Spring @Order 注解,你可能理解错了

1. 问题描述

最近在梳理项目中的基础设施模块,希望将自动扫描(@ComponentScan)的方式,改为基于 @Configuration 的方式,这样在编写测试类的时候,能够有选择的对基础设施相关的 Bean 进行装配。通过这样的梳理和思考,能够提升模块的内聚性。
但是,在操作的过程中基于实际情况,认为某些 Bean 的实例化有先后顺序,因此想当然的认为能够通过 @Order 注解(或者 Ordered 接口)来实现 Bean 实例化的先后顺序。其实不然。

2. 问题分析

我们定义了如下 3 个 Bean。分别实现 Ordered 接口,并分别返回 3、2、1。按预期的效果,应该值越小,越先初始化。

@Slf4j
@Component
public class AOrderBean implements Ordered {
    public AOrderBean() {
        log.info("init AOrderBean");
    }

    @Override
    public int getOrder() {
        return 3;
    }
}

@Slf4j
@Component
public class BOrderBean implements Ordered {
    public BOrderBean() {
        log.info("init BOrderBean");
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

@Slf4j
@Component
public class COrderBean implements Ordered {
    public COrderBean() {
        log.info("init COrderBean");
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

程序运行结果:
Spring @Order 注解,你可能理解错了_第1张图片

从运行结果分析,Ordered 接口并没有达到预期的效果。

3. Order排序的原理

通过分析 Spring 源码,发现基于 Order 的顺序性问题是通过 AnnotationAwareOrderComparator 实现的。该比较器调用的地方,就是 Order 生效的地方。

4. Order 生效的场景

通过全局搜索,在 Spring 和 Spring Boot 项目中,有如下地方使用到了该类:

4.1 spring-context 模块

Spring @Order 注解,你可能理解错了_第2张图片

如上图所示,在spring-context模块中有如下接口对 Order 生效:

  • Condition 接口
  • DeferredImportSelector 导入外部装配配置
  • ApplicationListener
  • EventListenerFactory
  • SchedulingConfigurer

4.2 spring-core 模块

spring-core

如上如所示,SpringFactoriesLoader 按指定类型加载对应配置时,可以生效。全局搜索该方法得到如下:
Spring @Order 注解,你可能理解错了_第3张图片

Spring Boot 在启动的时候对 spring.factories 中的相关配置进行读取时,就使用了该方法。

4.3 spring-test 模块

Spring @Order 注解,你可能理解错了_第4张图片

如上图所示:

  • ApplicationContextInitializer
  • TestExecutionListener

4.4 spring-web 模块

Spring @Order 注解,你可能理解错了_第5张图片

4.5 spring-boot

Spring @Order 注解,你可能理解错了_第6张图片

spring-boot 中可以被影响的有:

  • ApplicationRunner
  • CommandLineRunner
  • ErrorViewResolver
  • getSpringFactoriesInstances 方法调用的地方
    Spring @Order 注解,你可能理解错了_第7张图片
  • ApplicationListener
  • FailureAnalyzer
  • TypeSupplier
  • ErrorPageRegistrar
  • WebServerFactoryCustomizer
  • ServletContextInitializer

5. 其他已知生效场景

5.1 @Aspect 注解

通过 @Aspect 对相同的调用点进行增强时,当存在多个增强同时希望控制其顺序时,可以使用 @Order

5.2 装配集合类型

@Component
public class FilterChain {
    private List filterList;

    public FilterChain(List filterList) {
        System.out.println(filterList.getClass().getSimpleName());
        this.filterList = filterList;
    }

    @PostConstruct
    public void init() {
        filterList.stream().map(Filter::getName).forEach(System.out::println);
    }
}

如上述代码所示,通过集合类型装配,将所有实现了 Filter 接口的 Bean,装配到 filterList 时,如果各个 Filter 对应的 Bean 实现了 @Order,最终 List 中的 Bean 将时有序的。

5.3 PostProcessor

  • BeanPostProcessor
  • BeanFactoryPostProcessor
    上述两类处理器,在自动装配的 ApplicationContext 中通过实现 Ordered 接口,能够控制顺序,但是对于 @Order 注解,暂不支持。

6. 结论

限于篇幅,上边的查找可能并不全面,比如并没有查找 AnnotationAwareOrderComparator 的父类 OrderComparator 的调用点。但是可以得出一个结论:

Order 并不能改变 spring 实例化 Bean 的顺序。只能改变 Bean 运行顺序。因此,在实际配置中, Bean 之间的装配,依赖 spring 的默认装配机制来保证。对于间接依赖,可以通过 @DependsOn 注解进行微调。

对于 spring boot 的 *AutoConfiguration 来说,可以通过

  • @AutoConfigureBefore
  • @AutoConfigureAfter
  • @AutoConfigureOrder

来进行装配顺序的控制。该方式在基于扫描装配下的 @Configuration 模式,并不生效。

你可能感兴趣的:(Spring @Order 注解,你可能理解错了)