Spring Boot的自动装配中的@ConditionalOnBean条件装配注解在Spring启动过程中,是如何保证处理顺序靠后的
前言
为什么Spring Boot条件注解那么多,而标题中是@ConditionalOnBean呢?
因为,相比之下我们用的比较多的条件装配注解也就是@ConditionalOnClass、@ConditionalOnBean了,而@ConditionalOnClass对顺序并不敏感(说白了就是判断类加载器是否可以在其路径下加载到Class,所以和Spring的处理顺序没啥关系),但是@ConditionalOnBean就不同了,如果顺序无法保证,那么自动装配中的@ConditionalOnBean就可能会失效。
还有一点需要强调一下,正如Spring Boot官方建议的那样,请在自动装配类中使用条件装配注解,不要在自己定义普通配置类(普通配置类指的是我们自定义的@Configuration配置类)中使用,在普通配置类中使用条件装配注解,能不能生效那就看命了,尤其是@ConditionalOnBean这种对顺序敏感的注解;可能会出现在IDE中生效,但是到了线上就不生效了,因为你光靠ClassLoader的加载顺序是不靠谱的,在不同的操作系统环境下,class文件的加载顺序存在不确定性,文件是由文件系统管理,不同的文件管理系统有不同的机制。
可能有些人会想,我自定义@Configuration的普通配置类,我自己来显示的控制加载顺序,不过有一点请注意,你要控制的是BeanDefinition的注册顺序,而不是Bean的注入顺序;
我能想到的唯一可以控制BeanDefinition注册顺序的方式就是:自定义一个实现BeanDefinitionRegistryPostProcessor接口的实现类,并且还要实现PriorityOrder接口,要保证顺序比ConfigurationClassPostProcessor靠前,这样才有机会提前注册BeanDefinition到容器中;当然你也可以定义一个ApplicationContextInitializer接口实现类,然后通过initialize方法将自定义的BeanDefinitionRegistryPostProcessor接口的实现类添加到容器中,这样不用实现PriorityOrder接口也可以保证在ConfigurationClassPostProcessor前面执行。
不过上面说的BeanDefinitionRegistryPostProcessor接口实现类向容器添加BeanDefinition属于歪门邪道吧,明明正常扫描@Configuration注解来注册的配置类,非要把@Configuration注解去掉或者放到@ComponentScan扫不到的包下,然后由BeanDefinitionRegistryPostProcessor接口硬编码注入,应该也没有人会这么编码。
所以请切记,条件装配注解并不是无敌的,也是需要考虑使用场景的,不能随便的滥用,因此尽量在自动装配配置类中使用条件装配注解!!!
@ConditionalOnBean判断的是容器中是否存在BD,而不是判断的容器中是否存在Bean对象,这一点请注意。
为什么要保证顺序
我们举个例子来说一下@ConditionalOnBean注解处理顺序的重要性。
例子中是@ConditionalOnMissingBean注解,但是和@ConditionalOnBean注解的逻辑基本一致的。下面代码表达的意思就是,我们定义的普通配置类中,对RedisTemplete进行了个性化设置,此时我们肯定希望容器中只存在我们自定义的RedisTemplate;而Spring Boot的自动装配的条件装配机制也确实满足了我们的这种需求;
@Configuration
public class RedisConfig {//这是我们自定义的普通配置类@Beanpublic RedisTemplate<Object, Object> redisTemplate() {RedisTemplate<Object, Object> template = new RedisTemplate<>(); //巴拉巴拉一大堆个性化的逻辑//template.setXXX();return template;}
}public class RedisAutoConfiguration {//这是Spring Boot自动装配的配置类@Bean@ConditionalOnMissingBean(name = "redisTemplate")@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}
我们假设一下:如果RedisAutoConfiguration自动装配配置类在我们自定义的RedisConfig普通配置类前面执行了,那就会导致当时的容器中不存在名字为redisTemplate的bean对象,当处理我们自定义的RedisConfig配置类的时候,就会报错,因为存在同名的bean了。
总而言之,对于@ConditionalOnBean注解来说,顺序很重要,是必须要保证的。
顺序是如何保证的
关于原理其实还是挺多内容的,因为需要足够清楚Spring的ConfigurationClassPostProcessor以及Spring Boot的AutoConfigurationImportSelector,这样才能彻底掌握原理中的细节内容。考虑到篇幅问题,我们还是以关键逻辑为主,弄清楚@ConditionalOnBean顺序是怎么保证的即可。
首先我们说几点比较关键的基础知识。
- @EnableAutoConfiguration:主要作用是开启Spring Boot的自动装配,属于Spring Boot注解;
- @Import:主要作用是向容器中导入BeanDefinition,导入的BD直接当做ConfigurationClass来处理;
- ImportSelector:是一个接口,主要作用是向容器中导入BD;
- DeferredImportSelector:是一个继承了ImportSelector的接口,除了继承的能力外,还额外增加了延迟导入的能力(当然这个延迟导入借助的是Spring对ConfigurationClass的处理流程来实现的,并不是DeferredImportSelector自身具有延迟导入能力的方法);
- AutoConfigurationImportSelector:是一个实现了DeferredImportSelector接口的实现类,自动装配的核心逻辑主要就在这里;
可能对不熟悉Spring的人来说,没办法将上述几点串联起来。所以,我们在花费一些篇幅来详细的解释下上面几个点。
- 我们都知道一般会在启动类上存在@SpringBootApplication注解,而@SpringBootApplication注解上就标注了@EnableAutoConfiguration。
- @EnableAutoConfiguration注解上面配置了@Import(AutoConfigurationImportSelector.class)。
- AutoConfigurationImportSelector实现了DeferredImportSelector。
- DeferredImportSelector继承了ImportSelector。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {......此处省略很多无关内容
}@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {......此处省略一些无关内容
}public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {......此处省略很多无关内容
}public interface DeferredImportSelector extends ImportSelector {......此处省略一些无关内容
}
通过代码示例,我们可以清楚的看到,在编码层面,这几个注解、接口以及实现类已经串起来了。
下面画个流程简图,来说明一下Spring是如何保证顺序的。
绿色为相关度较高的关键代码逻辑;红色条件装配的判断节点;
我们再用文字把流程中的关键点整理一下:
- ConfigurationClass的处理分2步;第一步:处理ConfigurationClass的注解信息并保存下来;第二步:对保存下来的信息进行处理,创建BD并注册到容器中。
- 在第一步中,普通配置类先进行处理,遇到自动装配的AutoConfigurationImportSelector(属于DeferredImportSelector接口类型)会先保存起来,等到普通配置类处理完成后,再进行统一的DeferredImportSelector接口类型的处理;在处理过程中导入的配置类都会放在LinkedHashMap类型的configurationClasses集合中,这样就可以利用默认情况下LinkedHashMap按照插入顺序遍历的特性,来控制普通配置类和自动装配配置类顺序。
- 在第二步中,已经得到了带有顺序的configurationClasses集合,普通配置类在前,自动装配配置类在后;这样在循环处理的时候,就是先处理的普通配置类,普通配置类处理完成后,已经将相应的BD注册到了容器中;而在处理自动装配配置类的时候,通过条件装配的判断,可以准确的判断出当前容器中是否存在对应的BD,这样就保证了自动装配功能的正常。
结语
虽然@ConditionalOnBean是Spring Boot的注解,但是我们通过流程图发现,大部分核心的代码逻辑都是属于Spring的。因此,@ConditionalOnBean条件装配的顺序就是Spring的DeferredImportSelector接口延迟处理机制来保证的。
对于熟悉Spring源码的人来说,可能看这篇文章很容易;如果不太熟悉,可以了解一下ConfigurationClassPostProcessor这个类,因为对于注解驱动(使用xml配置文件的项目越来越少了)的项目来说,这个类真的是太重要了。这个类处理的是BeanDefinition阶段,因此可以不用对Bean注册阶段有过多的了解。了解了ConfigurationClassPostProcessor这个类,那么读懂条件装配的原理,并且掌握代码逻辑细节,那肯定是轻轻松松儿的。
下面是我整理的关于 ConfigurationClassPostProcessor的主要逻辑流程图,也附上,有需要的可以看看。
相关文章:

Spring Boot的自动装配中的@ConditionalOnBean条件装配注解在Spring启动过程中,是如何保证处理顺序靠后的
前言 为什么Spring Boot条件注解那么多,而标题中是ConditionalOnBean呢? 因为,相比之下我们用的比较多的条件装配注解也就是ConditionalOnClass、ConditionalOnBean了,而ConditionalOnClass对顺序并不敏感(说白了就是判…...

玩转数据-大数据-Flink SQL 中的时间属性
一、说明 时间属性是大数据中的一个重要方面,像窗口(在 Table API 和 SQL )这种基于时间的操作,需要有时间信息。我们可以通过时间属性来更加灵活高效地处理数据,下面我们通过处理时间和事件时间来探讨一下Flink SQL …...

【论文笔记】A Review of Motion Planning for Highway Autonomous Driving
文章目录 I. INTRODUCTIONII. CONSIDERATIONS FOR HIGHWAY MOTION PLANNINGA. TerminologyB. Motion Planning SchemeC. Specificities of Highway DrivingD. Constraints on Highway DrivingE. What Is at Stake in this Paper III. STATE OF THE ARTA. Taxonomy DescriptionB…...

YOLOv8改进算法之添加CA注意力机制
1. CA注意力机制 CA(Coordinate Attention)注意力机制是一种用于加强深度学习模型对输入数据的空间结构理解的注意力机制。CA 注意力机制的核心思想是引入坐标信息,以便模型可以更好地理解不同位置之间的关系。如下图: 1. 输入特…...

2023年10月腾讯云优惠活动汇总:腾讯云最新优惠、代金券整理
腾讯云作为国内领先的云服务提供商,致力于为用户提供优质、稳定的云服务。为了更好地满足用户需求,腾讯云推出了各种优惠活动。本文将给大家分享腾讯云最新优惠活动,帮助用户充分利用腾讯云提供的优惠。 一、腾讯云优惠券领取【点此领取】 腾…...

BUUCTF reverse wp 65 - 70
[SWPU2019]ReverseMe 反编译的伪码看不明白, 直接动调 这里显示"Please input your flag", 然后接受输入, 再和32进行比较, 应该是flag长度要求32位, 符合要求则跳转到loc_E528EE分支继续执行 动调之后伪码可以读了 int __cdecl main(int argc, const char **arg…...

xorm数据库操作之Join、Union
golang的数据库操作xorm使用起来非常方便,不用再自己写SQl语句,而且xorm自己给我们做了SQL防注入等操作,用起来既方便又安全。此次文章我不会记录xorm的基本操作,我值记录一些特殊用法问题,包括动态创建表单、基于xorm…...

排序:基数排序算法分析
1.算法思想 假设长度为n的线性表中每个结点aj的关键字由d元组 ( k j d − 1 , k j d − 2 , k j d − 3 , . . . , k j 1 , k j 0 ) (k_{j}^{d-1},k_{j}^{d-2},k_{j}^{d-3},... ,k_{j}^{1} ,k_{j}^{0}) (kjd−1,kjd−2,kjd−3,...,kj1,kj0)组成, 其中&am…...

用go实现http服务端和请求端
一、概述 本文旨在学习记录下如何用go实现建立一个http服务器,同时构造一个专用格式的http客户端。 二、代码实现 2.1 构造http服务端 1、http服务处理流程 基于HTTP构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。HTTP 请求从客户端…...

幂级数和幂级数的和函数有什么关系?
幂级数和幂级数的和函数有什么关系? 本文例子引用自:80_1幂级数运算,逐项积分、求导【小元老师】高等数学,考研数学 求幂级数 ∑ n 1 ∞ 1 n x n \sum\limits_{n1}^{\infty}\frac{1}{n}x^n n1∑∞n1xn 的和函数 ÿ…...

Git多账号管理通过ssh 公钥的方式,git,gitlab,gitee
按照目前国内访问git,如果不科学上网,我们很大可能访问会超时。基于这个,所以我现在的git 配置已经增加到了3个了 一个公司gitlab,一个git,一个gitee. 以下基于这个环境,我们来说明下如何创建配置ssh公钥。…...
在nodejs常见的不良做法及其优化解决方案
在nodejs常见的不良做法及其优化解决方案 当涉及到在express和nodejs中开发应用程序时。遵循最佳实践对于确保项目的健壮性、可维护性和安全性至关重要。 在本文中,我们将探索开发人员经常遇到的几种常见的错误做法,并通过代码示例研究优化的最佳做法&…...
关于layui upload上传组件上传文件无反应的问题
最近使用layui upload组件时,碰到了上传文件无反应的问题,感到非常困惑。 因为使用layui upload组件不是一次两次了,之前每次都可以,这次使用同样的配方,同样的姿势,为什么就不行了呢? 照例先…...

容器网络之Flannel
第一个问题位置变化,往往是通过一个称为注册中心的地方统一管理的,这个是应用自己做的。当一个应用启动的时候,将自己所在环境的 IP 地址和端口,注册到注册中心指挥部,这样其他的应用请求它的时候,到指挥…...
SVM(下):如何进行乳腺癌检测?
⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…...

嵌入式Linux应用开发-第十五章具体单板的按键驱动程序
嵌入式Linux应用开发-第十五章具体单板的按键驱动程序 第十五章 具体单板的按键驱动程序(查询方式)15.1 GPIO操作回顾15.2 AM335X的按键驱动程序(查询方式)15.2.1 先看原理图确定引脚及操作方法15.2.2 再看芯片手册确定寄存器及操作方法15.2.3 编程15.2.3.1 程序框架15.2.3.2 硬…...

MySQL体系结构和四层架构介绍
MySQL体系结构图如下: 四层介绍 1. 连接层: 它的主要功能是处理客户端与MySQL服务器之间的连接(比如Java应用程序通过JDBC连接MySQL)。当客户端应用程序连接到MySQL服务器时,连接层对用户进行身份验证、建立安全连接并管理会话状态。它还处理…...

【产品运营】如何做好B端产品规划
产品规划是基于当下掌握的多维度信息,为追求特定目的,而制定的产品资源投入计划。 产品规划是基于当下掌握的多维度信息(客户需求、市场趋势、竞争对手、竞争策略等),为追求特定目的(商业增长、客户满意等&…...
ruoyi-启动
1 springboot 版本 git 地址 ruoyi-vue-pro: 🔥 官方推荐 🔥 RuoYi-Vue 全新 Pro 版本,优化重构所有功能。基于 Spring Boot MyBatis Plus Vue & Element 实现的后台管理系统 微信小程序,支持 RBAC 动态权限、数据权限…...

select完成服务器并发
服务器 #include <myhead.h>#define PORT 4399 //端口号 #define IP "192.168.0.191"//IP地址//键盘输入事件 int keybord_events(fd_set readfds); //客户端交互事件 int cliRcvSnd_events(int , struct sockaddr_in*, fd_set *, int *); //客户端连接事件 …...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...