SpringBoot源码解析(六):打印Banner
SpringBoot源码系列文章
SpringBoot源码解析(一):SpringApplication构造方法
SpringBoot源码解析(二):引导上下文DefaultBootstrapContext
SpringBoot源码解析(三):启动开始阶段
SpringBoot源码解析(四):解析应用参数args
SpringBoot源码解析(五):准备应用环境
SpringBoot源码解析(六):打印Banner
目录
- 前言
- 一、入口
- 二、Banner接口类
- 1、打印Banner开关
- 三、打印Banner过程
- 1、console和log模式
- 2、四种Banner对象
- 2.1、图片Banner
- 2.2、文字Banner
- 2.2、备用Banner
- 2.3、默认Banner
- 总结
前言
在前文中,我们深入解析了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 设置为 trueconfigureHeadlessProperty();// 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.打印启动BannerBanner 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,直接返回 nullif (this.bannerMode == Banner.Mode.OFF) {return null;}// 确定资源加载器。如果当前实例的 resourceLoader 不为空,则使用它;否则创建一个默认的资源加载器ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(null);// 创建 Banner 打印器,负责加载和打印 BannerSpringApplicationBannerPrinter 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.outCONSOLE,// 将 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);}}// 如果没有找到任何图片横幅资源,返回 nullreturn 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,表示没有有效的横幅资源}// 如果资源无效或发生异常,返回 nullreturn 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(如控制台或日志)。*/@Overridepublic 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源码系列文章 SpringBoot源码解析(一):SpringApplication构造方法 SpringBoot源码解析(二):引导上下文DefaultBootstrapContext SpringBoot源码解析(三):启动开始阶段 SpringBoot源码解析(四):解析应用参数args Sp…...
【计算机网络】实验6:IPV4地址的构造超网及IP数据报
实验 6:IPV4地址的构造超网及IP数据报 一、 实验目的 加深对IPV4地址的构造超网(无分类编制)的了解。 加深对IP数据包的发送和转发流程的了解。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、了解IPV4地址的构造超网…...
easy excel 生成excel 文件
导包 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.3</version> </dependency> 内容 List<类> limspjreport 值; String fileName sdf.format(new Date()) "-…...
Ajax:回忆与节点
一点回忆 面对我的Ajax学习,实现前后端交互,最开始我采用的使用网络寻找intellij IDEA Ultimate破解方法,然后最终成功,然后按照相关教程配置java ee项目,然后中间又去配置了Tomcat服务器,然后又去学习了一…...
Python+OpenCV系列:Python和OpenCV的结合和发展
PythonOpenCV系列:Python和OpenCV的结合和发展 **引言****Python语言的发展****1.1 Python的诞生与发展****1.2 Python的核心特性与优势****1.3 Python的应用领域** **OpenCV的发展****2.1 OpenCV的起源与发展****2.2 OpenCV的功能特性****2.3 OpenCV的应用场景** *…...
Ubuntu20.04 由源码编译安装opencv3.2 OpenCV
Ubuntu20.04 由源码编译安装opencv3.2.0 获取 opencv 及opencv_contrib源代码 创建目录以存放opencv及opencv_contrib源代码 mkdir ~/opencv3.2.0 cd ~/opencv3.2.0获取opencv源代码并切换到对应tag git clone https://github.com/opencv/opencv.git cd opencv git checkou…...
A058-基于Spring Boot的餐饮管理系统的设计与实现
🙊作者简介:在校研究生,拥有计算机专业的研究生开发团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看项目链接获取⬇️,记得注明来意哦~🌹 赠送计算机毕业设计600个选题ex…...
RDIFramework.NET CS敏捷开发框架 SOA服务三种访问(直连、WCF、WebAPI)方式
1、介绍 在软件开发领域,尤其是企业级应用开发中,灵活性、开放性、可扩展性往往是项目成功的关键因素。对于C/S项目,如何高效地与后端数据库进行交互,以及如何提供多样化的服务访问方式,是开发者需要深入考虑的问题。…...
Linux——命名管道及日志
linux——进程间通信及管道的应用场景-CSDN博客 文章目录 目录 文章目录 前言 一、命名管道是什么? 理解: 2、编写代码 makefile 管道封装成类,想用中管道时只需要调用实例化 读端 写端 日志 1、日志是什么? 2、日志有什么&#x…...
Flink 常见面试题
1、Flink 的四大特征(基石) checkpoin基于Chandy-Lamport算法实现了分布式一致性快照提供了一致性的语义 state丰富的StateAPI time实现了Watermark机制,乱序数据处理,迟到数据容忍 window开箱即用的滚动,滑动会话窗口…...
rtc-pcf8563 0-0051: low voltage detected, date/time is not reliable
解决方法: 1、先测量pcf8563电源电压,是否满足要求。 2、pcf8563首次操作。第一次读取pcf8563的时间,未初始化,非法,芯片门槛电压检测配置不合理。使用hwclock命令写入一次,即可解决。 hwclock -f /dev/…...
(简单5步实现)部署本地AI大语言模型聊天系统:Chatbox AI + grok2.0大模型
摘要: 本文将指导您如何部署一个本地AI大语言模型聊天系统,使用Chatbox AI客户端应用和grok-beta大模型,以实现高效、智能的聊天体验。 引言: 由马斯克X-AI发布的Grok 2大模型以其卓越的性能超越了GPT4.0。Grok模型支持超长文本…...
MAUI APP开发蓝牙协议的经验分享:与跳绳设备对接
在开发MAUI应用程序时,蓝牙协议的应用是一个重要的环节,尤其是在需要与外部设备如智能跳绳进行数据交换的场景中。以下是我在开发过程中的一些经验和心得,希望能为你的项目提供帮助。 1. 蓝牙协议基础 蓝牙协议是无线通信的一种标准&#x…...
最新版Node.js下载安装及环境配置教程
目录 初识:Node.js 一、下载:Node.js 二、安装:Node.js 1.下载【node.js】压缩包安装文件 2.解压下载的安装包 3.打开解压的【node-v22.11.0-x64】文件夹 4.双击启动安装程序 5.点击【Next】 6.勾选【I accept the terms in the Lic…...
51c自动驾驶~合集39
我自己的原文哦~ https://blog.51cto.com/whaosoft/12707676 #DiffusionDrive 大幅超越所有SOTA!地平线DiffusionDrive:生成式方案或将重塑端到端格局? 近年来,由于感知模型的性能持续进步,端到端自动驾驶受到了来…...
单链表基础操作
文章目录 abstract定义结点结构初始化链表遍历链表求表长查找结点根据序号查找结点根据值查找结点 插入结点首尾位置插入一般位置插入(通用插入)找到尾元素|尾指针相关操作 删除结点 abstract 单链表是一种简单的动态数据结构,它由一系列结点组成,每个结…...
Asp.net MVC在VSCore中的页面的增删改查(以Blog项目为例),用命令代码
在VSCore中的页面的增删改查(以Blog项目为例) 1.创建项目(无解决方案)复杂项目才需要 dotnet new mvc -o Blog2.控制器 BlogsController.cs 控制器(Controller)名字和视图(View)中的文件名要一模一样 u…...
【Leecode】Leecode刷题之路第66天之加一
题目出处 66-加一-题目出处 题目描述 个人解法 思路: todo代码示例:(Java) todo复杂度分析 todo官方解法 66-加一-官方解法 方法1:找出最长的后缀9 思路: 代码示例:(Java&#…...
使用 VLC 在本地搭建流媒体服务器 (详细版)
提示:详细流程 避坑指南 Hi~!欢迎来到碧波空间,平时喜欢用博客记录学习的点滴,欢迎大家前来指正,欢迎欢迎~~ ✨✨ 主页:碧波 📚 📚 专栏:音视频 目录 借助VLC media pl…...
Ubuntu 常用解压与压缩命令
.zip文件 unzip FileName.zip # 解压 zip DirName.zip DirName # 将DirName本身压缩 zip -r DirName.zip DirName # 压缩,递归处理,将指定目录下的所有文件和子目录一起压缩 zip DirName.zip DirName 行为: 只压缩 DirName 目录本身ÿ…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
Linux-进程间的通信
1、IPC: Inter Process Communication(进程间通信): 由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...
leetcode_69.x的平方根
题目如下 : 看到题 ,我们最原始的想法就是暴力解决: for(long long i 0;i<INT_MAX;i){if(i*ix){return i;}else if((i*i>x)&&((i-1)*(i-1)<x)){return i-1;}}我们直接开始遍历,我们是整数的平方根,所以我们分两…...
