写点什么

如何控制 bean 的加载顺序?

作者:EquatorCoco
  • 2024-12-26
    福建
  • 本文字数:4067 字

    阅读完需:约 13 分钟

写在前面


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="..."/>这种方式会生效。


示例:


@Configurationpublic 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 initbean A initbean E initbean D initbean C init
复制代码


参数注入


@Bean标注的方法上,如果传入了参数,springboot 会自动会为这个参数在 spring 上下文里寻找这个类型的引用。并先初始化这个类的实例。


利用此特性,我们也可以控制 bean 的加载顺序。


示例:


@Beanpublic BeanA beanA(BeanB demoB){  System.out.println("bean A init");  return new BeanA();}  @Beanpublic BeanB beanB(){  System.out.println("bean B init");  return new BeanB();}
复制代码


以上结果,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)。


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


@Component@Order(1)public class BeanA {    // BeanA的定义}
@Component@Order(2)public class BeanB { // BeanB的定义}
复制代码


  • 正确使用案例:

首先定义两个 Bean 实现同一个接口,并添加上 @Order 注解。


public interface IBean {}
@Order(2)@Componentpublic class AnoBean1 implements IBean {
private String name = "ano order bean 1";
public AnoBean1() { System.out.println(name); }}
@Order(1)@Componentpublic class AnoBean2 implements IBean {
private String name = "ano order bean 2";
public AnoBean2() { System.out.println(name); }}
复制代码


然后在一个测试 bean 中,注入IBean的列表,我们需要测试这个列表中的 Bean 的顺序是否和定义的@Order规则一致


@Componentpublic 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 之间的依赖先后顺序


文章转载自:Seven

原文链接:https://www.cnblogs.com/seven97-top/p/18625259

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
如何控制bean的加载顺序?_C#_EquatorCoco_InfoQ写作社区