1:说明
该类定义如下:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {}
其中父接口比较关键的是org.springframework.boot.env.EnvironmentPostProcessor
,org.springframework.context.event.SmartApplicationListener
,因此ConfigFileApplicationListener是有两个角色的。
2:SmartApplicationListener子类时
2.1:onApplicationEvent
源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {
// ApplicationEnvironmentPreparedEvent:spring环境准备完毕对应的事件
if (event instanceof ApplicationEnvironmentPreparedEvent) {
// <202106131132>
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
// ApplicationPreparedEvent:spring容器初始化完毕对应的事件
if (event instanceof ApplicationPreparedEvent) {
// <202106131133>
onApplicationPreparedEvent(event);
}
}
<202106131132>
处是处理spring环境准备完毕事件,具体参考2.1.1:onApplicationEnvironmentPreparedEvent
,<202106131133>
处是处理spring容器初始化完毕事件,具体参考2.1.2:onApplicationPreparedEvent
。
2.1.1:onApplicationEnvironmentPreparedEvent
该方法会在spring环境准备完毕的时候进行调用,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// <202106141002>
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 因为自己也是一个EnvironmentPostPocessor,这里也添加自己
postProcessors.add(this);
// 排序
AnnotationAwareOrderComparator.sort(postProcessors);
// 循环,挨个调用postProcessEnvironment执行
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
<202106141002>
处是获取通过springboot SPI,即META-INF/spring.factories
文件配置的EnvironmentPostProcessor实例,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#loadPostProcessors
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
在META-INF/spring.factories
文件中配置的如下图:
获取结果如下图:
可以看到和配置文件中配置的是一样的。
关于这些EnvironmentPostProcessor的实现类,我们单起一部分讲解,具体参考3:EnvironmentPostProcessor的实现类们
。
2.1.2:onApplicationPreparedEvent
该方法会在spring容器准备完毕的时候进行调用,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationPreparedEvent
private void onApplicationPreparedEvent(ApplicationEvent event) {
// 不知道干啥的,先无视!
this.logger.switchTo(ConfigFileApplicationListener.class);
// <202106141043>
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
<202106141043>
处是添加PropertySourceOrderingPostProcessor
,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#addPostProcessors
protected void addPostProcessors(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new PropertySourceOrderingPostProcessor(context));
}
添加的BeanFactoryPostProcessor‘会在加载bean定义到bean工厂完毕后,通过bean定义生成spring bean之前进行调用,执行逻辑如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.PropertySourceOrderingPostProcessor#postProcessBeanFactory
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// <202106141113>
reorderSources(this.context.getEnvironment());
}
<202106141113>
处源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.PropertySourceOrderingPostProcessor#reorderSources
private void reorderSources(ConfigurableEnvironment environment) {
// 删除key为默认属性private static final String DEFAULT_PROPERTIES = "defaultProperties";
// 对应的属性源,删除信息作为返回值返回
PropertySource<?> defaultProperties = environment.getPropertySources().remove(DEFAULT_PROPERTIES);
// 如果有,重新添加到尾部,添加到尾部的原因应该是降低其优先级吧
if (defaultProperties != null) {
// <202106141116>
environment.getPropertySources().addLast(defaultProperties);
}
}
<202106141116>
处是将默认属性源defaultProperties添加到属性源集合的尾部,其中defaultProperties定义如下:
org.springframework.boot.SpringApplication#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));
}
...snip...
}
3:EnvironmentPostProcessor的实现类们
3.1:EnvironmentPostProcessor
在Environment加载完毕之后调用,如果使我们需要在该节点执行一些配置的话就可以通过该接口实现。
源码如下:
// 在ApplicationContext刷新之前对environment进行定制,具体的是实现类,
// 通过该接口的全限定名称作为key已经配置到了META-INF/spring.factories
// 中,另外如果是希望控制多个实现类执行的顺序的话可以实现Ordered,或者是
// @Order注解
@FunctionalInterface
public interface EnvironmentPostProcessor {
// environment:需要后置处理的Environment
// application:Environment所属的SpringApplication应用程序对象
void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
3.2:SpringApplicationJsonEnvironmentPostProcessor
该类的作用是读取通过spring.application.json
,或者是SPRING_APPLICATION_JSON
配置的json格式的数据转换成Map为底层数据源的PropertySource,并添加到Environment的PropertySources中,需要注意的是,这种方式配置的优先级高于系统属性
,源码如下:
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor#postProcessEnvironment
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 获取所有的属性源
MutablePropertySources propertySources = environment.getPropertySources();
// <202106141824>
propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
.ifPresent((v) -> processJson(environment, v));
}
<202106141824>
处propertySources.stream().map(JsonPropertyValue::get)
是遍历所有的PropertySource,调用org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue#get
方法,源码如下:
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor.JsonPropertyValue#get
public static JsonPropertyValue get(PropertySource<?> propertySource) {
// public static final String SPRING_APPLICATION_JSON_PROPERTY = "spring.application.json";
// public static final String SPRING_APPLICATION_JSON_ENVIRONMENT_VARIABLE = "SPRING_APPLICATION_JSON";
// private static final String[] CANDIDATES = { SPRING_APPLICATION_JSON_PROPERTY, SPRING_APPLICATION_JSON_ENVIRONMENT_VARIABLE };
// 遍历候选数组,找到合适的目标值
for (String candidate : CANDIDATES) {
Object value = propertySource.getProperty(candidate);
// 值为string,并且有内容
if (value instanceof String && StringUtils.hasLength((String) value)) {
// 封装为JsonPropertyValue对象,返回
return new JsonPropertyValue(propertySource, candidate, (String) value);
}
}
// 无合适候选,返回null
return null;
}
<202106141824>
处filter(Objects::nonNull)
是过滤propertySources.stream().map(JsonPropertyValue::get)
处理后结果为null的元素,最后调用org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor#processJson
方法处理数据,源码如下:
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor#processJson
private void processJson(ConfigurableEnvironment environment, JsonPropertyValue propertyValue) {
// 获取json解析器
JsonParserparser = JsonParserFactory.getJsonParser();
// 转成map
Map<String, Object> map = parser.parseMap(propertyValue.getJson());
// 不为空,则创建JsonPropertySource对象,添加到Environment中
if (!map.isEmpty()) {
addJsonPropertySource(environment, new JsonPropertySource(propertyValue, flatten(map)));
}
}
4:ConfigFileApplicationListener
我们知道ConfigFileApplicationListener实现了EnvironmentPostProcessor接口,本部分分析的就是作为EnvironmentPostProcessor时执行的逻辑,也是本文的核心,即配置文件信息的加载
。因此调用的方法是org.springframework.boot.context.config.ConfigFileApplicationListener#postProcessEnvironment
,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#postProcessEnvironment
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// <202106151329>
addPropertySources(environment, application.getResourceLoader());
}
<202106151329>
处源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener#addPropertySources
// 添加配置文件数据源到environment中
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// <202106151514>
RandomValuePropertySource.addToEnvironment(environment);
// <202106151523>
new Loader(environment, resourceLoader).load();
}
<202106151514>
处是设置随机数数据源,关于springboot随机数的使用可以参考这里,源码如下:
org.springframework.boot.env.RandomValuePropertySource#addToEnvironment
public static void addToEnvironment(ConfigurableEnvironment environment) {
// 添加随机值数据源RandomValuePropertySource
environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
logger.trace("RandomValuePropertySource add to Environment");
}
关于随机值数据源RandomValuePropertySource具体参考4.1:随机值数据源RandomValuePropertySource
。
<202106151523>
是执行具体的加载配置文件的信息,具体参考5:Loader加载配置文件信息
。
4.1:随机值数据源RandomValuePropertySource
看下获取随机值代码:
org.springframework.boot.env.RandomValuePropertySource#getProperty
@Override
public Object getProperty(String name) {
// 必须以private static final String PREFIX = "random.";开头
if (!name.startsWith(PREFIX)) {
return null;
}
// trace级别日志打印
if (logger.isTraceEnabled()) {
logger.trace("Generating random property for '" + name + "'");
}
// <202106151533>
// name.substring(PREFIX.length())截取获取要生成的随机值的类型,如random.int,结果就是int
return getRandomValue(name.substring(PREFIX.length()));
}
<202106151533>
处源码如下:
org.springframework.boot.env.RandomValuePropertySource#getRandomValue
private Object getRandomValue(String type) {
// 如random.int
if (type.equals("int")) {
return getSource().nextInt();
}
// 如random.long
if (type.equals("long")) {
return getSource().nextLong();
}
// 如random.int(10),小于10的数字
// 如random.int[1024,65536],大于等于1024,小于等于65536的数字
String range = getRange(type, "int");
// 获取某范围内的正整数
if (range != null) {
return getNextIntInRange(range);
}
// 如random.long(10),小于10的数字
// 如random.long[1024,65536],大于等于1024,小于等于65536的数字
range = getRange(type, "long");
// 获取某范围内的正整数
if (range != null) {
return getNextLongInRange(range);
}
// 如random.uuid
// 生成uuid
if (type.equals("uuid")) {
return UUID.randomUUID().toString();
}
// 如random.value
// 生成随机字符串
return getRandomBytes();
}
getSource()
返回的数据源是java.util.Random
。
5:Loader加载配置文件信息
5.1:构造函数
源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#Loader
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
// 环境
this.environment = environment;
// 创建用于替换占位符的类PropertySourcesPlaceholdersResolver
// 如${foo.bar}生成foo.bar
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
// 创建用于加载资源的类DefaultResourceLoader,用于加载配置文件
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
// 从META-INF/spring.factories获取资源加载类,如下配置
/*
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
一个用于加载.properties配置的文件,一个用于加载.yaml配置的文件
*/
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
5.2:load
源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load()
public void load() {
// 还没有处理的profiles,因为存在同时激活多个profile的场景,所以需要依次处理
// 比如profile=foo,则对应的配置文件可以是application-foo.properties/yaml/yml
// 比如profile=bar,则对应的配置文件可以是appplication-bar.properties/yaml/yml
this.profiles = new LinkedList<>();
// 已经处理过的profiles
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// <202106151646>
// 初始化profile
initializeProfiles();
// 循环
while (!this.profiles.isEmpty()) {
// 弹出栈顶Profile
Profile profile = this.profiles.poll();
// 不为空,且不是默认的profile
if (profile != null && !profile.isDefaultProfile()) {
// 添加到org.springframework.core.env.AbstractEnvironment#activeProfiles
addProfileToEnvironment(profile.getName());
}
// <202106151820>
// 有profile,使用MutablePropertySources::addLast添加到尾部,反转后具有高优先级
// 有点绕,通篇读完,这里应该能懂
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
// 添加到已经处理的profile集合中
this.processedProfiles.add(profile);
}
// 按照处理profile的顺序重新设置environment的activeProfiles数组,保证
// 其值和处理profile的顺序是一致的,可能是和优先级有关系,这里先知道吧
resetEnvironmentProfiles(this.processedProfiles);
// 无profile,但是配置文件配置了sprin.profiles的情况,通过this::getNegativeProfileFilter:
/*
private DocumentFilter getNegativeProfileFilter(Profile profile) {
return (Document document) -> (profile == null && !ObjectUtils.isEmpty(document.getProfiles())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles())));
}
*/
// 完成过滤,通过MutablePropertySources::addFirst添加到加载的配置文件结果集的头部(反转后优先级低)
// 有点绕,通篇读完,这里应该能懂
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
// 添加配置文件对应的属性源到总的属性源集合中,最终会在尾部追加,源码如下:
/*
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#addLoadedPropertySources
private void addLoadedPropertySources() {
MutablePropertySources destination = this.environment.getPropertySources();
List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
Collections.reverse(loaded);
String lastAdded = null;
Set<String> added = new HashSet<>();
for (MutablePropertySources sources : loaded) {
for (PropertySource<?> source : sources) {
if (added.add(source.getName())) {
addLoadedPropertySource(destination, lastAdded, source);
lastAdded = source.getName();
}
}
}
}
*/
// 加载后可能如下(设置了-Dspring.profiles.active=test,test1)
/*
0 = {ConfigurationPropertySourcesPropertySource@2619} "ConfigurationPropertySourcesPropertySource {name='configurationProperties'}"
1 = {PropertySource$StubPropertySource@2620} "StubPropertySource {name='servletConfigInitParams'}"
2 = {PropertySource$StubPropertySource@2621} "StubPropertySource {name='servletContextInitParams'}"
3 = {PropertiesPropertySource@2622} "PropertiesPropertySource {name='systemProperties'}"
4 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource@2623} "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}"
5 = {RandomValuePropertySource@2624} "RandomValuePropertySource {name='random'}"
6 = {OriginTrackedMapPropertySource@2656} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application-test1.properties]'}"
7 = {OriginTrackedMapPropertySource@2681} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application-test.properties]'}"
8 = {OriginTrackedMapPropertySource@2710} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}"
*/
addLoadedPropertySources();
}
<202106151646>
处是初始化profile相关信息,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#initializeProfiles
private void initializeProfiles() {
// null,对应application.properties/yaml文件,首先添加的目的是最先被处理,具有最低的优先级
// 如在application.properties文件中配置server.port=8081,spring.profiles.active=test
// 在application-test.properties文件中配置server.port=8082,此时端口号最终会使用8082
this.profiles.add(null);
// <202106151706>
// 获取所有设置激活的profile
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
// 添加其他来源的profile,如SpringAplication#additionalProfiles,知道即可
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
// <202106151751>
// 添加设置激活的profiles
addActiveProfiles(activatedViaProperty);
// size==1,说明是只有为null的,即没有显示设置的激活profile,则尝试提添加默认的
// 的激活profile
if (this.profiles.size() == 1) {
// 默认只有一个default的profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
<202106151706>
处是获取所有设置激活的profile,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getProfilesActivatedViaProperty
private Set<Profile> getProfilesActivatedViaProperty() {
// 如果是即不包含spring.profiles.active也不包含spring.profiles.include,则直接返回空集合
if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
return Collections.emptySet();
}
// 获取封装属性源迭代器和占位符替换对象的Binder对象
Binder binder = Binder.get(this.environment);
Set<Profile> activeProfiles = new LinkedHashSet<>();
// 添加spring.profiles.include配置的
// 如配置了-Dspring.profiles.active=test,foo,bar -Dspring.profiles.include=xx,yy
// 则以下两行代码执行后activeProfiles的结果是:
/*
0 = {ConfigFileApplicationListener$Profile@2568} "xx"
1 = {ConfigFileApplicationListener$Profile@2569} "yy"
2 = {ConfigFileApplicationListener$Profile@2570} "test"
3 = {ConfigFileApplicationListener$Profile@2571} "foo"
4 = {ConfigFileApplicationListener$Profile@2572} "bar"
*/
activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
return activeProfiles;
}
<202106151751>
处源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#addActiveProfiles
void addActiveProfiles(Set<Profile> profiles) {
// 无,直接return
if (profiles.isEmpty()) {
return;
}
// 已经激活过,不再重复激活
if (this.activatedProfiles) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied");
}
return;
}
// 添加到未处理profile集合中
this.profiles.addAll(profiles);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles));
}
// 修改标记
this.activatedProfiles = true;
// 删除默认的profile,源码如下:
/*
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#removeUnprocessedDefaultProfiles
private void removeUnprocessedDefaultProfiles() {
this.profiles.removeIf((profile) -> (profile != null && profile.isDefaultProfile()));
}
*/
removeUnprocessedDefaultProfiles();
}
<202106151820>
处是执行具体的加载操作,具体参考5.3:load(profile, filterFactory, consumer)
。
5.3:load(profile, filterFactory, consumer)
源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load(org.springframework.boot.context.config.ConfigFileApplicationListener.Profile, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilterFactory, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentConsumer)
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// <202106161316>
// 从指定路径下搜索指定名称(一般是application)的配置文件,一般getSearchLocations()返回的都是
// 默认的路径集合org.springframework.boot.context.config.ConfigFileApplicationListener#DEFAULT_SEARCH_LOCATIONS
// private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
// 因为我们一般也不会进行指定,所以这里我们可以默认结果就是这个
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// <202106161602>
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
// <202106161621>
// 循环处理指定路径下,指定名称,指定profile的配置文件
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
});
}
<202106161316>
处getSearchLocations()
是获取搜索的路径,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchLocations()
private Set<String> getSearchLocations() {
// <202106161326>
// 获取通过public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
// 配置的路径,如果有的话
if(this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
// <202106161327>
// 获取通过public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
// 配置的路径
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
return locations;
}
<202106161326>
,和<202106161327>
处都是通过org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchLocations(java.lang.String)
方法获取要搜索的路径,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchLocations(java.lang.String)
private Set<String> getSearchLocations(String propertyName) {
// 结果路径集合
Set<String> locations = new LinkedHashSet<>();
// 环境中包含属性
if (this.environment.containsProperty(propertyName)) {
// <202106161337>
// 获取所有的路径,并循环处理
for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) {
if (!path.contains("$")) {
// 转换路径,如处理相对路径“..”,"./",“路径分割符”等
// 如“d:\\test1”,会被转换为"d://test1"
path = StringUtils.cleanPath(path);
// 不能以”classpath*:“开头
Assert.state(!path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX),
"Classpath wildard patterns cannot be used as a search location");
// 如果不是url
if (!ResourceUtils.isUrl(path)) {
// 添加file:前缀
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
// 添加路径
locations.add(path);
}
}
return locations;
}
<202106161337>
处asResolvedSet方法是处理配置的数据为set集合,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#asResolvedSet
private Set<String> asResolvedSet(String value, String fallback) {
// 将逗号分隔字符串转成数组->对数组中的每个元素做去除前后空格处理->将数组转成List集合
List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
(value != null) ? this.environment.resolvePlaceholders(value) : fallback)));
// 反转元素,这里反转元素是因为默认配置在后面的优先级高,如果需要配置时要注意这点
Collections.reverse(list);
// 转Set集合并返回
return new LinkedHashSet<>(list);
}
<202106161602>
处是获取要检索的配置文件名字的集合,一般是默认的application
,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchNames
private Set<String> getSearchNames() {
// 如果是包含public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
// 获取spring.config.name配置的属性值
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
// 逗号分隔字符串转set
return asResolvedSet(property, null);
}
// ConfigFileApplicationListener.this.names相当于this.names,一般为null
// DEFAULT_NAMES:private static final String DEFAULT_NAMES = "application";
// 执行到这里话的结果一般都是[application]
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
<202106161621>
处是执行加载具体配置文件的工作,具体参考5.4:load(location, name, profile, filterFactory, consumer)
。
5.4:load(location, name, profile, filterFactory, consumer)
源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load(java.lang.String, java.lang.String, org.springframework.boot.context.config.ConfigFileApplicationListener.Profile, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilterFactory, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentConsumer)
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// 这里不为true,所以忽略
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
// 已经处理过的文件后缀,因为实际项目中到底使用什么后缀的配置文件是不确定,比如可能同时存在
// “.yaml”,”.yml”,”.properties”,因为不同的开发人员有不同的习惯,所以这种情况很普遍
Set<String> processed = new HashSet<>();
// this.propertySourceLoaders一般有用于加载.properties文件的PropertiesPropertySourceLoader,以及用于加载.yaml文件的YamlPropertySourceLoader
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// 获取资源加载类可以加载的资源后缀,不同的资源加载类和其能够加载的文件后缀关系如下
// PropertiesPropertySourceLoader -> “.properties”,”.xml”
// YamlPropertySourceLoader -> “.yml”,”.yaml”
for (String fileExtension : loader.getFileExtensions()) {
// processed.add(fileExtension):该后缀不存在才继续
if (processed.add(fileExtension)) {
// <202106161756>
// 确定文件了,执行加载
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
<202106161756>
处是执行具体的配置文件加载,参考5.5:loadForFileExtension
。
5.5:loadForFileExtension
确定了配置文件的配置,名称,扩展名,profile,执行具体的加载,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadForFileExtension
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// <202106171711>
// 获取org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilter#match中使用到的profile值为null的匿名内部DocumentFilter类,可以理解为创建对象时,预先为方法中的参数传值
// 这个对象只有在prifle不为null时才有用,主要用于判断当前profile是否和文件内部
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
// <202106171712>
// 获取org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilter#match中使用到的profile值为profile的匿名内部DocumentFilter类,可以理解为创建对象时,预先为方法中的参数传值
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
// profile不为null,如-Dspring.profiles.active=test,设置的test,就是这里处理
if (profile != null) {
// 路径+文件名,如“classpath:/application-test.properties”
String profileSpecificFile = prefix + "-" + profile + fileExtension;
// <202106181801>
// defaultFilter,默认在配置文件中没有设置spring.profiles情况下加载
// (正常这里会加载到,因为我们一般不会设置spring.profiles),以下代码
/*
return (Document document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
}
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
};
中的if (profile == null)会为true,而我们有没有设置spring.profiles,所以ObjectUtils.isEmpty(document.getProfiles())为true,最终return true
*/
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
// profileFilter,默认配置文件中配置了spring.profiles情况下加载()
// (正常这里不会加载到,因为我们一般不会设置spring.profiles)
// 同上注释,不过最终执行到如下代码:
/*
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));,因为没有设置
spring.profiles,所以结果为false
*/
load(loader, profileSpecificFile, profile, profileFilter, consumer);
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
}
}
}
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
<202106171711>
和<202106171712>
处是获取用于判断配置文件是否需要加载的对象DocumentFilter,其中filterFactory
通过前面的代码可以知道是通过lamda表达式this::getPositiveProfileFilter
定义的,相当于如下代码:
new ConfigFileApplicationListener.DocumentFilterFactory() {
@Override
public DocumentFilter getDocumentFilter(Profile profile) {
return new ConfigFileApplicationListener().getPositiveProfileFilter(profile);
}
}
因此这里获取DocumentFilter调用的方式是org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getPositiveProfileFilter
,接下来看下该方法,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getPositiveProfileFilter
private DocumentFilter getPositiveProfileFilter(Profile profile) {
/*
new DocumentFilter() {
@Override
public boolean match(Document document) {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
}
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
}
};
*/
return (Document document) -> {
// 如果是profile为空,则要求
if (profile == null) {
// 这里document.getProfiles(),取自当前文档spring.profiles配置的值,
// 如在文档中配置“spring.profiles=dev11111111,dev2222222”,则这里的结果就是
/*
0 = "dev11111111"
1 = "dev2222222"
*/
return ObjectUtils.isEmpty(document.getProfiles());
}
// document.getProfiles():在当前文档中通过spring.profiles配置的值
// profile.getName():当前正在处理的profile
// this.environment.acceptsProfiles(Profiles.of(document.getProfiles())):判断environment.activeProfiles是否包含文档中通过spring.profiles配置的值
// 其中environment.activeProfiles就是通过"spring.profiles.active","spring.profiles.include"等配置的值
// 因此此时profile有如下3部分
// 1:通过"spring.profiles.active","spring.profiles.include"等配置的值,存储在environment.activeProfiles中
// 2:文档中通过"spring.profiles"配置的值
// 3:当前处理的profile
// 因此,这里的意思就是
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
};
}
<202106181801>
方法处源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load(org.springframework.boot.env.PropertySourceLoader, java.lang.String, org.springframework.boot.context.config.ConfigFileApplicationListener.Profile, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilter, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentConsumer)
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
try {
// 使用资源加载器从指定路径加载文件资源
Resource resource = this.resourceLoader.getResource(location);
// 对应的文件资源不存在,比如配置了-Dspring.active.profiles=test,但是
// 没有提供application-test.properties/yml文件
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
// 打印配置文件缺失日志,注意不会报错,仅仅是日志提示,而且还是trace级别的
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;
}
// 结果值如:applicationConfig: [classpath:/application-test.properties]
String name = "applicationConfig: [" + location + "]";
// <202106191429>,此处正常都是一个,可以默认是一个
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;
}
// 已经加载的文档
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
// 如果是文档需要加载,其中的filter有两个类
// this::getNegativeProfileFilter,this::getPositiveProfileFilter
// 过滤详细过程在上面已经说明,有疑惑的朋友往上翻再看看
if (filter.match(document)) {
// 在配置文件中通过spring.profiles.active配置的要激活的profile
addActiveProfiles(document.getActiveProfiles());
// 在配置文件中通过spring.profiles.include配置的要激活的profile
addIncludedProfiles(document.getIncludeProfiles());
// 添加到已经加载的文档集合
loaded.add(document);
}
}
// 因为涉及到优先级问题,所以这里反转
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
// <202106201026>
// 添加到已加载配置文件结果集中
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);
}
}
<202106191429>处注意到返回的是一个集合,但是理论上一个配置文件应该对应一个才对,因为是存在一种情况,yml在一个文件中可以配置多组配置配置,相当于是多个配置文件,如下:
spring:
profiles: dev
aa:
bb: 111
---
spring:
profiles: stage
aa:
bb: 222
---
spring:
profiles: prod
aa:
bb: 333
<202106191429>
处是加载配置文件对应的文档集合,详细参考5.5.1:loadDocuments
。
5.5.1:loadDocuments
该方法用于加载配置文件对应的文档对象,源码如下:
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments
private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
throws IOException {
// 资源加载器+文件资源组合构成文档缓存的键对象
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
// 从缓存中获取
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
// 缓存中没有
if (documents == null) {
// <202106191452>
List<PropertySource<?>> loaded = loader.load(name, resource);
// 转成文档集合
documents = asDocuments(loaded);
// 放到缓存中
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
<202106191452>
加载生成PropertySource集合,以PropertiesPropertySourceLoader
实现类为例,源码如下:
org.springframework.boot.env.PropertiesPropertySourceLoader#load
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
// 获取配置文件k/v对对应的map,主要使用了PropertiesLoaderUtils工具类,工作中如果需要加载properties配置文件,也可以考虑使用该工具类
// 获取源码如下:
/*
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
return new OriginTrackedPropertiesLoader(resource).load();
}
*/
Map<String,?> properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
// 封装为OriginTrackedMapPropertySource集合并返回
return Collections.singletonList(new OriginTrackedMapPropertySource(name, properties));
}
<202106201026>
最终会调用org.springframework.core.env.MutablePropertySources#addFirst
和org.springframework.core.env.MutablePropertySources#addLast
添加到org.springframework.core.env.MutablePropertySources#propertySourceList
中。
6:实例配置文件加载过程分析
假设设置了-Dspring.profiles.active=prod,dev
,此时profile信息为null,prod,dev
,先附下主要源码:
private DocumentFilter getPositiveProfileFilter(Profile profile) {
return (Document document) -> {
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
}
return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
};
}
private DocumentFilter getNegativeProfileFilter(Profile profile) {
return (Document document) -> (profile == null && !ObjectUtils.isEmpty(document.getProfiles())
&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles())));
}
加载过程如下:
- getPositiveProfileFilter
profile为null时:
读取classpath/application.properties(yml)->成功
profile为prod时:
读取classpath/application-prod.properties(yml),defaultFilter->匹配成功
读取classpath/application-prod.properties(yml),profileFilter->匹配失败
读取classpath/application.properties(yml),defaultFilter->匹配失败(因为会拼接-prod后缀)
读取classpath/application.properties(yml),profileFilter->匹配失败(因为会拼接-prod后缀)
profile为dev时:
读取classpath/application-dev.properties(yml),defaultFilter->匹配成功
读取classpath/application-dev.properties(yml),profileFilter->匹配失败
读取classpath/application.properties(yml),defaultFilter->匹配失败(因为会拼接-prod后缀)
读取classpath/application.properties(yml),profileFilter->匹配失败(因为会拼接-prod后缀)
- getNegativeProfileFilter
只有profile为null,getNegativeProfileFilter的情况,具体,略,绕!!!