在网上查询 Bean 的加载顺序时, 看到了大量的文章中使用 @Order 注解的方式来控制 bean 的加载顺序, 不知道写这些的博文的同学自己有没有实际的验证过, 本文希望通过指出这些错误的使用姿势, 让观文的小伙伴可以知道 @Order 的具体的应用场景
I. 环境搭建
创建一个 maven 项目, pom 文件如下 (具体的项目代码, 可以在文末获取)
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.1.7</version>
- <relativePath/> <!-- lookup parent from update -->
- </parent>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- </dependencies>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- <repositories>
- <repository>
- <id>spring-milestones</id>
- <name>Spring Milestones</name>
- <url>https://repo.spring.io/milestone</url>
- <snapshots>
- <enabled>false</enabled>
- </snapshots>
- </repository>
- </repositories>
II. 错误姿势
下面我们会介绍两种典型注解的错误使用姿势, 一个 @Order, 一个 @AutoConfigureOrder
I. @Order
err.case1: 类上添加 Order 注解
一种常见的错误观点是在类上添加这个 Order 注解, 就可以指定 bean 之间的初始化顺序, order 值越小, 则优先级越高, 接下来我们实际测试一下, 是否如此
我们创建两个 DemoBean, 指定不同的 Order 顺序
- @Order(4)
- @Component
- public class BaseDemo1 {
- private String name = "base demo 1";
- public BaseDemo1() {
- System.out.println(name);
- }
- }
- @Order(3)
- @Component
- public class BaseDemo2 {
- private String name = "base demo 2";
- public BaseDemo2() {
- System.out.println(name);
- }
- }
根据前面的观点, orde 值小的优先级高, 那么 BaseDemo2 应该先被初始化, 实际测试一下, 输出如下
err.case2: 配置类中 Bean 声明方法上添加 @Order
Bean 除了上面的自动扫描之外, 还有一种方式就是通过 @Bean 注解, 下面我们演示一下在配置类中指定 bean 加载顺序的错误 case
同样我们新建两个测试 bean
- public class BaseDemo3 {
- private String name = "base demo 3";
- public BaseDemo3() {
- System.out.println(name);
- }
- }
- public class BaseDemo4 {
- private String name = "base demo 4";
- public BaseDemo4() {
- System.out.println(name);
- }
- }
接下来在配置类中定义 bean
- @Configuration
- public class ErrorDemoAutoConf {
- @Order(2)
- @Bean
- public BaseDemo3 baseDemo3() {
- return new BaseDemo3();
- }
- @Order(1)
- @Bean
- public BaseDemo4 baseDemo4() {
- return new BaseDemo4();
- }
- }
同样的, 如果 @Order 注解有效, 那么 BaseDemo4 应该先被初始化
从上面的实际测试输出可以看出,@Order 注解在上面的方式中也不生效, 如果有兴趣的同学可以试一下, 将上面配置类中的两个方法的顺序颠倒一下, 会发现 BaseDemo4 先加载
err.case3: @Order 注解修饰配置类
这也是一种常见的错误 case, 认为 @Order 注解是用来指定配置类的加载顺序的, 然而真的是这样么?
我们创建两个测试的配置类
- @Order(1)
- @Configuration
- public class AConf {
- public AConf() {
- System.out.println("AConf init!");
- }
- }
- @Order(0)
- @Configuration
- public class BConf {
- public BConf() {
- System.out.println("BConf init");
- }
- }
如果 @Order 注解生效, 那么 BConf 配置类会优先初始化, 那么我们实测一下
从上面的结果可以看出, 并不是 BConf 先被加载; 当然这种使用姿势, 实际上和第一种错误 case, 并没有什么区别, 配置类也是 bean, 前面不生效, 这里当然也不会生效
那么是不是我们的理解不对导致的呢, 实际上这个 @Order 放在配置类上之后, 是这个配置类中定义的 Bean 的优先于另一个配置类中定义的 Bean 呢?
同样的我们测试下这种 case, 我们定义三个 bean, 两个 conf
- public class Demo1 {
- private String name = "conf demo bean 1";
- public Demo1() {
- System.out.println(name);
- }
- }
- public class Demo2 {
- private String name = "conf demo bean 2";
- public Demo2() {
- System.out.println(name);
- }
- }
- public class Demo3 {
- private String name = "conf demo bean 3";
- public Demo3() {
- System.out.println(name);
- }
- }
然后我们将 Demo1, Demo3 放在一个配置中, Demo2 放在另外一个配置中
- @Order(2)
- @Configuration
- public class AConf1 {
- @Bean
- public Demo1 demo1() {
- return new Demo1();
- }
- @Bean
- public Demo3 demo3() {
- return new Demo3();
- }
- }
- @Order(1)
- @Configuration
- public class BConf1 {
- @Bean
- public Demo2 demo2() {
- return new Demo2();
- }
- }
如果 @Order 注解实际上控制的是配置类中 Bean 的加载顺序, 那么 BConf1 中的 Bean 应该优先加载, 也就是说 Demo2 会优先于 Demo1, Demo3, 实际测试一下, 输出如
上面的输出结果和我们预期的并不一样, 所以 @Order 注解来决定配置类的顺序也是不对的
2. @AutoConfigureOrder
从命名来看, 这个注解是用来指定配置类的顺序的, 然而对于这个注解的错误使用也是非常多的, 而大多的错误使用在于没有真正的了解到它的使用场景
接下来我们来演示一下错误的使用 case
在工程内新建两个配置类, 直接使用注解
- @Configuration
- @AutoConfigureOrder(1)
- public class AConf2 {
- public AConf2() {
- System.out.println("A Conf2 init!");
- }
- }
- @Configuration
- @AutoConfigureOrder(-1)
- public class BConf2 {
- public BConf2() {
- System.out.println("B conf2 init!");
- }
- }
当注解生效时, BConf 会优先级加载
从输出结果来看, 和我们预期的不一样; 那么这个注解是不是作用于配置类中的 Bean 的顺序, 而不是配置类本身呢?
同样的我们设计一个 case 验证一下
- public class DemoA {
- private String name = "conf demo bean A";
- public DemoA() {
- System.out.println(name);
- }
- }
- public class DemoB {
- private String name = "conf demo bean B";
- public DemoB() {
- System.out.println(name);
- }
- }
- public class DemoC {
- private String name = "conf demo bean C";
- public DemoC() {
- System.out.println(name);
- }
- }
对应的配置类
- @Configuration
- @AutoConfigureOrder(1)
- public class AConf3 {
- @Bean
- public DemoA demoA() {
- return new DemoA();
- }
- @Bean
- public DemoC demoC() {
- return new DemoC();
- }
- }
- @Configuration
- @AutoConfigureOrder(-1)
- public class BConf3 {
- @Bean
- public DemoB demoB() {
- return new DemoB();
- }
- }
如果 DemoB 后被加载, 则说明上面的观点是错误的, 实测结果如下
所以问题来了,@AutoConfigureOrder 这个注解并不能指定配置类的顺序, 还叫这个名, 干啥? 存粹是误导人不是!!!
接下来我们看一下 @Order 和 @AutoConfigureOrder 的正确使用方式
III. 使用说明
1. @Order
先看一下这个注解的官方注释
- {
- @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 之间的顺序, 没有任何影响; 这句话根据我们上面的测试也可以验证
接下来我们需要看一下通过 @Order 注解来注入集合时, 指定顺序的场景
首先我们定义两个 Bean 实现同一个接口, 并添加上 @Order 注解
- public interface IBean {
- }
- @Order(2)
- @Component
- public class AnoBean1 implements IBean {
- private String name = "ano order bean 1";
- public AnoBean1() {
- System.out.println(name);
- }
- }
- @Order(1)
- @Component
- public class AnoBean2 implements IBean {
- private String name = "ano order bean 2";
- public AnoBean2() {
- System.out.println(name);
- }
- }
然后再一个测试 bean 中, 注入 IBean 的列表, 我们需要测试这个列表中的 Bean 的顺序是否和我们定义的 @Order 规则一致
- @Component
- public class AnoTestBean {
- public AnoTestBean(List<IBean> anoBeanList) {
- for (IBean bean : anoBeanList) {
- System.out.println("in ano testBean:" + bean.getClass().getName());
- }
- }
- }
根据我们的预期, anoBeanList 集合中, anoBean2 应该在前面
根据上面的输出, 也可以看出列表中的顺序和我们预期的一致, 并且 AnoOrderBean1 与 AnoOrderBean2 的加载顺序和注解没有关系
2. @AutoConfigureOrder
这个注解用来指定配置文件的加载顺序, 然而前面的测试中并没有生效, 那么正确的使用姿势是怎样的呢?
@AutoConfigureOrder 适用于外部依赖的包中 AutoConfig 的顺序, 而不能用来指定本包内的顺序
为了验证上面的说法, 我们再次新建两个工程, 并指定自动配置类的顺序
工程一配置如下:
- @AutoConfigureOrder(1)
- @Configuration
- @ComponentScan(value = {"com.git.hui.boot.order.addition"})
- public class AdditionOrderConf {
- public AdditionOrderConf() {
- System.out.println("additionOrderConf init!!!");
- }
- }
注意自动配置类如要被正确加载, 需要在工程的 /META-INF/spring.factories 文件中定义
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.Git.hui.boot.order.addition.AdditionOrderConf
工程二的配置如下:
- @Configuration
- @AutoConfigureOrder(-1)
- @ComponentScan("com.git.hui.boot.order.addition2")
- public class AdditionOrderConf2 {
- public AdditionOrderConf2() {
- System.out.println("additionOrderConf2 init!!!");
- }
- }
然后我们在项目内部添加一个配置
- @AutoConfigureOrder(10)
- @Configuration
- public class OrderConf {
- public OrderConf() {
- System.out.println("inner order conf init!!!");
- }
- }
因为注解适用于外部依赖包中的自动配置类的顺序, 所以上面三个配置类中, 正确的话 AdditionOrderConf2 在 AdditionOrderConf1 之前; 而 OrderConf 并不会收到注解的影响, 默认环境下, 内部定义的配置类会优于外部依赖, 从下面的输出也可以佐证我们说明 (当然为了验证确实如次, 还应该调整下两个外部工程配置类的顺序, 并观察下加载顺序是否随之改变, 我们这里省略掉了)
IV. 小结
本篇主要介绍了网上对 @Order 和 @AutoConfigureOrder 常见的错误使用姿势, 并给出了正确的使用 case.
下面用简单的几句话介绍一下正确的姿势
@Order 注解不能指定 bean 的加载顺序, 它适用于 AOP 的优先级, 以及将多个 Bean 注入到集合时, 这些 bean 在集合中的顺序
@AutoConfigureOrder 指定外部依赖的 AutoConfig 的加载顺序 (即定义在
/META-INF/spring.factories
文件中的配置 bean 优先级), 在当前工程中使用这个注解并没有什么鸟用
同样的
@AutoConfigureBefore
和 @AutoConfigureAfter 这两个注解的适用范围和 @AutoConfigureOrder 一样
0. 项目
工程: https://github.com/liuyueyi/spring-boot-demo
源码模块: - - -
1. 一灰灰 Blog
尽信书则不如, 以上内容, 纯属一家之言, 因个人能力有限, 难免有疏漏和错误之处, 如发现 bug 或者有更好的建议, 欢迎批评指正, 不吝感激
下面一灰灰的个人博客, 记录所有学习和工作中的博文, 欢迎大家前去逛逛
一灰灰 Blog 个人博客 https://blog.hhui.top/
一灰灰 Blog-Spring 专题博客 http://spring.hhui.top/
来源: http://www.bubuko.com/infodetail-3263976.html