SpringBoot系列教程之Bean加載順序之錯誤使用姿式闢謠

在網上查詢 Bean 的加載順序時,看到了大量的文章中使用@Order註解的方式來控制 bean 的加載順序,不知道寫這些的博文的同窗本身有沒有實際的驗證過,本文但願經過指出這些錯誤的使用姿式,讓觀文的小夥伴能夠知道@Order的具體的應用場景java

原文地址: SpringBoot系列教程之Bean加載順序之錯誤使用姿式闢謠git

I. 環境搭建

建立一個 maven 項目,pom 文件以下(具體的項目代碼,能夠在文末獲取)github

<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,一個@AutoConfigureOrderweb

I. @Order

err.case1: 類上添加 Order 註解

一種常見的錯誤觀點是在類上添加這個 Order 註解,就能夠指定 bean 之間的初始化順序,order 值越小,則優先級越高,接下來咱們實際測試一下,是否如此spring

咱們建立兩個 DemoBean, 指定不一樣的 Order 順序bash

@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 應該先被初始化,實際測試一下,輸出以下maven

err.case2: 配置類中 Bean 聲明方法上添加@Order

Bean 除了上面的自動掃描以外,還有一種方式就是經過@Bean註解,下面咱們演示一下在配置類中指定 bean 加載順序的錯誤 casespring-boot

一樣咱們新建兩個測試 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 應該在前面

根據上面的輸出,也能夠看出列表中的順序和咱們預期的一致,而且 AnoOrderBean1AnoOrderBean2 的加載順序和註解沒有關係

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. 項目

1. 一灰灰 Blog

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

一灰灰blog
相關文章
相關標籤/搜索