Spring Boot 外部化配置 - 下篇

Spring Boot 外部化配置 - 下篇

文章说明

本系列会完整的介绍 Spring Boot 中外部化配置相关应用以及部分源码分析

  • 上篇 - 什么是外部化配置,外部化配置有哪些应用,顺序覆盖性
  • 中篇 - @Value 注入、Environment 抽象、@ConfigurationProperties、@ConditionalOnProperty 应用
  • 下篇 - 如何扩展外部化配置,在扩展的过程中,穿插覆盖顺序的相关代码演示

项目环境

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:

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. @SpringBootTest#properties annotation attribute on your tests.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. 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 抽象?

EnvironmentPropertySources 是一对一的关系, PropertySourcesPropertySource 是一对多的关系。

通过 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深度实践之核心技术篇》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值