来源:[宜信技术学院 ]
作者:石建伟
一、流程分析
入口程序
在 SpringApplication#run(String... args) 方法中,外部化配置关键流程分为以下四步
1public ConfigurableApplicationContext run(String... args) { 2 ... 3 SpringApplicationRunListeners listeners = getRunListeners(args); // 1 4 listeners.starting(); 5 try { 6 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 7 args); 8 ConfigurableEnvironment environment = prepareEnvironment(listeners, 9 applicationArguments); // 2 10 configureIgnoreBeanInfo(environment); 11 Banner printedBanner = printBanner(environment); 12 context = createApplicationContext(); 13 exceptionReporters = getSpringFactoriesInstances( 14 SpringBootExceptionReporter.class, 15 new Class[] { ConfigurableApplicationContext.class }, context); 16 prepareContext(context, environment, listeners, applicationArguments, 17 printedBanner); // 3 18 refreshContext(context); // 4 19 afterRefresh(context, applicationArguments); 20 stopWatch.stop(); 21 if (this.logStartupInfo) { 22 new StartupInfoLogger(this.mainApplicationClass) 23 .logStarted(getApplicationLog(), stopWatch); 24 } 25 listeners.started(context); 26 callRunners(context, applicationArguments); 27 } 28 ... 29}
关键流程思维导图

关键流程详解
对入口程序中标记的四步,分析如下
1、SpringApplication#getRunListeners
加载 META-INF/spring.factories 获取 SpringApplicationRunListener 的实例集合,存放的对象是 EventPublishingRunListener 类型 以及自定义的 SpringApplicationRunListener 实现类型

2、SpringApplication#prepareEnvironment
prepareEnvironment 方法中,主要的三步如下
1private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, 2 ApplicationArguments applicationArguments) { 3 // Create and configure the environment 4 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 2.1 5 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 2.2 6 listeners.environmentPrepared(environment); // 2.3 7 ... 8 return environment; 9}
2.1、getOrCreateEnvironment 方法
在 WebApplicationType.SERVLET web应用类型下,会创建 StandardServletEnvironment,本文以 StandardServletEnvironment 为例,类的层次结构如下

当创建 StandardServletEnvironment ,StandardServletEnvironment 父类 AbstractEnvironment 调用 customizePropertySources 方法,会执行 StandardServletEnvironment#customizePropertySources 和 StandardEnvironment#customizePropertySources ,源码如下
AbstractEnvironment
1public AbstractEnvironment() { 2 customizePropertySources(this.propertySources); 3 if (logger.isDebugEnabled()) { 4 logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources); 5 } 6}
StandardServletEnvironment#customizePropertySources
1/** Servlet context init parameters property source name: {@value} */ 2public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"; 3 4/** Servlet config init parameters property source name: {@value} */ 5public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"; 6 7/** JNDI property source name: {@value} */ 8public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties"; 9 10@Override 11protected void customizePropertySources(MutablePropertySources propertySources) { 12 propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); 13 propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); 14 if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { 15 propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); 16 } 17 super.customizePropertySources(propertySources); 18}
StandardEnvironment#customizePropertySources
1/** System environment property source name: {@value} */ 2public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; 3 4/** JVM system properties property source name: {@value} */ 5public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; 6 7@Override 8protected void customizePropertySources(MutablePropertySources propertySources) { 9 propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); 10 propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment()); 11}
PropertySources 顺序:
- servletConfigInitParams
- servletContextInitParams
- jndiProperties
- systemProperties
- systemEnvironment
PropertySources与PropertySource关系为 1 对 N
2.2、configureEnvironment 方法
调用 configurePropertySources(environment, args), 在方法里面设置 Environment 的 PropertySources , 包含 defaultProperties 和 SimpleCommandLinePropertySource (commandLineArgs),PropertySources 添加 defaultProperties 到最后,添加 SimpleCommandLinePropertySource (commandLineArgs)到最前面
PropertySources 顺序:
-
commandLineArgs
-
servletConfigInitParams
-
servletContextInitParams
-
jndiProperties
-
systemProperties
-
systemEnvironment
-
defaultProperties
2.3、listeners.environmentPrepared 方法
会按优先级顺序遍历执行 SpringApplicationRunListener#environmentPrepared,比如 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
-
EventPublishingRunListener发布ApplicationEnvironmentPreparedEvent事件 -
ConfigFileApplicationListener监听ApplicationEvent事件 、处理ApplicationEnvironmentPreparedEvent事件,加载所有EnvironmentPostProcessor包括自己,然后按照顺序进行方法回调ConfigFileApplicationListener#postProcessEnvironment方法回调 ,然后addPropertySources方法调用RandomValuePropertySource#addToEnvironment,在 systemEnvironment 后面添加 random,然后添加配置文件的属性源(详见源码ConfigFileApplicationListener.Loader#load()
-
扩展点
-
自定义
SpringApplicationRunListener,重写environmentPrepared方法 -
自定义
EnvironmentPostProcessor -
自定义
ApplicationListener监听ApplicationEnvironmentPreparedEvent事件
ConfigFileApplicationListener,即是 EnvironmentPostProcessor ,又是 ApplicationListener ,类的层次结构如下
1@Override 2public void onApplicationEvent(ApplicationEvent event) { 3 // 处理 ApplicationEnvironmentPreparedEvent 事件 4 if (event instanceof ApplicationEnvironmentPreparedEvent) { 5 onApplicationEnvironmentPreparedEvent( 6 (ApplicationEnvironmentPreparedEvent) event); 7 } 8 // 处理 ApplicationPreparedEvent 事件 9 if (event instanceof ApplicationPreparedEvent) { 10 onApplicationPreparedEvent(event); 11 } 12} 13 14private void onApplicationEnvironmentPreparedEvent( 15 ApplicationEnvironmentPreparedEvent event) { 16 // 加载 META-INF/spring.factories 中配置的 EnvironmentPostProcessor 17 List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); 18 // 加载自己 ConfigFileApplicationListener 19 postProcessors.add(this); 20 // 按照 Ordered 进行优先级排序 21 AnnotationAwareOrderComparator.sort(postProcessors); 22 // 回调 EnvironmentPostProcessor 23 for (EnvironmentPostProcessor postProcessor : postProcessors) { 24 postProcessor.postProcessEnvironment(event.getEnvironment(), 25 event.getSpringApplication()); 26 } 27} 28 29List<EnvironmentPostProcessor> loadPostProcessors() { 30 return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, 31 getClass().getClassLoader()); 32} 33 34@Override 35public void postProcessEnvironment(ConfigurableEnvironment environment, 36 SpringApplication application) { 37 addPropertySources(environment, application.getResourceLoader()); 38} 39 40/** 41 * Add config file property sources to the specified environment. 42 * @param environment the environment to add source to 43 * @param resourceLoader the resource loader 44 * @see #addPostProcessors(ConfigurableApplicationContext) 45 */ 46protected void addPropertySources(ConfigurableEnvironment environment, 47 ResourceLoader resourceLoader) { 48 RandomValuePropertySource.addToEnvironment(environment); 49 // 添加配置文件的属性源 50 new Loader(environment, resourceLoader).load(); 51}
RandomValuePropertySource
1public static void addToEnvironment(ConfigurableEnvironment environment) { 2 // 在 systemEnvironment 后面添加 random 3 environment.getPropertySources().addAfter( 4 StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, 5 new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME)); 6 logger.trace("RandomValuePropertySource add to Environment"); 7}
添加配置文件的属性源:
执行
new Loader(environment, resourceLoader).load();, 调用load(Profile, DocumentFilterFactory, DocumentConsumer)(getSearchLocations() 获取配置文件位置,可以指定通过 spring.config.additional-location 、spring.config.location 、spring.config.name 参数或者使用默认值 ), 然后调用addLoadedPropertySources -> addLoadedPropertySource(加载 查找出来的PropertySource到PropertySources,并确保放置到 defaultProperties 的前面 )默认的查找位置,配置为
"classpath:/,classpath:/config/,file:./,file:./config/",查找顺序从后向前
PropertySources 顺序:
- commandLineArgs
- servletConfigInitParams
- servletContextInitParams
- jndiProperties
- systemProperties
- systemEnvironment
- random
- application.properties …
- defaultProperties
3、SpringApplication#prepareContext
prepareContext 方法中,主要的三步如下
1private void prepareContext(ConfigurableApplicationContext context, 2 ConfigurableEnvironment environment, 3 SpringApplicationRunListeners listeners, 4 ApplicationArguments applicationArguments, 5 Banner printedBanner) { 6 ... 7 applyInitializers(context); // 3.1 8 listeners.contextPrepared(context); //3.2 9 ... 10 listeners.contextLoaded(context); // 3.3 11}
3.1、applyInitializers 方法
会遍历执行所有的 ApplicationContextInitializer#initialize
- 扩展点
- 自定义
ApplicationContextInitializer
3.2、listeners.contextPrepared 方法
会按优先级顺序遍历执行 SpringApplicationRunListener#contextPrepared,比如 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
- 扩展点
- 自定义
SpringApplicationRunListener,重写contextPrepared方法
3.3、listeners.contextLoaded 方法
会按优先级顺序遍历执行 SpringApplicationRunListener#contextLoaded,比如 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
-
EventPublishingRunListener发布ApplicationPreparedEvent事件 -
ConfigFileApplicationListener监听ApplicationEvent事件 处理ApplicationPreparedEvent事件 -
扩展点
-
自定义
SpringApplicationRunListener,重写contextLoaded方法 -
自定义
ApplicationListener,监听ApplicationPreparedEvent事件
ConfigFileApplicationListener
1@Override 2public void onApplicationEvent(ApplicationEvent event) { 3 // 处理 ApplicationEnvironmentPreparedEvent 事件 4 if (event instanceof ApplicationEnvironmentPreparedEvent) { 5 onApplicationEnvironmentPreparedEvent( 6 (ApplicationEnvironmentPreparedEvent) event); 7 } 8 // 处理 ApplicationPreparedEvent 事件 9 if (event instanceof ApplicationPreparedEvent) { 10 onApplicationPreparedEvent(event); 11 } 12} 13 14private void onApplicationPreparedEvent(ApplicationEvent event) { 15 this.logger.replayTo(ConfigFileApplicationListener.class); 16 addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); 17} 18 19// 添加 PropertySourceOrderingPostProcessor 处理器,配置 PropertySources 20protected void addPostProcessors(ConfigurableApplicationContext context) { 21 context.addBeanFactoryPostProcessor( 22 new PropertySourceOrderingPostProcessor(context)); 23}
PropertySourceOrderingPostProcessor
1// 回调处理(在配置类属性源解析) 2@Override 3public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 4 throws BeansException { 5 reorderSources(this.context.getEnvironment()); 6} 7 8// 调整 PropertySources 顺序,先删除 defaultProperties, 再把 defaultProperties 添加到最后 9private void reorderSources(ConfigurableEnvironment environment) { 10 PropertySource<?> defaultProperties = environment.getPropertySources() 11 .remove(DEFAULT_PROPERTIES); 12 if (defaultProperties != null) { 13 environment.getPropertySources().addLast(defaultProperties); 14 } 15}
PropertySourceOrderingPostProcessor是BeanFactoryPostProcessor
4、SpringApplication#refreshContext
会进行 @Configuration 配置类属性源解析,处理 @PropertySource annotations on your @Configuration classes,但顺序是在 defaultProperties 之后,下面会把 defaultProperties 调整到最后
AbstractApplicationContext#refresh 调用 invokeBeanFactoryPostProcessors (PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors), 然后进行 BeanFactoryPostProcessor 的回调处理 ,比如 PropertySourceOrderingPostProcessor 的回调(源码见上文)
PropertySources 顺序:
-
commandLineArgs
-
servletConfigInitParams
-
servletContextInitParams
-
jndiProperties
-
systemProperties
-
systemEnvironment
-
random
-
application.properties …
-
@PropertySourceannotations on your@Configurationclasses -
defaultProperties
不推荐使用这种方式,推荐使用在 refreshContext 之前准备好,
@PropertySource加载太晚,不会对自动配置产生任何影响
二、扩展外部化配置属性源
1、基于 EnvironmentPostProcessor 扩展
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
2、基于 ApplicationEnvironmentPreparedEvent 扩展
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>
3、基于 SpringApplicationRunListener 扩展
public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered
可以重写方法 environmentPrepared、contextPrepared、contextLoaded 进行扩展
4、基于 ApplicationContextInitializer 扩展
public class CustomApplicationContextInitializer implements ApplicationContextInitializer
关于与 Spring Cloud Config Client 整合,对外部化配置加载的扩展(绑定到Config Server,使用远端的property sources 初始化
Environment),参考源码PropertySourceBootstrapConfiguration(是对ApplicationContextInitializer的扩展)、ConfigServicePropertySourceLocator#locate获取远端的property sources是
RestTemplate通过向 http://{spring.cloud.config.uri}/{spring.application.name}/{spring.cloud.config.profile}/{spring.cloud.config.label} 发送 GET 请求方式获取的
5、基于 ApplicationPreparedEvent 扩展
public class ApplicationPreparedEventListener implements ApplicationListener<ApplicationPreparedEvent>
6、扩展实战
6.1、扩展配置
在 classpath 下添加配置文件 META-INF/spring.factories, 内容如下
1# Spring Application Run Listeners 2org.springframework.boot.SpringApplicationRunListener=\ 3springboot.propertysource.extend.listener.CustomSpringApplicationRunListener 4 5# Application Context Initializers 6org.springframework.context.ApplicationContextInitializer=\ 7springboot.propertysource.extend.initializer.CustomApplicationContextInitializer 8 9# Application Listeners 10org.springframework.context.ApplicationListener=\ 11springboot.propertysource.extend.event.listener.ApplicationEnvironmentPreparedEventListener,\ 12springboot.propertysource.extend.event.listener.ApplicationPreparedEventListener 13 14# Environment Post Processors 15org.springframework.boot.env.EnvironmentPostProcessor=\ 16springboot.propertysource.extend.processor.CustomEnvironmentPostProcessor
以上的扩展可以选取其中一种进行扩展,只是属性源的加载时机不太一样
6.2、扩展实例代码
https://github.com/shijw823/springboot-externalized-configuration-extend.git
PropertySources 顺序:
1propertySourceName: [ApplicationPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource] 2 3propertySourceName: [CustomSpringApplicationRunListener-contextLoaded], propertySourceClassName: [OriginTrackedMapPropertySource] 4 5propertySourceName: [CustomSpringApplicationRunListener-contextPrepared], propertySourceClassName: [OriginTrackedMapPropertySource] 6 7propertySourceName: [CustomApplicationContextInitializer], propertySourceClassName: [OriginTrackedMapPropertySource] 8 9propertySourceName: [bootstrapProperties], propertySourceClassName: [CompositePropertySource] 10 11propertySourceName: [configurationProperties], propertySourceClassName: [ConfigurationPropertySourcesPropertySource] 12 13propertySourceName: [CustomSpringApplicationRunListener-environmentPrepared], propertySourceClassName: [OriginTrackedMapPropertySource] 14 15propertySourceName: [CustomEnvironmentPostProcessor-dev-application], propertySourceClassName: [OriginTrackedMapPropertySource] 16 17propertySourceName: [ApplicationEnvironmentPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource] 18 19propertySourceName: [commandLineArgs], propertySourceClassName: [SimpleCommandLinePropertySource] 20 21propertySourceName: [servletConfigInitParams], propertySourceClassName: [StubPropertySource] 22 23propertySourceName: [servletContextInitParams], propertySourceClassName: [ServletContextPropertySource] 24 25propertySourceName: [systemProperties], propertySourceClassName: [MapPropertySource] 26 27propertySourceName: [systemEnvironment], propertySourceClassName: [OriginAwareSystemEnvironmentPropertySource] 28 29propertySourceName: [random], propertySourceClassName: [RandomValuePropertySource] 30 31propertySourceName: [applicationConfig: [classpath:/extend/config/springApplicationRunListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 32 33propertySourceName: [applicationConfig: [classpath:/extend/config/applicationListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 34 35propertySourceName: [applicationConfig: [classpath:/extend/config/applicationContextInitializer.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 36 37propertySourceName: [applicationConfig: [classpath:/extend/config/environmentPostProcessor.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 38 39propertySourceName: [applicationConfig: [classpath:/extend/config/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 40 41propertySourceName: [applicationConfig: [classpath:/extend/config/config.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 42 43propertySourceName: [applicationConfig: [classpath:/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 44 45propertySourceName: [springCloudClientHostInfo], propertySourceClassName: [MapPropertySource] 46 47propertySourceName: [applicationConfig: [classpath:/bootstrap.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] 48 49propertySourceName: [propertySourceConfig], propertySourceClassName: [ResourcePropertySource] 50 51propertySourceName: [defaultProperties], propertySourceClassName: [MapPropertySource]
bootstrapProperties 是 获取远端(config-server)的 property sources
加载顺序也可参考 http://{host}:{port}/actuator/env
PropertySources 单元测试顺序:
1@TestPropertySource#properties 2@SpringBootTest#properties 3@TestPropertySource#locations