前言
每次启动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、往下看到类的属性BANNER
和SPRING_BOOT
,也能辨认出是控制台打印的那些字符。
类里面只有一个方法printBanner
,负责打印Banner字符。逻辑比较清晰,第一部分逐行打印BANNER
形成图案;第二部分打印SpringBoot版本号,总长度由STRAP_LINE_SIZE
控制。
SpringBootBanner完整代码
Banner核心控制类 SpringApplicationBannerPrinter
1、上节找到了负责存储和打印Banner字符的类SpringBootBanner
,现在向调用链上方继续寻找,通过CTRL + B
或者全局搜索可以发现SpringBootBanner
在SpringApplicationBannerPrinter
类中作为类变量,大概能猜测出这个SpringApplicationBannerPrinter
类是Banner打印的核心控制器。
2、进入SpringApplicationBannerPrinter
类,照例先看注释Class used by SpringApplication to print the application banner.
,意思是当前类被SpringApplication
用来打印banner。
这个SpringApplication
好像有点眼熟,名字和我们SpringBoot项目的启动类有点相似,翻翻启动类的代码,想起我们就是通过SpringApplication
的run
方法启动项目,banner打印调用也是由SpringApplication
控制的,后续会详细分析。(占坑,后续SpringBoot启动流程也会出一篇博客去探讨一下)
回归正题,继续从类的属性开始看,根据名字猜测大概含义,留待后续验证:
BANNER_LOCATION_PROPERTY
:Spring配置,大概是banner文件的路径。BANNER_IMAGE_LOCATION_PROPERTY
:Spring配置,banner图片的路径(存疑,控制台难道能打印图片?)。DEFAULT_BANNER_LOCATION = "banner.txt"
:取值是txt文件,猜测是banner文件的默认位置。String[] IMAGE_EXTENSION = { "gif", "jpg", "png" }
:取值是常见图片的后缀,结合第二个属性猜测是用来对banner图片类型做限制。DEFAULT_BANNER = new SpringBootBanner()
:把上节分析的SpringBootBanner
当做Banner默认实现类ResourceLoader resourceLoader
:ResourceLoader
简单来说是Spring加载资源的统一抽象,由实现类提供具体逻辑。
在Spring中读取xml配置文件加载应用上下文的ClassPathXmlApplicationContext
,就是ResourceLoader
的子类。Banner fallbackBanner
:翻译过来是回退banner
,暂时猜不出作用,等待后续填坑。
3、往下看方法,只有两个非私有方法,都是print
的重载方法,差别在于第三个参数,分别是Log logger
和PrintStream out
,代表这两个方法分别负责日志打印和控制台打印。
紧扣主题,先看负责控制台打印的方法。
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) { | |
Banner banner = getBanner(environment); | |
banner.printBanner(environment, sourceClass, out); | |
return new PrintedBanner(banner, sourceClass); | |
} |
代码很精简,第一行获取Banner
类,第二行调用Banner
的print
方法打印banner图案,最后生成PrintedBanner
并返回。
1. getBanner
getBanner源码
查看getBanner
方法,首先创建Banners
,底层就是Banner
数组,由于存在控制台、日志两种打印方式,使用此类方便批量处理。
Banners源码
接着就是调用getImageBanner
和getTextBanner
方法获取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
的结构分析完了,实际情况我们知道走的是兜底方案,也就是只要我们能让getImageBanner
、getTextBanner
或者fallbackBanner
不为空,就能改变banner打印的图案。
带着这个想法,我们就去看看getImageBanner
和getTextBanner
是咋回事。
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 |
打印效果
后语
本篇文章干货不多,主要记录探究问题的心路历程,锻炼文笔,若观看文章过程有任何不适,敬请斧正。