实现了resources的加载工作
一句话概述:
ConfigFileApplicationListener加载file:./config,file:,classpath:/config,classpath:。这4个路径下的application命名的配置文件。
通过PropertiesPropertySourceLoader来加载后缀是"properties", "xml"的文件。
通过YamlPropertySourceLoader来加载后缀是"yml", "yaml"的文件。
所有加载的配置放在OriginTrackedMapPropertySource对象中,最后被封装成Document数组对象,随后获取到profile(spring:profiles:active:dev),加载对应的profile文件(如:application-dev.yaml),最终封装到ConfigurableEnvironment中返回,如下图
代码
SpringApplication
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建 ConfigurableEnvironment 对象,并进行配置
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 通知 SpringApplicationRunListener 的数组,环境变量已经准备完成。
listeners.environmentPrepared(environment);
// 绑定 environment 到 SpringApplication 上
bindToSpringApplication(environment);
// 如果非自定义 environment ,则根据条件转换
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
ConfigurationPropertySources.attach(environment);
return environment;
}
listeners.environmentPrepared(environment);
通知一下监听ConfigFileApplicationListener的onApplicationEvent方法
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 加载指定类型 EnvironmentPostProcessor 对应的,在 `META-INF/spring.factories` 里的类名的数组
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 加入自己
postProcessors.add(this);
// 排序 postProcessors 数组
AnnotationAwareOrderComparator.sort(postProcessors);
// 遍历 postProcessors 数组,逐个执行。
for (EnvironmentPostProcessor postProcessor : postProcessors) {
//@2
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
// 创建 PropertySourcesPlaceholdersResolver 对象
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
// 创建 DefaultResourceLoader 对象
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
// 加载指定类型 PropertySourceLoader 对应的,在 `META-INF/spring.factories` 里的类名的数组
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
}
public void load() {
// 初始化变量
this.profiles = new LinkedList<>(); // 未处理的 Profile 集合
this.processedProfiles = new LinkedList<>(); // 已处理的 Profile 集合
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 初始化 Spring Profiles 相关
initializeProfiles();
// 遍历 profiles 数组
while (!this.profiles.isEmpty()) {
// 获得 Profile 对象
Profile profile = this.profiles.poll();
// 添加 Profile 到 environment 中
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
// 加载配置
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
// 添加到 processedProfiles 中,表示已处理
this.processedProfiles.add(profile);
}
// 获得真正加载的 Profile 们,添加到 environment 中。
resetEnvironmentProfiles(this.processedProfiles);
// 加载配置
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
// 将加载的配置对应的 MutablePropertySources 到 environment 中
addLoadedPropertySources();
}
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 获得要检索配置的路径
getSearchLocations().forEach((location) -> {
// 判断是否为文件夹
boolean isFolder = location.endsWith("/");
// 获得要检索配置的文件名集合
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) {
// 默认情况下,name 为 DEFAULT_NAMES=application
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
Set<String> processed = new HashSet<>(); // 已处理的文件后缀集合
// 遍历 propertySourceLoaders 数组,逐个使用 PropertySourceLoader 读取配置
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 遍历每个 PropertySourceLoader 可处理的文件后缀集合
for (String fileExtension : loader.getFileExtensions()) {
// 添加到 processed 中,一个文件后缀,有且仅能被一个 PropertySourceLoader 所处理
if (processed.add(fileExtension)) {
// 加载 Profile 指定的配置文件(带后缀)
loadForFileExtension(loader, location + name, "." + fileExtension,
profile, filterFactory, consumer);
}
}
}
}
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
String fileExtension, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 获得 DocumentFilter 对象
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
// 加载 Profile 指定的配置文件(带后缀)。
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
// 加载 Profile 指定的配置文件(带后缀)。
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer); // 情况一,未配置 spring.profile 属性
load(loader, profileSpecificFile, profile, profileFilter, consumer); // 情况二,有配置 spring.profile 属性
// Try profile specific sections in files we've already processed
// 特殊情况,之前读取 Profile 对应的配置文件,也可被当前 Profile 所读取。
// 举个例子,假设之前读取了 Profile 为 common 对应配置文件是 application-common.properties ,里面配置了 spring.profile=dev,prod
// 那么,此时如果读取的 Profile 为 dev 时,也能读取 application-common.properties 这个配置文件
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension; // 拼接之前的配置文件名
load(loader, previouslyLoaded, profile, profileFilter, consumer); // 注意噢,传入的 profile 是当前的 profile
}
}
}
// Also try the profile-specific section (if any) of the normal file
// 加载(无需带 Profile)指定的配置文件(带后缀)。
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, DocumentConsumer consumer) {
try {
// 判断指定的配置文件是否存在。若不存在,则直接返回
Resource resource = this.resourceLoader.getResource(location);
if (!resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource, profile);
this.logger.trace(description);
}
return;
}
// 如果没有文件后缀的配置文件,则忽略,不进行读取
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped empty config extension ", location, resource, profile);
this.logger.trace(description);
}
return;
}
// 加载配置文件,返回 Document 数组
String name = "applicationConfig: [" + location + "]";
List<Document> documents = loadDocuments(loader, name, resource);
// 如果没加载到,则直接返回
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription(
"Skipped unloaded config ", location, resource, profile);
this.logger.trace(description);
}
return;
}
// 使用 DocumentFilter 过滤匹配的 Document ,添加到 loaded 数组中。
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
if (filter.match(document)) { // 匹配
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
// 使用 DocumentConsumer 进行消费 Document ,添加到本地的 loaded 中。
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
this.logger.debug(description);
}
}
} catch (Exception ex) {
throw new IllegalStateException("Failed to load property "
+ "source from location '" + location + "'", ex);
}
}