前言
每次啟動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打印策略。
@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); }}
打印效果
打印到控制臺
打印到日志:INFO級別
修改banner內容 文本
方式一:在src/main/resources下新建banner.txt,里面放入想要打印的內容即可。
方式二:修改配置文件
spring: banner: location: file/bannerText.txt #文件位置 src/main/resources/file/bannerText.txt
圖片
和文本方式相同,但是圖片類型有限制,只能是以下三種gif,、jpg、png。
方式一:在src/main/resources下新建banner.png,里面放入想要打印的內容即可。
方式二:修改配置文件
spring: banner: image: location: file/bannerImage.png #文件位置 src/main/resources/file/bannerImage.png
打印效果