Spring Boot 2.6.x源码系列:
一文搞懂Spring Boot中java -jar启动jar包的原理
一文搞懂SpringBoot启动流程及自动配置
一文搞懂SpringBoot内嵌的Tomcat
一文搞懂SpringApplication对象的构建及spring.factories的加载时机
一文搞懂Spring Boot中application.yml以及application-xx.yml配置文件的加载
在Spring Boot中何时、如何加载application.yml以及application-xx.yml配置文件?
前置条件:
创建好配置文件application-dev.yml
并在application.yml配置spring.profiles.active属性
1、我们进入SpringApplication的run方法。会发现配置环境的加载是在ApplicationContext创建之前,prepareEnvironment方法执行即准备环境之时。
public ConfigurableApplicationContext run(String... args) {
......
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
......
context = createApplicationContext();
......
}
准备环境prepareEnvironment方法。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
2、接下来我们对象上面步骤进行详细追踪。getOrCreateEnvironment获取ConfigurableEnvironment对象。ConfigurableEnvironment是一个大多数Environment类型要实现的接口,提供了设置Active和Default的Profiles的方法, 允许客户端通过ConfigurablePropertyResolver来设置和验证所需的属性,自定义转换服务(conversion service),以及提供了获取属性源的方法。下面是ConfigurableEnvironment源码:
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
/**
* 设置Active的配置文件,任何现有的配置文件(profiles)都会被替换为参数中的配置文件(profiles)
*/
void setActiveProfiles(String... profiles);
/**
* 将参数配置文件(profile)添加到已激活的配置文件集(active profiles)中
*/
void addActiveProfile(String profile);
/**
* 没有其他配置文件通过setActiveProfiles显式激活,则指定默认情况下激活的配置文件集
*/
void setDefaultProfiles(String... profiles);
/**
* 获取可变形式的当前环境的PropertySources,从而能在解析环境时操作PropertySources,
* 如addFirst,addLast,addBefore,addAfter等方法。这样可以确保用户自定义的PropertySources的搜索优先级高于默认的属性源。
*/
MutablePropertySources getPropertySources();
/**
* 获取系统属性
*/
Map<String, Object> getSystemProperties();
/**
* 获取系统环境
*/
Map<String, Object> getSystemEnvironment();
/**
* 将给定父环境的活动配置文件、默认配置文件和属性源附加到此(子)环境各自的集合中
* 对于父实例和子实例中存在的任何同名PropertySource实例,子实例将被保留,父实例将被丢弃。
* 这样做的效果是允许子级重写属性源,并避免通过常见属性源类型(例如系统环境和系统属性)进行冗余搜索。
*/
void merge(ConfigurableEnvironment parent);
}
我们通过getOrCreateEnvironment源码发现,可返回的ConfigurableEnvironment对象有ApplicationServletEnvironment,ApplicationReactiveWebEnvironment,ApplicationEnvironment,他们分别是SERVLET、REACTIVE、NONE等WebApplicationType的的环境。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationServletEnvironment();
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
3、configureEnvironment方法,它是一个模板方法,按顺序委托给了configurePropertySources方法和configureProfiles方法。重写这个方法可以实现控制自定义环境,重写configurePropertySources或configureProfiles可以实现对属性源或配置文件进行细粒度控制。
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
//配置环境设置转换服务(默认情况下配置了适合大多数Spring Boot application的转换器和格式化程序。)
environment.setConversionService(new ApplicationConversionService());
}
//在应用程序环境中配置属性源(添加、或删除或重排序属性源)
configurePropertySources(environment, args);
//为应用环境配置active的配置文件,也可以在配置文件处理期间,通过 spring.profiles.active 激活其他配置文件
configureProfiles(environment, args);
}
4、ConfigurationPropertySources.attach(environment) 这一步将ConfigurationPropertySource附加到指定的ConfigurableEnvironment。
public static void attach(Environment environment) {
//提供的environment必须是ConfigurableEnvironment类型
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
//从ConfigurableEnvironment对象中获取MutablePropertySources(可变的属性源)对象
MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
//从MutablePropertySources根据PropertySource适配器的名称configurationProperties来获取附加的属性源
PropertySource<?> attached = getAttached(sources);
if (attached == null || !isUsingSources(attached, sources)) {
//初始化附加的属性源为ConfigurationPropertySourcesPropertySource对象,从而可以暴露ConfigurationPropertySource实例
//以便它们能与PropertyResolver一起使用或将其添加到环境中
attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
new SpringConfigurationPropertySources(sources));
}
//若MutablePropertySources对象的propertySourceList有configurationProperties,将其移除
sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
//将附加的名称为configurationProperties的属性源添加到propertySourceList的首部(即将其配置为最高优先级的属性源)。
sources.addFirst(attached);
}
5、listeners.environmentPrepared(bootstrapContext, environment) 这一步是通过SpringApplicationRunListeners来广播ApplicationEnvironmentPreparedEvent事件,表明SpringApplication启动且环境首次可供检查和修改,此时环境已准备好但是ApplicationContext容器还未创建。本文的重点也即配置文件的加载就在这一部分。Spring Boot中的监听器这部分源码出于篇幅原因我们就不追了,大家有兴趣可参考我的另一篇文章一文搞懂Spring Boot 事件监听机制。我们重点关注application.xml配置文件的加载部分。
当执行到SimpleApplicationEventMulticaster的doInvokeListener方法时实际进入的是EnvironmentPostProcessorApplicationListener中的onApplicationEvent方法。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent();
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
此时我们的事件是ApplicationEnvironmentPreparedEvent,因此进入onApplicationEnvironmentPreparedEvent方法。
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
//从ApplicationEnvironmentPreparedEvent 事件中获取ConfigurableEnvironment对象,该对象里包含了propertySourceList,我们重点关注该属性源列表中属性源的变化
ConfigurableEnvironment environment = event.getEnvironment();
//从ApplicationEnvironmentPreparedEvent 事件中获取SpringApplication 对象
SpringApplication application = event.getSpringApplication();
//在此会获取一系列EnvironmentPostProcessor,我们重点关注ConfigDataEnvironmentPostProcessor,ConfigDataEnvironmentPostProcessor该配置环境后置处理器会加载配置数据并将其应用到环境中
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
进入ConfigDataEnvironmentPostProcessor对象的postProcessEnvironment方法,在该方法中添加了配置数据的后处理环境。
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
}
catch (UseLegacyConfigProcessingException ex) {
this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
ex.getConfigurationProperty()));
configureAdditionalProfiles(environment, additionalProfiles);
postProcessUsingLegacyApplicationListener(environment, resourceLoader);
}
}
继续跟踪上述getConfigDataEnvironment方法获取配置数据的环境,在该方法中构建了ConfigDataEnvironment实例,
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles, this.environmentUpdateListener);
}
构建时初始化了一个重要的属性ConfigDataLoaders,初始化ConfigDataLoaders时初始化了ConfigDataLoader列表,ConfigDataLoader是一个接口提供了用来加载给定ConfigDataResource的配置数据的load方法;
public interface ConfigDataLoader<R extends ConfigDataResource> {
//返回此context是否可以加载指定资源resource
default boolean isLoadable(ConfigDataLoaderContext context, R resource) {
return true;
}
//从给定的resource加载配置数据
ConfigData load(ConfigDataLoaderContext context, R resource)
throws IOException, ConfigDataResourceNotFoundException;
}
ConfigDataEnvironment中包含了配置数据的加载位置
optional:classpath:/;optional:classpath:/config/
optional:file:./;optional:file:./config/;optional:file:./config/*/
执行完getConfigDataEnvironment方法获取到ConfigDataEnvironment后就进入了processAndApply方法,debug时重点关注OriginTrackedMapPropertySource {name=‘xxx’’}
void processAndApply() {
//创建一个ConfigDataImporter实例
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
this.loaders);
registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
//初始化ConfigDataEnvironmentContributors ,即在没有激活上下文的情况下处理初始化配置环境contributors
//初始化时通过ConfigDataImporter加载了配置文件[application.yml]
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
//创建ConfigDataActivationContext对象,从初始化的ConfigDataEnvironmentContributors创建配置数据并激活context
ConfigDataActivationContext activationContext = createActivationContext(
contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
//使用初始化的ConfigDataActivationContext处理ConfigDataEnvironmentContributors
contributors = processWithoutProfiles(contributors, importer, activationContext);
//从ConfigDataEnvironmentContributors中推导出配置文件,并返回带有特定配置文件的新ConfigDataActivationContext
activationContext = withProfiles(contributors, activationContext);
//使用ConfigDataActivationContext处理ConfigDataEnvironmentContributors,此时这一步到这一步会通过ConfigDataImporter加载配置文件[application-dev.yml]
contributors = processWithProfiles(contributors, importer, activationContext);
//在这一步会将获取的ActiveProfiles添加ConfigurableEnvironment环境中。
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
importer.getOptionalLocations());
}
综上所述应是在初始化ConfigDataEnvironmentContributors 时加载了application.yml配置文件,并从初始化的ConfigDataEnvironmentContributors 激活了上下文,通过激活的上下文来处理ConfigDataEnvironmentContributors,从ConfigDataEnvironmentContributors中推导出配置文件application.yml并返回带有application.yml的新的上下文ConfigDataActivationContext ,通过该上下文处理ConfigDataEnvironmentContributorsq,从而加载配置文件application-dev.yml。
6、DefaultPropertiesPropertySource.moveToEnd(environment) 这一步将PropertySource列表中的名称为defaultProperties属性源移到最后,使之成为ConfigurableEnvironment的最后一个源。保证最后优先用定义好的配置属性源。
7、绑定环境到SpringApplication。
8、ConfigurationPropertySources.attach(environment)将ConfigurationPropertySources附加到ConfigurableEnvironment。