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 。

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 方法中,getEnvironment() 获取到 Environment 对象,并注册到容器中

// Register default environment beans.
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
		beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}

在 refresh() 方法的后续方法中,都有可能调用 Environment 对象,所以如果要进行扩展必须是在 AbstractApplicationContext#prepareBeanFactory 方法之前。

结论:

Spring Framework 中尽量在 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory 方法之前初始化。

Spring Boot 中尽量在 org.springframework.boot.SpringApplication#refreshContext 方法之前初始化。

2.2 理解 PropertySource 顺序

Spring Boot 官网 Externalized Configuration

这里提出了 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;

    @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

@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;

    @Test
    public void testUserId() {
      	Assert.assertEquals(userId, new Long(10));
      	System.err.println("user.id: " + userId);
    }
}

default.properties 文件如下

user.id = 1
user.name = 张三-properties

执行结果:

user.id: 10

1 并没有覆盖掉 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 属性源对象。

我们修改之前的测试类,通过 @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 = 张三-test

修改示例 增加 @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 {

    @Value("${user.id}")
    private Long userId;

    @Autowired
    private ConfigurableEnvironment environment;

    @Autowired
    private ConfigurableApplicationContext applicationContext;

    @Configuration
    @PropertySource(name = "test-propertySource", value = "classpath:/META-INF/test.properties")
    public static class MyConfig {

    }

    @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

带有名称的属性源,propertiesmapyaml文件。

3.扩展

3.1 基于 SpringApplicationRunListener#environmentPrepared 扩展

需要使用 Spring 的 SPI 机制进行实现

实现 SpringApplicationRunListener,Ordered 接口

这里 Ordered 我们可以去默认实现中的 Order - 1,表示优先级比原来的实现高 1 位

public class ExtendPropertySourceRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	public ExtendPropertySourceRunListener(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.zq.externalized.configuration.run.ExtendPropertySourceRunListener

新建引导类,这里我们加上其他三种属性源用来进行顺序覆盖的演示

  • 16.@PropertySource
  • 17.Default properties
  • 4.Command line arguments
@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)
				.profiles("user.id=99")	// 17. Default properties
				.run(of("--user.id=100"));	// 4. Command line arguments.

		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		System.out.println("用户id: " + environment.getProperty("user.id", Long.class));

		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());
		}

		// 关闭 Spring 应用上下文
		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'}]

顺序[2]-名称[from-environmentPrepared] 是我们 SpringApplicationRunListener#environmentPrepared 扩展的属性源。

3.2 基于 ApplicationEnvironmentPreparedEvent 扩展

因为 SpringApplicationRunListener 通过 environmentPrepared 发送一个 ApplicationEnvironmentPreparedEvent 事件,所以我们新建 ExtendPropertySourceApplicationListener 来监听 ApplicationEnvironmentPreparedEvent 事件。

public class ExtendPropertySourceApplicationListener implements SmartApplicationListener {

	@Override
	public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) ||
				ApplicationPreparedEvent.class.isAssignableFrom(eventType);
	}

	@Override
	public boolean supportsSourceType(Class<?> sourceType) {
		return true;
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			ApplicationEnvironmentPreparedEvent preparedEvent = (ApplicationEnvironmentPreparedEvent) event;
			ConfigurableEnvironment environment = preparedEvent.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);
		}
		if (event instanceof ApplicationPreparedEvent) {

		}
	}

	@Override
	public int getOrder() {
		return 0;
	}
}

resources/META-INF/spring.factories 文件新增对应的配置

# Application Listeners
org.springframework.context.ApplicationListener=\
com.zq.externalized.configuration.listener.ExtendPropertySourceApplicationListener

执行结果:

用户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'}]

顺序[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 接口

public class ExtendPropertySourceEnvironmentPostProcessor 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 文件新增对应的配置

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
com.zq.externalized.configuration.processor.ExtendPropertySourceEnvironmentPostProcessor

执行结果:

用户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]'}]

可以看到顺序[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

public class ExtendPropertySourceApplicationContextInitializer 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 文件新增对应的配置

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
com.zq.externalized.configuration.initializer.ExtendPropertySourceApplicationContextInitializer

执行结果:

用户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'}]

顺序[1]-名称[from-ApplicationContextInitializer] 就是我们新加入的 ExtendPropertySourcesInitializer

3.5 基于 SpringApplicationRunListener#contextPrepared &contextLoaded 扩展

这两个方法都在 SpringApplicationRunListener 中,所以我们可以写在一起

修改 3.1 中的例子 ExtendPropertySourceRunListener 的这两个方法即可

public class ExtendPropertySourceRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	public ExtendPropertySourceRunListener(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-SpringApplicationRunListener#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-SpringApplicaitonRunListener#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-SpringApplicaitonRunListener#contextLoaded]:[MapPropertySource {name='from-SpringApplicaitonRunListener#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-SpringApplicationRunListener#environmentPrepared]:[MapPropertySource {name='from-SpringApplicationRunListener#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'}]

顺序[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 也可以进行相应的操作。

我们新建 ExtendPropertySourceApplicationListener2 来监听 ApplicationPreparedEvent 事件。

		ApplicationListener<ApplicationPreparedEvent> {

	@Override
	public void onApplicationEvent(ApplicationPreparedEvent event) {
		ConfigurableApplicationContext context = event.getApplicationContext();
		ConfigurableEnvironment environment = context.getEnvironment();
		MutablePropertySources propertySources = environment.getPropertySources();

		Map<String, Object> map = new HashMap<>();
		map.put("user.id", 100);
		MapPropertySource propertySource = new MapPropertySource("from-ExtendPropertySourceApplicationListener2", map);

		propertySources.addFirst(propertySource);
	}
}

resources/META-INF/spring.factories 文件新增对应的配置

# Application Listeners
org.springframework.context.ApplicationListener=\
com.zq.externalized.configuration.listener.ExtendPropertySourceApplicationListener,\
com.zq.externalized.configuration.listener.ExtendPropertySourceApplicationListener2

执行结果:

用户id: 100
顺序[1]-名称[from-ExtendPropertySourceApplicationListener2]:[MapPropertySource {name='from-ExtendPropertySourceApplicationListener2'}]
顺序[2]-名称[from-SpringApplicaitonRunListener#contextLoaded]:[MapPropertySource {name='from-SpringApplicaitonRunListener#contextLoaded'}]
顺序[3]-名称[from-SpringApplicationRunListener#contextPrepared]:[MapPropertySource {name='from-SpringApplicationRunListener#contextPrepared'}]
顺序[4]-名称[from-ApplicationContextInitializer]:[MapPropertySource {name='from-ApplicationContextInitializer'}]
顺序[5]-名称[configurationProperties]:[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}]
顺序[6]-名称[from-ApplicationEnvironmentPreparedEvent]:[MapPropertySource {name='from-ApplicationEnvironmentPreparedEvent'}]
顺序[7]-名称[from-EnvironmentPostProcessor]:[MapPropertySource {name='from-EnvironmentPostProcessor'}]
顺序[8]-名称[from-SpringApplicationRunListener#environmentPrepared]:[MapPropertySource {name='from-SpringApplicationRunListener#environmentPrepared'}]
顺序[9]-名称[commandLineArgs]:[SimpleCommandLinePropertySource {name='commandLineArgs'}]
顺序[10]-名称[systemProperties]:[MapPropertySource {name='systemProperties'}]
顺序[11]-名称[systemEnvironment]:[OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}]
顺序[12]-名称[random]:[RandomValuePropertySource {name='random'}]
顺序[13]-名称[applicationConfig: [classpath:/application.properties]]:[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}]
顺序[14]-名称[from classpath:META-INF/default.properties]:[ResourcePropertySource {name='from classpath:META-INF/default.properties'}]

顺序顺序[1]-名称[from-ExtendPropertySourceApplicationListener2] 就是我们新加入的 ExtendPropertySourceApplicationListener2

4.参考

  • 慕课网-小马哥《Spring Boot2.0深度实践之核心技术篇》

文章作者: 张权
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张权 !
评论
  目录