
1. 启动横幅不只是花架子从一次排查经历说起很多人第一次看到SpringBoot项目启动时控制台打印的那段字符画第一反应是这玩意儿有啥用花里胡哨的。我一开始也这么想直到有一次在同时维护七八个微服务的时候本地起了三个实例日志窗口切来切去根本分不清哪个是订单服务、哪个是用户服务、哪个是网关。每次都要往上翻好几屏去看端口号才能确认效率极低。后来我给每个服务配了不同的Banner一眼就能认出来这个问题才算彻底解决。所以Banner这个东西表面上看是个装饰实际上在多服务并行开发、多环境切换、日志排查这些场景下它能起到视觉锚点的作用。这篇文章就围绕SpringBoot的Banner机制把它的加载原理、自定义方式、动态内容、关闭方法、以及实际项目里怎么用得漂亮全部讲透。不管你是刚接触SpringBoot的新手还是已经用了几年但从来没深究过Banner的老手看完都能有所收获。核心关键词先摆出来SpringBoot Banner、Ascii-Art、banner.txt、自定义启动横幅、Banner打印原理、动态Banner、关闭Banner。这些是本文要覆盖的核心技术点后面每个章节都会围绕它们展开。先说一下本文适合谁看如果你正在搭建SpringBoot项目脚手架想给团队做一套统一的启动标识那这篇可以直接抄作业如果你只是好奇Banner是怎么加载出来的想搞清楚SpringApplication.run背后干了什么那原理部分能帮你补上这块知识如果你嫌Banner碍事想关掉它我也告诉你最干净的几种关法。2. Banner的加载链路从SpringApplication.run到控制台输出2.1 默认Banner是怎么被找到的SpringBoot启动的时候SpringApplication.run()方法内部会调用一个叫printBanner()的方法。这个方法做的事情很直接创建一个Banner对象然后调用它的printBanner()把内容输出到控制台。默认情况下SpringBoot会使用SpringBootBanner这个实现类它内部硬编码了一段SPRING BOOT的字符画加上版本号。这就是你什么都没配的时候看到的那段默认输出。但SpringBoot并不是无脑用默认的。它的查找顺序是这样的先检查classpath根目录下有没有banner.txt文件有就用它。如果没有banner.txt再检查有没有banner.gif、banner.jpg、banner.png这些图片文件有就把图片转成字符画输出。如果都没有才回退到默认的SpringBootBanner。这个顺序很关键。很多人把banner.txt放在了src/main/resources下面却发现没生效八成是因为文件位置放错了或者文件名大小写不对。必须是classpath根目录也就是src/main/resources/banner.txt不能放在子目录里。2.2 Banner接口的设计为什么它这么灵活SpringBoot把Banner抽象成了一个接口只有两个方法public interface Banner { void printBanner(Environment environment, Class? sourceClass, PrintStream out); enum Mode { OFF, CONSOLE, LOG } }这个设计非常聪明。printBanner方法接收Environment意味着你可以在打印Banner的时候读取配置信息比如当前激活的profile、应用名称、版本号等等。这就为动态Banner提供了可能。Mode枚举则控制Banner的输出位置OFF关闭CONSOLE输出到控制台默认LOG输出到日志文件。我见过一些团队的做法是直接实现Banner接口写一个自定义的Banner类在里面读取Git提交信息、构建时间、当前环境等打印出一段信息量很大的启动横幅。这种做法在需要严格追踪部署版本的场景下非常有用。2.3 图片Banner的转换逻辑如果你放的是图片SpringBoot会用ImageBanner来处理。它会把图片的每个像素读取出来根据像素的亮度映射到不同的字符上亮度高的用密集字符比如、#亮度低的用稀疏字符比如.、空格从而在控制台上还原出图片的轮廓。这里有个实操经验图片不要太大。我试过用一张1920x1080的图片结果控制台刷了几千行启动日志全被冲掉了。比较合适的尺寸是宽80到120像素、高30到40像素这样在标准终端窗口里刚好能完整显示又不会太占地方。另外图片最好是高对比度的黑白图彩色图转出来的字符画往往糊成一团辨识度很差。2.4 字符画生成的工具选择自己手敲字符画不现实一般都用工具生成。常见的几类工具在线字符画生成器把文字输进去选字体和字符集直接出结果。优点是快缺点是样式受限于工具提供的字体。figlet命令行工具Linux和macOS上可以直接装支持多种字体figlet -f slant MY APP就能出一段斜体风格的字符画。ASCII艺术字库网上有很多现成的字库直接复制粘贴就行。我个人最常用的是figlet因为可以脚本化。比如在CI流程里根据项目名自动生成Banner就不用每次手动去在线工具里敲了。字体方面slant、big、standard这几个比较耐看banner字体太宽在窄终端里会换行不太推荐。3. 动手定制banner.txt的写法与占位符实战3.1 最基础的banner.txt在src/main/resources下新建一个banner.txt里面写上你想要的字符画启动项目就能看到效果。这是最直接的方式不需要任何额外配置。但这里有个坑字符画里的反斜杠需要转义。因为SpringBoot在解析banner.txt的时候会处理占位符反斜杠在某些情况下会被当成转义字符。如果你发现字符画显示出来缺了一块或者变形了先检查是不是反斜杠的问题。解决办法是用双反斜杠\\或者干脆换一种不含反斜杠的字体。3.2 占位符让Banner活起来banner.txt里可以使用SpringBoot提供的占位符启动时会自动替换成实际值。常用的有这些占位符含义示例输出${spring-boot.version}SpringBoot版本3.2.0${spring-boot.formatted-version}格式化版本(v3.2.0)${application.version}应用版本来自MANIFEST1.0.0${application.formatted-version}格式化应用版本(v1.0.0)${AnsiColor.BRIGHT_GREEN}颜色代码设置后续文字颜色${AnsiStyle.BOLD}样式代码加粗${application.title}应用标题My Application颜色占位符特别实用。你可以在字符画的不同部分插入不同的颜色代码让Banner有层次感。比如标题用亮绿色版本号用黄色环境信息用青色。注意颜色代码是ANSI转义序列在支持ANSI的终端里才有效Windows的cmd默认不支持但Windows Terminal和PowerShell是支持的。3.3 一个实际可用的banner.txt模板下面这个模板是我在多个项目里用过的结构清晰信息量适中${AnsiColor.BRIGHT_CYAN} ___ ___ _ _ ___ _ _ _____ ___ | \/ | | | | | / _ \ | | | || ___|/ _ \ | . . | | |_| |/ /_\ \| | | || |__ / /_\ \ | |\/| | | _ || _ || | | || __|| _ | | | | | | | | || | | || |_| || |___| | | | \_| |_/ \_| |_/\_| |_/ \___/ \____/\_| |_/ ${AnsiColor.BRIGHT_YELLOW} :: SpringBoot :: ${spring-boot.formatted-version} ${AnsiColor.BRIGHT_GREEN} :: Application :: ${application.title} ${application.formatted-version} :: Profile :: ${spring.profiles.active:default} :: Started At :: ${timestamp} ${AnsiColor.DEFAULT}这里用到了${spring.profiles.active:default}冒号后面的default是默认值当没有激活任何profile时显示default。${timestamp}不是SpringBoot内置的占位符需要自己扩展后面讲动态Banner的时候会说怎么实现。3.4 配置项控制Banner的行为在application.yml或application.properties里有几个和Banner相关的配置spring: banner: location: classpath:banner.txt # 指定Banner文件位置 charset: UTF-8 # 指定编码 mode: console # off / console / log main: banner-mode: console # 另一种关闭方式spring.banner.location可以指定任意路径的Banner文件不限于classpath根目录。spring.banner.charset在字符画包含中文或特殊符号时很有用不设的话可能乱码。spring.banner.mode和spring.main.banner-mode功能有重叠前者是较新的配置方式后者是早期版本的方式建议统一用spring.banner.mode。注意如果同时设置了spring.banner.location指向一个不存在的文件SpringBoot不会报错而是静默回退到默认Banner。这个行为容易让人困惑排查时记得先确认文件路径是否正确。4. 动态Banner把构建信息和运行环境印在启动画面上4.1 为什么需要动态Banner静态的字符画只能解决这是哪个服务的问题但解决不了这是哪个版本、哪个环境、什么时候构建的的问题。在微服务架构下同一个服务可能有dev、test、staging、prod多个环境每个环境跑的版本可能不一样。如果启动横幅里能直接显示这些信息排查问题时能省很多事。我经历过一次线上问题两个实例的代码版本不一致导致行为差异排查了半天才发现是发布的时候有一个实例没更新。如果当时Banner里带了构建版本号一眼就能看出来。4.2 通过实现Banner接口做动态内容实现自定义Banner的核心是拿到Environment对象从中读取配置再结合其他信息源拼出最终输出。下面是一个示例public class CustomBanner implements Banner { Override public void printBanner(Environment environment, Class? sourceClass, PrintStream out) { String appName environment.getProperty(spring.application.name, Unknown); String profile String.join(,, environment.getActiveProfiles()); String buildVersion environment.getProperty(build.version, dev); String buildTime environment.getProperty(build.time, unknown); out.println(); out.println( Application : appName); out.println( Profile : (profile.isEmpty() ? default : profile)); out.println( Build : buildVersion); out.println( Build Time : buildTime); out.println(); } }然后在启动类里注册public static void main(String[] args) { SpringApplication app new SpringApplication(MyApp.class); app.setBanner(new CustomBanner()); app.run(args); }build.version和build.time这些属性可以通过Maven的resource filtering在打包时写入application.properties这样每次构建都会自动更新。4.3 用Maven资源过滤注入构建信息在pom.xml里配置build resources resource directorysrc/main/resources/directory filteringtrue/filtering /resource /resources plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build然后在application.properties里写build.version${project.version} build.time${maven.build.timestamp}这样打包的时候Maven会把${project.version}和${maven.build.timestamp}替换成实际值。启动时Banner就能显示真实的构建版本和时间。提示maven.build.timestamp默认是UTC时间如果需要本地时间可以在properties里设置maven.build.timestamp.format或者用其他插件生成。4.4 结合Git信息做版本追踪更进一步的做法是把Git的commit id和分支名也打到Banner里。可以用git-commit-id-plugin这个Maven插件它会在打包时生成一个git.properties文件SpringBoot的GitProperties会自动加载它。配置好插件后在自定义Banner里注入GitPropertiesComponent public class GitInfoBanner implements Banner { Autowired private GitProperties gitProperties; Override public void printBanner(Environment environment, Class? sourceClass, PrintStream out) { out.println(Git Branch : gitProperties.getBranch()); out.println(Git Commit : gitProperties.getShortCommitId()); } }不过要注意Banner的打印时机非常早早于Spring容器的依赖注入。所以直接Autowired是拿不到Bean的。正确的做法是在SpringApplication.run()之后手动获取或者用ApplicationContextInitializer。这也是很多人踩过的坑在自定义Banner里注入Bean结果启动报NPE。5. 关闭Banner的几种方式与适用场景5.1 代码方式关闭最直接的方式是在启动类里设置SpringApplication app new SpringApplication(MyApp.class); app.setBannerMode(Banner.Mode.OFF); app.run(args);这种方式适合你完全确定不需要Banner的场景比如单元测试、集成测试。测试的时候控制台输出越干净越好Banner只会干扰日志阅读。5.2 配置文件方式关闭在application.yml里spring: main: banner-mode: off或者在application.properties里spring.main.banner-modeoff这种方式的好处是可以按环境区分。比如在application-dev.yml里保持Banner开启在application-test.yml里关闭。用profile来控制灵活度更高。5.3 启动参数方式关闭命令行启动的时候加参数java -jar myapp.jar --spring.main.banner-modeoff这种方式适合临时关闭比如在CI/CD流水线里跑集成测试的时候不想改代码也不想改配置文件直接加个启动参数就行。5.4 几种方式的优先级对比方式优先级适用场景是否可动态调整代码设置最高测试类、固定不需要Banner的应用否配置文件中按环境区分是改配置重启启动参数最高覆盖配置文件临时关闭、CI流水线是每次启动可不同实际项目中我一般是在测试环境的配置文件里关掉Banner生产和开发环境保留。因为测试环境日志量大Banner占的那几十行虽然不多但积少成多。开发和线上保留Banner方便快速识别服务。注意spring.main.banner-mode和spring.banner.mode同时存在时前者的优先级更高。如果你发现配置了spring.banner.modeoff但Banner还在打印检查一下是不是spring.main.banner-mode把它覆盖了。6. 踩过的坑与实战经验汇总6.1 中文乱码问题banner.txt里如果有中文默认编码可能不对导致控制台显示乱码。解决办法是在配置里显式指定编码spring: banner: charset: UTF-8但即使这样Windows的cmd默认代码页是GBK还是可能乱码。所以我的建议是Banner里尽量只用ASCII字符中文信息放在Banner下面的日志行里输出。这样跨平台兼容性最好。6.2 颜色代码在日志文件里的问题如果你把spring.banner.mode设成了logBanner会输出到日志文件。但ANSI颜色代码在日志文件里是乱码一样的转义字符看起来很不舒服。所以用颜色占位符的时候要么只用在console模式要么在日志采集端做过滤。我一般是在Banner里不用颜色保持纯文本这样console和log都干净。6.3 Banner文件位置放错这是新手最常见的错误。banner.txt必须放在src/main/resources根目录下打包后位于classpath根目录。如果你放在了src/main/resources/static/或者src/main/resources/config/下面SpringBoot是找不到的。排查方法很简单打包后打开jar看BOOT-INF/classes/下面有没有banner.txt。6.4 图片Banner的性能影响图片Banner在启动时会做像素到字符的转换如果图片很大这个转换过程会消耗一定时间。我实测过一张2000x2000的PNG转换花了将近1秒。虽然1秒对启动时间影响不大但没必要。控制在100x40以内转换基本是毫秒级的。6.5 自定义Banner里注入Bean的时机问题前面提过Banner的打印发生在Spring容器刷新之前所以任何依赖注入都是不可用的。如果你需要在Banner里用到某个Bean的信息有两个办法一是把信息提前写到Environment里比如通过ApplicationContextInitializer二是在Banner打印完之后再输出一段补充信息。我倾向于第二种因为更简单不破坏SpringBoot的启动流程。6.6 多模块项目的Banner管理在多模块项目里如果每个模块都有自己的banner.txt最终打包后classpath根目录只会有一个生效取决于打包顺序。所以正确的做法是只在最终的启动模块里放banner.txt其他模块不要放。如果确实需要区分可以用spring.banner.location指向不同路径的文件通过profile来切换。7. 把Banner用出花几个进阶玩法7.1 根据环境显示不同颜色的Banner利用Environment判断当前profile在自定义Banner里输出不同颜色的文字。比如dev环境用绿色prod环境用红色起到警示作用。实现方式是在printBanner方法里读取environment.getActiveProfiles()然后拼接对应的ANSI颜色代码。这个做法在团队里推广之后效果很好。开发同学一眼就能看出自己连的是哪个环境避免了误操作生产数据库这种低级错误。7.2 在Banner里显示启动耗时SpringBoot启动完成后会输出启动耗时但那是启动之后的事。如果你想在Banner里就显示上次启动耗时或者预计启动时间可以在Banner里读取一个记录文件。不过这个玩法比较小众实现起来也麻烦我试过一次就放弃了性价比不高。7.3 用Banner做简单的健康提示在Banner里显示当前数据库连接、Redis连接的状态。这个需要在Banner打印之前就完成连接检查会拖慢启动速度。我的建议是不要在Banner里做这种事用SpringBoot的HealthIndicator更合适。Banner就让它安安静静做个视觉标识就好。7.4 团队统一的Banner规范如果你带团队建议制定一个Banner规范统一字体风格、统一信息字段、统一颜色方案。这样所有服务的启动画面看起来是一套的专业感很强。我们团队的规范是第一行是服务名slant字体第二行是版本和环境第三行是构建时间下面空一行再开始正常的启动日志。这个规范落地之后新同学入职看到启动画面就能大概知道项目结构减少了沟通成本。7.5 Banner与日志系统的配合Banner输出到console日志也输出到console两者会混在一起。如果Banner太长会把日志冲散。我的做法是Banner控制在15行以内并且在Banner最后加一个明显的分隔线比如这样日志从哪开始一目了然。另外如果用了logback或log4j2可以配置日志格式在启动阶段用不同的pattern让Banner和日志在视觉上区分开。不过这个配置有点复杂一般项目没必要。8. 我个人的使用体会Banner这个东西说大不大说小不小。它不影响功能但影响开发体验。我在实际项目里的做法是开发和生产环境保留一个简洁的Banner包含服务名、版本、环境三个信息测试环境直接关掉保持日志干净。Banner文件用figlet生成字体固定用slant颜色只在服务名上用亮青色其他都是默认色。踩过几次坑之后我总结了一条原则Banner的信息密度要低视觉辨识度要高。不要试图在Banner里塞太多东西那只会让它变成另一段需要阅读的日志。它的核心价值就是让你在众多窗口里一眼认出这是哪个服务其他的都是锦上添花。最后分享一个小技巧如果你用IntelliJ IDEA可以在Run/Debug Configurations里给每个启动配置设置不同的Before launch任务配合不同的Banner文件这样在IDE里同时启动多个服务时每个服务的启动画面都不一样切换窗口的时候特别方便。这个技巧我用了好几年推荐你也试试。