最新要闻
- 【聚看点】存储一哥扛不住了!三星在售最旗舰SSD 990 PRO降至史低价
- 天天速看:15万买特斯拉 那得多“毛坯”?成本降50%是文字游戏
- 原生PCIe 5.0更安全!酷冷至尊GX1250 GOLD电源评测:满载电压偏移<0.1%
- 环球今热点:不再烧主板!ROG 2023系列内置环境光传感器:见光自动断电
- 索尼PS中国发文祝4位游戏女角节日快乐:蒂法、希里等出镜
- 全球微头条丨潍坊市寒亭区举行庆“三八”专场招聘会
- 当前热议!卡车
- 前沿热点:小心上当 国外免费软件被不良人士玩坏:收费299
- 环球消息!比亚迪开通苹果NFC数字车钥匙功能 两款车型已支持
- 魅族换新Logo了!魅族20系列官宣定档3月30日
- 每日速讯:未获版号就擅自出版网络游戏 重庆一公司被罚45万元
- 在鸡面前画条直线为何它呆住不动 科普:只是在装死
- 当猛男遇上脱毛仪:用前抗拒、用后真香
- 终身质保成笑话?女车主购入威马新车三年维保无配件
- Win11不支持拖动任务栏 强行修改注册表后:画面尴尬了
- 快播:爱吃辣的人不容易得糖尿病?是真的吗?
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
【环球报资讯】SpringBoot启动控制台的banner是怎么回事
前言
每次启动SpringBoot项目时,总是能看到控制台打印了一串字符,隐约能辨认出是“Spring”,不知大家是否也好奇过是怎么实现的,是直接打印固定的字符串,还是根据什么算法去生成的?于是闲暇无事,探究一番。
只想修改banner可以跳到文末查看
(相关资料图)
SpringBoot是怎么打印的
Banner默认实现类 SpringBootBanner
1、根据控制台打印的字符进行全局搜索,笔者选取:: Spring Boot ::
进行搜索,定位到了org.springframework.boot.SpringBootBanner
。
IDEA全局搜索:CTRL + SHIFT + R
2、进入SpringBootBanner
类,先看下注释Default Banner implementation which writes the "Spring" banner.
,说了两个信息:1、当前类是SpringBoot Banner的默认实现;2、打印的字符是“Spring”。
3、往下看,SpringBootBanner
实现了Banner
接口。Banner
包括printBanner
方法和枚举Mode
。根据Mode
中的注释和枚举值可以看出,Banner
有三种状态:关闭、打印到控制台、打印到日志。具体使用场景留待后续分析。
Banner源码
/** * Interface class for writing a banner programmatically. */@FunctionalInterfacepublic interface Banner {/** * Print the banner to the specified print stream. * @param environment the spring environment * @param sourceClass the source class for the application * @param out the output print stream */void printBanner(Environment environment, Class> sourceClass, PrintStream out);/** * An enumeration of possible values for configuring the Banner. */enum Mode {/** * Disable printing of the banner. */OFF,/** * Print the banner to System.out. */CONSOLE,/** * Print the banner to the log file. */LOG}}
4、往下看到类的属性BANNER
和SPRING_BOOT
,也能辨认出是控制台打印的那些字符。
类里面只有一个方法printBanner
,负责打印Banner字符。逻辑比较清晰,第一部分逐行打印BANNER
形成图案;第二部分打印SpringBoot版本号,总长度由STRAP_LINE_SIZE
控制。
SpringBootBanner完整代码
/** * Default Banner implementation which writes the "Spring" banner. */class SpringBootBanner implements Banner {private static final String[] BANNER = { "", " . ____ _ __ _ _"," /\\\\ / ___"_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | "_ | "_| | "_ \\/ _` | \\ \\ \\ \\"," \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " " |____| .__|_| |_|_| |_\\__, | / / / /"," =========|_|==============|___/=/_/_/_/" };private static final String SPRING_BOOT = " :: Spring Boot :: ";private static final int STRAP_LINE_SIZE = 42;@Overridepublic void printBanner(Environment environment, Class> sourceClass, PrintStream printStream) {for (String line : BANNER) {printStream.println(line);}String version = SpringBootVersion.getVersion();version = (version != null) ? " (v" + version + ")" : "";StringBuilder padding = new StringBuilder();while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {padding.append(" ");}printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),AnsiStyle.FAINT, version));printStream.println();}}
Banner核心控制类 SpringApplicationBannerPrinter
1、上节找到了负责存储和打印Banner字符的类SpringBootBanner
,现在向调用链上方继续寻找,通过CTRL + B
或者全局搜索可以发现SpringBootBanner
在SpringApplicationBannerPrinter
类中作为类变量,大概能猜测出这个SpringApplicationBannerPrinter
类是Banner打印的核心控制器。2、进入SpringApplicationBannerPrinter
类,照例先看注释Class used by SpringApplication to print the application banner.
,意思是当前类被SpringApplication
用来打印banner。
这个SpringApplication
好像有点眼熟,名字和我们SpringBoot项目的启动类有点相似,翻翻启动类的代码,想起我们就是通过SpringApplication
的run
方法启动项目,banner打印调用也是由SpringApplication
控制的,后续会详细分析。(占坑,后续SpringBoot启动流程也会出一篇博客去探讨一下)
回归正题,继续从类的属性开始看,根据名字猜测大概含义,留待后续验证:
BANNER_LOCATION_PROPERTY
:Spring配置,大概是banner文件的路径。BANNER_IMAGE_LOCATION_PROPERTY
:Spring配置,banner图片的路径(存疑,控制台难道能打印图片?)。DEFAULT_BANNER_LOCATION = "banner.txt"
:取值是txt文件,猜测是banner文件的默认位置。String[] IMAGE_EXTENSION = { "gif", "jpg", "png" }
:取值是常见图片的后缀,结合第二个属性猜测是用来对banner图片类型做限制。DEFAULT_BANNER = new SpringBootBanner()
:把上节分析的SpringBootBanner
当做Banner默认实现类ResourceLoader resourceLoader
:ResourceLoader
简单来说是Spring加载资源的统一抽象,由实现类提供具体逻辑。在Spring中读取xml配置文件加载应用上下文的ClassPathXmlApplicationContext
,就是ResourceLoader
的子类。Banner fallbackBanner
:翻译过来是回退banner
,暂时猜不出作用,等待后续填坑。
3、往下看方法,只有两个非私有方法,都是print
的重载方法,差别在于第三个参数,分别是Log logger
和PrintStream out
,代表这两个方法分别负责日志打印和控制台打印。
紧扣主题,先看负责控制台打印的方法。
Banner print(Environment environment, Class> sourceClass, PrintStream out) {Banner banner = getBanner(environment);banner.printBanner(environment, sourceClass, out);return new PrintedBanner(banner, sourceClass);}
代码很精简,第一行获取Banner
类,第二行调用Banner
的print
方法打印banner图案,最后生成PrintedBanner
并返回。
1. getBanner
getBanner源码
private Banner getBanner(Environment environment) { Banners banners = new Banners(); banners.addIfNotNull(getImageBanner(environment)); banners.addIfNotNull(getTextBanner(environment)); if (banners.hasAtLeastOneBanner()) { return banners; } if (this.fallbackBanner != null) { return this.fallbackBanner; } return DEFAULT_BANNER; }
查看getBanner
方法,首先创建Banners
,底层就是Banner
数组,由于存在控制台、日志两种打印方式,使用此类方便批量处理。
Banners源码
/** * {@link Banner} comprised of other {@link Banner Banners}. */private static class Banners implements Banner { private final List banners = new ArrayList<>(); void addIfNotNull(Banner banner) { if (banner != null) { this.banners.add(banner); } } boolean hasAtLeastOneBanner() { return !this.banners.isEmpty(); } @Override public void printBanner(Environment environment, Class> sourceClass, PrintStream out) { for (Banner banner : this.banners) { banner.printBanner(environment, sourceClass, out); }}
接着就是调用getImageBanner
和getTextBanner
方法获取Banner,如果Banner
数组不为空则返回,否则检查fallbackBanner
。
这个fallbackBanner
光看名字看不出是什么,使用CTRL+B
查看引用,发现是在SpringApplication#printBanner
里注入进来的,如下图。
继续查找this.banner
会发现,最终Banner
只能通过SpringApplicationBuilder#banner
注入。
SpringApplicationBuilder
是通过Constructor(构造器)模式实现的SpringApplication
构造器。查看banner
方法的注释,我们可以知道这里注入的Banner
实例会在没有静态banner文件时使用。回过头来,fallbackBanner
的坑填上了,它是在SpringApplicationBannerPrinter
找不到txt文件或者图片作为banner素材的时候使用。
如果fallbackBanner
也为空,则最终返回兜底方案-SpringBootBanner
。
getBanner
的结构分析完了,实际情况我们知道走的是兜底方案,也就是只要我们能让getImageBanner
、getTextBanner
或者fallbackBanner
不为空,就能改变banner打印的图案。带着这个想法,我们就去看看getImageBanner
和getTextBanner
是咋回事。
2、getImageBanner查看源码,首先environment.getProperty
读取配置spring.banner.image.location
获取图片位置。
配置文件读取若为空则遍历图片后缀数组IMAGE_EXTENSION
,采用"banner." + ext
拼接方式得到图片相对路径,并尝试加载。加载成功后会生成ImageBanner
并返回。
接收图片资源并处理打印的逻辑都封装在ImageBanner
中,后续单独写一篇文章尝试分析图片打印逻辑。
按照我们的分析,只要在配置文件中添加spring.banner.image.location
并赋值正确的图片路径,或者在resources目录下存放一张名字为“banner”、后缀是gif,jpg, png
其中之一的图片,SpringApplicationBannerPrinter
就会打印出来。注:为什么没加前缀classpath:
也可以放在resources目录下,可以查看DefaultResourceLoader#getResource
对于banner.jpg
这种location的处理逻辑。
后续章节会有打印效果。
getImageBanner源码
private Banner getImageBanner(Environment environment) { String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY); if (StringUtils.hasLength(location)) { Resource resource = this.resourceLoader.getResource(location); return resource.exists() ? new ImageBanner(resource) : null; } for (String ext : IMAGE_EXTENSION) { Resource resource = this.resourceLoader.getResource("banner." + ext); if (resource.exists()) { return new ImageBanner(resource); } } return null;}
3、getTextBanner查看源码,同样是先从配置文件中读取banner文件的location并尝试加载资源,和getImageBanner
不同的是,这里读取不到会使用默认值banner.txt
。
加载资源后有一个Resource
的限制条件!resource.getURL().toExternalForm().contains("liquibase-core")
,这里不明白这个条件的含义,只查询到了Liquibase
是一个用于跟踪、管理和应用数据库变化的开源工具。
资源校验通过后生成ResourceBanner
并返回。
getTextBanner源码
private Banner getTextBanner(Environment environment) { String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION); Resource resource = this.resourceLoader.getResource(location); try { if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) { return new ResourceBanner(resource); } } catch (IOException ex) { // Ignore } return null;}
接下来进入ResourceBanner
看下打印细节。printBanner
结构比较简单,第一部分设置banner字符集,优先读取配置spring.banner.charset
,无配置则默认设置为UTF-8
。第二部分去解析banner字符,比如将${xxx}
占位符解析成实际的值。第三部分就是调用流打印输出。
ResourceBanner#printBanner
@Overridepublic void printBanner(Environment environment, Class> sourceClass, PrintStream out) { try {// 设置banner字符集 String banner = StreamUtils.copyToString(this.resource.getInputStream(), environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));// 解析banner for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) { banner = resolver.resolvePlaceholders(banner); } out.println(banner); } catch (Exception ex) { logger.warn(LogMessage.format("Banner not printable: %s (%s: "%s")", this.resource, ex.getClass(), ex.getMessage()), ex); }}
banner打印调用方-SpringApplication
上节看完SpringApplicationBannerPrinter
,这节来寻找打印banner的调用方。
CTRL+B
查看SpringApplicationBannerPrinter#print
的引用,定位到了SpringApplication#printBanner
。源码如下。
从整体结构来看,printBanner
方法根据this.bannerMode
取值不同,执行不同的打印策略:不打印、打印到日志、打印到控制台。
那么这个
bannerMode
是怎么设置的?查看初始化的代码,默认值是CONSOLE
。继续寻找,最终定位到了SpringApplicationBuilder#bannerMode
,意味着bannerMode
只能通过构造器进行注入。
继续寻找printBanner
的调用方,定位到了SpringApplication#run(String...)
。
上面有提到过,通常我们SpringBoot项目都是去调用SpringApplication#run(Class>, String...)
去启动项目,底层是通过new
关键字创建SpringApplication
对象,最后调用SpringApplication#run(String...)
完成一系列的资源初始化。
所以这就可以解释大多数情况下,我们的SpringBoot项目启动时都会打印那个默认的“Spring”字符。
SpringApplication#printBanner源码
private Banner printBanner(ConfigurableEnvironment environment) { if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(null); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}
如何修改项目启动的banner
修改banner打印策略
经上分析,banner打印策略包括控制台、日志、不打印。
1. 隐式默认策略是控制台
,只需大多数情况一样,项目启动类通过SpringApplication.run(DistinctAppUserServiceApplication.class, args);
启动,无需指定。
2. 显式注入通过SpringApplicationBuilder
构造器显式注入banner打印策略。
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { new SpringApplicationBuilder(DemoApplication.class)// Banner.Mode.LOG 打印到日志// Banner.Mode.OFF 不打印 .bannerMode(Banner.Mode.CONSOLE) .run(args); }}
打印效果打印到控制台
打印到日志:INFO
级别
修改banner内容
文本
方式一:在src/main/resources
下新建banner.txt
,里面放入想要打印的内容即可。
方式二:修改配置文件
spring: banner: location: file/bannerText.txt #文件位置 src/main/resources/file/bannerText.txt
内容生成网站文字转换成符号:http://patorjk.com/software/taaghttp://life.chacuo.net/convertfont2char图片转换成符号:https://www.bootschool.net/ascii-art
图片
和文本方式相同,但是图片类型有限制,只能是以下三种gif,、jpg、png
。方式一:在src/main/resources
下新建banner.png
,里面放入想要打印的内容即可。
方式二:修改配置文件
spring: banner: image: location: file/bannerImage.png #文件位置 src/main/resources/file/bannerImage.png
打印效果
后语
本篇文章干货不多,主要记录探究问题的心路历程,锻炼文笔,若观看文章过程有任何不适,敬请斧正。
关键词:
-
环球新资讯:AppUploader教程:如何注册账号并激活AppUploader
在上一篇文章中,我们介绍了如何下载和安装AppUploader。但是,在使用该工具之前,您需要注册一个账号并...
来源: -
全球快看点丨Android工程接入UnityLibrary工程
目录结构Unity工程指UnityLibrary目录下文件;安卓工程指app目录下文件;整体指App目录下不包括app和Uni...
来源: 【环球报资讯】SpringBoot启动控制台的banner是怎么回事
环球新资讯:AppUploader教程:如何注册账号并激活AppUploader
全球快看点丨Android工程接入UnityLibrary工程
【聚看点】存储一哥扛不住了!三星在售最旗舰SSD 990 PRO降至史低价
天天速看:15万买特斯拉 那得多“毛坯”?成本降50%是文字游戏
原生PCIe 5.0更安全!酷冷至尊GX1250 GOLD电源评测:满载电压偏移<0.1%
环球今热点:不再烧主板!ROG 2023系列内置环境光传感器:见光自动断电
索尼PS中国发文祝4位游戏女角节日快乐:蒂法、希里等出镜
全球微头条丨潍坊市寒亭区举行庆“三八”专场招聘会
k8s-安全机制
Docker全家桶入门到进阶教程,Docker快速上手
焦点滚动:Docker入门到高级教程(一)Docker 的用途
当前热议!卡车
前沿热点:小心上当 国外免费软件被不良人士玩坏:收费299
环球消息!比亚迪开通苹果NFC数字车钥匙功能 两款车型已支持
魅族换新Logo了!魅族20系列官宣定档3月30日
每日速讯:未获版号就擅自出版网络游戏 重庆一公司被罚45万元
在鸡面前画条直线为何它呆住不动 科普:只是在装死
每日报道:qiankun 的使用
当前热议!Linux常用的20个命令(下)
【天天播资讯】Linux网卡驱动程序
讯息:(数据库系统概论|王珊)第十一章并发控制-第一节:并发控制概述
【环球速看料】如何轻松学习网页设计和网页编程?
当猛男遇上脱毛仪:用前抗拒、用后真香
终身质保成笑话?女车主购入威马新车三年维保无配件
Win11不支持拖动任务栏 强行修改注册表后:画面尴尬了
快播:爱吃辣的人不容易得糖尿病?是真的吗?
天天观焦点:你的护照又升值了!我国与马尔代夫互免签生效 浏览量暴增200%
环球时讯:凌云B股(900957)3月8日主力资金净卖出558.00元
全球观察:跨境电商卖家如何应对拒付、盗卡
热推荐:前端设计模式——观察者模式
热消息:代码审计之旅之百家CMS
环球头条:Paxos算法理解与java实现
环球消息!5、Redis慢日志和key有效期
今日聚焦!锐龙7000无缘单条48GB DDR5内存!点亮后却无法启动
每日热门:卖断货!湖北最强汽车补贴火了 有人“从业十年没见过”
每日热门:4.5级后广东河源再次发生3.4级地震:官方科普地震来了怎么办
全球观察:全年出货量仅2.6亿 PC电脑透心凉:复苏要等Win12
全球通讯!ChatGPT版佛祖爆火出圈:施主、说出你的烦恼
GO语言学习笔记-数据篇 Study for Go ! Chapter four - Data
全球速读:Go 数据结构
精彩看点:如何搞定MySQL锁(全局锁、表级锁、行级锁)?这篇文章告诉你答案!太TMD详细了!!!
最新资讯:vue组件更新引起组件更新的原因,如何引发组件的更新
播报:【JavaScript UI库和框架】上海道宁与Webix为您提供用于跨平台Web应用程序开发的JS框架及UI小部件
全球速看:平板电脑怎么连接wifi
8元保号没了!中国移动广东出手:最便宜4G套餐撤掉 同步下架5G全家享套餐
319元 小米米家智能直流变频塔扇2上市:吹一夏天电费仅0.65元
快看点丨燃油、纯电、混动:买轻卡你会选择谁?
柳宗元最著名的十首诗是什么?柳宗元在柳州的故事
天天通讯!基德谈欧文末节独砍17分:这就是他 他喜欢帮助他的球队赢球
天天微速讯:3张思维导图读懂 《钢铁是怎样炼成的》
VUE定时器任务(每天12点执行)
信息:mysql invalid conn排查
全球观速讯丨CNStack 多集群服务:基于 OCM 打造完善的集群管理能力
即时看!关于docker中-容器的管理操作-删除
韩国西江大学留学条件和费用是什么?韩国西江大学怎么样?
代课老师的养老保险什么时候启动?代课教师养老保险政策有哪些?
焦点热门:可抵抖音干半年!TVB淘宝直播间首播销售额达2350万
全球看点:一年脱轨1000次!美国俄亥俄州再发生铁路事故 卡车撞上火车
满满正能量!小学生拾得价值16万黄金后续:物归原主、获助学礼包
向残疾熊猫福菀泼水3游客身份未确定:初步断定为3女娃
一键开盖 耐热抗摔:哈尔斯Tritan材质单手开盖水杯15.9元发车
光棍节的由来和含义是什么?光棍节活动策划书
办公室副主任是什么级别?办公室副主任年度述职报告
手机厂商钟情的黄色配色 原来诺基亚十年前就有了
大小仅3.7GB:大佬基于Win10魔改经典WinXP系统
每日快报!为眼睛降低ISO感光度!万新偏光太阳镜大促:39.4到手 原价139元
焦点速递!跑车底盘+大电池!开了两天哪吒S 想把我的油车卖了
行走的27寸超大平板!小度推出添添闺蜜机 首发4999元
win7电脑怎么开启telnet命令?telnet命令的作用是什么?
暖暖环游世界怎么看收集度?暖暖环游世界兑换码2023
Win10系统怎么安装杜比音效驱动?杜比音效和普通音效有什么区别
中兴天机7哪个版本音质最好?中兴天机7手机参数
iphone怎么设置勿扰模式?iphone灵动岛怎么养宠物?
Liunx Vim常用命令
天天看点:C++笔记--控制语句
Windows 11提示“无法枚举容器中的对象。”
环球速读:A股异动 | 上海电影涨7% 拟收购上影元文化51%股权 拓展IP运营业务
视讯!国产CPU龙芯3A6000上半年流片明年出货 已评估7nm工艺
泰国电动车市场被国产车包了!2月销量哪吒、比亚迪双霸榜
5499元 华硕无畏15i轻薄本开卖:12核i5、2.8K 120Hz OLED好屏
不得杀疯了?曝比亚迪海鸥4月上市:仅6.58万起
世界快消息!苹果等级森严:标准版iPhone 15将不支持全天候显示功能
【世界速看料】无穷大符号
Spring Boot + MybatisX = 王炸!!
每日消息!(数据库系统概论|王珊)第十章数据库恢复技术:习题
焦点速读:前端设计模式——发布订阅模式
微资讯!《寂静岭2:重制版》新版护士形象曝光!身材依旧火辣
今热点:苹果、谷歌抢市场 Windows份额创美国史低:绝对垄断没了
NVIDIA紧急推送531.26修复补丁:解决N卡CPU占用异常问题
2月销量3863台 长安马自达CX-50本月下线:还能救市吗?
24岁高颜值女孩回应大学毕业养猪:父母支持 年薪10万
世界热讯:一次惨痛教训让我写了个Windows定期备份文件脚本
天天微速讯:模块简介、模块的两种导入语句、导入文件的补充、判断文件类型、模块的查找顺序
全球热点!自动化离线交付在云原生的应用和思考
今日要闻!Python常见面试题009. 元组和列表有什么区别
每日速递:地图标准先行 自动驾驶识途
大疆发布Ronin 4D Flex分体拓展系统:手持重量仅1/3 实现运镜自由
环球速看:你真离不开苹果!全球最畅销手机Top 10:iPhone无敌 安卓阵营被摩擦
全球热讯:落后中国几十年!H3运载火箭发射失败自毁 日本首相社交平台谢罪