分析第5步,打印banner。

解析

1. SpringApplication#run⽅法的第5步执⾏如下代码:

private Banner printBanner(ConfigurableEnvironment environment) {// 1. ⾸先判断banner的输出级别。如果禁⽤了,则直接返回空。if (this.bannerMode == Mode.OFF) {return null;} else {// 2. 获取资源加载器ResourceLoaderResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());// 3. 实例化SpringApplicationBannerPrinter类SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);// 如果banner的输出模式是Mode.LOG,则直接将其信息输出到logger⽇志中,否则将其输出到控制台,也就是System.outreturn this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);}}

做了4件事

1. 如果this.bannerMode等于Banner.Mode.OFF,则直接返回空。

2. 获取资源加载器ResourceLoader.代码如下:

ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());

对于当前的场景来说, SpringApplication 中的 resourceLoader为null.因此会实例化DefaultResourceLoader。

3. 实例化SpringApplicationBannerPrinter类.代码如下:

SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {this.resourceLoader = resourceLoader;this.fallbackBanner = fallbackBanner;}

注意.在当前场景下, SpringApplicationBannerPrinter中的fallbackBanner为null.

4. 如果banner的输出模式是Mode.LOG,则直接将其信息输出到logger⽇志中。

注意
默认情况下. SpringApplication中的banner输出模式为CONSOLE.因此是不会输出到⽇志的.

banner的输出默认如下:

enum Mode {
/**
* Disable printing of the banner.
*/
OFF,
/**
* Print the banner to System.out.
*/
CONSOLE,
/**
* Print the banner to the log file.
*/
LOG
}

5. 将banner输出到控制台,也就是System.out.代码如下:

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {// 1. 获取BannerBanner banner = getBanner(environment, this.fallbackBanner);// 2. 调⽤Banner中的printBanner⽅法banner.printBanner(environment, sourceClass, out);// 3. 实例化PrintedBanner类return new PrintedBanner(banner, sourceClass);
}

做了3件事:
1. 获取Banner
2. 调⽤Banner中的printBanner⽅法.进⾏banner的打印.
3. 实例化PrintedBanner类

2. 获取banner的⽅法如下:

private Banner getBanner(Environment environment, Banner definedBanner) {SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();banners.addIfNotNull(this.getImageBanner(environment));banners.addIfNotNull(this.getTextBanner(environment));// 如果Banners对象的banners不为空,也就是⾄少找到了banner.gif, banner.jpg, banner.png, banner.txt其中的⼀个,那么返回该Banners对象,否则返回默认的SpringBootBanner对象if (banners.hasAtLeastOneBanner()) {return banners;} else {return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;}}

做了3件事
1. 实例化Banners.然后为其设置ImageBanner和TextBanner.如果此时anners对象的banners不为空.则
返回Banners。
2. 如果fallbackBanner不为null的话,返回fallbackBanner.对于当前场景来说fallbackBanner为null。
3. 返回默认的banner.默认的bannenr为 SpringBootBanner。

这⾥有必要说明⼀下banner的继承体系.如下:

其只声明了⼀个⽅法.如下:

public interface Banner {void printBanner(Environment var1, Class<?> var2, PrintStream var3);public static enum Mode {OFF,CONSOLE,LOG;private Mode() {}}
}

Banners实例化后,会调⽤getImageBanner⽅法进⾏加载.代码如下:

static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };private Banner getImageBanner(Environment environment) {String location = environment.getProperty("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;}}

逻辑如下:
1. ⾸先判断是否配置了系统属性banner.image.location,如果有直接返回ImageBanner.
2. 如果没有配置则在classpath中查找banner.gif, banner.jpg, banner.png,如果找到,则创建⼀个
ImageBanner对象并添加到Banners对象的banners属性中,该属性是⼀个List.代码如下:

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

很明显 对于当前场景来说. getImageBanner返回的是null。

接下来调⽤getTextBanner.来加载TextBanner.代码如下:

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

还是同样的套路。
1. 从environment中获取banner.location属性,默认为banner.txt。
2. 进⾏加载.如果存在的话,则返回ResourceBanner.否则返回null。
对于当前场景来说.返回的是null。
因此,对于当前场景来说. getBanner返回的是SpringBootBanner。

3. 接下来调⽤SpringBootBanner#printBanner⽅法.代码如下:

public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {String[] var4 = BANNER;int var5 = var4.length;for(int var6 = 0; var6 < var5; ++var6) {String line = var4[var6];printStream.println(line);}String version = SpringBootVersion.getVersion();version = version == null ? "" : " (v" + version + ")";String padding;for(padding = ""; padding.length() < 42 - (version.length() + " :: Spring Boot :: ".length()); padding = padding + " ") {;}printStream.println(AnsiOutput.toString(new Object[]{AnsiColor.GREEN, " :: Spring Boot :: ", AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version}));printStream.println();}

做了3件事
1. 循环遍历BANNER数组,并依次进⾏数组内容的打印
2. 调⽤SpringBootVersion#getVersion,进⾏springboot版本信息的获取.然后为了与之前的输出字符
进⾏对⻬,在springboot版本信息前加空格.SpringBootVersion#getVersion代码如下:

public static String getVersion() {Package pkg = SpringApplication.class.getPackage();return pkg != null ? pkg.getImplementationVersion() : null;}

版本信息那些事
MANIFEST.MF

3. 通过AnsiOutput#toString⽅法⽣成字符.输出到PrintStream.最后输出⼀个回⻋换⾏.
代码如下:

public static String toString(Object... elements) {StringBuilder sb = new StringBuilder();if (isEnabled()) {buildEnabled(sb, elements);} else {buildDisabled(sb, elements);}return sb.toString();}

1. 实例化StringBuilder进⾏字符串拼接.
2. 判断是否可⽤.如果可以调⽤buildEnabled.否则调⽤buildDisabled. isEnabled⽅法如下:

private static boolean isEnabled() {if (enabled == AnsiOutput.Enabled.DETECT) {if (ansiCapable == null) {// 对于当前场景来说.ansiCapable 为 null.因此会执⾏detectIfAnsiCapable⽅法ansiCapable = detectIfAnsiCapable();}return ansiCapable;} else {return enabled == AnsiOutput.Enabled.ALWAYS;}}

这⾥⽤到了我们之前分析过的知识.springApplication run ⽅法执⾏前4步的过程中.发送了ApplicationEnvironmentPreparedEvent 时间. 其中AnsiOutputApplicationListener 对该事件进⾏了处理.代码如下:

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(event.getEnvironment(), "spring.output.ansi.");if (resolver.containsProperty("enabled")) {String enabled = resolver.getProperty("enabled");AnsiOutput.setEnabled((Enabled)Enum.valueOf(Enabled.class, enabled.toUpperCase()));}if (resolver.containsProperty("console-available")) {AnsiOutput.setConsoleAvailable((Boolean)resolver.getProperty("console-available", Boolean.class));}}

对于当前场景来说. resolver中是含有spring.output.ansi.enabled 的配置的.默认为true.注意,此时需
要我们在sts中通过右键-->run-as--> spring boot app 来启动项⽬.如图:

因此会将AnsiOutput的enabled 设置为Enabled.ALWAYS.

因此这⾥会执⾏buildEnabled⽅法.代码如下:

private static void buildDisabled(StringBuilder sb, Object[] elements) {Object[] var2 = elements;int var3 = elements.length;for(int var4 = 0; var4 < var3; ++var4) {Object element = var2[var4];if (!(element instanceof AnsiElement) && element != null) {sb.append(element);}}}

这⾥返回的字符串为:

[32m :: Spring Boot :: ​[39m ​[2m​[0;39m

⾃定义banner

通过之前的分析,我们知道了SpringApplicationBannerPrinter#getBanner 默认返回的是SpringBootBanner.但是当我们在类路径下 放⼊banner.txt或者在banner.image.location 放⼊图⽚.⼜该如何呢? 此时返回的是Banners.在打印时会调⽤Banners#printBanner⽅法.代码如下:

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

很简单循环遍历banners调⽤其printBanner进⾏打印.那么Banners会有哪些banner呢?由前可知有

1. ImageBanner

2. ResourceBanner

那么我们就分别看下其printBanner⽅法:

1. ImageBanner#printBanner 代码如下:

ublic void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {// 1. 获取系统环境变量中的java.awt.headless变量String headless = System.getProperty("java.awt.headless");try {// 2. 设置java.awt.headless变量值为true。并调⽤printBanner⽅法进⾏图案的打印⼯作System.setProperty("java.awt.headless", "true");this.printBanner(environment, out);} catch (Throwable var9) {logger.warn("Image banner not printable: " + this.image + " (" + var9.getClass() + ": '" + var9.getMessage() + "')");logger.debug("Image banner printing failure", var9);} finally {// 3. finally中还原操作系统中的java.awt.headless环境变量值if (headless == null) {System.clearProperty("java.awt.headless");} else {System.setProperty("java.awt.headless", headless);}}}

做了3件事
1. 获取系统环境变量中的java.awt.headless变量。
2. 设置java.awt.headless变量值为true。并调⽤printBanner⽅法进⾏图案的打印⼯作
3. finally中还原操作系统中的java.awt.headless环境变量值。

指定⼀提的是,java.awt.headless 默认就是true。

printBanner⽅法代码如下:

private void printBanner(Environment environment, PrintStream out) throws IOException {PropertyResolver properties = new RelaxedPropertyResolver(environment, "banner.image.");int width = (Integer)properties.getProperty("width", Integer.class, 76);int height = (Integer)properties.getProperty("height", Integer.class, 0);int margin = (Integer)properties.getProperty("margin", Integer.class, 2);boolean invert = (Boolean)properties.getProperty("invert", Boolean.class, false);BufferedImage image = this.readImage(width, height);this.printBanner(image, margin, invert, out);}

还是3件事
1. 读取banner.image.width,默认为 76 . 读取banner.image.height,默认为 0 . 读取
banner.image.margin,默认为 2. 读取banner.image.invert,默认为 false.
2. 调⽤readImage 进⾏图⽚的读取.代码如下:

private BufferedImage readImage(int width, int height) throws IOException {InputStream inputStream = this.image.getInputStream();BufferedImage var5;try {BufferedImage image = ImageIO.read(inputStream);var5 = this.resizeImage(image, width, height);} finally {inputStream.close();}return var5;}

通过ImageIO进⾏读取,最后通过读取图⽚的配置参数,进⾏图⽚的缩放处理。

3. printBanner 实现如下:

private void printBanner(BufferedImage image, int margin, boolean invert, PrintStream out) {AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT;out.print(AnsiOutput.encode(AnsiColor.DEFAULT));out.print(AnsiOutput.encode(background));out.println();out.println();AnsiColor lastColor = AnsiColor.DEFAULT;for(int y = 0; y < image.getHeight(); ++y) {int x;for(x = 0; x < margin; ++x) {out.print(" ");}for(x = 0; x < image.getWidth(); ++x) {Color color = new Color(image.getRGB(x, y), false);AnsiColor ansiColor = AnsiColors.getClosest(color);if (ansiColor != lastColor) {out.print(AnsiOutput.encode(ansiColor));lastColor = ansiColor;}out.print(this.getAsciiPixel(color, invert));}out.println();}out.print(AnsiOutput.encode(AnsiColor.DEFAULT));out.print(AnsiOutput.encode(AnsiBackground.DEFAULT));out.println();}

没什么可说的,图⽚是由⼀个⼀个的像素组成的,直接输出每个像素即可。

2. ResourceBanner#printBanner,代码如下:

public void printBanner(Environment environment, Class<?> sourceClass,PrintStream out) {try {// 1. 获取resource中的输⼊流,并将其转化为字符串 通过environment获取banner.charset变量,如果不存在,则默认使⽤UTF-8编码String banner =                         StreamUtils.copyToString(this.resource.getInputStream(),environment.getProperty("banner.charset", Charset.class,Charset.forName("UTF-8")));// 2. 循环遍历所有的PropertyResolver 去解析banner中配置的spel表达式for (PropertyResolver resolver : getPropertyResolvers(environment,sourceClass)) {banner = resolver.resolvePlaceholders(banner);}// 3. 打印字符串信息out.println(banner);}catch (Exception ex) {logger.warn("Banner not printable: " + this.resource + " (" + ex.getClass()+ ": '" + ex.getMessage() + "')", ex);}
}

还是3步:
1. 获取resource中的输⼊流,并将其转化为字符串 通过environment获取banner.charset变量,如果不
存在,则默认使⽤UTF-8编码
2. 循环遍历所有的PropertyResolver 去解析banner中配置的spel表达式.
⾸先通过getPropertyResolvers 获得所有的PropertyResolver.代码如下:

protected List<PropertyResolver> getPropertyResolvers(Environment environment,
Class<?> sourceClass) {// 1. 实例化resolvers集合,并添加environment元素, Environment接⼝继承⾃PropertyResolver接⼝List<PropertyResolver> resolvers = new ArrayList<PropertyResolver>();resolvers.add(environment);// 2. 调⽤getVersionResolver(sourceClass)⽅法并将其返回值添加到resolvers集合resolvers.add(getVersionResolver(sourceClass));// 3. 调⽤getAnsiResolver(sourceClass)⽅法并将其返回值添加到resolvers集合 直接设置开启了ansiresolvers.add(getAnsiResolver());// 4. 调⽤getTitleResolver(sourceClass)⽅法并将其返回值添加到resolvers集合resolvers.add(getTitleResolver(sourceClass));return resolvers;
}

4件事:
1. 实例化resolvers集合,并添加environment元素, Environment接⼝继承⾃PropertyResolver接

2. 调⽤getVersionResolver(sourceClass)⽅法并将其返回值添加到resolvers集合。

代码如下:

private PropertyResolver getVersionResolver(Class<?> sourceClass) {MutablePropertySources propertySources = new MutablePropertySources();propertySources.addLast(new MapPropertySource("version", getVersionsMap(sourceClas
s)));return new PropertySourcesPropertyResolver(propertySources);
}

其构建了⼀个MapPropertySource,名为version,value是通过getVersionsMap⽅法获得的.最后
返回⼀个PropertySourcesPropertyResolver.代码如下:

private Map<String, Object> getVersionsMap(Class<?> sourceClass) {// 获取sourceClass所在包的版本号String appVersion = getApplicationVersion(sourceClass);// 获取Boot版本号String bootVersion = getBootVersion();Map<String, Object> versions = new HashMap<String, Object>();versions.put("application.version", getVersionString(appVersion, false));versions.put("spring-boot.version", getVersionString(bootVersion, false));versions.put("application.formatted-version", getVersionString(appVersion, true));versions.put("spring-boot.formatted-version",getVersionString(bootVersion, true));return versions;
}
protected String getApplicationVersion(Class<?> sourceClass) {Package sourcePackage = (sourceClass == null ? null : sourceClass.getPackage());return (sourcePackage == null ? null : sourcePackage.getImplementationVersion());
}
protected String getBootVersion() {return SpringBootVersion.getVersion();
}
private String getVersionString(String version, boolean format) {if (version == null) {return "";
}return (format ? " (v" + version + ")" : version);
}

逻辑如下:
1. ⾸先通过调⽤getApplicationVersion⽅法获得appVersion.其是通过获取sourceClass所在
包的版本号. sourceClass为应⽤的启动类
2. 获取Boot版本号.同样是通过获得SpringApplication所在包的版本号完成的
3. 在map中存⼊数据.
该⽅法最终的数据为:

{application.formatted-version=, application.version=, spring-boot.formatted-version=, springboot.version=}

3. 调⽤getAnsiResolver(sourceClass)⽅法并将其返回值添加到resolvers集合 直接设置开启了ansi.代码如下:

private PropertyResolver getAnsiResolver() {MutablePropertySources sources = new MutablePropertySources();sources.addFirst(new AnsiPropertySource("ansi", true));return new PropertySourcesPropertyResolver(sources);
}

4. 调⽤getTitleResolver(sourceClass)⽅法并将其返回值添加到resolvers集合.代码如下:

private PropertyResolver getTitleResolver(Class<?> sourceClass) {MutablePropertySources sources = new MutablePropertySources();String applicationTitle = getApplicationTitle(sourceClass);// 获取当前启动类中所在的包中的Implementation-Title属性值,并将其添加到sources中。Map<String, Object> titleMap = Collections.<String,     Object>singletonMap("application.title", (applicationTitle == null ? "" : applicationTitle));sources.addFirst(new MapPropertySource("title", titleMap));return new PropertySourcesPropertyResolver(sources);
}

调⽤getApplicationTitle获得title.代码如下:

protected String getApplicationTitle(Class<?> sourceClass) {Package sourcePackage = (sourceClass == null ? null : sourceClass.getPackage());return (sourcePackage == null ? null : sourcePackage.getImplementationTitle());
}

参考链接

新年彩蛋: Spring Boot⾃定义Banner
Spring Boot Logback应⽤⽇志
springboot源码分析3-springboot之banner类架构以及原理
Spring Boot⼲货系列:(七)默认⽇志logback配置解析

SpringApplication#run⽅法第5步,打印banner(四)相关推荐

  1. SpringApplication.run做了哪些事情

    URL https://mp.weixin.qq.com/s/uP4seo__qYMJMzmbWyUUnA?tdsourcetag=s_pctim_aiomsg SpringApplication.r ...

  2. SpringApplication.run(MyApplication.class, args)运行流程源码分析

    目录 SpringApplication.run(MyApplication.class, args);如何启动springBoot项目的 run() ConfigurableApplicationC ...

  3. Alian解读SpringBoot 2.6.0 源码(五):启动流程分析之打印Banner

    目录 一.背景 1.1.run方法整体流程 1.2.本文解读范围 二.打印banner 2.1.整体流程 2.1.1.图片Banner获取流程 2.1.2.文本Banner获取流程 2.2.具体实现 ...

  4. 强化学习(七):n步自举法(多步引导法)

    强化学习(七):n步自举法(多步引导法)   在之前,我们知道求解有限马尔可夫决策过程可以通过蒙特卡洛和时序差分来通过与环境多次交互从经验中学习,然而,蒙特卡洛方法在一些不满足分幕式任务或连续型任务上 ...

  5. 超详细!17 步全解四象限导图的使用方法

    导读:四象限问题导图--为我们提供捕捉灵感.筛选故事主体内容的空间. 作者:玛丽昂·沙罗(Marion Charreau).珍妮弗·约翰逊(Jenifer L. Johnson) 来源:大数据DT(I ...

  6. 赵钱孙李称体重,按照由大到小的顺序,打印出四人的姓氏的首字母和体重数(中间用空格隔开,每人一行)

    题目 - 称体重 描述 赵.钱.孙.李四个人中既有大人也有小孩,给他们称体重时发现,他们每个人的体重都不一样,且体重(单位:公斤)恰好是10的整数倍,且他们的体重都不高于50公斤,已知赵.钱两人的体重 ...

  7. 经典运动估计算法之全搜索、三步搜索、四步搜索、菱形搜索

    全搜索算法 三步搜索算法 四步搜索算法 菱形搜索算法 由于搜索方法的不同,因此有多种运动估计算法,较为经典的运动估计搜索算法有全搜索法.三步搜索法.菱形搜索法以及四步搜索法等等.以下是几种运动估计搜索 ...

  8. 全国计算机英语四六级准考证打印准考证号,大学英语四六级准考证打印入口|四六级准考证打印入口2020...

    全国大学英语四.六级考试马上就开始啦,准备考四六级的你还不知道​​​大学英语四六级准考证打印入口|四六级准考证打印入口2020.中公教师网小编把​大学英语四六级准考证打印入口|四六级准考证打印入口20 ...

  9. 打印英语四六级准考证pdf

    打印英语四六级准考证 注意点: 1.你必须具备一个能知道自己身份的资料 2.你还需要有第三方的验证码接口,其实不贵,1元可以500次,直接搜快识别注册就可以了 3.使用ip代理(不推荐,这是不合法的) ...

  10. 回朔法象棋马步问题java编程,任意六十个点连通图的货郎担回路和马步哈密顿圈...

    [   穷举法 列举所有可能,然后一个个去,得到最优的结果.如图一,需要从A点一直走到G点,才能知道,F是最高的(最优解).这种算法得到的最优� ...] 任意六十个点连通图的货郎担回路和马步哈密顿圈 ...

最新文章

  1. 韩国国税局正调查华为当地分公司 回应称“例行常规审计”
  2. 系分考试论文实例12篇
  3. 手把手带你使用JS-SDK自定义微信分享效果
  4. 北斗导航 | 从北斗二号到北斗三号
  5. PHP处理Checkbox复选框表单提交
  6. 单多晶之争:光伏技术要靠市场检验
  7. 【环境搭建003】UBUNTU + ECLIPS + ANDROID 嵌入式系统编译环境搭建遇到的稀奇古怪的问题集合
  8. Thingsboard 3.1.0 - 数据订阅
  9. vue 同步加载_如何在vue里实现同步阻塞请求,请求完成之前不加载页面或组件?...
  10. 2019matlab安装
  11. python 直播源_直播源获取软件下载|直播源获取工具(斗鱼B站西瓜)下载-蛙扑下载站...
  12. 艾肯4nano声卡调试教程,效果演示
  13. C++ 创建 TcpClient 客户端,使用QAbstractSocket 、 QtNetwork
  14. CSDN:2020 年度 CSDN 博客之星评选——28 号【沉默王二】,感谢你投上的宝贵一票,感谢!
  15. CNN中的小tips
  16. java实现微信服务(公众)号用户关注时,获取openid,安全模式下的加密解密实现
  17. 刚入行java程序员VS 3年以上java程序员,太形象了,哈哈哈~·~
  18. AI:人工智能技术层企业简介(更新中)
  19. MATLAB快速获取二维图像/矩阵最大值和位置
  20. Neat Reader初体验

热门文章

  1. java severlet 例子_Java开发Servlet实例
  2. Spring Boot 2.0 从入门到精通 From Zero to Hero with Spring Boot - Brian Clozel
  3. 如何在Swift中掌握协议
  4. 编写时间的php,PHP如何实现简单日历类编写 PHP实现简单日历类编写代码
  5. python凯撒加密带大小写_python实现凯撒加密
  6. 微软改进的DSSM结构:
  7. 训练一个简单的游戏AI(Deep Q Network)
  8. MapReduce输出压缩格式文件
  9. win10开启文件共享服务器,墨涩网 - Windows10开启局域网文件共享功能——墨涩网...
  10. 手机同步查看html,手机版同步html几点注意使用