写在前面

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的加载顺序?的更多相关文章

  1. 如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇

    1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功 ...

  2. Spring Bean 的加载顺序

    一,单一Bean 装载 1. 实例化; 2. 设置属性值; 3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name; 4. 如果实现BeanFacto ...

  3. 为了控制Bean的加载我使出了这些杀手锏

    故事一: 绝代有佳人,幽居在空谷 美女同学小张,在工作中遇到了烦心事.心情那是破凉破凉的,无法言喻. 故事背景是最近由于需求变动,小张在项目中加入了MQ的集成,刚开始还没什么问题,后面慢慢问题的显露出 ...

  4. SpringBoot系列教程之Bean加载顺序之错误使用姿势辟谣

    在网上查询 Bean 的加载顺序时,看到了大量的文章中使用@Order注解的方式来控制 bean 的加载顺序,不知道写这些的博文的同学自己有没有实际的验证过,本文希望通过指出这些错误的使用姿势,让观文 ...

  5. 【spring】bean加载顺序

    问题来源 有一个bean为A,一个bean为B.想要A在容器实例化的时候的一个属性name赋值为B的一个方法funB的返回值. 如果只是在A里单纯的写着: private B b; private S ...

  6. web.xml的配置及加载顺序

    一web.xml加载过程(步骤): 1.启动WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener></listener> ...

  7. 【SpringBoot基础系列-实战】如何指定 bean 最先加载(应用篇)

    [基础系列-实战]如何指定 bean 最先加载(应用篇) 在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例 ...

  8. SpringBoot中的bean加载顺序

    https://www.dazhuanlan.com/2019/10/22/5daebc5d16429/ 最近在做传统Spring项目到SpringBoot项目迁移过程中,遇到了一些bean加载顺序的 ...

  9. spring bean加载顺序指定方式之一

    在某些情况下,我们在容器启动的时候做一些事情,举个例子,加载缓存等.. 此时我们会希望某个bean先被加载并执行其中的afterpropertiesset方法. 因为spring默认是通过contex ...

  10. web.xml加载顺序

    一 1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Ser ...

随机推荐

  1. src 和 href 的区别?

    src:都是引用资源 src:指向外部资源的位置 , 当浏览器解析到此元素时,会暂停其它资源的下载和处理 , 直到将该资源加载 , 编译 , 执行完毕 ,相当于将资源嵌入到文档中当前元素的所在的位置: ...

  2. 我们如何在 vue 应用我们的权限

    权限可以分为用户权限和按钮权限: 用户权限,让不同的用户拥有不同的路由映射 ,具体实现方法: 1. 初始化路由实例的时候,只把静态路由规则注入 ,不要注入动态路由规则 : 2. 用户登录的时候,根据返 ...

  3. Need BLUETOOTH PRIVILEGED permission以及requestMtu导致蓝牙断连问题

    在部分Android手机上,当连接上GATTService后直接requestMtu有可能会造成蓝牙连接中断,随后继续重新连接会报错Need BLUETOOTH PRIVILEGED permissi ...

  4. Tomcat通信概念篇

    在上一篇了解完网络通信的基本概念之后,本章节为了解Tomcat的基本逻辑方便以后对 UDP:(发短信,不管是否能接受成功都会发送) //发送端 //不需要连接服务器 public static voi ...

  5. 题解:P9788 [ROIR 2020 Day2] 区域规划

    题目传送门 洛谷题解 思路 首先我们看下数据范围, \(n <= 3000\) ,范围很小,所以暴力枚举. 于是第一份代码出来了. #include<bits/stdc++.h> u ...

  6. cxv文件合并

    1.新建bat脚本 @echo off setlocal enabledelayedexpansion copy *.csv new.csv echo @@@@@@@@@@@@@合并成功!@@@@@@ ...

  7. Paths和Files

    Paths 类 Paths 类主要用于操作文件和目录路径.它提供了一些静态方法,用于创建java.nio.file.Path实例,代表文件系统中的路径. // 创建一个Path实例,表示当前目录下的一 ...

  8. NDT算法详解与C++实现

    点云匹配在感知环节是一个很重要的信息获取手段,而其中的算法也有几个比较经典了,例如ICP(Iterative Closest Point,迭代最近点)算法,而本文决定记录学习的是NDT算法,也就是No ...

  9. OpenSSH9.3p1升级实践

    安装Telnet服务 为了避免升级OpenSSH导致服务器不可连接.需要先下载安装Telnet组件.升级期间使用Telnet作为升级期间的服务器连接方式. 先查询telnet是否安装 rpm -qa ...

  10. PBI自定义视觉对象环境配置

    开发文档地址:https://docs.microsoft.com/zh-cn/power-bi/service-custom-visuals-getting-started-with-develop ...