1. 项目概述与核心价值拆解SpringBoot 应用每次启动时控制台第一眼跳出来的那段字符画就是 Banner。很多人把它当成一个花哨的小装饰启动完就翻篇了。但如果你真正在团队协作、多环境部署、服务治理这些场景里待过就会发现这个 Banner 其实承载了不少实用价值。它不只是一个 ASCII 艺术字展示更是应用启动阶段的一个身份标识、环境提示和调试锚点。我第一次认真对待 Banner是因为一个多模块项目。当时同一个 jar 包在开发、测试、预发三套环境里跑日志文件混在一起排查问题时经常要翻好几屏才能确认当前到底是哪个环境、哪个版本。后来在 Banner 里加上了环境标识和构建版本号启动瞬间就能确认排查效率提升非常明显。从那以后Banner 就成了我每个 SpringBoot 项目的标配。这个内容适合所有使用 SpringBoot 的开发者不管你是刚接触框架的新手还是已经写过多个项目的老手。新手可以学会怎么自定义 Banner 让项目更有辨识度老手可以了解 Banner 背后的加载机制、条件判断逻辑以及怎么把它和构建流程、环境配置结合起来用。核心关键词包括 SpringBoot、Banner、ASCII-Art、自定义启动图案、banner.txt、SpringApplication、启动配置。Banner 的本质是 SpringBoot 启动过程中由SpringApplication在run方法早期阶段打印的一段文本。它默认从 classpath 下寻找banner.txt、banner.gif、banner.jpg、banner.png等资源文件如果找不到就使用内置的默认 Banner。这个机制看起来简单但里面涉及资源加载顺序、字符编码、占位符替换、条件属性控制等多个细节值得单独拿出来讲透。2. Banner 加载机制与核心原理2.1 默认 Banner 是怎么来的SpringBoot 内置的默认 Banner 其实就是一个硬编码的字符串常量定义在SpringBootBanner类里。它长这样class SpringBootBanner implements Banner { private static final String[] BANNER { , . ____ _ __ _ _, /\\\\ / ____ __ _ _(_)_ __ __ _ \\ \\ \\ \\, ( ( )\\___ | _ | _| | _ \\/ _ | \\ \\ \\ \\, \\\\/ ___)| |_)| | | | | || (_| | ) ) ) ), |____| .__|_| |_|_| |_\\__, | / / / /, |_||___//_/_/_/, :: Spring Boot :: (v SpringBootVersion.getVersion() ) }; }这段字符画就是那个经典的 Spring Boot 叶子图案。注意最后一行会动态拼接当前 SpringBoot 版本号所以不同版本启动时看到的版本信息是自动更新的。这个设计思路很值得借鉴Banner 不是完全静态的它可以包含动态内容。默认 Banner 的打印时机是在SpringApplication.run方法中环境准备完成之后、ApplicationContext 刷新之前。这个时间点很关键意味着此时配置文件的属性已经可以读取了但 Bean 还没有完全初始化。所以如果你在 Banner 里引用了某个自定义配置项它是能生效的但不要指望能拿到数据库连接之类的运行时资源。2.2 自定义 Banner 的加载优先级当你决定自定义 Banner 时SpringBoot 会按照一套固定的优先级去查找资源。理解这个优先级能帮你避免“明明放了文件却不生效”的困惑。查找顺序大致是这样的首先检查spring.banner.location属性指定的位置如果配置了就优先加载这个。如果没有配置该属性则在 classpath 根目录下依次查找banner.txt、banner.gif、banner.jpg、banner.png。如果以上都没找到再检查spring.banner.image.location指定的图片位置。如果还是没有最后回退到内置的默认 Banner。这里有个容易踩的坑banner.txt的优先级高于图片文件。也就是说如果你同时放了banner.txt和banner.png文本文件会赢。很多人放了图片发现没显示就是因为 classpath 里还残留着一个banner.txt。另外spring.banner.location可以指向 classpath 之外的路径比如file:./config/banner.txt这在需要动态替换 Banner 的部署场景里很有用。但要注意路径写法classpath 资源用classpath:前缀文件系统资源用file:前缀不写前缀默认按 classpath 处理。2.3 字符编码与占位符替换Banner 文件默认使用 UTF-8 编码读取。如果你在 Windows 环境下用记事本编辑banner.txt保存时可能默认是 GBK启动后就会看到乱码。这个问题我遇到过好几次排查半天才发现是编码问题。解决办法很简单用支持指定编码的编辑器保存为 UTF-8或者在配置里显式指定编码。SpringBoot 还支持在 Banner 中使用占位符格式是${...}。常用的占位符包括占位符含义示例值${spring-boot.version}SpringBoot 版本3.2.0${spring-boot.formatted-version}格式化版本v3.2.0${application.version}应用版本1.0.0${application.formatted-version}格式化应用版本v1.0.0${AnsiColor.BRIGHT_GREEN}ANSI 颜色绿色${AnsiStyle.BOLD}ANSI 样式加粗这些占位符是在 Banner 打印前由Banner接口的实现类进行替换的。application.version来自MANIFEST.MF文件中的Implementation-Version如果你用 Maven 或 Gradle 构建需要确保打包时写入了版本信息。我一般会在pom.xml里配置maven-jar-plugin的manifest条目把版本号写进去这样 Banner 里就能显示真实构建版本。ANSI 颜色占位符需要终端支持 ANSI 转义序列。在 IDEA 的控制台里通常没问题但如果你把日志重定向到文件这些转义字符会变成乱码。所以我的经验是颜色可以用但不要过度依赖关键信息还是靠文字表达。3. 自定义 Banner 的完整实操流程3.1 生成 ASCII 艺术字制作 Banner 的第一步是生成字符画。手工敲是不现实的得借助工具。常用的方式有几种第一种是在线生成网站。你输入文字选择字体和宽度它会输出对应的 ASCII 字符画。这类工具很多搜“ASCII art generator”就能找到。优点是方便快捷缺点是生成的图案风格有限而且有些网站输出的字符在等宽字体下对齐会出问题。第二种是用命令行工具figlet。Linux 和 macOS 上可以直接安装Windows 上可以通过包管理器或者 WSL 使用。基本用法figlet -f slant MY APP-f参数指定字体slant是一种斜体风格还有standard、big、small、banner等多种字体可选。figlet的输出是纯 ASCII 字符兼容性最好。如果想让输出更宽或更窄可以用-w指定宽度。第三种是用toilet工具它是figlet的增强版支持颜色和更丰富的字体。用法类似toilet -f mono12 -F metal MY APP-F参数指定滤镜效果metal会生成带金属质感的字符画。不过这种带滤镜的输出可能包含非 ASCII 字符在某些终端下显示会有问题用之前最好先测试。我个人的习惯是用figlet生成基础字符画然后手工微调。因为自动生成的图案往往太宽在标准 80 列终端里会换行看起来很难受。微调主要是删减一些装饰性字符保证每行不超过 80 个字符。3.2 编写 banner.txt 文件生成好字符画之后把它保存为banner.txt放到src/main/resources目录下。这是 Maven 和 Gradle 项目的标准资源目录打包后会自动进入 classpath 根目录。一个完整的banner.txt通常包含这几部分${AnsiColor.BRIGHT_CYAN} __ __ _ _ _ | \/ |_ _| | __ _ | | __ _| |_ __ _ | |\/| | | | | |/ _ | | |/ _ | __/ _ | | | | | |_| | | (_| | | | (_| | || (_| | |_| |_|\__, |_|\__,_| |_|\__,_|\__\__,_| |___/ ${AnsiColor.BRIGHT_GREEN} :: Application :: ${application.title} :: Version :: ${application.formatted-version} :: Profile :: ${spring.profiles.active:default} :: Started at :: ${timestamp} ${AnsiColor.DEFAULT}这里有几个细节值得说明。${spring.profiles.active:default}这种写法表示如果spring.profiles.active没有配置就显示default。冒号后面的部分是默认值这个语法在 Spring 的占位符解析里是通用的。${timestamp}这个占位符不是 SpringBoot 内置的直接写会原样输出。如果你想显示启动时间需要自己实现一个Banner接口或者在 Banner 打印后单独输出。我一般不在 Banner 里放时间戳因为 Banner 打印时应用还没完全启动这个时间意义不大。另外注意${AnsiColor.DEFAULT}要放在最后把颜色重置回默认值否则后续的控制台输出都会带着颜色看起来会很乱。3.3 配置属性控制 Banner 行为SpringBoot 提供了一组属性来精细控制 Banner 的显示行为这些属性可以写在application.properties或application.yml里。属性名默认值说明spring.banner.locationclasspath:banner.txt文本 Banner 位置spring.banner.charsetUTF-8Banner 文件编码spring.banner.image.locationclasspath:banner.gif图片 Banner 位置spring.main.banner-modeconsole显示模式console/log/offspring.banner.image.width76图片 Banner 宽度字符数spring.banner.image.height自动计算图片 Banner 高度spring.banner.image.margin2图片左边距spring.banner.image.invertfalse是否反转图片颜色spring.main.banner-mode这个属性特别实用。设为off可以完全关闭 Banner适合生产环境减少日志噪音设为log会把 Banner 输出到日志文件而不是控制台适合需要保留启动记录但不希望控制台太乱的场景。我在实际项目里的做法是开发环境用console模式方便一眼看到应用信息生产环境用log模式Banner 进日志文件控制台保持干净。这个可以通过 profile 配置轻松实现spring: config: activate: on-profile: dev main: banner-mode: console --- spring: config: activate: on-profile: prod main: banner-mode: log3.4 图片 Banner 的处理逻辑除了文本SpringBoot 还支持图片 Banner。它会把图片转换成 ASCII 字符画来显示。支持的格式有 GIF、JPG、PNG。图片 Banner 的转换逻辑大致是读取图片像素根据亮度映射到不同的字符然后按指定宽度输出。亮度高的像素用稀疏字符如.、亮度低的用密集字符如、#。这样就能在终端里还原出图片的轮廓。配置图片 Banner 时spring.banner.image.width控制输出宽度默认 76 个字符。这个值不要设太大否则终端会换行。spring.banner.image.height如果不设会根据图片宽高比自动计算。spring.banner.image.invert用于深色背景终端把颜色映射反过来。不过说实话图片 Banner 在实际项目里用得不多。一是转换后的效果往往不如手工设计的文本 Banner 精致二是图片文件会增加打包体积三是不同终端的字符宽度不一致显示效果很难保证。我试过几次之后还是回到了文本 Banner。4. 进阶玩法与工程化实践4.1 编程式自定义 Banner如果配置文件的方式满足不了需求比如你想根据运行环境动态生成 Banner 内容可以实现Banner接口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()); out.println(); out.println( Application: appName); out.println( Profile: profile); out.println( Java: System.getProperty(java.version)); out.println(); } }然后在启动类里替换默认 BannerSpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication app new SpringApplication(MyApplication.class); app.setBanner(new CustomBanner()); app.run(args); } }这种方式的好处是可以在 Banner 里输出任何运行时能获取到的信息比如 JVM 参数、系统属性、环境变量等。我在一个需要严格审计启动信息的项目里用过这招把启动用户、主机名、JVM 堆大小都打出来运维排查问题时非常方便。注意printBanner方法的out参数是PrintStream你可以直接往里写内容。但不要在这里做耗时操作因为 Banner 打印是在启动早期阻塞太久会影响启动速度。4.2 Banner 与构建流程集成把 Banner 和构建流程结合起来可以实现版本号、构建时间等信息的自动更新。以 Maven 为例可以用maven-resources-plugin的过滤功能build resources resource directorysrc/main/resources/directory filteringtrue/filtering includes includebanner.txt/include /includes /resource /resources /build然后在banner.txt里使用 Maven 属性占位符:: Version :: ${project.version} :: Built at :: ${maven.build.timestamp}这样每次构建时Banner 里的版本号和时间戳会自动更新。但要注意开启过滤后banner.txt里的${...}会被 Maven 先处理一遍SpringBoot 的占位符可能被误替换。解决办法是用不同的分隔符或者只对特定文件开启过滤。Gradle 的话可以用processResources任务配合expand方法processResources { filesMatching(banner.txt) { expand(project.properties) } }这个配置会让banner.txt里的${project.version}等占位符在构建时被替换。同样要注意和 SpringBoot 占位符的冲突问题。4.3 多环境 Banner 切换一个常见的需求是不同环境显示不同的 Banner。比如开发环境显示彩色大字生产环境显示简洁的版本信息。实现方式有几种第一种是用 profile 特定的配置文件指定不同的spring.banner.location# application-dev.yml spring: banner: location: classpath:banner-dev.txt # application-prod.yml spring: banner: location: classpath:banner-prod.txt第二种是在 Banner 文件里用条件占位符。SpringBoot 的占位符支持默认值语法但不支持条件判断。如果想根据 profile 显示不同内容还是得靠编程式 Banner。第三种是用 Maven 或 Gradle 的资源过滤在构建时根据 profile 替换 Banner 内容。这种方式适合 Banner 差异不大的场景。我一般用第一种方式简单直接不同环境的 Banner 文件分开维护互不干扰。4.4 Banner 在微服务架构中的实践在微服务架构里服务数量多启动日志混杂。Banner 可以作为一个轻量的服务标识手段。我的做法是在每个服务的banner.txt里加上服务名和端口号${AnsiColor.BRIGHT_YELLOW} _____ _ _ | ___| _ _ __ ___| |__ ___| |_ | |_ | | | | _ \ / __| _ \ / _ \ __| | _|| |_| | | | | (__| | | | __/ |_ |_| \__,_|_| |_|\___|_| |_|\___|\__| ${AnsiColor.BRIGHT_GREEN} :: Service :: user-service :: Port :: ${server.port} :: Profile :: ${spring.profiles.active:default} ${AnsiColor.DEFAULT}这样在容器日志里一眼就能区分是哪个服务在启动。配合日志采集系统还可以基于 Banner 内容做启动事件的监控告警。但要注意Banner 输出到日志时ANSI 颜色转义字符会变成乱码。所以如果生产环境用log模式建议去掉颜色占位符或者用spring.banner.image.invert之类的属性控制。我通常准备两份 Banner 文件一份带颜色给控制台用一份纯文本给日志用。5. 常见问题与排查技巧实录5.1 Banner 不生效的排查思路这是最常见的问题明明放了banner.txt启动时还是显示默认的 Spring Boot 图案。排查步骤可以按这个顺序来首先确认文件位置。banner.txt必须在 classpath 根目录下也就是src/main/resources/banner.txt。如果你放在了子目录里比如src/main/resources/config/banner.txt默认是找不到的需要配置spring.banner.locationclasspath:config/banner.txt。其次检查文件编码。用file命令或者编辑器的编码检测功能确认是 UTF-8。如果是 GBK改成 UTF-8 再试。然后检查是否有多个 Banner 文件冲突。比如同时存在banner.txt和banner.png文本文件会优先。如果你期望显示图片就得把banner.txt删掉或改名。最后检查spring.main.banner-mode是否被设为了off。这个属性可能写在配置文件里也可能通过命令行参数传入还可能被环境变量覆盖。用--debug启动可以看到配置属性的来源。5.2 占位符不替换的问题有时候 Banner 里的${application.version}原样输出了没有替换成实际版本号。原因通常是MANIFEST.MF里没有Implementation-Version条目。Maven 项目需要在pom.xml里配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId configuration archive manifest addDefaultImplementationEntriestrue/addDefaultImplementationEntries /manifest /archive /configuration /pluginGradle 项目在jar任务里配置jar { manifest { attributes( Implementation-Version: project.version ) } }配置后重新打包application.version就能正确替换了。注意这个占位符读取的是MANIFEST.MF不是pom.xml或build.gradle里的版本号所以必须确保打包时写入了 manifest。5.3 终端显示乱码或对齐错位ASCII 字符画在不同终端下的显示效果可能不一样主要原因是字体不是等宽字体或者终端宽度不够导致换行。解决办法一是确保终端使用等宽字体比如 Consolas、Monaco、Fira Code 等二是控制 Banner 每行宽度不超过 80 个字符兼容大多数终端三是避免使用非 ASCII 字符比如某些 Unicode 制表符在不同终端下宽度可能不一致。如果 Banner 里有中文还要注意中文字符的宽度问题。在等宽字体下一个中文通常占两个英文字符的宽度如果混排时没考虑这点对齐就会乱。我的建议是 Banner 里尽量只用 ASCII 字符中文信息放在 Banner 下方的普通文本行里。5.4 常见问题速查表问题现象可能原因解决方法显示默认 Banner文件位置不对放到src/main/resources/根目录显示乱码文件编码不是 UTF-8用编辑器另存为 UTF-8占位符未替换MANIFEST 缺少版本信息配置打包插件写入 manifestBanner 不显示banner-mode 为 off检查配置改为 console 或 log图片 Banner 变形终端字符宽高比不一致调整spring.banner.image.width颜色转义字符乱码输出到不支持 ANSI 的地方日志模式下去掉颜色占位符启动变慢Banner 文件过大精简字符画控制行数和宽度5.5 几个实操避坑心得第一个坑IDEA 里运行正常打包后 Banner 不生效。这通常是因为 IDEA 运行时 classpath 包含了src/main/resources而打包后资源文件的位置变了。检查pom.xml或build.gradle里有没有排除banner.txt或者资源目录配置是否正确。第二个坑SpringBoot 版本升级后 Banner 占位符失效。不同版本的 SpringBoot 支持的占位符可能不同比如${spring-boot.version}在 2.x 和 3.x 里都能用但某些格式化占位符的行为有变化。升级版本后最好重新测试一下 Banner 输出。第三个坑Banner 里的${}和 Maven 过滤冲突。如果开启了资源过滤Maven 会先处理${...}导致 SpringBoot 的占位符被替换成空值。解决办法是在banner.txt里用...作为 Maven 占位符需要配置maven-resources-plugin的delimiter或者对banner.txt关闭过滤。第四个坑Banner 输出到日志文件时带上了 ANSI 转义字符。这些字符在文本编辑器里看起来是乱码影响日志可读性。如果生产环境用log模式建议单独准备一份不带颜色的 Banner 文件通过 profile 切换。6. 一些扩展思路与个人体会Banner 这个功能看似边缘但用好了能解决不少实际问题。除了前面说的环境标识、版本追踪还可以做一些有意思的扩展。比如结合启动参数动态显示 Banner。通过实现Banner接口读取args里的自定义参数在 Banner 里显示本次启动的特殊配置。这在需要频繁调整参数的调试场景里很有用一眼就能确认参数有没有生效。再比如把 Banner 和健康检查结合。在 Banner 里输出应用的关键依赖状态比如数据库连接、缓存服务等。但要注意 Banner 打印时机很早此时很多 Bean 还没初始化能获取的信息有限。如果确实需要可以在ApplicationRunner里输出补充信息。还有一个思路是用 Banner 做简单的启动耗时统计。在main方法开始时记录时间戳在 Banner 打印时计算差值输出“启动耗时 XX ms”。这个对性能优化有帮助能快速判断启动是否变慢。我个人在实际操作中的体会是Banner 的价值不在于好看而在于信息传达。一个设计良好的 Banner 应该让运维和开发在启动瞬间就能获取关键信息——这是什么服务、什么版本、什么环境、什么端口。颜色和图案是锦上添花但不要为了花哨牺牲可读性。另外Banner 文件要纳入版本管理和代码一起维护避免不同环境不一致导致排查困难。最后分享一个小技巧如果你不确定 Banner 文件有没有被正确加载可以在启动时加上--debug参数SpringBoot 会输出自动配置报告其中包含 Banner 的加载信息。或者直接看启动日志的前几行Banner 内容会原样打印出来有没有生效一目了然。