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月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
36 0
|
2天前
|
决策智能 数据库 开发者
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
本项目旨在解决智能体的“超级入口”问题,通过开发基于意图识别的多智能体框架,实现用户通过单一交互入口使用所有智能体。项目依托阿里开源的Qwen2.5大模型,利用其强大的FunctionCall能力,精准识别用户意图并调用相应智能体。 核心功能包括: - 意图识别:基于Qwen2.5的大模型方法调用能力,准确识别用户意图。 - 业务调用中心:解耦框架与业务逻辑,集中处理业务方法调用,提升系统灵活性。 - 会话管理:支持连续对话,保存用户会话历史,确保上下文连贯性。 - 流式返回:支持打字机效果的流式返回,增强用户体验。 感谢Qwen2.5系列大模型的支持,使项目得以顺利实施。
118 5
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
|
3天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
1月前
|
Java Spring
SpringBoot入门 - 定制自己的Banner
SpringBoot入门 - 定制自己的Banner
26 2
SpringBoot入门 - 定制自己的Banner
|
26天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
87 5
|
1月前
|
Java Spring
SpringBoot自动装配的原理
在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
59 17
|
1月前
|
Java 容器
springboot自动配置原理
启动类@SpringbootApplication注解下,有三个关键注解 (1)@springbootConfiguration:表示启动类是一个自动配置类 (2)@CompontScan:扫描启动类所在包外的组件到容器中 (3)@EnableConfigutarion:最关键的一个注解,他拥有两个子注解,其中@AutoConfigurationpackageu会将启动类所在包下的所有组件到容器中,@Import会导入一个自动配置文件选择器,他会去加载META_INF目录下的spring.factories文件,这个文件中存放很大自动配置类的全类名,这些类会根据元注解的装配条件生效,生效
|
1月前
|
Java Spring
SpringBoot入门(5) - 定制自己的Banner
SpringBoot入门(5) - 定制自己的Banner
13 0
 SpringBoot入门(5) - 定制自己的Banner

热门文章

最新文章