springBoot 源码二:各种条件注解解析

news/2024/5/3 9:38:08/文章来源:https://blog.csdn.net/admin522043032/article/details/126642140

springboot各种条件注解解析

上一篇分析了springboot的自动配置过程。springboot拿到了需要自动配置的全类名,去加载那些自动配置类。就以springboot自动配置的tomcat举例。会根据不同的条件注解来判断是否加载配置类
在这里插入图片描述
那么springboot的条件注解有哪些呢?
条件注解
SpringBoot中的条件注解有:

  1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
  2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
  3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
  4. ConditionalOnClass:是否存在某个类
  5. ConditionalOnMissingClass:是否缺失某个类
  6. ConditionalOnExpression:指定的表达式返回的是true还是false
  7. ConditionalOnJava:判断Java版本
  8. ConditionalOnJndi:JNDI指定的资源是否存在
  9. ConditionalOnWebApplication:当前应用是一个Web应用
  10. ConditionalOnNotWebApplication:当前应用不是一个Web应用
  11. ConditionalOnProperty:Environment中是否存在某个属性
  12. ConditionalOnResource:指定的资源是否存在
  13. ConditionalOnWarDeployment:当前项目是不是以War包部署的方式运行
  14. ConditionalOnCloudPlatform:是不是在某个云平台上

所有的条件注解都是基于@Conditional来实现的。关于@Conditional属于spring的知识 这里不再赘述。在spring的生命周期中分析过。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所有的条件注解都是通过@Conditional加上对应的条件判断类来实现的。通过几个常用的条件注解类进行分析。
@ConditionalOnBean
@ConditionalOnBean对应的条件判断类是OnBeanCondition。
在这里插入图片描述
首先会调用Condition接口的matches方法。Condition是接口,那么就会调到SpringBootCondition的matches方法

@Overridepublic final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 针对每个条件注解进行条件判断// 条件注解写在了哪个类上,或哪个方法上String classOrMethodName = getClassOrMethodName(metadata);try {// 条件的判断结果ConditionOutcome outcome = getMatchOutcome(context, metadata);// 如果log的日志级别为trace,那就直接记录当前条件的判断结果logOutcome(classOrMethodName, outcome);// 将判断结果记录到ConditionEvaluationReport中//ConditionEvaluationReportLoggingListener会在收到ContextRefreshedEvent事件后把判断结果用日志的方式打印出来recordEvaluation(context, classOrMethodName, outcome);return outcome.isMatch();}catch (NoClassDefFoundError ex) {throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "+ "that class. This can also happen if you are "+ "@ComponentScanning a springframework package (e.g. if you "+ "put a @ComponentScan in the default package by mistake)", ex);}catch (RuntimeException ex) {throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);}}

判断的方法在于ConditionOutcome outcome = getMatchOutcome(context, metadata);它是一个抽象方法

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

那么会调到OnBeanCondition的getMatchOutcome方法

@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage matchMessage = ConditionMessage.empty();MergedAnnotations annotations = metadata.getAnnotations();// 如果存在ConditionalOnBean注解if (annotations.isPresent(ConditionalOnBean.class)) {//封装Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);//底层通过获取bean工厂,从bean工厂获取对应的bean,把结果封装到MatchResult MatchResult matchResult = getMatchingBeans(context, spec);// 如果某个Bean不存在if (!matchResult.isAllMatched()) {String reason = createOnBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(spec.message().because(reason));}// 所有Bean都存在matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,matchResult.getNamesOfAllMatches());}// 如果存在ConditionalOnSingleCandidate注解if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);MatchResult matchResult = getMatchingBeans(context, spec);// Bean不存在if (!matchResult.isAllMatched()) {return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());}// Bean存在Set<String> allBeans = matchResult.getNamesOfAllMatches();// 如果只有一个if (allBeans.size() == 1) {matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);}else {// 如果有多个List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,spec.getStrategy() == SearchStrategy.ALL);// 没有主Bean,那就不匹配if (primaryBeans.isEmpty()) {return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));}// 有多个主Bean,那就不匹配if (primaryBeans.size() > 1) {return ConditionOutcome.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));}// 只有一个主BeanmatchMessage = spec.message(matchMessage).found("a single primary bean '" + primaryBeans.get(0) + "' from beans").items(Style.QUOTE, allBeans);}}// 存在ConditionalOnMissingBean注解if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,ConditionalOnMissingBean.class);MatchResult matchResult = getMatchingBeans(context, spec);//有任意一个Bean存在,那就条件不匹配if (matchResult.isAnyMatched()) {String reason = createOnMissingBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(spec.message().because(reason));}// 都不存在在,则匹配matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();}return ConditionOutcome.match(matchMessage);}

上述方法中首先判断如果存在@ConditionalOnBean注解,就会从bean工厂中获取bean,如果能找到,那么就匹配成功,如果找不到就匹配失败,由于@ConditionalOnBean的属性值可能是多个,那么就必须满足所有bean都能找到才算成功。然后不论成功失败,都把结果封装到MatchResult 当中。在判断MatchResult 的结果,如果失败了 就直接返回,如果成功了 接着判断。接着往下判断是否存在@ConditionalOnSingleCandidate直接,如果存在接着判断bean是否存在,是否只有一个bean,等等。判断完这层就会接着判断是否存在@ConditionalOnMissingBean注解。同理判断有没有这个bean。
也就是说OnBeanCondition同时判断了@ConditionalOnBean,@ConditionalOnSingleCandidate,@ConditionalOnMissingBean。
在这里插入图片描述
在这里插入图片描述
可以看到这两个注解用的也正是OnBeanCondition这个条件类。
条件注解的执行过程就是这样其他注解也同理。
上一篇分析过在从spring.factories获取到所有自动配置类和过滤器filter。首先会通过filter进行过滤,过滤完才会交给springboot加载。接下来就分析如何过滤。
获取到的过滤器filter有
在这里插入图片描述

List<String> filter(List<String> configurations) {long startTime = System.nanoTime();String[] candidates = StringUtils.toStringArray(configurations);boolean skipped = false;// 逐个利用AutoConfigurationImportFilter来判断所有的自动配置类的条件是否匹配,匹配结果存在match数组中// 先利用OnBeanCondition进行过滤// 再利用OnClassCondition进行过滤// 再利用OnWebApplicationCondition进行过滤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;}// 把匹配的记录在result集合中,最后返回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.match(candidates, this.autoConfigurationMetadata)这部分。还是通过OnBeanCondition举例。由于OnBeanCondition没有实现match方法,调的就是父类FilteringSpringBootConditionmatch方法。

@Overridepublic boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);// autoConfigurationMetadata就是spring-autoconfigure-metadata.properties中的内容ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);boolean[] match = new boolean[outcomes.length];for (int i = 0; i < outcomes.length; i++) {match[i] = (outcomes[i] == null || outcomes[i].isMatch());// 首轮不匹配的,进行日志打印,以及记录到ConditionEvaluationReport中去if (!match[i] && outcomes[i] != null) {logOutcome(autoConfigurationClasses[i], outcomes[i]);if (report != null) {report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);}}}return match;}

核心代码就是ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);用来判断是否通过,判断能完成后把结果放到boolean[]数组中返回。而getOutcomes是抽象方法,就会调到子类也就是OnBeanCondition的getOutcomes方法

	@Overrideprotected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata) {ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];// 遍历处理每个自动配置类for (int i = 0; i < outcomes.length; i++) {String autoConfigurationClass = autoConfigurationClasses[i];if (autoConfigurationClass != null) {// 当前自动配置中@ConditionalOnBean所依赖的类Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");// 如果onBeanTypes都存在,则返回nulloutcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);if (outcomes[i] == null) {// 继续判断@ConditionalOnSingleCandidate所依赖的类Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,"ConditionalOnSingleCandidate");outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);}}}return outcomes;}

到此条件注解的流程就结束了,其他两个条件类流程同理。

总结

对于springboot各种条件注解,首先获取的条件注解中@Conditional的值。调用其抽象父类SpringBootCondition的matches方法。该方法中通过getMatchOutcome(context, metadata)方法将结果封装成ConditionOutcome 。同时getMatchOutcome是抽象方法,具体实现在子类。如果matches方法返回true表示可以加载,返回false表示不需要加载。

在自动配置过程中,从MATE-INF/spring.factories获取到自动配置类和过滤器filter。在过滤的过程中调用了FilteringSpringBootConditionmatch方法。该方法中getOutcomes(autoConfigurationClasses, autoConfigurationMetadata)用于判断是否通过,是一个抽象方法在具体子类实现,也就是调用了OnBeanCondition、OnClassCondition、OnWebApplicationCondition的getOutcomes方法。

条件注解案例

使用几个常用的条件注解
@ConditionalOnMissingBean和@ConditionalOnBean

@Component
@ConditionalOnMissingBean(DemoBean.class)
public class AaMissBean {public void test(){System.out.println("AaMissBean------------test");}
}@Component
@ConditionalOnBean(DemoBean.class)
public class AaOnBean {public void test(){System.out.println("AaBean------------test");}
}@Component
public class DemoBean {}

测试

@SpringBootApplication
public class Application {public static void main(String[] args) {ApplicationContext run = SpringApplication.run(Application.class, args);Object onBean = run.getBean("aaOnBean");System.out.println(onBean);Object missBean = run.getBean("aaMissBean");System.out.println(missBean);}
}

由于springboot中存在DemoBean ,那么AaOnBean 就会被加载,而AaMissBean 不会被加载
在这里插入图片描述
如果去掉DemoBean 的@Component注解,则相反 那么AaMissBean就会被加载,而AaOnBean 不会被加载
在这里插入图片描述
@ConditionalOnProperty
application.yml

sprinboot:aaaa: jiaza

@ConditionalOnProperty(prefix = “sprinboot”,name = “aaaa”,havingValue = “jiazai”,matchIfMissing = false)
通过配置文件的值来判断是否加载
prefix :前缀
name :名称
havingValue :对应的值
matchIfMissing :默认为false,必须匹配才能加载 如果为true 如果没找到sprinboot.aaaa的值也能加载。

@Component
//配置文件中的springboot.aaaa 必须等于 jiazai 才能加载
@ConditionalOnProperty(prefix = "sprinboot",name = "aaaa",havingValue = "jiazai",matchIfMissing = false)
public class AaProperBean {public void test(){System.out.println("----AaProperBean-------");}
}

测试

@SpringBootApplication
public class Application {public static void main(String[] args) {ApplicationContext run = SpringApplication.run(Application.class, args);AaProperBean aaProperBean = run.getBean("aaProperBean",AaProperBean.class);aaProperBean.test();}
}

在这里插入图片描述如果修改配置文件

sprinboot:aaaa: jiaza222

在这里插入图片描述
就不会加载。

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

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

相关文章

Java毕业设计-网上宠物店系统

&#x1f525;作者主页&#xff1a;疯狂行者&#x1f525; &#x1f496;✌java领域优质创作者,专注于Java技术领域技术交流✌&#x1f496; &#x1f496;文末获取源码&#x1f496; 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1…

一文看懂yolov7;yolov7详解

*免责声明: 1\此方法仅提供参考 2\搬了其他博主的操作方法,以贴上路径. 3* 场景一:yolo v7 场景二:yolo系列未完待续 … Yolo系列强推–>Yolo v1–v5 、 Yolox 场景一:yolo v7 强推先看–>yolov7基础知识先导篇 Yolov7论文地址 Yolov7的github项目地址 操作强推…

Linux环境详解

目录 vim的详细解绍 程序运行过程(C) Linux调试器——gdb 制作进度条 在Linux中使用gitee vim的详细解绍 vim的概念 vim是一个文本编辑器(多模式的编辑器)&#xff0c;从定位上&#xff0c;和记事本没有任何差别&#xff0c;是vi的前身 vim的主要三种模式 命令模式&a…

2022“杭电杯”中国大学生算法设计超级联赛(5)

Bragging Dice 两个人掷骰子&#xff0c;两人都知道对方手中和自己手中的牌数&#xff0c;现在有两种操作&#xff0c;一种是挑战&#xff0c;即打开盖子&#xff0c;看是否是前一人说的那样&#xff1b;另一种是声称&#xff0c;即给出判断&#xff0c;类似有x个y点的骰子这样…

[MySQL数据库部署及初始化相关]

一、MySQL安装前系统环境检测 1.selinux和iptables需要关闭 cat /etc/sysconfig/selinux sed -i s/enable/disable/g /etc/sysconfig/selinuxchkconfig --list|grep iptables chkconfig iptables off chkconfig --list|grep iptables2.I/O调度系统默认是cfq模式&#x…

IDEA 创建 Servelet 项目

本文主要讲述如何在 idea 中添加 Servelet &#xff0c;适合初学者及从 Eclipse 开发工具转为 IDEA 的开发人员学习 环境介绍 系统环境&#xff1a;win11 开发工具版本&#xff1a;IntelliJ IDEA 2022.2.1 项目创建及配置流程 1.创建 Java 项目 2.添加框架支持 3.添加 classes…

如何仅使用 CSS 创建响应式网站

如何仅使用 CSS 创建响应式网站 使用 vw 和 rem 构建响应式页面。Photo by 用户体验商店 on 不飞溅 前言 从移动浏览器或应用程序访问的网站越来越多。对我来说,在空闲时间,我基本上是用手机访问网站。移动浏览器对用户来说很方便,但对开发人员来说却是痛苦的,因为屏幕大…

概述:隐式神经表示(Implicit Neural Representations,INRs)

隐式神经表示&#xff08;Implicit Neural Representations&#xff0c;INRs&#xff09;1 简介1.1 传统的隐式表示1.1.1 代数表示1.1.2 函数表示1.1.3 水平集表示&#xff08;level set&#xff09;1.2 什么是隐式神经表示1.3 隐式神经表示的优缺点1.3.1 优点1.3.2 缺点2 应用…

GD32(7)程序烧录及运行

目录简介启动方式Boot00&#xff0c;Boot1xBoot01&#xff0c;Boot10Boot01&#xff0c;Boot11烧录方式ICPISPIAPIAP的作用IAP与ICP、ISP的运行差别IAP的Bootloader程序实现IAP的APP程序实现简介 微控制器在硬件中作为核心&#xff0c;通过执行保存在内部存储器中的程序&#x…

网站安全防护措施有哪些

想要我们的网站在网络中安全稳定运行&#xff0c;网站安全防护是不可或缺的环节&#xff0c;那么网站安全防护需要做哪些措施呢&#xff0c;这些措施能起到什么作用呢&#xff0c;接下来一起跟着小编一起来看看吧。 服务器安全狗和网站安全狗2022新版更新 更有效帮助用户防护网…

精品基于Uniapp+SSM实现的公园植物介绍APP

《[含文档PPT源码等]精品基于UniappSSM实现的公园植物介绍APP[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务…

设备通过国标GB28181/海康Ehome接入EasyCVR,视频无法打开的原因分析及解决方法

EasyCVR平台支持多类型设备、多协议方式接入&#xff0c;包括市场主流标准协议国标GB/T28181、RTMP、RTSP/Onvif协议等&#xff0c;以及厂家私有协议&#xff0c;如海康SDK、大华SDK、海康Ehome等。平台可将接入的流媒体进行处理及分发&#xff0c;分发的视频格式包括RTSP、RTM…

Swift Practice # 172 Swift 取得网页资料并制作台湾乡镇气象连结JSON

Swift Practice # 172 Swift 取得网页资料并制作台湾乡镇气象连结JSON 上一篇解决了使用Google Admob套件所产生的Link问题,让广告可以顺利显示。 [ Swift Practice # 171 Google Admod 闪退之-ObjC Linker 与SPM 上一篇简单的练习改变SwiftUI Map的显示比例,达到所有显示资料…

python3 词频统计计数分析+可视化词云 jieba+wordcloud 数据分析

hi&#xff0c; 大家好&#xff0c;我是宋哈哈&#xff0c;今天分享一个利用 python 的 jieba 库 和 wordcloud 词云库 做一个字符串的词频分析和词云可视化 编程环境&#xff1a; python 版本&#xff1a;3.6.8 编辑器&#xff1a;pycharm 2020.1.3 专业版 系统环境&#xff1…

使用聚类(K-means)分析方法对骑手进行分类标签定义

什么是聚类分析 聚类分析的目标就是在相似的基础上收集数据来分类&#xff0c;属于无监督学习。就是通过行为数据&#xff0c;通过算法将相似的人群聚集在一起&#xff0c;形成不带标签的人群簇。再人为的对人群簇进行分析&#xff0c;寻找特征标签。 一、数据构建 根据骑手的…

电脑重装系统开机后运行慢怎么办

小编就给大家分享四个电脑运行慢的方法&#xff0c;可以选择适合自己的方法去使用&#xff0c;一般情况都是可以解决掉电脑开机后运行慢的问题&#xff0c;我们接着看看吧。 还有其它的电脑重装系统方法 工具/原料&#xff1a; 系统版本&#xff1a;windows7系统 品牌版本&a…

Leetcode题解——30. 包含min函数的栈(辅助栈思想)

题目地址&#xff1a;剑指 Offer 30. 包含min函数的栈 - 力扣&#xff08;LeetCode&#xff09; 目录 一.算法思想 二.代码实现 三.拓展思考 首先说结论&#xff0c;这道题虽然难度不大&#xff0c;但是算法思想很重要&#xff0c;是辅助栈应用的生动实例。 所以&#xff…

(10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】

&#xff08;1&#xff09;工业界推荐系统-小红书推荐场景及内部实践【业务指标、链路、ItemCF】 &#xff08;2&#xff09;工业界推荐系统-小红书推荐场景及内部实践【UserCF、离线特征处理】 &#xff08;3&#xff09;工业界推荐系统-小红书推荐场景及内部实践【矩阵补充、…

VSCode 配置 C++ 环境

开学了&#xff0c;后面更新速度会更慢&#xff0c;望周知。 接上回: https://blog.csdn.net/orangebench11/article/details/126111356 先说一下, 这个教程不是给完整json复制粘贴, 是要跟教程配置 (放心, 大部分配置都很简单)。 安装VSCode 官网: Visual Studio Code - C…

2021年研究生数模B题论文记录

2021年研究生数模B题论文记录1.常见数据处理方法&#xff1a;2.相关性系数选择3.聚类算法4.一种数据降维方式5.预测模型文章来源 2021年全国大学生研究生数学建模竞赛优秀论文集合&#xff0c;B题&#xff0c;文章编号&#xff1a;B21100130067 1.常见数据处理方法&#xff1a;…