通過以上的學習,對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。但本人水平有限,肯定有很多不當和不完整支持,期待共同進步,本文中的實例用到的代碼地址爲:本文資源下載地址(免積分)