這篇文章來自我的部落格
正文之前
上一篇文章已經介紹了 Bean 的基本概念,所以今天這篇文章是基於上一篇中 Bean 的三種裝配方式來進一步探討,內容中會穿插著基礎的依賴注入的概念
主要內容
- 自動裝配
- 使用 XML 裝配
- 使用 Java 裝配
正文
1. 自動裝配
- Bean 的命名
上一篇文章中使用註解定義 Bean 的時候都是在類的定義上加註解,小寫的類名作為 Bean 的 ID,但是我們可以自己命名 Bean:
設定元件的值為 Bean 的名字,如果這麼做了,在測試中就找不到名為“student”的 Bean 了:
將測試中獲取的 Bean 的名字改為“myBean”之後就能通過測試了
- 元件掃描
上次講到了元件掃描,設定元件掃描的方式有兩種:
- XML 檔案中配置
- Java 配置類中新增註解
我們再還原 student 這個 Bean 的狀態,也就是在類的定義上加一個註解 @Component:
在配置檔案中新增組建掃描:
這個是上次講過的,就不再多說,然後來看看 Java 配置類的元件掃描:
新增的註解 @Component 帶一個引數,指定了掃描的包的位置,就相當於 XML 配置檔案中的 base-package 屬性
如果不填寫引數,就掃描此檔案所在的包,在這裡,我們掃描名為“bean”的包,點選註解左邊的那個按鈕就能跳轉到 Student 類(bean 包中)
- 進階自動裝配
此處引用《Spring實戰》的一句話:
簡單來說,自動裝配就是讓 Spring 自動滿足 Bean 依賴的一種方法,在滿足依賴的過程中,會在 Spring 應用上下文中尋找匹配某個 Bean 需求的其他 Bean
我們這裡新增一個介面 Book,至於為什麼用介面而不用實現類,因為我們後文要對這個介面做多個實現:
然後建立一個實現了 Book 介面的類,並且將其作為一個 Bean:
既然是自動裝配,肯定要用到註解 @Autowired,那麼,這個註解可以用在什麼地方呢?
- 例項化
- 構造器(構造器注入)
- Setter 方法(設值注入)
第一點之前說過,在例項化時候新增這個註解進行自動裝配
怎麼在構造器上使用呢?
我們修改一下 Student 類:
在構造 Student 的時候,我將一個 Book 類注入,因為我們剛才已經有了 Book 類的實現,所以這裡注入的是 englishBook 這個 Bean
需要注意的是:Spring 在建立 student 這個 Bean 的時候,會傳入一個 Book 型別的 Bean
有兩種情況會導致丟擲異常,沒有所匹配的 Bean 或有多個所匹配的 Bean
為了驗證第一種情況,我將 EnglishBook 的 @Component 註解去掉,這樣子就沒有 Book 型別的 Bean 了
- 將 @Autowired 的屬性值 required 設定為 false,可避免圖中的缺少 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
- 若有多個符合要求的 Bean,就需要一下方案來消除歧義:
- 標識首選的一個 Bean:@Primary 註解
測試後,證實了註解有效:
- 在需要注入的地方使用 @Qualifier註解:
這個註解對於構造器注入是不可用的,接下來在 Setter 方法上裝配的時候再講解
怎麼在 Setter 方法上使用呢?
我們需要修改一下 Student 類,並且暫時隱藏一下 mathBook 這個 Bean:
帶有 @Autowired 註解,在例項化時能夠自動匹配 Book 型別的 Bean:
關於 Setter 方法和構造器來裝配 Bean,就目前的學習情況來說,只有一點區別,如果有多個不同型別的 Bean,使用構造器能夠指定裝配的順序
現在我們還原 mathBook 這個 Bean,如果出現上述的兩個可能丟擲異常的情況,這裡有兩種解決方式:
- @Primary 註解:
和上述類似,不多說
- 在需要注入的地方使用限定符,也就是 @Qualifier註解:
然後就能列印出英語書的資訊(不截圖了)
這個註解有一點靈活的地方就是,能夠在 Bean 的定義上設定限定符,也就是說,限定符不一定要是 Bean 的名字,做個測試:
這時候,如果還使用 englishBook 作為限定符,也能夠定位到這個 Bean,但是我們用自定義的限定符也可以,修改 Setter 方法上的限定符,進行測試:
證明使用 testQualifier 作為限定符也能得到結果
2. 使用 XML 裝配
使用 XML 裝配也有分構造器注入還是設值注入(Setter)方法,接下來的前三個使用的是構造器注入
1. 構造器注入
傳遞 Bean 的引用
將自動裝配部分新增的註解取消掉,把程式碼還原成初始狀態,然後我們開始使用 XML 裝配:
暫時不配置 mathBook 這個 Bean,並修改 StudentTest 的上下文配置資訊:
最後便是 Bean 的配置檔案:
首先建立 englishBook 這個 Bean,在建立 student 這個 Bean,這裡有一個元素
<constructor-arg ref=""/>
複製程式碼
因為我在 student 的構造器中有一個名叫 englishBook 的Bean 作為引數,所以 Spring 會建立一個 ID 為 englishBook 的Bean,並將其引用傳遞至 student 的構造器中,ref 屬性表明了傳遞引用的 Bean 的ID
傳遞字串
如果我在 student 的構造器中傳入的不是某個 Bean 的引用,而是某個字串呢?
修改 XML 檔案中的配置資訊:
value 屬性表示的是構造器中的字面量的值,可以是字串、數字或布林值等
然後測試一下:
證明字面量被成功注入了
傳遞列表
一個學生不可能只有一本書,所以我們要考慮,如何傳入列表呢?
首先我們先修改 Student 類,傳入的引數不僅是字串,還有一個存放書本的列表:
然後在 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 的引用,然後來進行測試:
如果列表中也存放字串,而不是引用,在元素中使用屬性來代替屬性
2. 設值注入
修改 Student 類和 Bean 配置檔案:
然後在 Bean 的配置檔案中加上這麼一句:
<bean id="student" class="bean.Student">
<property name="book" ref="englishBook"/>
</bean>
複製程式碼
屬性是為 Setter 方法提供服務的,和上述的屬性所提供的服務是一樣的,在這個語句中,我在 book 中注入了 ID 為 englishBook 的 Bean 的引用,然後測試,會輸出英語書的資訊:
關於字面量和列表的注入,其實和構造器注入是類似的,將屬性替換為,只不過,設定注入需要設定屬性名,也就是將 a Bean 的引用(或字面量或集合)注入到 b 屬性中,就不再詳述
3. 使用 Java 裝配
除了使用 XML,還可使用 Java 程式碼來裝配 Bean 並且達到依賴注入的目的
所以我們需要先修改測試中的上下文配置資訊:
@ContextConfiguration(classes = StudentConfig.class)
複製程式碼
再將 Student 類改回構造器注入的型別:
使用 Java 進行 Bean 的裝配在上一篇文章中已經提到,這裡就說一說依賴注入的實現:
在 Java 配置類中定義兩個 Bean:
在 student 中使用構造器注入了 englishBook 這個 Bean,執行測試,將會得到英語書的輸出:
總結
其實,這一篇文章把 Bean 的裝配和依賴注入混合起來將了,可能有些人會覺得亂,但是三種方式其實從思路來說是類似的,通過認真地閱讀這兩篇關於 Bean 的介紹,就能對 Spring 的 DI 以及 IoC 有一個基本瞭解
DI(Dependency Injection)稱為依賴注入,通俗的說,是在執行時,動態地滿足某個物件對其他物件的需求,用上面的例子來說,student 在執行時需要 Book 型別的 Bean 作為依賴,我就給它注入一個 Bean,叫做 englishBook,這就能夠叫做依賴注入
IoC(Inversion of Control)稱為控制反轉,在上面做的所有測試中,我都沒有手動建立某個物件的例項,比如說注入一個列表,我甚至都沒有建立一個列表的例項,那麼為什麼還會通過測試呢?因為 Spring 幫我在程式執行時建立了各個 Bean 的例項
IoC 能夠由 DI 來實現,也就是說,程式中的各個元件之間的依賴關係都是由 Spring 來管理的,這就稱作控制反轉,這是 Spring 的核心
接下來還陸續會有文章來講述這一概念