上篇文章我們已經學習了
1.4
小結中關於依賴注入跟方法注入的內容。這篇文章我們繼續學習這結中的其他內容,順便解決下我們上篇文章留下來的一個問題-----注入模型。
文章目錄
前言:
在看下面的內容之前,我們先要對自動注入及精確注入有一個大概的了解,所謂精確注入就是指,我們通過構造函數或者setter方法指定了我們對象之間的依賴,也就是我們上篇文章中講到的依賴注入,然后Spring根據我們指定的依賴關系,精確的給我們完成了注入。那么自動注入是什么?我們看下面一段代碼:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/dbeans/spring-beans.xsd" >
<bean id="auto" class="com.dmz.official.service.AutoService" autowire="byType"/>
<bean id="dmzService" class="com.dmz.official.service.DmzService"/>
</beans>
public class AutoService {
DmzService service;
public void setService(DmzService dmzService){
System.out.println("注入dmzService"+dmzService);
service = dmzService;
}
}
public class DmzService {
}
public class Main03 {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
System.out.println(cc.getBean("auto"));
}
}
在上面的例子中我們可以看到
- 我們沒有采用注解
@Autowired
進行注入 - XML中沒有指定屬性標簽
<property>
- 沒有使用構造函數
但是,打印結果如下:
注入dmzServicecom.dmz.official.service.DmzService@73a8dfcc // 這里完成了注入
com.dmz.official.service.AutoService@1963006a
可能細心的同學已經發現了,在AutoService
的標簽中我們新增了一個屬性autowire="byType"
,那么這個屬性是什么意思呢?為什么加這個屬性就能幫我們完成注入呢?不要急,我們帶着問題繼續往下看。
自動注入:
這部分內容主要涉及官網中的1.4.5小結。
我們先看官網上怎么說的:
自動注入的優點:
大概翻譯如下:
Spring可以自動注入互相協作的bean之間的依賴。自動注入有以下兩個好處:
- 自動注入能顯著的減少我們指定屬性或構造參數的必要。這個不難理解,我們在上篇文章中講過了,依賴注入的兩種方式,setter方法跟構造函數,見上篇文章依賴注入。在前言中的例子我們也能發現,我們並不需要指定屬性或構造參數
- 自動裝配可以隨着對象的演化更新配置。例如,如果需要向類添加依賴項,則可以自動滿足該依賴項,而不需要修改配置。因此,自動裝配在開發過程中特別有用,但是當我們的代碼庫變的穩定時,自動裝配也不會影響我們將裝配方式切換到精確注入(這個詞是我根據官網閱讀加自己理解翻譯過來的,也就是官網中的(explicit wiring)
注入模型:
接下來,官網給我們介紹了自動注入的四種模型,如圖:
我們一一進行解析並測試:
no
這是目前Spring默認的注入模型,也可以說默認情況下Spring是關閉自動注入,必須要我們通過setter方法或者構造函數完成依賴注入,並且Spring也不推薦修改默認配置。我們使用IDEA時也可以看到
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DZC5fi2F-1576598384639)(image/2019120204.jpg)]
用紅線框出來的部分建議我們使用精確的方式注入依賴。
從上面來說,Spring自動注入這種方式在我們實際開發中基本上用不到,但是為了更好的理解跟學習Spring源碼,我們也是需要好好學習這部分知識的。
byName
這種方式,我們為了讓Spring完成自動注入需要提供兩個條件
- 提供setter方法
- 如果需要注入的屬性為
xxx
,那么setter方法命名必須是setXxx
,也就是說,命名必須規范
在找不到對應名稱的bean的情況下,Spring也不會報錯,只是不會給我們完成注入。
測試代碼:
//記得需要將配置信息修改為:<bean id="auto" class="com.dmz.official.service.AutoService" autowire="byName"/>
public class AutoService {
DmzService dmzService;
/** * setXXX,Spring會根據XXX到容器中找對應名稱的bean,找到了就完成注入 */
public void setDmzService(DmzService dmzService){
System.out.println("注入dmzService"+dmzService);
service = dmzService;
}
}
另外我在測試的時候發現,這種情況下,如果我們提供的參數不規范也不會完成注入的,如下:
public class AutoService {
DmzService dmzService;
// indexService也被Spring所管理
IndexService indexService;
/** * setXXX,Spring會根據XXX到容器中找對應名稱的bean,找到了就完成注入 */
public void setDmzService(DmzService dmzService,IndexService indexService) {
System.out.println("注入dmzService" + dmzService);
this.dmzService = dmzService;
}
}
本以為這種情況Spring會注入dmzService,indexService為null,實際測試過程中發現這個set方法根本不會被調用,說明Spring在選擇方法時,還對參數進行了校驗,byName
這種注入模型下,參數只能是我們待注入的類型且只能有一個
byType
測試代碼跟之前唯一不同的就是修改配置autowire="byType"
,這里我們測試以下三種異常情況
- 找不到合適類型的bean,發現不報異常,同時不進行注入
- 找到了多個合適類型的bean,Spring會直接報錯
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.dmz.official.service.DmzService' available: expected single matching bean but found 2: dmzService,dmzService2
- set方法中有兩個參數,切兩個參數都能找到唯一一個類型符合的bean,不報異常,也不進行注入
另外需要說明的是,我在測試的過程,將set方法僅僅命名為set
,像這樣public void set(DmzService dmzService)
,這種情況下Spring也不會進行注入
我們可以發現,對於這兩種注入模型都是依賴setter方法完成注入的,並且對setter方法命名有一定要求(只要我們平常遵從代碼書寫規范,一般也不會踩到這些坑)。第一,不能有多個參數;第二,不能僅僅命名為set
constructor
當我們使用這種注入模型時,Spring會根據構造函數查找有沒有對應參數名稱的bean,有的話完成注入(跟前文的byName
差不多),如果根據名稱沒找到,那么它會再根據類型進行查找,如果根據類型還是沒找到,就會報錯。
自動注入的缺陷:
這里不得不說一句,Spring官網在這一章節有三分之二的內容是在說自定注入的缺陷以及如何將一個類從自動注入中排除,結合默認情況下自動注入是關閉的(默認注入模型為no
),可以說明,在實際使用情況中,Spring是非常不推薦我們開啟自動注入這種模型的。從官網中我們總結自動注入有以下幾個缺陷:
- 精確注入會覆蓋自動注入。並且我們不能注入基本數據類型,字符串,Class類型(這些數據的數組也不行)。而且這是Spring故意這樣設計的
- 自動注入不如精確注入准確。而且我們在使用自動注入時,對象之間的依賴關系不明確
- 對於一些為Spring容器生成文檔的工具,無法獲取依賴關系
- 容器中的多個bean定義可能會與自動注入的setter方法或構造函數參數指定的類型匹配。對於數組、集合或映射實例,這可能不會產生什么問題。但是,對於期望單個值的依賴項,我們無法隨意確定到底有誰進行注入。如果沒有唯一的bean定義可用,則會拋出異常
如何將Bean從自動注入中排除?
這里主要用到autowire-candidate
這個屬性,我們要將其設置為false
,這里需要注意以下幾點:
- 這個設置只對類型注入生效。這也很好理解,例如我們告訴Spring要自動注入一個
indexService
,同時我們又在indexService
的配置中將其從自動注入中排除,這就是自相矛盾的。所以在byName
的注入模型下,Spring直接忽略了autowire-candidate
這個屬性 autowire-candidate=false
這個屬性代表的是,這個bean不作為候選bean注入到別的bean中,而不是說這個bean不能接受別的bean的注入。例如在我們上面的例子中我們對AutoService
進行了如下配置:
<bean id="auto" class="com.dmz.official.service.AutoService" autowire="byType" autowire-candidate="false"/>
代表的是這個bean不會被注入到別的bean中,但是dmzService
任何會被注入到AutoService
中
另外需要說明的是,對於自動注入,一般我們直接在頂級的標簽中進行全局設置,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" <!--在這里進行配置-->
default-autowire="byName">
自動注入跟精確注入的比較總結:
連同上篇文章依賴注入,我畫了下面一個圖:
- 從關注的點上來看,自動注入是針對的整個對象,或者一整批對象。比如我們如果將
autoService
這個bean的注入模型設置為byName
,Spring會為我們去尋找所有符合要求的名字(通過set方法)bean並注入到autoService
中。而精確注入這種方式,是我們針對對象中的某個屬性,比如我們在autoService
中的dmzService
這個屬性字段上添加了@AutoWired
注解,代表我們要精確的注入dmzService
這個屬性。而方法注入主要是基於方法對對象進行注入。 - 我們通常所說***byName***,byType***跟我們在前文提到的注入模型中的
byName
,byType
是完全不一樣的。通常我們說的***byName,***byType***是Spring尋找bean的手段。比如,當我們注入模型為constructor
時,Spring會先通過名稱找對符合要求的bean,這種通過名稱尋找對應的bean的方式我們可以稱為byName
。我們可以將一次注入分為兩個階段,首先是尋找符合要求的bean,其次再是將符合要求的bean注入。也可以畫圖如下:
補充(1.4小結的剩余部分)
這部分比較簡單,也是1.4
小節中剩余的兩個小知識,在這篇文章我們也一並學習了~
depends-on:
我們首先要知道,默認情況下,Spring在實例化容器中的對象時是按名稱進行自然排序進行實例化的。比如我們現在有A,B,C三個對象,那么Spring在實例化時會按照A,B,C這樣的順序進行實例化。但是在某些情況下我們可能需要讓B在A之前完成實例化,這個時候我們就需要使用depends-on
這個屬性了。我們可以通過形如下面的配置完成:
<bean id="a" class="xx.xx.A" depends-on="b"/>
<bean id="b" class="xx.xx.B" />
或者:
@Component
@DependsOn("b")
public class A {
}
lazy:
默認情況下,Spring會在容器啟動階段完成所有bean的實例化,以及一系列的生命周期回調。某些情況下,我們
可能需要讓某一個bean延遲實例化。這種情況下,我們需要用到lazy
屬性,有以下兩種方式:
- XML中bean標簽的
lazy-init
屬性
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
@Lazy
注解
@Component
// 懶加載
@Lazy
public class A {
}
到此為止,官網中1.4
小節中的內容我們就全學習完啦!最核心的部分應該就是上文中的這個圖了。我們主要總結了Spring讓對象產生依賴的方式,同時對各個方式進行了對比。通過這部分的學習,我覺得大家應該對Spring的依賴相關知識會更加系統,這樣我們之后學習源碼時碰到疑惑也會少很多。
下面我們還要繼續學習Spring的官網,比如前面文章提到的Beandefinition
到底是什么東西?Spring中的Bean的生命周期回調又是什么?這些在官網中都能找到答案。
給自己加油,也給所有看到這篇文章的同學加油~!!