SpringBoot源码简读——4.1 补充-ConfigFileApplicationListener

本文围绕Spring Boot配置加载展开,介绍了ConfigFileApplicationListener接口在ApplicationEnvironmentPreparedEvent事件阶段对配置的处理,引出EnvironmentPostProcessor对象。详细阐述了Loader内部类的加载功能,包括构造函数、数据加载、配置文件加载等核心逻辑,最后将加载数据添加到环境属性源中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前在学习监听器的时候知道了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

对容器环境进行修改的处理器.有一点要注意下,它不仅仅是个接口,还是个函数式接口。

可以查看其实现类
image

其中有几个类在之前已经了解过了。现在主要了解下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的区别

image

一个对应 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多层,又频繁使用函数方法,看起来会比较让人迷惑。
自己一行一行的添加注释,本来想添加到笔记中,但是后来发现会显得非常杂乱。所以只添加了主要的流程。细节注释还是看代码记录了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大·风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值