目录

  • 一、背景
    • 1.1、run方法整体流程
    • 1.2、本文解读范围
  • 二、打印banner
    • 2.1、整体流程
      • 2.1.1、图片Banner获取流程
      • 2.1.2、文本Banner获取流程
    • 2.2、具体实现
    • 2.3、打印效果
    • 2.4、Banner生成网址

一、背景

  上两篇我们解读了环境准备及配置文件的加载,本计划是打印banner和创建容器一起解读的,但是创建容器的内容也不少,又会超出字数,编辑特别的慢,希望官方优化下,这次就单独把打印banner分出来了,虽说我觉得这个打印这个东西意义不是很大,不管怎么样,我们还是去了解下,首先我们还是先回顾下启动的整体流程。

1.1、run方法整体流程

  接下来的几个方法所在类的具体路径:org.springframework.boot.SpringApplication

 public ConfigurableApplicationContext run(String... args) {// 1、记录启动的开始时间(单位纳秒)long startTime = System.nanoTime();// 2、初始化启动上下文、初始化应用上下文DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;// 3、设置无头属性:“java.awt.headless”,默认值为:true(没有图形化界面)configureHeadlessProperty();// 4、获取所有 Spring 运行监听器SpringApplicationRunListeners listeners = getRunListeners(args);// 发布应用启动事件listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 5、初始化默认应用参数类(命令行参数)ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 6、根据运行监听器和应用参数 来准备 Spring 环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置忽略bean信息configureIgnoreBeanInfo(environment);// 7、创建 Banner 并打印Banner printedBanner = printBanner(environment);// 8、创建应用上下文context = createApplicationContext();// 设置applicationStartupcontext.setApplicationStartup(this.applicationStartup);// 9、准备应用上下文prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 10、刷新应用上下文(核心)refreshContext(context);// 11、应用上下文刷新后置处理afterRefresh(context, applicationArguments);// 13、时间信息、输出日志记录执行主类名Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 14、发布应用上下文启动完成事件listeners.started(context, timeTakenToStartup);// 15、执行所有 Runner 运行器callRunners(context, applicationArguments);} catch (Throwable ex) {// 运行错误处理handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {// 16、发布应用上下文就绪事件(可以使用了)Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);} catch (Throwable ex) {// 运行错误处理handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}// 17、返回应用上下文return context;}

1.2、本文解读范围

  本文主要讲解到Banner打印,也就是:

 // 7、创建 Banner 并打印Banner printedBanner = printBanner(environment);

二、打印banner

2.1、整体流程

  此方法所在类的具体路径:org.springframework.boot.SpringApplication

 // banner打印模式默认是控制台private Banner.Mode bannerMode = Banner.Mode.CONSOLE;private Banner printBanner(ConfigurableEnvironment environment) {// 默认是控制台if (this.bannerMode == Banner.Mode.OFF) {return null;}// 由于此时resourceLoader 为null,所以resourceLoader 最终为DefaultResourceLoaderResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(null);// 实例化一个SpringApplicationBannerPrinterSpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);// 默认是控制台if (this.bannerMode == Mode.LOG) {return bannerPrinter.print(environment, this.mainApplicationClass, logger);}// 调用SpringApplicationBannerPrinter的打印方法return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}
  • 如果Banner的打印模式是关闭的就不再执行
  • 获取资源加载器
  • 实例化SpringApplicationBannerPrinter
  • 如果Banner的打印模式是输出到日志,则通过SpringApplicationBannerPrinter 进行打印
  • 默认是输出到控制台,也是通过SpringApplicationBannerPrinter

  可以通过配置文件修改打印的模式(log,console,off),比如:

spring:main:banner-mode: log

  类的具体路径:org.springframework.boot.SpringApplicationBannerPrinter

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 = { "gif", "jpg", "png" };private static final Banner DEFAULT_BANNER = new SpringBootBanner();private final ResourceLoader resourceLoader;private final Banner fallbackBanner;SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {this.resourceLoader = resourceLoader;this.fallbackBanner = fallbackBanner;}// 打印Banner到日志文件Banner print(Environment environment, Class<?> sourceClass, Log logger) {// 通过环境配置获取BannerBanner banner = getBanner(environment);try {// 打印Banner到日志文件logger.info(createStringFromBanner(banner, environment, sourceClass));} catch (UnsupportedEncodingException ex) {logger.warn("Failed to create String for banner", ex);}// 返回打印对象return new PrintedBanner(banner, sourceClass);}// 打印Banner到控制台Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {// 通过环境配置获取BannerBanner banner = getBanner(environment);// 打印Banner到控制台banner.printBanner(environment, sourceClass, out);// 返回打印对象return new PrintedBanner(banner, sourceClass);}// 获取bannersprivate Banner getBanner(Environment environment) {// 实例化Banners,Banners是Banner的实现类Banners banners = new Banners();// 获取图片形式的Banner,如果不为空则加入列表banners.addIfNotNull(getImageBanner(environment));// 获取文本形式的Banner,如果不为空则加入列表banners.addIfNotNull(getTextBanner(environment));// 只要列表不为空则返回Bannersif (banners.hasAtLeastOneBanner()) {return banners;}// 此处为nullif (this.fallbackBanner != null) {return this.fallbackBanner;}// 默认是SpringBootBannerreturn DEFAULT_BANNER;}// 获取文本形式的bannerprivate Banner getTextBanner(Environment environment) {// 获取属性"spring.banner.location"的值,没有取到就使用默认值"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")) {// 符合条件时构建ResourceBanner对象返回return new ResourceBanner(resource);}} catch (IOException ex) {// Ignore}// 默认返回nullreturn null;}// 获取图片形式的bannerprivate Banner getImageBanner(Environment environment) {// 获取属性"spring.banner.image.location"的值String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);// 如果配置了值if (StringUtils.hasLength(location)) {// 通过资源加载器加载资源Resource resource = this.resourceLoader.getResource(location);// 如果图片资源存在,则构建ImageBanner返回,否则返回nullreturn resource.exists() ? new ImageBanner(resource) : null;}//如果没有配置值for (String ext : IMAGE_EXTENSION) {// 则尝试加载"banner.gif", "banner.jpg", "banner.png"Resource resource = this.resourceLoader.getResource("banner." + ext);if (resource.exists()) {// 只要加载到了,则构建ImageBanner返回return new ImageBanner(resource);}}return null;}private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)throws UnsupportedEncodingException {// 构建字节数组输出流ByteArrayOutputStream baos = new ByteArrayOutputStream();// 构建打印流banner.printBanner(environment, mainApplicationClass, new PrintStream(baos));// 获取"spring.banner.charset",默认编码为"UTF-8"String charset = environment.getProperty("spring.banner.charset", "UTF-8");// 转为字符串return baos.toString(charset);}// 静态内部类Bannersprivate static class Banners implements Banner {private final List<Banner> banners = new ArrayList<>();void addIfNotNull(Banner banner) {if (banner != null) {this.banners.add(banner);}}boolean hasAtLeastOneBanner() {return !this.banners.isEmpty();}@Overridepublic void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {for (Banner banner : this.banners) {banner.printBanner(environment, sourceClass, out);}}}// 静态内部类PrintedBannerprivate static class PrintedBanner implements Banner {private final Banner banner;private final Class<?> sourceClass;PrintedBanner(Banner banner, Class<?> sourceClass) {this.banner = banner;this.sourceClass = sourceClass;}@Overridepublic void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {sourceClass = (sourceClass != null) ? sourceClass : this.sourceClass;this.banner.printBanner(environment, sourceClass, out);}}}
  • 通过环境配置获取Banner,先获取图片Banner(ImageBanner),然后是文本Banner(ResourceBanner),都没有获取到则使用默认的SpringBootBanner
  • 打印Banner到控制台或者日志文件,如果是图片Banner,需要先转为打印流,图片Banner和文本Banner同时存在时,都会打印,先打印图片banner
  • 返回打印对象

2.1.1、图片Banner获取流程

  • 通过在配置文件配置spring.banner.image.location,通过资源加载器加载资源,获取到资源则返回对象ImageBanner,未获取到则返回null
  • 未指定spring.banner.image.location的值时,可以在classPath中添加banner.gif、banner.jpg、banner.png任意一个文件,但是如果添加了多个,则会打印一个,优先级:gif > jpg > png

2.1.2、文本Banner获取流程

  • 通过在配置文件配置spring.banner.location,通过环境对象Environment获取路径,如果没有获取到则默认使用classPath"banner.txt"
  • 通过资源加载器去加载,如果加载到了,并且满足条件,则返回对象ResourceBanner,否则返回null

2.2、具体实现

  ResourceBanner的打印需要处理占位符,然后转为字符串进行输出

public class ResourceBanner implements Banner {private Resource resource;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);}}protected List<PropertyResolver> getPropertyResolvers(Environment environment, Class<?> sourceClass) {List<PropertyResolver> resolvers = new ArrayList<>();resolvers.add(environment);resolvers.add(getVersionResolver(sourceClass));resolvers.add(getAnsiResolver());resolvers.add(getTitleResolver(sourceClass));return resolvers;}
}

  ImageBanner 的打印时会先将资源转为输入流,然后再转为图片输入流,然后转为对象数组Frame[ ],Frame是 ImageBanner 静态内部类,里包含BufferedImagedelayTime,然后遍历数组进行打印。

public class ImageBanner implements Banner {private final Resource image;@Overridepublic 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(LogMessage.format("Image banner not printable: %s (%s: '%s')", 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);// 获取位属性BitDepth bitDepth = getBitDepthProperty(environment);// 获取像素属性PixelMode pixelMode = getPixelModeProperty(environment);// 获取Frame[]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());}}private Frame[] readFrames(int width, int height) throws IOException {// Java7的语法,自动关闭流// 图片资源转为输入流try (InputStream inputStream = this.image.getInputStream()) {// 输入流转为图片输入流try (ImageInputStream imageStream = ImageIO.createImageInputStream(inputStream)) {// 根据宽高和图片输入流获取Frame[]return readFrames(width, height, imageStream);}}}private Frame[] readFrames(int width, int height, ImageInputStream stream) throws IOException {// 返回包含所有当前已注册 ImageReader 的 IteratorIterator<ImageReader> readers = ImageIO.getImageReaders(stream);Assert.state(readers.hasNext(), "Unable to read image banner source");ImageReader reader = readers.next();try {// 返回一个适合此格式的默认 ImageReadParam 对象ImageReadParam readParam = reader.getDefaultReadParam();// 设置指定的 ImageInputStream 输入源reader.setInput(stream);// 返回当前输入源中可用的图像数int frameCount = reader.getNumImages(true);Frame[] frames = new Frame[frameCount];for (int i = 0; i < frameCount; i++) {// 通过ImageReader 把图片输入流转为Frameframes[i] = readFrame(width, height, reader, i, readParam);}// 返回数组对象return frames;} finally {reader.dispose();}}private Frame readFrame(int width, int height, ImageReader reader, int imageIndex, ImageReadParam readParam)throws IOException {// 使用所提供的 ImageReadParam 来读取通过索引 imageIndex 指定的对象BufferedImage image = reader.read(imageIndex, readParam);// 跳转图像大小BufferedImage resized = resizeImage(image, width, height);// 获取延迟时间int delayTime = getDelayTime(reader, imageIndex);// 返回Framereturn new Frame(resized, delayTime);}private static class Frame {private final BufferedImage image;private final int delayTime;Frame(BufferedImage image, int delayTime) {this.image = image;this.delayTime = delayTime;}BufferedImage getImage() {return this.image;}int getDelayTime() {return this.delayTime;}}}

  有兴趣的小伙伴可以看看具体的实现,详情见代码注释

2.3、打印效果

  我们再配置文件中加入如下配置:

server:port: 8080servlet:context-path: /springbootspring:main:sources: com.alian.springbootbanner-mode: consolebanner:location: classpath:banner.txtimage:location: classpath:csdn.jpg

  也就是我们指定了图片Banner和文本Banner资源的路径,然后分别把 banner.txtcsdn.png 放到 classpath 下,当然这里不配置文本Banner的地址也可以,只要放了那个文本文件,因为默认会读取 classpath 下的 banner.txt

             &#########     *@@@@@@@@@@  &@@@@@#@@*        :@@@@@@@#@.        &##@##########  @@@@#@@@@@@@@#  @@@@@@@@@@@#@@   @##@@@@@@@@@@#      ####@#           @@@@@           #@@@      @@@@@  @@@@*     @@@@@     #@###             @@@@#@@@@       @@@@       @@@@  #@@@      @@@@@     ####@                 @##@@@@#@   #@@@       @@@#  #@@#      @@@#8     #@####                    @@@@@8  #@#*     o@@@@   #@@@      @@@#      ##@##########@  @#@@@@@#@@@@@@  :@@@@@@@@#@@@@    #@@#      @@@@      8#########:  @#@@@@@@@#@     @@@@@@@#@@o      @@@@       @@@@      ___                                     ___           ___     /  /\                      ___          /  /\         /__/\    /  /::\                    /  /\        /  /::\        \  \:\   /  /:/\:\    ___     ___   /  /:/       /  /:/\:\        \  \:\  /  /:/~/::\  /__/\   /  /\ /__/::\      /  /:/~/::\   _____\__\:\ /__/:/ /:/\:\ \  \:\ /  /:/ \__\/\:\__  /__/:/ /:/\:\ /__/::::::::\\  \:\/:/__\/  \  \:\  /:/     \  \:\/\ \  \:\/:/__\/ \  \:\~~\~~\/\  \::/        \  \:\/:/       \__\::/  \  \::/       \  \:\  ~~~ \  \:\         \  \::/        /__/:/    \  \:\        \  \:\     \  \:\         \__\/         \__\/      \  \:\        \  \:\    \__\/                                   \__\/         \__\/    2021-12-07 11:45:32 895 [main] INFO initialize 108:Tomcat initialized with port(s): 8080 (http)
2021-12-07 11:45:32 900 [main] INFO log 173:Initializing ProtocolHandler ["http-nio-8080"]
2021-12-07 11:45:32 900 [main] INFO log 173:Starting service [Tomcat]
2021-12-07 11:45:32 900 [main] INFO log 173:Starting Servlet engine: [Apache Tomcat/9.0.55]
2021-12-07 11:45:32 941 [main] INFO log 173:Initializing Spring embedded WebApplicationContext
2021-12-07 11:45:32 942 [main] INFO prepareWebApplicationContext 290:Root WebApplicationContext: initialization completed in 474 ms
2021-12-07 11:45:33 132 [main] INFO log 173:Starting ProtocolHandler ["http-nio-8080"]
2021-12-07 11:45:33 142 [main] INFO start 220:Tomcat started on port(s): 8080 (http) with context path '/springboot'
2021-12-07 11:45:33 157 [main] INFO logStarted 61:Started SpringbootApplication in 1.116 seconds (JVM running for 1.621)

2.4、Banner生成网址

  比较常用的Banner制作网站如下:

  • http://patorjk.com/software/taag/
  • https://www.bootschool.net/ascii
  • http://www.network-science.de/ascii/
  • http://www.degraeve.com/img2txt.php

Alian解读SpringBoot 2.6.0 源码(五):启动流程分析之打印Banner相关推荐

  1. Alian解读SpringBoot 2.6.0 源码(八):启动流程分析之刷新应用上下文(中)

    目录 一.背景 1.1.刷新的整体调用流程 1.2.本文解读范围 二.调用后处理器 2.1.调用在上下文中注册为beanFactory的后置处理器 2.2.invokeBeanFactoryPostP ...

  2. Alian解读SpringBoot 2.6.0 源码(七):启动流程分析之准备应用上下文

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.准备应用上下文 2.1.整体流程 2.2.设置环境 2.3.应用上下文进行后置处理 2.4.应用所有初始化器 2.5.发布应用上下 ...

  3. Alian解读SpringBoot 2.6.0 源码(十):启动流程之自动装配原理

    目录 一.背景 1.1.主类的加载 1.2.后置处理器的获取 二.配置类后处理器 2.1.获取配置类 2.2. 2.3.解析主类 2.3.1.整体解析过程 2.3.2.核心解析过程 2.3.3.延迟导 ...

  4. Alian解读SpringBoot 2.6.0 源码(六):启动流程分析之创建应用上下文

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.创建应用上下文 2.1.初始化入口 2.2.初始化AbstractApplicationContext 2.3.初始化Generi ...

  5. Alian解读SpringBoot 2.6.0 源码(八):启动流程分析之刷新应用上下文(下)

    目录 一.背景 1.1.刷新的整体调用流程 1.2.本文解读范围 二.初始化特定上下文子类中的其他特殊bean 2.1.初始化主体资源 2.2.创建web服务 三.检查监听器bean并注册它们 四.实 ...

  6. Alian解读SpringBoot 2.6.0 源码(九):启动流程分析之应用上下文刷新后处理(启动完成事件,Runner运行器,就绪事件)

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.应用上下文刷新后置处理 三.时间信息.输出日志记录执行主类名 四.发布应用上下文启动完成事件 4.1.ApplicationSta ...

  7. Alian解读SpringBoot 2.6.0 源码(二):启动流程分析之监听器解析

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.记录应用启动的开始时间 三.初始化启动上下文 3.1.初始化启动上下文 3.2.初始化应用程序事件广播器 3.3.初始化应用上下文 ...

  8. Alian解读SpringBoot 2.6.0 源码(四):启动流程分析之应用环境准备

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.应用环境准备 2.1.准备环境的整体流程 2.2.创建环境 2.3.配置环境 2.3.1.注册默认的转换器.格式化组件 2.3.1 ...

  9. Alian解读SpringBoot 2.6.0 源码(八):启动流程分析之刷新应用上下文(上)

    目录 一.背景 1.1.run方法整体流程 1.2.刷新的整体调用流程 1.3.本文解读范围 二.准备刷新 2.1.准备刷新的流程 2.2.初始化上下文环境中servlet相关属性源 2.3.校验re ...

  10. Alian解读SpringBoot 2.6.0 源码(一):SpringApplication对象创建(Spring工厂加载机制)

    目录 一.背景 二.SpringApplication实例化 2.1.实例化方法入口 2.2.推断应用程序类型 2.3.Spring工厂加载机制 2.3.1.获取Spring工厂实例(重要) 2.3. ...

最新文章

  1. 用js 判断datagrid 中的 checkbox 是否被选中
  2. 【C++】【三】单向链表
  3. oracle包写入程序失败_ORA-12571 : TNS : 包写入程序失败
  4. 合成(composite)模式
  5. ES6 模块加载export 、import、export default 、import() 语法与区别,笔记总结
  6. 自定义相册、九宫格显示图片
  7. 80后程序员月薪30K+感慨中年危机,面试必问!
  8. SharpZipLib压缩解压
  9. notebook python 已停止工作_Python/Jupyter Notebook初学遇到的一些问题总结(20201108)...
  10. “软下来”的苹果和小米能否拯救智能手机的焦虑? | 畅言
  11. 爱创课堂每日一题第四十八天- html5有哪些新特性、移除了那些元素?
  12. SQL调优技巧:统计信息(文末福利)
  13. 连续 3 年支撑双 11,阿里云神龙如何扛住全球流量洪峰?
  14. 综合布线系统工程施工管理
  15. TCP实现消息传输和文件传输,UDP实现消息发送和聊天,URL下载文件
  16. 【Cesium】点击billboard弹出自定义气泡框
  17. 蜗牛星际b款装服务器系统,星际蜗牛 篇八:蜗牛星际B款改装美化 —— 拆!拆!拆!...
  18. VM虚拟机安装CentOS系统的常见BUG
  19. 【科研】ET-BERT代码分析
  20. 决策力--用别人预测自己(3)

热门文章

  1. 小米互联网生态链PK格力产品专利,你站哪边?
  2. ThickBox 3.1参数详解
  3. 阿里云体验有奖:如何将 PolarDB-X 与大数据等系统互通
  4. 验证码不显示的解决方法
  5. openlayers6【十九】vue HeatmapLayer热力图层实现热力图效果详解
  6. 用VB实现SmartQQ机器人
  7. 前端复制、剪切、禁止复制等
  8. Extjs基础(一)
  9. 软件测试入门之软件测试的原则与测试工程师的要求(了解即可)
  10. Mybatis实现*mapper.xml热部署-分子级更新