Spring Boot的自动装配中的@ConditionalOnBean条件装配注解在Spring启动过程中,是如何保证处理顺序靠后的_conditionalonbean顺序问题-程序员宅基地

技术标签: spring  spring boot  面试  java  后端  

前言

        为什么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 {//这是我们自定义的普通配置类
    @Bean
    public 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的人来说,没办法将上述几点串联起来。所以,我们在花费一些篇幅来详细的解释下上面几个点。

  1. 我们都知道一般会在启动类上存在@SpringBootApplication注解,而@SpringBootApplication注解上就标注了@EnableAutoConfiguration。
  2. @EnableAutoConfiguration注解上面配置了@Import(AutoConfigurationImportSelector.class)。
  3. AutoConfigurationImportSelector实现了DeferredImportSelector。
  4. 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是如何保证顺序的。

绿色为相关度较高的关键代码逻辑;红色条件装配的判断节点; 

        我们再用文字把流程中的关键点整理一下:

  1. ConfigurationClass的处理分2步;第一步:处理ConfigurationClass的注解信息并保存下来;第二步:对保存下来的信息进行处理,创建BD并注册到容器中。
  2. 在第一步中,普通配置类先进行处理,遇到自动装配的AutoConfigurationImportSelector(属于DeferredImportSelector接口类型)会先保存起来,等到普通配置类处理完成后,再进行统一的DeferredImportSelector接口类型的处理;在处理过程中导入的配置类都会放在LinkedHashMap类型的configurationClasses集合中,这样就可以利用默认情况下LinkedHashMap按照插入顺序遍历的特性,来控制普通配置类和自动装配配置类顺序。
  3. 在第二步中,已经得到了带有顺序的configurationClasses集合,普通配置类在前,自动装配配置类在后;这样在循环处理的时候,就是先处理的普通配置类,普通配置类处理完成后,已经将相应的BD注册到了容器中;而在处理自动装配配置类的时候,通过条件装配的判断,可以准确的判断出当前容器中是否存在对应的BD,这样就保证了自动装配功能的正常。
结语

        虽然@ConditionalOnBean是Spring Boot的注解,但是我们通过流程图发现,大部分核心的代码逻辑都是属于Spring的。因此,@ConditionalOnBean条件装配的顺序就是Spring的DeferredImportSelector接口延迟处理机制来保证的。

        对于熟悉Spring源码的人来说,可能看这篇文章很容易;如果不太熟悉,可以了解一下ConfigurationClassPostProcessor这个类,因为对于注解驱动(使用xml配置文件的项目越来越少了)的项目来说,这个类真的是太重要了。这个类处理的是BeanDefinition阶段,因此可以不用对Bean注册阶段有过多的了解。了解了ConfigurationClassPostProcessor这个类,那么读懂条件装配的原理,并且掌握代码逻辑细节,那肯定是轻轻松松儿的。

        下面是我整理的关于 ConfigurationClassPostProcessor的主要逻辑流程图,也附上,有需要的可以看看。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44182586/article/details/133432042

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法