Spring Boot外部化配置实战解析
一、流程分析
1.1 入口程序
在 SpringApplication#run(String... args) 方法中,外部化配置关键流程分为以下四步
public ConfigurableApplicationContext
run(String... args) {
...
SpringApplicationRunListeners listeners = getRunListeners(args); //
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments); //
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner); //
refreshContext(context); //
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
...
}
1.2 关键流程思维导图

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

1.3.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;
}
1) getOrCreateEnvironment 方法
在 WebApplicationType.SERVLET web应用类型下,会创建 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
StringSERVLET_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) configureEnvironment 方法
调用 configurePropertySources(environment, args), 在方法里面设置 Environment 的 PropertySources , 包含 defaultProperties 和
SimpleCommandLinePropertySource(commandLineArgs),PropertySources 添加 defaultProperties 到最后,添加
SimpleCommandLinePropertySource(commandLineArgs)到最前面
PropertySources 顺序:
commandLineArgs
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
defaultProperties
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
// 加载自己 ConfigFileApplicationListener
postProcessors.add(this);
// 按照 Ordered 进行优先级排序
AnnotationAwareOrderComparator.sort(postProcessors);
// 回调 EnvironmentPostProcessor
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
List
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
1.3.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
}
1)applyInitializers 方法
会遍历执行所有的 ApplicationContextInitializer#initialize
扩展点
- 自定义 ApplicationContextInitializer
2)listeners.contextPrepared 方法
会按优先级顺序遍历执行 SpringApplicationRunListener#contextPrepared,比如 EventPublishingRunListener 和 自定义的 SpringApplicationRunListener
扩展点
自定义 SpringApplicationRunListener ,重写 contextPrepared 方法
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
.remove(DEFAULT_PROPERTIES);
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
}
}
PropertySourceOrderingPostProcessor 是 BeanFactoryPostProcessor
1.3.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 ...
@PropertySource annotations on your @Configuration classes
defaultProperties
(不推荐使用这种方式,推荐使用在 refreshContext 之前准备好,@PropertySource 加载太晚,不会对自动配置产生任何影响)
二、扩展外部化配置属性源
2.1 基于 EnvironmentPostProcessor 扩展
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
2.2 基于 ApplicationEnvironmentPreparedEvent 扩展
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener
2.3 基于 SpringApplicationRunListener 扩展
public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered
可以重写方法 environmentPrepared、contextPrepared、contextLoaded 进行扩展
2.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 请求方式获取的
2.5 基于 ApplicationPreparedEvent 扩展
public class ApplicationPreparedEventListener implements ApplicationListener
2.6 扩展实战
2.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
以上的扩展可以选取其中一种进行扩展,只是属性源的加载时机不太一样
2.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
三、参考资料
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config
作者:石建伟
来源:宜信技术学院
Spring Boot外部化配置实战解析的更多相关文章
- Spring Boot 外部化配置(一)- Environment、ConfigFileApplicationListener
目录 前言 1.起源 2.外部化配置的资源类型 3.外部化配置的核心 3.1 Environment 3.1.1.ConfigFileApplicationListener 3.1.2.关联 Spri ...
- Spring Boot 外部化配置(二) - @ConfigurationProperties 、@EnableConfigurationProperties
目录 3.外部化配置的核心 3.2 @ConfigurationProperties 3.2.1 注册 Properties 配置类 3.2.2 绑定配置属性 3.1.3 ConfigurationP ...
- Spring配置文件外部化配置及.properties的通用方法
摘要:本文深入探讨了配置化文件(即.properties)的普遍应用方式.包括了Spring.一般的.远程的三种使用方案. 关键词:.properties, Spring, Disconf, Java ...
- 曹工谈Spring Boot:Spring boot中怎么进行外部化配置,一不留神摔一跤;一路debug,原来是我太年轻了
spring boot中怎么进行外部化配置,一不留神摔一跤:一路debug,原来是我太年轻了 背景 我们公司这边,目前都是spring boot项目,没有引入spring cloud config,也 ...
- Dubbo 新编程模型之外部化配置
外部化配置(External Configuration) 在Dubbo 注解驱动例子中,无论是服务提供方,还是服务消费方,均需要转配相关配置Bean: @Bean public Applicatio ...
- 玩转Spring Boot 自定义配置、导入XML配置与外部化配置
玩转Spring Boot 自定义配置.导入XML配置与外部化配置 在这里我会全面介绍在Spring Boot里面如何自定义配置,更改Spring Boot默认的配置,以及介绍各配置的优先 ...
- SpringBoot的外部化配置最全解析!
目录 SpringBoot中的配置解析[Externalized Configuration] 本篇要点 一.SpringBoot官方文档对于外部化配置的介绍及作用顺序 二.各种外部化配置举例 1.随 ...
- Spring Boot -- 外部配置的属性使用
Spring Boot允许使用propertities文件.yaml文件或者命令行参数作为外部配置. 命令行参数配置 Spring Boot可以基于jar包运行,打成jar包的程序可以直接通过下面的命 ...
- spring boot多数据源配置(mysql,redis,mongodb)实战
使用Spring Boot Starter提升效率 虽然不同的starter实现起来各有差异,但是他们基本上都会使用到两个相同的内容:ConfigurationProperties和AutoConfi ...
随机推荐
- Codeforces 891 C Envy
题目大意 给定一个 $n$ 个点 $m$ 条边的连通的无向图,每条边有一个权值,可能有重边.给出 $q$ 组询问,一组询问给出 $k$ 条边,问是否存在一棵最小生成树包含这 $k$ 条边. 思路 这道 ...
- 正则表达式的\b与\B总结
\b 单词边界,是指单词与符号之间的边界,是一个位置,不是空格或字符.(这里单词可以是中文字符,英文字符,数字: 符号可以是中文符号,英文符号,空格,制表符,换行).不能与量词?+*{1}{2,5} ...
- 【CF1016B】Segment Occurrences(模拟)
题意:给定两个串s和t,多次询问s的一个区间[l ,r]中有多少个子串与t串相同 len<=1e3,q<=1e5 思路:前缀和 #include<cstdio> #includ ...
- 【HDOJ6229】Wandering Robots(马尔科夫链,set)
题意:给定一个n*n的地图,上面有k个障碍点不能走,有一个机器人从(0,0)出发,每次等概率的不动或者往上下左右没有障碍的地方走动,问走无限步后停在图的右下部的概率 n<=1e4,k<=1 ...
- 【Eclipse】Eclipse中tomcat的Server配置(解决修改代码不断的重启服务器)以及设置tomcat文件发布位置与JSP编译位置查看
Eclipse有时候修改一点JS或者JSP都会自动重启,有时候修改完JS或者JSP之后必须重启服务器才生效,下面研究了server的一些选项之后彻底解决了这些问题,下面做记录: 我的 Eclipse ...
- 转 Python爬虫入门一之综述
转自: http://cuiqingcai.com/927.html 静觅 » Python爬虫入门一之综述 首先爬虫是什么? 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为 ...
- 写一简单kernel心得
当人按下笔记本开机键时.cpu的cs寄存器(基址)跟ip(偏移量)寄存器加电.被强制初始化为(jmp xxx:xxx) 跳转到bios所在的地址. 接着bios开机自检(这个不需要了解,只需了解最后跳 ...
- dedecms--二次开发之前后台登录分开
最近在写dedecms系统下会员功能二次开发,然后发现在本地测试的时候每次登录后台,管理员帐号都会在前台页面也显示登录了,但是如果真的是在前台页面用管理员账号登录的话那是登陆不了的,所以我觉得这样的效 ...
- hdu 4506(数学,循环节+快速幂)
小明系列故事——师兄帮帮忙 Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)Tot ...
- Codefroces Gym101572 I.Import Spaghetti-有向图跑最小环输出路径(Floyd)
暑假学的很多东西,现在都忘了,补这道题还要重新学一下floyd,有点难过,我暑假学的东西呢??? 好了,淡定,开始写题解. 这个题我是真的很难过啊,输入简直是有毒啊(内心已经画圈诅咒出题人无数次了.. ...