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

继续上文的<<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.version、spring-boot.version、application.formatted-version、spring-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相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。 

   

作者:分享牛
    
出处:http://blog.csdn.net/qq_30739519

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

时间: 2024-10-19 03:26:44

springboot源码分析3-springboot之banner类架构以及原理的相关文章

springboot源码分析2-springboot 之banner定制以及原理

1. springboot源码分析2-springboot 之banner定制以及原理 springboot在启动的时候,默认会在控制台输出默认的banner.也就是我们经常所说的图案,输出的图案如下所示:   .   ____          _            __ _ _  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  \\/  ___)| |_)| | | |

springboot源码分析11-ApplicationContextInitializer原理

摘要:springboot源码分析10-ApplicationContextInitializer使用一文中,我们详细地讲解了ApplicationContextInitializer的三种使用方式,本文我们重点看一下为何这三种方式都可以使用,也就是框架是如何处理的.包括内置的ContextIdApplicationContextInitializer.DelegatingApplicationContextInitializer. 1.1. 用户手动添加ApplicationContextIn

springboot源码分析6-springboot之PropertySource类初探

摘要:本小节重点梳理一下PropertySource类的相关结构以及职责,本文的学习前提是学习了springboot源码分析5-springboot之命令行参数以及原理一文. 在springboot源码分析5-springboot之命令行参数以及原理一文中,我们看到了实例化Source类的时候,会去先实例化其父类SimpleCommandLinePropertySource.SimpleCommandLinePropertySource类的构造函数中直接解析了命令行参数以及值,然后返回封装好的C

springboot源码分析10-ApplicationContextInitializer使用

摘要:spring中ApplicationContextInitializer接口是在ConfigurableApplicationContext刷新之前初始化ConfigurableApplicationContext的回调接口.当spring框架内部执行 ConfigurableApplicationContext#refresh() 方法的时候回去回调. 1.1. 实现方式一 首先,我们需要自定义一个类并且实现ApplicationContextInitializer接口.示例代码如下:

springboot源码分析7-环境属性构造过程(上)

使用springboot的目的就是在项目开发中,快速出东西,因此springboot对于配置文件的格式支持是非常丰富的,最常见的配置文件后缀有如下四种:properties.xml.yml.yaml,比如我们在springboot项目根目录中配置了一个application.properties文件,则springboot项目启动的时候就会自动将该文件的内容解析并设置到环境中,这样后续需要使用该文件中配置的属性的时候,只需要使用@value即可.同理application.xml.applica

springboot源码分析14-ApplicationContextInitializer原理Springboot中PropertySource注解多环境支持以及原理

摘要:Springboot中PropertySource注解的使用一文中,详细讲解了PropertySource注解的使用,通过PropertySource注解去加载指定的资源文件.然后将加载的属性注入到指定的配置类,@value以及@ConfigurationProperties的使用.但是也遗留一个问题,PropertySource注解貌似是不支持多种环境的动态切换?这个问题该如何解决呢?我们需要从源码中看看他到底是否支持. 首先,我们开始回顾一下上节课说的PropertySource注解的

JVM源码分析之谨防JDK8重复类定义造成的内存泄漏

概述 如今JDK8成了主流,大家都紧锣密鼓地进行着升级,享受着JDK8带来的各种便利,然而有时候升级并没有那么顺利?比如说今天要说的这个问题.我们都知道JDK8在内存模型上最大的改变是,放弃了Perm,迎来了Metaspace的时代.如果你对Metaspace还不熟,之前我写过一篇介绍Metaspace的文章,大家有兴趣的可以看看我前面的那篇文章. 我们之前一般在系统的JVM参数上都加了类似-XX:PermSize=256M -XX:MaxPermSize=256M的参数,升级到JDK8之后,因

springboot源码分析4-springboot之SpringFactoriesLoader使用

摘要:本文我们重点分析一下Spring框架中的SpringFactoriesLoader类以及META-INF/spring.factories的使用.在详细分析之前,我们可以思考一个问题?在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?其一:我们可以规范不同的系统调用,也就是传递一个系统标识:其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理:其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一

springboot源码分析9-random的使用以及原理

摘要:springboot框架为我们提供了很多的便利,其中有一个非常有意思的功能,那就是可以通过变量的方式来配置一个随机数random,然后使用random随机出各式各样数值.本位重点讲解一下random的使用以及框架内部的实现机制. 1.1. Springboot中random的使用 首先我们定义一个配置类,如下所示: 1 @Component 2 public class Config { 3  @Value("${random.value}") 4  private String