SpringBoot中的bean加載順序

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

最近在作傳統Spring項目到SpringBoot項目遷移過程當中,遇到了一些bean加載順序的問題:
好比一個config中的bean依賴於另外一個config中的bean進行初始化,因而查了一些資料,出現了一些新的概念:java

  • @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.git

在Spring 4.0版本以前,@Order註解只能控制AOP的執行順序,在Spring 4.0以後,它還能夠控制集合注入中bean的順序。
控制AOP順序很好理解,例如能夠在@Aspect註解的切面上加入@Order註解,控制切面的執行順序。
還有@EnableTransactionManagement(order = 10),這種寫法,因爲Spring的事務也是用AOP實現,也能夠控制優先級。github

下面舉個例子說明控制集合注入中bean的順序。spring

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;
}
}

最後是測試類:app

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集合多是亂序的。less

有一種錯誤的用法:
先定義兩個service:ide

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的注入順序的。spring-boot

@AutoConfigureAfter註解

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

相似的註解還有:

  • @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

相關文章
相關標籤/搜索