一. banner概述

Spring banner是Spring Boot应用启动时在控制台显示的横幅内容,通常包含ASCII艺术字、项目信息等,用于增强应用标识或展示版本信息。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.7.18)

一般SpringBoot有一个默认banner,同时SpringBoot也支持对banner进行自定义设置。

演示环境

java: 11
springboot: 2.7.18

二. 自定义banner

1. 文本
  1. 自定义文本banner时,只需要在resources下新建一个名为banner.txt的文件,然后编辑banner的文本即可,如下所示。

                       _ooOoo_
                      o8888888o
                      88" . "88
                      (| -_- |)
                      O\  =  /O
                   ____/`---'\____
                 .'  \\|     |//  `.
                /  \\|||  :  |||//  \
               /  _||||| -:- |||||_  \
               |   | \\\  -  /// |   |
               | \_|  ''\---/''  |   |
               \  .-\__  `-`  ___/-. /
             ___`. .'  /--.--\  `. . __
          ."" '<  `.___\_<|>_/___.'  >'"".
         | | :  `- \`.;`\ _ /`;.`/ - ` : | |
         \  \ `-.   \_ __\ /__ _/   .-` /  /
    ======`-.____`-.___\_____/___.-`____.-'======
                       `=---='
               佛祖保佑        永无BUG
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
  2. 如果自定义banner的文件名不是banner.txt,则需要通过在配置文件中配置spring.banner.location,如下所示。

    spring.banner.location: favorite.txt
    
2. 图片
  1. springboot还允许我们自定义图片banner。并且如果两种banner同时存在,则先输出图片banner、再输出文本banner。
  2. 默认地,springboot将从classpath类路径下获取banner.gif、banner.jpg、banner.png作为图片banner。当然也可以通过在配置文件中配置spring.banner.image.location来指定图片的位置。
  3. 在输出图片banner时,springboot将会把图片转化成ASCII艺术画输出,而非无脑式地将图片输出。
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
3. 兜底banner

SpringBoot自定义banner也支持通过实现Banner接口的方式,然后通过setBanner进行添加,一般这种方式添加的banner叫做兜底banner。

public class MyBanner implements Banner {
    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        System.out.println("This is 兜底 banner");
    }
}
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Main.class);
        springApplication.setBanner(new MyBanner());
        springApplication.run(args);
    }
}

运行结果:

10:48:33.316 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@49a2f430
This is 兜底 banner
2025-11-10 10:48:33.692  INFO 11488 --- [  restartedMain] org.example.Main                         : Starting Main using Java 11.0.15.1 on LAPTOP-8BL7V074 with PID 11488 (D:\project\springboot_demo\target\classes started by chenli in D:\project\springboot_demo)
2025-11-10 10:48:33.692  INFO 11488 --- [  restartedMain] org.example.Main                         : No active profile set, falling back to 1 default profile: "default"
2025-11-10 10:48:33.739  INFO 11488 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-11-10 10:48:33.739  INFO 11488 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2025-11-10 10:48:34.873  INFO 11488 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
4. placeholder占位符

springboot允许我们在banner中使用${}格式的占位符,但仅限于文本banner。常见的内置的占位符有应用版本、springboot版本、应用名。

占位符中文名占位符英文名
应用版本${application.version} 或 ${application.formatted-version}
springboot版本${spring-boot.version} 或 ${spring-boot.formatted-version}
应用名${application.title}

5. 关闭banner

如果项目启动时候不需要输出banner,可以选择关闭bannner,关闭banner的方式有两种:

  1. 配置文件

    spring.main.banner_mode=off
    
  2. SpringApplication类setBannerMode方法设置

    public class Main {
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(Main.class);
            // 关闭banner输出
            springApplication.setBannerMode(Banner.Mode.OFF);
            springApplication.run(args);
        }
    }
    

三. 源码解析

SpringBoot中banner输出的逻辑全部都在SpringApplication的printBanner方法中,它在启动时被run方法调用。

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. 获取banner

获取banner的逻辑实现在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;
	}

该方法的逻辑就是获取图片和文字banner,并添加到banners中,如果banners不为空则返回banners,否则返回兜底banner;如果兜底banner也不存在,则返回默认banner。

2. 打印banner

前面在getBanner()方法中获取到的banner集合被添加到banners中,类Banners是Banner的子类,在它实现的printBanner()方法中,通过遍历内部的banner集合并调用printBanner()方法对不同的banner进行打印。

@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
	for (Banner banner : this.banners) {
		banner.printBanner(environment, sourceClass, out);
	}
}

下面对不同类型bannner的打印逻辑进行分析

  • 图片banner
    图片banner被封装在ImageBanner对象中,在打印图片banner时,会对java.awt.headless的配置进行处理,然后再调用其私有方法printBanner真正输出图片banner。

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
    	try {
    		if (System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS) == null) {
    			System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, "true");
    		}
    		printBanner(environment, out);
    	}
    	catch (Throwable ex) {
    		logger.warn(LogMessage.format("Image banner not printable: %s (%s: '%s')", this.image, ex.getClass(),
    				ex.getMessage()));
    		logger.debug("Image banner printing failure", ex);
    	}
    }
    

    以下是printBanner()私有方法,查看其真正输出图片banner的逻辑。

    private void printBanner(Environment environment, PrintStream out) throws IOException {
    		int width = getProperty(environment, "width", Integer.class, 76);
    		int height = getProperty(environment, "height", Integer.class, 0);
    		int margin = getProperty(environment, "margin", Integer.class, 2);
    		boolean invert = getProperty(environment, "invert", Boolean.class, false);
    		BitDepth bitDepth = getBitDepthProperty(environment);
    		PixelMode pixelMode = getPixelModeProperty(environment);
    		Frame[] frames = readFrames(width, height);
    		for (int i = 0; i < frames.length; i++) {
    			if (i > 0) {
    				resetCursor(frames[i - 1].getImage(), out);
    			}
    			printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out);
    			sleep(frames[i].getDelayTime());
    		}
    	}
    

    在该方法中,从配置中获取图片banner的宽高等基本样式,然后将其输出,在输出过程中将图片转为ASCII艺术图。

  • 文本banner
    文本banner被封装在ResourceBanner对象中,banner打印的逻辑在printBanner()方法中。

    该方法的逻辑首先将文本banner按照配置文件中spring.banner.charset指定的字符集转换为对应的banner字符串;然后获取用于解析${}形式的占位符的解析器集合,利用解析器处理banner字符串中的占位符。最后将处理后的banner字符串输出。

    @Override
    	public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
    		try {
    			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);
    			}
    			out.println(banner);
    		}
    		catch (Exception ex) {
    			logger.warn(LogMessage.format("Banner not printable: %s (%s: '%s')", this.resource, ex.getClass(),
    					ex.getMessage()), ex);
    		}
    	}
    

    文本banner的占位符解析器主要有版本号解析器、文本格式解析器、应用标题解析器。

    protected List<PropertyResolver> getPropertyResolvers(Environment environment, Class<?> sourceClass) {
    		MutablePropertySources sources = new MutablePropertySources();
    		if (environment instanceof ConfigurableEnvironment) {
    			((ConfigurableEnvironment) environment).getPropertySources().forEach(sources::addLast);
    		}
    		sources.addLast(getTitleSource(sourceClass));
    		sources.addLast(getAnsiSource());
    		sources.addLast(getVersionSource(sourceClass));
    		List<PropertyResolver> resolvers = new ArrayList<>();
    		resolvers.add(new PropertySourcesPropertyResolver(sources));
    		return resolvers;
    	}
    
  • 默认banner
    默认banner被封装在SpringBootBanner中,banner打印的逻辑在printBanner()方法中。

    该方法逻辑主要是按行输出BANNER字符串的内容,同时获取SpringBoot的版本号并输出。

    @Override
    	public 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();
    	}
    
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐