spring environment加载过程分析
activeProfiles
activeProfiles在AbstractEnvironment里定义,是一个LinkedHashSet,其内容来自于:
spring.profiles.active + spring.profiles.include
其顺序则依赖于插入顺序(LinkedHashSet自身的特点)。
注意
:activeProfiles是所有配置文件指定的spring.profiles.active 和 spring.profiles.include项的合集,并不像其他选项那样是先后覆盖关系!
所以,如果有特别依赖插入顺序的场合,要注意spring.profiles.include里的profile的顺序。
application.yml文件优先级说明
spring boot下application-xx.yml里的配置会覆盖application.yml,测一下即可知道。
环境加载过程分析
创建基本配置
一开始,会根据web应用的类型,创建基本的环境:
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
} else {
switch(this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
}
对于我们的springboot应用来说,就是StandardServletEnvironment。它自带的propertySource列表如下:
0 = {PropertySource$StubPropertySource@3839} "StubPropertySource {name='servletConfigInitParams'}"
1 = {PropertySource$StubPropertySource@3840} "StubPropertySource {name='servletContextInitParams'}"
2 = {PropertiesPropertySource@3841} "PropertiesPropertySource {name='systemProperties'}"
3 = {SystemEnvironmentPropertySource@3842} "SystemEnvironmentPropertySource {name='systemEnvironment'}"
其中,SystemProperties是java虚拟机启动参数,SystemEnvironment是操作系统环境变量(比如我们经常配的JAVA_HOME,就在其中)。
注意
:propertySource列表里,排最前面的优先级最高。
加载application-XX.yml配置
接着,会发送ApplicationEnvironmentPreparedEvent 事件,经过ApplicationListener处理后,环境的propertySource列表如下:
0 = {ConfigurationPropertySourcesPropertySource@5596} "ConfigurationPropertySourcesPropertySource {name='configurationProperties'}"
1 = {PropertySource$StubPropertySource@5597} "StubPropertySource {name='servletConfigInitParams'}"
2 = {PropertySource$StubPropertySource@5598} "StubPropertySource {name='servletContextInitParams'}"
3 = {PropertiesPropertySource@5599} "PropertiesPropertySource {name='systemProperties'}"
4 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource@5600} "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}"
5 = {RandomValuePropertySource@5601} "RandomValuePropertySource {name='random'}"
6 = {BootstrapApplicationListener$ExtendedDefaultPropertySource@5602} "ExtendedDefaultPropertySource {name='springCloudDefaultProperties'}"
7 = {MapPropertySource@5603} "MapPropertySource {name='springCloudClientHostInfo'}"
...
11 = {OriginTrackedMapPropertySource@5607} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application-local.yml]'}"
12 = {OriginTrackedMapPropertySource@5608} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}"
13 = {MapPropertySource@5609} "MapPropertySource {name='defaultProperties'}"
configurationProperties不用多看,它只是引用了全部的propertySource列表。
springCloudClientHostInfo持有的是本地主机的机器名和ip地址。
springCloudDefaultProperties里是用于spring cloud的配置,一般没配置bootstrap.yml就没有。
springCloudClientHostInfo、application-local.yml、application.yml都是由ConfigFileApplicationListener把它们加入到环境里的。
ConfigFileApplicationListener实现原理
来看看ConfigFileApplicationListener响应ApplicationEnvironmentPreparedEvent 事件的处理:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
Iterator var3 = postProcessors.iterator();
while(var3.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
可见,它依赖可定制的EnvironmentPostProcessor,每个processor要实现postProcessEnvironment()接口。我们的框架下有9个:
0 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor@3949}
1 = {SpringApplicationJsonEnvironmentPostProcessor@3950}
2 = {HostInfoEnvironmentPostProcessor@3951}
3 = {EnvirmentConfigListener@3952}
4 = {CloudFoundryVcapEnvironmentPostProcessor@3953}
5 = {ConfigFileApplicationListener@3917}
6 = {ApolloApplicationContextInitializer@3954}
7 = {TraceEnvironmentPostProcessor@3955}
8 = {DebugAgentEnvironmentPostProcessor@3956}
注意
:其中的ConfigFileApplicationListener就是它自己,因为ConfigFileApplicationListener自己也实现了EnvironmentPostProcessor接口,它就是在这个接口里加载了application-XX.yml,参见“ConfigFileApplicationListener默认动作分析”一节。
HostInfoEnvironmentPostProcessor生成了springCloudClientHostInfo配置:
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
HostInfo hostInfo = this.getFirstNonLoopbackHostInfo(environment);
LinkedHashMap<String, Object> map = new LinkedHashMap();
//找到机器名和ip地址,生成springCloudClientHostInfo配置
map.put("spring.cloud.client.hostname", hostInfo.getHostname());
map.put("spring.cloud.client.ip-address", hostInfo.getIpAddress());
MapPropertySource propertySource = new MapPropertySource("springCloudClientHostInfo", map);
environment.getPropertySources().addLast(propertySource);
}
这个类定义在spring-cloud-commons包里。
TraceEnvironmentPostProcessor生成了defaultProperties配置,是为sleuth准备的:
logging.pattern.level -> %5p [${spring.zipkin.service.name:${spring.application.name:}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]
spring-boot环境配置加载了两次的问题
从debug过程来看,foundation框架执行了两次SpringApplication.run,第二次run()后的environment如下:
0 = {MapPropertySource@15650} "MapPropertySource {name='server.ports'}"
1 = {EncryptableEnumerablePropertySourceWrapper@15651} "EncryptableEnumerablePropertySourceWrapper {name='ApolloPropertySources'}"
2 = {ConfigurationPropertySourcesPropertySource$$EnhancerBySpringCGLIB$$e8d7ddec@15652} "ConfigurationPropertySourcesPropertySource {name='configurationProperties'}"
3 = {EncryptablePropertySourceWrapper@15653} "EncryptablePropertySourceWrapper {name='servletConfigInitParams'}"
4 = {EncryptablePropertySourceWrapper@15654} "EncryptablePropertySourceWrapper {name='servletContextInitParams'}"
5 = {EncryptableMapPropertySourceWrapper@15655} "EncryptableMapPropertySourceWrapper {name='systemProperties'}"
6 = {EncryptableMapPropertySourceWrapper@15656} "EncryptableMapPropertySourceWrapper {name='systemEnvironment'}"
7 = {EncryptablePropertySourceWrapper@15657} "EncryptablePropertySourceWrapper {name='random'}"
8 = {EncryptableMapPropertySourceWrapper@15658} "EncryptableMapPropertySourceWrapper {name='springCloudClientHostInfo'}"
...
12 = {EncryptableMapPropertySourceWrapper@15662} "EncryptableMapPropertySourceWrapper {name='applicationConfig: [classpath:/application-local.yml]'}"
13 = {EncryptableMapPropertySourceWrapper@15663} "EncryptableMapPropertySourceWrapper {name='applicationConfig: [classpath:/application.yml]'}"
14 = {EncryptableMapPropertySourceWrapper@15664} "EncryptableMapPropertySourceWrapper {name='springCloudDefaultProperties'}"
15 = {EncryptablePropertySourceWrapper@15665} "EncryptablePropertySourceWrapper {name='cachedrandom'}"
16 = {EncryptableMapPropertySourceWrapper@15666} "EncryptableMapPropertySourceWrapper {name='defaultProperties'}"
这里,ApolloPropertySources在第二次的refreshContext阶段加了进来,且优先级最高。这也能解释,为何apollo的配置优先级最高,优先级接下来依次是:java命令行参数、环境变量、application-xx.yml、application.yml。
环境配置加载了两次,跟BootstrapApplicationListener有关,也就是说,在prepareEnvironment()阶段,就有一个临时的springApplication执行了一次完整的run过程,产生了一个父ApplicationContext,我们称之为bootstrap context。第二次springApplication.run才是主ApplicationContext的创建,这个主ApplicationContext也就是应用最终看到的ApplicationContext。
我们来看代码:
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// spring.cloud.bootstrap.enabled默认为true,也可以禁掉
if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
//几乎所有的ApplicationListener里都有这样的判断,确保两次加载的情况下,不会多次执行本listener的逻辑
if (!environment.getPropertySources().contains("bootstrap")) {
ConfigurableApplicationContext context = null;
......
if (context == null) {
//通过临时的springApplication来构建bootstrap context,这里的configName为bootstrap
context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
//这个是为了在整个springApplication失败时能关闭bootstrap ApplicationContext
event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
}
//在主SpringApplication里做一个BootstrapMarkerConfiguration标记,表明bootstrap过程已经做完,后续不用反复做了。同时,补充bootstrap过程引入的新的ApplicationContextInitializer到主SpringApplication中
this.apply(context, event.getSpringApplication(), environment);
}
}
}
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
//先新建一个StandardEnvironment的基本环境,一开始有:java命令行参数+环境变量等2个默认propertySource
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
Iterator var6 = bootstrapProperties.iterator();
//清空bootstrapEnvironment里的propertySource
while(var6.hasNext()) {
PropertySource<?> source = (PropertySource)var6.next();
bootstrapProperties.remove(source.getName());
}
String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
String configAdditionalLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap();
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
if (StringUtils.hasText(configAdditionalLocation)) {
bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
}
//往bootstrapEnvironment里添加bootstrap propertySource
bootstrapProperties.addFirst(new MapPropertySource("bootstrap", bootstrapMap));
//environment是主applicationContext里的环境配置
Iterator var9 = environment.getPropertySources().iterator();
//将主环境里除了servletConfigInitParams和servletContextInitParams之外的propertySource都加到bootstrap 环境里,此时,bootstrap 环境里的propertySource列表是:bootstrap、configurationProperties、systemProperties、systemEnvironment
while(var9.hasNext()) {
PropertySource<?> source = (PropertySource)var9.next();
if (!(source instanceof StubPropertySource)) {
bootstrapProperties.addLast(source);
}
}
//用bootstrap 环境来构建一个临时的SpringApplication
SpringApplicationBuilder builder = (new SpringApplicationBuilder(new Class[0])).profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF).environment(bootstrapEnvironment).registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
SpringApplication builderApplication = builder.application();
......
//用于构建bootstrap过程的导入配置,导入的是spring.factories文件中的org.springframework.cloud.bootstrap.BootstrapConfiguration指定的配置类,这些配置类将在bootstrap过程的refreshContext阶段里处理掉。
builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
//这里运行临时springApplication,构建出bootstrap Context
ConfigurableApplicationContext context = builder.run(new String[0]);
context.setId("bootstrap");
this.addAncestorInitializer(application, context);
bootstrapProperties.remove("bootstrap");
//将springCloudDefaultProperties 同时merge到application和bootstrap环境里,springCloudDefaultProperties里有可能会包含bootstrap.yml配置文件,用于spring cloud的启动配置
this.mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
总的来讲,bootstrap过程就是为了引入BootstrapConfiguration配置类,并完成bootstrap.yml文件的加载(spring cloud下如果有的话),其所创建的bootstrap Context与主ApplicationContext其实没啥关系。
bootstrap阶段的作用:
- 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
- 一些固定的不能被覆盖的属性(bootstrap.yml里的配置不能被application.yml覆盖)
- 一些加密/解密的场景