Spring Boot 外部化配置实战解析
一、流程分析
入口程序
在
SpringApplication#run(String... args)方法中,外部化配置关键流程分为以下四步
public ConfigurableApplicationContext run(String... args) { ... SpringApplicationRunListeners listeners = getRunListeners(args); // 1 listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 2 configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 3 refreshContext(context); // 4 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } ... }
关键流程思维导图
![](Spring Boot 外部化配置关键流程.png)
关键流程详解
对入口程序中标记的四步,分析如下
1、SpringApplication#getRunListeners
加载
META-INF/spring.factories获取
SpringApplicationRunListener的实例集合,存放的对象是
EventPublishingRunListener类型 以及自定义的
SpringApplicationRunListener实现类型
2、SpringApplication#prepareEnvironment
prepareEnvironment方法中,主要的三步如下
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 2.1 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 2.2 listeners.environmentPrepared(environment); // 2.3 ... return environment; }
2.1、getOrCreateEnvironment
方法
在
WebApplicationType.SERVLETweb应用类型下,会创建
StandardServletEnvironment,本文以
StandardServletEnvironment为例,类的层次结构如下
当创建
StandardServletEnvironment,
StandardServletEnvironment父类
AbstractEnvironment调用
customizePropertySources方法,会执行
StandardServletEnvironment#customizePropertySources和
StandardEnvironment#customizePropertySources,源码如下
AbstractEnvironment
public AbstractEnvironment() { customizePropertySources(this.propertySources); if (logger.isDebugEnabled()) { logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources); } }
StandardServletEnvironment#customizePropertySources
/** Servlet context init parameters property source name: {@value} */ public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"; /** Servlet config init parameters property source name: {@value} */ public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"; /** JNDI property source name: {@value} */ public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties"; @Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources); }
StandardEnvironment#customizePropertySources
/** System environment property source name: {@value} */ public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; /** JVM system properties property source name: {@value} */ public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; @Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment()); }
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,类的层次结构如下
@Override public void onApplicationEvent(ApplicationEvent event) { // 处理 ApplicationEnvironmentPreparedEvent 事件 if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } // 处理 ApplicationPreparedEvent 事件 if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { // 加载 META-INF/spring.factories 中配置的 EnvironmentPostProcessor List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); // 加载自己 ConfigFileApplicationListener postProcessors.add(this); // 按照 Ordered 进行优先级排序 AnnotationAwareOrderComparator.sort(postProcessors); // 回调 EnvironmentPostProcessor for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } List<EnvironmentPostProcessor> loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); } @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); } /** * Add config file property sources to the specified environment. * @param environment the environment to add source to * @param resourceLoader the resource loader * @see #addPostProcessors(ConfigurableApplicationContext) */ protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); // 添加配置文件的属性源 new Loader(environment, resourceLoader).load(); }
RandomValuePropertySource
public static void addToEnvironment(ConfigurableEnvironment environment) { // 在 systemEnvironment 后面添加 random environment.getPropertySources().addAfter( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME)); logger.trace("RandomValuePropertySource add to Environment"); }
添加配置文件的属性源:
执行
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方法中,主要的三步如下
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { ... applyInitializers(context); // 3.1 listeners.contextPrepared(context); //3.2 ... listeners.contextLoaded(context); // 3.3 }
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
@Override public void onApplicationEvent(ApplicationEvent event) { // 处理 ApplicationEnvironmentPreparedEvent 事件 if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } // 处理 ApplicationPreparedEvent 事件 if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationPreparedEvent(ApplicationEvent event) { this.logger.replayTo(ConfigFileApplicationListener.class); addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } // 添加 PropertySourceOrderingPostProcessor 处理器,配置 PropertySources protected void addPostProcessors(ConfigurableApplicationContext context) { context.addBeanFactoryPostProcessor( new PropertySourceOrderingPostProcessor(context)); }
PropertySourceOrderingPostProcessor
// 回调处理(在配置类属性源解析) @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { reorderSources(this.context.getEnvironment()); } // 调整 PropertySources 顺序,先删除 defaultProperties, 再把 defaultProperties 添加到最后 private void reorderSources(ConfigurableEnvironment environment) { PropertySource<?> defaultProperties = environment.getPropertySources() .remove(DEFAULT_PROPERTIES); if (defaultProperties != null) { environment.getPropertySources().addLast(defaultProperties); } }
PropertySourceOrderingPostProcessor是BeanFactoryPostProcessor
4、SpringApplication#refreshContext
会进行
@Configuration配置类属性源解析,处理
@PropertySourceannotations on your
@Configurationclasses,但顺序是在 defaultProperties 之后,下面会把 defaultProperties 调整到最后
AbstractApplicationContext#refresh调用
invokeBeanFactoryPostProcessors(
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors), 然后进行
BeanFactoryPostProcessor的回调处理 ,比如
PropertySourceOrderingPostProcessor的回调(源码见上文)
PropertySources顺序:
-
commandLineArgs
-
servletConfigInitParams
-
servletContextInitParams
-
jndiProperties
-
systemProperties
-
systemEnvironment
-
random
-
application.properties …
-
@PropertySource
annotations on your@Configuration
classes -
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, 内容如下
# Spring Application Run Listeners org.springframework.boot.SpringApplicationRunListener=\ springboot.propertysource.extend.listener.CustomSpringApplicationRunListener # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ springboot.propertysource.extend.initializer.CustomApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener=\ springboot.propertysource.extend.event.listener.ApplicationEnvironmentPreparedEventListener,\ springboot.propertysource.extend.event.listener.ApplicationPreparedEventListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ springboot.propertysource.extend.processor.CustomEnvironmentPostProcessor
以上的扩展可以选取其中一种进行扩展,只是属性源的加载时机不太一样
6.2、扩展实例代码
https://github.com/shijw823/springboot-externalized-configuration-extend.git
PropertySources顺序:
propertySourceName: [ApplicationPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [CustomSpringApplicationRunListener-contextLoaded], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [CustomSpringApplicationRunListener-contextPrepared], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [CustomApplicationContextInitializer], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [bootstrapProperties], propertySourceClassName: [CompositePropertySource] propertySourceName: [configurationProperties], propertySourceClassName: [ConfigurationPropertySourcesPropertySource] propertySourceName: [CustomSpringApplicationRunListener-environmentPrepared], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [CustomEnvironmentPostProcessor-dev-application], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [ApplicationEnvironmentPreparedEventListener], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [commandLineArgs], propertySourceClassName: [SimpleCommandLinePropertySource] propertySourceName: [servletConfigInitParams], propertySourceClassName: [StubPropertySource] propertySourceName: [servletContextInitParams], propertySourceClassName: [ServletContextPropertySource] propertySourceName: [systemProperties], propertySourceClassName: [MapPropertySource] propertySourceName: [systemEnvironment], propertySourceClassName: [OriginAwareSystemEnvironmentPropertySource] propertySourceName: [random], propertySourceClassName: [RandomValuePropertySource] propertySourceName: [applicationConfig: [classpath:/extend/config/springApplicationRunListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [applicationConfig: [classpath:/extend/config/applicationListener.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [applicationConfig: [classpath:/extend/config/applicationContextInitializer.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [applicationConfig: [classpath:/extend/config/environmentPostProcessor.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [applicationConfig: [classpath:/extend/config/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [applicationConfig: [classpath:/extend/config/config.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [applicationConfig: [classpath:/application.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [springCloudClientHostInfo], propertySourceClassName: [MapPropertySource] propertySourceName: [applicationConfig: [classpath:/bootstrap.properties]], propertySourceClassName: [OriginTrackedMapPropertySource] propertySourceName: [propertySourceConfig], propertySourceClassName: [ResourcePropertySource] propertySourceName: [defaultProperties], propertySourceClassName: [MapPropertySource]
bootstrapProperties 是 获取远端(config-server)的 property sources
加载顺序也可参考 http://{host}:{port}/actuator/env
PropertySources单元测试顺序:
@TestPropertySource#properties @SpringBootTest#properties @TestPropertySource#locations
三、参考资料
阅读更多- spring boot实战(第五篇)配置源码解析
- spring boot实战(第五篇)配置源码解析
- 详解Spring Boot配置使用Logback进行日志记录的实战
- 【Spring实战】----Spring配置文件的解析
- Spring Boot干货系列:(二)配置文件解析
- Spring Boot项目打成jar包后关于配置文件的外部化配置
- SpringBoot属性配置实战(多种方式)
- 笔记--Spring Boot中解析yml配置 注入到对象
- spring boot实战(第四篇)分散配置
- Spring Boot干货系列:(七)默认日志logback配置解析
- 【转】Spring Boot干货系列:(二)配置文件解析
- 5.玩转Spring Boot 自定义配置、导入XML配置与外部化配置
- 【Spring实战】----Spring事务管理配置解析
- Spring Boot 揭秘与实战 自己实现一个简单的自动配置模块
- SpringBoot学习-第二章 常用配置方式-<Spring Boot 实战>
- Spring Boot自动配置原理、实战
- 【spring boot】配置文件 application.properties 属性解析
- Spring Boot实战之Java配置
- SpringBoot实战(自动配置属性整理)读书笔记
- 【Spring实战】----源码解析Spring Security4.1.3中的过滤器Filter配置