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相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。
 

   

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


相关文章
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
3月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
658 3
|
1月前
|
监控 Cloud Native Java
Spring Boot 3.x 微服务架构实战指南
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Spring Boot 3.x与微服务架构,探索云原生、性能优化与高可用系统设计。以代码为笔,在二进制星河中谱写极客诗篇。关注我,共赴技术星辰大海!(238字)
Spring Boot 3.x 微服务架构实战指南
|
1月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
266 3
|
1月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
337 2
|
3月前
|
机器学习/深度学习 算法 文件存储
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
神经架构搜索(NAS)正被广泛应用于大模型及语言/视觉模型设计,如LangVision-LoRA-NAS、Jet-Nemotron等。本文回顾NAS核心技术,解析其自动化设计原理,探讨强化学习、进化算法与梯度方法的应用与差异,揭示NAS在大模型时代的潜力与挑战。
786 6
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
|
2月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
752 1
|
1月前
|
机器学习/深度学习 自然语言处理 监控
23_Transformer架构详解:从原理到PyTorch实现
Transformer架构自2017年Google发表的论文《Attention Is All You Need》中提出以来,彻底改变了深度学习特别是自然语言处理领域的格局。在短短几年内,Transformer已成为几乎所有现代大型语言模型(LLM)的基础架构,包括BERT、GPT系列、T5等革命性模型。与传统的RNN和LSTM相比,Transformer通过自注意力机制实现了并行化训练,极大提高了模型的训练效率和性能。
|
4月前
|
存储 监控 算法
园区导航系统技术架构实现与原理解构
本文聚焦园区导航场景中室内外定位精度不足、车辆调度路径规划低效、数据孤岛难以支撑决策等技术痛点,从架构设计到技术原理,对该系统从定位到数据中台进行技术拆解。
200 0
园区导航系统技术架构实现与原理解构