之前在学习监听器的时候知道了ConfigFileApplicationListener接口会进行配置的处理
ConfigFileApplicationListener
此监听器对配置的处理主要在ApplicationEnvironmentPreparedEvent事件阶段
onApplicationEnvironmentPreparedEvent
对在ApplicationEnvironmentPreparedEvent事件的处理
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
// 加载在`META-INF/spring.factories` 定义的EnvironmentPostProcessor对应的类名
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 把自己加入其中,因为其也实现了EnvironmentPostProcessor
postProcessors.add(this);
// 排序
AnnotationAwareOrderComparator.sort(postProcessors);
// 遍历处理器(EnvironmentPostProcessor)并依次执行其业务逻辑
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
}
这段代码看起来发现在onApplicationEnvironmentPreparedEvent
并没与做任何直接和配置相关的操作,但是添加了EnvironmentPostProcessor对象到应用中。我们之前已经知道在后续进行应用的刷新的时候这些EnvironmentPostProcessor方法的逻辑都会被执行一遍。那么现在引出了另一个我们需要关注的对象EnvironmentPostProcessor。
EnvironmentPostProcessor
对容器环境进行修改的处理器.有一点要注意下,它不仅仅是个接口,还是个函数式接口。
可以查看其实现类
其中有几个类在之前已经了解过了。现在主要了解下ConfigFileApplicationListener。和之前不同的之前我们以监控器身份来了解它,现在他的身份是EnvironmentPostProcessor了
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
// 将RandomValuePropertySource添加到容器环境中
RandomValuePropertySource.addToEnvironment(environment);
// 开始进行加载操作
new Loader(environment, resourceLoader).load();
}
RandomValuePropertySource 是springboot的随机值属性源
加载配置的核心逻辑
// 开始进行加载操作
new Loader(environment, resourceLoader).load();
在配置完环境和资源加载器之后又初始化了属性源资源加载器。
Loader
Loader是ConfigFileApplicationListener内部类,实现加载器功能
构造函数
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
// 创建默认加载器
this.resourceLoader = (resourceLoader != null) ? resourceLoader
: new DefaultResourceLoader();
// 加载在 `META-INF/spring.factories` 里的PropertySourceLoader对应的类数组
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
PropertySourceLoader.class, getClass().getClassLoader());
}
load
进行数据的加载
public void load() {
// 未处理的数据集合
this.profiles = new LinkedList<>();
// 已处理的数据集合
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 初始化 配置内容
initializeProfiles();
// 遍历profiles
while (!this.profiles.isEmpty()) {
// 获得profiles
Profile profile = this.profiles.poll();
// 如果profile不为空,且不是默认的profiles,则添加profiles资源到容器的环境中
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
// 加载配置,涉及到的方法getPositiveProfileFilter ;MutablePropertySources.addLast
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
// 将最终的profiles添加至环境中
resetEnvironmentProfiles(this.processedProfiles);
// 加载配置
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
// 将加载的配置添加到属性源中
addLoadedPropertySources();
}
initializeProfiles
对配置文件进行加载
private void initializeProfiles() {
this.profiles.add(null);
// 从配置中获得激活的Profile
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
// 先添加不在配置中的Profiles添加至this.profiles
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
// 再添加激活的配置信息添加至this.profiles
addActiveProfiles(activatedViaProperty);
// 如果profiles唯一,profile为空,则使用默认的Profiles
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);
}
}
}
getProfilesActivatedViaProperty
主要是获取 spring.profiles.active或spring.profiles.include的参数,并且根据spring.profiles参数获得配置
private Set<Profile> getProfilesActivatedViaProperty() {
// 假如环境中不存在ACTIVE_PROFILES_PROPERTY,INCLUDE_PROFILES_PROPERTY 则返回空集合
// spring.profiles.active ; spring.profiles.include
if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
return Collections.emptySet();
}
// 从环境中获得INCLUDE_PROFILES_PROPERTY,ACTIVE_PROFILES_PROPERTY对应的值
// 处理并且返回
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;
}
getOtherActiveProfiles
获得不在配置中的但是激活的Profile
private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) {
// 循环容器环境中已经激活的Profiles,取出其中不在activatedViaProperty中的对象
return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new)
.filter((profile) -> !activatedViaProperty.contains(profile))
.collect(Collectors.toList());
}
addActiveProfiles
将配置添加到profiles中,并且移除默认配置。
addProfileToEnvironment
当profile不为空,且不是默认的profiles的时候是用此方法,将profile添加到环境中的ActiveProfile中
private void addProfileToEnvironment(String profile) {
// 获得正在激活的Profiles,并且遍历,假如目标profile已经存在,则返回,
// 否则添加至激活的profile中
for (String activeProfile : this.environment.getActiveProfiles()) {
if (activeProfile.equals(profile)) {
return;
}
}
this.environment.addActiveProfile(profile);
}
load
开始进行配置的加载
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 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);
// 添加 searchLocations 到 locations 中
// 假如不存在 则添加classpath:/,classpath:/config/,file:./,file:./config/进去
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);
// 假如资源不是url,则添加file:前缀
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
// 返回解析后的路径
return locations;
}
检索出最后的数据后,开始进行属性加载
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<>();
for (PropertySourceLoader loader : this.propertySourceLoaders) {
// PropertySourceLoader遍历可以处理文件
for (String fileExtension : loader.getFileExtensions()) {
// 将文件添加至处理任务重
if (processed.add(fileExtension)) {
// 加载 Profile 指定的配置文件
loadForFileExtension(loader, location + name, "." + fileExtension,
profile, filterFactory, consumer);
}
}
}
}
开始检索带后缀的配置文件
Extension
private void loadForFileExtension(PropertySourceLoader loader, String prefix,
String fileExtension, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
// 获得默认DocumentFilter和配置的DocumentFilter
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
// 特殊情况,之前读取 Profile 对应的配置文件,也可被当前 Profile 所读取
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
// 加载(无需带 Profile)指定的配置文件
load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
两个 DocumentFilter,其中一个(defaultFilter)是用来加载不带后缀的配置文件,另外一个是来加载带后缀的配置文件
需要注意的是DocumentFilter是个函数方法,主要作用是用于Document 的匹配
@FunctionalInterface
private interface DocumentFilterFactory {
/**
*/
DocumentFilter getDocumentFilter(Profile profile);
}
defaultFilter和profileFilter的区别
一个对应 this::getNegativeProfileFilter,一个对应this::getPositiveProfileFilter。
private DocumentFilter getPositiveProfileFilter(Profile profile) {
return (Document document) -> {
// 为空,则document.getProfiles()也需要空
if (profile == null) {
return ObjectUtils.isEmpty(document.getProfiles());
}
// 要求 document.profiles 包含 profile
// 并且,environment.activeProfiles 包含 document.profiles
return ObjectUtils.containsElement(document.getProfiles(),
profile.getName())
&& this.environment.acceptsProfiles(document.getProfiles());
};
}
private DocumentFilter getNegativeProfileFilter(Profile profile) {
return (Document document) -> (profile == null
&& !ObjectUtils.isEmpty(document.getProfiles())
&& this.environment.acceptsProfiles(document.getProfiles()));
}
这一块的作用是
defaultFilter:profile 为 null 的情况,处理默认情况,即未定义 spring.profiles
profileFilter:profile 非 null 的情况,处理配置文件中定义了 spring.profiles 属性,则需要使用 profile 和 spring.profiles 匹配。
就是当配置文件中配置了spring.profiles,位置所属的profile就不在根据文件名后缀来判断而是根据spring.profiles来判断
load
进行最终的资源加载
private void load(PropertySourceLoader loader, String location, Profile profile,
DocumentFilter filter, DocumentConsumer consumer) {
try {
// 文件是否存在,不存在则直接返回
Resource resource = this.resourceLoader.getResource(location);
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Skipped missing config "
+ getDescription(location, resource, profile));
}
return;
}
// 没有后缀的文件则不进行读取
if (!StringUtils.hasText(
StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Skipped empty config extension "
+ getDescription(location, resource, profile));
}
return;
}
// 加载配置文件
String name = "applicationConfig: [" + location + "]";
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Skipped unloaded config "
+ getDescription(location, resource, profile));
}
return;
}
// 使用 DocumentFilter 过滤匹配的 Document ,添加到 loaded 数组中
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
// 匹配的话就添加document到loaded
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()) {
this.logger.debug("Loaded config file "
+ getDescription(location, resource, profile));
}
}
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load property "
+ "source from location '" + location + "'", ex);
}
}
上面逻辑大概的逻辑可以分为:
- 获得资源加载器
- 通过资源对象和配置名称获得资源的document
- 将document处理成profiles然后加载进loaded
addLoadedPropertySources
将this.loaded中的数据加载进this.environment的PropertySources中
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();
}
}
}
}
总结
配置加载的代码中load代码最为复杂,中间嵌套了n多层,又频繁使用函数方法,看起来会比较让人迷惑。
自己一行一行的添加注释,本来想添加到笔记中,但是后来发现会显得非常杂乱。所以只添加了主要的流程。细节注释还是看代码记录了。