Spring Boot 外部化配置 - 下篇
文章说明
本系列会完整的介绍 Spring Boot 中外部化配置相关应用以及部分源码分析
- 上篇 - 什么是外部化配置,外部化配置有哪些应用,顺序覆盖性
- 中篇 - @Value 注入、Environment 抽象、@ConfigurationProperties、@ConditionalOnProperty 应用
- 下篇 - 如何扩展外部化配置,在扩展的过程中,穿插覆盖顺序的相关代码演示
项目环境
- jdk 1.8
- Spring Boot 2.0.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/deep-in-spring-boot
- 本章模块:externalized-configuration
1.扩展外部化配置
- 基于 SpringApplicationRunListener#environmentPrepared 扩展
- 基于 ApplicationEnvironmentPreparedEvent 扩展
- 基于 EnvironmentPostProcessor 扩展
- 基于 ApplicationContextInitializer 扩展
- 基于 SpringApplicationRunListener#contextPrepared 扩展
- 基于 SpringApplicationRunListener#contextLoaded扩展
- 基于 ApplicationPreparedEvent 扩展
2.如何扩展,在哪里扩展?
2.1 理解 Spring Boot Environment 生命周期
了解生命周期,可以知道 Environment 什么时候被创建,什么时候被使用,在创建和使用之间,正是我们扩展区间。
一切从 SpringApplication#run()
方法开始
- Environment 对象准备阶段
SpringApplication#prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment 来创建 Environment 对象。
题外话 web 场景中创建的 StandardServletEnvironment 对象
- org.springframework.web.context.support.StandardServletEnvironment
对应外部化配置覆盖顺序的 6 和 7 。
6.
ServletConfig
init parameters.7.
ServletContext
init parameters.相关源码
StandardServletEnvironment#customizePropertySources
@Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); ...
- Environment 对象创建完成之后,后面有个 environmentPrepared 方法如下:
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
循环遍历获取 SpringApplicationRunListener 的实现,并通过 environmentPrepared 发送一个 ApplicationEnvironmentPreparedEvent 事件
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
-
当 Environment 对象在 Spring Boot 中创建好之后,在
prepareContext
中通过context.setEnvironment(environment);
设置当前上下文的 Environment,然后继续执行refreshContext(context);
,此方法调用 Spring Freamwork 中 AbstractApplicationContext.refresh() 方法启动应用上下文 -
最终在 AbstractApplicationContext#prepareBeanFactory 方法 668 行,getEnvironment() 获取到 Environment 对象,并注册到容器中
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
- 在 refresh() 方法的后续方法中,都有可能调用 Environment 对象,所以如果要进行扩展必须是在 prepareBeanFactory 方法之前
结论:
Spring Framework 中尽量在 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
方法之前初始化。
Spring Boot 中尽量在 org.springframework.boot.SpringApplication#refreshContext
方法之前初始化。
2.2 理解 PropertySource
顺序
Spring Boot 官网 24. Externalized Configuration
Spring Boot uses a very particular
PropertySource
order that is designed to allow sensible overriding of values. Properties are considered in the following order:
- Devtools global settings properties on your home directory (
~/.spring-boot-devtools.properties
when devtools is active).@TestPropertySource
annotations on your tests.@SpringBootTest#properties
annotation attribute on your tests.- Command line arguments.
- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property).ServletConfig
init parameters.ServletContext
init parameters.- JNDI attributes from
java:comp/env
.- Java System properties (
System.getProperties()
).- OS environment variables.
- A
RandomValuePropertySource
that has properties only inrandom.*
.- Profile-specific application properties outside of your packaged jar (
application-{profile}.properties
and YAML variants).- Profile-specific application properties packaged inside your jar (
application-{profile}.properties
and YAML variants).- Application properties outside of your packaged jar (
application.properties
and YAML variants).- Application properties packaged inside your jar (
application.properties
and YAML variants).@PropertySource
annotations on your@Configuration
classes.- Default properties (specified by setting
SpringApplication.setDefaultProperties
).
这里提出了 17 种配置源方式,前面优先级会高于后面,即相同属性覆盖后面的值
我们先测试顺序 2.@TestPropertySource
在 test 目录下新建一个测试用例
@RunWith(SpringRunner.class)
@TestPropertySource(properties = "user.id = 9")
public class PropertySourceOrderTest {
@Value("${user.id}")
private Long userId;
@Test
public void testUserId() {
Assert.assertEquals(userId, new Long(10));
System.err.println("user.id:" + userId);
}
}
执行结果:
java.lang.AssertionError:
Expected :9
Actual :10
我们取到的结果是 9 ,因为 @TestPropertySource 的优先级高于默认的 application.properties 。
我们再加入排名第 3 的 @SpringBootTest
@RunWith(SpringRunner.class)
@TestPropertySource(
properties = "user.id = 9"
)
@SpringBootTest(properties = "user.id = 10",
classes = {PropertySourceOrderTest.class},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class PropertySourceOrderTest {
@Value("${user.id}")
private Long userId;
@Autowired
private ConfigurableEnvironment environment;
@Test
public void testUserId() {
Assert.assertEquals(userId, new Long(10));
System.err.println("user.id:" + userId);
}
}
执行结果:
java.lang.AssertionError:
Expected :9
Actual :10
可以看到结果还是 9,表示 @SpringBootTest 中的属性并没有覆盖掉 @TestPropertySource 中的属性,符合预期。
接着我们注释掉 @TestPropertySource#properties 属性,同时增加一个 locations 属性,@TestPropertySource#locations 支持读取文件,我们将路径配置为 classpath:META-INF/default.properties
@TestPropertySource(
// properties = "user.id = 9",
locations = "classpath:META-INF/default.properties"
)
@SpringBootTest(properties = "user.id = 10",
classes = {PropertySourceOrderTest.class},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class PropertySourceOrderTest {
...
default.properties 文件如下
user.id = 7
user.name = 小仙
执行结果:
user.id:10
7 并没有覆盖掉 10,所以由结果可知,@SpringBootTest#properties 的优先级大于 @TestPropertySource#locations
通过上面的例子,我们可以知道三者的顺序如下
1.@TestPropertySource#properties
2.@SpringBootTest#properties
3.@TestPropertySource#locations
官网中漏掉了 @TestPropertySource#locations 这种情况,所以准确来说应该是 18 种;通过上面的例子我们大致知道了,外部化配置的属性源是按照这 18 种顺序来进行覆盖的,其他的方式在后续的文章中会进行演示。
2.3 什么是 Environment
抽象?
Environment
与 PropertySources
是一对一的关系, PropertySources
与 PropertySource
是一对多的关系。
通过 ConfigurableEnvironment#getPropertySources
可以获取到 PropertySources 集合对象,然后遍历集合,可以获取到这个 Environment 中所有的 PropertySource
对象。
我们修改之前的测试类,通过 @Autowired 的方式注入 ConfigurableEnvironment 对象,再通过迭代的方式看看里面到底关联了哪些 PropertySources 属性源对象。
@RunWith(SpringRunner.class)
@TestPropertySource(
// properties = "user.id = 9",
locations = "classpath:META-INF/default.properties"
)
@SpringBootTest(properties = "user.id = 10",
classes = {PropertySourceOrderTest.class},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class PropertySourceOrderTest {
@Value("${user.id}")
private Long userId;
@Autowired
private ConfigurableEnvironment environment;
@Test
public void testUserId() {
Assert.assertEquals(userId, new Long(10));
System.err.println("user.id:" + userId);
}
@Test
public void testPropertySources() {
MutablePropertySources propertySources = this.environment.getPropertySources();
int i = 0;
for (org.springframework.core.env.PropertySource<?> propertySource : propertySources) {
i++;
System.out.printf("顺序[%d]-名称[%s]:[%s]\n", i, propertySource.getName(), propertySource.toString());
}
}
}
执行结果:
顺序[1]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[2]-名称[Inlined Test Properties]:[MapPropertySource {name='Inlined Test Properties'}]
顺序[3]-名称[class path resource [META-INF/default.properties]]:[ResourcePropertySource {name='class path resource [META-INF/default.properties]'}]
顺序[4]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[5]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[6]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[7]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[3]-名称[class path resource [META-INF/default.properties]] 就是我们配置的
@TestPropertySource(locations = "classpath:META-INF/default.properties")
顺序[7]-名称[applicationConfig: [classpath:/application.properties]] 是 Spring Boot 默认读取的配置文件。
我们继续测试 16.@PropertySource
在 test 目录下新建 resouces/META-INF/test.properties
user.id = 11
user.name = 小仙
修改示例 增加 @PropertySource 相关的配置源
@RunWith(SpringRunner.class)
@TestPropertySource(
// properties = "user.id = 9",
locations = "classpath:META-INF/default.properties")
@SpringBootTest(properties = "user.id = 10",
classes = {PropertySourceOrderTest.class, PropertySourceOrderTest.MyConfig.class},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class PropertySourceOrderTest {
@Configuration
@PropertySource(name = "test-propertySource", value = "classpath:META-INF/test.properties")
public static class MyConfig {
}
@Autowired
private ConfigurableApplicationContext applicationContext;
@Autowired
private ConfigurableEnvironment environment;
@Value("${user.id}")
private Long userId;
@Test
public void testEnvironment() {
Assert.assertSame(environment, applicationContext.getEnvironment());
}
@Test
public void testUserId() {
Assert.assertEquals(userId, new Long(10));
System.err.println("user.id:" + userId);
}
@Test
public void testPropertySources() {
MutablePropertySources propertySources = this.environment.getPropertySources();
int i = 0;
for (org.springframework.core.env.PropertySource<?> propertySource : propertySources) {
i++;
System.out.printf("顺序[%d]-名称[%s]:[%s]\n", i, propertySource.getName(), propertySource.toString());
}
}
@Test
public void testMyConfigBean() {
MyConfig bean = applicationContext.getBean(MyConfig.class);
System.out.println(bean);
}
}
执行测试用例 testPropertySources
顺序[1]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[2]-名称[Inlined Test Properties]:[MapPropertySource {name='Inlined Test Properties'}]
顺序[3]-名称[class path resource [META-INF/default.properties]]:[ResourcePropertySource {name='class path resource [META-INF/default.properties]'}]
顺序[4]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[5]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[6]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[7]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[8]-名称[test-propertySource]:[ResourcePropertySource {name='test-propertySource'}]
结论:
并不是每次都会加载所有的 18 种配置源信息,根据对应的场景进行加载。
2.4 如何理解 PropertySource
?
带有名称的属性源,properties、map、yaml 文件。
3.扩展
3.1 基于 SpringApplicationRunListener#environmentPrepared 扩展
需要使用 Spring 的 SPI 机制进行实现
实现 SpringApplicationRunListener,Ordered 接口
- 这里 Ordered 我们可以去默认实现中的 Order - 1,表示优先级比原来的实现高 1 位
/**
* 扩展{@link PropertySources}
*/
public class ExtendPropertySourcesRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
public ExtendPropertySourcesRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
@Override
public void starting() {
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 5);
MapPropertySource propertySource = new MapPropertySource("from-environmentPrepared", map);
propertySources.addFirst(propertySource);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
}
@Override
public void started(ConfigurableApplicationContext context) {
}
@Override
public void running(ConfigurableApplicationContext context) {
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
}
@Override
public int getOrder() {
return new EventPublishingRunListener(application, args).getOrder() - 1;
}
}
resources/META-INF 目录下新建 spring.factories 文件
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.huajie.deepinspringboot.externlized.configuration.event.ExtendPropertySourcesRunListener
新建引导类
这里我们加上其他三种属性源用来进行顺序覆盖的演示
- 16.@PropertySource
- 17.Default properties
- 4.Command line arguments
/**
* 扩展{@link PropertySources} 引导类
*/
@EnableAutoConfiguration
@PropertySource(name="from classpath:META-INF/default.properties",value="classpath:META-INF/default.properties")//16.@PropertySource
public class ExtendPropertySourceBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new SpringApplicationBuilder(ExtendPropertySourceBootstrap.class)
.web(WebApplicationType.NONE)
.properties("user.id=99")// 17.Default properties
.run(of("--user.id=100"));//4.Command line arguments
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Long userId = environment.getProperty("user.id", Long.class);
System.out.println("用户id:" + userId);
MutablePropertySources propertySources = environment.getPropertySources();
int i = 0;
for (org.springframework.core.env.PropertySource<?> propertySource : propertySources) {
i++;
System.out.printf("顺序[%d]-名称[%s]:[%s]\n", i, propertySource.getName(), propertySource.toString());
}
applicationContext.close();
}
private static <T> T[] of(T... args) {
return args;
}
}
执行结果:
用户id:5
顺序[1]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[2]-名称[from-environmentPrepared]:[MapPropertySource {name='from-environmentPrepared'}]
顺序[3]-名称[commandLineArgs]:[SimpleCommandLinePropertySource {name='commandLineArgs'}]
顺序[4]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[5]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[6]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[7]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[8]-名称[from classpath:META-INF/default.properties]:[ResourcePropertySource {name='from classpath:META-INF/default.properties'}]
顺序[9]-名称[defaultProperties]:[MapPropertySource {name='defaultProperties'}]
顺序[2]-名称[from-environmentPrepared] 是我们 SpringApplicationRunListener#environmentPrepared
扩展的属性源。
3.2 基于 ApplicationEnvironmentPreparedEvent 扩展
因为 SpringApplicationRunListener 通过 environmentPrepared 发送一个 ApplicationEnvironmentPreparedEvent 事件,所以我们新建 ExtendPropertySourcesEventListener 来监听 ApplicationEnvironmentPreparedEvent 事件
/**
* 扩展 {@link PropertySources} {@link ApplicationListener}实现,监听{@link ApplicationEnvironmentPreparedEvent}
* @Author xwf
* @Date 2020\5\22 0022 20:48
*/
public class ExtendPropertySourcesEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 6);
MapPropertySource propertySource = new MapPropertySource("from-ApplicationEnvironmentPreparedEvent", map);
propertySources.addFirst(propertySource);
}
}
resources/META-INF/spring.factories 文件新增对应的配置
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.huajie.deepinspringboot.externlized.configuration.event.ExtendPropertySourcesRunListener
org.springframework.context.ApplicationListener=\
com.huajie.deepinspringboot.externlized.configuration.listener.ExtendPropertySourcesEventListener
执行结果:
用户id:6
顺序[1]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[2]-名称[from-ApplicationEnvironmentPreparedEvent]:[MapPropertySource {name='from-ApplicationEnvironmentPreparedEvent'}]
顺序[3]-名称[from-environmentPrepared]:[MapPropertySource {name='from-environmentPrepared'}]
顺序[4]-名称[commandLineArgs]:[SimpleCommandLinePropertySource {name='commandLineArgs'}]
顺序[5]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[6]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[7]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[8]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[9]-名称[from classpath:META-INF/default.properties]:[ResourcePropertySource {name='from classpath:META-INF/default.properties'}]
顺序[10]-名称[defaultProperties]:[MapPropertySource {name='defaultProperties'}]
顺序[2]-名称[from-ApplicationEnvironmentPreparedEvent] 是我们新增的监听,由于顺序高于之前 SpringApplicationRunListener#environmentPrepared 的配置,所以覆盖了 user.id
属性。
3.3 基于 EnvironmentPostProcessor 扩展
在 ApplicationListener 的内建实现 ConfigFileApplicationListener 中, 当 ConfigFileApplicationListener 监听到 ApplicationEnvironmentPreparedEvent 事件时触发调用。
ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent,遍历所有的 EnvironmentPostProcessor 并执行 postProcessEnvironment 方法
- postProcessors.add(this); 表示 ConfigFileApplicationListener 本身也是一个 EnvironmentPostProcessor 的实现
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
自定义 ExtendPropertySourcesEnvironmentPostProcessor 实现 EnvironmentPostProcessor,Ordered 接口
- 这里 Ordered 我们可以去默认实现中的 Order - 1,表示优先级比原来的实现高 1 位
/**
* 扩展 {@link PropertySources} {@link EnvironmentPostProcessor}实现
*
* @Author xwf
* @Date 2020\5\22 0022 21:06
*/
public class ExtendPropertySourcesEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 16);
MapPropertySource propertySource = new MapPropertySource("from-EnvironmentPostProcessor", map);
propertySources.addFirst(propertySource);
}
@Override
public int getOrder() {
return ConfigFileApplicationListener.DEFAULT_ORDER - 1;
}
}
resources/META-INF/spring.factories 文件新增对应的配置
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.huajie.deepinspringboot.externlized.configuration.event.ExtendPropertySourcesRunListener
org.springframework.context.ApplicationListener=\
com.huajie.deepinspringboot.externlized.configuration.listener.ExtendPropertySourcesEventListener
org.springframework.boot.env.EnvironmentPostProcessor=\
com.huajie.deepinspringboot.externlized.configuration.processor.ExtendPropertySourcesEnvironmentPostProcessor
执行结果
用户id:6
顺序[1]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[2]-名称[from-ApplicationEnvironmentPreparedEvent]:[MapPropertySource {name='from-ApplicationEnvironmentPreparedEvent'}]
顺序[3]-名称[from-EnvironmentPostProcessor]:[MapPropertySource {name='from-EnvironmentPostProcessor'}]
顺序[4]-名称[from-environmentPrepared]:[MapPropertySource {name='from-environmentPrepared'}]
顺序[5]-名称[commandLineArgs]:[SimpleCommandLinePropertySource {name='commandLineArgs'}]
顺序[6]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[7]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[8]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[9]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[10]-名称[from classpath:META-INF/default.properties]:[ResourcePropertySource {name='from classpath:META-INF/default.properties'}]
顺序[11]-名称[defaultProperties]:[MapPropertySource {name='defaultProperties'}]
可以看到 顺序[3]-名称[from-EnvironmentPostProcessor] 在 顺序[2]-名称[from-ApplicationEnvironmentPreparedEvent] 后面,结果符合预期;因为 EnvironmentPostProcessor 在 ApplicationEnvironmentPreparedEvent 事件监听到之后才会执行。
3.4 基于 ApplicationContextInitializer 扩展
在 SpringApplication#run 启动方法中
org.springframework.boot.SpringApplication#run(java.lang.String…)
- org.springframework.boot.SpringApplication#prepareContext
- org.springframework.boot.SpringApplication#applyInitializers
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
此方法在 refreshContext(context);
之前进行调用,也就是 Spring Fremawrok 应用上下文启动之前,在此时也可以对 Environment 进行操作。
同样我们实现 ApplicationContextInitializer
/**
* 扩展 {@link PropertySources} {@link ApplicationContextInitializer}实现
*
* @Author xwf
* @Date 2020\5\22 0022 21:20
*/
public class ExtendPropertySourcesInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 25);
MapPropertySource propertySource = new MapPropertySource("from-ApplicationContextInitializer", map);
propertySources.addFirst(propertySource);
}
}
resources/META-INF/spring.factories 文件新增对应的配置
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.huajie.deepinspringboot.externlized.configuration.event.ExtendPropertySourcesRunListener
org.springframework.context.ApplicationListener=\
com.huajie.deepinspringboot.externlized.configuration.listener.ExtendPropertySourcesEventListener
org.springframework.boot.env.EnvironmentPostProcessor=\
com.huajie.deepinspringboot.externlized.configuration.processor.ExtendPropertySourcesEnvironmentPostProcessor
org.springframework.context.ApplicationContextInitializer=\
com.huajie.deepinspringboot.externlized.configuration.initializer.ExtendPropertySourcesInitializer
执行结果:
用户id:25
顺序[1]-名称[from-ApplicationContextInitializer]:[MapPropertySource {name='from-ApplicationContextInitializer'}]
顺序[2]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[3]-名称[from-ApplicationEnvironmentPreparedEvent]:[MapPropertySource {name='from-ApplicationEnvironmentPreparedEvent'}]
顺序[4]-名称[from-EnvironmentPostProcessor]:[MapPropertySource {name='from-EnvironmentPostProcessor'}]
顺序[5]-名称[from-environmentPrepared]:[MapPropertySource {name='from-environmentPrepared'}]
顺序[6]-名称[commandLineArgs]:[SimpleCommandLinePropertySource {name='commandLineArgs'}]
顺序[7]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[8]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[9]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[10]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[11]-名称[from classpath:META-INF/default.properties]:[ResourcePropertySource {name='from classpath:META-INF/default.properties'}]
顺序[12]-名称[defaultProperties]:[MapPropertySource {name='defaultProperties'}]
顺序[1]-名称[from-ApplicationContextInitializer] 就是我们新加入的 ExtendPropertySourcesInitializer
3.5 基于 SpringApplicationRunListener#contextPrepared &contextLoaded 扩展
这两个方法都在 SpringApplicationRunListener 中,所以我们可以写在一起
修改 3.1 中的例子 ExtendPropertySourcesRunListener 的这两个方法即可
/**
* 扩展{@link PropertySources}
*/
public class ExtendPropertySourcesRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
public ExtendPropertySourcesRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
@Override
public void starting() {
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 5);
MapPropertySource propertySource = new MapPropertySource("from-environmentPrepared", map);
propertySources.addFirst(propertySource);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 35);
MapPropertySource propertySource = new MapPropertySource("from-SpringApplicationRunListener#contextPrepared", map);
propertySources.addFirst(propertySource);
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, Object> map = new HashMap<>();
map.put("user.id", 45);
MapPropertySource propertySource = new MapPropertySource("from-SpringApplicationRunListener#contextLoaded", map);
propertySources.addFirst(propertySource);
}
@Override
public void started(ConfigurableApplicationContext context) {
}
@Override
public void running(ConfigurableApplicationContext context) {
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
}
@Override
public int getOrder() {
return new EventPublishingRunListener(application, args).getOrder() - 1;
}
}
执行结果:
用户id:45
顺序[1]-名称[from-SpringApplicationRunListener#contextLoaded]:[MapPropertySource {name='from-SpringApplicationRunListener#contextLoaded'}]
顺序[2]-名称[from-SpringApplicationRunListener#contextPrepared]:[MapPropertySource {name='from-SpringApplicationRunListener#contextPrepared'}]
顺序[3]-名称[from-ApplicationContextInitializer]:[MapPropertySource {name='from-ApplicationContextInitializer'}]
顺序[4]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[5]-名称[from-ApplicationEnvironmentPreparedEvent]:[MapPropertySource {name='from-ApplicationEnvironmentPreparedEvent'}]
顺序[6]-名称[from-EnvironmentPostProcessor]:[MapPropertySource {name='from-EnvironmentPostProcessor'}]
顺序[7]-名称[from-environmentPrepared]:[MapPropertySource {name='from-environmentPrepared'}]
顺序[8]-名称[commandLineArgs]:[SimpleCommandLinePropertySource {name='commandLineArgs'}]
顺序[9]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[10]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[11]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[12]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[13]-名称[from classpath:META-INF/default.properties]:[ResourcePropertySource {name='from classpath:META-INF/default.properties'}]
顺序[14]-名称[defaultProperties]:[MapPropertySource {name='defaultProperties'}]
顺序[1] 和 顺序[2] 就是我们加入的两个方法实现。
3.6 基于 ApplicationPreparedEvent 扩展
org.springframework.boot.SpringApplication#run(java.lang.String…)
- org.springframework.boot.SpringApplication#prepareContext
- org.springframework.boot.context.event.EventPublishingRunListener#contextLoaded
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
在 EventPublishingRunListener 这个内建的实现中,contextLoaded 方法中最后发送了 ApplicationPreparedEvent 事件,如果我们来监听这个事件,通过参数 application 获取到 Environment 也可以进行相应的操作,和 3.5 中的实现方式没有本质上的区别,我们这里就不演示了。
4.参考
- 慕课网-小马哥《Spring Boot2.0深度实践之核心技术篇》