Spring Boot的配置文件加载的逻辑是在类ConfigFileApplicationListener来完成的。如果一开始不知道这个类,可以通过一步一步debug或者在项目中通过如下方式查找关键字application.properties.首先打开Find in Path对话框,然后输入application.properties,点击Scope,默认范围是All Places。也可以找到这个类。
首先看一下官方对这个类的介绍:
这个类的注解详细了说明了需要加载的文件名称和路径、包括文件的优先级,同时还提供了一些方便的配置自己来进行配置路径。按照默认的情况:
- 首先读取默认路径下的application.properties(yml).
- 如果指定了当前激活的版本,比如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中支持在配置文件中配置随机值,但是需要首先引入一个叫做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);
}
}
}
- 首先添加null作为profile,最低优先级
- 然后解析已经存在的环境属性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());
}
- 在读取环境中其他的激活版本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。
指定搜索路径和搜索名称加载资源文件
- 加载指定搜索路径location下对应搜索名称为name的资源文件
- 通过DocumentFilter进行过滤
- 最后通过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);
}
}
大功告成!