@TOC
一、综述
上一节分析了环境上下文准备整体逻辑逻辑,留下来了一个点就是SpringApplication发布ApplicationEnvironmentPreparedEvent事件,相关监听器收到事件执行响应,其中有一个监听器执行的逻辑很复杂,也很重要,它就是ConfigFileApplicationListener。
ConfigFileApplicationListener是我们springboot应用初始化外部配置然后加载到环境上下文对象里面来的核心逻辑。包括有名的appollo都是通过这个listener加载配置到应用中来的。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
onApplicationEvent这个方法就是监听器执行逻辑的入口,由于这里是ApplicationEnvironmentPreparedEvent,所以进入到这个分支:
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
//加载所有的postProcessors
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
//循环执行每一个processor的逻辑
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
1、加载processors
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
}
这个代码已经很熟悉了都分析了很多次了,这里简单说一下,它就是通过SpringFactoriesLoader把项目里面所有spring.factories文件中的实现了EnvironmentPostProcessor接口的类加载进jvm并实例化,然后返回
2、循环执行每一个processor的逻辑
系统里面加载了多少processor呢?我通过debug打一个断点来看看就知道了:
一般来说有四个processor,如果依赖了appollo,就还应该有一个processor,它就是ApolloApplicationContextInitializer,这里我就不解析apollo的这个processor了,有兴趣的朋友可以自己去阅读相关源码,思想都类似。
1、SystemEnvironmentPropertySourceEnvironmentPostProcessor
二、SystemEnvironmentPropertySourceEnvironmentPostProcessor逻辑分析
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// 这个sourceName的值是‘systemEnvironment’
String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
//这个PropertySource上节中分析过,根据名称‘systemEnvironment’ 获取对应的PropertySource
PropertySource<?> propertySource = environment.getPropertySources()
.get(sourceName);
if (propertySource != null) {
//替换原来的值
replacePropertySource(environment, sourceName, propertySource);
}
}
这个方法到底做了什么事情?
先进入replacePropertySource方法,只要搞清楚了它做了什么,几乎就可以了解这个方法到底要做什么了
private void replacePropertySource(ConfigurableEnvironment environment,
String sourceName, PropertySource<?> propertySource) {
// 获取source(source是一个泛型,这里是一个map)
Map<String, Object> originalSource = (Map<String, Object>) propertySource
.getSource();
// 从新包装之前的source
SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(
sourceName, originalSource);
//用包装后的PropertySource把原来环境上下文中名称为'systemEnvironment'的PropertySource替换掉
environment.getPropertySources().replace(sourceName, source);
}
其实这个从新包装,没有做任何其他事情,就是用了一个新的功能更强大的子类来包装了原来的数据,如果大家有兴趣可以去读源码,我这里就不在赘述。
至此,SystemEnvironmentPropertySourceEnvironmentPostProcessor的所有逻辑就分析完了。
三、SpringApplicationJsonEnvironmentPostProcessor源码解析
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
StreamSupport.stream(propertySources.spliterator(), false)
.map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
.ifPresent((v) -> processJson(environment, v));
}
这个方法只有两行代码,核心逻辑在第二行的processJson方法里面。
private void processJson(ConfigurableEnvironment environment,
JsonPropertyValue propertyValue) {
try {
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(propertyValue.getJson());
if (!map.isEmpty()) {
addJsonPropertySource(environment,
new JsonPropertySource(propertyValue, flatten(map)));
}
}
catch (Exception ex) {
logger.warn("Cannot parse JSON for spring.application.json: "
+ propertyValue.getJson(), ex);
}
}
这个方法的代码逻辑都很简单,就是把配置中json格式的字符串解析后放入到ConfigurableEnvironment。注意其中有行代码是把json解析成了map封装到了JsonPropertySource对象里面,这个对象里面有很多类似于map的方法,可以直接根据key获取到对应的值。
总结起来说就是 SpringApplicationJsonEnvironmentPostProcessor就是把我们配置的spring.application.json属性的值通过json解析放到了ConfigurableEnvironment对象里面,后续可以很方便的通过json字段直接获取到字段对应的值。
至此,SpringApplicationJsonEnvironmentPostProcessor的源码解析就结束了。
四、ConfigFileApplicationListener源码解析
第三个processor由于我们很少用到,我这儿就不做源码分析,而且它的源码也很简单,大家可以自行阅读,直接解析ConfigFileApplicationListener。
ConfigFileApplicationListener就是监听器本身,它既实现了listener接口,又实现了processor接口。
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
//添加随机属性,在生产开发中根本用不到,就不详细分析了 RandomValuePropertySource.addToEnvironment(environment);
//加载配置文件中的属性
new Loader(environment, resourceLoader).load();
}
直接进入到load方法
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 初始化profiles
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
//把profile加入到环境中 addProfileToEnvironment(profile.getName());
}
// 根据profile加载配置
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
//从新设置环境的profile
resetEnvironmentProfiles(this.processedProfiles);
//加载null profile的属性
load(null, this::getNegativeProfileFilter,
//把加载过的属性都添加到列表中
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
}
这个方法主要做的事情其实就是,根据你配置的profile,加载环境下所有符合条件的属性和属性值,当然这里面也包含空profile,空profile相当于application.properties,所以我们进入这个load方法
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));
});
}
先进入getSearchLocations()
这个方法看看到底是得到了什么集合
private Set<String> getSearchLocations() {
// 是否存在‘spring.config.location’属性值
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(
CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
return locations;
}
// 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/";
/**
* The "config additional location" property name.
*/
public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
这个方法大致要做的事情就是:
1、如果环境中配置了‘spring.config.location’的值,就到这个配置值所对应的目录去找配置文件
2、如果不存在这个配置,就再找到‘spring.config.additional-location’这个配置的值所对应目录找配置文件
3、把‘classpath:/,classpath:/config/,file:./,file:./config/’这些目录下的对应配置文件路径加入到locations中返回。
所以综上,就是在所有符合条件的路径下找到所有的文件,然后用于循环匹配符合条件的文件。这个匹配逻辑和文件加载逻辑就在循环体内,我们只要搞清楚匹配规则和加载逻辑,加载顺序。这个配置文件解析的逻辑就全部搞清楚了
1、匹配逻辑
很显然,这里的所有locations都应该是文件夹,所以都会进入到getSearchNames()这个方法:
private Set<String> getSearchNames() {
//在环境中找'spring.config.name'属性的值
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
return asResolvedSet(property, null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
一般来说我们都没有配置‘spring.config.name’这个属性,所以会走默认配置文件名的逻辑:
所以springboot为啥会默认找名字为application的配置文件了吧?
2、加载配置文件
进入到load(location, name, profile, filterFactory, consumer));
private void load(String location, String name, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
//name肯定有值,不走这个分支
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<>();
//不同的SourceLoader加载不同文件结尾的文件,这里有两个sourceLoader,一个加载properties,一个加载yaml文件,下面我有对应的截图
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
loadForFileExtension(loader, location + name, "." + fileExtension,
profile, filterFactory, consumer);
}
}
}
}
进入到loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer);
这个方法看个究竟吧:
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
String fileExtension, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) {
// Try profile-specific file & profile section in profile file (gh-340)
String profileSpecificFile = prefix + "-" + profile + fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile
+ fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
// Also try the profile-specific section (if any) of the normal file
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
上图是我debug调试的时候的截图,大家看明白了吗?
如果profile不为空,那么程序会加载我们的配置文件名是这样拼接出来的。
后面就是通过resource把文件通过流读入内存加载到环境上下文中,后面的逻辑就不再解析了。
至此,ConfigFileApplicationListener的processor逻辑就解析完成了
总结
springboot发布ApplicationEnvironmentPreparedEvent事件后,对应的所有监听器处理的逻辑非常重要,是springboot配置环境上下文环境配置变量的关键。它主要做了以下事情:
1、SystemEnvironmentPropertySourceEnvironmentPostProcessor把系统属性用一个新的类包装了以下然后替换了原来的属性
2、SpringApplicationJsonEnvironmentPostProcessor就是把我们配置的spring.application.json属性的值通过json解析放到了ConfigurableEnvironment对象里面,后续可以很方便的通过json字段直接获取到字段对应的值
3、ConfigFileApplicationListener就是把系统中的配置文件的属性值全部读取到ConfigurableEnvironment对象中,以便后续好使用