Spring學習(十八)Bean 的三種依賴注入方式介紹
阿新 • • 發佈:2019-02-01
依賴注入:讓呼叫類對某一介面實現類的依賴關係由第三方注入,以移除呼叫類對某一介面實現類的依賴。
接下來將詳細的向大家介紹Spring容器支援的三種依賴注入的方式以及具體配置方法:
• 屬性注入方法
• 建構函式注入方法
• 工廠方法注入方法
一.屬性注入
屬性注入即通過setXXX()方法注入Bean的屬性值或者依賴物件,由於屬性注入方式具有可選擇性和靈活高的優點,因此屬性注入是實際中最常採用的注入方式。
Spring首先會呼叫bean的預設建構函式例項化bean物件,然後再通過反射的方法來呼叫set方法來注入屬性值。
屬性注入要求bean提供一個預設的建構函式,並且得為需要注入的屬性提供set方法。
注意:所謂預設建構函式是指不帶參的建構函式
JAVA中定義:
如果類中沒有定義任何的建構函式,則JAVA虛擬機器自動為其生成一個預設的建構函式。反之,如果類中顯式的定義了建構函式,則JAVA虛擬機器便不會在為其生成建構函式。
來看一段java示例程式碼:
:Spring只會檢查bean中是否有setter方法,而是否有對應的屬性變數則不做具體要求,但按照約定俗成的規則我們最好為其設定相應的屬性變數。
補充: Spring <property>元素的命名規範:
Spring配置檔案中<property>元素所指定的屬性名和Bean實現類的Setter方法滿足Sun JavaBean的屬性命名規範,即xxx的屬性對應setXxx()的方法。一般情況下,java的屬性變數名都以小寫字母開頭,但考慮到一些特殊意義的英文縮略詞,java bean也允許一些大寫字母開頭的變數名。但必須滿足特點的條件:
變數的前兩個字母要麼全部大寫,要麼全部小寫
但以程式設計經驗來說:最好屬性名全部使用小寫字母,方便程式設計。
對於使用屬性注入方法來說,只能人為在配置檔案中提供保證,而無法在語法級別提供保證。那麼這時我們便要介紹如下這種注入方式:建構函式注入,通過這種方法便可以很好的滿足要求。
二.建構函式注入
建構函式注入是除屬性注入之外的另一種常用的注入方式,它保證一些必要的屬性在Bean例項化時就得到了設定,並在例項化後就可以使用。
使用建構函式注入的前提是:bean必須提供帶參的建構函式。
對於建構函式注入,配置檔案可以有以下幾種方式:
可以看到其實重點在於第三個入參的型別,所以我們在配置檔案中對其指定了索引和型別,這樣便使得Spring可以知道對那個建構函式進行引數注入了。 注意: 假如我們的配置檔案中存在歧義問題,Spring容器是可以正常啟動的。並不會報錯,他將隨機採用一個匹配的建構函式例項化bean。而隨機選擇的建構函式可能並不是使用者所需要的,所以我們在程式設計時必須小心避免出現這種歧義情況的出現。 4.通過自身型別反射匹配入參 如果bean建構函式入參的型別是可辨別的,由於java反射機制可以獲取建構函式入參的型別,即使建構函式的注入不提供型別和索引的資訊,Spring依舊可以完成建構函式資訊的注入。 下面的示例程式碼當中的Boss類的建構函式的入參型別就是可以辨別的:
在其配置檔案當中便可以直接配置入參值。
配置檔案編寫:
可以看到Car這個類有一個String型別的brand屬性,併為其提供了setter和getter方法。 我們再來看看與之相對應的屬性注入配置檔案:Public class Car { private String brand; public void setBrand(String brand) { this.brand = brand; } public String getBrand() { return this.brand; } }
<bean id=“car” class=“com.jike.***.Car” >
<property name=“brand”>
<value>賓士</value>
</property>
</bean>
其中每一個屬性值對應一個property標籤,name為屬性的名稱。
在bean實現類中擁有與其對應的實現方法setBrand()。
注意- 按型別匹配入參
- 按索引匹配入參
- 聯合使用型別和索引匹配入參
- 通過自身型別反射匹配入參
public class Car{
private String brand;
private double price;
public Car(String brand, double price) {
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
我們為bean編寫配置檔案<bean id="Car" class="cn.lovepi.chapter02.reflect.Car">
<constructor-arg type="String">
<value>紅旗CA72</value>
</constructor-arg>
<constructor-arg type="double">
<value>26666</value>
</constructor-arg>
</bean>
Spring的配置檔案採用和元素標籤順序無關的配置策略。這種策略可以在一定程度上保證配置資訊的確定性。
那麼當bean中的建構函式的多個引數型別一樣時,按照型別匹配入參的這種方式便會產生混淆,那麼我們便來看一看另外一種方式:按照索引匹配入參。
2.按照索引匹配入參方法:
先來編寫對應bean的程式碼:
public class Car{
private String brand;
private String corp;
private double price;
public Car(String brand, String corp, double price) {
this.brand = brand;
this.corp = corp;
this.price = price;
}
}
可以看到,在這個bean中的建構函式中存在兩個String型別的引數,那麼我們便使用索引來對其中的引數進行注入。<bean id="Car" class="cn.lovepi.chapter02.reflect.Car">
<constructor-arg index="0" value="紅旗CA72"/>
<constructor-arg index="1" value="中國一汽"/>
<constructor-arg index="2" value="2666"/>
</bean>
注意:
在屬性注入時,Spring按java bean 的規範確定配置屬性和對應的setter方法,並使用java 反射機制呼叫屬性的setter方法完成屬性注入。但java反射機制並不會記住建構函式的入參名。因此我們不能通過指定建構函式的入參名稱來進行建構函式的配置,所以我們只能通過入參的型別及其索引來間接的完成建構函式的屬性注入。
3.聯合使用型別和索引匹配入參
在某些複雜的配置檔案當中,需要使用type和index同時出馬才能完成建構函式的引數注入。那麼接下里我們看一下下面的這個例子:
public class Car{
private String brand;
private String corp;
private double price;
private int maxSpeed;
public Car(String brand, String corp, double price) {
this.brand = brand;
this.corp = corp;
this.price = price;
}
public Car(String brand, String corp, int maxSpeed) {
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
}
在這個類中,有兩個過載的建構函式,他們都有三個入參,在這種情況下使用type和index的方法都不能完成要求,這時候就需要聯合他們兩個屬性一起使用了。
接下來我們來看一看相應的配置檔案:
<bean id="Car" class="cn.lovepi.chapter02.reflect.Car">
<constructor-arg index="0" type="String">
<value>紅旗CA72</value>
</constructor-arg>
<constructor-arg index="1" type="String">
<value>中國一汽</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>200</value>
</constructor-arg>
</bean>
可以看到其實重點在於第三個入參的型別,所以我們在配置檔案中對其指定了索引和型別,這樣便使得Spring可以知道對那個建構函式進行引數注入了。 注意: 假如我們的配置檔案中存在歧義問題,Spring容器是可以正常啟動的。並不會報錯,他將隨機採用一個匹配的建構函式例項化bean。而隨機選擇的建構函式可能並不是使用者所需要的,所以我們在程式設計時必須小心避免出現這種歧義情況的出現。 4.通過自身型別反射匹配入參 如果bean建構函式入參的型別是可辨別的,由於java反射機制可以獲取建構函式入參的型別,即使建構函式的注入不提供型別和索引的資訊,Spring依舊可以完成建構函式資訊的注入。 下面的示例程式碼當中的Boss類的建構函式的入參型別就是可以辨別的:
public class Boss {
private String name;
private Car car;
private Office office;
public Boss(String name, Car car, Office office) {
this.name = name;
this.car = car;
this.office = office;
}
}
在其配置檔案當中便可以直接配置入參值。
<bean id="Boss" class="cn.lovepi.chapter03.scope.Boss">
<constructor-arg>
<value>wang</value>
</constructor-arg>
<constructor-arg>
<ref bean="car"/>
</constructor-arg>
<constructor-arg>
<ref bean="office"/>
</constructor-arg>
</bean>
以上的幾種方法都可以實現建構函式引數的注入,但是為了避免問題的發生,建議還是使用顯示的index和type來配置建構函式的入參資訊。
三.工廠方法注入方法
工廠方法是應用中被經常使用的設計模式,也是控制反轉和單例項設計思想的主要實現方法。
工廠類負責建立一個或多個工廠類例項,工廠類方法一般以介面或抽象類變數的形式返回目標類例項。
工廠類對外遮蔽了目標類的例項化步驟,呼叫者甚至根本不用指定具體的目標類是什麼。
由於Spring容器以框架的方法提供工廠方法的功能,並以透明的方式開放給開發者。因此很少需要手工編寫工程方法。但在一些遺留系統或第三方類庫中還是會碰到工廠方法,此時便可以使用Spring工廠注入的方法來進行Spring的注入。
Spring工廠注入的方法可以分為靜態和非靜態兩種:
1.非靜態工廠方式
有些工廠方法是非靜態的,則必須例項化工廠類後才能呼叫工廠方法。
下面來看一個程式碼示例:
public class CarFactory{
public Car createHongQiCar(){
Car car=new Car();
car.setBrand("紅旗CA72");
return car;
}
}
配置檔案編寫:
<bean id="carFactory" class="cn.lovepi.chapter02.reflect.CarFactory"/>
<bean id="car" factory-bean="carFactory"
factory-method="createHongQiCar">
</bean>
由於carFactory的工廠方法不是靜態的,所以首先需要定義一個工廠類的bean,然後通過factory-bean這個屬性來引用工廠bean例項。在通過屬性factory-method來指定對應的工廠方法。
2.靜態工廠方法
很多工廠類方法都是靜態的,這意味在無須建立工廠類例項的情況下就可以呼叫工廠類方法。
因此靜態工廠方法比非靜態工廠方法的呼叫更加方便。
那麼我們來看靜態工廠方法的bean程式碼:
public class CarFactory{
public static Car createCar(){
Car car=new Car();
car.setBrand("紅旗CA72");
return car;
}
}
則其配置檔案為:<bean id="car" class="cn.lovepi.chapter02.reflect.Car” factory-method="createCar"></bean>
總結:
Spring提供了三種可供選擇的注入方式,在實際應用中,我們究竟應該選擇哪種注入方式,並沒有統一的標準,如下是一些可以參考的理由:
建構函式注入理由:
- 建構函式保證重要屬性預先設定
- 無需提供每個屬性Setter方法,減少類的方法個數
- 可更好的封裝類變數,避免外部錯誤呼叫
- 屬性過多時,建構函式變的臃腫可怕
- 建構函式注入靈活性不強,有時需要為屬性注入null值
- 多個建構函式時,配置上產生歧義,複雜度升高
- 建構函式不利於類的繼承和擴充套件
- 建構函式注入會引起迴圈依賴的問題