SpringBoot打印Banner的實現示例
前言
在前文中,我們深入解析了SpringBoot啟動時應用環(huán)境的準備過程
。接下來將深入介紹啟動Banner
打印的具體實現及流程。
SpringBoot版本2.7.18SpringApplication的run方法的執(zhí)行邏輯如下,本文將詳細介紹第5小節(jié):打印啟動Banner
// SpringApplication類方法 public ConfigurableApplicationContext run(String... args) { // 記錄應用啟動的開始時間 long startTime = System.nanoTime(); // 1.創(chuàng)建引導上下文,用于管理應用啟動時的依賴和資源 DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; // 配置無頭模式屬性,以支持在無圖形環(huán)境下運行 // 將系統(tǒng)屬性 java.awt.headless 設置為 true configureHeadlessProperty(); // 2.獲取Spring應用啟動監(jiān)聽器,用于在應用啟動的各個階段執(zhí)行自定義邏輯 SpringApplicationRunListeners listeners = getRunListeners(args); // 啟動開始方法(發(fā)布開始事件、通知應用監(jiān)聽器ApplicationListener) listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 3.解析應用參數 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 4.準備應用環(huán)境,包括讀取配置文件和設置環(huán)境變量 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // 配置是否忽略 BeanInfo,以加快啟動速度 configureIgnoreBeanInfo(environment); // 5.打印啟動Banner Banner printedBanner = printBanner(environment); // 6.創(chuàng)建應用程序上下文 context = createApplicationContext(); // 設置應用啟動的上下文,用于監(jiān)控和管理啟動過程 context.setApplicationStartup(this.applicationStartup); // 7.準備應用上下文,包括加載配置、添加 Bean 等 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 8.刷新上下文,完成 Bean 的加載和依賴注入 refreshContext(context); // 9.刷新后的一些操作,如事件發(fā)布等 afterRefresh(context, applicationArguments); // 計算啟動應用程序的時間,并記錄日志 Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } // 10.通知監(jiān)聽器應用啟動完成 listeners.started(context, timeTakenToStartup); // 11.調用應用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便執(zhí)行自定義的啟動邏輯 callRunners(context, applicationArguments); } catch (Throwable ex) { // 12.處理啟動過程中發(fā)生的異常,并通知監(jiān)聽器 handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { // 13.計算應用啟動完成至準備就緒的時間,并通知監(jiān)聽器 Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); } catch (Throwable ex) { // 處理準備就緒過程中發(fā)生的異常 handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } // 返回已啟動并準備就緒的應用上下文 return context; }
一、入口
// 5.打印啟動Banner Banner printedBanner = printBanner(environment); // 打印啟動 Banner 的方法,根據配置的 Banner 模式選擇打印方式 private Banner printBanner(ConfigurableEnvironment environment) { // 如果 Banner 模式被設置為 OFF,則不打印 Banner,直接返回 null if (this.bannerMode == Banner.Mode.OFF) { return null; } // 確定資源加載器。如果當前實例的 resourceLoader 不為空,則使用它;否則創(chuàng)建一個默認的資源加載器 ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(null); // 創(chuàng)建 Banner 打印器,負責加載和打印 Banner SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); // 根據 Banner 模式決定打印到日志還是控制臺 if (this.bannerMode == Mode.LOG) { // 如果 Banner 模式為 LOG,則將 Banner 打印到日志中 return bannerPrinter.print(environment, this.mainApplicationClass, logger); } // 默認情況下(CONSOLE 模式),將 Banner 打印到標準輸出(控制臺) return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }
二、Banner接口類
// 一個用于以編程方式輸出 Banner 的接口類 @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開關
- 默認情況是打印到
控制臺
- 可以通過
properties或yml
設置關閉打印Banner
spring.main.banner-mode=off
上一節(jié)有講spring.main
開頭的屬性會綁定到SpringApplication
對象上,這樣就可以通過配置文件的屬性來決定Banner的打印模式。
三、打印Banner過程
1、console和log模式
- console控制臺模式,默認設置
// SpringApplicationBannerPrinter類方法 Banner print(Environment environment, Class<?> sourceClass, PrintStream out) { // 根據提供的環(huán)境信息獲取橫幅。 Banner banner = getBanner(environment); // 將橫幅打印到指定的輸出流中。 banner.printBanner(environment, sourceClass, out); // 返回一個 PrintedBanner 對象,包含打印的橫幅和源類信息。 return new PrintedBanner(banner, sourceClass); }
- log日志文件模式,通過在配置文件中設置
spring.main.banner-mode=log
,可以將應用啟動Banner輸出到日志文件中
// SpringApplicationBannerPrinter類方法 Banner print(Environment environment, Class<?> sourceClass, Log logger) { // 根據提供的環(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); } // 返回一個 PrintedBanner 對象,包含打印的橫幅和源類信息。 return new PrintedBanner(banner, sourceClass); }
- log模式就是獲取
打印流內容轉換為字符串
,然后由log日志打印罷了
兩種方式都會返回一個PrintedBanner
對象,主要是為以后在應用上下文中注冊為Bean或供其他組件使用做準備。
2、四種Banner對象
- 圖片和文本橫幅組合的Banners
- 備用Banner
- 默認Banner
// SpringApplicationBannerPrinter類方法 // 默認Banner private static final Banner DEFAULT_BANNER = new SpringBootBanner(); // 根據當前環(huán)境獲取適當的橫幅(Banner) private Banner getBanner(Environment environment) { // 創(chuàng)建一個 Banners 對象,用于存儲圖片和文本橫幅 Banners banners = new Banners(); // 嘗試獲取圖片橫幅,并將其添加到 Banners 中(如果非空) banners.addIfNotNull(getImageBanner(environment)); // 嘗試獲取文本橫幅,并將其添加到 Banners 中(如果非空) banners.addIfNotNull(getTextBanner(environment)); // 如果至少包含一個橫幅,則返回組合的 Banners 對象 if (banners.hasAtLeastOneBanner()) { return banners; } // 如果沒有任何橫幅但存在備用橫幅,則返回備用橫幅 if (this.fallbackBanner != null) { return this.fallbackBanner; } // 如果沒有任何橫幅,則返回默認橫幅 // Banner DEFAULT_BANNER = new SpringBootBanner(); return DEFAULT_BANNER; }
Banners
對象內部持有多個Banner
實現類,遍歷調用Banner的printBanner
方法
2.1、圖片Banner
- 嘗試根據環(huán)境信息獲取圖片橫幅(Image Banner)
- 環(huán)境變量
spring.banner.image.location
用于指定圖片路徑;如果未設置,則默認加載路徑為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); // 如果路徑不為空,嘗試加載對應的資源 if (StringUtils.hasLength(location)) { Resource resource = this.resourceLoader.getResource(location); // 如果資源存在,返回對應的 ImageBanner 對象 return resource.exists() ? new ImageBanner(resource) : null; } // 如果未指定路徑,嘗試加載默認圖片橫幅文件 // String[] IMAGE_EXTENSION = { "gif", "jpg", "png" }; for (String ext : IMAGE_EXTENSION) { Resource resource = this.resourceLoader.getResource("banner." + ext); // 如果找到存在的文件資源,返回對應的 ImageBanner 對象 if (resource.exists()) { return new ImageBanner(resource); } } // 如果沒有找到任何圖片橫幅資源,返回 null return null; }
控制臺效果
2.2、文字Banner
- 嘗試根據環(huán)境信息獲取文本橫幅(Text Banner)
- 環(huán)境變量
spring.banner.location
用于指定文本路徑;如果未設置,則默認加載路徑為banner.txt
。
private Banner getTextBanner(Environment environment) { // 獲取橫幅的路徑,優(yōu)先使用環(huá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"(防止加載到不相關的資源) if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) { // 如果資源有效,返回對應的 ResourceBanner 對象 return new ResourceBanner(resource); } } catch (IOException ex) { // 忽略異常,可能是資源加載時出錯或路徑無效 // 在這里不拋出異常,而是返回 null,表示沒有有效的橫幅資源 } // 如果資源無效或發(fā)生異常,返回 null return null; }
控制臺效果
2.2、備用Banner
SpringApplicationBannerPrinter
對象的備用Banner屬性fallbackBanner
是由SpringApplication
對象的私有屬性banner
傳遞而來的。
- 可以在SpringBoot啟動類中通過調用
SpringApplication
的setBanner
方法直接設置自定義的Banner
對象
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); // 設置全局備用橫幅 app.setBanner((environment, sourceClass, out) -> { out.println("===== 全局備用橫幅 ====="); out.println(" 默認橫幅已啟用 "); out.println("====================="); }); app.run(args); } }
控制臺效果
2.3、默認Banner
如果未設置自定義文字圖片Banner或備用Banner,SpringBoot將使用默認的啟動橫幅(SpringBootBanner
)作為顯示內容。
// 默認的 Banner 實現,用于打印 "Spring" 的啟動橫幅,和版本信息 class SpringBootBanner implements Banner { // 預定義的 ASCII 藝術橫幅,每行為一個數組元素 private static final String[] BANNER = { "", " . ____ _ __ _ _", " /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\", " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /", " =========|_|==============|___/=/_/_/_/" }; // 固定的 Spring Boot 標識符,用于橫幅輸出 private static final String SPRING_BOOT = " :: Spring Boot :: "; // 橫幅固定寬度,用于計算 padding 的空格數 private static final int STRAP_LINE_SIZE = 42; /** * 輸出橫幅到指定的 PrintStream(如控制臺或日志)。 */ @Override public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) { // 遍歷并打印每一行 ASCII 藝術橫幅 for (String line : BANNER) { printStream.println(line); } // 獲取 Spring Boot 的版本信息 String version = SpringBootVersion.getVersion(); // 如果版本信息不為空,則格式化為 "(vX.X.X)" version = (version != null) ? " (v" + version + ")" : ""; // 構造 padding 空格,使橫幅版本號對齊 StringBuilder padding = new StringBuilder(); while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) { padding.append(" "); } // 打印 Spring Boot 標識符和版本信息,使用 ANSI 輸出樣式 printStream.println(AnsiOutput.toString( AnsiColor.GREEN, // 綠色輸出 Spring Boot 標識符 SPRING_BOOT, AnsiColor.DEFAULT, // 恢復默認顏色 padding.toString(), // 填充的空格 AnsiStyle.FAINT, // 微弱樣式(淡化顯示版本信息) version // 版本號 )); // 添加空行用于分隔橫幅和其他輸出 printStream.println(); } }
控制臺效果
總結
本文全面解析了SpringBoot啟動橫幅的實現原理、打印流程及自定義方法,介紹了文本橫幅(默認路徑為banner.txt
,可通過spring.banner.location
配置)、圖片橫幅(默認路徑為banner.gif
、banner.jpg
或banner.png
,可通過spring.banner.image.location
配置)、備用橫幅和默認橫幅的使用,幫助開發(fā)者靈活運用橫幅機制提升項目啟動體驗。
到此這篇關于SpringBoot打印Banner的實現示例的文章就介紹到這了,更多相關SpringBoot打印Banner內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
如何在mapper文件中使用in("str1","str2")
這篇文章主要介紹了如何在mapper文件中使用in("str1","str2"),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01Socket+JDBC+IO實現Java文件上傳下載器DEMO詳解
這篇文章主要介紹了Socket+JDBC+IO實現Java文件上傳下載器DEMO詳解,需要的朋友可以參考下2017-05-05@Transactional解讀(作用、失效場景與解決方式)
這篇文章主要介紹了關于@Transactional作用、失效場景與解決方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08