SpringBoot是如何加载application.properties的?

Spring Boot的配置文件加载的逻辑是在类ConfigFileApplicationListener来完成的。如果一开始不知道这个类,可以通过一步一步debug或者在项目中通过如下方式查找关键字application.properties.首先打开Find in Path对话框,然后输入application.properties,点击Scope,默认范围是All Places。也可以找到这个类。
在这里插入图片描述
首先看一下官方对这个类的介绍:
在这里插入图片描述
这个类的注解详细了说明了需要加载的文件名称和路径、包括文件的优先级,同时还提供了一些方便的配置自己来进行配置路径。按照默认的情况:

  1. 首先读取默认路径下的application.properties(yml).
  2. 如果指定了当前激活的版本,比如web,还会继续读取application-web.properties(yml).
    接下来分析一下这个类结构:
    在这里插入图片描述
    在这里插入图片描述
    这个类继承了两个重要的接口:ApplicationListener和EnvironmentPostProcessor,前面的想必阅读过Spring源码的会非常熟悉,这是观察者模式,通过监听相应的事件来触发不同的行为。后一个接口呢,是用于在Spring上下文在被刷新之前来自定义当前环境属性的,这个接口的实现类必须在META-INF/spring.factories文件中定义。EnvironmentPostProcessor可以通过实现Ordered接口或者添加@Order注解来进行多个实现了的排序。这个接口的的定义如下:
public interface EnvironmentPostProcessor {

	/**
	 * Post-process the given {@code environment}.
	 * @param environment the environment to post-process
	 * @param application the application to which the environment belongs
	 */
	void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);

}

以上两个接口在ConfigFileApplicationListener中的实现如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
	addPropertySources(environment, application.getResourceLoader());
}

在这里插入图片描述
然后在以上两个地方打上断点,以debug模式启动Spring Boot项目。然后会停在以上第一个断点
在这里插入图片描述
依次点击左下角栈帧信息:

onApplicationEvent:164, ConfigFileApplicationListener (org.springframework.boot.context.config)
doInvokeListener:172, SimpleApplicationEventMulticaster (org.springframework.context.event)
invokeListener:165, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:139, SimpleApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:127, SimpleApplicationEventMulticaster (org.springframework.context.event)
environmentPrepared:76, EventPublishingRunListener (org.springframework.boot.context.event)
environmentPrepared:53, SpringApplicationRunListeners (org.springframework.boot)
prepareEnvironment:342, SpringApplication (org.springframework.boot)
run:305, SpringApplication (org.springframework.boot)
run:1215, SpringApplication (org.springframework.boot)
main:10, ManagingTransactionsApplication (com.example.managingtransactions)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这个断点以及调用栈帧不难看出在Spring Boot进行环境准备的时候,会发布一个ApplicationEnvironmentPreparedEvent事件,然后ConfigFileApplicationListener会监听到并进行事件的处理。

从整个Spring Boot逻辑来看,在前面构造了SpringApplication的对象,现在准备好环境配置,然后再创建Spring的上下文ApplicationContext,然后再进行refresh。

那么接下来详细分析一下这个类的源码:
首先监听到ApplicationEnvironmentPreparedEvent事件,会进入如下方法:

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	// 通过SPI机制加载EnvironmentPostProcessor实现类
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	// 并将当前类也加入到postProcessors列表中
	postProcessors.add(this);
	// 根据Ordered接口或者添加@Order注解来给这个列表的对象进行排序
	AnnotationAwareOrderComparator.sort(postProcessors);
	// 依次调用EnvironmentPostProcessor的postProcessEnvironment的方法
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
	}
}

那么当前通过SPI机制能加载到几个EnvironmentPostProcessor呢?

List<EnvironmentPostProcessor> loadPostProcessors() {
	// 读取 META-INF/spring.factories 文件中定义的EnvironmentPostProcessor的实现类
	return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}

此处不详细分析SpringFactoriesLoader这个类的逻辑(具体可以去学习Spring Boot的SPI机制,非本文的重点)。但是通过这个参数可以知道在spring,factories文件中定义时是以org.springframework.boot.env.EnvironmentPostProcessor为key的,尝试通过文件搜索看看。

在这里插入图片描述
能搜到3个结果,但是仔细看,第二个是test包内的,可以不管,而第一个和第三个其实是同一个项目,只不过第三个是源码包。因此对应的实现类如下所示:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

那么是不是这样的呢?我们看一下debug的结果。
在这里插入图片描述
debug结果验证了我们的查找结果。
接下来会将当前ConfigFileApplicationListener类对象也作为一个EnvironmentPostProcessor加入到列表中,然后进行排序,然后依次调用。
在这里插入图片描述
可以大致看一下:
SystemEnvironmentPropertySourceEnvironmentPostProcessor这个类实现了Ordered接口,对应的顺序参数为:

/**
 * The default order for the processor.
 */
 // -1 所以比SpringApplicationJsonEnvironmentPostProcessor优先级要高
public static final int DEFAULT_ORDER = SpringApplicationJsonEnvironmentPostProcessor.DEFAULT_ORDER - 1;

private int order = DEFAULT_ORDER;

SpringApplicationJsonEnvironmentPostProcessor类实现了Ordered接口

/**
 * The default order for the processor.  
 */
// int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;

private int order = DEFAULT_ORDER;

CloudFoundryVcapEnvironmentPostProcessor也实现了Ordered接口

// Before ConfigFileApplicationListener so values there can use these ones
// 比ConfigFileApplicationListener优先级要高
private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;

而ConfigFileApplicationListener为

/**
 * The default order for the processor.
 */
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;

这个数值比SpringApplicationJsonEnvironmentPostProcessor的要高,因此优先级要低,所以排序结果就如上所示。
以上三个EnvironmentPostProcessor都会对环境参数进行一些处理,非本文重点,直接略过。分析到这不难看出,ConfigFileApplicationListener最后是在postProcessEnvironment这个方法中进行默认配置文件加载并设置到环境属性Enviromment中去的。
加载之前的情况如下:
在这里插入图片描述
对于propertySourceList留个心。因为加载默认配置文件就是往这个list中添加一个属性资源,也就是property sources.

@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);
	// 加载application.properties资源
	new Loader(environment, resourceLoader).load();
}

这里引入了两个类,首先介绍一下RandomValuePropertySource。
在这里插入图片描述
首先参考一下Spring Boot的官方文档说明:https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config-random-values
在这里插入图片描述
在Spring Boot中支持在配置文件中配置随机值,但是需要首先引入一个叫做RandomValuePropertySource 的东西,然后加入到前面说的propertySourceList中,然后才能去解析application.properties的随机值解析。RandomValuePropertySource对应的代码如下:


/**
 * Name of the random {@link PropertySource}.  资源名称为random
 */
public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";

public RandomValuePropertySource(String name) {
	// 名称为random 资源为Random对象
	super(name, new Random());
}

public static void addToEnvironment(ConfigurableEnvironment environment) {
    //  addAfter 保证配置文件的优先级 排在systemEnvironment后面
	environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
			new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
	logger.trace("RandomValuePropertySource add to Environment");
}

在这里插入图片描述
接下来出现的类为Loader,看起来应该真正执行加载application.properties的类。
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader这个类作为ConfigFileApplicationListener的私有内部类,外部是无法访问的,这是作为内部类的好处之一,另一个好处就是可以访问外部类的属性资源。
首先是对这个类的构造

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	// 保存环境属性
	this.environment = environment;
	// 创建一个资源解析器
	this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
	// 获取资源加载器,如果不存在则创建一个
	this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
	// 通过SPI机制读取META-INF/spring.factories文件中的属性资源加载器
	this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
			getClass().getClassLoader());
}

在这里插入图片描述
这个对象的构造无非就是涉及到先拿到已经初始化好的ConfigurableEnvironment对象,然后初始化资源加载器ResourceLoader、属性解析器PropertySourcesPlaceholdersResolver、属性资源加载器PropertySourceLoader。
按照以上类似的查找方式,可以知道通过SPI机制加载的PropertySourceLoader实现有如下两个:
在这里插入图片描述

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoade

在这里插入图片描述
执行load方法

public void load() {
	this.profiles = new LinkedList<>();
	this.processedProfiles = new LinkedList<>();
	this.activatedProfiles = false;
	this.loaded = new LinkedHashMap<>();
	// 初始化profile信息
	initializeProfiles();
	while (!this.profiles.isEmpty()) {
		Profile profile = this.profiles.poll();
		if (profile != null && !profile.isDefaultProfile()) {
			addProfileToEnvironment(profile.getName());
		}
		load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
		this.processedProfiles.add(profile);
	}
	resetEnvironmentProfiles(this.processedProfiles);
	// 加载默认版本(默认为null 也就是我们通常的application.properties)和激活版本(默认为default,所以也会加载application-default.properties资源),如果能在指定的搜索路径下查找到指定名称和后缀的资源文件,最后就会添加到loadDocumentsCache缓存中和loaded属性中
	load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
	// 最后从loaded属性中将解析好的资源赋值到Environment中
	addLoadedPropertySources();
}
初始化版本信息
/**
 * 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 = getProfilesActivatedViaProperty();
	this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
	// Any pre-existing active profiles set via property sources (e.g.
	// System properties) take precedence over those added in config files.
	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);
		}
	}
}
  1. 首先添加null作为profile,最低优先级
  2. 然后解析已经存在的环境属性key值spring.profiles.active和spring.profiles.include
    在这里插入图片描述
/**
 * The "active profiles" property name. 外部类属性
 */
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";

/**
 * The "includes profiles" property name. 外部类属性
 */
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";

private Set<Profile> getProfilesActivatedViaProperty() {
	if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
			&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
		return Collections.emptySet();
	}
	Binder binder = Binder.get(this.environment);
	Set<Profile> activeProfiles = new LinkedHashSet<>();
	activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
	activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
	return activeProfiles;
}

private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);

private Set<Profile> getProfiles(Binder binder, String name) {
	return binder.bind(name, STRING_ARRAY).map(this::asProfileSet).orElse(Collections.emptySet());
}
  1. 在读取环境中其他的激活版本profile(AbstractEnvironment类中的spring.profiles.active)。如果无法读取到,则直接返回,在map时返回。如果能读取到,然后就创建Profile对象,并判断是否已经存在activatedViaProperty集合中,不存在,则添加到返回的集合中。如果已经存在,则忽略
private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) {
	return Arrays.stream(this.environment.getActiveProfiles())
				 // 创建Profile对象	
				 .map(Profile::new)
				 // 如果当前activatedViaProperty集合不包含 则进行收集 返回
			     .filter((profile) -> !activatedViaProperty.contains(profile))
			     .collect(Collectors.toList());
}
// org.springframework.core.env.AbstractEnvironment
@Override
public String[] getActiveProfiles() {
	return StringUtils.toStringArray(doGetActiveProfiles());
}

/**
 * Return the set of active profiles as explicitly set through
 * {@link #setActiveProfiles} or if the current set of active profiles
 * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
 * property and assign its value to the set of active profiles.
 * @see #getActiveProfiles()
 * @see #ACTIVE_PROFILES_PROPERTY_NAME
 */
protected Set<String> doGetActiveProfiles() {
	synchronized (this.activeProfiles) {
		if (this.activeProfiles.isEmpty()) {
			String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
			if (StringUtils.hasText(profiles)) {
				setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
						StringUtils.trimAllWhitespace(profiles)));
			}
		}
		return this.activeProfiles;
	}
}

在这里插入图片描述
4. 如果通过以上的方式都无法加载到激活版本,则默认为default
在这里插入图片描述

处理激活版本信息并查找路径 加载文件

循环处理profiles,首先处理优先级低的,也就是null,然后是default

while (!this.profiles.isEmpty()) {
	Profile profile = this.profiles.poll();
	if (profile != null && !profile.isDefaultProfile()) {
		// 此处null不执行
		addProfileToEnvironment(profile.getName());
	}
	// 解析路径 并进行加载
	load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
	this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);

下面会用到Loader的一个属性

private Map<Profile, MutablePropertySources> loaded;

对于load这个方法,第一次看起来很费解,比较长,而且引入两个接口实现,并且涉及到一个新的类。
首先来了解这个类org.springframework.boot.context.config.ConfigFileApplicationListener.Document,作为一个静态内部类,只在内部使用,看了一下里面的属性,可以作为一个中间容器,用于解析各种资源时临时存放的。

/**
 * A single document loaded by a {@link PropertySourceLoader}.
 */
private static class Document {

	private final PropertySource<?> propertySource;

	private String[] profiles;

	private final Set<Profile> activeProfiles;

	private final Set<Profile> includeProfiles;

	Document(PropertySource<?> propertySource, String[] profiles, Set<Profile> activeProfiles,
			Set<Profile> includeProfiles) {
		this.propertySource = propertySource;
		this.profiles = profiles;
		this.activeProfiles = activeProfiles;
		this.includeProfiles = includeProfiles;
	}
}

然后再看看与这个类有关的三个接口定义,也是上面那个load方法中涉及的。

/**
 * Factory used to create a {@link DocumentFilter}.
 */
@FunctionalInterface
private interface DocumentFilterFactory {

	/**
	 * Create a filter for the given profile.
	 * @param profile the profile or {@code null}
	 * @return the filter
	 */
	DocumentFilter getDocumentFilter(Profile profile);
}

@FunctionalInterface
private interface DocumentFilter {
	// 当资源加载时进行过滤匹配的
	boolean match(Document document);
}

/**
 * Consumer used to handle a loaded {@link Document}.
 */
@FunctionalInterface
private interface DocumentConsumer {
	// 专门用于处理profile和document的接口
	void accept(Profile profile, Document document);
}

从这两个接口来看第一个是用于产生DocumentFilter 的工厂,在load这个方法中这个工厂的实现很简单,就是创建DocumentFilter,如下方法getPositiveProfileFilter ,而DocumentFilter 是用于进行过滤的,主要的实现也在getPositiveProfileFilter方法中。而DocumentConsumer 是进行document的最后处理的。
以下是关于他们的实现

private DocumentFilter getPositiveProfileFilter(Profile profile) {
	return (Document document) -> {
		if (profile == null) {
			// profile不存在而且document中profile为空
			return ObjectUtils.isEmpty(document.getProfiles());
		}
		// 当前document包含profile
		return ObjectUtils.containsElement(document.getProfiles(), profile.getName())
				&& this.environment.acceptsProfiles(Profiles.of(document.getProfiles()));
	};
}

在这里插入图片描述
其中ObjectUtils.containsElement方法用于判断数组中是否存在某个元素

/**
 * Check whether the given array contains the given element.
 * @param array the array to check (may be {@code null},
 * in which case the return value will always be {@code false})
 * @param element the element to check for
 * @return whether the element has been found in the given array
 */
public static boolean containsElement(@Nullable Object[] array, Object element) {
	if (array == null) {
		return false;
	}
	for (Object arrayEle : array) {
		if (nullSafeEquals(arrayEle, element)) {
			return true;
		}
	}
	return false;
}

上面还用到一个接口的默认实现在接口org.springframework.core.env.Profiles中有个如下方法

static Profiles of(String... profiles) {
	return ProfilesParser.parse(profiles);
}

以下就是关于它的说明,返回的结果是一个ProfileParser对象
在这里插入图片描述
解析获取Profiles,再调用org.springframework.core.env.AbstractEnvironment中的方法acceptsProfiles和isProfileActive方法

@Override
public boolean acceptsProfiles(Profiles profiles) {
	Assert.notNull(profiles, "Profiles must not be null");
	return profiles.matches(this::isProfileActive);
}

/**
 * Return whether the given profile is active, or if active profiles are empty
 * whether the profile should be active by default.
 * @throws IllegalArgumentException per {@link #validateProfile(String)}
 */
protected boolean isProfileActive(String profile) {
	validateProfile(profile);
	Set<String> currentActiveProfiles = doGetActiveProfiles();
	return (currentActiveProfiles.contains(profile) ||
			(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}

又调到Profiles的matches方法,也就是ParsedProfiles的方法

@Override
public boolean matches(Predicate<String> activeProfiles) {
	for (Profiles candidate : this.parsed) {
		if (candidate.matches(activeProfiles)) {
			return true;
		}
	}
	return false;
}

以下为DocumentConsumer实现

// addMethod = MutablePropertySources::addLast
// checkForExisting = false
private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
		boolean checkForExisting) {
	return (profile, document) -> {
		// 是否检查存在性 默认为false	
		if (checkForExisting) {
		    // 遍历已经加载的资源 如果当前资源包含document中的资源 就不继续进行处理
			for (MutablePropertySources merged : this.loaded.values()) {
				if (merged.contains(document.getPropertySource().getName())) {
					return;
				}
			}
		}
		// 如果当前profile元素不存在则构造MutablePropertySources对象并存放到loaded中
		MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
				(k) -> new MutablePropertySources());
		// 通过BiConsumer-》	addMethod处理 也就是上面的MutablePropertySources::addLast方法
		addMethod.accept(merged, document.getPropertySource());
	};
}

最后是load方法的说明了。首先解析spring.config.location属性和spring.config.additional-location属性,然后再次判断ConfigFileApplicationListener类中属性searchLocations是否已经存在解析好的路径如果有,则进行解析,可以处理占位符。如果不存在,则使用默认的位置classpath:/,classpath:/config/,file:./,file:./config/"

private Set<String> getSearchLocations() {
	// 判断是否包含spring.config.location属性 当前环境中不存在
	if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
		return getSearchLocations(CONFIG_LOCATION_PROPERTY);
	}
	// 判断是否包含spring.config.additional-location属性 当前页不存在
	Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
	// 查找默认的位置 classpath:/,classpath:/config/,file:./,file:./config/"
	locations.addAll(
			asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
	return locations;
}

首先判断已经存在的,然后将默认作为最后的选择。

private Set<String> asResolvedSet(String value, String fallback) {
	List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
			(value != null) ? this.environment.resolvePlaceholders(value) : fallback)));
	Collections.reverse(list);
	return new LinkedHashSet<>(list);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
进行加载的逻辑

private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null);

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	getSearchLocations().forEach((location) -> {
	    // 首先判断是否是文件夹 比如 file:./config/就是文件夹 默认的四个路径也都是文件夹
		boolean isFolder = location.endsWith("/");
		// 如果是文件夹 才会去获取查找的名称 否则返回包含一个空元素的集合
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
	});
}

获取到搜索路径之后,就会首先判断是否判断是否为文件夹,如果是文件夹就会查找搜索名称。

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);
	}
	// 如果不包含spring.config.name配置,则去application
	return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}

private Set<String> asResolvedSet(String value, String fallback) {
	List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
			(value != null) ? this.environment.resolvePlaceholders(value) : fallback)));
	Collections.reverse(list);
	return new LinkedHashSet<>(list);
}

在这里插入图片描述
从上面分析下来,当前搜索路径(SearchLocation)为file:./config/,而搜索名称(SearchName)为application。
在这里插入图片描述
在这里插入图片描述

指定搜索路径和搜索名称加载资源文件
  1. 加载指定搜索路径location下对应搜索名称为name的资源文件
  2. 通过DocumentFilter进行过滤
  3. 最后通过DocumentConsumer进行处理
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
		DocumentConsumer consumer) {
	// 名称为空	
	if (!StringUtils.hasText(name)) {
	    // 此处的propertySourceLoaders是在外部类构造时通过SPI机制加载的
		for (PropertySourceLoader loader : this.propertySourceLoaders) {
		    // 
			if (canLoadFileExtension(loader, location)) {
				load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
				return;
			}
		}
	}
	Set<String> processed = new HashSet<>();
	for (PropertySourceLoader loader : this.propertySourceLoaders) {
		for (String fileExtension : loader.getFileExtensions()) {
			if (processed.add(fileExtension)) {
				loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
						consumer);
			}
		}
	}
}

判断是否是可以加载的资源文件,比对后缀

private boolean canLoadFileExtension(PropertySourceLoader loader, String name) {
		return Arrays.stream(loader.getFileExtensions())
					  // 有一个匹配就可以	
				     .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name, fileExtension));
	}

比如PropertiesPropertySourceLoader就支持"properties", “xml” 两种格式

@Override
public String[] getFileExtensions() {
	return new String[] { "properties", "xml" };
}

而YamlPropertySourceLoader支持"yml", "yaml"两种格式

@Override
public String[] getFileExtensions() {
	return new String[] { "yml", "yaml" };
}

在限定后缀进行加载
在这里插入图片描述

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);
}

最终进行文件加载的逻辑

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
		DocumentConsumer consumer) {
	try {
	    // 通过resourceLoader来加载对应目录下的资源 比如file:./config/application.xml
		Resource resource = this.resourceLoader.getResource(location);
		if (resource == null || !resource.exists()) {
			if (this.logger.isTraceEnabled()) {
				StringBuilder description = getDescription("Skipped missing config ", location, resource,
						profile);
				// 资源不存在  打印日志 级别为trace 所以默认情况下是没有这个日志的		
				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.properties]
		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;
		}
		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);
		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);
	}
}

如果当前目录下没有想要查找的资源文件,则会打印trace日志并返回
在这里插入图片描述
如果可以查找到资源,然后对于没有后缀的也不进行处理。后缀没有问题的话,再继续解析
在这里插入图片描述

// 一个map缓存
private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>();

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
		throws IOException {
	// 构建一个缓存key	
	DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
	// 首先读取缓存
	List<Document> documents = this.loadDocumentsCache.get(cacheKey);
	if (documents == null) {
		// 缓存不存在 则进行加载
		List<PropertySource<?>> loaded = loader.load(name, resource);
		// 将加载的资源作为documents 
		documents = asDocuments(loaded);
		// 加入到缓存中
		this.loadDocumentsCache.put(cacheKey, documents);
	}
	return documents;
}

进行资源的加载,比如PropertiesPropertySourceLoader 加载properties文件

public class PropertiesPropertySourceLoader implements PropertySourceLoader {

private static final String XML_FILE_EXTENSION = ".xml";

@Override
public String[] getFileExtensions() {
	return new String[] { "properties", "xml" };
}

@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
	Map<String, ?> properties = loadProperties(resource);
	if (properties.isEmpty()) {
		return Collections.emptyList();
	}
	return Collections.singletonList(new OriginTrackedMapPropertySource(name, properties));
}

@SuppressWarnings({ "unchecked", "rawtypes" })
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();
}
}
将加载到的资源转为Document
private List<Document> asDocuments(List<PropertySource<?>> loaded) {
	if (loaded == null) {
		return Collections.emptyList();
	}
	return loaded.stream()
	             // 遍历 转为Document      
	             .map((propertySource) -> {
		Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),
				this.placeholdersResolver);
		return new Document(propertySource,
		// private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class); 
							binder.bind("spring.profiles", STRING_ARRAY).orElse(null),
		// spring.profiles.active					
							getProfiles(binder, ACTIVE_PROFILES_PROPERTY), 
		// spring.profiles.include					
							getProfiles(binder, INCLUDE_PROFILES_PROPERTY));})
				.collect(Collectors.toList());
}

在这里插入图片描述
在这里插入图片描述
接下来进性遍历过滤,然后进行消费

在这里插入图片描述
在这里插入图片描述
将资源文件添加到propertySourceList中。
在这里插入图片描述
后续还会继续处理当前激活版本对应的资源文件,比如当前激活版本为default,那么也会去找资源: file:./config/application-default.properties
在这里插入图片描述
在这里插入图片描述

关于Binder

在这里插入图片描述

private Set<Profile> getProfiles(Binder binder, String name) {
		// name = spring.profiles.active
		// Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
		return binder.bind(name, STRING_ARRAY)
					 .map(this::asProfileSet)
					 .orElse(Collections.emptySet());
}

/**
 * Bind the specified target {@link Bindable} using this binder's
 * {@link ConfigurationPropertySource property sources}.
 * @param name the configuration property name to bind
 * @param target the target bindable
 * @param <T> the bound type
 * @return the binding result (never {@code null})
 * @see #bind(ConfigurationPropertyName, Bindable, BindHandler)
 */
public <T> BindResult<T> bind(String name, Bindable<T> target) {
	return bind(ConfigurationPropertyName.of(name), target, null);
}
将资源最后赋值到Environment中
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();
				}
			}
		}
	}

	private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
			PropertySource<?> source) {
		if (lastAdded == null) {
			if (destination.contains(DEFAULT_PROPERTIES)) {
				destination.addBefore(DEFAULT_PROPERTIES, source);
			}
			else {
				destination.addLast(source);
			}
		}
		else {
			destination.addAfter(lastAdded, source);
		}
	}

大功告成!

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值