Spring之依賴配置詳解

通過以上的學習,對spring容器和DI的概念應該比較清晰了,DI(依賴注入)作爲spring的核心,spring當然提供了一套完善的機制來進行依賴注入。前篇文章從概念上介紹了依賴注入,本篇着重學習spring依賴注入的方法,這裏主要採用xml的方式。

基本注入

構造器注入和設值注入是依賴注入的兩種主要方式,spring對此有很完善的實現,下面首先以代碼的形式進行簡要的說明。

構造器注入

Spring容器通過調用bean的構造函數(可能帶有幾個參數,每個參數代表一個依賴)完成構造器注入。通過靜態工廠方法和調用構造器及其類似,這裏一併進行說明。

  Spring中構造器依賴注入採用以下的形式,每個參數用bean標籤的子標籤  < constructor-arg >進行配置:

    <bean id="..." class="com.test.wdi.ExampleBean">
       <constructor-arg index="..." value="..." name="..." ref="..." type="..."></constructor-arg>
.. .. ..
    </bean>

Index:在構造函數中參數的位置,從0開始<constructor-arg >標籤中的配置包含5個項,使用他們中的某些進行某個依賴的配置,簡單的描述如下:

Value:參數值,通常是基本類型和 type或者index配合使用。

Name:構造函數中參數的名字

Ref:引用其他Spring管理的對象

Type:參數的類型,值爲基本類型或者完全限定的類名。

基本Bean

下面採用代碼加註釋的形式分別對以上的配置項加以說明,引用的類庫見以前的文章,首先是簡單的代碼結構截圖:


Fruit

Apple、Banana是用來演示ref配置項的,只是一個簡單的類,沒有任何字段。Fruit包含一個Apple和一個Banana,其代碼如下:

package com.test.wdi;
 
 
/**  
* @date 2015-2-27
* @Description:此類主要是爲了說明構造器注入的ref項,包含了基本的構造函數,靜態工廠方法,和實例工廠方法
*/
 
public class Fruit {
 
    private Apple apple;
   
    private Banana banana;
   
   
    public Fruit() {
       super();
    }
 
   
    /**  
    * 靜態工廠方法
    */
    public static Fruit newInstance(Apple apple, Banana banana){
       return new Fruit(apple, banana);
    }
   
    /**  
    * 實例工廠方法
    */
    public Fruit  newInstance1(Apple apple, Banana banana){
       return new Fruit(apple, banana);
    }
   
    /**  
    * 傳統帶參構造函數
    */
    public Fruit(Apple apple, Banana banana) {
       super();
       this.apple = apple;
       this.banana = banana;
    }
 
    public Apple getApple() {
       return apple;
    }
 
    public void setApple(Apple apple) {
       this.apple = apple;
    }
 
    public Banana getBanana() {
       return banana;
    }
 
    public void setBanana(Banana banana) {
       this.banana = banana;
    }
 
    @Override
    public String toString() {
       return hashCode()+ "Fruit [apple=" + apple + ", banana=" + banana + "]";
    }
   
}

ExampleBean

ExampleBean是用來演示除ref之外的其他配置項的,它有連個基本類型的字段,其代碼如下:

package com.test.wdi;
 
/**  
* @date 2015-2-28
* @Description:此類用來說明構造器注入配置項中的 name index type value的,爲了方便重寫了toString方法
*/
public class ExampleBean {
 
    private int years;
    private String ultimateAnswer;
   
    private boolean test;
   
  
    public ExampleBean() {
       super();
    }
 
    /**
     * 構造函數2,和構造函數1一樣具有兩個參數,如果bean配置中不加入type的限制,
     * 將會有和預期不一致的結果
     */
    public ExampleBean(int years, boolean test) {
        this.years = years;
        this.test = test;
    }
   
   
    /**
     * 構造函數1
     */
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
   
   
 
    @Override
    public String toString() {
       return "ExampleBean [years=" + years + ", ultimateAnswer=" + ultimateAnswer + ", test=" + test + "]";
    }
 
   
}

BeanXMl配置

Bean的配置,將依次說明<constructor-arg >標籤中每個配置屬性的簡單用法和它們的組合。bean配置包含連個文件,allbean.xml是主配置文件,它通過import標籤引入t1.xml,而t1.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">
 
    <!--
    此配置文件是spring 元數據的配置文件,根標籤爲 beans標籤。
    在這個標籤下可以定義由Spring 管理的 bean,其定義形式如下被註釋的地方所示:
    基本的配置包括一個容器全局唯一的字符串id,和一個完全限定的類名。
   
    但在這個例子中,有兩個import標籤,分別引入了另外兩個 xml元數據配置文件。
    這麼做的好處是,不同的 bean定義對應相應的程序體系架構。
  import標籤導入的配置文件路徑是相對路徑,相對於當前文件。
 
  路徑前面可以與斜槓,但不建議這麼做。如下示例不推薦使用
      <import resource="/com/test/dao/dao.xml"/>
 
  類似../的路徑也不推薦使用
   
     -->
   
   
    <!-- 測試spring依賴注入 -->
    <import resource="com/test/wdi/t1.xml"/>
   
   
</beans>

<?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">
 
    <!-- 定義一個Apple Bean -->
    <bean id="a" class="com.test.wdi.Apple">
    </bean>
       <!-- 定義一個Banana Bean -->
   
    <bean id="b" class="com.test.wdi.Banana">
    </bean>
    <!--傳統的構造函數注入,採用ref配置,引用spring管理的其他bean -->
    <bean id="f1" class="com.test.wdi.Fruit">
       <constructor-arg ref="a"></constructor-arg>
       <constructor-arg ref="b"></constructor-arg>
    </bean>
   
    <!--靜態工廠方法注入,將調用Fruit 的靜態方法 newInstance,   採用ref配置,引用spring管理的其他bean -->
    <bean id="f2" class="com.test.wdi.Fruit" factory-method="newInstance">
       <constructor-arg ref="a"></constructor-arg>
       <constructor-arg ref="b"></constructor-arg>
    </bean>
    <!--實例工廠方法注入,將調用對象 f2 的靜態方法 newInstance,   採用ref配置,引用spring管理的其他bean -->
    <bean id="f3" factory-bean="f2" factory-method="newInstance1">
       <constructor-arg ref="a"></constructor-arg>
       <constructor-arg ref="b"></constructor-arg>
    </bean>
   
    <!--直接採用 value配置項,在沒有模糊性的前提下,spring能直接把value轉化爲正確的類型,但這裏存在模糊性,因爲ExampleBean中存在兩個具有兩個參數的構造函數
      據測試,哪個構造函數在前,就會優先使用哪個構造函數,在不匹配的情況下,依次類推。
      下面兩個配置第一個使用     public ExampleBean(int years, boolean test)
      第二個因爲true1 不能轉化爲 bool類型,則使用構造函數
        public ExampleBean(int years, String ultimateAnswer)
       
        但是問題在於,假如第一個配置的初衷是把 ExampleBean.ultimateAnswer的值設爲"true" ,那麼就和預期不一樣。
       
        接下來的配置來解決以上問題
    -->
    <bean id="exam1a" class="com.test.wdi.ExampleBean">
       <constructor-arg value="1111"></constructor-arg>
       <constructor-arg value="true"></constructor-arg>
    </bean>
    <bean id="exam1b" class="com.test.wdi.ExampleBean">
       <constructor-arg value="1111"></constructor-arg>
       <constructor-arg value="true1"></constructor-arg>
    </bean>
   
    <!--直接採用 value配置項,採用type進行限定,使用正確的構造器型 -->
    <bean id="exam2" class="com.test.wdi.ExampleBean">
       <constructor-arg type="int" value="1"></constructor-arg>
       <constructor-arg type="java.lang.String" value="true"></constructor-arg>
    </bean>
    <!--直接採用 value配置項,採用type進行限定,使用正確的構造器型  使用index,可以不按順序-->
    <bean id="exam3" class="com.test.wdi.ExampleBean">
        <constructor-arg index="1" value="true" type="java.lang.String"></constructor-arg>
       <constructor-arg index="0" value="2"></constructor-arg>
    </bean>
    <!--直接採用 value配置項,採用name進行限定,使用正確的構造器型  使用index,可以不按順序
    不過使用name 有一些限制,限制在於必須以debug的形式編譯類,因爲只有這樣,纔可以保留構造函數中參數的變量名。
    或者使用在構造函數中使用註解 @ConstructorProperties
   
    如下
        @ConstructorProperties({"years", "ultimateAnswer"})
      public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
    -->
   
    <bean id="exam4" class="com.test.wdi.ExampleBean">
        <constructor-arg name="ultimateAnswer" value="true" />
       <constructor-arg name="years" value="3" />
    </bean>
</beans>

測試程序

測試程序使用main函數,依次打印出來t1中定義的那些bean,代碼如下:

package com.test;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
/**  
 * @Description:
 * 本示例例環境爲eclipse
 */
public class TestMain {
    public static void main(String[] args) {
       /**
        * ApplicationContext代表spring容器,而ClassPathXmlApplicationContext是它的一個實現,它從類路徑下讀取相應的
        *  xml 元數據配置,並初始化容器。其中allbean.xml是相應的元數據配置
        */
       ApplicationContext context = new ClassPathXmlApplicationContext("allbean.xml");
      
       //依次打印對象
       System.out.println(context.getBean("f1"));
       System.out.println(context.getBean("f2"));
       System.out.println(context.getBean("f3"));
 
      
       System.out.println(context.getBean("exam1a"));
       System.out.println(context.getBean("exam1b"));
 
       System.out.println(context.getBean("exam2"));
       System.out.println(context.getBean("exam3"));
       System.out.println(context.getBean("exam4"));
 
    }
 
}

測試結果如下截圖,均符合預期:


注:構造器注意需要注意循環依賴的問題。

設值注入

設值注入是spring容器在調用過無參構造函數或者無參工廠方法後,然後通過調用其setter方法進行依賴的注入。

Set注入比較簡單,之前的文章spring容器和配置初識所舉的例子就是setter注入。下面是一段xml配置極其說明:

<!-- 通過 setter 進行注入,spring首先調用無參的構造函數,然後分別調用其相應的set方法
       property 標籤有三個屬性 name  ref  和 value
       其中 name是將要被注入的屬性的名稱。
       ref 是引用被spring管理的對象
      
       value 是實際的值,被spring 根據屬性的類型,轉換爲對應的值。
       此轉換由propertyEditor進行,這裏暫且不提,value的值也可以是 spring表達式 spel
    -->
    <bean id="f4" class="com.test.wdi.Banana" >
        <property name="apple" ref="a"></property>
        <property name="banana" ref="b"></property>
    </bean>

依賴注入實例

依賴有多種類型,包含基本類型(int、double、string等)和複雜類型(系統類和自定義的類),下面依次說明它們在注入過程中需要注意的問題。

設置注入和構造器注入對應的標籤分別是<property> 和<constructor-arg>它們的子標籤的意義保持一致,下面的例子一般情況下,只做其中一種的說明。另外它們的屬性除了 <constructor-arg> 單獨擁有的type和 index 其他的也保持一致的意義。

直接類型(原始類型、字符串等)

直接類型直接使用value子標籤或者屬性配置即可。Spring的conversion service 會自動把字符串轉化爲真正的屬性(屬性或者構造函數參數)。

對應設置注入,由javabean規範和name屬性可以準確的確定類型,故轉換不存在問題。但是對於構造器注入,如果不對type或者name進行限制,可能會產生非預期結果。

如下一段配置實是上文中構造器中的一個片段,另外增加了設置注入加以比較。運行結果可以自行測試:

<!--直接採用 value配置項,在沒有模糊性的前提下,spring能直接把value轉化爲正確的類型,但這裏存在模糊性,因爲ExampleBean中存在兩個具有兩個參數的構造函數
       據測試,哪個構造函數在前,就會優先使用哪個構造函數,在不匹配的情況下,依次類推。下面兩個配置第一個使用 public ExampleBean(int
       years, boolean test) 第二個因爲true1 不能轉化爲 bool類型,則使用構造函數 public ExampleBean(int
       years, String ultimateAnswer) 但是問題在於,假如第一個配置的初衷是把 ExampleBean.ultimateAnswer的
       值設爲"true" ,那麼就和預期不一樣。解決的辦法是加入type屬性的限定,不要只使用參數的個數(對於基本類型會有模糊性) -->
   
 
    <bean id="exam1a" class="com.test.wdi.ExampleBean">
       <constructor-arg value="1111"></constructor-arg>
       <constructor-arg value="true"></constructor-arg>
    </bean>
    <bean id="exam1b" class="com.test.wdi.ExampleBean">
       <constructor-arg value="1111"></constructor-arg>
       <constructor-arg value="true1"></constructor-arg>
    </bean>
    <!-- 設值注入有name 可以確定需要轉換的類型 -->
   
    <bean id="exam1c" class="com.test.wdi.ExampleBean">
       <property name="years" value="1111"></property>
       <property name="ultimateAnswer" value="true1"></property>
       <property name="test" value="true"></property>
    </bean>


另外類型爲java.util.properties可以採用類似下面的配置:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 
    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Idref標籤

Idref子標籤只是一個把某個bean的id傳遞作爲依賴傳遞給另外一個bean的防止錯誤的方式。通過只傳遞bean的id,可以構建構建更加靈活的程序。

基本的配置如下:

<!-- 以下兩個配置等價  ,這兩個配置的初衷在於把  "exam1c" 這個bean id 作爲依賴傳遞給另一個bean
    (在此例子中是傳遞給 id爲 exam1d* 的bean ),從而構建更通用的方法。通用的方法比如說是直接從
    spring容器中通過id獲取相應的對象,執行相應的方法。
   
    區別在於第一種方法 spring容器會驗證id 或者name 爲  exam1c 的bean在容器(當前容器或者父容器)中真的存在,如果不存在則會報出異常,及早發現,而第二種則不會,直道真正運行時才發現。
    -->
    <bean id="exam1d1" class="com.test.wdi.ExampleBean">
        <property name="years" value="1111"></property>
        <property name="ultimateAnswer" >
            <idref bean="exam1c"/></property>
        <property name="test" value="true"></property>
    </bean>
    <bean id="exam1d2" class="com.test.wdi.ExampleBean">
        <property name="years" value="1111"></property>
        <property name="ultimateAnswer" value="exam1c"> </property>
        <property name="test" value="true"></property>
    </bean>

引用其他bean

引用其他bean是spring中及其常見的場景,這種配置通過<constructor-arg/> 或<property/> 的子標籤<ref>實現,或者通過ref屬性(實質上是子標籤的一種縮寫)。而子標籤的形式提供了更具體的用法和配置。

基本樣式如下:

<ref bean="someBean"/>

或者

<ref parent="someBean"/>

以上兩者的區別是:前者在當前spring容器和父容器中尋找id(name)爲someBean 的對象,而後者只在父容器中尋找。

而ref屬性是前者的簡寫。

下面看具體的例子:


<!-- 以下兩種配置等同,不同點在於前者是使用標籤配置,後者使用屬性配置 -->
    <bean id="f5a" class="com.test.wdi.Fruit">
       <property name="apple">
           <ref bean="a" />
       </property>
       <property name="banana">
           <ref bean="b" />
       </property>
    </bean>
    <bean id="f5b" class="com.test.wdi.Fruit">
       <property name="apple" ref="a">
       </property>
       <property name="banana" ref="b"></property>
    </bean>

內部bean

在<constructor-arg/> 或<property/>內部定義的bean稱之爲內部bean,spring會自動忽略其id、name 屬性和 scope屬性,配置如下:

<!-- 內部bean  ,會自動忽略其id、name 屬性和 scope屬性-->
    <bean id="f5c" class="com.test.wdi.Fruit">
       <property name="apple">
           <bean id="idIgnore" class="com.test.wdi.Apple" ></bean>
       </property>
       <property name="banana">
           <bean class="com.test.wdi.Banana"></bean>
 
       </property>
    </bean>

集合

Java的強大之處之一在於jdk提供了強大的集合框架,spring當然提供了注入集合的方法。

自從java1.5開始,支持泛型,我們的集合儘量使用泛型,另外關於集合合併涉及到bean定義的繼承,這裏暫不討論。

首先引入一個帶集合屬性的bean,省略了構造函數和get set方法。

public class FruitCollection {
 
    private Set<Fruit> fruits;
    private List<Fruit> fruitList;
 
    private Map<String, Fruit> fruitMap;
   
    private Properties ppp;
}

具體見下面的配置代碼:

<!-- 集合的注入,包括 set list map properties -->
    <bean id="fruitSet" class="com.test.wdid.FruitCollection">
       <property name="fruits">
           <!-- 集合 set標籤,可以定義內部bean,引用其他bean -->
           <set>
              <bean class="com.test.wdi.Fruit">
                  <property name="apple" ref="a">
                  </property>
                  <property name="banana" ref="b"></property>
              </bean>
              <ref bean="f1" />
              <ref bean="f2" />
              <ref bean="f3" />
              <ref bean="f4" />
              <ref bean="f5a" />
              <ref bean="f5b" />
              <ref bean="f5c" />
           </set>
       </property>
 
       <property name="fruitList">
                   <!--  list標籤,可以定義內部bean,引用其他bean -->
           <list>
              <bean class="com.test.wdi.Fruit">
                  <property name="apple" ref="a">
                  </property>
                  <property name="banana" ref="b"></property>
              </bean>
              <ref bean="f1" />
              <ref bean="f2" />
              <ref bean="f3" />
              <ref bean="f4" />
              <ref bean="f5a" />
              <ref bean="f5b" />
              <ref bean="f5c" />
           </list>
       </property>
 
       <property name="fruitMap">
           <!--  map標籤  定義enrty 子標籤,屬性有  key value key-ref value-ref ,意思很明顯-->
           <map>
              <entry key="f1" value-ref="f1" ></entry>
              <entry key="f2" value-ref="f2"></entry>
              <entry key="f3" value-ref="f3"></entry>
              <entry key="f4" value-ref="f4"></entry>
           </map>
       </property>
 
       <property name="ppp">
          
           <!--  props標籤  定義prop 子標籤,本質是字符串類型的鍵值對-->
          
           <props>
              <prop key="1">t1</prop>
              <prop key="2">t2</prop>
              <prop key="3">t3</prop>
              <prop key="4">t4</prop>
           </props>
       </property>
    </bean>

Null和空串

Spring支持注入null和字符串的空串,見如下的配置:

<!-- 空串和null -->
    <bean id="exam5Null" class="com.test.wdi.ExampleBean">
        <property name="years" value="1111"></property>
       <property name="ultimateAnswer">
           <null/>
       </property>
       <property name="test" value="true"></property>
    </bean>
   
    <bean id="exam5Empty" class="com.test.wdi.ExampleBean">
         <property name="years" value="1111"></property>
       <property name="ultimateAnswer" value="">
       </property>
       <property name="test" value="true"></property>
    </bean>

實際相當於分別執行了如下方法:

setUltimateAnswer(null);
setUltimateAnswer("");

縮寫

Spring的bean配置文件的格式除了以上用了多次的標準形式,還有一些簡單的縮略形式,這些縮略形式是基於xml命名空間,分別是p-nameplace和c-nameplace,依次對應seter注入和 構造器注入。

這裏不再贅述。直接貼一個spring官方文檔的示例來說明p命名空間:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="[email protected]"/>
    </bean>
 
    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[email protected]"/>
</beans>

複合屬性名

嵌套的注入,fruit 有個apple屬性,假設apple 有個 color屬性。那麼可以在fruit的定義中直接爲color注入屬性,前提是apple不爲null:

    <!-- 縮寫複合屬性名  假設apple 有個 color屬性 -->
    <bean id="fwithColor" class = "com.test.wdi.Fruit">
        <property name="apple" ref="a"></property>
        <property name="apple.color" value="yellow"></property>
    </bean>

結束

本文從spring兩種依賴注入講起,然後舉出具體的配置例子依次講解到了大部門的注入實例,本文中大部分的例子均通過測試,環境爲spring4.1.5。但本人水平有限,肯定有很多不當和不完整支持,期待共同進步,本文中的實例用到的代碼地址爲:本文資源下載地址(免積分)

 

發佈了35 篇原創文章 · 獲贊 18 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章