Spring系列第八講 依賴注入之手動注入

qwer1030274531發表於2020-10-30

依賴回顧

通常情況下,系統中類和類之間是有依賴關係的,如果一個類對外提供的功能需要透過呼叫其他類的方法來實現的時候,說明這兩個類之間存在依賴關係,如:

public class UserService{
    public void insert(UserModel model){
        //插入使用者資訊
    }}public class UserController{
    private UserService userService;
    public void insert(UserModel model){
        this.userService.insert(model);
    }}123456789101112131415

UserController中的insert方法中需要呼叫userService的insert方法,說明UserController依賴於UserService,如果userService不存在,此時UserControler無法對外提供insert操作。

那麼我們建立UserController物件的時候如何將給userService設定值呢?通常有2種方法。

依賴物件的初始化方式

透過構造器設定依賴物件
UserController中新增一個有參構造方法,如下:

public class UserController{
    private UserService userService;
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void insert(UserModel model){
        this.userService.insert(model);
    }}//UserController使用UserSerivce userService = new UserService();UserController userController = new UserController(userService);//然後就可以使用userController物件了123456789101112131415161718

透過set方法設定依賴物件
可以在UserController中給userService新增一個set方法,如:

public class UserController{
    private UserService userService;
    public setUserService(UserService userService){
        this.userService = userService;
    }
    public void insert(UserModel model){
        this.userService.insert(model);
    }}//UserController使用UserSerivce userService = new UserService();UserController userController = new UserController();userController.setService(userService);//然後就可以使用userController物件了12345678910111213141516171819

上面這些操作,將被依賴的物件設定到依賴的物件中,spring容器內部都提供了支援,這個在spirng中叫做依賴注入。

spring依賴注入

spring中依賴注入主要分為手動注入和自動注入,本文我們主要說一下手動注入,手動注入需要我們明確配置需要注入的物件。

剛才上面我們回顧了,將被依賴方注入到依賴方,通常有2種方式:建構函式的方式和set屬性的方式,spring中也是透過這兩種方式實現注入的,下面詳解2種方式。

透過構造器注入

構造器的引數就是被依賴的物件,構造器注入又分為3種注入方式:

  • 根據構造器引數索引注入

  • 根據構造器引數型別注入

  • 根據構造器引數名稱注入

根據構造器引數索引注入

用法

<bean id="diByConstructorParamIndex" class="com.javacode2018.lesson001.demo5.UserModel">
    <constructor-arg index="0" value="路人甲Java"/>
    <constructor-arg index="1" value="上海市"/></bean>1234

constructor-arg使用者指定構造器的引數

index:構造器引數的位置,從0開始

value:構造器引數的值,value只能用來給簡單的型別設定值,value對應的屬性型別只能為byte,int,long,float,double,boolean,Byte,Long,Float,Double,列舉,spring容器內部注入的時候會將value的值轉換為對應的型別。

案例

UserModel.java

package com.javacode2018.lesson001.demo5;public class UserModel {
    private String name;
    private int age;
    //描述資訊
    private String desc;
    public UserModel() {
    }
    public UserModel(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    public UserModel(String name, int age, String desc) {
        this.name = name;
        this.age = age;
        this.desc = desc;
    }
    @Override
    public String toString() {
        return "UserModel{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", desc='" + desc + '\'' +
                '}';
    }}12345678910111213141516171819202122232425262728293031

注意上面的3個建構函式。

diByConstructorParamIndex.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.3.xsd">
    <!-- 透過構造器引數的索引注入 -->
    <bean id="diByConstructorParamIndex" class="com.javacode2018.lesson001.demo5.UserModel">
        <constructor-arg index="0" value="路人甲Java"/>
        <constructor-arg index="1" value="我是透過構造器引數位置注入的"/>
    </bean></beans>12345678910111213

上面建立UserModel例項程式碼相當於下面程式碼:

UserModel userModel = new UserModel("路人甲Java","我是透過構造器引數型別注入的");1

工具類IoUtils.java

package com.javacode2018.lesson001.demo5;import org.springframework.context.support.ClassPathXmlApplicationContext;public class IocUtil {
    public static ClassPathXmlApplicationContext context(String beanXml) {
        return new ClassPathXmlApplicationContext(beanXml);
    }}123456789

測試用例DiTest.java

package com.javacode2018.lesson001.demo5;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;public class DiTest {
    /**
     * 透過構造器的引數位置注入
     */
    @Test
    public void diByConstructorParamIndex() {
        String beanXml = "classpath:/com/javacode2018/lesson001/demo5/diByConstructorParamIndex.xml";
        ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
        System.out.println(context.getBean("diByConstructorParamIndex"));
    }}123456789101112131415161718

效果
執行diByConstructorParamIndex輸出

UserModel{name='路人甲Java', age=0, desc='我是透過構造器引數位置注入的'}1

優缺點

引數位置的注入對引數順序有很強的依賴性,若建構函式引數位置被人調整過,會導致注入出錯。

不過通常情況下,不建議去在程式碼中修改建構函式,如果需要新增引數的,可以新增一個建構函式來實現,這算是一種擴充套件,不會影響目前已有的功能。

根據構造器引數型別注入

用法

<bean id="diByConstructorParamType" class="com.javacode2018.lesson001.demo5.UserModel">
    <constructor-arg type="引數型別" value="引數值"/>
    <constructor-arg type="引數型別" value="引數值"/></bean>1234

constructor-arg使用者指定構造器的引數

type:建構函式引數的完整型別,如:java.lang.String,int,double

value:構造器引數的值,value只能用來給簡單的型別設定值

案例

diByConstructorParamType.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.3.xsd">
    <!-- 透過構造器引數的型別注入 -->
    <bean id="diByConstructorParamType" class="com.javacode2018.lesson001.demo5.UserModel">
        <constructor-arg type="int" value="30"/>
        <constructor-arg type="java.lang.String" value="路人甲Java"/>
        <constructor-arg type="java.lang.String" value="我是透過構造器引數型別注入的"/>
    </bean></beans>1234567891011121314

上面建立UserModel例項程式碼相當於下面程式碼:

UserModel userModel = new UserModel("路人甲Java",30,"我是透過構造器引數型別注入的");1

新增測試用例

DiTest類中新增一個測試用例

/**
 * 透過構造器的引數型別注入
 */@Testpublic void diByConstructorParamType() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo5/diByConstructorParamType.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diByConstructorParamType"));}123456789

效果

執行diByConstructorParamType輸出

UserModel{name='路人甲Java', age=30, desc='我是透過構造器引數型別注入的'}1

優缺點

實際上按照引數位置或者按照引數的型別注入,都有一個問題,很難透過bean的配置檔案,知道這個引數是對應UserModel中的那個屬性的,程式碼的可讀性不好,比如我想知道這每個引數對應UserModel中的那個屬性,必須要去看UserModel的原始碼,下面要介紹按照引數名稱注入的方式比上面這2種更優秀一些。

根據構造器引數名稱注入

用法

<bean id="diByConstructorParamName" class="com.javacode2018.lesson001.demo5.UserModel">
    <constructor-arg name="引數型別" value="引數值"/>
    <constructor-arg name="引數型別" value="引數值"/></bean>1234

constructor-arg使用者指定構造器的引數

name:構造引數名稱

value:構造器引數的值,value只能用來給簡單的型別設定值

關於方法引數名稱的問題

java透過反射的方式可以獲取到方法的引數名稱,不過原始碼中的引數透過編譯之後會變成class物件,通常情況下原始碼變成class檔案之後,引數的真實名稱會丟失,引數的名稱會變成arg0,arg1,arg2這樣的,和實際引數名稱不一樣了,如果需要將原始碼中的引數名稱保留在編譯之後的class檔案中,編譯的時候需要用下面的命令:

javac -parameters java原始碼1

但是我們難以保證編譯程式碼的時候,操作人員一定會帶上-parameters引數,所以方法的引數可能在class檔案中會丟失,導致反射獲取到的引數名稱和實際引數名稱不符,這個我們需要先了解一下。

引數名稱可能不穩定的問題,spring提供瞭解決方案,透過ConstructorProperties註解來定義引數的名稱,將這個註解加在構造方法上面,如下:

@ConstructorProperties({"第一個引數名稱", "第二個引數的名稱",..."第n個引數的名稱"})public 類名(String p1, String p2...,引數n) {}123

案例

CarModel.java

package com.javacode2018.lesson001.demo5;import java.beans.ConstructorProperties;public class CarModel {
    private String name;
    //描述資訊
    private String desc;
    public CarModel() {
    }
    @ConstructorProperties({"name", "desc"})
    public CarModel(String p1, String p2) {
        this.name = p1;
        this.desc = p2;
    }
    @Override
    public String toString() {
        return "CarModel{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }}1234567891011121314151617181920212223242526

diByConstructorParamName.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.3.xsd">
    <!-- 透過構造器引數的名稱注入 -->
    <bean id="diByConstructorParamName" class="com.javacode2018.lesson001.demo5.CarModel">
        <constructor-arg name="desc" value="我是透過構造器引數型別注入的"/>
        <constructor-arg name="name" value="保時捷Macans"/>
    </bean></beans>12345678910111213

上面建立CarModel例項程式碼相當於下面程式碼:

CarModel carModel = new CarModel("保時捷Macans","我是透過構造器引數型別注入的");1

新增測試用例

DiTest類中新增一個測試用例

/**
 * 透過構造器的引數名稱注入
 */@Testpublic void diByConstructorParamName() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo5/diByConstructorParamName.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diByConstructorParamName"));}123456789

效果

執行diByConstructorParamName輸出

CarModel{name='保時捷Macans', desc='我是透過構造器引數型別注入的'}1

setter注入

通常情況下,我們的類都是標準的javabean,javabean類的特點:

  • 屬性都是private訪問級別的

  • 屬性通常情況下透過一組setter(修改器)和getter(訪問器)方法來訪問

  • setter方法,以set開頭,後跟首字母大寫的屬性名,如:setUserName,簡單屬性一般只有一個方法引數,方法返回值通常為void;

  • getter方法,一般屬性以get開頭,對於boolean型別一般以is開頭,後跟首字母大寫的屬性名,如:getUserName,isOk;

spring對符合javabean特點類,提供了setter方式的注入,會呼叫對應屬性的setter方法將被依賴的物件注入進去。

用法

<bean id="" class="">
    <property name="屬性名稱" value="屬性值" />
    ...
    <property name="屬性名稱" value="屬性值" /></bean>12345

property用於對屬性的值進行配置,可以有多個

name:屬性的名稱

value:屬性的值

案例

MenuModel.java

package com.javacode2018.lesson001.demo5;/**
 * 選單類
 */public class MenuModel {
    //選單名稱
    private String label;
    //同級別排序
    private Integer theSort;
    public String getLabel() {
        return label;
    }
    public void setLabel(String label) {
        this.label = label;
    }
    public Integer getTheSort() {
        return theSort;
    }
    public void setTheSort(Integer theSort) {
        this.theSort = theSort;
    }
    @Override
    public String toString() {
        return "MenuModel{" +
                "label='" + label + '\'' +
                ", theSort=" + theSort +
                '}';
    }}1234567891011121314151617181920212223242526272829303132333435

diBySetter.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.3.xsd">
    <bean id="diBySetter" class="com.javacode2018.lesson001.demo5.MenuModel">
        <property name="label" value="spring系列"/>
    </bean></beans>1234567891011

新增測試用例

DiTest類中新增一個測試方法

/**
 * 透過setter方法注入
 */@Testpublic void diBySetter() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo5/diBySetter.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diBySetter"));}123456789

效果

執行diBySetter輸出

MenuModel{label='spring系列', theSort=null}1

優缺點

setter注入相對於建構函式注入要靈活一些,建構函式需要指定對應建構函式中所有引數的值,而setter注入的方式沒有這種限制,不需要對所有屬性都進行注入,可以按需進行注入。

上面介紹的都是注入普通型別的物件,都是透過value屬性來設定需要注入的物件的值的,value屬性的值是String型別的,spring容器內部自動會將value的值轉換為物件的實際型別。

若我們依賴的物件是容器中的其他bean物件的時候,需要用下面的方式進行注入。

注入容器中的bean

注入容器中的bean有兩種寫法:

  • ref屬性方式

  • 內建bean的方式

ref屬性方式
將上面介紹的constructor-arg或者property元素的value屬性名稱替換為ref,ref屬性的值為容器中其他bean的名稱,如:

構造器方式,將value替換為ref:

<constructor-arg ref="需要注入的bean的名稱"/>1

setter方式,將value替換為ref:

<property name="屬性名稱" ref="需要注入的bean的名稱" />1

內建bean的方式

構造器的方式:

<constructor-arg>
    <bean class=""/></constructor-arg>123

setter方式:

<property name="屬性名稱">
    <bean class=""/></property>123

案例

PersonModel.java

package com.javacode2018.lesson001.demo5;public class PersonModel {
    private UserModel userModel;
    private CarModel carModel;
    public PersonModel() {
    }
    public PersonModel(UserModel userModel, CarModel carModel) {
        this.userModel = userModel;
        this.carModel = carModel;
    }
    public UserModel getUserModel() {
        return userModel;
    }
    public void setUserModel(UserModel userModel) {
        this.userModel = userModel;
    }
    public CarModel getCarModel() {
        return carModel;
    }
    public void setCarModel(CarModel carModel) {
        this.carModel = carModel;
    }
    @Override
    public String toString() {
        return "PersonModel{" +
                "userModel=" + userModel +
                ", carModel=" + carModel +
                '}';
    }}1234567891011121314151617181920212223242526272829303132333435363738

PersonModel中有依賴於2個物件UserModel、CarModel,下面我們透過spring將UserModel和CarModel建立好,然後注入到PersonModel中,下面建立bean配置檔案

diBean.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.3.xsd">
    <bean id="user" class="com.javacode2018.lesson001.demo5.UserModel"></bean>
    <!-- 透過構造器方式注入容器中的bean -->
    <bean id="diBeanByConstructor" class="com.javacode2018.lesson001.demo5.PersonModel">
        <!-- 透過ref引用容器中定義的其他bean,user對應上面定義的id="user"的bean -->
        <constructor-arg index="0" ref="user"/>
        <constructor-arg index="1">
            <bean class="com.javacode2018.lesson001.demo5.CarModel">
                <constructor-arg index="0" value="賓利"/>
                <constructor-arg index="1" value=""/>
            </bean>
        </constructor-arg>
    </bean>
    <!-- 透過setter方式注入容器中的bean -->
    <bean id="diBeanBySetter" class="com.javacode2018.lesson001.demo5.PersonModel">
        <!-- 透過ref引用容器中定義的其他bean,user對應上面定義的id="user"的bean -->
        <property name="userModel" ref="user"/>
        <property name="carModel">
            <bean class="com.javacode2018.lesson001.demo5.CarModel">
                <constructor-arg index="0" value="保時捷"/>
                <constructor-arg index="1" value=""/>
            </bean>
        </property>
    </bean></beans>123456789101112131415161718192021222324252627282930313233

新增測試用例

DiTest中新增一個測試方法

@Testpublic void diBean(){
    String beanXml = "classpath:/com/javacode2018/lesson001/demo5/diBean.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diBeanByConstructor"));
    System.out.println(context.getBean("diBeanBySetter"));}1234567

效果

執行diBean用例,輸出:

PersonModel{userModel=UserModel{name='null', age=0, desc='null'}, carModel=CarModel{name='賓利', desc=''}}PersonModel{userModel=UserModel{name='null', age=0, desc='null'}, carModel=CarModel{name='保時捷', desc=''}}12

其他型別注入 /cure/

注入java.util.List(list元素)

<list>
    <value>Spring</value>
    或    <ref bean="bean名稱"/>
    或    <list></list>
    或    <bean></bean>
    或    <array></array>
    或    <map></map></list>12345678910111213

注入java.util.Set(set元素)

<set>
    <value>Spring</value>
    或    <ref bean="bean名稱"/>
    或    <list></list>
    或    <bean></bean>
    或    <array></array>
    或    <map></map></set>12345678910111213

注入java.util.Map(map元素)

<map>
    <entry key="路人甲Java" value="30" key-ref="key引用的bean名稱" value-ref="value引用的bean名稱"/>
    或    <entry>
        <key>
            value對應的值,可以為任意型別        </key>
        <value>
            value對應的值,可以為任意型別        </value>
    </entry></map>123456789101112

注入陣列(array元素)

<array>
    陣列中的元素</array>123

注入java.util.Properties(props元素)

Properties類相當於鍵值都是String型別的Map物件,使用props進行注入,如下:

<props>
    <prop key="key1">java高併發系列</prop>
    <prop key="key2">mybatis系列</prop>
    <prop key="key3">mysql系列</prop></props>12345

案例

對於上面這些型別來個綜合案例。

DiOtherTypeModel.java

package com.javacode2018.lesson001.demo5;import java.util.*;public class DiOtherTypeModel {
    private List<String> list1;
    private Set<UserModel> set1;
    private Map<String, Integer> map1;
    private int[] array1;
    private Properties properties1;
    public List<String> getList1() {
        return list1;
    }
    public void setList1(List<String> list1) {
        this.list1 = list1;
    }
    public Set<UserModel> getSet1() {
        return set1;
    }
    public void setSet1(Set<UserModel> set1) {
        this.set1 = set1;
    }
    public Map<String, Integer> getMap1() {
        return map1;
    }
    public void setMap1(Map<String, Integer> map1) {
        this.map1 = map1;
    }
    public int[] getArray1() {
        return array1;
    }
    public void setArray1(int[] array1) {
        this.array1 = array1;
    }
    public Properties getProperties1() {
        return properties1;
    }
    public void setProperties1(Properties properties1) {
        this.properties1 = properties1;
    }
    @Override
    public String toString() {
        return "DiOtherTypeModel{" +
                "list1=" + list1 +
                ", set1=" + set1 +
                ", map1=" + map1 +
                ", array1=" + Arrays.toString(array1) +
                ", properties1=" + properties1 +
                '}';
    }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162

上面這個類中包含了剛才我們介紹的各種型別,下面來進行注入。

diOtherType.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.3.xsd">
    <bean id="user1" class="com.javacode2018.lesson001.demo5.UserModel"/>
    <bean id="user2" class="com.javacode2018.lesson001.demo5.UserModel"/>
    <bean id="diOtherType" class="com.javacode2018.lesson001.demo5.DiOtherTypeModel">
        <!-- 注入java.util.List物件 -->
        <property name="list1">
            <list>
                <value>Spring</value>
                <value>SpringBoot</value>
            </list>
        </property>
        <!-- 注入java.util.Set物件 -->
        <property name="set1">
            <set>
                <ref bean="user1"/>
                <ref bean="user2"/>
                <ref bean="user1"/>
            </set>
        </property>
        <!-- 注入java.util.Map物件 -->
        <property name="map1">
            <map>
                <entry key="路人甲Java" value="30"/>
                <entry key="路人" value="28"/>
            </map>
        </property>
        <!-- 注入陣列物件 -->
        <property name="array1">
            <array>
                <value>10</value>
                <value>9</value>
                <value>8</value>
            </array>
        </property>
        <!-- 注入java.util.Properties物件 -->
        <property name="properties1">
            <props>
                <prop key="key1">java高併發系列</prop>
                <prop key="key2">mybatis系列</prop>
                <prop key="key3">mysql系列</prop>
            </props>
        </property>
    </bean></beans>12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455

新增測試用例

DiTest類中新增一個方法

/**
 * 其他各種型別注入
 */@Testpublic void diOtherType() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo5/diOtherType.xml";
    ClassPathXmlApplicationContext context = IocUtils.context(beanXml);
    System.out.println(context.getBean("diOtherType"));}123456789

效果

執行測試用例輸出:

DiOtherTypeModel{list1=[Spring, SpringBoot], set1=[UserModel{name='null', age=0, desc='null'}, UserModel{name='null', age=0, desc='null'}], map1={路人甲Java=30, 路人=28}, array1=[10, 9, 8], properties1={key3=mysql系列, key2=mybatis系列, key1=java高併發系列}}1

總結 dxb.myzx.cn/lanzhou/

  1. 本文主要講解了xml中bean的依賴注入,都是採用硬編碼的方式進行注入的,這種算是手動的方式

  2. 注入普通型別透過value屬性或者value元素設定注入的值;注入物件如果是容器的其他bean的時候,需要使用ref屬性或者ref元素或者內建bean元素的方式

  3. 還介紹了其他幾種型別List、Set、Map、陣列、Properties型別的注入,多看幾遍加深理解

  4. 後面我們將介紹spring為我們提供的更牛逼的自動注入


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2731167/,如需轉載,請註明出處,否則將追究法律責任。

相關文章