spring boot版本为:2.1.6.RELEASE
前言
Spring Framework 3.1
开始引入 Environment
抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer
,而且还支持更丰富的配置属性源(PropertySource
)。
理解springboot外部化配置原理,有助于帮助我们理解spring cloud config、nacos config等分布式配置中心。
源码分析
Environment的初始化
在springboot启动流程的run方法中,有一个 prepareEnvironment
方法,这个方法就是用来准备Environment
这个对象的。
SpringApplication
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备Environment
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);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
进入prepareEnvironment(listeners, applicationArguments)
方法:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建environment对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置environment对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发送ApplicationEnvironmentPreparedEvent类型的事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
这个方法主要就是创建和配置spring容器的环境信息,进入getOrCreateEnvironment
看它是怎么创建环境的,进去后发现这个方法,就是根据当前的webApplication
类型匹配对应的environment
,在servlet容器中就是StandardServletEnvironment
,如果是spring webflux,则是StandardReactiveWebEnvironment
。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
StandardServletEnvironment
是整个spring boot应用运行环境的实现类,后面所有的关于环境相关的配置操作都是基于这个类,它的类的结构图如下:
StandardServletEnvironment
的初始化过程会做一些事情,就是配置一些基本的属性来源。StandardServletEnvironment
会初始化父类 AbstractEnvironment
,在这个类的构造方法中,会调用一个自定义配置文件的方法,这个是spring中比较常见的实现手法,前面在看ribbon、eureka中都有看到。
AbstractEnvironment
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
customizePropertySources
这个方法被 StandardServletEnvironment
重写了,所以会调用StandardServletEnvironment
中的 customizePropertySources
方法。不难看出,这里是将几个不同的配置源封装成 StubPropertySource
添加到
MutablePropertySources
中,调用 addLast
是表示一直往最后的位置添加。
-
SERVLET_CONFIG_PROPERTY_SOURCE_NAME
:servlet config的配置信息 -
SERVLET_CONTEXT_PROPERTY_SOURCE_NAME
: 这个是servlet初始化的上下文,也就是以前我们在web.xml
中配置的 context-param 。 -
JNDI_PROPERTY_SOURCE_NAME
: 加载jndi.properties配置信息。
StandardServletEnvironment
@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_PROPERTIES_PROPERTY_SOURCE_NAME
: 系统变量,通过System.setProperty
设置的变量,默认可以看到 java.version 、 os.name 等。SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME
: 系统环境变量,也就是我们配置JAVA_HOME
的地方。
StandardEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
这里要明确一点,就是添加PropertySource
的目的其实就是要告诉Environment
,解析哪些位置的属性文件进行加载。而在这个添加过程中,所有的添加都是基于 addLast
,也就是最早添加的PropertySource
会放在最前面。 systemEnvironment
是在 systemProperties
前面,这点很重要。因为前面的配置会覆盖后面的配置,也就是说系统变量中的配置比系统环境变量中的配置优先级更高。
MutablePropertySources
在上面的代码中可以看到,所有的外部资源配置都是添加到了一个MutablePropertySources
对象中,这个对象封装了属性资源的集合。而从 MutablePropertySources
命名来说,Mutable
是一个可变的意思,也就是意味着它动态的管理了PropertySource
的集合。
public class MutablePropertySources implements PropertySources {
private final List> propertySourceList = new CopyOnWriteArrayList<>();
// 省略...
}
通过上面的类图可以发现AbstractEnvironment
实现了文件解析器ConfigurablePropertyResolver
,而在下面这段代码中我们把 MutablePropertySources
传递到PropertySourcesPropertyResolver
中。这样就可以让 AbstractEnvironment
具备文件解析的功能,只是这个功能,委托给了PropertySourcesPropertyResolver
来实现。
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
//省略...
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
//省略...
}
现在回退到SpringApplication
类的prepareEnvironment
方法,我们继续来看 configureEnvironment
这个方法,这个方法有三个作用:
-
addConversionService
:添加类型转化的服务,我们知道properties文件中配置的属性都是String类型的,而转化为Java对象之后要根据合适的类型进行转化,而ConversionService
是一套通用的转化方案,请参考《SpringMVC之类型转换Converter》,这里把这个转化服务设置到当前的Environment
,很显然,就是为Environment
配置解析时提供一个类型转化的解决方案。 -
configurePropertySources
: 配置Environment
中的propertysources
-
configureProfiles
:配置profiles
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
进入configurePropertySources
方法:
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
- 设置
defaultProperties
属性来源,此处可以在构造SpringApplication对象时,通过他的setDefaultProperties方法传入额外的配置。 - 设置
commandLineProperties
来源,如果设置了命令行参数,则会加载SimpleCommandLinePropertySource
作为propertySource
到目前为止,还是在初始化外部化配置的数据来源。接着进入configureProfiles
方法,这个方法就比较容易理解,就是配置当前激活的profiles
,将当前的activeProfiles
设置到enviroment
中。这样就能够使得我们完成不同环境下配置的获取问题。
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
environment.getActiveProfiles(); // ensure they are initialized
// But these ones should go first (last wins in a property key clash)
Set profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
此时environment
中的activeProfiles
集合还是空的。additionalProfiles
可以通过SpringApplication
对象的setAdditionalProfiles
方法设置。
经过上面的操作spring的配置信息都已加载完成,但有一个很重要的配置还没有加载,那就是springboot
的yml或properties文件中的配置信息,现在回退到SpringApplication
类的prepareEnvironment
类。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 省略...
listeners.environmentPrepared(environment);
// 省略...
return environment;
}
进入listeners.environmentPrepared
方法:
SpringApplicationRunListeners
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
循环遍历SpringApplicationRunListener
对象集合,然后调用它的environmentPrepared
方法。在springboot环境中,默认在META-INF/spring.factories
中,配置的实现类是EventPublishingRunListener
。
EventPublishingRunListener
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
内部会委托给SimpleApplicationEventMulticaster对象,进行广播ApplicationEnvironmentPreparedEvent事件。在这个类内部最终会循环遍历spring容器中实现了ApplicationListener的bean,然后调用它的onApplicationEvent方法。
SimpleApplicationEventMulticaster
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
以上代码其实也是spring事件机制背后的实现原理。
在springboot环境中,ConfigFileApplicationListener
类实现了ApplicationListener
接口,那么进入ConfigFileApplicationListener
类会看到一个onApplicationEvent
方法:
ConfigFileApplicationListener
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
因为现在发布的是ApplicationEnvironmentPreparedEvent
类型的事件,所以会进入onApplicationEnvironmentPreparedEvent
方法中:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
此方法主要做了三件事:
- 从
spring.factories
文件中,加载实现了EnvironmentPostProcessor
接口的类。 - 将当前对象加入到
postProcessors
列表中,因为当前类实现了EnvironmentPostProcessor
接口 - 循环遍历
postProcessors
列表,调用postProcessEnvironment
方法
由于当前类实现了EnvironmentPostProcessor
接口,所以会调用它的postProcessEnvironment
方法:
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
进入addPropertySources
方法:
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
这个方法做两个事情:
- 添加一个
RandomValuePropertySource
到Environment
的MutablePropertySources
中,放在系统环境变量的后面 - 加载spring boot中的配置信息,比如
application.yml
或者application.properties
进入Loader
的load
方法:
ConfigFileApplicationListener#Loader
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
}
这块的代码就不继续深入分析了,有点绕,感兴趣的小伙伴深入去看看。load 所做的事情如下:
- 获取默认的配置文件路径,有4种:
classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/
- 遍历所有的路径,拼装配置文件名称。
- 再遍历解析器,选择yml或者properties解析,将解析结果添加到集合
MutablePropertySources
当中。
至此,springBoot中的资源文件加载完毕,解析顺序从上到下,所以前面的配置文件会覆盖后面的配置文件。可以看到 application.properties 的优先级最低,系统变量和环境变量的优先级相对较高。
扩展外部化配置属性源
扩展点
- 基于 EnvironmentPostProcessor 扩展
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor
- 基于 ApplicationEnvironmentPreparedEvent事件扩展
public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener
- 基于 SpringApplicationRunListener 扩展
public class CustomSpringApplicationRunListener implements SpringApplicationRunListener, Ordered
- 基于 ApplicationContextInitializer 扩展
public class CustomApplicationContextInitializer implements ApplicationContextInitializer
- 基于 ApplicationPreparedEvent 扩展
public class ApplicationPreparedEventListener implements ApplicationListener
配置
在 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
以上的扩展可以选取其中一种进行扩展,只是属性源的加载时机不太一样,越早越好。
总结
掌握了springboot外部化配置的实现原理,为以后学习和理解spring cloud环境下,加载远程配置中心的配置原理,打下良好的基础。
欢迎关注我的公众号:程序员L札记