SpringBoot源码解析(十一)自定义banner

本文解析Spring Application run方法中的banner打印过程,介绍配置项、默认行为和如何自定义公司logo。

上篇文章结束了prepareEnvironment方法的分析,本篇继续SpringApplication的run方法往下走,看一个比较简单的点——banner打印

所谓banner就是SpringBoot应用启动的时候打印在控制台的一个logo
在这里插入图片描述

涉及到的代码为下面这行printBanner
在这里插入图片描述
在这之前,先简单说下上面的这行configureIgnoreBeanInfo

    private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        if (System.getProperty("spring.beaninfo.ignore") == null) {
            Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
            System.setProperty("spring.beaninfo.ignore", ignore.toString());
        }
    }

它将环境配置中的spring.beaninfo.ignore属性设置到系统变量中,如果没有配置,就默认为true

这个属性主要和內省机制有关,这里不详细展开,很多工具类比如BeanUtils底层都依赖了內省,spring.beaninfo.ignore设置为true,会忽略自定义的BeanInfo

至于为什么SpringBoot把这个属性默认设置为true,我个人的理解是很多底层的机制都依赖了內省,而自定义BeanInfo很可能干扰使用了內省机制的相关功能,所以SpringBoot不推荐使用,实际工作中也极少会做BeanInfo层面的定制

接下来进入正题,点开printBanner方法

    private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Mode.OFF) {
            return null;
        } else {
            ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());
            SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
            return this.bannerMode == Mode.LOG ? 
            	bannerPrinter.print(environment, this.mainApplicationClass, logger) : 
            	bannerPrinter.print(environment, this.mainApplicationClass, System.out);
        }
    }

bannerMode是banner打印的模式,取值有三个

public static enum Mode {
    OFF,
    CONSOLE,
    LOG;
}

OFF就是不打印banner,CONSOLE是打印到标准输出流,会输出到控制台,LOG就是打印到日志文件

bannerMode在SpringApplication对象的构造函数里初始化为CONSOLE

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    ......
    this.bannerMode = Mode.CONSOLE;
    ......
}

所以默认就是只打印到控制台的,但是一般我们项目都会集成一些日志框架,这些框架也可以把标准输出流的内容重定向到日志文件中

如果没有设置为关闭,就进入printBanner方法的else分支

......
ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);
......

第一行定义了一个ResourceLoader,由于SpringApplication对象的resourceLoader并没有初始化,所以new了一个DefaultResourceLoader,这个东西我们已经见了很多次了

接下来新建了一个SpringApplicationBannerPrinter,入参是刚创建的DefaultResourceLoader和SpringApplication对象的banner属性,这个属性同样没有初始化,所以此时也是null

SpringApplicationBannerPrinter内部封装了将banner打印到不同渠道的方法,先看下它的成员变量

class SpringApplicationBannerPrinter {
    static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
    static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
    static final String DEFAULT_BANNER_LOCATION = "banner.txt";
    static final String[] IMAGE_EXTENSION = new String[]{"gif", "jpg", "png"};
    private static final Banner DEFAULT_BANNER = new SpringBootBanner();
    private final ResourceLoader resourceLoader;
    private final Banner fallbackBanner;

banner可以是图片形式,也可以是文本形式,可供我们自定义配置,前两个成员变量就是自定义文本banner和图片banner的默认配置名,后续寻找banner的时候,先看有没有自定义,如果没有,再找默认的banner文件
第三个变量为默认的文本banner文件,即classpath下的banner.txt
第四个变量为默认的图片banner的后缀名,图片banner可以支持多种格式,即会到classpath下找图片banner.gif / jpg / png

最后根据bannerMode是LOG还是CONSOLE,调用SpringApplicationBannerPrinter对象不同的print方法,这两个print方法内部实现大致相同,只不过打印的目的地分别为log文件以及标准输出流System.out,我们这里就按照默认情况,看下打印到System.out的代码

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

第一行先获取Banner对象

    private Banner getBanner(Environment environment) {
        SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();
        banners.addIfNotNull(this.getImageBanner(environment));
        banners.addIfNotNull(this.getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        } else {
            return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
        }
    }

Banner对象就是对banner内容的封装,文本类型的banner会封装到ResourceBanner中,图片类型则封装到ImageBanner,而SpringBootBanner是默认的banner,我们下面会见到
在这里插入图片描述

先尝试获取图片类型的banner

    private Banner getImageBanner(Environment environment) {
        String location = environment.getProperty("spring.banner.image.location");
        if (StringUtils.hasLength(location)) {
            Resource resource = this.resourceLoader.getResource(location);
            return resource.exists() ? new ImageBanner(resource) : null;
        } else {
            String[] var3 = IMAGE_EXTENSION;
            int var4 = var3.length;
            for(int var5 = 0; var5 < var4; ++var5) {
                String ext = var3[var5];
                Resource resource = this.resourceLoader.getResource("banner." + ext);
                if (resource.exists()) {
                    return new ImageBanner(resource);
                }
            }
            return null;
        }
    }

到environment中查找配置spring.banner.image.location(SpringBoot中很多地方都是这样,虽然上面定义了静态变量,但是下面很多代码并没有引用),如果没有指定,就到classpath下看有没有默认的banner图片,也就是上面提到的在classpath下找图片banner.gif / jpg / png

接下来再找txt类型的banner

    private Banner getTextBanner(Environment environment) {
        String location = environment.getProperty("spring.banner.location", "banner.txt");
        Resource resource = this.resourceLoader.getResource(location);
        return resource.exists() ? new ResourceBanner(resource) : null;
    }

先到environment看是否有配置spring.banner.location,如果没有,再默认找classpath下的banner.txt

如果图片类型和文本类型的banner都没有找到,就取fallbackBanner

return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;

这个fallbackBanner传进来的是SpringApplication对象的banner属性,它并没有初始化,所以是null的,也就是说最终的fallbackBanner赋值为DEFAULT_BANNER,它是SpringApplicationBannerPrinter的静态变量,初始化为SpringBootBanner

class SpringApplicationBannerPrinter {
    ......
    private static final Banner DEFAULT_BANNER = new SpringBootBanner();
    ......

看下这个SpringBootBanner 的实现

class SpringBootBanner implements Banner {
    private static final String[] BANNER = new String[]{"", "  .   ____          _            __ _ _", " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\", " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", "  '  |____| .__|_| |_|_| |_\\__, | / / / /", " =========|_|==============|___/=/_/_/_/"};
    private static final String SPRING_BOOT = " :: Spring Boot :: ";
    private static final int STRAP_LINE_SIZE = 42;

    SpringBootBanner() {
    }

第一行String类型的数组,其实就是我们文章开头看到的SpringBoot默认的banner,它是以字符串的形式写死在代码里的

获取到banner后,调用它的print方法,最终通过System.out.println()将banner内容写到控制台

    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        try {
            String banner = StreamUtils.copyToString(this.resource.getInputStream(), (Charset)environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));

            PropertyResolver resolver;
            for(Iterator var5 = this.getPropertyResolvers(environment, sourceClass).iterator(); var5.hasNext(); banner = resolver.resolvePlaceholders(banner)) {
                resolver = (PropertyResolver)var5.next();
            }

            out.println(banner);
        } catch (Exception var7) {
            logger.warn("Banner not printable: " + this.resource + " (" + var7.getClass() + ": '" + var7.getMessage() + "')", var7);
        }

    }

很多公司都会在SpringBoot基础上做一层封装,然后加上自己公司的banner,这里附上一个自定义banner的网站

自定义banner
(https://patorjk.com/software/taag/#p=display&f=Doom&t=mybanner)

比如取名为MyBanner,然后将内同拷贝到classpath下的banner.txt
在这里插入图片描述

在这里插入图片描述

启动项目,默认banner已经被替换了
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值