前言 每次啟動SpringBoot項目時,總是能看到控制台列印了一串字元,隱約能辨認出是“Spring”,不知大家是否也好奇過是怎麼實現的,是直接列印固定的字元串,還是根據什麼演算法去生成的?於是閑暇無事,探究一番。 只想修改banner可以跳到文末查看 SpringBoot是怎麼列印的 Banner ...
前言
每次啟動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源碼
/**
* Interface class for writing a banner programmatically.
*/
@FunctionalInterface
public interface Banner {
/**
* Print the banner to the specified print stream.
* @param environment the spring environment
* @param sourceClass the source class for the application
* @param out the output print stream
*/
void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);
/**
* An enumeration of possible values for configuring the Banner.
*/
enum Mode {
/**
* Disable printing of the banner.
*/
OFF,
/**
* Print the banner to System.out.
*/
CONSOLE,
/**
* Print the banner to the log file.
*/
LOG
}
}
4、往下看到類的屬性BANNER
和SPRING_BOOT
,也能辨認出是控制台列印的那些字元。
類裡面只有一個方法printBanner
,負責列印Banner字元。邏輯比較清晰,第一部分逐行列印BANNER
形成圖案;第二部分列印SpringBoot版本號,總長度由STRAP_LINE_SIZE
控制。
SpringBootBanner完整代碼
/**
* Default Banner implementation which writes the 'Spring' banner.
*/
class SpringBootBanner implements Banner {
private static final String[] BANNER = { "", " . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),
AnsiStyle.FAINT, version));
printStream.println();
}
}
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源碼
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment));
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
查看getBanner
方法,首先創建Banners
,底層就是Banner
數組,由於存在控制台、日誌兩種列印方式,使用此類方便批量處理。
Banners源碼
/**
* {@link Banner} comprised of other {@link Banner Banners}.
*/
private static class Banners implements Banner {
private final List<Banner> banners = new ArrayList<>();
void addIfNotNull(Banner banner) {
if (banner != null) {
this.banners.add(banner);
}
}
boolean hasAtLeastOneBanner() {
return !this.banners.isEmpty();
}
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
for (Banner banner : this.banners) {
banner.printBanner(environment, sourceClass, out);
}
}
接著就是調用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源碼
private Banner getImageBanner(Environment environment) {
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : null;
}
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
3、getTextBanner
查看源碼,同樣是先從配置文件中讀取banner文件的location並嘗試載入資源,和getImageBanner
不同的是,這裡讀取不到會使用預設值banner.txt
。
載入資源後有一個Resource
的限制條件!resource.getURL().toExternalForm().contains("liquibase-core")
,這裡不明白這個條件的含義,只查詢到了Liquibase
是一個用於跟蹤、管理和應用資料庫變化的開源工具。
資源校驗通過後生成ResourceBanner
並返回。
getTextBanner源碼
private Banner getTextBanner(Environment environment) {
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
Resource resource = this.resourceLoader.getResource(location);
try {
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
return new ResourceBanner(resource);
}
}
catch (IOException ex) {
// Ignore
}
return null;
}
接下來進入ResourceBanner
看下列印細節。
printBanner
結構比較簡單,第一部分設置banner字元集,優先讀取配置spring.banner.charset
,無配置則預設設置為UTF-8
。
第二部分去解析banner字元,比如將${xxx}
占位符解析成實際的值。
第三部分就是調用流列印輸出。
ResourceBanner#printBanner
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
try {
// 設置banner字元集
String banner = StreamUtils.copyToString(this.resource.getInputStream(),
environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));
// 解析banner
for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) {
banner = resolver.resolvePlaceholders(banner);
}
out.println(banner);
}
catch (Exception ex) {
logger.warn(LogMessage.format("Banner not printable: %s (%s: '%s')", this.resource, ex.getClass(),
ex.getMessage()), ex);
}
}
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源碼
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
如何修改項目啟動的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
內容生成網站
文字轉換成符號:http://patorjk.com/software/taag
http://life.chacuo.net/convertfont2char
圖片轉換成符號:https://www.bootschool.net/ascii-art
圖片
和文本方式相同,但是圖片類型有限制,只能是以下三種gif,、jpg、png
。
方式一:在src/main/resources
下新建banner.png
,裡面放入想要列印的內容即可。
方式二:修改配置文件
spring:
banner:
image:
location: file/bannerImage.png #文件位置 src/main/resources/file/bannerImage.png
列印效果
後語
本篇文章乾貨不多,主要記錄探究問題的心路歷程,鍛煉文筆,若觀看文章過程有任何不適,敬請斧正。
本文來自博客園,作者:冰兀朮,轉載請註明原文鏈接:https://www.cnblogs.com/gxy2825/p/17168863.html