spring boot 2源码系列(三)- banner

《Spring Boot源码博客》

spring boot自定义banner很简单,只需要在resources目录下添加 banner.txt 文件即可。

banner.txt内容如下:

                     _ooOoo_
                    o8888888o
                    88" . "88
                    (| ^_^ |)
                    O\  =  /O
                 ____/`---'\____
               .'  \\|     |//  `.
              /  \\|||  :  |||//  \
             /  _||||| -:- |||||-  \
             |   | \\\  -  /// |   |
             | \_|  ''\---/''  |   |
             \  .-\__  `-`  ___/-. /
           ___`. .'  /--.--\  `. . ___
         .""'<  `.___\_<|>_/___.'  >'"".
       | | :  `- \`.;`\ _ /`;.`/ - ` : | |
       \  \ `-.   \_ __\ /__ _/   .-` /  /
========`-.____`-.___\_____/___.-`____.-'========
                     '=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              我佛慈悲,请赐我一段姻缘

启动工程,控制台就会打印此banner。

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了。

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<String> 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();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值