【Spring Boot 源码学习】OnClassCondition 详解

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 {@Overrideprotected 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 {// ...省略@Overridepublic 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 {// ... 省略@Overridepublic String get(String className, String key) {return get(className, key, null);}@Overridepublic 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 {// ...@Overridepublic 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.mzph.cn/news/75864.shtml

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

相关文章

尚硅谷大数据项目《在线教育之离线数仓》笔记007

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第12章 报表数据导出 P112 01、创建数据表 02、修改datax的jar包 03、ads_traffic_stats_by_source.json文件 P113 P114 P115 P116 P117 P118 P119 P120 P121 P122【122_在…

小米13Pro/13Ultra刷面具ROOT后激活LSPosed框架微X模块详细教程

喜欢买小米手机&#xff0c;很多是因为小米手机的开放&#xff0c;支持root权限&#xff0c;而ROOT对普通用户来说更多的是刷入DIY模块功能&#xff0c;今天ROM乐园小编就教大家如何使用面具ROOT&#xff0c;实现大家日常情况下非常依赖的微X模块功能&#xff0c;体验微X模块的…

Redis原理:动态字符串SDS

&#xff08;课程总结自b站黑马程序员课程&#xff09; 一、引言 Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&…

DHTMLX Gantt 8.0.5 Crack -甘特图

8.0.5 2023 年 9 月 1 日。错误修复版本 修复 修复通过gantt.getGanttInstance配置启用扩展而触发的错误警告修复启用skip_off_time配置时gantt.exportToExcel()的不正确工作示例查看器的改进 8.0.4 2023 年 7 月 31 日。错误修复版本 修复 修复数据处理器不跟踪资源数据…

微信小程序slot插槽的介绍,以及如何通过uniapp使用动态插槽

微信小程序文档 - slots介绍 由上述文档看俩来&#xff0c;微信小程序官方并没有提及动态插槽内容。 uniapp文档 - slots介绍 uni官方也未提及关于动态插槽的内容 在实际使用中&#xff0c;直接通过 <<slot :name"item.xxx" /> 这种形式会报错&#xff…

23062C++QTday4

仿照string类&#xff0c;完成myString 类 代码&#xff1a; #include <iostream> #include <cstring> using namespace std; class myString {private:char *str; //记录c风格的字符串int size; //记录字符串的实际长度public://无参构造my…

分布式AKF拆分原则

目录 1 前言2 什么是AKF3 如何基于 AKF X 轴扩展系统&#xff1f;4 如何基于 AKF Y 轴扩展系统&#xff1f;5 如何基于 AKF Z 轴扩展系统&#xff1f;6 小结 1 前言 当我们需要分布式系统提供更强的性能时&#xff0c;该怎样扩展系统呢&#xff1f;什么时候该加机器&#xff1…

项目打包docker镜像 | 上传nexus | jenkins一键构建

文章目录 前言准备实操1、打开docker的远程访问2、编写dockerfile文件3、指定nexus环境4、配置jenkins5、使用jenkins构建 总结 前言 Docker部署项目是指使用Docker容器化技术将应用程序及其依赖项打包成一个独立的、可移植的运行环境&#xff0c;并在各种操作系统和平台上进行…

Unreal Engine Loop 流程

引擎LOOP 虚幻引擎的启动是怎么一个过程。 之前在分析热更新和加载流程过程中&#xff0c;做了一个图。记录一下&#xff01;&#xff01; ![在这里插入图片描述](https://img-blog.csdnimg.cn/f11f7762f5dd42f9b4dd9b7455fa7a74.png#pic_center 只是记录&#xff0c;以备后用…

使用LightPicture开源搭建私人图床:详细教程及远程访问配置方法

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

大数据技术之Hadoop:Yarn集群部署(七)

目录 一、部署说明 二、集群规划 三、开始配置 3.1 MapReduce配置文件 3.2 YARN配置文件 3.3 分发配置文件 四、集群启停 4.1 命令介绍 4.2 演示 4.3 查看YARN的WEB UI页面 一、部署说明 Hadoop HDFS分布式文件系统&#xff0c;我们会启动&#xff1a; NameNode进…

LeGo-LOAM 源码解析

文章目录 0、整体框架1、imageProjection —— 点云分割0. main()1. cloudHandler()2. copyPointCloud()3. findStartEndAngle()4. projectPointCloud()5. groundRemoval()6. cloudSegmentation()7. labelComponents()8. publishCloud()9. resetParameters() 2、featureAssocia…

java多线程(超详细)

1 - 线程 1.1 - 进程 进程就是正在运行中的程序&#xff08;进程是驻留在内存中的&#xff09; 是系统执行资源分配和调度的独立单位 每一进程都有属于自己的存储空间和系统资源 注意&#xff1a;进程A和进程B的内存独立不共享。 1.2 - 线程 线程就是进程中的单个顺序控制…

无涯教程-JavaScript - OCT2HEX函数

描述 OCT2HEX函数将八进制数转换为十六进制。 语法 OCT2HEX (number, [places])争论 Argument描述Required/OptionalNumber 您要转换的八进制数。 数字不得超过10个八进制字符(30位)。数字的最高有效位是符号位。其余的29位是幅度位。 负数使用二进制补码表示。 RequiredPl…

C# OpenCvSharp 通道分离

效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp; using OpenCvSharp.Extensions;namespac…

平板触控笔哪款好用?好用的第三方apple pencil

而对于那些把ipad当做学习工具的人而言&#xff0c;苹果Pencil就成了必备品。但因为苹果Pencil太贵了&#xff0c;不少的学生们买不起。因此&#xff0c;最佳的选择还是平替电容笔&#xff0c;今天在这里整理了一些高性价比的电容笔&#xff01; 一、挑选电容笔的要点&#xf…

springboot之二:整合junit进行单元测试+整合redis(本机、远程)+整合mybatis

资源地址&#xff1a; 整合junit的代码&#xff1a;https://download.csdn.net/download/zhiaidaidai/88291527 整合redis的代码&#xff1a;https://download.csdn.net/download/zhiaidaidai/88291536 整合mybatis的代码&#xff1a;https://download.csdn.net/download/zh…

零基础教程:使用yolov8训练无人机VisDrone数据集

1.准备数据集 1.先给出VisDrone2019数据集的下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1e2Q0NgNT-H-Acb2H0Cx8sg 提取码&#xff1a;31dl 2.将数据集VisDrone放在datasets目录下面 2.数据集转换程序 1.在根目录下面新建一个.py文件&#xff0c;取名叫…

使用POI实现操作Excel文件。

1、添加依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-o…

[keil] uv编译分析

假设Keil安装路径: C:\Keil_v5\ 假设工程在 d:\HELLO , 工程Targets名:Simulator [在Manage Project Items中可修改] 如下指令为:Build(F7) C:\Keil_v5\UV4\UV4.exe -b d:\HELLO\Hello.uvproj -j0 -t Simulator -o d:\HELLO\uv4.log 如下指令为:Rebuild(CtrlAltF7) C:\Kei…