springboot源码分析3-springboot之banner类架构以及原理

简介: 继续上文的进行讲解,上一节我们详细详解了banner的三种输出模式、banner的输出模式设置、banner类的架构、SpringApplicationBannerPrinter类、ImageBanner以及TextBanner的处理方式。

继续上文的<<springboot源码分析2-springboot banner定制以及原理章节>>进行讲解,上一节我们详细详解了banner的三种输出模式、banner的输出模式设置、banner类的架构、SpringApplicationBannerPrinter类、ImageBanner以及TextBanner的处理方式。本小节我们来重点讲解一下各种banner处理类的相关实现逻辑以及设计意图和职责。

1.1 SpringBootBanner类

SpringBootBanner类为默认的banner处理类。该类的核心代码如下所示:

1 class SpringBootBanner implements Banner {

2  private static final String[] BANNER = { "",

3  "  .   ____          _            __ _ _",

4  " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",

5  "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",

6  " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",

7  "  '  |____| .__|_| |_|_| |_\\__, | / / / /",

8  " =========|_|==============|___/=/_/_/_/" };

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

10  private static final int STRAP_LINE_SIZE = 42;

11  @Override

12  public void printBanner(Environment environment, Class<?> sourceClass,PrintStream printStream) {

13    for (String line : BANNER) {

             printStream.println(line);

14    }

15  String version = SpringBootVersion.getVersion();

16  version = (version == null ? "" : " (v" + version + ")");

17  String padding = "";

18  while (padding.length() < STRAP_LINE_SIZE

19  - (version.length() + SPRING_BOOT.length())) {

20  padding += " ";

21  }

22  printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,

23  AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version));

24  printStream.println();

25  }

26 

27 }

看到上述的BANNER变量,是不是很眼熟,没错这个就是springboot启动的时候,默认的输出图案。我们再次看一下springboot启动的时候默认输出的图案,如下图所示:

                      

 

BANNER变量的内容正是上图中的上部分。也就是SPRING字符串。

注意:BANNER变量是一个字符串数组,数组的每一个元素内容均对应上图中的每一行字符串。为何这样设计呢?我们思考一下?这样设计的目的究其原因是为了防止定义一个大的字符串而出现跑位或者字符串便宜的问题。

  接下来的重心看一下printBanner方法的执行逻辑:

1、循环遍历BANNER数组,并依次进行数组内容的打印。代码为printStream.println(line)

2、调用SpringBootVersion.getVersion(),进行springboot版本信息的获取工作,这种方式我们在前面的系列文章中也详细讲解了,再次不再累赘。

3、version字符串进行再加工,因此version最终的值为 (v2.0.0.M3)

4、开始拼接:: Spring Boot ::(v2.0.0.M3)的值,最终的效果如上图所示。通过while循环的逻辑可知、一行的字符串长度是42个。

5、开始打印字符串,这个大家有兴趣可以自己跟踪一下,相对而言比较简单。主要看下AnsiOutput.toString方法即可。

至此、springboot默认的banner图案以及输出逻辑我们已经梳理完毕。

 

1.2 PrintedBanner类

PrintedBanner类中的核心代码如下:

1 private static class PrintedBanner implements Banner {

2  private final Banner banner;

3  private final Class<?> sourceClass;

4  PrintedBanner(Banner banner, Class<?> sourceClass) {

5    this.banner = banner;

6    this.sourceClass = sourceClass;

7  }

8  public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {

9    sourceClass = (sourceClass == null ? this.sourceClass : sourceClass);

10    this.banner.printBanner(environment, sourceClass, out);

11  }

12  }

   PrintedBanner类看上去貌似没有什么神奇的作用,但是这个类却有点装饰者模式的味道,该类的构造函数需要一个Banner 类型以及Class类型的参数。在printBanner方法中,唯一做的事情就是判断并设置sourceClass参数,然后直接调用banner的printBanner方法。为何这样设计呢?其实这个地方病没有太多神秘的地方。我们只需要知道一点sourceClass是可以自己定义的即可。sourceClass类又是做什么的呢?关于这点我们稍有印象即可,稍后即可看到。

1.3 ImageBanner

ImageBanner类的核心实现如下所示:

1  public void printBanner(Environment environment, Class<?> sourceClass,PrintStream out) {

2  String headless = System.getProperty("java.awt.headless");

3  try {

4       System.setProperty("java.awt.headless", "true");

5       printBanner(environment, out);

6  }

7  catch (Throwable ex) {

8           ......

9  }

10  finally {

11  if (headless == null) {

12  System.clearProperty("java.awt.headless");

13  }

14  else {

15  System.setProperty("java.awt.headless", headless);

16  }

17  }

18  }

上述的代码主要从如下几个步骤进行。

1、获取系统环境变量中的java.awt.headless变量。

2、设置java.awt.headless变量值为true。并调用printBanner方法进行图案的打印工作。

3、finally中还原操作系统中的java.awt.headless环境变量值。

细心的朋友就会发现上述的步骤2有问题的。如果系统中已经设置java.awt.headless变量值为true,还有必要再设置一次吗?很显然,这个地方的代码可以改进下,加一个if判断。

1.3.1 java.awt.headless 模式

下面补充下java.awt.headless的相关知识点。

1. 什么是 java.awt.headless
Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
2. 何时使用和headless mode
Headless模式虽然不是我们愿意见到的,但事实上我们却常常需要在该模式下工作,尤其是服务器端程序开发者。因为服务器(如提供Web服务的主机)往往可能缺少前述设备,但又需要使用他们提供的功能,生成相应的数据,以提供给客户端(如浏览器所在的配有相关的显示设备、键盘和鼠标的主机)。

1.3.2 printBanner方法

继续看一下printBanner方法吧,该方法的实例代码如下:

1 private void printBanner(Environment environment, PrintStream out)throws IOException {

2  int width = environment.getProperty("banner.image.width", Integer.class, 76);

3  int height = environment.getProperty("banner.image.height", Integer.class, 0);

4  int margin = environment.getProperty("banner.image.margin", Integer.class, 2);

5  boolean invert = environment.getProperty("banner.image.invert", Boolean.class,false);

6  BufferedImage image = readImage(width, height);

7  printBanner(image, margin, invert, out);

8  }

上述的方法开始获取banner.image.width、banner.image.height、banner.image.margin、banner.image.invert四个属性值。这几个属性均可以在application.properties中,或者通过命令行参数进行相应的设置。如下图所示:

 

 

上述四个参数的含义、默认值说明:

banner.image.width":默认值76,图案的宽度

"banner.image.height":默认值0,图案的高度

"banner.image.margin":默认值 2,空字符的数量

"banner.image.invert":默认值false,是否颠倒

在这里,我们只需要记住上述的几个变量是可以配置的即可,关于图片流的读取以及输出,在这里我们就不详细讲解了。

1.4 ResourceBanner

ResourceBanner类为资源Banner。这个类可能很少有人使用到,但是麻雀虽小五脏俱全,这个类涉及到的知识点不少,我们先看下如何使用这个类。实例代码如下:

1 @SpringBootApplication

2 public class DemoApplication {

3  public static void main(String[] args) {

4  SpringApplication springApplication=new SpringApplication();

5  springApplication.setBannerMode(Banner.Mode.CONSOLE);

6  Resource resource=new ClassPathResource("banner.txt");

7  springApplication.setBanner(new ResourceBanner(resource));

8  springApplication.run(DemoApplication.class, args);

9  }

10 }

    我们实例化了一个ClassPathResource类并传了字符串banner.txt。在这里还要脑补一下Spring中的东西,那就是Resource

Resource是抽象了所有的配置文件以及属性文件、在Spring框架看来所有的文件、网络资源、jar、属性配置文件等都是资源,因此也提供了不同的资源读取类,其中ClassPathResource就是读取ClassPath路径中的一些资源文件,这里我们传递的是banner.txt该文件的内容信息如下:分享牛原创网:${application.title}

其中application.title定义在application.properties中,如下所示:

application.title=http://www.shareniu.com/

运行上述代码程序的输出如下:

分享牛原创:http://www.shareniu.com/

   这个类确实有点意思,不仅支持自定义资源文件的读取,而且还支持Spring中的spel表达式。我们迫不及待的要去看看ResourceBanner类。

ResourceBanner类的printBanner方法如下所示:

1 public void printBanner(Environment environment, Class<?> sourceClass,PrintStream out) {

2  try {

3  String banner = StreamUtils.copyToString(this.resource.getInputStream(),

4  environment.getProperty("banner.charset", Charset.class,Charset.forName("UTF-8")));

5  for (PropertyResolver resolver : getPropertyResolvers(environment,sourceClass)) {

6            banner = resolver.resolvePlaceholders(banner);

7  }

8  out.println(banner);

9  }

10  catch (Exception ex) {

11  }

12  }

上述方法的执行逻辑进行如下的总结:

1、获取resource中的输入流,并将其转化为字符串。

2、通过environment获取banner.charset变量,如果不存在,则默认使用UTF-8编码。在这里我们再次啰嗦一句话,springboot中所有的配置属性信息最后都会封装为environment中去,因此可以通过environment获取到项目中所有的配置属性信息。

3、循环遍历所有的PropertyResolver 去解析banner中配置的spel表达式。比如上文中的${application.title}就是在这个步骤进行处理的。

4、打印字符串信息。

1.4.1 PropertyResolvers集合初始化

    上述中的第二步调用了getPropertyResolvers方法,该方法的实例代码如下:

1 protected List<PropertyResolver> getPropertyResolvers(Environment environment,

2  Class<?> sourceClass) {

3  List<PropertyResolver> resolvers = new ArrayList<>();

4  resolvers.add(environment);

5  resolvers.add(getVersionResolver(sourceClass));

6  resolvers.add(getAnsiResolver());

7  resolvers.add(getTitleResolver(sourceClass));

8  return resolvers;

9  }

getPropertyResolvers方法的执行逻辑如下:

1、实例化resolvers集合,并添加environment元素,Environment接口继承自PropertyResolver接口。

2、调用getVersionResolver(sourceClass)方法并将其返回值添加到resolvers集合。getVersionResolver(sourceClass)方法的实现如下所示:

1 private PropertyResolver getVersionResolver(Class<?> sourceClass) {

2  MutablePropertySources propertySources = new MutablePropertySources();

3    propertySources.addLast(new MapPropertySource("version", getVersionsMap(sourceClass)));

4   return new PropertySourcesPropertyResolver(propertySources);

5  }

6  private Map<String, Object> getVersionsMap(Class<?> sourceClass) {

7      String appVersion = getApplicationVersion(sourceClass);//获取sourceClass所在包的版本号

8      String bootVersion = getBootVersion();//获取Boot版本号,我使用的版本是v2.0.0.M3

9      Map<String, Object> versions = new HashMap<>();

10      versions.put("application.version", getVersionString(appVersion, false));

11      versions.put("spring-boot.version", getVersionString(bootVersion, false));

12     versions.put("application.formatted-version", getVersionString(appVersion, true));

13     versions.put("spring-boot.formatted-version",getVersionString(bootVersion, true));

14      return versions;

15  }

    上述代码中,直接实例化MutablePropertySources类,并将其添加到环境propertySources中,在这里在强调一点,环境变量的相关知识点后续会专门单独一章进行讲解。大家可以理解为propertySources是所有的属性容器就够了,我们可以通过propertySources获取到项目中配置的所有属性以及值。

   上述的代码设置了如下几个属性以及值:application.versionspring-boot.versionapplication.formatted-versionspring-boot.formatted-version其中application.version以及application.formatted-version两个我们可以自定义设置。

上述的几个属性,我将其打印了,输出信息如下:

{application.formatted-version=, application.version=, spring-boot.formatted-version= (v2.0.0.M3), spring-boot.version=2.0.0.M3}

3、调用getAnsiResolver(sourceClass)方法并将其返回值添加到resolvers集合。getAnsiResolver(sourceClass)方法的实现如下所示:

1 private PropertyResolver getAnsiResolver() {

2  MutablePropertySources sources = new MutablePropertySources();

3  sources.addFirst(new AnsiPropertySource("ansi", true));

4  return new PropertySourcesPropertyResolver(sources);

5  }

直接设置开启了ansi。讲解到环境变量的时候一起进行讲解。

4、调用getTitleResolver(sourceClass)方法并将其返回值添加到resolvers集合。getTitleResolver(sourceClass)方法的实现如下所示:

1  private PropertyResolver getTitleResolver(Class<?> sourceClass) {

2  MutablePropertySources sources = new MutablePropertySources();

3  String applicationTitle = getApplicationTitle(sourceClass);

4  Map<String, Object> titleMap = Collections.<String, Object>singletonMap(

5  "application.title", (applicationTitle == null ? "" : applicationTitle));

6  sources.addFirst(new MapPropertySource("title", titleMap));

7  return new PropertySourcesPropertyResolver(sources);

8  }

获取当前启动类中所在的包中的Implementation-Title属性值,并将其添加到sources中。

关于占位符的替换,我们后续的文章开始展开讲解。饭要一口口的吃嘛。至此各种Banner类已经讲解完毕。


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Java相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。
 

   

作者:分享牛
         
本博客中未标明转载的文章归作者 分享牛所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


相关文章
|
1月前
|
存储 搜索推荐 数据挖掘
ElasticSearch架构介绍及原理解析
ElasticSearch架构介绍及原理解析
86 0
|
1月前
|
存储 运维 负载均衡
MFS详解(二)——MFS原理和架构
MFS详解(二)——MFS原理和架构
30 0
|
2月前
|
架构师 安全 Java
资深架构师带你解析Synchronize关键字原理
众所周知 Synchronize 关键字是解决并发问题常用解决方案,有以下三种使用方式:
26 0
|
1月前
|
消息中间件 Cloud Native Java
【Spring云原生系列】SpringBoot+Spring Cloud Stream:消息驱动架构(MDA)解析,实现异步处理与解耦合
【Spring云原生系列】SpringBoot+Spring Cloud Stream:消息驱动架构(MDA)解析,实现异步处理与解耦合
|
1月前
|
存储 缓存 运维
ISCSI详解(三)——ISCSI原理和架构
ISCSI详解(三)——ISCSI原理和架构
44 2
|
20天前
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
23 0
|
20天前
|
存储 Java 应用服务中间件
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
42 0
|
24天前
|
程序员 Python
类的设计奥秘:从代码到架构的科普全解
类的设计奥秘:从代码到架构的科普全解
12 2
|
29天前
|
存储 消息中间件 算法
深度思考:架构师必须掌握的五大类架构设计风格
数据流风格注重数据在组件间的流动,适合处理大量数据。调用返回风格则强调函数或方法的调用与返回,过程清晰明了。独立构件风格让每个构件独立运作,通过接口交互,提升灵活性和可重用性。虚拟机风格则模拟完整系统,实现资源的高效利用。
深度思考:架构师必须掌握的五大类架构设计风格
|
1月前
|
Java Spring
springboot自定义banner
springboot自定义banner
springboot自定义banner