前言
每次啟動(dòng)SpringBoot項(xiàng)目時(shí),總是能看到控制臺(tái)打印了一串字符,隱約能辨認(rèn)出是“Spring”,不知大家是否也好奇過(guò)是怎么實(shí)現(xiàn)的,是直接打印固定的字符串,還是根據(jù)什么算法去生成的?于是閑暇無(wú)事,探究一番。
只想修改banner可以跳到文末查看SpringBoot是怎么打印的 Banner默認(rèn)實(shí)現(xiàn)類(lèi) SpringBootBanner
1、根據(jù)控制臺(tái)打印的字符進(jìn)行全局搜索,筆者選取:: Spring Boot ::進(jìn)行搜索,定位到了org.springframework.boot.SpringBootBanner。
IDEA全局搜索:CTRL + SHIFT + R
2、進(jìn)入SpringBootBanner類(lèi),先看下注釋Default Banner implementation which writes the 'Spring' banner.,說(shuō)了兩個(gè)信息:1、當(dāng)前類(lèi)是SpringBoot Banner的默認(rèn)實(shí)現(xiàn);2、打印的字符是“Spring”。
3、往下看,SpringBootBanner實(shí)現(xiàn)了Banner接口。Banner包括printBanner方法和枚舉Mode。
根據(jù)Mode中的注釋和枚舉值可以看出,Banner有三種狀態(tài):關(guān)閉、打印到控制臺(tái)、打印到日志。具體使用場(chǎng)景留待后續(xù)分析。
Banner源碼
4、往下看到類(lèi)的屬性BANNER和SPRING_BOOT,也能辨認(rèn)出是控制臺(tái)打印的那些字符。
類(lèi)里面只有一個(gè)方法printBanner,負(fù)責(zé)打印Banner字符。邏輯比較清晰,第一部分逐行打印BANNER形成圖案;第二部分打印SpringBoot版本號(hào),總長(zhǎng)度由STRAP_LINE_SIZE控制。
SpringBootBanner完整代碼
Banner核心控制類(lèi) SpringApplicationBannerPrinter
1、上節(jié)找到了負(fù)責(zé)存儲(chǔ)和打印Banner字符的類(lèi)SpringBootBanner,現(xiàn)在向調(diào)用鏈上方繼續(xù)尋找,通過(guò)CTRL + B或者全局搜索可以發(fā)現(xiàn)SpringBootBanner在SpringApplicationBannerPrinter類(lèi)中作為類(lèi)變量,大概能猜測(cè)出這個(gè)SpringApplicationBannerPrinter類(lèi)是Banner打印的核心控制器。
2、進(jìn)入SpringApplicationBannerPrinter類(lèi),照例先看注釋Class used by SpringApplication to print the application banner.,意思是當(dāng)前類(lèi)被SpringApplication用來(lái)打印banner。
這個(gè)SpringApplication好像有點(diǎn)眼熟,名字和我們SpringBoot項(xiàng)目的啟動(dòng)類(lèi)有點(diǎn)相似,翻翻啟動(dòng)類(lèi)的代碼,想起我們就是通過(guò)SpringApplication的run方法啟動(dòng)項(xiàng)目,banner打印調(diào)用也是由SpringApplication控制的,后續(xù)會(huì)詳細(xì)分析。(占坑,后續(xù)SpringBoot啟動(dòng)流程也會(huì)出一篇博客去探討一下)
回歸正題,繼續(xù)從類(lèi)的屬性開(kāi)始看,根據(jù)名字猜測(cè)大概含義,留待后續(xù)驗(yàn)證:
- BANNER_LOCATION_PROPERTY:Spring配置,大概是banner文件的路徑。
- BANNER_IMAGE_LOCATION_PROPERTY:Spring配置,banner圖片的路徑(存疑,控制臺(tái)難道能打印圖片?)。
- DEFAULT_BANNER_LOCATION = "banner.txt":取值是txt文件,猜測(cè)是banner文件的默認(rèn)位置。
- String[] IMAGE_EXTENSION = { "gif", "jpg", "png" }:取值是常見(jiàn)圖片的后綴,結(jié)合第二個(gè)屬性猜測(cè)是用來(lái)對(duì)banner圖片類(lèi)型做限制。
- DEFAULT_BANNER = new SpringBootBanner():把上節(jié)分析的SpringBootBanner當(dāng)做Banner默認(rèn)實(shí)現(xiàn)類(lèi)
- ResourceLoader resourceLoader:ResourceLoader簡(jiǎn)單來(lái)說(shuō)是Spring加載資源的統(tǒng)一抽象,由實(shí)現(xiàn)類(lèi)提供具體邏輯。
在Spring中讀取xml配置文件加載應(yīng)用上下文的ClassPathXmlApplicationContext,就是ResourceLoader的子類(lèi)。 - Banner fallbackBanner:翻譯過(guò)來(lái)是回退banner,暫時(shí)猜不出作用,等待后續(xù)填坑。
3、往下看方法,只有兩個(gè)非私有方法,都是print的重載方法,差別在于第三個(gè)參數(shù),分別是Log logger和PrintStream out,代表這兩個(gè)方法分別負(fù)責(zé)日志打印和控制臺(tái)打印。
緊扣主題,先看負(fù)責(zé)控制臺(tái)打印的方法。
Banner print(Environment environment, Class sourceClass, PrintStream out) { Banner banner = getBanner(environment); banner.printBanner(environment, sourceClass, out); return new PrintedBanner(banner, sourceClass); }
代碼很精簡(jiǎn),第一行獲取Banner類(lèi),第二行調(diào)用Banner的print方法打印banner圖案,最后生成PrintedBanner并返回。
1. getBanner
getBanner源碼
查看getBanner方法,首先創(chuàng)建Banners,底層就是Banner數(shù)組,由于存在控制臺(tái)、日志兩種打印方式,使用此類(lèi)方便批量處理。
Banners源碼
接著就是調(diào)用getImageBanner和getTextBanner方法獲取Banner,如果Banner數(shù)組不為空則返回,否則檢查fallbackBanner。
這個(gè)fallbackBanner光看名字看不出是什么,使用CTRL+B查看引用,發(fā)現(xiàn)是在SpringApplication#printBanner里注入進(jìn)來(lái)的,如下圖。
繼續(xù)查找this.banner會(huì)發(fā)現(xiàn),最終Banner只能通過(guò)SpringApplicationBuilder#banner注入。
SpringApplicationBuilder是通過(guò)Constructor(構(gòu)造器)模式實(shí)現(xiàn)的SpringApplication構(gòu)造器。
查看banner方法的注釋?zhuān)覀兛梢灾肋@里注入的Banner實(shí)例會(huì)在沒(méi)有靜態(tài)banner文件時(shí)使用。
回過(guò)頭來(lái),fallbackBanner的坑填上了,它是在SpringApplicationBannerPrinter找不到txt文件或者圖片作為banner素材的時(shí)候使用。
如果fallbackBanner也為空,則最終返回兜底方案-SpringBootBanner。
getBanner的結(jié)構(gòu)分析完了,實(shí)際情況我們知道走的是兜底方案,也就是只要我們能讓getImageBanner、getTextBanner或者fallbackBanner不為空,就能改變banner打印的圖案。
帶著這個(gè)想法,我們就去看看getImageBanner和getTextBanner是咋回事。
2、getImageBanner
查看源碼,首先environment.getProperty讀取配置spring.banner.image.location獲取圖片位置。
配置文件讀取若為空則遍歷圖片后綴數(shù)組IMAGE_EXTENSION,采用"banner." + ext拼接方式得到圖片相對(duì)路徑,并嘗試加載。加載成功后會(huì)生成ImageBanner并返回。
接收?qǐng)D片資源并處理打印的邏輯都封裝在ImageBanner中,后續(xù)單獨(dú)寫(xiě)一篇文章嘗試分析圖片打印邏輯。
按照我們的分析,只要在配置文件中添加spring.banner.image.location并賦值正確的圖片路徑,或者在resources目錄下存放一張名字為“banner”、后綴是gif,jpg, png其中之一的圖片,SpringApplicationBannerPrinter就會(huì)打印出來(lái)。
注: 為什么沒(méi)加前綴classpath:也可以放在resources目錄下,可以查看DefaultResourceLoader#getResource對(duì)于banner.jpg這種location的處理邏輯。
后續(xù)章節(jié)會(huì)有打印效果。
getImageBanner源碼
3、getTextBanner
查看源碼,同樣是先從配置文件中讀取banner文件的location并嘗試加載資源,和getImageBanner不同的是,這里讀取不到會(huì)使用默認(rèn)值banner.txt。
加載資源后有一個(gè)Resource的限制條件!resource.getURL().toExternalForm().contains("liquibase-core"),這里不明白這個(gè)條件的含義,只查詢到了Liquibase是一個(gè)用于跟蹤、管理和應(yīng)用數(shù)據(jù)庫(kù)變化的開(kāi)源工具。
資源校驗(yàn)通過(guò)后生成ResourceBanner并返回。
getTextBanner源碼
接下來(lái)進(jìn)入ResourceBanner看下打印細(xì)節(jié)。
printBanner結(jié)構(gòu)比較簡(jiǎn)單,第一部分設(shè)置banner字符集,優(yōu)先讀取配置spring.banner.charset,無(wú)配置則默認(rèn)設(shè)置為UTF-8。
第二部分去解析banner字符,比如將${xxx}占位符解析成實(shí)際的值。
第三部分就是調(diào)用流打印輸出。
ResourceBanner#printBanner
banner打印調(diào)用方-SpringApplication
上節(jié)看完SpringApplicationBannerPrinter,這節(jié)來(lái)尋找打印banner的調(diào)用方。
CTRL+B查看SpringApplicationBannerPrinter#print的引用,定位到了SpringApplication#printBanner。源碼如下。
從整體結(jié)構(gòu)來(lái)看,printBanner方法根據(jù)this.bannerMode取值不同,執(zhí)行不同的打印策略:不打印、打印到日志、打印到控制臺(tái)。
那么這個(gè)bannerMode是怎么設(shè)置的?查看初始化的代碼,默認(rèn)值是CONSOLE。 繼續(xù)尋找,最終定位到了SpringApplicationBuilder#bannerMode,意味著bannerMode只能通過(guò)構(gòu)造器進(jìn)行注入。
繼續(xù)尋找printBanner的調(diào)用方,定位到了SpringApplication#run(String...)。
上面有提到過(guò),通常我們SpringBoot項(xiàng)目都是去調(diào)用SpringApplication#run(Class, String...)去啟動(dòng)項(xiàng)目,底層是通過(guò)new關(guān)鍵字創(chuàng)建SpringApplication對(duì)象,最后調(diào)用SpringApplication#run(String...)完成一系列的資源初始化。
所以這就可以解釋大多數(shù)情況下,我們的SpringBoot項(xiàng)目啟動(dòng)時(shí)都會(huì)打印那個(gè)默認(rèn)的“Spring”字符。
SpringApplication#printBanner源碼
如何修改項(xiàng)目啟動(dòng)的banner 修改banner打印策略
經(jīng)上分析,banner打印策略包括控制臺(tái)、日志、不打印。
1. 隱式
默認(rèn)策略是控制臺(tái),只需大多數(shù)情況一樣,項(xiàng)目啟動(dòng)類(lèi)通過(guò)SpringApplication.run(DistinctAppUserServiceApplication.class, args);啟動(dòng),無(wú)需指定。
2. 顯式注入
通過(guò)SpringApplicationBuilder構(gòu)造器顯式注入banner打印策略。
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { new SpringApplicationBuilder(DemoApplication.class) // Banner.Mode.LOG 打印到日志 // Banner.Mode.OFF 不打印 .bannerMode(Banner.Mode.CONSOLE) .run(args); }}
打印效果
打印到控制臺(tái)
打印到日志:INFO級(jí)別
修改banner內(nèi)容 文本
方式一:在src/main/resources下新建banner.txt,里面放入想要打印的內(nèi)容即可。
方式二:修改配置文件
spring: banner: location: file/bannerText.txt #文件位置 src/main/resources/file/bannerText.txt
圖片
和文本方式相同,但是圖片類(lèi)型有限制,只能是以下三種gif,、jpg、png。
方式一:在src/main/resources下新建banner.png,里面放入想要打印的內(nèi)容即可。
方式二:修改配置文件
spring: banner: image: location: file/bannerImage.png #文件位置 src/main/resources/file/bannerImage.png
打印效果