Spring|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|Spring @Order 注解,你可能理解错了
文章图片

从运行结果分析,Ordered 接口并没有达到预期的效果。
3. Order排序的原理 通过分析 Spring 源码,发现基于 Order 的顺序性问题是通过 AnnotationAwareOrderComparator 实现的。该比较器调用的地方,就是 Order 生效的地方。
4. Order 生效的场景 通过全局搜索,在 Spring 和 Spring Boot 项目中,有如下地方使用到了该类:
4.1 spring-context 模块
Spring|Spring @Order 注解,你可能理解错了
文章图片

如上图所示,在spring-context模块中有如下接口对 Order 生效:
  • Condition 接口
  • DeferredImportSelector 导入外部装配配置
  • ApplicationListener
  • EventListenerFactory
  • SchedulingConfigurer
4.2 spring-core 模块
【Spring|Spring @Order 注解,你可能理解错了】Spring|Spring @Order 注解,你可能理解错了
文章图片

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

Spring Boot 在启动的时候对 spring.factories 中的相关配置进行读取时,就使用了该方法。
4.3 spring-test 模块
Spring|Spring @Order 注解,你可能理解错了
文章图片

如上图所示:
  • ApplicationContextInitializer
  • TestExecutionListener
4.4 spring-web 模块
Spring|Spring @Order 注解,你可能理解错了
文章图片

4.5 spring-boot
Spring|Spring @Order 注解,你可能理解错了
文章图片

spring-boot 中可以被影响的有:
  • ApplicationRunner
  • CommandLineRunner
  • ErrorViewResolver
  • getSpringFactoriesInstances 方法调用的地方
    Spring|Spring @Order 注解,你可能理解错了
    文章图片
  • 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 模式,并不生效。

    推荐阅读