初識Spring —— Bean的裝配(二)

胖若倆人發表於2018-06-12

初識Spring —— Bean的裝配(二)

這篇文章來自我的部落格

正文之前

上一篇文章已經介紹了 Bean 的基本概念,所以今天這篇文章是基於上一篇中 Bean 的三種裝配方式來進一步探討,內容中會穿插著基礎的依賴注入的概念

主要內容

  1. 自動裝配
  2. 使用 XML 裝配
  3. 使用 Java 裝配

正文

1. 自動裝配

  • Bean 的命名

上一篇文章中使用註解定義 Bean 的時候都是在類的定義上加註解,小寫的類名作為 Bean 的 ID,但是我們可以自己命名 Bean:

初識Spring —— Bean的裝配(二)

設定元件的值為 Bean 的名字,如果這麼做了,在測試中就找不到名為“student”的 Bean 了:

初識Spring —— Bean的裝配(二)

初識Spring —— Bean的裝配(二)

將測試中獲取的 Bean 的名字改為“myBean”之後就能通過測試了

  • 元件掃描

上次講到了元件掃描,設定元件掃描的方式有兩種:

  1. XML 檔案中配置
  2. Java 配置類中新增註解

我們再還原 student 這個 Bean 的狀態,也就是在類的定義上加一個註解 @Component

在配置檔案中新增組建掃描:

初識Spring —— Bean的裝配(二)

這個是上次講過的,就不再多說,然後來看看 Java 配置類的元件掃描:

初識Spring —— Bean的裝配(二)

新增的註解 @Component 帶一個引數,指定了掃描的包的位置,就相當於 XML 配置檔案中的 base-package 屬性

如果不填寫引數,就掃描此檔案所在的包,在這裡,我們掃描名為“bean”的包,點選註解左邊的那個按鈕就能跳轉到 Student 類(bean 包中)

  • 進階自動裝配

此處引用《Spring實戰》的一句話:

簡單來說,自動裝配就是讓 Spring 自動滿足 Bean 依賴的一種方法,在滿足依賴的過程中,會在 Spring 應用上下文中尋找匹配某個 Bean 需求的其他 Bean

我們這裡新增一個介面 Book,至於為什麼用介面而不用實現類,因為我們後文要對這個介面做多個實現:

初識Spring —— Bean的裝配(二)

然後建立一個實現了 Book 介面的類,並且將其作為一個 Bean:

初識Spring —— Bean的裝配(二)

既然是自動裝配,肯定要用到註解 @Autowired,那麼,這個註解可以用在什麼地方呢?

  • 例項化
  • 構造器(構造器注入)
  • Setter 方法(設值注入)

第一點之前說過,在例項化時候新增這個註解進行自動裝配

怎麼在構造器上使用呢?

我們修改一下 Student 類:

初識Spring —— Bean的裝配(二)

在構造 Student 的時候,我將一個 Book 類注入,因為我們剛才已經有了 Book 類的實現,所以這裡注入的是 englishBook 這個 Bean

需要注意的是:Spring 在建立 student 這個 Bean 的時候,會傳入一個 Book 型別的 Bean

有兩種情況會導致丟擲異常,沒有所匹配的 Bean 或有多個所匹配的 Bean

為了驗證第一種情況,我將 EnglishBook 的 @Component 註解去掉,這樣子就沒有 Book 型別的 Bean 了

初識Spring —— Bean的裝配(二)

  • @Autowired 的屬性值 required 設定為 false,可避免圖中的缺少 Bean 的情況:

初識Spring —— Bean的裝配(二)

執行測試之後,看到兩條主要的錯誤資訊:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'student' defined in file [C:\Users\94545\Desktop\Developer Folder\Java Test Folder\springtest\out\production\springtest\bean\Student.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'bean.Book' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'bean.Book' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

首先是名為“student” 的 Bean 的構造器中的依賴關係出錯了,第二是沒有 Book 型別的 Bean,導致 Student 的 Bean 的構造器中條件不符合

為了驗證第二種情況,我將程式碼還原,並且建立另一個 Book 型別的 Bean:mathBook,在此情況下,測試是不會通過的,因為沒有單一符合條件的 Bean

初識Spring —— Bean的裝配(二)

  • 若有多個符合要求的 Bean,就需要一下方案來消除歧義:
  1. 標識首選的一個 Bean:@Primary 註解

初識Spring —— Bean的裝配(二)

測試後,證實了註解有效:

初識Spring —— Bean的裝配(二)

  1. 在需要注入的地方使用 @Qualifier註解:

這個註解對於構造器注入是不可用的,接下來在 Setter 方法上裝配的時候再講解

初識Spring —— Bean的裝配(二)

怎麼在 Setter 方法上使用呢?

我們需要修改一下 Student 類,並且暫時隱藏一下 mathBook 這個 Bean:

初識Spring —— Bean的裝配(二)

帶有 @Autowired 註解,在例項化時能夠自動匹配 Book 型別的 Bean:

初識Spring —— Bean的裝配(二)

關於 Setter 方法和構造器來裝配 Bean,就目前的學習情況來說,只有一點區別,如果有多個不同型別的 Bean,使用構造器能夠指定裝配的順序

現在我們還原 mathBook 這個 Bean,如果出現上述的兩個可能丟擲異常的情況,這裡有兩種解決方式:

  1. @Primary 註解:

和上述類似,不多說

  1. 在需要注入的地方使用限定符,也就是 @Qualifier註解:

初識Spring —— Bean的裝配(二)

然後就能列印出英語書的資訊(不截圖了)

這個註解有一點靈活的地方就是,能夠在 Bean 的定義上設定限定符,也就是說,限定符不一定要是 Bean 的名字,做個測試:

初識Spring —— Bean的裝配(二)

這時候,如果還使用 englishBook 作為限定符,也能夠定位到這個 Bean,但是我們用自定義的限定符也可以,修改 Setter 方法上的限定符,進行測試:

初識Spring —— Bean的裝配(二)

證明使用 testQualifier 作為限定符也能得到結果

2. 使用 XML 裝配

使用 XML 裝配也有分構造器注入還是設值注入(Setter)方法,接下來的前三個使用的是構造器注入

1. 構造器注入
傳遞 Bean 的引用

將自動裝配部分新增的註解取消掉,把程式碼還原成初始狀態,然後我們開始使用 XML 裝配:

初識Spring —— Bean的裝配(二)

暫時不配置 mathBook 這個 Bean,並修改 StudentTest 的上下文配置資訊:

初識Spring —— Bean的裝配(二)

最後便是 Bean 的配置檔案:

初識Spring —— Bean的裝配(二)

首先建立 englishBook 這個 Bean,在建立 student 這個 Bean,這裡有一個元素

<constructor-arg ref=""/>
複製程式碼

因為我在 student 的構造器中有一個名叫 englishBook 的Bean 作為引數,所以 Spring 會建立一個 ID 為 englishBook 的Bean,並將其引用傳遞至 student 的構造器中,ref 屬性表明了傳遞引用的 Bean 的ID

傳遞字串

如果我在 student 的構造器中傳入的不是某個 Bean 的引用,而是某個字串呢?

初識Spring —— Bean的裝配(二)

修改 XML 檔案中的配置資訊:

初識Spring —— Bean的裝配(二)

value 屬性表示的是構造器中的字面量的值,可以是字串、數字或布林值等

然後測試一下:

初識Spring —— Bean的裝配(二)

證明字面量被成功注入了

傳遞列表

一個學生不可能只有一本書,所以我們要考慮,如何傳入列表呢?

首先我們先修改 Student 類,傳入的引數不僅是字串,還有一個存放書本的列表:

初識Spring —— Bean的裝配(二)

然後在 XML 檔案中配置 Bean:

    <bean id="englishBook" class="bean.EnglishBook"/>
    <bean id="mathBook" class="bean.MathBook"/>
複製程式碼

最後配置 student 這個 Bean,並且配置構造器引數:

    <bean id="student" class="bean.Student">
        <constructor-arg value="I have some books"/>
        <constructor-arg>
            <list>
                <ref bean="englishBook"/>
                <ref bean="mathBook"/>
            </list>
        </constructor-arg>
    </bean>
複製程式碼

第一個引數是字串,第二個是 Book 型別的 List,列表中存放的是剛才建立的兩個 Bean 的引用,然後來進行測試:

初識Spring —— Bean的裝配(二)

如果列表中也存放字串,而不是引用,在元素中使用屬性來代替屬性

2. 設值注入

修改 Student 類和 Bean 配置檔案:

初識Spring —— Bean的裝配(二)

然後在 Bean 的配置檔案中加上這麼一句:

    <bean id="student" class="bean.Student">
        <property name="book" ref="englishBook"/>
    </bean>
複製程式碼

屬性是為 Setter 方法提供服務的,和上述的屬性所提供的服務是一樣的,在這個語句中,我在 book 中注入了 ID 為 englishBook 的 Bean 的引用,然後測試,會輸出英語書的資訊:

初識Spring —— Bean的裝配(二)

關於字面量和列表的注入,其實和構造器注入是類似的,將屬性替換為,只不過,設定注入需要設定屬性名,也就是將 a Bean 的引用(或字面量或集合)注入到 b 屬性中,就不再詳述

3. 使用 Java 裝配

除了使用 XML,還可使用 Java 程式碼來裝配 Bean 並且達到依賴注入的目的

所以我們需要先修改測試中的上下文配置資訊:

@ContextConfiguration(classes = StudentConfig.class)
複製程式碼

再將 Student 類改回構造器注入的型別:

初識Spring —— Bean的裝配(二)

使用 Java 進行 Bean 的裝配在上一篇文章中已經提到,這裡就說一說依賴注入的實現:

在 Java 配置類中定義兩個 Bean:

初識Spring —— Bean的裝配(二)

在 student 中使用構造器注入了 englishBook 這個 Bean,執行測試,將會得到英語書的輸出:

初識Spring —— Bean的裝配(二)

總結

其實,這一篇文章把 Bean 的裝配和依賴注入混合起來將了,可能有些人會覺得亂,但是三種方式其實從思路來說是類似的,通過認真地閱讀這兩篇關於 Bean 的介紹,就能對 Spring 的 DI 以及 IoC 有一個基本瞭解

DI(Dependency Injection)稱為依賴注入,通俗的說,是在執行時,動態地滿足某個物件對其他物件的需求,用上面的例子來說,student 在執行時需要 Book 型別的 Bean 作為依賴,我就給它注入一個 Bean,叫做 englishBook,這就能夠叫做依賴注入

IoC(Inversion of Control)稱為控制反轉,在上面做的所有測試中,我都沒有手動建立某個物件的例項,比如說注入一個列表,我甚至都沒有建立一個列表的例項,那麼為什麼還會通過測試呢?因為 Spring 幫我在程式執行時建立了各個 Bean 的例項

IoC 能夠由 DI 來實現,也就是說,程式中的各個元件之間的依賴關係都是由 Spring 來管理的,這就稱作控制反轉,這是 Spring 的核心

接下來還陸續會有文章來講述這一概念

相關文章