1. 程式人生 > >Spring Bean管理--依賴注入、自動裝配

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配置。