SpringBoot打印Banner的實(shí)現(xiàn)示例
前言
在前文中,我們深入解析了SpringBoot啟動(dòng)時(shí)應(yīng)用環(huán)境的準(zhǔn)備過(guò)程。接下來(lái)將深入介紹啟動(dòng)Banner打印的具體實(shí)現(xiàn)及流程。
SpringBoot版本2.7.18SpringApplication的run方法的執(zhí)行邏輯如下,本文將詳細(xì)介紹第5小節(jié):打印啟動(dòng)Banner
// SpringApplication類(lèi)方法
public ConfigurableApplicationContext run(String... args) {
// 記錄應(yīng)用啟動(dòng)的開(kāi)始時(shí)間
long startTime = System.nanoTime();
// 1.創(chuàng)建引導(dǎo)上下文,用于管理應(yīng)用啟動(dòng)時(shí)的依賴(lài)和資源
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 配置無(wú)頭模式屬性,以支持在無(wú)圖形環(huán)境下運(yùn)行
// 將系統(tǒng)屬性 java.awt.headless 設(shè)置為 true
configureHeadlessProperty();
// 2.獲取Spring應(yīng)用啟動(dòng)監(jiān)聽(tīng)器,用于在應(yīng)用啟動(dòng)的各個(gè)階段執(zhí)行自定義邏輯
SpringApplicationRunListeners listeners = getRunListeners(args);
// 啟動(dòng)開(kāi)始方法(發(fā)布開(kāi)始事件、通知應(yīng)用監(jiān)聽(tīng)器ApplicationListener)
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 3.解析應(yīng)用參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 4.準(zhǔn)備應(yīng)用環(huán)境,包括讀取配置文件和設(shè)置環(huán)境變量
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置是否忽略 BeanInfo,以加快啟動(dòng)速度
configureIgnoreBeanInfo(environment);
// 5.打印啟動(dòng)Banner
Banner printedBanner = printBanner(environment);
// 6.創(chuàng)建應(yīng)用程序上下文
context = createApplicationContext();
// 設(shè)置應(yīng)用啟動(dòng)的上下文,用于監(jiān)控和管理啟動(dòng)過(guò)程
context.setApplicationStartup(this.applicationStartup);
// 7.準(zhǔn)備應(yīng)用上下文,包括加載配置、添加 Bean 等
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 8.刷新上下文,完成 Bean 的加載和依賴(lài)注入
refreshContext(context);
// 9.刷新后的一些操作,如事件發(fā)布等
afterRefresh(context, applicationArguments);
// 計(jì)算啟動(dòng)應(yīng)用程序的時(shí)間,并記錄日志
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 10.通知監(jiān)聽(tīng)器應(yīng)用啟動(dòng)完成
listeners.started(context, timeTakenToStartup);
// 11.調(diào)用應(yīng)用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便執(zhí)行自定義的啟動(dòng)邏輯
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 12.處理啟動(dòng)過(guò)程中發(fā)生的異常,并通知監(jiān)聽(tīng)器
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 13.計(jì)算應(yīng)用啟動(dòng)完成至準(zhǔn)備就緒的時(shí)間,并通知監(jiān)聽(tīng)器
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
// 處理準(zhǔn)備就緒過(guò)程中發(fā)生的異常
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 返回已啟動(dòng)并準(zhǔn)備就緒的應(yīng)用上下文
return context;
}
一、入口
// 5.打印啟動(dòng)Banner
Banner printedBanner = printBanner(environment);
// 打印啟動(dòng) Banner 的方法,根據(jù)配置的 Banner 模式選擇打印方式
private Banner printBanner(ConfigurableEnvironment environment) {
// 如果 Banner 模式被設(shè)置為 OFF,則不打印 Banner,直接返回 null
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
// 確定資源加載器。如果當(dāng)前實(shí)例的 resourceLoader 不為空,則使用它;否則創(chuàng)建一個(gè)默認(rèn)的資源加載器
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
// 創(chuàng)建 Banner 打印器,負(fù)責(zé)加載和打印 Banner
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
// 根據(jù) Banner 模式?jīng)Q定打印到日志還是控制臺(tái)
if (this.bannerMode == Mode.LOG) {
// 如果 Banner 模式為 LOG,則將 Banner 打印到日志中
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
// 默認(rèn)情況下(CONSOLE 模式),將 Banner 打印到標(biāo)準(zhǔn)輸出(控制臺(tái))
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
二、Banner接口類(lèi)
// 一個(gè)用于以編程方式輸出 Banner 的接口類(lèi)
@FunctionalInterface
public interface Banner {
// 將 Banner 輸出到指定的打印流
void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);
// 用于配置 Banner 的模式枚舉
enum Mode {
// 禁用 Banner 的打印
OFF,
// 將 Banner 輸出到 System.out
CONSOLE,
// 將 Banner 輸出到日志文件
LOG
}
}
1、打印Banner開(kāi)關(guān)
- 默認(rèn)情況是打印到
控制臺(tái)

- 可以通過(guò)
properties或yml設(shè)置關(guān)閉打印Banner
spring.main.banner-mode=off
上一節(jié)有講spring.main開(kāi)頭的屬性會(huì)綁定到SpringApplication對(duì)象上,這樣就可以通過(guò)配置文件的屬性來(lái)決定Banner的打印模式。
三、打印Banner過(guò)程
1、console和log模式
- console控制臺(tái)模式,默認(rèn)設(shè)置
// SpringApplicationBannerPrinter類(lèi)方法
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
// 根據(jù)提供的環(huán)境信息獲取橫幅。
Banner banner = getBanner(environment);
// 將橫幅打印到指定的輸出流中。
banner.printBanner(environment, sourceClass, out);
// 返回一個(gè) PrintedBanner 對(duì)象,包含打印的橫幅和源類(lèi)信息。
return new PrintedBanner(banner, sourceClass);
}
- log日志文件模式,通過(guò)在配置文件中設(shè)置
spring.main.banner-mode=log,可以將應(yīng)用啟動(dòng)Banner輸出到日志文件中
// SpringApplicationBannerPrinter類(lèi)方法
Banner print(Environment environment, Class<?> sourceClass, Log logger) {
// 根據(jù)提供的環(huán)境信息獲取橫幅。
Banner banner = getBanner(environment);
try {
logger.info(createStringFromBanner(banner, environment, sourceClass));
}
catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
// 返回一個(gè) PrintedBanner 對(duì)象,包含打印的橫幅和源類(lèi)信息。
return new PrintedBanner(banner, sourceClass);
}
- log模式就是獲取
打印流內(nèi)容轉(zhuǎn)換為字符串,然后由log日志打印罷了

兩種方式都會(huì)返回一個(gè)PrintedBanner對(duì)象,主要是為以后在應(yīng)用上下文中注冊(cè)為Bean或供其他組件使用做準(zhǔn)備。
2、四種Banner對(duì)象
- 圖片和文本橫幅組合的Banners
- 備用Banner
- 默認(rèn)Banner
// SpringApplicationBannerPrinter類(lèi)方法
// 默認(rèn)Banner
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
// 根據(jù)當(dāng)前環(huán)境獲取適當(dāng)?shù)臋M幅(Banner)
private Banner getBanner(Environment environment) {
// 創(chuàng)建一個(gè) Banners 對(duì)象,用于存儲(chǔ)圖片和文本橫幅
Banners banners = new Banners();
// 嘗試獲取圖片橫幅,并將其添加到 Banners 中(如果非空)
banners.addIfNotNull(getImageBanner(environment));
// 嘗試獲取文本橫幅,并將其添加到 Banners 中(如果非空)
banners.addIfNotNull(getTextBanner(environment));
// 如果至少包含一個(gè)橫幅,則返回組合的 Banners 對(duì)象
if (banners.hasAtLeastOneBanner()) {
return banners;
}
// 如果沒(méi)有任何橫幅但存在備用橫幅,則返回備用橫幅
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
// 如果沒(méi)有任何橫幅,則返回默認(rèn)橫幅
// Banner DEFAULT_BANNER = new SpringBootBanner();
return DEFAULT_BANNER;
}
Banners對(duì)象內(nèi)部持有多個(gè)Banner實(shí)現(xiàn)類(lèi),遍歷調(diào)用Banner的printBanner方法

2.1、圖片Banner
- 嘗試根據(jù)環(huán)境信息獲取圖片橫幅(Image Banner)
- 環(huán)境變量
spring.banner.image.location用于指定圖片路徑;如果未設(shè)置,則默認(rèn)加載路徑為banner.gif、banner.jpg或banner.png(按順序查找)。
private Banner getImageBanner(Environment environment) {
// 從環(huán)境變量中獲取橫幅圖片的路徑
// String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
// 如果路徑不為空,嘗試加載對(duì)應(yīng)的資源
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
// 如果資源存在,返回對(duì)應(yīng)的 ImageBanner 對(duì)象
return resource.exists() ? new ImageBanner(resource) : null;
}
// 如果未指定路徑,嘗試加載默認(rèn)圖片橫幅文件
// String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
// 如果找到存在的文件資源,返回對(duì)應(yīng)的 ImageBanner 對(duì)象
if (resource.exists()) {
return new ImageBanner(resource);
}
}
// 如果沒(méi)有找到任何圖片橫幅資源,返回 null
return null;
}
控制臺(tái)效果

2.2、文字Banner
- 嘗試根據(jù)環(huán)境信息獲取文本橫幅(Text Banner)
- 環(huán)境變量
spring.banner.location用于指定文本路徑;如果未設(shè)置,則默認(rèn)加載路徑為banner.txt。
private Banner getTextBanner(Environment environment) {
// 獲取橫幅的路徑,優(yōu)先使用環(huán)境變量中的配置,如果沒(méi)有配置則使用默認(rèn)路徑
// String BANNER_LOCATION_PROPERTY = "spring.banner.location";
// String DEFAULT_BANNER_LOCATION = "banner.txt";
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
// 使用 ResourceLoader 加載指定路徑的資源
Resource resource = this.resourceLoader.getResource(location);
try {
// 檢查資源是否存在,且路徑中不包含 "liquibase-core"(防止加載到不相關(guān)的資源)
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
// 如果資源有效,返回對(duì)應(yīng)的 ResourceBanner 對(duì)象
return new ResourceBanner(resource);
}
} catch (IOException ex) {
// 忽略異常,可能是資源加載時(shí)出錯(cuò)或路徑無(wú)效
// 在這里不拋出異常,而是返回 null,表示沒(méi)有有效的橫幅資源
}
// 如果資源無(wú)效或發(fā)生異常,返回 null
return null;
}
控制臺(tái)效果

2.2、備用Banner
SpringApplicationBannerPrinter對(duì)象的備用Banner屬性fallbackBanner是由SpringApplication對(duì)象的私有屬性banner傳遞而來(lái)的。

- 可以在SpringBoot啟動(dòng)類(lèi)中通過(guò)調(diào)用
SpringApplication的setBanner方法直接設(shè)置自定義的Banner對(duì)象
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
// 設(shè)置全局備用橫幅
app.setBanner((environment, sourceClass, out) -> {
out.println("===== 全局備用橫幅 =====");
out.println(" 默認(rèn)橫幅已啟用 ");
out.println("=====================");
});
app.run(args);
}
}
控制臺(tái)效果

2.3、默認(rèn)Banner
如果未設(shè)置自定義文字圖片Banner或備用Banner,SpringBoot將使用默認(rèn)的啟動(dòng)橫幅(SpringBootBanner)作為顯示內(nèi)容。
// 默認(rèn)的 Banner 實(shí)現(xiàn),用于打印 "Spring" 的啟動(dòng)橫幅,和版本信息
class SpringBootBanner implements Banner {
// 預(yù)定義的 ASCII 藝術(shù)橫幅,每行為一個(gè)數(shù)組元素
private static final String[] BANNER = {
"",
" . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )",
" ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/"
};
// 固定的 Spring Boot 標(biāo)識(shí)符,用于橫幅輸出
private static final String SPRING_BOOT = " :: Spring Boot :: ";
// 橫幅固定寬度,用于計(jì)算 padding 的空格數(shù)
private static final int STRAP_LINE_SIZE = 42;
/**
* 輸出橫幅到指定的 PrintStream(如控制臺(tái)或日志)。
*/
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
// 遍歷并打印每一行 ASCII 藝術(shù)橫幅
for (String line : BANNER) {
printStream.println(line);
}
// 獲取 Spring Boot 的版本信息
String version = SpringBootVersion.getVersion();
// 如果版本信息不為空,則格式化為 "(vX.X.X)"
version = (version != null) ? " (v" + version + ")" : "";
// 構(gòu)造 padding 空格,使橫幅版本號(hào)對(duì)齊
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
// 打印 Spring Boot 標(biāo)識(shí)符和版本信息,使用 ANSI 輸出樣式
printStream.println(AnsiOutput.toString(
AnsiColor.GREEN, // 綠色輸出 Spring Boot 標(biāo)識(shí)符
SPRING_BOOT,
AnsiColor.DEFAULT, // 恢復(fù)默認(rèn)顏色
padding.toString(), // 填充的空格
AnsiStyle.FAINT, // 微弱樣式(淡化顯示版本信息)
version // 版本號(hào)
));
// 添加空行用于分隔橫幅和其他輸出
printStream.println();
}
}
控制臺(tái)效果

總結(jié)
本文全面解析了SpringBoot啟動(dòng)橫幅的實(shí)現(xiàn)原理、打印流程及自定義方法,介紹了文本橫幅(默認(rèn)路徑為banner.txt,可通過(guò)spring.banner.location配置)、圖片橫幅(默認(rèn)路徑為banner.gif、banner.jpg或banner.png,可通過(guò)spring.banner.image.location配置)、備用橫幅和默認(rèn)橫幅的使用,幫助開(kāi)發(fā)者靈活運(yùn)用橫幅機(jī)制提升項(xiàng)目啟動(dòng)體驗(yàn)。
到此這篇關(guān)于SpringBoot打印Banner的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringBoot打印Banner內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot中redis的緩存穿透問(wèn)題實(shí)現(xiàn)
這篇文章主要介紹了springboot中redis的緩存穿透問(wèn)題實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
詳解eclipse項(xiàng)目中.classpath文件的使用
這篇文章主要介紹了詳解eclipse項(xiàng)目中.classpath文件的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Java基于Servlet和JSP實(shí)現(xiàn)登錄功能
在 Web 開(kāi)發(fā)中,用戶登錄功能是非常常見(jiàn)的模塊之一,本文將通過(guò)使用 Java Servlet 和 JSP 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的用戶登錄功能,展示如何創(chuàng)建登錄頁(yè)面、處理用戶登錄請(qǐng)求,并使用數(shù)據(jù)庫(kù)驗(yàn)證用戶信息,需要的朋友可以參考下2024-11-11
Spring MVC獲取HTTP請(qǐng)求頭的兩種方式小結(jié)
這篇文章主要介紹了Spring MVC獲取HTTP請(qǐng)求頭的兩種方式小結(jié),幫助大家更好的理解和使用Spring MVC,感興趣的朋友可以了解下2021-01-01
Java之String類(lèi)型的編碼方式轉(zhuǎn)換
這篇文章主要介紹了Java之String類(lèi)型的編碼方式轉(zhuǎn)換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
JAVA中實(shí)現(xiàn)原生的 socket 通信機(jī)制原理
本篇文章主要介紹了JAVA中實(shí)現(xiàn)原生的 socket 通信機(jī)制原理,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解
這篇文章主要介紹了Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解,JCBC技術(shù)是java開(kāi)發(fā)必備的只是,jdbc連接mysql數(shù)據(jù)庫(kù),這是一個(gè)比較簡(jiǎn)單的方法,有興趣的可以了解一下2020-07-07
詳談Java中的事件監(jiān)聽(tīng)機(jī)制
下面小編就為大家?guī)?lái)一篇詳談Java中的事件監(jiān)聽(tīng)機(jī)制。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
java的main方法中調(diào)用spring的service方式
這篇文章主要介紹了在java的main方法中調(diào)用spring的service方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12

