spring boot自定义banner很简单,只需要在resources目录下添加 banner.txt 文件即可。
banner.txt内容如下:
_ooOoo_
o8888888o
88" . "88
(| ^_^ |)
O\ = /O
____/`---'\____
.' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' | |
\ .-\__ `-` ___/-. /
___`. .' /--.--\ `. . ___
.""'< `.___\_<|>_/___.' >'"".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `-. \_ __\ /__ _/ .-` / /
========`-.____`-.___\_____/___.-`____.-'========
'=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我佛慈悲,请赐我一段姻缘
启动工程,控制台就会打印此banner。
1、banner打印代码调用点在 SpringApplication#run(java.lang.String...) 方法中
//源码位置org.springframework.boot.SpringApplication#run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
// 打印banner
Banner printedBanner = printBanner(environment);
}
2、Banner printedBanner = printBanner(environment); 源码
// printBanner源码。源码位置 org.springframework.boot.SpringApplication.printBanner
private Banner printBanner(ConfigurableEnvironment environment) {
// 是否关闭banner打印,默认不关闭
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
// getClassLoader()返回AppClassLoader
// 使用AppClassLoader创建ResourceLoader实例对象
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
// 是否打印到日志文件中,默认打印到控制台
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
// bannerPrinter打印banner
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
3、bannerPrinter.print(environment, this.mainApplicationClass, System.out); 源码
// 源码位置 org.springframework.boot.SpringApplicationBannerPrinter.print(org.springframework.core.env.Environment, java.lang.Class>, java.io.PrintStream)
Banner print(Environment environment, Class> sourceClass, PrintStream out) {
// 获取banner
Banner banner = getBanner(environment);
// 打印banner
banner.printBanner(environment, sourceClass, out);
return new SpringApplicationBannerPrinter.PrintedBanner(banner, sourceClass);
}
3.1 Banner banner = getBanner(environment); 源码
// 源码位置 org.springframework.boot.SpringApplicationBannerPrinter.getBanner
private Banner getBanner(Environment environment) {
// Banners是对Banner的包装,一个Banners对象可以包含多个Banner
Banners banners = new Banners();
// 如果定义了图片banner,添加到banners中
banners.addIfNotNull(getImageBanner(environment));
// 如果定义了文本banner,添加到banners中
// 本教程定义了一个文本类型的banner
banners.addIfNotNull(getTextBanner(environment));
// 如果存在自定义的banner,返回包装对象Banners
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
// 如果没有自定义banner,返回默认的banner
return DEFAULT_BANNER;
}
3.1.1 getTextBanner(environment) 获取文本banner源码
// 源码位置 org.springframework.boot.SpringApplicationBannerPrinter.getTextBanner
private Banner getTextBanner(Environment environment) {
/**
* BANNER_LOCATION_PROPERTY = "spring.banner.location"
* DEFAULT_BANNER_LOCATION = "banner.txt"
* 获取application.properties中配置的spring.banner.location。如果没配置spring.banner.location,返回banner.txt
*/
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
// 创建resource
Resource resource = this.resourceLoader.getResource(location);
// 如果存在banner.txt文件,返回new ResourceBanner(resource);
// 本教程存在banner.txt
if (resource.exists()) {
return new ResourceBanner(resource);
}
return null;
}
3.2 banner.printBanner(environment, sourceClass, out); 源码
// 源码位置 org.springframework.boot.SpringApplicationBannerPrinter.Banners.printBanner
@Override
public void printBanner(Environment environment, Class> sourceClass, PrintStream out) {
// 循环获取自定义banner,调用printBanner方法
for (Banner banner : this.banners) {
banner.printBanner(environment, sourceClass, out);
}
}
3.2.1 banner.printBanner(environment, sourceClass, out); 源码
// 源码位置 org.springframework.boot.ResourceBanner.printBanner
@Override
public void printBanner(Environment environment, Class> sourceClass, PrintStream out) {
try {
// 读取banner.txt的文本内容。
String banner = StreamUtils.copyToString(this.resource.getInputStream(),
environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));
// 处理占位符,这个先忽略,后面再讲
for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) {
banner = resolver.resolvePlaceholders(banner);
}
// 这句代码就是打印banner,默认打印到控制台
out.println(banner);
}
catch (Exception ex) {
logger.warn(LogMessage.format("Banner not printable: %s (%s: '%s')", this.resource, ex.getClass(),
ex.getMessage()), ex);
}
}
printBanner(Environment environment, Class> sourceClass, PrintStream out) 方法中 banner 变量储存的是banner.txt的文本内容。out.println(banner);是打印文本到控制台。至此,控制台就会输出banner了。
// 处理占位符,这个先忽略,后面再讲
for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) {
banner = resolver.resolvePlaceholders(banner);
}
这段代码主要是处理banner中的占位符。举个例子说明:
_ooOoo_
o8888888o
88" . "88
(| ^_^ |)
O\ = /O
____/`---'\____
.' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' | |
\ .-\__ `-` ___/-. /
___`. .' /--.--\ `. . ___
.""'< `.___\_<|>_/___.' >'"".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `-. \_ __\ /__ _/ .-` / /
========`-.____`-.___\_____/___.-`____.-'========
'=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我佛慈悲,请赐我一段${my.marriage}。
1、resources目录下新建banner-two.txt。banner-two.txt内容如下,使用了${my.marriage}获取环境属性。
2、application.properties中配置
# 修改banner
spring.banner.location=banner-two.txt
my.marriage=姻缘
3、启动工程,控制台输出如下:
_ooOoo_
o8888888o
88" . "88
(| ^_^ |)
O\ = /O
____/`---'\____
.' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' | |
\ .-\__ `-` ___/-. /
___`. .' /--.--\ `. . ___
.""'< `.___\_<|>_/___.' >'"".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `-. \_ __\ /__ _/ .-` / /
========`-.____`-.___\_____/___.-`____.-'========
'=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我佛慈悲,请赐我一段姻缘。
banner中的${my.marriage}被替换“姻缘”。替换操作由 banner = resolver.resolvePlaceholders(banner); 完成。
断点调试banner = resolver.resolvePlaceholders(banner); 源码,最终进入如下方法中
org.springframework.util.PropertyPlaceholderHelper#parseStringValue()
parseStringValue()源码分析
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set visitedPlaceholders) {
/**
* value是banner文本内容
* this.placeholderPrefix是${
* 查找banner文本中${的起始位置
*/
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
// 这个while循环很复杂,banner中可能存在多个占位符的情况。本教程仅讲解有一个占位符的情形
while (startIndex != -1) {
// 找到占位符结束标识 } 的位置
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 通过${、}切割出占位符,本教程placeholder是my.marriage
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// placeholderResolver.resolvePlaceholder(placeholder)l;源码是调用getProperty方法获取环境变量my.marriage的值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
// 将banner中的占位符${my.marriage}替换为“姻缘”
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
// 返回替换了占位符后的文本
return result.toString();
}