SpringBoot——banner解析

banner演示

SpringBoot項(xiàng)目啟動(dòng)時(shí)會(huì)在控制臺(tái)打印一個(gè)默認(rèn)的LOGO,這個(gè)LOGO就是我們要講的banner。


制作自己的banner

實(shí)際上,Spring Boot 支持自定義 logo 的功能。讓我們來(lái)看看如何實(shí)現(xiàn)的。
只要你在 resources 目錄下放置名為 banner.txt、banner.gif 、banner.jpg 或 banner.png 的文件,Spring Boot 會(huì)自動(dòng)加載,將其作為啟動(dòng)時(shí)打印的 logo。

  • 對(duì)于文本文件,Spring Boot 會(huì)將其直接輸出。
  • 對(duì)于圖像文件( banner.gif 、banner.jpg 或 banner.png ),Spring Boot 會(huì)將圖像轉(zhuǎn)為 ASCII 字符,然后輸出。

變量

banner.txt 文件中還可以使用變量來(lái)設(shè)置字體、顏色、版本號(hào)。

變量 描述
${application.version} MANIFEST.MF 中定義的版本。如:1.0
${application.formatted-version} MANIFEST.MF 中定義的版本,并添加一個(gè) v 前綴。如:v1.0
${spring-boot.version} Spring Boot 版本。如:2.1.1.RELEASE.
${spring-boot.formatted-version} Spring Boot 版本,并添加一個(gè) v 前綴。如:v2.1.1.RELEASE
${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME}) ANSI 顏色、字體。更多細(xì)節(jié),參考:AnsiPropertySource。
${application.title} MANIFEST.MF 中定義的應(yīng)用名。

示例:
在 Spring Boot 項(xiàng)目中的 resources 目錄下添加一個(gè)名為 banner.txt 的文件,內(nèi)容如下:

${AnsiColor.BRIGHT_YELLOW}${AnsiStyle.BOLD}
 ________  ___  ___  ________   ___       __   ___  ___
|\   ___ \|\  \|\  \|\   ___  \|\  \     |\  \|\  \|\  \
\ \  \_|\ \ \  \\\  \ \  \\ \  \ \  \    \ \  \ \  \\\  \
 \ \  \ \\ \ \  \\\  \ \  \\ \  \ \  \  __\ \  \ \  \\\  \
  \ \  \_\\ \ \  \\\  \ \  \\ \  \ \  \|\__\_\  \ \  \\\  \
   \ \_______\ \_______\ \__\\ \__\ \____________\ \_______\
    \|_______|\|_______|\|__| \|__|\|____________|\|_______|
${AnsiBackground.WHITE}${AnsiColor.RED}${AnsiStyle.UNDERLINE}
:: Spring Boot ::             (v${spring-boot.version})
:: Spring Boot Tutorial ::    (v1.0.0)

注:${} 設(shè)置字體顏色的變量之間不能換行或空格分隔,否則會(huì)導(dǎo)致除最后一個(gè)變量外,都不生效。

啟動(dòng)應(yīng)用后,控制臺(tái)將打印如下 logo:


推薦兩個(gè)生成字符畫(huà)的網(wǎng)站,可以將生成的字符串放入這個(gè)banner.txt 文件:

配置

application.properties 中與 Banner 相關(guān)的配置:

# banner 模式。有三種模式:console/log/off
# console 打印到控制臺(tái)(通過(guò) System.out)
# log - 打印到日志中
# off - 關(guān)閉打印
spring.main.banner-mode = off
# banner 文件編碼
spring.banner.charset = UTF-8
# banner 文本文件路徑
spring.banner.location = classpath:banner.txt
# banner 圖像文件路徑(可以選擇 png,jpg,gif 文件)
spring.banner.image.location = classpath:banner.gif
used).
# 圖像 banner 的寬度(字符數(shù))
spring.banner.image.width = 76
# 圖像 banner 的高度(字符數(shù))
spring.banner.image.height =
# 圖像 banner 的左邊界(字符數(shù))
spring.banner.image.margin = 2
# 是否將圖像轉(zhuǎn)為黑色控制臺(tái)主題
spring.banner.image.invert = false

編程方式設(shè)置banner的兜底邏輯

@SpringBootApplication
@MapperScan("com.yibo.source.code.mapper")//掃描Mapper接口
public class Application {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        springApplication.setBanner(new ResourceBanner(new ClassPathResource("banner_bak.txt")));
        springApplication.run(args);
    }
}

banner_bak.txt

////////////////////////////////////////////////////////////////////
//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//             佛祖保佑          永無(wú)故障         永不修改             //
////////////////////////////////////////////////////////////////////

輸出:


注意:

默認(rèn),Spring Boot 會(huì)注冊(cè)一個(gè) SpringBootBanner 的單例 Bean,用來(lái)負(fù)責(zé)打印 Banner。

如果想完全個(gè)人定制 Banner,可以這么做:先實(shí)現(xiàn) org.springframework.boot.Banner#printBanner 接口來(lái)自己定制 Banner。在將這個(gè) Banner 通過(guò) SpringApplication.setBanner(…) 方法注入 Spring Boot。

banner獲取原理

banner的入口在SpringApplication的run方法中:

public class SpringApplication {
    public ConfigurableApplicationContext run(String... args) {
        ......
        Banner printedBanner = printBanner(environment);
    }
}

接著跟進(jìn)printBanner(environment);方法

private Banner printBanner(ConfigurableEnvironment environment) {
    //判斷是否關(guān)閉打印
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    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);
    }
    //直接打印到控制臺(tái)
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

接著跟進(jìn)bannerPrinter.print()方法

public Banner print(Environment environment, Class<?> sourceClass, Log logger) {
    Banner banner = getBanner(environment);
    try {
        logger.info(createStringFromBanner(banner, environment, sourceClass));
    }
    catch (UnsupportedEncodingException ex) {
        logger.warn("Failed to create String for banner", ex);
    }
    return new PrintedBanner(banner, sourceClass);
}

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

不管是輸出到日志還是打印到控制臺(tái)都會(huì)調(diào)用getBanner(Environment environment)方法

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;
}
getImageBanner(environment)
  • 可以通過(guò)spring.banner.image.location指定位置
  • 可以使用的圖片格式為"gif", "jpg", "png"
static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
private Banner getImageBanner(Environment environment) {
    String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
    if (StringUtils.hasLength(location)) {
        Resource resource = this.resourceLoader.getResource(location);
        return resource.exists() ? new ImageBanner(resource) : null;
    }
    for (String ext : IMAGE_EXTENSION) {
        Resource resource = this.resourceLoader.getResource("banner." + ext);
        if (resource.exists()) {
            return new ImageBanner(resource);
        }
    }
    return null;
}
getTextBanner(environment)
  • 可以通過(guò)spring.banner.location指定位置
  • 默認(rèn)名字為banner.txt
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
private Banner getTextBanner(Environment environment) {
    String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
    Resource resource = this.resourceLoader.getResource(location);
    if (resource.exists()) {
        return new ResourceBanner(resource);
    }
    return null;
}
獲取banner步驟

banner輸出原理

默認(rèn)輸出:
  • 先輸出banner指定內(nèi)容
  • 獲取version信息
  • 文本內(nèi)容前后對(duì)其
  • 文本內(nèi)容染色
  • 輸出文本內(nèi)容
class SpringBootBanner implements Banner {

    private static final String[] BANNER = { "", "  .   ____          _            __ _ _",
            " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
            " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", "  '  |____| .__|_| |_|_| |_\\__, | / / / /",
            " =========|_|==============|___/=/_/_/_/" };

    private static final String SPRING_BOOT = " :: Spring Boot :: ";

    private static final int STRAP_LINE_SIZE = 42;

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

}
文本輸出
  • 可以通過(guò)spring.banner.charset指定字符集
  • 獲取文本內(nèi)容
  • 替換占位符
  • 輸出文本內(nèi)容
private static class Banners implements Banner {

    private final List<Banner> banners = new ArrayList<>();

    public void addIfNotNull(Banner banner) {
        if (banner != null) {
            this.banners.add(banner);
        }
    }

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

}

public class ResourceBanner implements 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(
                    "Banner not printable: " + this.resource + " (" + ex.getClass() + ": '" + ex.getMessage() + "')",
                    ex);
        }
    }
}
圖片輸出
  • 可以通過(guò)spring.banner.image.*設(shè)置圖片屬性
  • 讀取圖片文件流
  • 輸出圖片內(nèi)容
private static class Banners implements Banner {

    private final List<Banner> banners = new ArrayList<>();

    public void addIfNotNull(Banner banner) {
        if (banner != null) {
            this.banners.add(banner);
        }
    }

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

}

public class ImageBanner implements Banner {

    private static final String PROPERTY_PREFIX = "spring.banner.image.";

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        String headless = System.getProperty("java.awt.headless");
        try {
            System.setProperty("java.awt.headless", "true");
            printBanner(environment, out);
        }
        catch (Throwable ex) {
            logger.warn("Image banner not printable: " + this.image + " (" + ex.getClass() + ": '" + ex.getMessage()
                    + "')");
            logger.debug("Image banner printing failure", ex);
        }
        finally {
            if (headless == null) {
                System.clearProperty("java.awt.headless");
            }
            else {
                System.setProperty("java.awt.headless", headless);
            }
        }
    }

    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);
        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, out);
            sleep(frames[i].getDelayTime());
        }
    }
}

參考:
https://www.cnblogs.com/jingmoxukong/p/10159493.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容