SpringBoot启动控制台的banner是怎么回事

2023-11-07

前言

每次启动SpringBoot项目时,总是能看到控制台打印了一串字符,隐约能辨认出是“Spring”,不知大家是否也好奇过是怎么实现的,是直接打印固定的字符串,还是根据什么算法去生成的?于是闲暇无事,探究一番。

只想修改banner可以跳到文末查看

SpringBoot是怎么打印的

Banner默认实现类 SpringBootBanner

1、根据控制台打印的字符进行全局搜索,笔者选取:: Spring Boot ::进行搜索,定位到了org.springframework.boot.SpringBootBanner

IDEA全局搜索:CTRL + SHIFT + R

2、进入SpringBootBanner类,先看下注释Default Banner implementation which writes the 'Spring' banner.,说了两个信息:1、当前类是SpringBoot Banner的默认实现;2、打印的字符是“Spring”。

3、往下看,SpringBootBanner实现了Banner接口。Banner包括printBanner方法和枚举Mode
根据Mode中的注释和枚举值可以看出,Banner有三种状态:关闭、打印到控制台、打印到日志。具体使用场景留待后续分析。

Banner源码

4、往下看到类的属性BANNERSPRING_BOOT,也能辨认出是控制台打印的那些字符。

类里面只有一个方法printBanner,负责打印Banner字符。逻辑比较清晰,第一部分逐行打印BANNER形成图案;第二部分打印SpringBoot版本号,总长度由STRAP_LINE_SIZE控制。

SpringBootBanner完整代码

Banner核心控制类 SpringApplicationBannerPrinter

1、上节找到了负责存储和打印Banner字符的类SpringBootBanner,现在向调用链上方继续寻找,通过CTRL + B或者全局搜索可以发现SpringBootBannerSpringApplicationBannerPrinter类中作为类变量,大概能猜测出这个SpringApplicationBannerPrinter类是Banner打印的核心控制器。


2、进入SpringApplicationBannerPrinter类,照例先看注释Class used by SpringApplication to print the application banner.,意思是当前类被SpringApplication用来打印banner。

这个SpringApplication好像有点眼熟,名字和我们SpringBoot项目的启动类有点相似,翻翻启动类的代码,想起我们就是通过SpringApplicationrun方法启动项目,banner打印调用也是由SpringApplication控制的,后续会详细分析。(占坑,后续SpringBoot启动流程也会出一篇博客去探讨一下)

回归正题,继续从类的属性开始看,根据名字猜测大概含义,留待后续验证:

  1. BANNER_LOCATION_PROPERTY:Spring配置,大概是banner文件的路径。
  2. BANNER_IMAGE_LOCATION_PROPERTY:Spring配置,banner图片的路径(存疑,控制台难道能打印图片?)。
  3. DEFAULT_BANNER_LOCATION = "banner.txt":取值是txt文件,猜测是banner文件的默认位置。
  4. String[] IMAGE_EXTENSION = { "gif", "jpg", "png" }:取值是常见图片的后缀,结合第二个属性猜测是用来对banner图片类型做限制。
  5. DEFAULT_BANNER = new SpringBootBanner():把上节分析的SpringBootBanner当做Banner默认实现类
  6. ResourceLoader resourceLoaderResourceLoader简单来说是Spring加载资源的统一抽象,由实现类提供具体逻辑。
    在Spring中读取xml配置文件加载应用上下文的ClassPathXmlApplicationContext,就是ResourceLoader的子类。
  7. Banner fallbackBanner:翻译过来是回退banner,暂时猜不出作用,等待后续填坑。
     

3、往下看方法,只有两个非私有方法,都是print的重载方法,差别在于第三个参数,分别是Log loggerPrintStream out,代表这两个方法分别负责日志打印和控制台打印。

紧扣主题,先看负责控制台打印的方法。

 
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}

代码很精简,第一行获取Banner类,第二行调用Bannerprint方法打印banner图案,最后生成PrintedBanner并返回。


1. getBanner

getBanner源码

查看getBanner方法,首先创建Banners,底层就是Banner数组,由于存在控制台、日志两种打印方式,使用此类方便批量处理。

Banners源码

接着就是调用getImageBannergetTextBanner方法获取Banner,如果Banner数组不为空则返回,否则检查fallbackBanner

这个fallbackBanner光看名字看不出是什么,使用CTRL+B查看引用,发现是在SpringApplication#printBanner里注入进来的,如下图。

继续查找this.banner会发现,最终Banner只能通过SpringApplicationBuilder#banner注入。

SpringApplicationBuilder是通过Constructor(构造器)模式实现的SpringApplication构造器。
查看banner方法的注释,我们可以知道这里注入的Banner实例会在没有静态banner文件时使用
回过头来,fallbackBanner的坑填上了,它是在SpringApplicationBannerPrinter找不到txt文件或者图片作为banner素材的时候使用。

如果fallbackBanner也为空,则最终返回兜底方案-SpringBootBanner

getBanner的结构分析完了,实际情况我们知道走的是兜底方案,也就是只要我们能让getImageBannergetTextBanner或者fallbackBanner不为空,就能改变banner打印的图案。
带着这个想法,我们就去看看getImageBannergetTextBanner是咋回事。


2、getImageBanner
查看源码,首先environment.getProperty读取配置spring.banner.image.location获取图片位置。

配置文件读取若为空则遍历图片后缀数组IMAGE_EXTENSION,采用"banner." + ext拼接方式得到图片相对路径,并尝试加载。加载成功后会生成ImageBanner并返回。

接收图片资源并处理打印的逻辑都封装在ImageBanner中,后续单独写一篇文章尝试分析图片打印逻辑。

按照我们的分析,只要在配置文件中添加spring.banner.image.location并赋值正确的图片路径,或者在resources目录下存放一张名字为“banner”、后缀是gif,jpg, png其中之一的图片,SpringApplicationBannerPrinter就会打印出来。
注: 为什么没加前缀classpath:也可以放在resources目录下,可以查看DefaultResourceLoader#getResource对于banner.jpg这种location的处理逻辑。

后续章节会有打印效果。

getImageBanner源码


3、getTextBanner
查看源码,同样是先从配置文件中读取banner文件的location并尝试加载资源,和getImageBanner不同的是,这里读取不到会使用默认值banner.txt

加载资源后有一个Resource的限制条件!resource.getURL().toExternalForm().contains("liquibase-core"),这里不明白这个条件的含义,只查询到了Liquibase是一个用于跟踪、管理和应用数据库变化的开源工具。

资源校验通过后生成ResourceBanner并返回。

getTextBanner源码

接下来进入ResourceBanner看下打印细节。
printBanner结构比较简单,第一部分设置banner字符集,优先读取配置spring.banner.charset,无配置则默认设置为UTF-8
第二部分去解析banner字符,比如将${xxx}占位符解析成实际的值。
第三部分就是调用流打印输出。

ResourceBanner#printBanner

banner打印调用方-SpringApplication

上节看完SpringApplicationBannerPrinter,这节来寻找打印banner的调用方。

CTRL+B查看SpringApplicationBannerPrinter#print的引用,定位到了SpringApplication#printBanner。源码如下。

从整体结构来看,printBanner方法根据this.bannerMode取值不同,执行不同的打印策略:不打印、打印到日志、打印到控制台。

那么这个bannerMode是怎么设置的?查看初始化的代码,默认值是CONSOLE


继续寻找,最终定位到了SpringApplicationBuilder#bannerMode,意味着bannerMode只能通过构造器进行注入。

继续寻找printBanner的调用方,定位到了SpringApplication#run(String...)

上面有提到过,通常我们SpringBoot项目都是去调用SpringApplication#run(Class<?>, String...)去启动项目,底层是通过new关键字创建SpringApplication对象,最后调用SpringApplication#run(String...)完成一系列的资源初始化。

所以这就可以解释大多数情况下,我们的SpringBoot项目启动时都会打印那个默认的“Spring”字符。

SpringApplication#printBanner源码

如何修改项目启动的banner

修改banner打印策略

经上分析,banner打印策略包括控制台日志不打印

1. 隐式
默认策略是控制台,只需大多数情况一样,项目启动类通过SpringApplication.run(DistinctAppUserServiceApplication.class, args);启动,无需指定。

2. 显式注入
通过SpringApplicationBuilder构造器显式注入banner打印策略。

 
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(DemoApplication.class)
// Banner.Mode.LOG 打印到日志
// Banner.Mode.OFF 不打印
.bannerMode(Banner.Mode.CONSOLE)
.run(args);
}
}

打印效果
打印到控制台

打印到日志:INFO级别

修改banner内容

文本

方式一:在src/main/resources下新建banner.txt,里面放入想要打印的内容即可。

方式二:修改配置文件

spring:
banner:
location: file/bannerText.txt #文件位置 src/main/resources/file/bannerText.txt

内容生成网站
文字转换成符号:Text to ASCII Art Generator (TAAG)
                           http://life.chacuo.net/convertfont2char
图片转换成符号:Ascii艺术字实现个性化Spring Boot启动banner图案,轻松修改更换banner.txt文件内容,收集了丰富的banner艺术字和图,并且支持中文banner下载,让你的banner好玩儿更有意思。-bootschool.net

图片

和文本方式相同,但是图片类型有限制,只能是以下三种gif,、jpg、png
方式一:在src/main/resources下新建banner.png,里面放入想要打印的内容即可。

方式二:修改配置文件

 
spring:
banner:
image:
location: file/bannerImage.png #文件位置 src/main/resources/file/bannerImage.png

打印效果

后语

本篇文章干货不多,主要记录探究问题的心路历程,锻炼文笔,若观看文章过程有任何不适,敬请斧正。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot启动控制台的banner是怎么回事 的相关文章

  • Java EE:如何获取我的应用程序的 URL?

    在 Java EE 中 如何动态检索应用程序的完整 URL 例如 如果 URL 是 localhost 8080 myapplication 我想要一个可以简单地将其作为字符串或其他形式返回给我的方法 我正在运行 GlassFish 作为应
  • 给定两个 SSH2 密钥,我如何检查它们是否属于 Java 中的同一密钥对?

    我正在尝试找到一种方法来验证两个 SSH2 密钥 一个私有密钥和一个公共密钥 是否属于同一密钥对 我用过JSch http www jcraft com jsch 用于加载和解析私钥 更新 可以显示如何从私钥 SSH2 RSA 重新生成公钥
  • 制作一个交互式Windows服务

    我希望我的 Java 应用程序成为交互式 Windows 服务 用户登录时具有 GUI 的 Windows 服务 我搜索了这个 我发现这样做的方法是有两个程序 第一个是服务 第二个是 GUI 程序并使它们进行通信 服务将从 GUI 程序获取
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • 反射找不到对象子类型

    我试图通过使用反射来获取包中的所有类 当我使用具体类的代码 本例中为 A 时 它可以工作并打印子类信息 B 扩展 A 因此它打印 B 信息 但是当我将它与对象类一起使用时 它不起作用 我该如何修复它 这段代码的工作原理 Reflection
  • 操作错误不会显示在 JSP 上

    我尝试在 Action 类中添加操作错误并将其打印在 JSP 页面上 当发生异常时 它将进入 catch 块并在控制台中打印 插入异常时出错 请联系管理员 在 catch 块中 我添加了它addActionError 我尝试在jsp页面中打
  • 磁模拟

    假设我在 n m 像素的 2D 表面上有 p 个节点 我希望这些节点相互吸引 使得它们相距越远吸引力就越强 但是 如果两个节点之间的距离 比如 d A B 小于某个阈值 比如 k 那么它们就会开始排斥 谁能让我开始编写一些关于如何随时间更新
  • 斯坦福 NLP - 处理文件列表时 OpenIE 内存不足

    我正在尝试使用斯坦福 CoreNLP 中的 OpenIE 工具从多个文件中提取信息 当多个文件 而不是一个 传递到输入时 它会给出内存不足错误 All files have been queued awaiting termination
  • 十进制到八进制的转换[重复]

    这个问题在这里已经有答案了 可能的重复 十进制转换错误 https stackoverflow com questions 13142977 decimal conversion error 我正在为一个类编写一个程序 并且在计算如何将八进
  • 如何为俚语和表情符号构建正则表达式 (regex)

    我需要构建一个正则表达式来匹配俚语 即 lol lmao imo 等 和表情符号 即 P 等 我按照以下示例进行操作http www coderanch com t 497238 java java Regular Expression D
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 如何从指定日期获取上周五的日期? [复制]

    这个问题在这里已经有答案了 如何找出上一个 上一个 星期五 或指定日期的任何其他日期的日期 public getDateOnDay Date date String dayName 我不会给出答案 先自己尝试一下 但是 也许这些提示可以帮助
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两
  • Spring Boot @ConfigurationProperties 不从环境中检索属性

    我正在使用 Spring Boot 1 2 1 并尝试创建一个 ConfigurationProperties带有验证的bean 如下所示 package com sampleapp import java net URL import j

随机推荐

  • Makefile中使用Shell

    http blog csdn net zdl1016 article details 6448989 http blog csdn net absurd article details 636418 Makefile与Shell的问题 大概
  • FPGA程序上板调试问题

    FPGA程序上板调试问题 1 memory block 找不到coe文件 原因1 coe文件格式有问题 memory initialization radix 是数值格式 memory initialization vector 是初始化的
  • Neuronal Dynamics:第五章笔记

    Neuronal Dynamics 第五章笔记
  • 一文读懂C++的if与else判断语句

    说个明白 先从最简单的说起 if语句 if语句是C 中最简单的判断语句 if S 语句1 语句2 如果S为真 非零 则执行语句1 否则执行语句2 如果语句1就是单个语句 则可以这么写 if S 语句1 这里的分号不能少 或者这么写 if S
  • DevExpress v15.2.4帮助文档下载(全)

    原文地址 http www devexpresscn com Resources Documentation 498 html DevExpress v15 2帮助文档下载列表大全来啦 包含 net系列所有帮助文档 提供CHM和PDF两个版
  • 一个产品的风险预测怎么写_创业计划书中,项目风险评估怎么写?

    展开全部 首先需要了解在实施过程中我们可能碰到哪些风险 按照一般意义 我们常常所说的风险分为两大e69da5e6ba903231313335323631343130323136353331333436316264类 一种是不可预知的 一种是
  • STM32CubeMX定时器输出比较模式——输出相位可调矩形波

    1 介绍 STM32的定时器通道输出矩形波 可以使用PWM模式和输出比较模式 PWM模式能够产生频率和占空比可调的矩形波信号 但不能对信号的相位进行调节 使用输出比较模式 可以实现信号的相位调节和频率调节 但不能对信号的占空比进行调节 输出
  • 100天精通Python(基础篇)——第30天:数据容器-list列表索引

    从前往后索引 name list 0 name list 123 666 print name list 0 print name list 1 print type name list 从后向前索引 name list 1 name li
  • 【数据结构与算法】1.树、二叉树、字典树、红黑树

    文章目录 简介 1 树 Tree 2 二叉树 Binary Tree 2 1 二叉树数据结构 2 2 二叉树的三种遍历方式 3 二叉查找树 Binary Search Tree 3 1 二叉查找树的概念和定义 3 2 二分查找算法 4 字典
  • 手动安装Python第三库vtk库

    我们在使用py进行可视化操作时大概率会用到vtk库 一般方法是通过pip 安装 但是这玩意得看人品 本人人品十分不好 哈哈哈哈 所以我们就要自己下载轮子手动安装 附上第三方库地址 https www lfd uci edu gohlke p
  • 真实!大概五分之一的年轻人存款在一万元以内。

    近日 有调查称 大概五分之一的年轻人存款在一万元以内 10万元存款是一个 坎 存款超过10万就会超过53 7 的人 年轻人 存款 两个词碰撞在一起 引来了广泛的关注和讨论 你认为年轻人存款难吗 可以从以下几个角度发表你的看法 角度一 你的目
  • 计算机应用问题,计算机应用的现状与发展的问题

    计算机应用的现状与发展的问题 来源 职称阁时间 2018 07 27 11 59热度 这篇论文主要介绍的是计算机应用的现状与发展的问题的相关内容 本文作者就是通过对计算机的应用现状等内容做出详细的阐述与介绍 特推荐这篇优秀的论文供相关人士参
  • 【C++设计模式】依赖倒转原则

    2023年8月30日 周三上午 目录 概述 含义 举个简单的例子 传统做法 使用依赖倒转原则 代码说明 再举一个具体的例子 以生活为例 概述 依赖倒转原则 Dependency Inversion Principle DIP 是面向对象设计
  • Hadoop 端口

    1 系统 8080 80 用于tomcat和apache的端口 22 ssh的端口 2 Web UI 用于访问和监控Hadoop系统运行状态 Daemon 缺省端口 配置参数 HDFS Namenode 50070 dfs http add
  • Shiro源码分析-----认证流程/授权流程----------Subject

    本文转载自 认证流程和授权流程 源码分析的第二篇以Subject的初始化为题 一 回顾Apache Shiro创建Subject的步骤如下 1 获取SecurityManager工厂 此处使用Ini配置文件初始化SecurityManage
  • csgo删除服务器地图在哪个文件夹,CSGO de_cache地图

    资源说明 CSGO de cache地图 由 Volcano 制作 需要的玩家不要错过哦 资源详情 Sal Volcano Garozzo发布了其最新制作的CS GO地图de cache Volcano制作的首个地图de nuke ve 相
  • Java请求合并与分而治之

    在系统设计的时候 你是否也遇到过这两个问题 1 大量请求造成数据库压力过大 2 大量数据库查询造成请求执行时间过长 本文将介绍在高并发 大数据环境下 以上两种问题的应对思路 一 请求合并 首先思考一个场景 在高并发的系统中 在每秒内有大量的
  • 全连接神经网络单层模型原理

    全连接神经网络单层模型原理 前言 单层MLP 1 前向传播 2 激活函数 2 1 Sigmoid函数 2 2 tanh函数 2 3 ReLu函数 2 4 Leaky ReLu函数 3 损失函数 4 梯度下降 前言 深度学习是学习样本数据的内
  • ​少儿机器人编程与三大主课关系

    说到机器人编程 在常规概念里 很难将其和三大主课联系到一起 回首过去 国家并不提倡未成年人去学专业技术含量较高的机器人编程教育的 格物斯坦提示 但随着现在老龄化的社会现象的出现 国家需要大批有志少年用高科技方式去改造社会 这就要精通机器人编
  • SpringBoot启动控制台的banner是怎么回事

    前言 每次启动SpringBoot项目时 总是能看到控制台打印了一串字符 隐约能辨认出是 Spring 不知大家是否也好奇过是怎么实现的 是直接打印固定的字符串 还是根据什么算法去生成的 于是闲暇无事 探究一番 只想修改banner可以跳到