springboot之加载application.properties/yml过程分析

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#addFirstorg.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的情况,具体,略,绕!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值