自定义banner

Spring Boot 默认打印的banner是这样的,Java工程师看都看腻了。
Spring Boot banner详解-LMLPHP

一般的公司如果有自己脚手架,都会选择自定义banner,放一个公司Logo或者框架别名。

简易版banner

首先生成一个自己的banner,比如我生成的
Spring Boot banner详解-LMLPHP

把生成的内容copy到txt中,命名为"banner.txt"(UTF-8),然后放到resources下。
启动Spring Boot 即可看到效果。

Spring Boot banner详解-LMLPHP

自定义banner路径

上述的banner.txt 只能放在resources根目录下,不能在resources子目录或其他的目录,使用spring.banner.location指定该文件的路径,如果该文件不是UTF-8编码,使用spring.banner.charset指定文件编码,比如我将文件放到resources的子目录static中。

Spring Boot banner详解-LMLPHP

自定义banner 样式

光一个Logo也还是太单调,如果能再打印个Spring Boot 版本、应用程序版本就更好了,Spring Boot 都给我们提供了相关变量,可以在banner.txt中使用。

  • ${application.version} 应用程序版本
  • ${application.formatted-version} 格式化的应用程序版本,前缀是v
  • ${spring-boot.version} Spring Boot框架版本
  • ${spring-boot.formatted-version} 格式化的Spring Boot框架版本,前缀是v
  • ${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME})
    给文字加颜色,加背景,加样式,NAME的值可以在这里获取ansi
  • ${application.title} 应用程序的标题

如图我给banner 加上了颜色,加上了版本

Spring Boot banner详解-LMLPHP
配置如下:

${AnsiColor.GREEN}
  /$$$$$$   /$$$$$$  /$$$$$$$  /$$   /$$       /$$$$$$ /$$$$$$$$ /$$$$$$   /$$$$$$  /$$     /$$ /$$$$$$   /$$$$$$  /$$     /$$
 /$$__  $$ /$$__  $$| $$__  $$| $$$ | $$      |_  $$_/|__  $$__//$$__  $$ /$$__  $$|  $$   /$$//$$__  $$ /$$__  $$|  $$   /$$/
| $$  \__/| $$  \__/| $$  \ $$| $$$$| $$        | $$     | $$  | $$  \__/| $$  \ $$ \  $$ /$$/| $$  \__/| $$  \ $$ \  $$ /$$/
| $$      |  $$$$$$ | $$  | $$| $$ $$ $$ /$$$$$$| $$     | $$  |  $$$$$$ | $$$$$$$$  \  $$$$/ |  $$$$$$ | $$$$$$$$  \  $$$$/
| $$       \____  $$| $$  | $$| $$  $$$$|______/| $$     | $$   \____  $$| $$__  $$   \  $$/   \____  $$| $$__  $$   \  $$/
| $$    $$ /$$  \ $$| $$  | $$| $$\  $$$        | $$     | $$   /$$  \ $$| $$  | $$    | $$    /$$  \ $$| $$  | $$    | $$
|  $$$$$$/|  $$$$$$/| $$$$$$$/| $$ \  $$       /$$$$$$   | $$  |  $$$$$$/| $$  | $$    | $$   |  $$$$$$/| $$  | $$    | $$
 \______/  \______/ |_______/ |__/  \__/      |______/   |__/   \______/ |__/  |__/    |__/    \______/ |__/  |__/    |__/
${AnsiColor.DEFAULT}
::Spring Boot Version: ${AnsiColor.RED}${spring-boot.formatted-version}${AnsiColor.DEFAULT}
::Application Version: ${AnsiColor.RED}${application.formatted-version}${AnsiColor.DEFAULT}

使用图片做banner

在Spring Boot 3.x版本中已经不被支持

编码方式定义banner

自定义一个CustomBanner类,实现Banner接口,如:

import org.springframework.boot.Banner;
import org.springframework.core.env.Environment;

import java.io.PrintStream;

public class CustomBanner implements Banner {

    public static final String BANNER = "  /$$$$$$   /$$$$$$  /$$$$$$$  /$$   /$$       /$$$$$$ /$$$$$$$$ /$$$$$$   /$$$$$$  /$$     /$$ /$$$$$$   /$$$$$$  /$$     /$$\n" +
            " /$$__  $$ /$$__  $$| $$__  $$| $$$ | $$      |_  $$_/|__  $$__//$$__  $$ /$$__  $$|  $$   /$$//$$__  $$ /$$__  $$|  $$   /$$/\n" +
            "| $$  \\__/| $$  \\__/| $$  \\ $$| $$$$| $$        | $$     | $$  | $$  \\__/| $$  \\ $$ \\  $$ /$$/| $$  \\__/| $$  \\ $$ \\  $$ /$$/ \n" +
            "| $$      |  $$$$$$ | $$  | $$| $$ $$ $$ /$$$$$$| $$     | $$  |  $$$$$$ | $$$$$$$$  \\  $$$$/ |  $$$$$$ | $$$$$$$$  \\  $$$$/  \n" +
            "| $$       \\____  $$| $$  | $$| $$  $$$$|______/| $$     | $$   \\____  $$| $$__  $$   \\  $$/   \\____  $$| $$__  $$   \\  $$/   \n" +
            "| $$    $$ /$$  \\ $$| $$  | $$| $$\\  $$$        | $$     | $$   /$$  \\ $$| $$  | $$    | $$    /$$  \\ $$| $$  | $$    | $$    \n" +
            "|  $$$$$$/|  $$$$$$/| $$$$$$$/| $$ \\  $$       /$$$$$$   | $$  |  $$$$$$/| $$  | $$    | $$   |  $$$$$$/| $$  | $$    | $$    \n" +
            " \\______/  \\______/ |_______/ |__/  \\__/      |______/   |__/   \\______/ |__/  |__/    |__/    \\______/ |__/  |__/    |__/    \n";

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
        printStream.println(BANNER);
    }
}

SpringApplication 中配置banner

SpringApplication springApplication = new SpringApplication(SpringBootDemoApplication.class);
springApplication.setBanner(new CustomBanner());

禁用banner

Spring Boot 提供了spring.main.banner-mode 配置,OFF-关闭banner打印,CONSOLE-使用System.out打印banner,log文件不会记录,LOG-打印到log文件

另外同样可以用springApplication.setBannerMode(Banner.Mode.OFF);方式设置banner

加载打印原理

在之前的《Spring Boot 框架整体启动流程详解》中,我们看到有一步是

//打印banner
Banner printedBanner = printBanner(environment);

这一步就是加载打印banner的核心。

private Banner printBanner(ConfigurableEnvironment environment) {
//如果bannerMode是关闭的,就会不打印banner
	if (this.bannerMode == Banner.Mode.OFF) {
		return null;
	}
	//获取资源加载器,如果当前没有就使用默认的资源加载器
	ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
			: new DefaultResourceLoader(null);
//创建一个SpringBoot应用程序的banner打印类,如果通过setBanner设置,this.banner会有值
	SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
	//如果bannerMode 是打印到log文件
	if (this.bannerMode == Mode.LOG) {
		return bannerPrinter.print(environment, this.mainApplicationClass, logger);
	}
	//默认打印到控制台
	return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

先看下如何打印到log文件:

Banner print(Environment environment, Class<?> sourceClass, Log logger) {
//获取Banner对象
	Banner banner = getBanner(environment);
	try {
	//使用log 的info 级别打印
		logger.info(createStringFromBanner(banner, environment, sourceClass));
	}
	catch (UnsupportedEncodingException ex) {
		logger.warn("Failed to create String for banner", ex);
	}
	//创建一个打印banner的装饰器bean,允许后期再次使用
	return new PrintedBanner(banner, sourceClass);
}

如何获取的Banner对象:

private Banner getBanner(Environment environment) {
//获取txt文本banner
	Banner textBanner = getTextBanner(environment);
	if (textBanner != null) {
		return textBanner;
	}
	//fallbackBanner 为前期通过setBanner设置的自定义banner
	//可见如果两者同时设置,优先使用的txt文本banner
	if (this.fallbackBanner != null) {
		return this.fallbackBanner;
	}
	//都没有,返回一个默认的SpringBootBanner
	return DEFAULT_BANNER;
}

如果获取txt文本banner:

private Banner getTextBanner(Environment environment) {
//从环境变量中获取spring.banner.location指定的banner地址,如果没有,使用banner.txt
	String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
	//获取指定的资源对象
	Resource resource = this.resourceLoader.getResource(location);
	try {
	//资源存在,并且资源路径中不包含liquibase-core
		if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
		//创建一个从源打印的banner对象,实现了Banner接口
			return new ResourceBanner(resource);
		}
	}
	catch (IOException ex) {
		// Ignore
	}
	return null;
}

获取到文本banner的ResourceBanner资源对象后,回到print(Environment environment, Class<?> sourceClass, Log logger) 这个方法中,接下来就是要把获取到banner对象打印出来,createStringFromBanner将获取到banner对象,调用其中的printBanner方法,把输出流转为UTF-8的字符串输出到log文件中。

private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
		throws UnsupportedEncodingException {
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	banner.printBanner(environment, mainApplicationClass, new PrintStream(baos));
	String charset = environment.getProperty("spring.banner.charset", "UTF-8");
	return baos.toString(charset);
}

ResourceBanner的printBanner方法:

public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
	try {
	//从流读取banner.txt 字符串,使用spring.banner.charset编码,或者UTF-8
		String banner = StreamUtils.copyToString(this.resource.getInputStream(),
				environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));
				//获取用于解析占位符的所有属性源
		for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) {
		//解析banner,使用PropertyPlaceholderHelper工具类解析
			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);
	}
}

bannerPrinter.print(environment, this.mainApplicationClass, System.out);
打印到控制台的逻辑也是一样的,只是直接输出到控制台

Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
	Banner banner = getBanner(environment);
	banner.printBanner(environment, sourceClass, out);
	return new PrintedBanner(banner, sourceClass);
}

总结

通过图来总结一下整个流程
Spring Boot banner详解-LMLPHP

06-10 00:48