上一篇《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的子类,不需要转换