SpringBoot banner打印原理

156 阅读4分钟

在上一篇文章中总结了4种修改banner打印的方法,那么banner打印底层原理是怎么样的呢??

## 在run方法中
public ConfigurableApplicationContext run(String... args) {
            /**
            * 中间省略多个方法
            **/
            
            《!---**banner打印的核心方法** ----!》
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

1:我们进入到Banner printedBanner = printBanner(environment);

private Banner printBanner(ConfigurableEnvironment environment) {
        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);
        }
        //因为this.bannerMode = CONSOLE,所以前面二个if不会进入,前面也是实例化一个资源加载器
        //核心方法还是这个
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }
    

2:我们进入到bannerPrinter.print(environment, this.mainApplicationClass, System.out);

   public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
          //获取banner实例
          Banner banner = getBanner(environment);
          //打印banner
          banner.printBanner(environment, sourceClass, out);
          return new PrintedBanner(banner, sourceClass);
   }
   

3:看下如何获取banner实例

 private Banner getBanner(Environment environment) {
        //Banners是Banner接口的实现,里面封装了一个Banner实例的集合
        Banners banners = new Banners();
        //获取图片的banner
        banners.addIfNotNull(getImageBanner(environment));
        //获取txt文本的banner
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        }
        if (this.fallbackBanner != null) {
            return this.fallbackBanner;
        }
        //如果前面的banners为空,那么就返回默认的banner
        return DEFAULT_BANNER;
    }
    

4:首选看下Banners类的源码

 private static class Banners implements Banner {
        //封装了Banner的一个集合
        private final List<Banner> banners = new ArrayList<>();
        
        //往集合中添加banner实例
        public void addIfNotNull(Banner banner) {
            if (banner != null) {
                this.banners.add(banner);
            }
        }

        public boolean hasAtLeastOneBanner() {
            return !this.banners.isEmpty();
        }

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

5:现在回头看下第3步的,首先是获取图片的banner

 private Banner getImageBanner(Environment environment) {
        // BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
        //spirngboot首先去看下配置文件中有没有配置这个属性,如果配置了就会根据
        //路径去加载,然后返回一个ImageBanner的实例回去
        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;
        }
        //如果没有配置 "spring.banner.image.location" 这个属性,那么就会在resources目录下
        //找是否有banner.png || banner.jpg || banner.gif三个文件
        //为什么是这三个呢??
        //看下 String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
        //如果找到了就会返回一个ImageBanner实例回去
        for (String ext : IMAGE_EXTENSION) {
            Resource resource = this.resourceLoader.getResource("banner." + ext);
            if (resource.exists()) {
                return new ImageBanner(resource);
            }
        }
        //如果以上二者都没有,那么直接返回null
        return null;
    }

6:同理,我们看下获取文本的banner

private Banner getTextBanner(Environment environment) {

      //先看下这二个属性:
      // String BANNER_LOCATION_PROPERTY = "spring.banner.location";
      // String DEFAULT_BANNER_LOCATION = "banner.txt";
      // SpringBoot会首先去配置文件找,看是否配置了 "spring.banner.location" 这个属性,配置了就会
      //去加载这个文本内容,如果没有配置就会在reources目录下去找banner.txt文件,从这个可以看出
      //配置文件的优先级是高于banner.txt的,找到其中的一个资源后就会返回一个ResourceBanner实例
      String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
              DEFAULT_BANNER_LOCATION);
      Resource resource = this.resourceLoader.getResource(location);
      if (resource.exists()) {
          return new ResourceBanner(resource);
      }
      //如果以上二个都没找到就直接返回null
      return null;
  }

7: 再看下获取banner的方法,如果文本和图片的banner都为空,就会返回默认的banner,现在看下默认的banner是怎么样的

 private Banner getBanner(Environment environment) {
        //Banners是Banner接口的实现,里面封装了一个Banner实例的集合
        Banners banners = new Banners();
        //获取图片的banner
        banners.addIfNotNull(getImageBanner(environment));
        //获取txt文本的banner
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        }
        if (this.fallbackBanner != null) {
            return this.fallbackBanner;
        }
        //如果前面的banners为空,那么就返回默认的banner
        return DEFAULT_BANNER;
    }
    

8:查看默认banner,我们点击DEFAULT_BANNER

 private static final Banner DEFAULT_BANNER = new SpringBootBanner();
 默认的banner实现是SpringBootBanner(),看下这个类,我只截取前面一部分,是不是很熟悉,就是我们默认的banner打印的内容
 
 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;
    

9:现在所有的banner获取到了,那么看下打印吧,回头看下第2步,从3-8都是解析获取banner实例的

 public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
              //获取banner实例
              Banner banner = getBanner(environment);
              //打印banner
              banner.printBanner(environment, sourceClass, out);
              return new PrintedBanner(banner, sourceClass);
       }

10:现在看下打印banner

 因为获取的Banner实例是Banners对象,可以从第7步看出返回的Banners对象,所以我们到Banners类的printBanner方法中
 
    @Override
	public void printBanner(Environment environment, Class<?> sourceClass,
			PrintStream out) {
        //遍历我们之前的获取的Banner,其实就二类 ImageBanner和ResourceBanner
        //然后分别进入这个类的 banner.printBanner(environment, sourceClass, out);方法进行打印
		for (Banner banner : this.banners) {
			banner.printBanner(environment, sourceClass, out);
		}
	}
    

11:最后就是获取文件内容然后打印到控制台上