1. 程式人生 > >SpringBoot系列教程之Bean載入順序之錯誤使用姿勢闢謠

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

在網上查詢 Bean 的載入順序時,看到了大量的文章中使用@Order註解的方式來控制 bean 的載入順序,不知道寫這些的博文的同學自己有沒有實際的驗證過,本文希望通過指出這些錯誤的使用姿勢,讓觀文的小夥伴可以知道@Order的具體的應用場景

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

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 應該在前面

根據上面的輸出,也可以看出列表中的順序和我們預期的一致,並且 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. 專案

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 原始碼模組: - https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/008-beanorder - https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/008-beanorder-addition - https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/008-beanorder-addition2

1. 一灰灰 Blog

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

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

  • 一灰灰 Blog 個人部落格 https://blog.hhui.top
  • 一灰灰 Blog-Spring 專題部落格 http://spring.hhui.top

相關推薦

SpringBoot系列教程Bean載入順序錯誤使用姿勢闢謠

在網上查詢 Bean 的載入順序時,看到了大量的文章中使用@Order註解的方式來控制 bean 的載入順序,不知道寫這些的博文的同學自己有沒有實際的驗證過,本文希望通過指出這些錯誤的使用姿勢,讓觀文的小夥伴可以知道@Order的具體的應用場景 原文地址: SpringBoot系列教程之Bean載入順序

SpringBoot系列教程Bean指定初始化順序的若干姿勢

上一篇博文介紹了@Order註解的常見錯誤理解,它並不能指定 bean 的載入順序,那麼問題來了,如果我需要指定 bean 的載入順序,那應該怎麼辦呢? 本文將介紹幾種可行的方式來控制 bean 之間的載入順序 構造方法依賴 @DependOn 註解 BeanPostProcessor 擴充套件 原

Java工程師SpringBoot系列教程前言&目錄

前言 與時俱進是每一個程式設計師都應該有的意識,當一個Java程式設計師在當代步遍佈的時候,你就行該想到我能多學點什麼。可觀的是後端的框架是穩定的,它們能夠維持更久的時間在應用中,而不用擔心技術的更新換代。但是類似SSH,SSM這些框架已經太久了,人們迫不及待地想使用更為優雅而又簡便的框架來代替,所以Spr

SpringBoot系列教程應用篇藉助Redis搭建一個簡單站點統計服務

判斷一個網站值不值錢的一個重要標準就是看pv/uv,那麼你知道pv,uv是怎麼統計的麼?當然現在有第三方做的比較完善的可以直接使用

SpringBoot系列教程JPA新增記錄使用姿勢

SpringBoot系列教程JPA之新增記錄使用姿勢 上一篇文章介紹瞭如何快速的搭建一個JPA的專案環境,並給出了一個簡單的

SpringBoot系列教程JPA基礎環境搭建

JPA(Java Persistence API)Java持久化API,是 Java 持久化的標準規範,Hibernate是持久

SpringBoot系列教程JPAupdate使用姿勢

原文: 190623-SpringBoot系列教程JPA之update使用姿勢 上面兩篇博文拉開了jpa使用姿勢的面紗一角,

SpringBoot系列教程web篇Post請求引數解析姿勢彙總

作為一個常年提供各種Http介面的後端而言,如何獲取請求引數可以說是一項基本技能了,本篇為《190824-SpringBoot系列

SpringBoot系列教程Redis叢集環境配置

之前介紹的幾篇redis的博文都是基於單機的redis基礎上進行演示說明的,然而在實際的生產環境中,使用redis叢集的可能性應該

SpringBoot系列教程JPA指定id儲存

原文連結: 191119-SpringBoot系列教程JPA之指定id儲存 前幾天有位小夥伴問了一個很有意思的問題,使用 JPA 儲存資料時,即便我指定了主鍵 id,但是新插入的資料主鍵卻是 mysql 自增的 id;那麼是什麼原因導致的呢?又可以如何解決呢? 本文將介紹一下如何使用 JPA 的 AUTO

SpringBoot系列教程事務傳遞屬性

200202-SpringBoot系列教程之事務傳遞屬性 對於mysql而言,關於事務的主要知識點可能幾種在隔離級別上;在Spring體系中,使用事務的時候,還有一個知識點事務的傳遞屬性同樣重要,本文將主要介紹7中傳遞屬性的使用場景 I. 配置 本文的case,將使用宣告式事務,首先我們建立一個Sp

spring原始碼閱讀(1)- ioc依賴注入bean載入

還是先看下DefaultListableBeanFactory的類結構圖  我們從User user = (User) beanFactory.getBean("user");入手進入bean的載入管理流程。 這裡還是堅持走主線的流程,去掉無關的枝葉,儘量讓業務變得簡

python初級實戰系列教程《一、爬蟲爬取網頁、圖片、音視訊》

python基礎知識可以到廖雪峰大佬的官網學習哦! 廖雪峰官網網址 學完python就開始我們的實戰吧!首先我們就來學習下python爬蟲 學習Python爬蟲,先是介紹一個最容易上手的庫urll

Spring原始碼閱讀Bean載入(xml)1

先上兩張圖,簡單的畫了一下beanFactory各個類之間的關係,XmlBeanFactory是bean載入的入口和核心。Spring中大量使用了設計模式和UML中的設計原則,比如單一職責原則,從類圖可以看出,BeanFactory派生的各個介面,根據名字的不同,都增加了

【thinkphp5操作redis系列教程】雜湊型別hMset

<?php namespace app\index\controller; use Redis; class Index { public function index() { $redis = new Redis();

資料探勘入門系列教程(四點五)Apriori演算法

[TOC] ## 資料探勘入門系列教程(四點五)之Apriori演算法 Apriori(先驗)演算法**關聯規則**學習的經典演算法之一,用來尋找出資料集中頻繁出現的資料集合。如果看過以前的部落格,是不是想到了這個跟[資料探勘入門系列教程(一)之親和性分析](https://www.cnblogs.com

資料探勘入門系列教程(七點五)神經網路介紹

[TOC] ## 資料探勘入門系列教程(七點五)之神經網路介紹 這篇部落格是是為了下一篇部落格“使用神經網路破解驗證碼”做準備。主要是對神經網路的原理做介紹。同時這篇部落格主要是參考了西瓜書,如果身邊有西瓜書的同學,強烈建議直接去看西瓜書,至於我這篇部落格,你就當個樂子好了(因為你會發現內容與西瓜書很相似

資料探勘入門系列教程(八點五)SVM介紹以及從零開始推導公式

目錄SVM介紹線性分類間隔最大間隔分類器拉格朗日乘子法(Lagrange multipliers)拉格朗日乘子法推導KKT條件(Karush-Kuhn-Tucker Conditions)拉格朗日乘子法對偶問題Slater 條件最大間隔分類器與拉格朗日乘子法核技巧核函式軟間隔軟間隔支援向量機推導SMO演算法S

資料探勘入門系列教程(十點五)DNN介紹及公式推導

## 深度神經網路(DNN,Deep Neural Networks)簡介 首先讓我們先回想起在之前部落格([資料探勘入門系列教程(七點五)之神經網路介紹](https://www.cnblogs.com/xiaohuiduan/p/12623925.html))中介紹的神經網路:為了解決M-P模型中無法處

SpringBoot系列教程起步

本篇學習目標 SpringBoot is What? How use it ? Quick to use 本章原始碼下載 SpringBoot is What? Spring的目標是致力於全方位簡化Java開發 SpringBoot是簡化Java開發 我們來探討一下一個