【Spring Boot 源码学习】OnClassCondition 详解

news2024/11/19 7:27:12

Spring Boot 源码学习系列

在这里插入图片描述

OnClassCondition 详解

  • 引言
  • 往期内容
  • 主要内容
    • 1. getOutcomes 方法
    • 2. 多处理器拆分处理
    • 3. StandardOutcomesResolver 内部类
    • 4. getMatchOutcome 方法
  • 总结

引言

上篇博文带大家从源码深入了自动配置过滤匹配父类 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 的实现,敬请期待!!!

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

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

相关文章

说完 Java 的 Abstract 后再来说说接口 (interface )

如你对 Abstract 修饰的抽象类不是非常了解的话&#xff0c;请自行先考古下。 这篇文章需要对 Java 定义过的抽象类有一些基本的了解才可以。 抽象类和抽象方法 用 Abstract 修饰的类&#xff0c;叫做抽象类&#xff0c;那么用 Abstract 修饰的方法叫做抽象方法。 在 Java 中…

LabVIEW更改Tab所选标签的颜色

LabVIEW更改Tab所选标签的颜色 在开发过程中&#xff0c;有时会出现要将不同tab页设置不同颜色的情况。此VI允许编程方式更改前面板选项卡控件上选项卡的颜色。它是突出显示所选选项卡的理想选择 在某些应用程序中&#xff0c;用户希望在按下时突出显示选项卡控件。此VI使用事…

算法从未放弃你,放弃你的只有你自己

在人生的旅程中&#xff0c;我们常常会遇到各种挫折和困难。有些人在面对困境时&#xff0c;会选择放弃&#xff0c;将责任归咎于命运或外部环境。然而&#xff0c;算法教给我们一个重要的道理&#xff1a;永远不要放弃 当我们遇到问题或挑战时&#xff0c;算法可以帮助我们找到…

AWT中常用组件

基本组件 组件名 功能 Button Button Canvas 用于绘图的画布 Checkbox 复选框组件&#xff08;也可当做单选框组件使用&#xff09; CheckboxGroup 用于将多个Checkbox 组件组合成一组&#xff0c; 一组 Checkbox 组件将只有一个可以 被选中 &#xff0c; 即全部变成单…

python 使用requests爬取百度图片并显示

爬取百度图片并显示 引言一、图片显示二、代码详解2.1 得到网页内容2.2 提取图片url2.3 图片显示 三、完整代码 引言 爬虫&#xff08;Spider&#xff09;&#xff0c;又称网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;是一种自动化程序&#xff0c;可以自动地浏览…

C++数据结构--红黑树

目录 一、红黑树的概念二、红黑树的性质三、红黑树的节点的定义四、红黑树结构五、红黑树的插入操作参考代码 五、代码汇总 一、红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过…

数字花园的指南针:微信小程序排名的提升之道

微信小程序&#xff0c;是一片数字花园&#xff0c;其中各种各样的小程序竞相绽放&#xff0c;散发出各自独特的芬芳。在这个花园中&#xff0c;排名优化就像是精心照料花朵的园丁&#xff0c;让我们一同走进这个数字花园&#xff0c;探寻如何提升微信小程序的排名优化&#xf…

Idea项目爆红

解决办法&#xff1a; 方案一&#xff1a;重新加载Maven依赖 方案二&#xff1a;清除缓存 方案三&#xff1a; 在当前项目下执行以下命令&#xff0c;重新生成.iml文件 mvn idea:module

Hadoop的安装和使用,Windows使用shell命令简单操作HDFS

1&#xff0c;Hadoop简介 Hadoop是一个能够对大量数据进行分布式处理的软件框架&#xff0c;并且是以一种可靠、高效、可伸缩的方式进行处理的&#xff0c;它具有以下几个方面的特性。 高可靠性。 高效性。 高可扩展性。 高容错性。 成本低。 运行在Linux平台上。 支持多种编程…

【Redis7】--2.十大数据类型

文章目录 Redis十大数据类型1.Key通用命令1.1keys *1.2EXISTS1.3DEL1.4EXPIRE1.5TTL1.6TYPE1.7DBSIZE1.8SELECT1.9MOVE1.10FLUSHDB1.11FLUSHALL1.12help1.13CONFIG 2.Redis十大数据类型2.1String2.1.1SET和GET2.1.2MSET和MGET2.1.3INCR、INCRBY2.1.4SETNX和SETEX2.1.5MSETNX2.1…

Android笔记(二十九):利用python自动生成多语言

背景 项目需要支持十几种多语言&#xff0c;而且每个版本的新功能ui都有很多地方需要多语言&#xff0c;如果手动添加非常耗时&#xff0c;于是设计了一个python脚本&#xff0c;通过excel表格转化多语言到项目values/strings文件内 步骤 android工程项目结构 脚本位于langu…

Unity实现用WASD控制一个物体前后左右移动-小白课程01

1 根据业务逻辑搭建场景 02 根据业务写代码 using System.Collections; using System.Collections.Generic; using UnityEngine;//实现让被挂在的物体往前移动 //按下W键往前移动&#xff0c;按下S键往后移动 public class RoleMove : MonoBehaviour { public float myspe…

Enterprise Architect15(EA) 工具栏,隐藏后显示快捷方式

没有工具栏 显示工具栏 快捷键&#xff1a;ctrl shift 3 或者Design-->点击ToolBox 工具栏中直接拖动即可创建对应的元素&#xff1a;

springboot集成qq邮箱

1.maven依赖 <!-- email依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>org.springframework.boot</group…

seata的部署和集成:部署Seata的tc-server、微服务集成seata、TC服务的高可用和异地容灾

seata的部署和集成 一、部署Seata的tc-server 1.下载 首先我们要下载seata-server包&#xff0c;地址在http&#x1f615;/seata.io/zh-cn/blog/download.html 当然&#xff0c;课前资料也准备好了&#xff1a; 2.解压 在非中文目录解压缩这个zip包&#xff0c;其目录结构…

git 合并分支某次(commit)提交

需求&#xff1a;将develop分支某次提交合并到master上面&#xff0c;其他修改不同步&#xff1b; //切换到master分支 git checkout master //查看develop分支提交记录&#xff0c;获取对应记录哈希值&#xff1b; git log develop // 按上下按钮可以上下查询对应记录&#xf…

分享一个python实验室设备预约管理系统 实验室设备维修系统源码 lw 调试

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

【Word】页眉编辑小技巧

页眉编辑小技巧 1 奇偶页不同2 仅设置正文有页眉3 页眉设置信息为章节内容参考 1 奇偶页不同 2 仅设置正文有页眉 1、定位到目录页之后&#xff0c;点击“布局——分隔符——分节符中的下一页”&#xff0c;在目录页和正文之间插入一个分节符&#xff0c;使得目录页和正文成为…

YOLOv5算法改进(16)— 增加小目标检测层

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。小目标检测层是指在目标检测任务中用于检测小尺寸目标的特定网络层。由于小目标具有较小的尺寸和低分辨率&#xff0c;它们往往更加难以检测和定位。YOLOv5算法的检测速度与精度较为平衡&#xff0c;但是对于小目标的检测效…

[管理与领导-85]:IT基层管理者 - 核心技能 - 高效执行力 - 10 - 高效执行力的9个段位

目录 前言&#xff1a; 一段&#xff1a;准确执行&#xff0c;快速反应&#xff0c;坚决执行 &#xff08;态度很重要&#xff09; 二段&#xff1a;结果导向 苦劳过后&#xff0c;有功劳&#xff08;有结果很重要&#xff09; 三段&#xff1a;有始有终 主动反馈、有始有终…