写在前面

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 条件注解家族

@Conditional是 Spring 4.0 引入的核心注解,用于条件化地注册 Bean。可以用于根据特定条件来决定是否将 Bean 注册到 Spring 容器中。

基本使用:

  1. 定义条件类实现 Condition 接口,实现matchs方法

  2. 在 @Conditional 注解中指定条件类

  3. Spring 启动时检查条件:return true表示符合条件,return false表示不符合条件

  4. 条件满足 → 注册 Bean

  5. 条件不满足 → 跳过注册

  • 实现 Condition 接口

  • 在配置类中使用:

同时,Spring Boot 也提供了很多基于 @Conditional的派生注解: 注解作用使用场景@ConditionalOnBean存在指定 Bean 时生效Bean 依赖检查@ConditionalOnMissingBean不存在指定 Bean 时生效默认 Bean 配置@ConditionalOnClass类路径存在指定类时生效类库依赖检查@ConditionalOnMissingClass类路径不存在指定类时生效可选依赖@ConditionalOnProperty配置属性满足条件时生效配置开关@ConditionalOnExpressionSpEL 表达式为 true 时生效复杂条件@ConditionalOnJava指定 Java 版本时生效版本兼容@ConditionalOnWebApplicationWeb 应用时生效Web 环境@ConditionalOnNotWebApplication非 Web 应用时生效非 Web 环境

@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的扩展点。在相应的扩展点加入自己的业务初始化代码。从来达到顺序的控制。

具体关于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)。

  • 错误案例:以下案例代码是无法指定配置顺序的

  • 正确使用案例:

首先定义两个 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之间的依赖先后顺序。