1.目录&前言
3.1.1 设置ApppliplicationListener
3.2.2.1 initializeProfiles()方法
3.2.2.3 addLoadedPropertySources()方法
前言:
在日常开发中,我们会经常使用到配置在JVM和配置文件的配置参数,本文将解读SpringBoot如何读取JVM参数和常见的如application.xml等配置文件的参数。
在SpringBoot项目的启动流程中,Environment变量的设置是在Spring容器中Bean定义对象的读取、Bean对象创建以前完成的,因此了解其生命周期对于理解整个Spring框架都大有益处。
本文篇幅稍长,字数超18000+,码字不易,尤其是原创技术类文章,各位如果觉得还行请点赞、收藏。
2.准备工作
在SpringBoot中,解读Environment变量的读取需要引入至少两个JAR包:spring-boot以及spring-core,不过这里我们引入spring-boot-starter即可, 因为它包含了这两个JAR包。
注意这里是引入小于2.4的版本,因为在2.4+的版本,对配置文件的读取做了升级,并且个人觉得新版本的实现逻辑比旧版本的要更难阅读理解,但本文主要是为了理解SpringBoot中的一些基本大致原理,因此在这里以较为简单的版本进行解读。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
</dependencies>
3.源码解读
源码内容较多,本章节将挑选重点、关键点源码进行解读,把这些关键点串联起来后,可以理解其源码大概的设计思路。
本章节会详细介绍Enviroment变量的完整的生命周期和特点、以及其如归并配置文件数据时用到的一些设计模式等。
在SpringBoot框架中,是通过运行Main函数中的SpringApplication.run(primarySource.class, args)代码行启动项目的,其中primarySource是启动类的class对象。
在SpringApplication#run(Class<?> primarySource, String... args)方法里面实际上是通过创建一个具体的SpringApplication的实例对象,后续再调用run(String... args)去完成SpringBoot框架的一个搭建工作。
Enviroment变量的创建正是在run(String... args)进行的,不过在这里有一个个人觉得比较巧妙的地方,那就是在SpringApplication的构造函数里面,设置了会在后面Enviroment变量创建过程中用到的监听器对象——ApplicationListener。
因此本章节将主要介绍SpringApplication和Enviroment变量的创建过程,以及其中值得深究的一些东西,如设计模式等。
3.1 SpringApplication
SpringBoot程序的启动直接依赖于SpringApplication对象,那么让我们看看它的创建特点是什么?
在下面的伪代码可以看到,其构造函数中入参有一个Class对象,代表的是启动类的class对象,接着就是设置标记其的primarySources、mainApplicationClass等属性,这么做的原因其中之一是为了后面以启动类为入口 ,做SpringBoot自动装配等工作。
但本章节重点是setListeners(getSpringFactoriesInstances(ApppliplicationListener.class))这一行代码,因为会通过这引入ApplicationListener监听器对象,这有什么用呢?
其中一个用处就是引入配置文件的工作需要的监听器对象——ConfigFileApplicationListener。
public class SpringApplication {
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 配置文件的读取关键在这里,读取所有META/spring.factories文件下的
// ApplicationListener的候选类并生成对象
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
}
3.1.1 设置ApppliplicationListener
在SpringApplication构造函数中,可以看到有下面这么一行代码,就是设置监听器,那么此目的何在呢?
setInitializers((Collection)getSpringFactoriesInstances(ApplicationListener.class));
其实这些监听器对象是一种后面回调使用的,是一种监听器的设计模式。主要是为了比如说,有些资源的加载过程中需要被修饰、增强之类的,那么就提前准备好监听器对象,在程序认为合适的地方再调用这些监听器发挥其作用。
通过这一行代码会读取本项目下所有的JAR包中路径为META-INF的spring.factories中的key是ApplicationContextInitializer的全限定类名下的内容。
如下图所示,在引入的spring-boot的JAR包下,有一个ConfigFileApplicationListener类,它就是后面将会负责读取项目中常见的如application.properties、application.yaml等配置文件的数据并加载到Enviroment变量中。
本小节介绍只做“来龙”的工作,介绍了ConfigFileApplicationListener等监听器是如何引入的,而“去脉”的工作——读取配置文件的原理等将会在后面小节进行详细讲解。
3.2 run(String... args)
在run(String... args)方法里面做了许多事情,其中一件事情是本文要讲解的的,创建Environment变量和读取配置文件并将其数据归并。
如下伪代码所示,会创建一个实例是ConfigurableEnvironment的变量(后简称Environment变量),它是通过preparenEnvironment(...)方法完成的。
其实这个方法也是干了好几件事情,第一件事情就是创建一个具体的实例对象,而按照项目背景的不同所创建的实际变量对象也不一样。
接着就是跟上一个小节讲到的设置ApppliplicationListener监听器对象直接相关的,因为在这里将会应用到负责读取配置文件的监听器——ConfigFileApplicationListener。
不过,请注意看preparenEnvironment(...)的入参是SpringApplicationRunListeners而非从JAR包指定路径META-INF/spring.factories下的ApppliplicationListener对象。这里我们不需要过度探究,仅仅将SpringApplicationRunListeners视作是所有的ApppliplicationListener对象封装对象即可,通过它可以获取所有的监听器对象。
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
....
SpringApplicationRunListeners listeners = getRunListeners(args);
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 创建environment变量
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
....
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建一个ConfigurableEnvironment变量对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 主要是创建一个转换后的命令行配置源&为environment设置激活的配置文件
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 添加一个名叫configurationProperties的配置到environment配置源集合的第一位
ConfigurationPropertySources.attach(environment);
// 这便是ConfigFileApplicationListener等监听器派上用场的地方
listeners.environmentPrepared(environment);
......
return environment;
}
}