SpringBoot打印Banner的实现示例
作者:冬天vs不冷
前言
在前文中,我们深入解析了SpringBoot启动时应用环境的准备过程
。接下来将深入介绍启动Banner
打印的具体实现及流程。
SpringBoot版本2.7.18SpringApplication的run方法的执行逻辑如下,本文将详细介绍第5小节:打印启动Banner
// SpringApplication类方法 public ConfigurableApplicationContext run(String... args) { // 记录应用启动的开始时间 long startTime = System.nanoTime(); // 1.创建引导上下文,用于管理应用启动时的依赖和资源 DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; // 配置无头模式属性,以支持在无图形环境下运行 // 将系统属性 java.awt.headless 设置为 true configureHeadlessProperty(); // 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑 SpringApplicationRunListeners listeners = getRunListeners(args); // 启动开始方法(发布开始事件、通知应用监听器ApplicationListener) listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 3.解析应用参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 4.准备应用环境,包括读取配置文件和设置环境变量 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // 配置是否忽略 BeanInfo,以加快启动速度 configureIgnoreBeanInfo(environment); // 5.打印启动Banner Banner printedBanner = printBanner(environment); // 6.创建应用程序上下文 context = createApplicationContext(); // 设置应用启动的上下文,用于监控和管理启动过程 context.setApplicationStartup(this.applicationStartup); // 7.准备应用上下文,包括加载配置、添加 Bean 等 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 8.刷新上下文,完成 Bean 的加载和依赖注入 refreshContext(context); // 9.刷新后的一些操作,如事件发布等 afterRefresh(context, applicationArguments); // 计算启动应用程序的时间,并记录日志 Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } // 10.通知监听器应用启动完成 listeners.started(context, timeTakenToStartup); // 11.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑 callRunners(context, applicationArguments); } catch (Throwable ex) { // 12.处理启动过程中发生的异常,并通知监听器 handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { // 13.计算应用启动完成至准备就绪的时间,并通知监听器 Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); } catch (Throwable ex) { // 处理准备就绪过程中发生的异常 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 不为空,则使用它;否则创建一个默认的资源加载器 ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(null); // 创建 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
上一节有讲spring.main
开头的属性会绑定到SpringApplication
对象上,这样就可以通过配置文件的属性来决定Banner的打印模式。
三、打印Banner过程
1、console和log模式
- console控制台模式,默认设置
// SpringApplicationBannerPrinter类方法 Banner print(Environment environment, Class<?> sourceClass, PrintStream out) { // 根据提供的环境信息获取横幅。 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) { // 根据提供的环境信息获取横幅。 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(); // 根据当前环境获取适当的横幅(Banner) private Banner getBanner(Environment environment) { // 创建一个 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
- 尝试根据环境信息获取图片横幅(Image Banner)
- 环境变量
spring.banner.image.location
用于指定图片路径;如果未设置,则默认加载路径为banner.gif
、banner.jpg
或banner.png
(按顺序查找)。
private Banner getImageBanner(Environment environment) { // 从环境变量中获取横幅图片的路径 // 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
- 尝试根据环境信息获取文本横幅(Text Banner)
- 环境变量
spring.banner.location
用于指定文本路径;如果未设置,则默认加载路径为banner.txt
。
private Banner getTextBanner(Environment environment) { // 获取横幅的路径,优先使用环境变量中的配置,如果没有配置则使用默认路径 // 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,表示没有有效的横幅资源 } // 如果资源无效或发生异常,返回 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
配置)、备用横幅和默认横幅的使用,帮助开发者灵活运用横幅机制提升项目启动体验。
到此这篇关于SpringBoot打印Banner的实现示例的文章就介绍到这了,更多相关SpringBoot打印Banner内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!