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 目录本身ÿ…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
二维FDTD算法仿真
二维FDTD算法仿真,并带完全匹配层,输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...
RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上
一、软件介绍 文末提供程序和源码下载 RushDB 改变了您处理图形数据的方式 — 不需要 Schema,不需要复杂的查询,只需推送数据即可。 二、Key Features ✨ 主要特点 Instant Setup: Be productive in seconds, not days 即时设置 :在几秒钟…...
