基於XML的DI

山丘i發表於2020-08-09


DI 是ioc(控制反轉)的技術實現
ioc技術實現使用的DI(Dependency Injection) :依賴注入, 只需要在程式中提供要使用的物件名稱就可以, 至於物件如何在容器中建立,賦值,查詢都由容器內部實現。

spring是使用的di實現了ioc的功能, spring底層建立物件,使用的是反射機制。

spring是一個容器,管理物件,給屬性賦值, 底層是反射建立物件

一、注入分類

bean 例項在呼叫無參構造器建立物件後,就要對 bean 物件的屬性進行初始化。

初始化是由容器自動完成的,稱為注入
根據注入方式的不同,常用的有兩類:set 注入、構造注入

二、set注入

set 注入也叫設值注入,是指通過 setter 方法傳入被呼叫者的例項,這種注入方式簡單、直觀,因而在 Spring 的依賴注入中大量使用

1. 簡單型別

專案的具體建立看上一篇就可以了,這裡直接寫重點

首先宣告一個Studnet的類

package com.md.b1;

/**
 * @author MD
 * @create 2020-08-07 19:55
 */
public class Student {
    private String name;
    private int age;

    public Student() {
        System.out.println("我是Student類的無參構造方法");
    }

    public void setName(String name) {
        System.out.println("setName:"+name);
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

然後寫對應的配置檔案

<?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">

    <!--
        宣告Student的物件

        簡單型別:spring中java的基本資料型別和String都是簡單資料型別

        di:給屬性賦值也就是注入
        1. set注入 :spring來呼叫類的set方法,在set方法中完成屬性的賦值
            1. 簡單型別的注入
            <bean id="xx" class="yyy">
               <property name="屬性名字" value="此屬性的值"/>
               一個property只能給一個屬性賦值
               <property....>
            </bean>

            必須要有屬性對應的set方法,沒有的話就報錯
            但是set方法裡面的內容是你能控制,除了賦值,你還可以在set裡多寫幾條java語句

    -->

    <bean id="student" class="com.md.b1.Student">
        <property name="name" value="張三" /> <!-- setName("張三")-->
        <property name="age" value="20"/>
    </bean>

</beans>

結構圖

測試類

注意:此時由於這個檔案不是直接在resources下面,而是在下面的b1包的下面,所以指定的路徑得加上

 @Test
    public void test01(){
        String config = "b1/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 從容器中獲取Student的物件
        Student student = (Student) ac.getBean("student");

        System.out.println(student);

//        我是Student類的無參構造方法
//        setName:張三
//        Student{name='張三', age=20}

    }

注意一:沒有屬性但有set方法

還可以在Student的類中加入這個方法,

  public void setEmail(String eamil) {
        System.out.println("setEmail:"+eamil);
    }

對應的配置檔案

 <bean id="student" class="com.md.b1.Student">

        <property name="name" value="張三" /> <!-- setName("張三")-->
        <property name="age" value="20"/>
        <property name="email" value="zs@qq.com"/>

    </bean>

此時在Student類中沒有email屬性,但是有setEmail方法,能順利執行不?
能,只要有對應的set方法都是正確的,無論屬性名是否存在

測試:

@Test
    public void test01(){
        // 注意:此時由於這個檔案不是直接在resources下面,而是在下面的b1包的下面,所以指定的路徑得加上
        String config = "b1/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        // 從容器中獲取Student的物件
        Student student = (Student) ac.getBean("student");
        System.out.println(student);

//        我是Student類的無參構造方法
//        setName:張三
//        setEmail:zs@qq.com
//        Student{name='張三', age=20}
    }

注意二:對於非自定義的類

在配置檔案中

    <!--
        非自定義類設定屬性
        只有這個類中有setXXX(),就可以
    -->

    <bean id="mydate" class="java.util.Date">

        <!--setTime(993462034956)-->
        <property name="time" value="9348362034" />

    </bean>

測試:

 @Test
    public void test02(){
        String config = "b1/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        Date mydate = (Date) ac.getBean("mydate");
        System.out.println(mydate);
    }

2. 引用型別

當指定 bean 的某屬性值為另一 bean 的例項時,通過 ref 指定它們間的引用關係

ref的值必須為某 bean 的 id 值

如下:

先建立一個School類

package com.md.b2;

/**
 * @author MD
 * @create 2020-08-07 20:40
 */
public class School {

    private String name;
    private String address;

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

再建立一個Student類,在裡面引用School的類的物件

package com.md.b2;

/**
 * @author MD
 * @create 2020-08-07 19:55
 */
public class Student {


    private String name;
    private int age;


//    宣告一個引用資料型別
    private School school;

    public void setName(String name) {

        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(School school) {
        System.out.println("setSchool:"+school);
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

寫對應的配置檔案

<?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">

    <!--
           2. 引用資料型別的注入:spring來呼叫類的set方法,
           <property name="屬性名稱" ref="bean的id也就是物件的名稱"/>
    -->


    <!--
        宣告school物件
    -->
    <bean id="school" class="com.md.b2.School">
        <property name="name" value="清華"/>
        <property name="address" value="北京"/>
    </bean>


    <bean id="student" class="com.md.b2.Student">
        <property name="name" value="張三"/>
        <property name="age" value="40"/>
        <!--
            引用資料型別,呼叫的是setSchool(school),就是上面的
        -->
        <property name="school" ref="school"/>
    </bean>

</beans>

測試:

   @Test
    public void test02(){
        // 注意:此時由於這個檔案不是直接在resources下面,而是在下面的b2包的下面,所以指定的路徑得加上
        String config = "b2/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        Student student = (Student) ac.getBean("student");
        System.out.println(student);

//        setSchool:School{name='清華', address='北京'}
//        Student{name='張三', age=40, school=School{name='清華', address='北京'}}

    }

三、構造注入

構造注入是指,spring在呼叫類的有參構造方法,在建立物件的同時,在構造方法中進行屬性的賦值

語法:使用<constructor-arg />標籤,具體看下面的使用

首先還在Student類中寫有參構造器

public Student(String name, int age, School school) {
        System.out.println("我是Student類的有參構造方法");
        this.name = name;
        this.age = age;
        this.school = school;
    }

然後在配置檔案中

<?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">

    <!--
       構造注入
            spring在呼叫類的有參構造方法,在建立物件的同時,在構造方法中進行屬性的賦值
               構造注入使用 <constructor-arg> 標籤
          <constructor-arg> 標籤:一個<constructor-arg>表示構造方法一個引數。
          <constructor-arg> 標籤屬性:
             name:表示構造方法的形參名
             index:表示構造方法的引數的位置,引數從左往右位置是 0 , 1 ,2的順序
             value:構造方法的形參型別是簡單型別的,使用value
             ref:構造方法的形參型別是引用型別的,使用ref
    -->


    <!--
        宣告school物件
    -->
    <bean id="school" class="com.md.b3.School">
        <property name="name" value="清華"/>
        <property name="address" value="北京"/>
    </bean>


  <!--推薦用name-->
    
    <!--呼叫類的有參構造方法-->
    <!--<bean id="student" class="com.md.b3.Student">-->
        <!--<constructor-arg name="name"  value="張三"/>-->
        <!--<constructor-arg name="age" value="30"/>-->
        <!--<constructor-arg name="school" ref="school"/>-->
    <!--</bean>-->

    <!-- 或者這樣也是可以的,根據引數的位置-->
    <!--<bean id="student" class="com.md.b3.Student">-->
        <!--<constructor-arg index="0" value="張三"/>-->
        <!--<constructor-arg index="1" value="30"/>-->
        <!--<constructor-arg index="2" ref="school"/>-->
    <!--</bean>-->
    <!---->

    <!--或者直接省略-->
    <bean id="student" class="com.md.b3.Student">
        <constructor-arg value="張三"/>
        <constructor-arg value="30"/>
        <constructor-arg ref="school"/>
    </bean>


</beans>

測試:

    @Test
    public void test01(){
        // 注意:此時由於這個檔案不是直接在resources下面,而是在下面的b3包的下面,所以指定的路徑得加上
        String config = "b3/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);

        Student student = (Student) ac.getBean("student");

        System.out.println(student);

//        我是Student類的有參構造方法
//        Student{name='張三', age=30, school=School{name='清華', address='北京'}}
    }

四、引用型別屬性自動注入

對於引用型別屬性的注入,也可不在配置檔案中顯示的注入。可以通過為<bean/>標籤設定 autowire 屬性值,為引用型別屬性進行隱式自動注入(預設是不自動注入引用型別屬性)

根據自動注入判斷標準的不同,可以分為兩種:

  • byName:根據名稱自動注入
  • byType: 根據型別自動注入

1. byName 方式自動注入

當配置檔案中被呼叫者 bean 的 id 值與程式碼中呼叫者 bean 類的屬性名相同時,可使用byName 方式,讓容器自動將被呼叫者 bean 注入給呼叫者 bean。容器是通過呼叫者的 bean類的屬性名與配置檔案的被呼叫者 bean 的 id 進行比較而實現自動注入的

語法:

byName(按名稱注入) : java類中引用型別的屬性名和spring容器中(配置檔案)<bean>的id名稱一樣,
                     且資料型別是一致的,這樣的容器中的bean,spring能夠賦值給引用型別。
       語法:
       <bean id="xx" class="yyy" autowire="byName">
          簡單型別屬性賦值
       </bean>

例子:

public class School {

    private String name;
    private String address;
    // 省略set
}

//---------------------------------
public class Student {
    private String name;
    private int age;

//    宣告一個引用資料型別
    private School school;
    
    // 省略set
}  

在配置檔案中

<bean id="school" class="com.md.b4.School">
        <property name="name" value="北大"/>
        <property name="address" value="北京"/>
 </bean>

 <!--/////////////////////////////-->

 <bean id="student" class="com.md.b4.Student" autowire="byName">
        <property name="name" value="張三"/>
        <property name="age" value="20"/>
     <!--自動賦值引用資料型別-->
    </bean>

如上:

java類中引用型別的屬性名school 和 spring容器中(配置檔案)<bean>的id名稱一樣,且資料型別一致,這樣就能自動注入

2. byType 方式自動注入

使用 byType 方式自動注入,要求:配置檔案中被呼叫者 bean 的 class 屬性指定的類,要與程式碼中呼叫者 bean 類的某引用型別屬性型別同源。即要麼相同,要麼有 is-a 關係(子類,或是實現類)

但這樣的同源的被呼叫 bean 只能有一個。多於一個,容器就不知該匹配哪一個了

語法:

byType(按型別注入) : java類中引用型別的資料型別和spring容器中(配置檔案)<bean>的class屬性
                            是同源關係的,這樣的bean能夠賦值給引用型別
       同源就是一類的意思:
        1.java類中引用型別的資料型別和bean的class的值是一樣的。
        2.java類中引用型別的資料型別和bean的class的值父子類關係的。
        3.java類中引用型別的資料型別和bean的class的值介面和實現類關係的
       語法:
       <bean id="xx" class="yyy" autowire="byType">
          簡單型別屬性賦值
       </bean>

       注意:在byType中, 在xml配置檔案中宣告bean只能有一個符合條件的,
              多餘一個是錯誤的

還是上面的例子:

五、指定多個 Spring 配置檔案

在實際應用裡,隨著應用規模的增加,系統中 Bean 數量也大量增加,導致配置檔案變得非常龐大、臃腫。為了避免這種情況的產生,提高配置檔案的可讀性與可維護性,可以將Spring 配置檔案分解成多個配置檔案,其中一個為主配置檔案

total.xml

<?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">

    <!--
         包含關係的配置檔案:
         total表示主配置檔案 : 包含其他的配置檔案的,主配置檔案一般是不定義物件的。
         語法:<import resource="其他配置檔案的路徑" />
         關鍵字:"classpath:" 表示類路徑(class檔案所在的目錄),
               在spring的配置檔案中要指定其他檔案的位置, 需要使用classpath,告訴spring到哪去載入讀取檔案。
    -->

    <!--載入的是檔案列表-->
    <!--
    <import resource="classpath:b4/spring-school.xml" />
    <import resource="classpath:b4/spring-student.xml" />
    -->
<!--/////////////////////////////-->
    <!--
       在包含關係的配置檔案中,可以萬用字元(*:表示任意字元)
       注意: 主的配置檔名稱不能包含在萬用字元的範圍內(不能叫做spring-total.xml)
    -->
    <import resource="classpath:b4/spring-*.xml" />

</beans>

六、總結

1. set注入

spring呼叫類的set方法實現屬性賦值

  1. 簡單型別的set注入: <property name="屬性名" value="屬性的值"/>
  2. 引用型別的set注入:<property name="屬性名" ref="bean的id"/>

2. 構造注入

spring呼叫有引數的構造方法

  1. <constructor-arg>的name屬性,name表示構造方法的形參名
  2. <constructor-arg>的index屬性,表示構造方法形參的位置,從0開始

3. 自動注入

由spring根據某些規則,給引用型別完成賦值,有byName、byType

  1. byName:按名稱注入,java類中引用型別的屬性名和spring容器中bean的id一樣,資料型別一樣
  2. byType:按型別注入,java類中引用型別的資料型別和spring容器中bean的class是同源關係

相關文章