spring boot启动源码分析(三)之Environment准备

上一篇《spring-boot启动源码分析(二)之SpringApplicationRunListener

环境介绍:

spring boot版本:2.7.18

主要starter:spring-boot-starter-web

本篇开始讲启动过程中Environment环境准备,Environment是管理所有配置的实例对象,像application.yml、系统属性、环境变量等配置都可以通过Environment.getProperty(key)获取

入口如下:

(一)创建ConfigurableEnvironment

首先会从spring.factories中获取ApplicationContextFactory.class接口实现类,根据webApplicationType会实例化对应的ConfigurableEnvironment(这里是SERVLET,所以是AnnotationConfigServletWebServerApplicationContext.Factory创建的ApplicationServletEnvironment)

ApplicationServletEnvironment的类结构如下:

ApplicationServletEnvironment调用构造方法实例化时,父类AbstractEnvironment构造方法中会有初始化操作:

propertySources是Environment的核心属性,包含了各个配置源,这里赋值了一个MutablePropertySources,它实际是一个迭代器:

propertyResolver:是属性解析器,如占位符的解析等

AbstractEnvironment构造器采用类似模板方法将customizePropertySources(propertySources)交给子类实现,子类可以在此方法中加载数据源

可以看到子类StandardServletEnvironment,添加了StubPropertySource,桩配置源,类似打桩,这两个桩配置源分别是servletConfigInitParams和servletContextInitParams。这两个在webserver启动后会被替换成实际的配置源,这里只是个站位,没有实际的作用。

之后再次调用超类的customizePropertySources:

在StandardEnvironment中会添加两个重要的配置源,systemProperties和systemEnvironment。

systemProperties对应System.getProperties(),即所有的系统属性:

systemEnvironment对应的是System.getenv(),即系统的所有环境变量

(二)对Environment进行其他配置

(1)首先添加conversionService:environment中的propertyResolver(配置解析器)配置应用转换服务,例如NumberToNumberConverterFactory,StringToCharacterConverter等。应该是为propertyResource中的数据解析时进行数据转换用的

(2)configurePropertySources:

如果SpringApplication中Map<String, Object> defaultProperties不为空,则会创建一个名为"defaultProperties",实例类型为DefaultPropertiesPropertySource的配置源。

java进程传入的命令行参数加载进配置源中,配置源名称为commandLineArgs,实例类型为CommandLinePropertySource。此方法为模板方法,可被子类覆盖实现自己的资源加载方式configureProfiles

(3)此为空实现,看注释是说配置此应用程序环境中哪些配置文件处于活动状态(或默认情况下处于活动状态)。在配置文件处理过程中,可以通过{@code-spring.profiles.active}属性激活其他配置文件。

(三)绑定ConfigurationPropertySources

ConfigurationPropertySources.attach(environment);

增加一个名为"configurationProperties"的ConfigurationPropertySourcesPropertySource和environment绑定,实际上是把environment中所有的propertySource(也包括configurationProperties)包装进一个SpringConfigurationPropertySources实例中,而SpringConfigurationPropertySources是ConfigurationPropertySourcesPropertySource中的一个属性值,这也意味着configurationProperties可以访问所有的propertySource,这样它可以作为配置的统一访问入口。可以看如下configurationProperties对应实例属性

所以ConfigurationPropertySourcesPropertySource.getProperty(String name),实际上就是遍历每个PropertySource,获取它们的配置,但它对每个PropertySource进行了适配,以便以统一的接口进行获取配置。

getSource获取的是SpringConfigurationPropertySources,是一个迭代器,iterator()中会对配置源进行适配,适配成ConfigurationPropertySource

(四)发布environmentPrepared事件

事件的发布流程都差不多,这里不再赘述。主要差别是哪些监听器会监听了此事件,并做了什么逻辑处理。这里我们重点说一下这个:

主要有6个监听器:

代理监听器可以跳过,没有实际的逻辑处理。我们关注spring boot自定义的监听器。

(1)EnvironmentPostProcessorApplicationListener

这里主要是从spring.fatories获取EnvironmentPostProcessor接口实现类并实例化:

然后调用对应postProcessEnvironment,这些EnvironmentPostProcessor主要是添加不同的数据源配置,如ConfigDataEnvironmentPostProcessor解析我们常用的application.yml作为配置源,详情可看《Spring boot源码之EnvironmentPostProcessor

(2)AnsiOutputApplicationListener

这里主要是根据spring.output.ansi.enabled和spring.output.ansi.console-available,设置AnsiOutput对应的属性。为输出到控制台的日志信息添加 ANSI 转义码,以实现彩色输出。这个用的比较少,我看只在打印banner和logback日志的颜色转换器中用到了

(3)LoggingApplicationListener

在上一篇发布starting事件中,日志系统进行了初步初始化,在这里则会完成全部的初始化。

a、getLoggingSystemProperties(environment).apply():会将配置文件中配置的日志相关配置设置到系统属性中,以便后续日志系统初始化时可以从系统属性读取。

如:配置文件中如果设置了logging.pattern.console,那么就会设置到系统属性变量CONSOLE_LOG_PATTERN中。这里还设置了PID以及下面的日志策略

b、initializeEarlyLoggingLevel(environment):初始化早期日志级别,实际只是根据Environment中是否配置debug和trace,设置springBootLogging对应级别

c、initializeSystem(environment, this.loggingSystem, this.logFile):这里是主要的初始化逻辑,如果之前日志已经初始化过,这里会使用spring boot会重新初始化,对日志进行增强。比如在logback中,会使用SpringBootJoranConfigurator,对配置文件进行加载解析,同时新定义了一些解析规则

所以在spring boot中可以使用springProperty,添加属性,值可以是Environment中配置的属性值

如上,是配置了一个pid变量,值是Environment中pid属性对应的属性值

d、initializeFinalLoggingLevels(environment, this.loggingSystem):初始化日志的最终的日志级别:

如果b步骤设置了springBootLogging,那么spring boot会初始化一些logger,设置其日志级别,如过是debug级别,则下图圈出来的都会设置是debug级别

另外则是对配置属性中有logging.level为前缀的设置其对应的日志级别:

如上,则会设置com.example为true。

复制代码
e、registerShutdownHookIfNecessary(environment, this.loggingSystem)

往SpringApplication注册日志系统的shutdownHandler,会在shutdown发生时,停止日志上下文。

(4)BackgroundPreinitializer

启动一个后台线程去出发早期的初始化,并且有个countDownLatch,只有初始化完成,当ApplicationReadyEvent监听触发时,得等线程执行完才会继续执行,否则等待

(5)FileEncodingApplicationListener

当系统设置了spring.mandatory-file-encoding编码格式时,会判断是否和System.getProperty("file.encoding")相同,如果不相等则抛出异常阻止系统的启动,默认没设置不影响

(五)其他

上面基本已经把环境变量准备完毕,剩下的是一些细微处理:

(1)将defaultProperties配置源移到最后,即查询资源时最后才查它

(2)SpringApplication相关环境变量绑定

即将spring.main开头的配置,绑定到对应SpringApplication的属性中。如spring.main.allow-circular-references=true,那么就会设置SpringApplication的allowCircularReferences值为true.

(3)Environment转换ConfigurableEnvironment为应用类型的环境,这里是ApplicationServletEnvironment,是ConfigurableEnvironment的子类,不需要转换

相关推荐
史迪奇_xxx1 分钟前
9、C/C++ 内存管理详解:从基础到面试题
java·c语言·c++
hweiyu004 分钟前
Spring Boot 项目集成 Gradle:构建、测试、打包全流程教程
java·spring boot·后端·gradle
一勺菠萝丶5 分钟前
Spring Boot 项目启动报错:`Could not resolve type id ... no such class found` 终极解决方案!
java·spring boot·后端
聪明的笨猪猪11 分钟前
Java Redis “底层结构” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
Chris.Yuan77017 分钟前
泛型学习——看透通配符?与PECS 法则
java·学习
这周也會开心1 小时前
云服务器安装JDK、Tomcat、MySQL
java·服务器·tomcat
hrrrrb2 小时前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶2 小时前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
周杰伦_Jay3 小时前
【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
java·jvm
摇滚侠4 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端