如何控制bean的加载顺序?
写在前面
springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题。在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功能。
在一般业务场景,可能是不需要关心一个bean是如何被注册进spring容器的,只需要把需要注册进容器的bean声明为@Component即可,因为spring会自动扫描到这个Bean完成初始化并加载到spring上下文容器。
但是,如果加载Bean的过程中部分Bean和Bean之间存在依赖关系,也就是说Bean A的加载需要等待Bean B加载完成之后才能进行;或者你正在开发某个中间件需要完成自动装配时,你会声明自己的Configuration类,但是可能你面对的是好几个有互相依赖的Bean,如果不加以控制,这时候可能会报找不到依赖的错误。
而Spring框架在没有明确指定加载顺序的情况下是无法按照业务逻辑预期的顺序进行Bean加载,所以需要Spring框架提供能让开发人员显示地指定Bean加载顺序的能力。
几个误区
在正式说如何控制加载顺序之前,先说2个误区:
- 在标注了
@Configuration的类中,写在前面的@Bean一定会被先注册吗?
这个不存在的,spring在xml的时代,也不存在写在前面一定会被先加载的逻辑。因为xml不是渐进的加载,而是全部parse好,再进行依赖分析和注册。到了springboot中,只是省去了xml被parse成spring内部对象的这一过程,但是加载方式并没有大的改变。
- 利用
@Order这个标注就一定能进行加载顺序的控制吗?
严格的说,不是所有的Bean都可以通过@Order这个标注进行顺序的控制。因为把@Order这个标注加在普通的方法上或者类上是没有影响的,
那@Order能控制哪些bean的加载顺序呢?官方解释:
{@code @Order} defines the sort order for an annotated component. Since Spring 4.0, annotation-based ordering is supported for many kinds of components in Spring, even for collection injection where the order values of the target components are taken into account (either from their target class or from their {@code @Bean} method). While such order values may influence priorities at injection points, please be aware that they do not influence singleton startup order which is an orthogonal concern determined by dependency relationships and {@code @DependsOn} declarations (influencing a runtime-determined dependency graph).
最开始@Order注解用于切面的优先级指定;在 4.0 之后对它的功能进行了增强,支持集合的注入时,指定集合中 bean 的顺序,并且特别指出了,它对于单实例的 bean 之间的顺序,没有任何影响。目前用的比较多的有以下3点:
- 控制AOP的类的加载顺序,也就是被
@Aspect标注的类 - 控制
ApplicationListener实现类的加载顺序 - 控制
CommandLineRunner实现类的加载顺序
使用详情请看后文
如何控制
@Conditional 条件注解家族
- @ConditionalOnClass:当类路径下存在指定的类时,配置类才会生效。
@Configuration
// 当类路径下存在指定的类时,配置类才会生效。
@ConditionalOnClass(name = "com.example.SomeClass")
public class MyConfiguration {
// ...
}
- @ConditionalOnMissingClass:当类路径下不存在指定的类时,配置类才会生效。
- @ConditionalOnBean:当容器中存在指定的Bean时,配置类才会生效。
- @ConditionalOnMissingBean:当容器中不存在指定的Bean时,配置类才会生效。
@DependsOn
@DependsOn注解可以用来控制bean的创建顺序,该注解用于声明当前bean依赖于另外一个bean。所依赖的bean会被容器确保在当前bean实例化之前被实例化。
@DependsOn的使用:
- 直接或者间接标注在带有
@Component注解的类上面; - 直接或者间接标注在带有
@Bean注解的方法上面; - 使用
@DependsOn注解到类层面仅仅在使用 component-scanning 方式时才有效,如果带有@DependsOn注解的类通过XML方式使用,该注解会被忽略,<bean depends-on="..."/>这种方式会生效。
示例:
@Configuration
public class BeanOrderConfiguration {
@Bean
@DependsOn("beanB")
public BeanA beanA(){
System.out.println("bean A init");
return new BeanA();
}
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
@Bean
@DependsOn({"beanD","beanE"})
public BeanC beanC(){
System.out.println("bean C init");
return new BeanC();
}
@Bean
@DependsOn("beanE")
public BeanD beanD(){
System.out.println("bean D init");
return new BeanD();
}
@Bean
public BeanE beanE(){
System.out.println("bean E init");
return new BeanE();
}
}
以上代码bean的加载顺序为:
bean B init
bean A init
bean E init
bean D init
bean C init
参数注入
在@Bean标注的方法上,如果传入了参数,springboot会自动会为这个参数在spring上下文里寻找这个类型的引用。并先初始化这个类的实例。
利用此特性,我们也可以控制bean的加载顺序。
示例:
@Bean
public BeanA beanA(BeanB demoB){
System.out.println("bean A init");
return new BeanA();
}
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
以上结果,beanB先于beanA被初始化加载。
需要注意的是,springboot会按类型去寻找。如果这个类型有多个实例被注册到spring上下文,那就需要加上@Qualifier("Bean的名称")来指定
利用bean的生命周期中的扩展点
在spring体系中,从容器到Bean实例化&初始化都是有生命周期的,并且提供了很多的扩展点,允许在这些步骤时进行逻辑的扩展。
这些可扩展点的加载顺序由spring自己控制,大多数是无法进行干预的。可以利用这一点,扩展spring的扩展点。在相应的扩展点加入自己的业务初始化代码。从来达到顺序的控制。
具体关于spring容器中大部分的可扩展点的分析,之前已经写了一篇文章详细介绍了:Spring&SpringBoot中所有的扩展点
实现Ordered/PriorityOrdered接口/注解
在Spring中提供了如下的方法来进行Bean加载顺序的控制:
- 实现
Ordered/PriorityOrdered接口,重写order方法 - 使用
@Order/@Priority注解,@Order注解可以用于方法级别,而@Priority注解则不行;
针对自定义的Bean而言,上述的方式都可以实现Bean加载顺序的控制。无论是实现接口的方式还是使用注解的方式,值设置的越小则优先级越高,而通过实现PriorityOrdered接口或者使用@Priority注解的Bean时其加载优先级会高于实现Ordered接口或者使用@Order注解的Bean。
需要注意的是,使用上述方式只会改变实现同一接口Bean加载到集合(比如List、Set等)中的顺序(或者说优先级),但是这种方式并不会影响到Spring应用上下文启动时不同Bean的初始化顺序(startup order)。
- 错误案例:以下案例代码是无法指定配置顺序的
@Component
@Order(1)
public class BeanA {
// BeanA的定义
}
@Component
@Order(2)
public class BeanB {
// BeanB的定义
}
- 正确使用案例:
首先定义两个 Bean 实现同一个接口,并添加上@Order注解。
public interface IBean {
}
@Order(2)
@Component
public class AnoBean1 implements IBean {
private String name = "ano order bean 1";
public AnoBean1() {
System.out.println(name);
}
}
@Order(1)
@Component
public class AnoBean2 implements IBean {
private String name = "ano order bean 2";
public AnoBean2() {
System.out.println(name);
}
}
然后在一个测试 bean 中,注入IBean的列表,我们需要测试这个列表中的 Bean 的顺序是否和定义的@Order规则一致
@Component
public class AnoTestBean {
public AnoTestBean(List<IBean> anoBeanList) {
for (IBean bean : anoBeanList) {
System.out.println("in ano testBean: " + bean.getClass().getName());
}
}
}
@AutoConfigureOrder
这个注解用来指定配置文件的加载顺序。但是在实际测试中发现,以下这样使用是不生效的:
@Configuration
@AutoConfigureOrder(2)
public class BeanOrderConfiguration1 {
@Bean
public BeanA beanA(){
System.out.println("bean A init");
return new BeanA();
}
}
@Configuration
@AutoConfigureOrder(1)
public class BeanOrderConfiguration2 {
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
}
无论你2个数字填多少,都不会改变其加载顺序结果。那这个@AutoConfigureOrder到底是如何使用的呢?
@AutoConfigureOrder适用于外部依赖的包中 AutoConfig 的顺序,而不能用来指定本包内的顺序。能被你工程内部scan到的包,都是内部的Configuration,而spring引入外部的Configuration,都是通过spring特有的spi文件:spring.factories
换句话说,@AutoConfigureOrder能改变spring.factories中的@Configuration的顺序。
具体使用方式:
@Configuration
@AutoConfigureOrder(10)
public class BeanOrderConfiguration1 {
@Bean
public BeanA beanA(){
System.out.println("bean A init");
return new BeanA();
}
}
@Configuration
@AutoConfigureOrder(1)
public class BeanOrderConfiguration2 {
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
}
spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.BeanOrderConfiguration1,\
com.example.demo.BeanOrderConfiguration2
总结
其实在工作中,我相信很多人碰到过复杂的依赖关系的bean加载,把这种不确定性交给spring去做,还不如我们自己去控制,这样在阅读代码的时候 ,也能轻易看出bean之间的依赖先后顺序。
面试题专栏
Java面试题专栏已上线,欢迎访问。
- 如果你不知道简历怎么写,简历项目不知道怎么包装;
- 如果简历中有些内容你不知道该不该写上去;
- 如果有些综合性问题你不知道怎么答;
那么可以私信我,我会尽我所能帮助你。
如何控制bean的加载顺序?的更多相关文章
- 如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇
1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功 ...
- Spring Bean 的加载顺序
一,单一Bean 装载 1. 实例化; 2. 设置属性值; 3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name; 4. 如果实现BeanFacto ...
- 为了控制Bean的加载我使出了这些杀手锏
故事一: 绝代有佳人,幽居在空谷 美女同学小张,在工作中遇到了烦心事.心情那是破凉破凉的,无法言喻. 故事背景是最近由于需求变动,小张在项目中加入了MQ的集成,刚开始还没什么问题,后面慢慢问题的显露出 ...
- SpringBoot系列教程之Bean加载顺序之错误使用姿势辟谣
在网上查询 Bean 的加载顺序时,看到了大量的文章中使用@Order注解的方式来控制 bean 的加载顺序,不知道写这些的博文的同学自己有没有实际的验证过,本文希望通过指出这些错误的使用姿势,让观文 ...
- 【spring】bean加载顺序
问题来源 有一个bean为A,一个bean为B.想要A在容器实例化的时候的一个属性name赋值为B的一个方法funB的返回值. 如果只是在A里单纯的写着: private B b; private S ...
- web.xml的配置及加载顺序
一web.xml加载过程(步骤): 1.启动WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener></listener> ...
- 【SpringBoot基础系列-实战】如何指定 bean 最先加载(应用篇)
[基础系列-实战]如何指定 bean 最先加载(应用篇) 在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例 ...
- SpringBoot中的bean加载顺序
https://www.dazhuanlan.com/2019/10/22/5daebc5d16429/ 最近在做传统Spring项目到SpringBoot项目迁移过程中,遇到了一些bean加载顺序的 ...
- spring bean加载顺序指定方式之一
在某些情况下,我们在容器启动的时候做一些事情,举个例子,加载缓存等.. 此时我们会希望某个bean先被加载并执行其中的afterpropertiesset方法. 因为spring默认是通过contex ...
- web.xml加载顺序
一 1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Ser ...
随机推荐
- Linux中ln 链接命令的用法
ln的语法 Usage: ln [OPTION]... [-T] TARGET LINK_NAME (1st form) or: ln [OPTION]... TARGET (2nd form) or ...
- 云原生周刊:KubeCon China 2023 详细议程公布 | 2023.8.7
开源项目推荐 Spiderpool Spiderpool 是一个 Kubernetes 底层网络解决方案.它提供丰富的 IPAM 功能和 CNI 集成能力,为开源社区的 CNI 项目提供支持,允许多个 ...
- 关于sizeof、strlen的理解与辨析以及strlen的模拟实现
关于sizeof.strlen的理解与辨析以及strlen的模拟实现 1. sizeof() sizeof的作用: 计算变量所占内存内存空间大小,单位是字节. 注意事项: ① 首先要明确的一点是:si ...
- Python面相对象的编程
文章目录 1.基本概念 2.常用方法及代码实现 1.基本概念 Python的面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它使用"对象&q ...
- MindSponge分子动力学模拟——增强采样(2024.11)
技术背景 关于增强采样(Enhanced Sampling)算法的具体原理,这里暂不做具体介绍,感兴趣的童鞋可以直接参考下这篇综述文章:Enhanced sampling in molecular d ...
- Windows Server 开启远程桌面
工作中不可避免地经常要用到WindowsServer,为了使用的方便,配置了远程桌面,这里记录一下远程桌面配置过程中遇到的一些问题以及解决方法. Windows Server2008 R2 x64 1 ...
- 计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
文章目录 一.TCP/IP五层模型(重要) 二.应用层常见的协议 三.TCP与UDP 3.1 TCP.UDP的区别(重要) 3.2 运行于TCP.UDP上的协议 3.3 TCP的三次握手.四次挥手 3 ...
- Netty 如何自动探测内存泄露的发生
本文基于 Netty 4.1.112.Final 版本进行讨论 本文是 Netty 内存管理系列的最后一篇文章,在第一篇文章 <聊一聊 Netty 数据搬运工 ByteBuf 体系的设计与实现& ...
- Typora实现双击图片放大 Mac
前置条件 Typora LightBox: github下载 添加LightBox拓展 打开应用程序,找到typora,右键选择"显示包内容". 解压lightbox2代码,将di ...
- PostgreSQL加密连接SSL配置
PostgreSQL加密连接SSL配置 环境说明 操作系统 主机名 IP 类型 说明 CentOS Linux release 7.6.1810 (Core) centos7.6 192.168.1. ...