如何控制 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 的加载顺序呢?官方解释:
最开始@Order
注解用于切面的优先级指定;在 4.0 之后对它的功能进行了增强,支持集合的注入时,指定集合中 bean 的顺序,并且特别指出了,它对于单实例的 bean 之间的顺序,没有任何影响。目前用的比较多的有以下 3 点:
控制 AOP 的类的加载顺序,也就是被
@Aspect
标注的类控制
ApplicationListener
实现类的加载顺序控制
CommandLineRunner
实现类的加载顺序
使用详情请看后文
如何控制
@Conditional 条件注解家族
@ConditionalOnClass:当类路径下存在指定的类时,配置类才会生效。
@ConditionalOnMissingClass:当类路径下不存在指定的类时,配置类才会生效。
@ConditionalOnBean:当容器中存在指定的 Bean 时,配置类才会生效。
@ConditionalOnMissingBean:当容器中不存在指定的 Bean 时,配置类才会生效。
@DependsOn
@DependsOn
注解可以用来控制 bean 的创建顺序,该注解用于声明当前 bean 依赖于另外一个 bean。所依赖的 bean 会被容器确保在当前 bean 实例化之前被实例化。
@DependsOn
的使用:
直接或者间接标注在带有
@Component
注解的类上面;直接或者间接标注在带有
@Bean
注解的方法上面;使用
@DependsOn
注解到类层面仅仅在使用 component-scanning 方式时才有效,如果带有@DependsOn
注解的类通过 XML 方式使用,该注解会被忽略,<bean depends-on="..."/>
这种方式会生效。
示例:
以上代码 bean 的加载顺序为:
参数注入
在@Bean
标注的方法上,如果传入了参数,springboot 会自动会为这个参数在 spring 上下文里寻找这个类型的引用。并先初始化这个类的实例。
利用此特性,我们也可以控制 bean 的加载顺序。
示例:
以上结果,beanB 先于 beanA 被初始化加载。
需要注意的是,springboot 会按类型去寻找。如果这个类型有多个实例被注册到 spring 上下文,那就需要加上@Qualifier("Bean的名称")
来指定
利用 bean 的生命周期中的扩展点
在 spring 体系中,从容器到 Bean 实例化 &初始化都是有生命周期的,并且提供了很多的扩展点,允许在这些步骤时进行逻辑的扩展。
这些可扩展点的加载顺序由 spring 自己控制,大多数是无法进行干预的。可以利用这一点,扩展 spring 的扩展点。在相应的扩展点加入自己的业务初始化代码。从来达到顺序的控制。
实现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)。
错误案例:以下案例代码是无法指定配置顺序的
正确使用案例:
首先定义两个 Bean 实现同一个接口,并添加上 @Order 注解。
然后在一个测试 bean 中,注入IBean
的列表,我们需要测试这个列表中的 Bean 的顺序是否和定义的@Order
规则一致
@AutoConfigureOrder
这个注解用来指定配置文件的加载顺序。但是在实际测试中发现,以下这样使用是不生效的:
无论你 2 个数字填多少,都不会改变其加载顺序结果。那这个@AutoConfigureOrder
到底是如何使用的呢?
@AutoConfigureOrder 适用于外部依赖的包中 AutoConfig 的顺序,而不能用来指定本包内的顺序。能被你工程内部 scan 到的包,都是内部的 Configuration,而 spring 引入外部的 Configuration,都是通过 spring 特有的 spi 文件:spring.factories
换句话说,@AutoConfigureOrder
能改变spring.factories
中的@Configuration
的顺序。
具体使用方式:
spring.factories
:
总结
其实在工作中,我相信很多人碰到过复杂的依赖关系的 bean 加载,把这种不确定性交给 spring 去做,还不如我们自己去控制,这样在阅读代码的时候 ,也能轻易看出 bean 之间的依赖先后顺序
文章转载自:Seven
评论