Spring Bean管理--依賴注入、自動裝配
一、摘要
本文主要介紹Spring Bean管理的依賴注入(Dependency Injection,DI)部分:基於Setter方法注入,建構函式注入,自動裝配注入,@Autowired註解注入等。
二、依賴注入
所謂注入,就是給某一個bean例項的屬性設值時,無需顯性編寫Java程式碼就可以實現屬性賦值;所謂依賴注入,則通常指bean例項引用了其它例項,如常見的service引用dao,則對於service來說,用到的dao就需要依賴注入,spring ioc容器在建立service bean時會根據既定規則自動注入dao例項。
1、基於建構函式的依賴注入
建構函式注入也就是通過構造方法注入依賴,建構函式的引數一般情況下就是依賴項,spring容器會根據bean中指定的建構函式引數來決定呼叫那個建構函式,看一個案例:
TextEditor.java
package com.marcus.spring.beans;
public class TextEditor {
private SpellChecker spellChecker;
private String generateType;
private String text;
@SuppressWarnings("rawtypes")
private List keywords;
public TextEditor() {
}
@SuppressWarnings("rawtypes")
public TextEditor( List list) {
System.out.println("Inside TextEditor constructor(list).");
this.keywords = list;
}
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor(checker).");
this.spellChecker = spellChecker;
}
public TextEditor(SpellChecker spellChecker, String generateType) {
System.out.println("Inside TextEditor constructor.");
this.spellChecker = spellChecker;
this.generateType = generateType;
}
public TextEditor(SpellChecker spellChecker, String generateType, String text) {
System.out.println("Inside TextEditor constructor(checker, generteType, text).");
this.spellChecker = spellChecker;
this.generateType = generateType;
this.text = text;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
public String getGenerateType() {
System.out.println("TextEditor generate type: " + this.generateType);
return this.generateType;
}
@SuppressWarnings("rawtypes")
public List getKeyWords() {
return this.keywords;
}
public void spellCheck() {
spellChecker.checkSpelling("TextEditor");
}
}
SpellChecker.java
package com.marcus.spring.beans;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class SpellChecker {
@SuppressWarnings("rawtypes")
private List wordsList;
@SuppressWarnings("rawtypes")
private Set wordsSet;
@SuppressWarnings("rawtypes")
private Map wordsMap;
Properties wordsProp;
public SpellChecker() {
System.out.println("Inside SpellChecker constructor.");
}
public String checkSpelling(String text) {
String result = "success";
String errWords = "";
if (text == null || text.trim().length() < 1
|| wordsList == null || wordsList.size() < 1) {
return result;
}
String[] words = text.trim().split(",");
for(String word : words) {
if (word != null && word.trim().length() > 0) {
if(! wordsList.contains(word.trim().toLowerCase())) {
errWords += "".equals(errWords) ? word.trim() : "," + word.trim();
}
}
}
return errWords.length() < 1 ? result : errWords + " spells error!";
}
@SuppressWarnings("rawtypes")
public void setWordsList(List wordsList) {
this.wordsList = wordsList;
}
@SuppressWarnings("rawtypes")
public List getWordsList() {
System.out.println("List Elements :" + wordsList);
return wordsList;
}
@SuppressWarnings("rawtypes")
public void setWordsSet(Set wordsSet) {
this.wordsSet = wordsSet;
}
@SuppressWarnings("rawtypes")
public Set getWordsSet() {
System.out.println("Set Elements :" + wordsSet);
return wordsSet;
}
@SuppressWarnings("rawtypes")
public void setWordsMap(Map wordsMap) {
this.wordsMap = wordsMap;
}
@SuppressWarnings("rawtypes")
public Map getWordsMap() {
System.out.println("Map Elements :" + wordsMap);
return wordsMap;
}
public void setWordsProp(Properties wordsProp) {
this.wordsProp = wordsProp;
}
public Properties getWordsProp() {
System.out.println("Property Elements :" + wordsProp);
return wordsProp;
}
}
ioc-di.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-3.0.xsd">
<bean id="textEditor" class="com.marcus.spring.beans.TextEditor">
<constructor-arg ref="spellChecker"/>
</bean>
<bean id="textEditor2" class="com.marcus.spring.beans.TextEditor">
<constructor-arg ref="spellChecker"/>
<constructor-arg type="java.lang.String" value="constructor(checker, generatType)"/>
</bean>
<bean id="textEditor3" class="com.marcus.spring.beans.TextEditor">
<constructor-arg ref="spellChecker"/>
<constructor-arg type="java.lang.String" index="1" value="constructor(checker, generatType)"/>
<constructor-arg type="java.lang.String" index="2" value="Text, Wood, SuperMen"/>
</bean>
<bean id="textEditor4" class="com.marcus.spring.beans.TextEditor">
<constructor-arg type="java.util.List">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
</list>
</constructor-arg>
</bean>
<bean id="spellChecker" class="com.marcus.spring.beans.SpellChecker" />
</beans>
該配置檔案定義了TextEditor的3個例項,對應TextEditor的3個建構函式。spring ioc容器會根據引數型別嘗試查詢合適的建構函式並建立TextEditor例項,因此<constructor-arg>的注入順序並不重要。
請注意textEditor3,其建構函式後兩個引數型別都是string,此時,我們必須標記引數位置索引index,否則對於spring ioc容器來說是一個災難。
請注意textEditor4,其建構函式引數是一個集合,map、set等其它集合類似。
如果你想向一個物件傳遞一個引用,你需要使用標籤的 ref 屬性,如果你想直接傳遞值,那麼你應該使用如上所示的 value 屬性,如果引數是集合型別則參考textEditor4。
測試類BeanDIApp.java
package com.marcus.spring.beans;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanDIApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("ioc-di.xml");
System.out.println("=================================================");
TextEditor textEditor = context.getBean("textEditor", TextEditor.class);
System.out.println("textEditor: " + textEditor.toString());
TextEditor textEditor2 = context.getBean("textEditor2", TextEditor.class);
System.out.println("textEditor2: " + textEditor2.toString());
TextEditor textEditor3 = context.getBean("textEditor3", TextEditor.class);
System.out.println("textEditor3: " + textEditor3.toString());
System.out.println("textEditor3->text: " + textEditor3.getText());
TextEditor textEditor4 = context.getBean("textEditor4", TextEditor.class);
System.out.println("textEditor4: " + textEditor4.toString());
System.out.println("textEditor4->keywords: " + textEditor4.getKeyWords());
context.close();
}
}
輸出結果
Inside SpellChecker constructor.
Inside TextEditor constructor(checker).
Inside TextEditor constructor(checker, generteType).
Inside TextEditor constructor(checker, generteType, text).
Inside TextEditor constructor(list).
=================================================
textEditor: com.marcus.spring.beans.TextEditor@14bf9759
textEditor2: com.marcus.spring.beans.TextEditor@5f341870
textEditor3: com.marcus.spring.beans.TextEditor@589838eb
textEditor3->text: Text, Wood, SuperMen
textEditor4: com.marcus.spring.beans.TextEditor@544fe44c
textEditor4->keywords: [INDIA, Pakistan, USA]
迴圈依賴
建構函式迴圈依賴,class A構造時引用B,class B構造時引用A,就是迴圈依賴,這種情況下spring ioc容器無法例項化這兩個bean,會報Is there an unresolvable circular reference?看一個案例:
LoopDependA.java
package com.marcus.spring.beans;
public class LoopDependA {
private LoopDependB dependB;
public LoopDependA(){
}
public LoopDependA(LoopDependB dependB) {
this.dependB = dependB;
}
public void setDependency(LoopDependB dependB) {
this.dependB = dependB;
}
public LoopDependB getDependency() {
return this.dependB;
}
}
LoopDependB.java
package com.marcus.spring.beans;
public class LoopDependB {
private LoopDependA dependA;
public LoopDependB() {
}
public LoopDependB(LoopDependA dependA) {
this.dependA = dependA;
}
public void setDependency(LoopDependA dependA) {
this.dependA = dependA;
}
public LoopDependA getDependency() {
return this.dependA;
}
}
ioc-di-loop.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-3.0.xsd">
<!-- 建構函式迴圈依賴 -->
<!--
<bean id="loopA" class="com.marcus.spring.beans.LoopDependA">
<constructor-arg ref="loopB"/>
</bean>
<bean id="loopB" class="com.marcus.spring.beans.LoopDependB">
<constructor-arg ref="loopA"/>
</bean>
-->
<bean id="loopA" class="com.marcus.spring.beans.LoopDependA">
<property name="dependency" ref="loopB" />
</bean>
<bean id="loopB" class="com.marcus.spring.beans.LoopDependB">
<property name="dependency" ref="loopA" />
</bean>
</beans>
建構函式迴圈依賴時,spring ioc容器無法例項化這2個bean,簡單理解就是競爭鎖,A例項化時需要B(B尚未例項化),B例項化需要A(A尚未例項化),A等B,B等A,誰也建立不了。
換成Setter注入,則可以,原因是Setter注入是在bean例項化完成之後通過呼叫set方法完成。因此,強烈不建議在配置檔案中使用迴圈依賴。
DILoopApp.java 測試類和輸出結果
package com.marcus.spring.beans;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* BeanApp.
*/
public class DILoopApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("ioc-di-loop.xml");
LoopDependA loopA = context.getBean("loopA", LoopDependA.class);
System.out.println("LoopDependA->getDependency(), " + loopA.getDependency());
LoopDependB loopB = context.getBean("loopB", LoopDependB.class);
System.out.println("LoopDependB->getDependency(), " + loopB.getDependency());
context.close();
//輸出結果
//LoopDependA->getDependency(), com.marcus.spring.beans.LoopDependB@77cd7a0
//LoopDependB->getDependency(), com.marcus.spring.beans.LoopDependA@204f30ec
}
}
小結
- 引用型別請用標籤的 ref 屬性,簡單值型別請用標籤的value屬性,如果是集合型別則參考上文的textEditor4例項配置
- 多個引數請設定type屬性
- 多個引數型別一致時,如多個字串等,請設定index屬性
- 避免迴圈依賴
2、基於Setter函式的依賴注入
Setter注入顧名思義,被注入的屬性需要有set方法, Setter注入支援簡單型別、集合型別和引用型別,Setter注入時在bean例項建立完成後執行。Setter注入與建構函式注入唯一的區別就是在基於建構函式注入中,我們使用的是〈bean〉標籤中的〈constructor-arg〉元素,而在基於設值函式的注入中,我們使用的是〈bean〉標籤中的〈property〉元素。看一個案例:
TextEditor.java, SpellChecker.java 同上例
ioc-di2.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="textEditor" class="com.marcus.spring.beans.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
<property name="text" value="Text, Wood, SuperMen"/>
</bean>
<bean id="textEditor2" class="com.marcus.spring.beans.TextEditor"
p:spellChecker-ref="spellChecker" p:text="Text, Wood, SuperMen, Compute"/>
<bean id="spellChecker" class="com.marcus.spring.beans.SpellChecker">
<property name="wordsList">
<list>
<value>text</value>
<value>wood</value>
<value>superman</value>
</list>
</property>
</bean>
</beans>
例項textEditor設定了2個屬性:spellChecker和text,textEditor2功能等同textEditor,但是我們發現設定屬性值時簡單了很多。
可以使用 p-namespace 以一種更簡潔的方式來設定bean屬性值,要求配置檔案必須引入xmlns:p=“http://www.springframework.org/schema/p”,參考textEditor2 bean配置。