【Spring Boot 源码学习】自动装配流程源码解析(下)

news2024/9/30 23:32:19

自动装配流程源码解析(下)

  • 引言
  • 往期内容
  • 主要内容
    • 4. 排除指定自动配置组件
    • 5. 过滤自动配置组件
    • 6. 触发自动配置事件
  • 总结

引言

上篇博文,笔者带大家了解了自动装配流程中有关自动配置加载的流程;

本篇将介绍自动装配流程剩余的内容,包含了自动配置组件的排除和过滤、触发自动配置事件。

往期内容

在开始本篇的内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:

Spring Boot 源码学习
Spring Boot 项目介绍
Spring Boot 核心运行原理介绍
【Spring Boot 源码学习】@EnableAutoConfiguration 注解
【Spring Boot 源码学习】@SpringBootApplication 注解
【Spring Boot 源码学习】走近 AutoConfigurationImportSelector
【Spring Boot 源码学习】自动装配流程源码解析(上)

主要内容

书接上篇,本篇继续从源码分析自动装配流程:

4. 排除指定自动配置组件

如果我们在实际使用时,并不需要其中的某些组件,那就可以通过 @EnableAutoConfiguration 注解的 excludeexcludeName 属性来进行有针对性的排除 或者 在Spring Boot 的配置文件进行排除。

下面我们来分析一下排除逻辑的源码:

Set<String> exclusions = getExclusions(annotationMetadata, attributes);

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	Set<String> excluded = new LinkedHashSet<>();
	// 获取 exclude 属性 配置的 待排除的自动配置组件
	excluded.addAll(asList(attributes, "exclude"));
	// 获取 excludeName 属性 配置的 待排除的自动配置组件
	excluded.addAll(asList(attributes, "excludeName"));
	// 获取 Spring Boot 配置文件中 配置的 待排除的自动配置组件
	excluded.addAll(getExcludeAutoConfigurationsProperty());
	return excluded;
}

protected List<String> getExcludeAutoConfigurationsProperty() {
	Environment environment = getEnvironment();
	if (environment == null) {
		return Collections.emptyList();
	}
	if (environment instanceof ConfigurableEnvironment) {
		Binder binder = Binder.get(environment);
		return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
			.map(Arrays::asList)
			.orElse(Collections.emptyList());
	}
	String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
	return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

上面的代码也挺好理解,分别从注解属性 exclude 、 excludeName 以及配置文件中获取待排除的自动配置组件。

下面我们来演示一下该如何配置,从而排除我们不需要的自动配置组件:

  • 添加注解属性 exclude 和 excludeName
    在这里插入图片描述
  • 添加配置文件属性
    在这里插入图片描述
  • 我们启动先前建的 Spring Boot 项目的应用类,分别查看到如下的信息:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

当上面获取了被排除的自动配置组件之后,需要对待排除的类进行检查,如下所示:

checkExcludedClasses(configurations, exclusions);

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
	List<String> invalidExcludes = new ArrayList<>(exclusions.size());
	for (String exclusion : exclusions) {
		// 如果待排除的自动配置类存在且可以加载
		// 并且已去重过的自动配置组件中不存在该待排除的自动配置类
		if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
			// 添加到非法的排除列表中
			invalidExcludes.add(exclusion);
		}
	}
	// 如果存在非法的排除项,则抛出相应的异常信息
	if (!invalidExcludes.isEmpty()) {
		handleInvalidExcludes(invalidExcludes);
	}
}

protected void handleInvalidExcludes(List<String> invalidExcludes) {
	StringBuilder message = new StringBuilder();
	for (String exclude : invalidExcludes) {
		message.append("\t- ").append(exclude).append(String.format("%n"));
	}
	throw new IllegalStateException(String.format(
			"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
			message));
}

上述代码中对于待排除类的检查逻辑也好理解,如果待排除的自动配置类存在且可以加载【即存在于当前的ClassLoader中】,并且已去重过的自动配置组件中不存在该待排除的自动配置类,则认为待排除的自动配置类是非法的,抛出相关异常。

我们下面通过示例来验证一下:

  • 在我们的示例项目中添加一个自动配置类【注意这里只做演示,无其他意义】

  • 配置文件添加项目中的一个自动配置类
    在这里插入图片描述

  • 我们启动先前建的 Spring Boot 项目的应用类,可以看到如下的启动异常报错:
    在这里插入图片描述

如果上述检查通过,则说明待排除的自动配置类都符合要求,则调用如下代码从自动配置集合中移除上面获取的待排除的自动配置类信息。

configurations.removeAll(exclusions);

5. 过滤自动配置组件

经过上面的自动配置组件排除逻辑之后,接下来就要过滤自动配置组件了,而过滤逻辑主要是通过检查配置类的注解是否符合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解检查条件,来决定该过滤哪些自动配置组件。

下面开始分析相关代码,如下所示【Spring Boot 2.7.9】:

configurations = getConfigurationClassFilter().filter(configurations);

进入 getConfigurationClassFilter 方法,如下所示:

private ConfigurationClassFilter getConfigurationClassFilter() {
	if (this.configurationClassFilter == null) {
		List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
		for (AutoConfigurationImportFilter filter : filters) {
			invokeAwareMethods(filter);
		}
		this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
	}
	return this.configurationClassFilter;
}

getConfigurationClassFilter 方法返回一个 ConfigurationClassFilter 实例,用来过滤掉不必要的配置类。

继续看 getAutoConfigurationImportFilters 方法,如下所示:

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
	return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

它通过 SpringFactoriesLoader 类的 loadFactories 方法来获取 META-INF/spring.factories 中配置 keyAutoConfigurationImportFilterFilters 列表;

我们可以查看相关配置了解一下,如下所示:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

如上所示,在 spring-boot-autoconfigure 中默认配置了三个筛选条件:OnBeanConditionOnClassConditionOnWebApplicationCondition,它们均实现了 AutoConfigurationImportFilter 接口。

相关类图如下所示:

在这里插入图片描述

我们继续往下看 invokeAwareMethods,如下所示:

private void invokeAwareMethods(Object instance) {
	if (instance instanceof Aware) {
		if (instance instanceof BeanClassLoaderAware) {
			((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
		}
		if (instance instanceof BeanFactoryAware) {
			((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
		}
		if (instance instanceof EnvironmentAware) {
			((EnvironmentAware) instance).setEnvironment(this.environment);
		}
		if (instance instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
		}
	}
}

这里先判断传入的 instance 对象是否是 Aware 接口?

如果是 Aware 接口,则判断是否是它的 BeanClassLoaderAwareBeanFactoryAwareEnvironmentAwareResourceLoaderAware 这 4 个子接口实现?

如果是,则调用对应的回调方法设置相应参数。

Aware 接口是一个一个标记超接口,它表示一个 bean 有资格通过回调方式从 Spring 容器中接收特定框架对象的通知。具体的方法签名由各个子接口确定,但通常应该只包括一个接受单个参数并返回 void 的方法。

继续往下翻看源码,在 getConfigurationClassFilter 方法最后,我们可以看到它返回了一个内部类 ConfigurationClassFilter 的实例对象。

有了内部类 ConfigurationClassFilter ,接下来就可以开始自动配置组件的过滤操作,主要是通过内部类 ConfigurationClassFilterfilter 方法来实现过滤自动配置组件的功能。

不过在分析 filter 方法之前,我们先了解下内部类 ConfigurationClassFilter 中两个成员变量 :

  • List<AutoConfigurationImportFilter> filters : 上面已介绍,它是 META-INF/spring.factories 中配置的 keyAutoConfigurationImportFilterFilters 列表
  • AutoConfigurationMetadata autoConfigurationMetadata :元数据文件 META-INF/ spring-autoconfigure-metadata.properties 中配置对应实体类,详细分析请看下面。

AutoConfigurationMetadata 自动配置元数据,这个前面没有涉及到,从内部类 ConfigurationClassFilter 的构造函数中,我们可以看到如下:

this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);

详细代码,由于篇幅受限,这里就不贴了,大家可以自行查看相关源码,从如下的截图中,我们也可以直观了解下。

在这里插入图片描述
在这里插入图片描述

好了,现在我们进入 filter 方法中,最关键的就是下面 的双层 for 循环处理:

List<String> filter(List<String> configurations) {
	long startTime = System.nanoTime();
	String[] candidates = StringUtils.toStringArray(configurations);
	boolean skipped = false;
	// 具体的过滤匹配操作
	for (AutoConfigurationImportFilter filter : this.filters) {
		boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
		for (int i = 0; i < match.length; i++) {
			if (!match[i]) {
				// 不符合过滤匹配要求,则清空当前的自动配置组件
				candidates[i] = null;
				skipped = true;
			}
		}
	}
	// 如果匹配完了,都无需跳过,直接返回当前配置即可
	if (!skipped) {
		return configurations;
	}
	// 有一个不满足过滤匹配要求,都重新处理并返回符合要求的自动配置组件
	List<String> result = new ArrayList<>(candidates.length);
	for (String candidate : candidates) {
		// 如果当前自动配置组件不满足过滤匹配要求,则上面会被清空
		// 因此这里只需判断即可获取符合要求的自动配置组件
		if (candidate != null) {
			result.add(candidate);
		}
	}
	if (logger.isTraceEnabled()) {
		int numberFiltered = configurations.size() - result.size();
		logger.trace("Filtered " + numberFiltered + " auto configuration class in "
				+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
	}
	return result;
}

翻看上面的 filter 方法源码,我们可以很明显地看到,Spring Boot 就是通过如下的代码来实现具体的过滤匹配操作。

boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

在介绍如何实现具体的过滤匹配操作之前,先来看一下 AutoConfigurationImportFilter 接口的源码:

@FunctionalInterface
public interface AutoConfigurationImportFilter {
	boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}

上面的 match 方法就是实现具体的过滤匹配操作;

参数:

  • String[] autoConfigurationClasses :待过滤的自动配置类数组
  • AutoConfigurationMetadata autoConfigurationMetadata :自动配置的元数据信息

返回值:

过滤匹配后的结果布尔数组,数组的大小与 autoConfigurationClasses 一致,如果自动配置组件需过滤掉,则设置布尔数组对应值为 false

结合上面的关联类图,我们可以看到 AutoConfigurationImportFilter 接口实际上是由抽象类 FilteringSpringBootCondition 来实现的,另外该抽象类还定义了一个抽象方法 getOutcomes ,然后 OnBeanConditionOnClassConditionOnWebApplicationCondition 继承该抽象类,实现 getOutcomes 方法,完成实际的过滤匹配操作。

抽象类 FilteringSpringBootCondition 的相关源码如下【Spring Boot 2.7.9】:

abstract class FilteringSpringBootCondition extends SpringBootCondition
		implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {

	// 其他代码省略

	@Override
	public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
		ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
		// 调用 由子类实现的 getOutcomes 方法,完成实际的过滤匹配操作
		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
		boolean[] match = new boolean[outcomes.length];
		// 将 getOutcomes 方法返回结果转换成布尔数组
		for (int i = 0; i < outcomes.length; i++) {
			match[i] = (outcomes[i] == null || outcomes[i].isMatch());
			if (!match[i] && outcomes[i] != null) {
				logOutcome(autoConfigurationClasses[i], outcomes[i]);
				if (report != null) {
					report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
				}
			}
		}
		return match;
	}

	protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata);

	// 其他代码省略
}

通过上面源码可以看出,抽象类 FilteringSpringBootConditionmatch 方法主要是调用 getOutcomes 方法,并将其返回的结果转换成布尔数组。而这个 getOutcomes 方法是过滤匹配的核心功能,由抽象类 FilteringSpringBootCondition 的子类来实现它。

有关 OnBeanConditionOnClassConditionOnWebApplicationCondition 的内容由于篇幅受限,后续 Huazie 会再通过一篇博文详细讲解。

6. 触发自动配置事件

经过上面的排除和过滤之后,我们需要的自动配置类集合已经可以返回了。不过在返回之前,还需要再进行最后一步,触发自动配置导入事件,用来通知所有注册的自动配置监听器进行相关处理。

fireAutoConfigurationImportEvents(configurations, exclusions);

进入 fireAutoConfigurationImportEvents 方法,可以看到如下源码:

private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
	List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
	if (!listeners.isEmpty()) {
		AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
		for (AutoConfigurationImportListener listener : listeners) {
			invokeAwareMethods(listener);
			listener.onAutoConfigurationImportEvent(event);
		}
	}
}

接着,我们进入 getAutoConfigurationImportListeners 方法里,它是通过SpringFactoriesLoader 类提供的 loadFactories 方法将 spring.factories 中配置的接口 AutoConfigurationImportListener 的实现类加载出来。

protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
	return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

spring.factories 中配置的自动配置监听器,如下所示:

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

然后,将过滤出的自动配置类集合和被排除的自动配置类集合作为入参创建一个 AutoConfigurationImportEvent 事件对象;

其中 invokeAwareMethods(listener); 类似上面的 invokeAwareMethods(filter); 这里不再赘述了。

最后,调用上述自动配置监听器的 onAutoConfigurationImportEvent 方法,并传入上述获取的 AutoConfigurationImportEvent 事件对象,来通知所有注册的监听器进行相应的处理。

那这样做有什么好处呢?

通过触发 AutoConfigurationImportEvent 事件,来通知所有注册的监听器进行相应的处理,我们就可以在导入自动配置类之后,执行一些附加的自定义逻辑或修改自动配置行为。

总结

本篇 Huazie 带大家通读了 Spring Boot 自动装配逻辑的源码,详细分析了自动装配的后续流程,主要包含 自动配置的排除 和 过滤。超过万字,能够看到这的小伙伴,Huazie 在这感谢各位的支持。后续我将持续输出有关 Spring Boot 源码学习系列的博文,想要及时了解更新的朋友,订阅这里即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/905671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Freertos基础入门】同步互斥与通信

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、同步互斥与通信是什么&#xff1f;1.基础概念2.freertos通信可用的手段 二、同步与互斥的概念三、各类通信的区别与适用场景总结 前言 本系列基于stm32系列…

wustojc2005中英长度单位换算

#include <stdio.h> int main() {int n, inch;int foot;scanf("%d", &n);foot n /100.0/0.3048;inch (n/100.0/0.3048-foot)*12;printf("%d %d",foot,inch);return 0; }

使用PyMuPDF添加PDF水印

使用Python添加PDF水印的博客文章。 C:\pythoncode\new\pdfwatermark.py 使用Python在PDF中添加水印 在日常工作中&#xff0c;我们经常需要对PDF文件进行处理。其中一项常见的需求是向PDF文件添加水印&#xff0c;以保护文件的版权或标识文件的来源。本文将介绍如何使用Py…

Python自动化测试五种模型

一、前言 在自动化测试中&#xff0c;我们往往将自动化脚本都归纳属于哪种框架模型&#xff0c;比如关键字驱动模型等。 本篇将列举实际自动化测试中&#xff0c;Python 自动化测试的五种模型&#xff1a;线性模型、模块化驱动模型、数据驱动模型、关键字驱动模型、行为驱动模…

大模型框架LangChain开发实战(一)

一、概述 在大模型应用中&#xff0c;通常是基于框架来呼叫模型的&#xff0c;大模型提供了两个最重要的功能&#xff0c;一是提供了具体的intermediate steps&#xff08;即做事情的中间步骤&#xff0c;模型作为reasoning engine&#xff09;&#xff0c;二是提供了evaluati…

vue3实现容器内容滚动到底,触底加载新数据

scroll方式实现 在Vue3中&#xff0c;可以使用ref和onMounted钩子函数获取容器元素并监听滚动事件&#xff0c;判断内容是否滚动到了底部&#xff0c;从而触发加载新数据。 以下是一个简单的示例代码&#xff1a; <template><div class"container" ref&…

Kafka的底层“真面目”

简介 kafka是一个分布式消息队列。具有高性能、持久化、多副本备份、横向扩展能力。生产者往队列里写消息&#xff0c;消费者从队列里取消息进行业务逻辑。一般在架构设计中起到解耦、削峰、异步处理的作用。 kafka对外使用topic的概念&#xff0c;生产者往topic里写消息&…

并查集路径压缩(Java 实例代码)

目录 并查集路径压缩 Java 实例代码 UnionFind3.java 文件代码&#xff1a; 并查集路径压缩 并查集里的 find 函数里可以进行路径压缩&#xff0c;是为了更快速的查找一个点的根节点。对于一个集合树来说&#xff0c;它的根节点下面可以依附着许多的节点&#xff0c;因此&am…

Redis数据结构之String

String 类型是 Redis 的最基本的数据类型&#xff0c;一个 key 对应一个 value&#xff0c;可以理解成与Memcached一模一样的类型。 String 类型是二进制安全的&#xff0c;意思是 Redis 的 String 可以包含任何数据&#xff0c;比如图片或者序列化的对象&#xff0c;一个 Redi…

【Redis从头学-6】Redis中的Hash数据类型实战场景之购物车

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Re…

如何使用媒体查询(media query)来适配不同设备上的样式?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用媒体查询适配不同设备上的样式⭐ 基本语法⭐ 示例⭐ 常见的媒体特性⭐ 创建响应式布局⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来…

(2)、将SpringCache扩展功能封装为starter

(2)、将SpringCache扩展功能封装为starter 1、准备工作 前面我们写了一个common-cache模块,尽可能的将自定义的RedisConnectionFactory, RedisTemplate, RedisCacheManager等Bean封装了起来。 就是为了方便我们将其封装为一个Starter。 我们这里直接《SpringCache+Redis实…

LLM 生成式配置的推理参数温度 top k tokens等 Generative configuration inference parameters

在这个视频中&#xff0c;你将了解一些方法和相关的配置参数&#xff0c;这些参数可以用来影响模型在下一个词生成时的最终决策方式。如果你在Hugging Face网站或AWS的游乐场中使用过LLMs&#xff0c;你可能已经看到了这些控制选项&#xff0c;用来调整LLM的行为。每个模型都暴…

Communication Channels

沟通渠道 n * (n - 1) / 2 你1 相关方3 4 4 * 3 / 2 6 你1 相关方3 相关方1 5 5 * 4 / 2 10 人越多&#xff0c;沟通渠道越多&#xff0c;沟通成本理论越高

Qt文件系统操作和文件的读写

一、文件操作类概述 QIODevice&#xff1a;所有输入输出设备的基础类 QFile&#xff1a;用于文件操作和文件数据读写的类QSaveFile&#xff1a;用于安全保存文件的类QTemporaryFile&#xff1a;用于创建临时文件的类QTcpSocket和QUdpSocket&#xff1a;分别实现了TCP和UDP的类…

CSSCI、北核期刊投稿指南(2023年更新)

该数据为经管类的期刊投稿指南&#xff0c;包含发表难度&#xff0c;文章数量&#xff0c;影响因子&#xff0c;用户评价等指标。共5份文件&#xff0c;分别为国内所有期刊信息库、投稿指南&#xff08;CSSCI版本、CSSCI扩展版本、北大核刊版本、建议期刊版本&#xff09; 一、…

Risk Probability

风险概率计算 sum p1 * v1 p2 * v2 p3 * v3 ... pn * vn

memmove的实现与使用

memmove与memcpy相比&#xff0c;可以实现同一数组的赋值 memmove要点 1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 2.如果源空间和目标空间出现重叠&#xff0c;就得使用memmove函数处理。 函数实现 void* my_memmove(void* dest, void* sor…

【无监督】5、DINO | 使用自蒸馏和 transformer 来释放自监督学习的超能力(ICCV2021)

文章目录 一、背景二、相关工作三、方法四、效果 论文&#xff1a;Emerging Properties in Self-Supervised Vision Transformers 代码&#xff1a;https://github.com/facebookresearch/dino 出处&#xff1a;ICCV2021 | FAIR DINO&#xff1a; self-DIstillation with NO …

Ubuntu服务器service版本初始化

下载 下载路径 官网&#xff1a;https://cn.ubuntu.com/ 下载路径&#xff1a;https://cn.ubuntu.com/download 服务器&#xff1a;https://cn.ubuntu.com/download/server/step1 点击下载&#xff08;22.04.3&#xff09;&#xff1a;https://cn.ubuntu.com/download/server…