前言
springboot在启动过程中需要加载一些系统的配置文件和用户自定义的配置文件。
- 系统的配置文件是在初始化Environment时加载。
- 用户自定配置文件是通过SpringBoot的消息广播机制由ConfigFileApplicationListener类完成加载的。ConfigFileApplicationListener首先在springBoot启动过程中通过SPI机制加载被实例化,然后监听ApplicationEnvironmentPreparedEvent事件进行配置文件加载,
其中配置文件加载涉及springBoot的SPI机制和消息广播机制,这两个点不展开说明,本文重点研究配置文件的加载。
一、springBoot启动涉及配置文件加载的流程
springBoot启动过程中配置文件加载部分流程如下:
二、系统配置文件加载
在实例化StandardServletEnvironment过程中会依次加载四个配置文件
- servletConfigInitParams
- servletContextInitParams
- systemProperties:jvm的系统配置
- systemEnvironment:操作系统的系统配置
代码如下:
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
public StandardServletEnvironment() {
}
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
propertySources.addLast(new StubPropertySource("servletContextInitParams"));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource("jndiProperties"));
}
super.customizePropertySources(propertySources);
}
}
public class StandardEnvironment extends AbstractEnvironment {
/** 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 PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
二、用户配置文件加载
1.事件触发过程
监听ApplicationEnvironmentPreparedEvent事件,然后内部最终是通过调用Loader进行配置文件加载。
具体流程如下:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
//1.如果是ApplicationEnvironmentPreparedEvent事件,调用次方法
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 2.将自己加入到处理列表中
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
// 3.调用postProcessEnvironment方法
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
@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);
// 4.生成loader并加载用户配置文件
new Loader(environment, resourceLoader).load();
}
2.加载具体过程
加载用户自定义文件需要知晓文件位置、文件名称和文件格式,其中配置文件的名称和格式默认约定为配置文件的格式为{name}-{profile}.后缀。springboot会把所有的配置文件加载到内存中,只是根据文件的顺序和查找配置的过程来实现配置在不同文件的优先级。
具体加载过程如下:
1.首先进行profile的计算,profiles队列中优先级由低到高
2.确定文件加载位置,加载位置优先级由高到低【根目录config文件夹下>根目录>类文件目录config文件夹>类文件目录】
3.确定文件名称
4.确定文件加载器,加载器优先级由高到低【同一个目录下 properties后缀文件优先级高于yml后缀文件】
5.加载文件并加入到loaded队列中
6.将loaded中内容反转【将profile优先级调整到正常】,然后将其中的配置文件加入到environment的PropertySources中。propertysources可以理解为一个持有所有配置文件的队列,从propertysources中取值,会根据key依次从每个文件中找key对应的value,如果有值则返回。
由以上加载过程就可以知道,在配置文件name相同的情况下,其他三个因素的优先级为:profile>文件位置>文件后缀。
代码如下:
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
// key为profile,value位配置文件,暂时存放每个profile对应的所有配置文件
this.loaded = new LinkedHashMap<>();
// 1.进行profile的初始化和排序,优先级由低到高
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
// 进行某个prifile文件的加载,并把文件内容依次放置到loaded队尾
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
// 进行无profile的文件的加载放到loaded队头
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
// 6.会将loaded队列反转【profile优先级高的在队头】,然后加入到environment的PropertySources中
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 2.按照文件位置依次加载文件->getSearchLocations()
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// 3.按照文件名称依次加载文件->getSearchNames()
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
// 4.根据文件加载器【文件后缀】一次加载文件
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
throw new IllegalStateException("File extension of config file location '" + location
+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
+ "a directory, it must end in '/'");
}
Set<String> processed = new HashSet<>();
// 4.根据文件加载器【文件后缀】一次加载文件
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
2.1 配置文件profile
/**
* Initialize profile information from both the {@link Environment} active
* profiles and any {@code spring.profiles.active}/{@code spring.profiles.include}
* properties that are already set.
*/
private void initializeProfiles() {
// The default profile for these purposes is represented as null. We add it
// first so that it is processed first and has lowest priority.
this.profiles.add(null);
Set<Profile> activatedViaProperty = getProfilesFromProperty(ACTIVE_PROFILES_PROPERTY);
Set<Profile> includedViaProperty = getProfilesFromProperty(INCLUDE_PROFILES_PROPERTY);
List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
this.profiles.addAll(otherActiveProfiles);
// Any pre-existing active profiles set via property sources (e.g.
// System properties) take precedence over those added in config files.
this.profiles.addAll(includedViaProperty);
addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) { // only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
2.1 文件加载位置和文件名称
private static final String DEFAULT_PROPERTIES = "defaultProperties";
// Note the order is from least to most specific (last one wins)
// 配置文件位置由此变量决定,加载文件时会按此变量的内容依次从对接位置加载
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
// 配置文件的默认名称
private static final String DEFAULT_NAMES = "application";
2.2 文件加载器
// 文件加载器,通过SPI机制加载文件加载器
private final List<PropertySourceLoader> propertySourceLoaders;
// springboot 中spring.factory配置了两个加载器
org.springframework.boot.env.PropertySourceLoader=\
// 负责加载properties和xml后缀配置文件
org.springframework.boot.env.PropertiesPropertySourceLoader,\
// 负责加载yml和yaml后缀配置文件
org.springframework.boot.env.YamlPropertySourceLoader
总结
本文主要是通过源码的阅读了解springboot针对配置文件的加载原理和处理过程,可以更好的理解关于配置文件中变量的优先级以及配置文件的使用。