Spring 依賴注入三種方式的實現,及迴圈依賴問題的解決(原始碼+XML配置)
搬磚啦,搬磚啦,這幾天在看Spring相關的書,下面給大家分享一下這幾天的心得與收穫,Go Go Go!
Spring支援兩種依賴注入方式,分別是屬性注入,建構函式注入。除此之外,Spring還支援工廠注入方式。
接下來,我們一起來了解一下Spring的幾種注入方式。
一.屬性注入
首先來了解一下定義:屬性注入是指通過 setXxx()方法注入Bean的屬性或依賴物件。
為什麼要使用: 因為屬性注入方式具有可選擇性和高靈活性的特點,所以屬性注入方式是實際應用中最常採用的注入方式。
來來來,直接上程式碼!
造個Car實體類
Car類中定義了3個屬性,並分別提供了對應的Setter方法
package com.vtstar.entity; /** * @ClassName Car * @Description TODO * @Author XiaoWu * @Date 2018/9/6 9:53 * @Version 1.0 **/ public class Car { private int maxSpeed; public String brand; private double price; private Boss boss; public Car() { } public Car(double price, Boss boss) { System.out.println("I'm the Car's construct two " + price + " boss " + boss.getName()); this.price = price; this.boss = boss; } public Car(int maxSpeed, String brand, double price) { System.out.println("車的時速是" + maxSpeed + " 品牌為:" + brand + " 價格為:" + price); this.maxSpeed = maxSpeed; this.brand = brand; this.price = price; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { System.out.println("The maximum speed is " + maxSpeed); this.maxSpeed = maxSpeed; } public String getBrand() { return brand; } public void setBrand(String brand) { System.out.println("It is a " + brand + " Car"); this.brand = brand; } public double getPrice() { return price; } public void setPrice(double price) { System.out.println("The price of this car " + price); this.price = price; } }
在Spring配置檔案中對Car進行屬性注入的配置橋段
<!--&&&&&&&&&&&&&&&&&&&&&&&&& setter屬性方式注入 &&&&&&&&&&&&&&&&&&&&&& --> <bean id="car" class="com.vtstar.entity.Car"> <property name="brand" value="保時捷 k3"/> <property name="maxSpeed" value="100"/> <property name="price" value="30.1"/> </bean>
上面的程式碼配置的一個Bean,class屬性對應前面建好的實體Car,<property/>標籤對應Bean中的每一個屬性,name為屬性的名稱,value為引數值,在Bean中擁有與其對應的Setter方法,maxSpeed對應setMaxSpeed(),price對應setPrice();
執行一下 Tomcat,Spring容器會在Tomcat啟動的時候建立;
控制檯輸出的結果:
二.建構函式注入
建構函式注入是除屬性注入外的另外一種注入方式,它保證了一些必要的屬性在Bean例項化時就得到設定,確保在Bean例項化後就可以使用。
值得一提的是建構函式注入又分為多種方式,我們慢慢來看。
1. 按型別匹配入參
如果任何可用的Car物件都需要使用到brand,maxSpeed,price的值,那使用Setter注入方式,則只能人為的在配置時提供保證而無法再語法上提供保證,那這個時候使用建構函式注入就能滿足這一個要求,使用建構函式注入的前提是要保證Bean中有提供帶參的建構函式。
<!--1.根據引數型別注入-->
<bean id="car1" class="com.vtstar.entity.Car">
<constructor-arg type="int" value="300"/>
<constructor-arg type="java.lang.String" value="紅旗"/>
<constructor-arg type="double" value="20000000.9"/>
</bean>
在<constructor/>元素中有一個type元素,它為Spring提供了判斷配置項和建構函式入參對應關係的“資訊”。
控制檯輸出的結果:
2. 按索引匹配入參
Java語言通過入參的型別及順序區分不同的過載方法。如果建構函式中有兩個型別相同的入參,那麼使用第一種方式是行不通的,因為type無法確認對應的關係。這時我們就需要使用索引匹配入參的方式來進行確認。
為了更好的演示按索引匹配入參,將Car建構函式進行了修改
public Car(String brand, String corp, double price) {
System.out.println("brand :" + brand + " corp :" + corp + " price :"+price);
this.brand = brand;
this.corp = corp;
this.price = price;
}
<!--2.通過入參位置下標-->
<bean id="car2" class="com.vtstar.entity.Car">
<constructor-arg index="0" value="400"/>
<constructor-arg index="1" value="大眾輝騰"/>
<constructor-arg index="2" value="20000000"/>
</bean>
因為brand和corp都是String型別,所以Spring無法確定type為String的<constructor-arg/>到底對應的是brand還是corp。但是這種按索引匹配入參的方式能夠消除這種不確定性。
控制檯輸出的結果:
3. 聯合使用型別和索引匹配入參
有時需要Type和index聯合使用才能確定配置項和建構函式入參的對應關係,舉個栗子
public Car(String brand, String corp, double price) {
System.out.println("brand :" + brand + " corp :" + corp + " price :"+price);
this.brand = brand;
this.corp = corp;
this.price = price;
}
public Car(String brand, String corp, int maxSpeed) {
System.out.println("brand :" + brand + " corp :" + corp + " maxSpeed :"+maxSpeed);
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
在這裡,Car擁有兩個過載的建構函式,它們都有兩個相同型別的入參,按照index的方式針對這樣的情況又難以滿足了這時就需要聯合使用<constructor-arg/>中的type和index了。
<!--3.通過引數型別和入參位置聯合注入-->
<bean id="car3" class="com.vtstar.entity.Car">
<constructor-arg index="0" type="java.lang.String" value="30000000.0"/>
<constructor-arg index="1" type="java.lang.String" value="卡迪拉克"/>
<constructor-arg index="2" type="int" value="400"/>
</bean>
對於上圖的程式碼清單如果只根據index來進行匹配入參,那麼Spring無法確認第三個引數是price還是maxSpeed了,所以解決這種有歧義的衝突,請將type和index結合使用,對於因引數數目相同而型別不同引起的潛在配置歧義問題,Spring容器可以正確的啟動且不會給出報錯資訊,他將隨機採用一個匹配的建構函式例項化Bean,而被選擇的建構函式可能並不是使用者所期望的那個。因此,必須要特別謹慎,以避免潛在的錯誤。
控制檯輸出的結果:
4.通過自身型別反射入參
如果Bean的建構函式入參型別是可辨別的,什麼是可辨別的入參型別呢?(非基礎資料型別且入參型別各異)
我們再建一個Boss實體類,在Boss類中引用Car類
package com.vtstar.entity;
import java.util.Date;
/**
* @ClassName Boss
* @Description TODO
* @Author XiaoWu
* @Date 2018/9/6 11:22
* @Version 1.0
**/
public class Boss {
private String name;
private Car car;
private Integer age;
public Boss() {
}
public Boss(String name, Car car,Integer age) {
System.out.println("The name of the boss " + name + " ,He has a " + car.getBrand()+" age is "+age);
this.name = name;
this.car = car;
this.age = age;
}
public String getName() {
return name;
}
public Car getCar() {
return car;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Spring配置Boss相關的Bean,由於name, car ,age入參都是可辨別的,所以無須在<constructor-arg/>中指定type和index。
<!--4.通過自身型別反射入參-->
<bean id="boss" class="com.vtstar.entity.Boss">
<constructor-arg value="Tom"/>
<constructor-arg ref="car1"/>
<constructor-arg value="20"/>
</bean>
但是為了避免潛在配置歧義引起的張冠李戴的情況,如果Bean存在多個建構函式,那麼顯式指定index和type屬性是一種良好的配置習慣。
看控制檯輸出的結果:
5.迴圈依賴問題
Spring容器對建構函式配置Bean進行例項化有一個前提,即Bean建構函式入參引用的物件必須已經準備就緒。由於這個機制,如果兩個Bean都相互引用,都採用建構函式注入方式,就會發生類似於執行緒死鎖的迴圈依賴問題。
<!--5.迴圈依賴注入-->
<bean id="boss1" class="com.vtstar.entity.Boss">
<constructor-arg index="0" value="Tom"/>
<constructor-arg index="1" ref="car4"/>
<constructor-arg index="2" value="20"/>
</bean>
<bean id="car4" class="com.vtstar.entity.Car">
<constructor-arg index="0" value="232.9"/>
<constructor-arg index="1" ref="boss1"/>
</bean>
控制檯輸出的結果:(這就是採用迴圈注入方式產生最大的問題)
如何解決這種問題?將相互依賴的兩個Bean中的其中一個Bean採用Setter注入的方式即可。
<!--5.迴圈依賴注入-->
<bean id="boss1" class="com.vtstar.entity.Boss">
<constructor-arg index="0" value="Tom"/>
<constructor-arg index="1" ref="car"/>
</bean>
<bean id="car" class="com.vtstar.entity.Car">
<property name="brand" value="保時捷 k3"/>
<property name="maxSpeed" value="100"/>
<property name="price" value="30.1"/>
</bean>
控制檯輸出結果:
建構函式注入方式:
優點:
1.建構函式可以保證一些重要的屬性在bean例項化的時候就設定好,避免因為一些重要的屬性沒有提供而導致一個無用的Bean 例項情況
2.不需要為每個屬性提供Setter方法,減少了類的方法個數
3.可以更好的封裝類變數,不需要為每個屬性提供Setter方法,避免外部錯誤的呼叫
缺點:
1.如果一個類屬性太多,那麼建構函式的引數將變成一個龐然大物,可讀性較差
2.靈活性不強,在有些屬性是可選的情況下,如果通過建構函式注入,也需要為可選的引數提供一個null值
3.如果有多個建構函式,則需要考慮配置檔案和具體建構函式匹配歧義的問題,配置上相對複雜
4.建構函式不利於類的繼承和拓展,因為子類需要引用父類複雜的建構函式
5.建構函式注入有時會造成迴圈依賴的問題
三. 工廠注入
既然需要一個工廠,那麼我們需要建立一個CarFactory類
package com.vtstar.ioc;
import com.vtstar.entity.Car;
/**
* @ClassName CarFactory
* @Description TODO
* @Author XiaoWu
* @Date 2018/9/6 13:55
* @Version 1.0
**/
public class CarFactory {
/*
* @methodName createHongQiCar
* @Description 建立紅旗轎車製造工廠
* @Date 2018/9/6 13:58
* @Param []
* @return com.vtstar.entity.Car
**/
public Car createHongQiCar(){
Car car = new Car();
car.setBrand("紅旗H1");
System.out.println("這裡是非靜態工廠的建立..." + car.getBrand());
return car;
}
/*
* @methodName createDaZhongCar
* @Description 建立大眾汽車製造工廠
* @Date 2018/9/6 14:02
* @Param []
* @return com.vtstar.entity.Car
**/
public static Car createDaZhongCar(){
Car car = new Car();
car.setBrand("大眾GoGoGo");
System.out.println("這裡是靜態工廠的建立..." + car.getBrand());
return car;
}
}
工廠注入方式分為 靜態工廠和非靜態工廠,相關Spring配置如下:
<!--非靜態注入工廠方法-->
<bean id="carFactory" class="com.vtstar.ioc.CarFactory"/>
<bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar"/>
<!--靜態注入工廠方法-->
<bean id="car6" class="com.vtstar.ioc.CarFactory" factory-method="createDaZhongCar"/>
看控制檯輸出的結果:
Spring 提供給了我們多種注入方式,需要使用哪種方式各位同鞋可以根據自身的場景下考量,
這篇文章就分享到這啦,溜啦溜啦~~