【Spring Boot 源码学习】OnClassCondition 详解

2023-11-15

Spring Boot 源码学习系列

在这里插入图片描述

引言

上篇博文带大家从源码深入了自动配置过滤匹配父类 FilteringSpringBootCondition,那么笔者接下来的博文将要介绍它的三个子类 OnClassConditionOnBeanConditionOnWebApplicationCondition 的实现。

往期内容

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

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

主要内容

话不多说,我们开始本篇的内容,重点详解 OnClassCondition 的实现。

1. getOutcomes 方法

OnClassCondition 也是 FilteringSpringBootCondition 的子类,我们首先从 getOutcomes 方法源码来分析【Spring Boot 2.7.9】:

// OnClassCondition 用于检查是否存在特定类
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {

	@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		// 如果有多个处理器可用,则拆分工作并在后台线程中执行一半。
		// 使用单个附加线程似乎可以提供最佳性能。
		// 线程越多,情况就越糟。
		if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
			return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
		}
		else {
			OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
					autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
			return outcomesResolver.resolveOutcomes();
		}
	}
	// ...
}

上述 getOutcomes 方法中,如果有多个处理器可用,则拆分工作并在后台线程中执行一半,使用单个附加线程似乎可以提供最佳性能【不过线程越多,情况就越糟】;否则,直接新建 StandardOutcomesResolver 来处理。

2. 多处理器拆分处理

先来看看 resolveOutcomesThreaded 的源码【Spring Boot 2.7.9】:

private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
	int split = autoConfigurationClasses.length / 2;
	OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);
	OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
			autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
	ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
	ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
	ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
	System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
	System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
	return outcomes;
}

进入 resolveOutcomesThreaded 方法,我们可以看到这里主要采用了分半处理的方法来提升处理效率【单个附加线程处理一半数据,主线程处理一半数据】。

我们来仔细分析一下:

  • 首先,获取自动配置类数组的一半长度,用于后续分半处理。

  • 然后,通过调用 createOutcomesResolver 方法【入参表示要处理自动配置类数组的前面一半的数据】创建了一个OutcomesResolver 对象 firstHalfResolver;进入 createOutcomesResolver 方法,我们可以看到这里是先新建了一个 StandardOutcomesResolver,然后将其作为构造函数入参,返回一个 ThreadedOutcomesResolver 对象,通过翻看代码,发现就是这里面会新启动一个线程来处理数据。

    private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
      		AutoConfigurationMetadata autoConfigurationMetadata) {
      	OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end,
      			autoConfigurationMetadata, getBeanClassLoader());
      	try {
      		return new ThreadedOutcomesResolver(outcomesResolver);
      	}
      	catch (AccessControlException ex) {
      		return outcomesResolver;
      	}
    }
    

    在这里插入图片描述

  • 接着,先新建了一个 StandardOutcomesResolver【其构造方法入参表示要处理自动配置类数组的后面一半的数据】,并赋值给 一个 OutcomesResolver 对象 secondHalfResolver

  • 最后,调用 firstHalfResolversecondHalfResolverresolveOutcomes 方法来处理自动配置类数据,并将处理结果合并到 outcomes 中返回。

通过上面分析,我们发现不论是 单个附加线程处理一半数据,还是 主线程处理一半数据,其核心还是 StandardOutcomesResolver 这个类。

3. StandardOutcomesResolver 内部类

下面我们来看看内部类 StandardOutcomesResolver 中的 resolveOutcomes 方法的实现代码【Spring Boot 2.7.9】:

private static final class StandardOutcomesResolver implements OutcomesResolver {
	// ...省略

	@Override
	public ConditionOutcome[] resolveOutcomes() {
		return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
	}
}

进入 resolveOutcomes 方法,我们可以看到这里直接调用了 getOutcomes 方法并返回处理结果,如下所示:

	private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
		for (int i = start; i < end; i++) {
			String autoConfigurationClass = autoConfigurationClasses[i];
			if (autoConfigurationClass != null) {
				String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
				if (candidates != null) {
					outcomes[i - start] = getOutcome(candidates);
				}
			}
		}
		return outcomes;
	}

上述逻辑也好理解,那就是遍历并处理自动配置类数组 autoConfigurationClasses 在 索引 startend - 1 之间的数据。其中循环里面:

  • 首先,获取要处理的自动配置类 autoConfigurationClass

  • 然后,通过调用 AutoConfigurationMetadata 接口的 get(String className, String key) 方法来获取与autoConfigurationClass 关联的名为 "ConditionalOnClass" 的条件属性值。而该 get 方法的具体实现可见 AutoConfigurationMetadataLoader 类,这个我们在上一篇博文中也提及到,它会加载 META-INF/spring-autoconfigure-metadata.properties 中的配置。
    在这里插入图片描述

    final class AutoConfigurationMetadataLoader {
      	// ... 省略
    
      	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {
      		// ... 省略
      		@Override
      		public String get(String className, String key) {
      			return get(className, key, null);
      		}
    
      		@Override
      		public String get(String className, String key, String defaultValue) {
      			String value = this.properties.getProperty(className + "." + key);
      			return (value != null) ? value : defaultValue;
      		}
      	}
    }
    

    通过上述截图和代码,我们可以看到 AutoConfigurationMetadataLoader 的内部类PropertiesAutoConfigurationMetadata 实现了 AutoConfigurationMetadata 接口的具体方法,其中就包含上述用到的 get(String className, String key) 方法。

    仔细查看 get 方法的实现,我们不难发现上述 getOutcomes 方法中获取的 candidates,其实就是 META-INF/spring-autoconfigure-metadata.properties 文件中配置的 key自动配置类名.ConditionalOnClass 的字符串,而 value 为其获得的值。

    我们以 RedisCacheConfiguration 为例,可以看到如下配置:

    在这里插入图片描述

  • 最后,调用 getOutcome(String candidates) 方法来完成最后的过滤匹配工作。

    下面来看看相关的源码实现:

    private ConditionOutcome getOutcome(String candidates) {
    	try {
    		if (!candidates.contains(",")) {
    			return getOutcome(candidates, this.beanClassLoader);
    		}
    		for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
    			ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
    			if (outcome != null) {
    				return outcome;
    			}
    		}
    	}
    	catch (Exception ex) {
    		// We'll get another chance later
    	}
    	return null;
    }
    

    如果 candidates 不包含逗号,说明只有一个,直接调用 getOutcome(String className, ClassLoader classLoader) 返回过滤匹配结果;否则就是包含多个,调用 StringUtils.commaDelimitedListToStringArray(candidates) 将逗号分隔的字符串(如candidates)转换为一个字符串数组,然后遍历处理,还是调用 getOutcome(String className, ClassLoader classLoader) 过滤匹配结果,如果 outcome 不为空,则直接返回 outcome

    StringUtils.commaDelimitedListToStringArray(candidates) 它会根据逗号来分割输入的字符串,并移除每个元素中的空格。返回的字符串数组包含了被分割后的各个元素

    下面我们直接进入 getOutcome(String className, ClassLoader classLoader) 方法查看其源码:

    private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
    	if (ClassNameFilter.MISSING.matches(className, classLoader)) {
    		return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
    			.didNotFind("required class")
    			.items(Style.QUOTE, className));
    	}
    	return null;
    }
    

    我们这里可以看到上面介绍过的 ClassNameFilter.MISSING ,它是用于校验指定的类是否加载失败。而这里意思就是如果 className 对应的类不存在,则返回没有满足过滤匹配的结果【即 ConditionOutcome.noMatch.didNotFind ,其中不存在需要的类】;否则返回 null

结合 FilteringSpringBootCondition 的介绍,我们知道了 OnClassConditiongetOutComes 方法判断的是 自动配置类关联的 OnClassCondition 配置属性对应的类,如果它存在,则后面处理时保留自动配置类;否则,后面会清空自动配置类;

4. getMatchOutcome 方法

通过翻看源码,我们其实也可以发现,OnClassCondition 类还实现了 FilteringSpringBootCondition 的父类 SpringBootCondition 中的抽象方法。

如下是 SpringBootCondition 类的部分源码【Spring Boot 2.7.9】:

public abstract class SpringBootCondition implements Condition {

	// ...

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			// ...
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			// ...
		}
		catch (RuntimeException ex) {
			// ...
		}
	}

	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

}

SpringBootCondition 中有个最终方法 matches,该方法逻辑很简单,就是调用 getMatchOutcome 方法获取过滤匹配结果,然后通过 outcome.isMatch() 返回过滤匹配结果值【true:满足过滤匹配 false:不满足过滤匹配

简单了解上述内容之后,我们继续看 OnClassConditiongetMatchOutcome 的完整实现:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	ClassLoader classLoader = context.getClassLoader();
	ConditionMessage matchMessage = ConditionMessage.empty();
	List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
	if (onClasses != null) {
		List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
		if (!missing.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
				.didNotFind("required class", "required classes")
				.items(Style.QUOTE, missing));
		}
		matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
			.found("required class", "required classes")
			.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
	}
	List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
	if (onMissingClasses != null) {
		List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
		if (!present.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
				.found("unwanted class", "unwanted classes")
				.items(Style.QUOTE, present));
		}
		matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
			.didNotFind("unwanted class", "unwanted classes")
			.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
	}
	return ConditionOutcome.match(matchMessage);
}

上面的逻辑大致可以总结为如下两处:

  • 获取自动配置类上的 ConditionalOnClass 注解配置的类,然后调用父类 FilteringSpringBootCondition 中的 filter 方法,获取匹配失败的类集合。
    如果匹配失败的类集合不为空,则返回不满足过滤匹配的结果【即 ConditionOutcome.noMatch.didNotFind,其中不存在需要的类】

    List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
    if (!missing.isEmpty()) {
    	return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
    		.didNotFind("required class", "required classes")
    		.items(Style.QUOTE, missing));
    }
    

    如果匹配失败的集合为空,则添加满足过滤匹配的结果,并返回【即 ConditionMessage.empty.andCondition.found,其中找到了需要的类】。

    matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
    		.found("required class", "required classes")
    		.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
    
    return ConditionOutcome.match(matchMessage);
    
  • 获取自动配置类上的 ConditionalOnMissingClass 注解配置的类,然后调用父类 FilteringSpringBootCondition 中的 filter 方法,获取匹配成功的类集合。
    如果匹配成功的类集合不为空,则返回不满足过滤匹配的结果【即 ConditionOutcome.noMatch.found,其中存在不想要的类】

    List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
    if (!present.isEmpty()) {
    	// 找到了不想要的类
    	return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
    		.found("unwanted class", "unwanted classes")
    		.items(Style.QUOTE, present));
    }
    

    如果匹配成功的类集合为空,则添加满足过滤匹配的结果【即 ConditionMessage.empty.andCondition.didNotFind,其中没有找到不想要的类】。

    matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
    	.didNotFind("unwanted class", "unwanted classes")
    	.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
    
    return ConditionOutcome.match(matchMessage);
    

总结

本篇 Huazie 带大家介绍了自动配置过滤匹配子类 OnClassCondition,内容较多,感谢大家的支持;笔者接下来的博文还将详解 OnBeanConditionOnWebApplicationCondition 的实现,敬请期待!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Spring Boot 源码学习】OnClassCondition 详解 的相关文章

随机推荐

  • 【MySql】复合查询

    文章目录 回顾基本查询 多表查询 自连接 子查询 合并查询 OJ题目 前面我们讲解的mysql表的查询都是对一张表进行查询 在实际开发中这远远不够 回顾基本查询 回顾一下前面所学的基本查询 通过一些案例来练习回顾 查询工资高于500或岗位为
  • Discuz论坛添加一行即可保留安装插件和模板里面的xml文件 可以无限制安装了

    请首先打开 config config global php 文件 在文件结尾添加以下代码开启插件设计者模式 config plugindeveloper 1
  • Ubuntu 系统下如何远程访问 Windows 桌面 ?

    你一定听说过 Windows 应用程序远程桌面连接 该应用程序系统自带不用安装 并允许您远程访问另一台 PC 或服务器 它使用远程桌面协议建立远程桌面连接会话 一些 Linux 发行版可能会提供 RDP 客户端来连接到 Windows 系统
  • 最近发现一个爬虫开源项目weixin_crawler

    最近发现一个爬虫开源项目weixin crawler weixin crawler weixin crawler是一款使用Scrapy Flask Echarts Elasticsearch等实现的微信公众号文章爬虫 自带分析报告和全文检索
  • Qt Xlsx的基本使用

    前言 在很多时候 我们需要将数据导出 之前我是将数据表导出为CSV格式 CSV文件虽然可以用Excel打开 但相对于真正的Xlsx文件 还是有许多不足的 偶然之间 发现了QtXlsx这个第三方库 1 QtXlsx官网对QtXlsx的介绍 Q
  • 死锁的排查工具有哪些?

    死锁是多线程编程中常见的问题 可以使用多种工具和技术来排查和分析死锁问题 以下是一些常用的死锁排查工具和方法 JConsole 和 JVisualVM 这些是Java自带的监控工具 可以用于监视Java应用程序的运行状态 通过查看线程状态和
  • matlab 集成学习,集成学习

    假设你去随机问很多人一个很复杂的问题 然后把它们的答案合并起来 通常情况下你会发现这个合并的答案比一个专家的答案要好 这就叫做群体智慧 同样的 如果你合并了一组分类器的预测 像分类或者回归 你也会得到一个比单一分类器更好的预测结果 这一组分
  • 给定一个仅包含小写字母的字符串,求它的最长回文子串的长度。

    include
  • 2轴直线插补程序分析

    1 数据采样插补法 在CNC系统中较广泛采用的另一种插补计算方法即所谓数据采样插补法 或称为时间分割法 它尤其适合于闭环和半闭环以直流或交流电机为执行机构的位置采样控制系统 这种方法是把加工一段直线或圆弧的整段时间细分为许多相等的时间间隔
  • 计算机视觉智能中医(六):基于曲线拟合舌体胖瘦的自动分析

    返回至系列文章导航博客 文章目录 1 简介 2 原理讲解 多项式曲线拟合 2 1 舌体曲线拟合参数与形状的关系 2 2 胖瘦指数定义 3 具体实现过程 4 代码实现 4 1 contour to py 4 2 outline cut py
  • 生成项目树形结构

    当你写博客或者项目描述的时候 想要展示项目的树形结构 怎么办呢 手写 程序员是不可能手写的 win R输入cmd进入dos 输入命令 tree 项目根目录 gt 生成的树形结构文件保存地址 比如 tree E workspace flink
  • ORB-SLAM2:基于可识别特征的自主导航与地图构建

    目录 ORB SLAM2 基于可识别特征的自主导航与地图构建 简介 地图 A 地图特征点或3D ORB B 关键帧 C 可视化图像 位置识别 A 图像识别数据库 B 高效优化的ORB匹配 C 视觉一致性 自主导航追踪 A ORB特征获取 B
  • Vue中使用element-ui使用表单提交时间

    在Vue中使用element ui组件的表单提交 想要上传笔记创建的时间 出现的问题 element ui的源码
  • SQLPub免费的MySQL数据库

    SQLPub免费的MySQL数据库 提供最新版本 甚至是开发者版本的 MySQL 服务器测试服务 您可以轻易地 注册免费账号 测试您的应用 例如 您可以测试在MySQL版本升级后您的应用是否依然能够正常运行 sqlpub com 也是让您学
  • javaWeb数据库连接池,过滤器和监听器

    数据库连接池 JDBC 1 什么是数据库连接池 是一个数据库的工具 能够分配 管理和释放数据库连接 它允许应用程序重复使用一个现有的数据库连接 而不是再重新建立一个 常见数据库连接池 C3P0 是一个开放源代码的JDBC连接池 它在lib目
  • sd模型分类

    标题模型主要分为四类 Checkpoint LoRA Textual Inversion Hypernetwork 分别对应 4 种不同的训练方式 Checkpoint 通过 Dreambooth 训练方式得到的大模型 特点是出图效果好 但
  • 【MySQL】使用Visio绘制E-R图

    使用Visio绘制E R图 1 创建项目 文件 新建 常规 基本框图 2 调整页面方向 纵向或横向 文件 页面设置 3 准备E R图的三个基本形状 实体用矩形 关系用菱形 属性用椭圆 4 绘制E R图 双击形状后可以在形状中编辑文字 通过绘
  • Cursor!!!GPT-4帮我写代码

    首先介绍一款产品 cursor 官网 https www cursor so IDE作者 https twitter com amanrsanger 目前为止应该是第一个免费能够使用GPT4工作的软件 看作者的Twitter 他说自己提前向
  • 由于找不到d3dx9_43.dll无法继续执行此代码怎么修复?这个三个方法可以解决问题

    在运行游戏 软件的时候 计算机提示 由于找不到d3dx9 43 dll无法继续执行此代码 是怎么回事 其实d3dx9 43 dll是Windows操作系统下的DirectX9的一个组件 而DirectX是Windows系统支持游戏和显卡游戏
  • 【Spring Boot 源码学习】OnClassCondition 详解

    Spring Boot 源码学习系列 OnClassCondition 详解 引言 往期内容 主要内容 1 getOutcomes 方法 2 多处理器拆分处理 3 StandardOutcomesResolver 内部类 4 getMatc