概述
这是一个ApplicationListener
,也是一个EnvironmentPostProcessor
。
作为ApplicationListener
,它监听了事件ApplicationEnvironmentPreparedEvent
和ApplicationPreparedEvent
。
ApplicationEnvironmentPreparedEvent
事件发生时,它将Springboot
内置配置的其他EnvironmentPostProcessor
和自己放到一起,排序,然后应用到应用上下文环境对象上。该EnvironmentPostProcessor
对应用上下文环境所做的操作就是读取配置文件将它们添加到应用上下文环境中去。
缺省情况下,配置属性会从以下路径的’application.properties/yml’文件中读取 :
- classpath:
- file:./
- classpath:config/
- file:./config/:
也可以设置其他的搜索位置或者设置其它配置文件名称:
#setSearchLocations(String)
#setSearchNames(String)
根据活跃
profile
配置的不同,可能还有其他属性文件会被加载。比如对于web
应用,application-web.properties/yml
会被加载。
另外也可以通过属性
spring.config.location
/spring.config.name
指定不同的搜索位置和指定名称的配置文件。
然后ApplicationPreparedEvent
事件发生时,它向应用上下文注册了一个PropertySourceOrderingPostProcessor
,这是一个BeanFactoryPostProcessor
。该BeanFactoryPostProcessor
拥有最高优先级,在应用时,它会将Environment
对象中的缺省属性源(名为"defaultProperties")放到最低优先级(也就是Environment
对象中属性源列表的最后一项)。
源码分析
本文源代码基于 Springboot 2.1.0
package org.springframework.boot.context.config;
// 省略 imports
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
// Note the order is from least to most specific (last one wins)
// 缺省的配置文件搜索路径(靠后的胜出)
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";
private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null);
private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
/**
* 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";
/**
* The "config name" property name.
*/
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
/**
* The "config location" property name.
*/
public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
/**
* The "config additional location" property name.
*/
public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
/**
* The default order for the processor.
*/
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
private final DeferredLog logger = new DeferredLog();
private String searchLocations;
private String names;
private int order = DEFAULT_ORDER;
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
|| ApplicationPreparedEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
// 应用上下文环境对象准备好时加载Spring内置的EnvironmentPostProcessors,
// (SpringFactoriesLoader扫描各个jar包的"META-INF/spring.factories",
// 找出其中指定的EnvironmentPostProcessor类
// 这里 Spring内置的其他EnvironmentPostProcessor有3个:
// 1. SystemEnvironmentPropertySourceEnvironmentPostProcessor
// 2. SpringApplicationJsonEnvironmentPostProcessor
// 3. CloudFoundryVcapEnvironmentPostProcessor)
// 再加上自己对象,也是一个EnvironmentPostProcessor,
// 排序,然后在当前应用上下文的环境对象上调用它们的postProcessEnvironment()
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
// 加载Spring内置的EnvironmentPostProcessor
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
private void onApplicationPreparedEvent(ApplicationEvent event) {
this.logger.switchTo(ConfigFileApplicationListener.class);
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
/**
* 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);
// 加载环境对象中的PropertySources,
new Loader(environment, resourceLoader).load();
}
/**
* Add appropriate post-processors to post-configure the property-sources.
* 往应用上下文中增加一个PropertySourceOrderingPostProcessor,这是一个
* BeanFactoryPostProcessor,用于post configure PropertySources,其实是将名称
* 为 defaultProperties 的 PropertySource 从 PropertySources 中拿掉,然后添加到
* PropertySources 的最后
* @param context the context to configure
*/
protected void addPostProcessors(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(
new PropertySourceOrderingPostProcessor(context));
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
/**
* Set the search locations that will be considered as a comma-separated list. Each
* search location should be a directory path (ending in "/") and it will be prefixed
* by the file names constructed from #setSearchNames(String) search names and
* profiles (if any) plus file extensions supported by the properties loaders.
* Locations are considered in the order specified, with later items taking precedence
* (like a map merge).
* @param locations the search locations
*/
public void setSearchLocations(String locations) {
Assert.hasLength(locations, "Locations must not be empty");
this.searchLocations = locations;
}
/**
* Sets the names of the files that should be loaded (excluding file extension) as a
* comma-separated list.
* @param names the names to load
*/
public void setSearchNames(String names) {
Assert.hasLength(names, "Names must not be empty");
this.names = names;
}
/**
* BeanFactoryPostProcessor to re-order our property sources below any
* @PropertySource items added by the ConfigurationClassPostProcessor.
*/
private class PropertySourceOrderingPostProcessor
implements BeanFactoryPostProcessor, Ordered {
private ConfigurableApplicationContext context;
PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) {
this.context = context;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
reorderSources(this.context.getEnvironment());
}
private void reorderSources(ConfigurableEnvironment environment) {
PropertySource<?> defaultProperties = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
}
}
}
/**
* Loads candidate property sources and configures the active profiles.
*/
private class Loader {
private final Log logger = ConfigFileApplicationListener.this.logger;
private final ConfigurableEnvironment environment;
private final PropertySourcesPlaceholdersResolver placeholdersResolver;
private final ResourceLoader resourceLoader;
private final List<PropertySourceLoader> propertySourceLoaders;
private Deque<Profile> profiles;
private List<Profile> processedProfiles;
private boolean activatedProfiles;
private Map<Profile, MutablePropertySources> loaded;
private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>();
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(
this.environment);
this.resourceLoader = (resourceLoader != null) ? resourceLoader
: new DefaultResourceLoader();
// Springboot内置提供了两种属性源加载器:
// 1. PropertiesPropertySourceLoader : 加载 XML,properties格式属性文件;
// 2. YamlPropertySourceLoader : 加载YAML格式配置文件(yml,yaml为文件结尾);
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
PropertySourceLoader.class, getClass().getClassLoader());
}
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
// 此时 this.profiles 经过初始化会有两个元素 :
// 1. null (对应 application.yml/properties)
// 2. Profile default (对应 application-default.yml/properties)
// 下面的while-loop 从上面两个初始profile开始读取配置文件,
// 遇到配置文件中的设置spring.profiles.active/include,
// 根据该设置修改 this.profiles
while (!this.profiles.isEmpty()) {
// 取出头部profile
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
// 如果不是 null 或者 default profile 则直接增加到环境的active profiles
addProfileToEnvironment(profile.getName());
}
// 加载配置文件的内容,同时包含了对配置文件中spring.profiles.active/include
// 属性项的处理,该处理过程会改动 this.profiles
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
// 将从配置文件加载得到的PropertySources添加到Environment
addLoadedPropertySources();
}
/**
* Initialize profile information from both the Environment active
* profiles and any spring.profiles.active/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);
}
}
}
// 看环境属性"spring.profiles.active"/"spring.profiles.include"中是否指定了
// 活跃profile,如果指定了的话,构造成Profile对象,返回这些Profile对象的集合
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 List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) {
return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new)
.filter((profile) -> !activatedViaProperty.contains(profile))
.collect(Collectors.toList());
}
void addActiveProfiles(Set<Profile> profiles) {
if (profiles.isEmpty()) {
return;
}
if (this.activatedProfiles) {
// 这个逻辑控制一旦activatedProfiles为true,不再接接受新的active profiles 设置
// 换句话讲,也就是说如果 命令行参数,多个配置文件中设置了多个 spring.profiles.active,
// 则只有第一个被处理的有效,其他的不生效
if (this.logger.isDebugEnabled()) {
this.logger.debug("Profiles already activated, '" + profiles
+ "' will not be applied");
}
return;
}
this.profiles.addAll(profiles);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Activated activeProfiles "
+ StringUtils.collectionToCommaDelimitedString(profiles));
}
this.activatedProfiles = true;
// 现在有了active profiles,删除 this.profiles中标记了defaultProfile=true的profiles
removeUnprocessedDefaultProfiles();
}
private void removeUnprocessedDefaultProfiles() {
this.profiles.removeIf(
(profile) -> (profile != null && profile.isDefaultProfile()));
}
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())));
}
private DocumentConsumer addToLoaded(
BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) {
for (MutablePropertySources merged : this.loaded.values()) {
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
// 将 document中的PropertySource封装为一个MutablePropertySources对象
// 增加到 loaded
MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
(k) -> new MutablePropertySources());
addMethod.accept(merged, document.getPropertySource());
};
}
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
private void load(String location, String name, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile,
filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
// 记录已经处理过的文件扩展名
Set<String> processed = new HashSet<>();
// 每个propertySourceLoader可以处理一种或者多种扩展名的属性源文件,
// 使用这些propertySourceLoaders处理他们能处理的每种扩展名的属性源文件
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));
}
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 {
// location 表示将要加载的属性源文件的位置,例如 :classpath:/application.yaml,
// location 表示的目标文件并不一定存在,所以下面先根据其存在性决定如何处理
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
// 发现location对应的资源文件不存在,直接返回,不做处理
if (this.logger.isTraceEnabled()) {
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;
}
// location 对应的属性源资源文件存在,也含有文件扩展名,符合被加载的条件,现在加载它
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)) {
// 将从属性源分析到的active profiles添加到this.profiles
// 并标记 this.activatedProfiles = true
addActiveProfiles(document.getActiveProfiles());
// 将从属性源分析到的include profiles添加到this.profiles
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);
}
}
private void addIncludedProfiles(Set<Profile> includeProfiles) {
LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
this.profiles.clear();
this.profiles.addAll(includeProfiles);
this.profiles.removeAll(this.processedProfiles);
this.profiles.addAll(existingProfiles);
}
/**
* @param loader 属性源加载器,比如xml/properties属性文件加载器或者yaml属性文件加载器
* @param name 属性源文件名称, 比如 applicationConfig: [classpath:/application.yml]
* @param resource 属性源文件对应的Resource对象,类型为ClassPathResource,FileSystemResource等
**/
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) {
// 读取属性源文件的内容
// name 例子 : applicationConfig: [classpath:/application.yml]
// resource 是name对应的配置文件的Resource实例
List<PropertySource<?>> loaded = loader.load(name, resource);
// 将每个PropertySource对象转换成一个Document对象
documents = asDocuments(loaded);
this.loadDocumentsCache.put(cacheKey, documents);
}
return documents;
}
private List<Document> asDocuments(List<PropertySource<?>> loaded) {
if (loaded == null) {
return Collections.emptyList();
}
// 将每个PropertySource对象转换成一个Document对象
return loaded.stream().map((propertySource) -> {
Binder binder = new Binder(
ConfigurationPropertySources.from(propertySource),
this.placeholdersResolver);
// 借助Binder将propertySource转成一个Document对象
// 1. 在propertySource中,各个属性都是平等的name/value对
// 2. 新的Document对象记录了原来的propertySource对象,并且分析出了active profiles
// 和 include profiles,构造出了相应的Profile对象,放在该Document的属性中
return new Document(propertySource,
binder.bind("spring.profiles", STRING_ARRAY).orElse(null),
getProfiles(binder, ACTIVE_PROFILES_PROPERTY),
getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
}).collect(Collectors.toList());
}
private StringBuilder getDescription(String prefix, String location,
Resource resource, Profile profile) {
StringBuilder result = new StringBuilder(prefix);
try {
if (resource != null) {
String uri = resource.getURI().toASCIIString();
result.append("'");
result.append(uri);
result.append("' (");
result.append(location);
result.append(")");
}
}
catch (IOException ex) {
result.append(location);
}
if (profile != null) {
result.append(" for profile ");
result.append(profile);
}
return result;
}
private Set<Profile> getProfiles(Binder binder, String name) {
return binder.bind(name, STRING_ARRAY).map(this::asProfileSet)
.orElse(Collections.emptySet());
}
private Set<Profile> asProfileSet(String[] profileNames) {
List<Profile> profiles = new ArrayList<>();
for (String profileName : profileNames) {
profiles.add(new Profile(profileName));
}
return new LinkedHashSet<>(profiles);
}
private void addProfileToEnvironment(String profile) {
for (String activeProfile : this.environment.getActiveProfiles()) {
if (activeProfile.equals(profile)) {
return;
}
}
this.environment.addActiveProfile(profile);
}
private Set<String> getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(
CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
return locations;
}
private Set<String> getSearchLocations(String propertyName) {
Set<String> locations = new LinkedHashSet<>();
if (this.environment.containsProperty(propertyName)) {
for (String path : asResolvedSet(
this.environment.getProperty(propertyName), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
return locations;
}
private Set<String> getSearchNames() {
// 如果环境属性spring.config.name制定了要搜索的配置文件的名称则使用它,否则使用缺省值。
// 缺省值 DEFAULT_NAMES:application
// 环境属性spring.config.name的值的格式是逗号分隔的文件名字符串(不含文件扩展名部分)
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
return asResolvedSet(property, null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
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);
}
/**
* This ensures that the order of active profiles in the {@link Environment}
* matches the order in which the profiles were processed.
* @param processedProfiles the processed profiles
*/
private void resetEnvironmentProfiles(List<Profile> processedProfiles) {
String[] names = processedProfiles.stream()
.filter((profile) -> profile != null && !profile.isDefaultProfile())
.map(Profile::getName).toArray(String[]::new);
this.environment.setActiveProfiles(names);
}
// 将从配置文件加载得到的PropertySources添加到Environment
private void addLoadedPropertySources() {
// 这是Environment中已经存在的PropertySources
MutablePropertySources destination = this.environment.getPropertySources();
// 这是该配置文件应用监听器所加载的PropertySources
List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
Collections.reverse(loaded);
String lastAdded = null;
Set<String> added = new HashSet<>();
// 现在将配置文件加载得到的PropertySources添加到Environment
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);
}
}
}
/**
* A Spring Profile that can be loaded.
*/
private static class Profile {
private final String name;
private final boolean defaultProfile;
Profile(String name) {
this(name, false);
}
Profile(String name, boolean defaultProfile) {
Assert.notNull(name, "Name must not be null");
this.name = name;
this.defaultProfile = defaultProfile;
}
public String getName() {
return this.name;
}
public boolean isDefaultProfile() {
return this.defaultProfile;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return ((Profile) obj).name.equals(this.name);
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
}
/**
* Cache key used to save loading the same document multiple times.
*/
private static class DocumentsCacheKey {
private final PropertySourceLoader loader;
private final Resource resource;
DocumentsCacheKey(PropertySourceLoader loader, Resource resource) {
this.loader = loader;
this.resource = resource;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
DocumentsCacheKey other = (DocumentsCacheKey) obj;
return this.loader.equals(other.loader)
&& this.resource.equals(other.resource);
}
@Override
public int hashCode() {
return this.loader.hashCode() * 31 + this.resource.hashCode();
}
}
/**
* A single document loaded by a 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;
}
public PropertySource<?> getPropertySource() {
return this.propertySource;
}
public String[] getProfiles() {
return this.profiles;
}
public Set<Profile> getActiveProfiles() {
return this.activeProfiles;
}
public Set<Profile> getIncludeProfiles() {
return this.includeProfiles;
}
@Override
public String toString() {
return this.propertySource.toString();
}
}
/**
* Factory used to create a 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);
}
/**
* Filter used to restrict when a Document is loaded.
*/
@FunctionalInterface
private interface DocumentFilter {
boolean match(Document document);
}
/**
* Consumer used to handle a loaded Document.
*/
@FunctionalInterface
private interface DocumentConsumer {
void accept(Profile profile, Document document);
}
}