https://www.dazhuanlan.com/2019/10/22/5daebc5d16429/

最近在做传统Spring项目到SpringBoot项目迁移过程中,遇到了一些bean加载顺序的问题:
比如一个config中的bean依赖于另一个config中的bean进行初始化,于是查了一些资料,出现了一些新的概念:

  • @Order
  • @AutoConfigureAfter
  • @DependsOn

@Order注解

Before Spring 4.0, the @Order annotation was used only for the AspectJ execution order. It means the highest order advice will run first.

Since Spring 4.0, it supports the ordering of injected components to a collection. As a result, Spring will inject the auto-wired beans of the same type based on their order value.

在Spring 4.0版本之前,@Order注解只能控制AOP的执行顺序,在Spring 4.0之后,它还可以控制集合注入中bean的顺序。
控制AOP顺序很好理解,例如可以在@Aspect注解的切面上加入@Order注解,控制切面的执行顺序。
还有@EnableTransactionManagement(order = 10),这种写法,由于Spring的事务也是用AOP实现,也可以控制优先级。

下面举个例子说明控制集合注入中bean的顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface {
int getRating();
}
 
 
@Order(1)
public class Excellent implements {
 
@Override
public int getRating() {
return 1;
}
}
 
 
@Order(2)
public class Good implements {
 
@Override
public int getRating() {
return 2;
}
}
 
 
@Order(Ordered.LOWEST_PRECEDENCE)
public class Average implements {
 
@Override
public int getRating() {
return 3;
}
}

最后是测试类:

1
2
3
4
5
6
7
8
9
10
11
12
public class RatingRetrieverUnitTest {
 
@Autowired
private List<Rating> ratings;
 
@Test
public void givenOrder_whenInjected_thenByOrderValue() {
assertThat(ratings.get(0).getRating(), is(equalTo(1)));
assertThat(ratings.get(1).getRating(), is(equalTo(2)));
assertThat(ratings.get(2).getRating(), is(equalTo(3)));
}
}

如果不使用@Order注解,那ratings集合可能是乱序的。

有一种错误的用法:
先定义两个service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class OrderService1 {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1.class);
 
public OrderService1() {
LOGGER.info("OrderService1 constructor");
}
 
public String name() {
return "orderService1";
}
}
 
@Service
public class OrderService2 {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService2.class);
 
public OrderService2() {
LOGGER.info("OrderService2 constructor");
}
 
public String name() {
return "orderService2";
}
}

然后写两个config注入bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@Order(1)
public class OrderService1Config {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1Config.class);
 
@Bean
public OrderService1 orderService1() {
LOGGER.info("orderService1 init");
return new OrderService1();
}
}
@Configuration
@Order(0)
public class OrderService2Config {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService2Config.class);
 
@Bean
public OrderService2 orderService2() {
LOGGER.info("orderService2 init");
return new OrderService2();
}
}

本意是想通过@Order控制bean的注入顺序,先注入orderService2,再注入orderService1。但是并没有效果。
所以,@Order注解放到@Configuration中是无法控制bean的注入顺序的。

@AutoConfigureAfter注解

Hint for that an EnableAutoConfiguration auto-configuration should be applied after other specified auto-configuration classes.

类似的注解还有:

  • @AutoConfigureBefore
  • @AutoConfigureOrder

这三个注解是特地用于autoconfigure类的,不能用于普通的配置类。

有必要先说明一下autoconfigure类项目。

通常我们会在主类入口上标注@SpringBootApplication注解,或者直接标注@EnableAutoConfiguration注解。
这个注解是用来根据类路径中的依赖包猜测需要注入的bean,实现自动注入:

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined.

可以理解为,@EnableAutoConfiguration是服务于自动注入的bean的,即spring-boot-starter中bean的自动加载顺序。
被排序的这些类,都是通过xxx-spring-boot-autoconfigure项目中的src/resources/META-INF/spring.factories配置文件获取的,这个文件中的配置内容一般为:

1
2
3
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration

Spring Boot 只会对从这个文件读取的配置类进行排序。

但是不要以为将自己的配置类也配置在spring.factories中就能实现排序,如果你的类被自己Spring Boot启动类扫描到了,这个类的顺序会优先于所有通过spring.factories读取的配置类。

Auto-configuration is always applied after user-defined beans have been registered.

@DependsOn注解

Beans on which the current bean depends. Any beans specified are guaranteed to be created by the container before this bean.

Used infrequently in cases where a bean does not explicitly depend on another through properties or constructor arguments, but rather depends on the side effects of another bean’s initialization.

May be used on any class directly or indirectly annotated with org.springframework.stereotype.Component or on methods annotated with Bean.

Using DependsOn at the class level has no effect unless component-scanning is being used. If a DependsOn-annotated class is declared via XML, DependsOn annotation metadata is ignored, and is respected instead.

从java doc中可以看出,@DependsOn注解可以用来控制bean的创建顺序,该注解用于声明当前bean依赖于另外一个bean。所依赖的bean会被容器确保在当前bean实例化之前被实例化。
一般用在一个bean没有通过属性或者构造函数参数显式依赖另外一个bean,但实际上会使用到那个bean或者那个bean产生的某些结果的情况。

用法

  • 直接或者间接标注在带有@Component注解的类上面;
  • 直接或者间接标注在带有@Bean注解的方法上面;
  • 使用@DependsOn注解到类层面仅仅在使用 component-scanning 方式时才有效;如果带有@DependsOn注解的类通过XML方式使用,该注解会被忽略,<bean depends-on="..."/>这种方式会生效。

例如,我们有一个FileProcessor依赖于FileReaderFileWriterFileReaderFileWriter需要在FileProcessor之前初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@ComponentScan("com.baeldung.dependson")
public class Config {
 
@Bean
@DependsOn({"fileReader","fileWriter"})
public FileProcessor fileProcessor(){
return new FileProcessor();
}
 
@Bean("fileReader")
public FileReader fileReader() {
return new FileReader();
}
 
@Bean("fileWriter")
public FileWriter fileWriter() {
return new FileWriter();
}
}

也可以在Component上标注:

1
2
3
 
@DependsOn({"filereader", "fileWriter"})
public class FileProcessor {}

属性注入和构造器注入

上面说到@DependsOn注解时提到,它一般用在一个bean没有通过属性或者构造函数参数显式依赖另外一个bean,但实际上会使用到那个bean或者那个bean产生的某些结果的情况。
如果bean直接依赖于另一个bean,我们可以将其通过属性或者构造函数引入进来。
而使用构造函数的方法显示依赖一个bean,能够保证被依赖的bean先初始化。但是属性注入不可以。

constructor-injection automatically enforces the order and completeness of the instantiated.

因此,我们可以在Component中使用构造函数显示注入依赖的bean:

1
2
3
4
@Autowired
public MyComponent(@Qualifier("jedisTemplateNew") JedisTemplate jedisTemplateNew) {
 
}

注意,需要使用@Qualifier限定bean名称时,不能标注在构造方法上,而是应该标注在参数上。原因跟@Resource不能标注构造方法一样,它不知道你要限定哪个参数。

假设有两个service,OrderService1依赖于OrderService2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class OrderService1 {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1.class);
 
private OrderService2 orderService2;
 
public OrderService1(OrderService2 orderService2) {
LOGGER.info("OrderService1 constructor");
this.orderService2 = orderService2;
}
 
public String name() {
String name = orderService2.name();
LOGGER.info("OrderService1 print orderService2 name={}", name);
return "orderService1";
}
}
 
public class OrderService2 {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService2.class);
 
public OrderService2() {
LOGGER.info("OrderService2 constructor");
}
 
public String name() {
return "orderService2";
}
}

对应的Configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
public class OrderService1Config {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1Config.class);
 
@Autowired
private OrderService2Config orderService2Config;
 
@Bean
public OrderService1 orderService1() {
LOGGER.info("orderService1 init");
return new OrderService1(orderService2Config.orderService2());
}
}
 
@Configuration
public class OrderService2Config {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService2Config.class);
 
@Bean
public OrderService2 orderService2() {
LOGGER.info("orderService2 init");
return new OrderService2();
}
}

输出:

1
2
3
4
c.m.s.config.OrderService1Config : orderService1 init
c.m.s.config.OrderService2Config : orderService2 init
c.m.s.service.OrderService2 : OrderService2 constructor
c.m.s.service.OrderService1 : OrderService1 constructor

可以看出,OrderService2先初始化。

换一种OrderService1写法,使用属性注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class OrderService1 {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1.class);
 
private OrderService2 orderService2;
 
public void setOrderService2(OrderService2 orderService2) {
this.orderService2 = orderService2;
}
 
public OrderService1() {
LOGGER.info("OrderService1 constructor");
}
 
public String name() {
String name = orderService2.name();
LOGGER.info("OrderService1 print orderService2 name={}", name);
return "orderService1";
}
}
 
@Configuration
public class OrderService1Config {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1Config.class);
 
@Autowired
private OrderService2Config orderService2Config;
 
@Bean
public OrderService1 orderService1() {
LOGGER.info("orderService1 init");
OrderService1 orderService1 = new OrderService1();
orderService1.setOrderService2(orderService2Config.orderService2());
return orderService1;
}
}

输出:

1
2
3
4
c.m.s.config.OrderService1Config : orderService1 init
c.m.s.service.OrderService1 : OrderService1 constructor
c.m.s.config.OrderService2Config : orderService2 init
c.m.s.service.OrderService2 : OrderService2 constructor

可以看出,OrderService2并没有先初始化。

当然,OrderService1Config也可以使用构造器注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class OrderService1Config {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService1Config.class);
 
private final OrderService2Config orderService2Config;
 
@Autowired
public OrderService1Config(OrderService2Config orderService2Config) {
this.orderService2Config = orderService2Config;
}
 
@Bean
public OrderService1 orderService1() {
LOGGER.info("orderService1 init");
OrderService1 orderService1 = new OrderService1();
orderService1.setOrderService2(orderService2Config.orderService2());
return orderService1;
}
}

参考:
@Order in Spring
Controlling Bean Creation Order with @DependsOn Annotation

SpringBoot中的bean加载顺序的更多相关文章

  1. SpringBoot系列教程之Bean加载顺序之错误使用姿势辟谣

    在网上查询 Bean 的加载顺序时,看到了大量的文章中使用@Order注解的方式来控制 bean 的加载顺序,不知道写这些的博文的同学自己有没有实际的验证过,本文希望通过指出这些错误的使用姿势,让观文 ...

  2. 详解web.xml中元素的加载顺序

    一.背景 最近在项目中遇到了启动时出现加载service注解注入失败的问题,后来经过不懈努力发现了是因为web.xml配置文件中的元素加载顺序导致的,那么就抽空研究了以下tomcat在启动时web.x ...

  3. springboot由于bean加载顺序导致的问题

    先记录现象: dubbo整合zipkin时,我的配置文件是这样的 @Bean("okHttpSender") public OkHttpSenderFactoryBean okHt ...

  4. SpringBoot中资源初始化加载的几种方式(看这一片就够了)

    一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么 ...

  5. SpringBoot中资源初始化加载的几种方式

    一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么 ...

  6. 【spring】bean加载顺序

    问题来源 有一个bean为A,一个bean为B.想要A在容器实例化的时候的一个属性name赋值为B的一个方法funB的返回值. 如果只是在A里单纯的写着: private B b; private S ...

  7. 服务器启动时Webapp的web.xml中配置的加载顺序

    一 1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Ser ...

  8. 服务器启动时Webapp的web.xml中配置的加载顺序(转载)

    一 1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Ser ...

  9. 「快学SpringBoot」配置文件的加载顺序和配置项默认值设置

    前言 有的时候,配置信息是我们无法在开发过程中就能确定的.比如,给客户开发的项目,客户需要根据自身的情况自定义配置,如数据库配置,加密密钥配置等等.这时候,就需要把配置文件放在外面,让用户自定义配置部 ...

随机推荐

  1. 二维数组 A[m][n] 按行优先和按列优先的 下标地址转换公式

    设二维数组 A[m][n] 按行优先存储, 每个元素占 p 个字节, 则 Loc(i, j) 的地址为 (i * n + m) * p, 第 i 行前面有 i 行, 每行有 n 个元素, 加上 第 i ...

  2. Winform中怎样对窗体进行隐藏,再次打开时仍然保留上次的窗体

    场景 点击按钮后打开窗口,点击窗口的确定按钮后即使窗体返回了Ok,此时不关闭窗体,将窗体隐藏. 再次点击按钮后,仍然打开上次的窗体. 注: 博客主页: https://blog.csdn.net/ba ...

  3. JavaSE学习笔记(6)---异常

    JavaSE学习笔记(6)---异常 ​ 软件程序在运行过程中,非常可能遇到问题,我们称之为异常,英文是:Exception,意思是例外.遇到这些例外情况,或者叫异常,我们怎么让写的程序做出合理的处理 ...

  4. Spring Cloud feign使用okhttp3

    指南 maven <dependency> <groupId>io.github.openfeign</groupId> <artifactId>fei ...

  5. win下的终端使用指南

    win下的终端使用指南 win 下的命令行工具是真的难用 . 具体的难用就不形容了 . 有了 PowerShell 也没觉得好用 . 还是喜欢Linux的终端,及Bash命令. 替换方案 比较好的替换 ...

  6. Spark学习之路 (十)SparkCore的调优之Shuffle调优[转]

    概述 大多数Spark作业的性能主要就是消耗在了shuffle环节,因为该环节包含了大量的磁盘IO.序列化.网络数据传输等操作.因此,如果要让作业的性能更上一层楼,就有必要对shuffle过程进行调优 ...

  7. 【巨杉数据库SequoiaDB】巨杉Tech | 巨杉数据库的并发 malloc 实现

    本文由巨杉数据库北美实验室资深数据库架构师撰写,主要介绍巨杉数据库的并发malloc实现与架构设计.原文为英文撰写,我们提供了中文译本在英文之后. SequoiaDB Concurrent mallo ...

  8. win10系统家庭版升级到专业版

    win10家庭版升级专业版密钥:VK7JG-NPHTM-C97JM-9MPGT-3V66T4N7JM-CV98F-WY9XX-9D8CF-369TT FMPND-XFTD4-67FJC-HDR8C-3 ...

  9. PAT (Basic Level) Practice (中文)1023 组个最小数 (20 分) (排序)

    给定数字 0-9 各若干个.你可以以任意顺序排列这些数字,但必须全部使用.目标是使得最后得到的数尽可能小(注意 0 不能做首位).例如:给定两个 0,两个 1,三个 5,一个 8,我们得到的最小的数就 ...

  10. 【Unity|C#】基础篇(20)——枚举器与迭代器(IEnumerable/IEnumerator)

    [学习资料] <C#图解教程>(第18章):https://www.cnblogs.com/moonache/p/7687551.html 电子书下载:https://pan.baidu. ...