1. 程式人生 > >Spring 依賴注入三種方式的實現,及迴圈依賴問題的解決(原始碼+XML配置)

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 提供給了我們多種注入方式,需要使用哪種方式各位同鞋可以根據自身的場景下考量,

這篇文章就分享到這啦,溜啦溜啦~~