1. 程式人生 > >Spring-更多DI的知識

Spring-更多DI的知識

移除 進行 繼續 -c invoke 這就是 ima 不同的 string

3.3.1 延遲初始化Bean

延遲初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用時才創建及初始化Bean。

配置方式很簡單只需在<bean>標簽上指定 “lazy-init” 屬性值為“true”即可延遲初始化Bean。

Spring容器會在創建容器時提前初始化“singleton”作用域的Bean,“singleton”就是單例的意思即整個容器每個Bean只有一個實例,後邊會詳細介紹。Spring容器預先初始化Bean通常能幫助我們提前發現配置錯誤,所以如果沒有什麽情況建議開啟,除非有某個Bean可能需要加載很大資源,而且很可能在整個應用程序生命周期中很可能使用不到,可以設置為延遲初始化。

延遲初始化的Bean通常會在第一次使用時被初始化;或者在被非延遲初始化Bean作為依賴對象註入時在會隨著初始化該Bean時被初始化,因為在這時使用了延遲初始化Bean。

容器管理初始化Bean消除了編程實現延遲初始化,完全由容器控制,只需在需要延遲初始化的Bean定義上配置即可,比編程方式更簡單,而且是無侵入代碼的。

具體配置如下:

java代碼:

<bean id="helloApi"
class="cn.javass.spring.chapter2.helloworld.HelloImpl"
lazy-init="true"/>
3.3.2 使用depends-on

depends-on是指指定Bean初始化及銷毀時的順序,使用depends-on屬性指定的Bean要先初始化完畢後才初始化當前Bean,由於只有“singleton”Bean能被Spring管理銷毀,所以當指定的Bean都是“singleton”時,使用depends-on屬性指定的Bean要在指定的Bean之後銷毀。

配置方式如下:

java代碼:

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="decorator"
class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
depends-on="helloApi">
<property name="helloApi"><ref bean="helloApi"/></property>
</bean>

“decorator”指定了“depends-on”屬性為“helloApi”,所以在“decorator”Bean初始化之前要先初始化“helloApi”,而在銷毀“helloApi”之前先要銷毀“decorator”,大家註意一下銷毀順序,與文檔上的不符。

“depends-on”屬性可以指定多個Bean,若指定多個Bean可以用“;”、“,”、空格分割。

那“depends-on”有什麽好處呢?主要是給出明確的初始化及銷毀順序,比如要初始化“decorator”時要確保“helloApi”Bean的資源準備好了,否則使用“decorator”時會看不到準備的資源;而在銷毀時要先在“decorator”Bean的把對“helloApi”資源的引用釋放掉才能銷毀“helloApi”,否則可能銷毀 “helloApi”時而“decorator”還保持著資源訪問,造成資源不能釋放或釋放錯誤。

讓我們看個例子吧,在平常開發中我們可能需要訪問文件系統,而文件打開、關閉是必須配對的,不能打開後不關閉,從而造成其他程序不能訪問該文件。讓我們來看具體配置吧:

1)準備測試類:

ResourceBean從配置文件中配置文件位置,然後定義初始化方法init中打開指定的文件,然後獲取文件流;最後定義銷毀方法destroy用於在應用程序關閉時調用該方法關閉掉文件流。

DependentBean中會註入ResourceBean,並從ResourceBean中獲取文件流寫入內容;定義初始化方法init用來定義一些初始化操作並向文件中輸出文件頭信息;最後定義銷毀方法用於在關閉應用程序時想文件中輸出文件尾信息。

具體代碼如下:

java代碼:

package cn.javass.spring.chapter3.bean;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ResourceBean {
private FileOutputStream fos;
private File file;
//初始化方法
public void init() {
System.out.println("ResourceBean:========初始化");
//加載資源,在此只是演示
System.out.println("ResourceBean:========加載資源,執行一些預操作");
try {
this.fos = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//銷毀資源方法
public void destroy() {
System.out.println("ResourceBean:========銷毀");
//釋放資源
System.out.println("ResourceBean:========釋放資源,執行一些清理操作");
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public FileOutputStream getFos() {
return fos;
}
public void setFile(File file) {
this.file = file;
}
}

java代碼:

package cn.javass.spring.chapter3.bean;
import java.io.IOException;
public class DependentBean {
ResourceBean resourceBean;
public void write(String ss) throws IOException {
System.out.println("DependentBean:=======寫資源");
resourceBean.getFos().write(ss.getBytes());
}
//初始化方法
public void init() throws IOException {
System.out.println("DependentBean:=======初始化");
resourceBean.getFos().write("DependentBean:=======初始化=====".getBytes());
}
//銷毀方法
public void destroy() throws IOException {
System.out.println("DependentBean:=======銷毀");
//在銷毀之前需要往文件中寫銷毀內容
resourceBean.getFos().write("DependentBean:=======銷毀=====".getBytes());
}

public void setResourceBean(ResourceBean resourceBean) {
this.resourceBean = resourceBean;
}
}

2)類定義好了,讓我們來進行Bean定義吧,具體配置文件如下:

java代碼:

<bean id="resourceBean"
class="cn.javass.spring.chapter3.bean.ResourceBean"
init-method="init" destroy-method="destroy">
<property name="file" value="D:/test.txt"/>
</bean>
<bean id="dependentBean"
class="cn.javass.spring.chapter3.bean.DependentBean"
init-method="init" destroy-method="destroy" depends-on="resourceBean">
<property name="resourceBean" ref="resourceBean"/>
</bean>

<property name="file" value="D:/test.txt"/>配置:Spring容器能自動把字符串轉換為java.io.File。

init-method="init" :指定初始化方法,在構造器註入和setter註入完畢後執行。

destroy-method="destroy":指定銷毀方法,只有“singleton”作用域能銷毀,“prototype”作用域的一定不能,其他作用域不一定能;後邊再介紹。

在此配置中,resourceBean初始化在dependentBean之前被初始化,resourceBean銷毀會在dependentBean銷毀之後執行。

3)配置完畢,測試一下吧:

java代碼:

package cn.javass.spring.chapter3;
import java.io.IOException;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.javass.spring.chapter3.bean.DependentBean;
public class MoreDependencyInjectTest {
@Test
public void testDependOn() throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("chapter3/depends-on.xml");
//一點要註冊銷毀回調,否則我們定義的銷毀方法不執行
context.registerShutdownHook();
DependentBean dependentBean =
context.getBean("dependentBean", DependentBean.class);
dependentBean.write("aaa");
}
}

測試跟其他測試完全一樣,只是在此我們一定要註冊銷毀方法回調,否則銷毀方法不會執行。

如果配置沒問題會有如下輸出:

java代碼:

ResourceBean:========初始化
ResourceBean:========加載資源,執行一些預操作
DependentBean:=========初始化
DependentBean:=========寫資源
DependentBean:=========銷毀
ResourceBean:========銷毀
ResourceBean:========釋放資源,執行一些清理操作

3.3.3 自動裝配

自動裝配就是指由Spring來自動地註入依賴對象,無需人工參與。

目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四種自動裝配,默認是“no”指不支持自動裝配的,其中Spring3.0已不推薦使用之前版本的“autodetect”自動裝配,推薦使用Java 5+支持的(@Autowired)註解方式代替;如果想支持“autodetect”自動裝配,請將schema改為“spring-beans-2.5.xsd”或去掉。

自動裝配的好處是減少構造器註入和setter註入配置,減少配置文件的長度。自動裝配通過配置<bean>標簽的“autowire”屬性來改變自動裝配方式。接下來讓我們挨著看下配置的含義。

一、default:表示使用默認的自動裝配,默認的自動裝配需要在<beans>標簽中使用default-autowire屬性指定,其支持“no”、“byName ”、“byType”、“constructor”四種自動裝配,如果需要覆蓋默認自動裝配,請繼續往下看;

二、no:意思是不支持自動裝配,必須明確指定依賴。

三、byName:通過設置Bean定義屬性autowire="byName",意思是根據名字進行自動裝配,只能用於setter註入。比如我們有方法“setHelloApi”,則“byName”方式Spring容器將查找名字為helloApi的Bean並註入,如果找不到指定的Bean,將什麽也不註入。

例如如下Bean定義配置:

java代碼:

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byName"/>

測試代碼如下:

java代碼:

package cn.javass.spring.chapter3;
import java.io.IOException;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.javass.spring.chapter2.helloworld.HelloApi;
public class AutowireBeanTest {
@Test
public void testAutowireByName() throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("chapter3/autowire-byName.xml");
HelloApi helloApi = context.getBean("bean", HelloApi.class);
helloApi.sayHello();
}
}

是不是不要配置<property>了,如果一個bean有很多setter註入,通過“byName”方式是不是能減少很多<property>配置。此處註意了,在根據名字註入時,將把當前Bean自己排除在外:比如“hello”Bean類定義了“setHello”方法,則hello是不能註入到“setHello”的。

四、“byType”:通過設置Bean定義屬性autowire="byType",意思是指根據類型註入,用於setter註入,比如如果指定自動裝配方式為“byType”,而“setHelloApi”方法需要註入HelloApi類型數據,則Spring容器將查找HelloApi類型數據,如果找到一個則註入該Bean,如果找不到將什麽也不註入,如果找到多個Bean將優先註入<bean>標簽“primary”屬性為true的Bean,否則拋出異常來表明有個多個Bean發現但不知道使用哪個。讓我們用例子來講解一下這幾種情況吧。

1)根據類型只找到一個Bean,此處註意了,在根據類型註入時,將把當前Bean自己排除在外,即如下配置中helloApi和bean都是HelloApi接口的實現,而“bean”通過類型進行註入“HelloApi”類型數據時自己是排除在外的,配置如下(具體測試請參考AutowireBeanTest.testAutowireByType1方法):

java代碼:

<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byType"/>

2)根據類型找到多個Bean時,對於集合類型(如List、Set)將註入所有匹配的候選者,而對於其他類型遇到這種情況可能需要使用“autowire-candidate”屬性為false來讓指定的Bean放棄作為自動裝配的候選者,或使用“primary”屬性為true來指定某個Bean為首選Bean:

2.1)通過設置Bean定義的“autowire-candidate”屬性為false來把指定Bean後自動裝配候選者中移除:

java代碼:

<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 從自動裝配候選者中去除 -->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"
autowire-candidate="false"/>
<bean id="bean1" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byType"/>

2.2)通過設置Bean定義的“primary”屬性為true來把指定自動裝配時候選者中首選Bean:

java代碼:

<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 自動裝配候選者中的首選Bean-->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
<bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="byType"/>

具體測試請參考AutowireBeanTest類的testAutowireByType***方法。

五、“constructor”:通過設置Bean定義屬性autowire="constructor",功能和“byType”功能一樣,根據類型註入構造器參數,只是用於構造器註入方式,直接看例子吧:

java代碼:


<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 自動裝配候選者中的首選Bean-->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
<bean id="bean"
class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="constructor"/>

測試代碼如下:

java代碼:


@Test
public void testAutowireByConstructor() throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("chapter3/autowire-byConstructor.xml");
HelloApi helloApi = context.getBean("bean", HelloApi.class);
helloApi.sayHello();
}

六、autodetect:自動檢測是使用“constructor”還是“byType”自動裝配方式,已不推薦使用。如果Bean有空構造器那麽將采用“byType”自動裝配方式,否則使用“constructor”自動裝配方式。此處要把3.0的xsd替換為2.5的xsd,否則會報錯。

java代碼:

<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 自動裝配候選者中的首選Bean-->
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" primary="true"/>
<bean id="bean"
class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
autowire="autodetect"/>
</beans>


可以采用在“<beans>”標簽中通過“default-autowire”屬性指定全局的自動裝配方式,即如果default-autowire=”byName”,將對所有Bean進行根據名字進行自動裝配。

不是所有類型都能自動裝配:

不能自動裝配的數據類型:Object、基本數據類型(Date、CharSequence、Number、URI、URL、Class、int)等;
通過“<beans>”標簽default-autowire-candidates屬性指定的匹配模式,不匹配的將不能作為自動裝配的候選者,例如指定“*Service,*Dao”,將只把匹配這些模式的Bean作為候選者,而不匹配的不會作為候選者;
通過將“<bean>”標簽的autowire-candidate屬性可被設為false,從而該Bean將不會作為依賴註入的候選者。
數組、集合、字典類型的根據類型自動裝配和普通類型的自動裝配是有區別的:

數組類型、集合(Set、Collection、List)接口類型:將根據泛型獲取匹配的所有候選者並註入到數組或集合中,如“List<HelloApi> list”將選擇所有的HelloApi類型Bean並註入到list中,而對於集合的具體類型將只選擇一個候選者,“如 ArrayList<HelloApi> list”將選擇一個類型為ArrayList的Bean註入,而不是選擇所有的HelloApi類型Bean進行註入;
字典(Map)接口類型:同樣根據泛型信息註入,鍵必須為String類型的Bean名字,值根據泛型信息獲取,如“Map<String, HelloApi> map” 將選擇所有的HelloApi類型Bean並註入到map中,而對於具體字典類型如“HashMap<String, HelloApi> map”將只選擇類型為HashMap的Bean註入,而不是選擇所有的HelloApi類型Bean進行註入。

自動裝配我們已經介紹完了,自動裝配能帶給我們什麽好處呢?首先,自動裝配確實減少了配置文件的量;其次, “byType”自動裝配能在相應的Bean更改了字段類型時自動更新,即修改Bean類不需要修改配置,確實簡單了。

自動裝配也是有缺點的,最重要的缺點就是沒有了配置,在查找註入錯誤時非常麻煩,還有比如基本類型沒法完成自動裝配,所以可能經常發生一些莫名其妙的錯誤,在此我推薦大家不要使用該方式,最好是指定明確的註入方式,或者采用最新的Java5+註解註入方式。所以大家在使用自動裝配時應該考慮自己負責項目的復雜度來進行衡量是否選擇自動裝配方式。

自動裝配註入方式能和配置註入方式一同工作嗎?當然可以,大家只需記住配置註入的數據會覆蓋自動裝配註入的數據。

大家是否註意到對於采用自動裝配方式時如果沒找到合適的的Bean時什麽也不做,這樣在程序中總會莫名其妙的發生一些空指針異常,而且是在程序運行期間才能發現,有沒有辦法能在提前發現這些錯誤呢?接下來就讓我來看下依賴檢查吧。

3.3.4 依賴檢查

上一節介紹的自動裝配,很可能發生沒有匹配的Bean進行自動裝配,如果此種情況發生,只有在程序運行過程中發生了空指針異常才能發現錯誤,如果能提前發現該多好啊,這就是依賴檢查的作用。

依賴檢查:用於檢查Bean定義的屬性都註入數據了,不管是自動裝配的還是配置方式註入的都能檢查,如果沒有註入數據將報錯,從而提前發現註入錯誤,只檢查具有setter方法的屬性。

Spring3+也不推薦配置方式依賴檢查了,建議采用Java5+ @Required註解方式,測試時請將XML schema降低為2.5版本的,和自動裝配中“autodetect”配置方式的xsd一樣。

java代碼:

<?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-2.5.xsd
</beans>

依賴檢查有none、simple、object、all四種方式,接下來讓我們詳細介紹一下:

一、none:默認方式,表示不檢查;

二、objects:檢查除基本類型外的依賴對象,配置方式為:dependency-check="objects",此處我們為HelloApiDecorator添加一個String類型屬性“message”,來測試如果有簡單數據類型的屬性為null,也不報錯;

java代碼:

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 註意我們沒有註入helloApi,所以測試時會報錯 -->
<bean id="bean"
class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
dependency-check="objects">
<property name="message" value="Haha"/>
</bean>

註意由於我們沒有註入bean需要的依賴“helloApi”,所以應該拋出異常UnsatisfiedDependencyException,表示沒有發現滿足的依賴:

java代碼:

package cn.javass.spring.chapter3;
import java.io.IOException;
import org.junit.Test;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DependencyCheckTest {
@Test(expected = UnsatisfiedDependencyException.class)
public void testDependencyCheckByObject() throws IOException {
//將拋出異常
new ClassPathXmlApplicationContext("chapter3/dependency-check-object.xml");
}
}

三、simple:對基本類型進行依賴檢查,包括數組類型,其他依賴不報錯;配置方式為:dependency-check="simple",以下配置中沒有註入message屬性,所以會拋出異常:

java代碼:

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<!-- 註意我們沒有註入message屬性,所以測試時會報錯 -->
<bean id="bean"
class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
dependency-check="simple">
<property name="helloApi" ref="helloApi"/>
</bean>

四、all:對所以類型進行依賴檢查,配置方式為:dependency-check="all",如下配置方式中如果兩個屬性其中一個沒配置將報錯。

java代碼:

<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/>
<bean id="bean"
class="cn.javass.spring.chapter3.bean.HelloApiDecorator"
dependency-check="all">
<property name="helloApi" ref="helloApi"/>
<property name="message" value="Haha"/>
</bean>

依賴檢查也可以通過“<beans>”標簽中default-dependency-check屬性來指定全局依賴檢查配置。

3.3.5 方法註入

所謂方法註入其實就是通過配置方式覆蓋或攔截指定的方法,通常通過代理模式實現。Spring提供兩種方法註入:查找方法註入和方法替換註入。

因為Spring是通過CGLIB動態代理方式實現方法註入,也就是通過動態修改類的字節碼來實現的,本質就是生成需方法註入的類的子類方式實現。

在進行測試之前,我們需要確保將“com.springsource.cn.sf.cglib-2.2.0.jar”放到lib裏並添加到“Java Build Path”中的Libararies中。否則報錯,異常中包含“nested exception is java.lang.NoClassDefFoundError: cn/sf/cglib/proxy/CallbackFilter”。

傳統方式和Spring容器管理方式唯一不同的是不需要我們手動生成子類,而是通過配置方式來實現;其中如果要替換createPrinter()方法的返回值就使用查找方法註入;如果想完全替換sayHello()方法體就使用方法替換註入。 接下來讓我們看看具體實現吧。

一、查找方法註入:又稱為Lookup方法註入,用於註入方法返回結果,也就是說能通過配置方式替換方法返回結果。使用<lookup-method name="方法名" bean="bean名字"/>配置;其中name屬性指定方法名,bean屬性指定方法需返回的Bean。

方法定義格式:訪問級別必須是public或protected,保證能被子類重載,可以是抽象方法,必須有返回值,必須是無參數方法,查找方法的類和被重載的方法必須為非final:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

因為“singleton”Bean在容器中只有一個實例,而“prototype”Bean是每次獲取容器都返回一個全新的實例,所以如果“singleton”Bean在使用“prototype” Bean情況時,那麽“prototype”Bean由於是“singleton”Bean的一個字段屬性,所以獲取的這個“prototype”Bean就和它所在的“singleton”Bean具有同樣的生命周期,所以不是我們所期待的結果。因此查找方法註入就是用於解決這個問題。

1) 首先定義我們需要的類,Printer類是一個有狀態的類,counter字段記錄訪問次數:

java代碼:

package cn.javass.spring.chapter3.bean;
public class Printer {
private int counter = 0;
public void print(String type) {
System.out.println(type + " printer: " + counter++);
}
}

HelloImpl5類用於打印歡迎信息,其中包括setter註入和方法註入,此處特別需要註意的是該類是抽象的,充分說明了需要容器對其進行子類化處理,還定義了一個抽象方法createPrototypePrinter用於創建“prototype”Bean,createSingletonPrinter方法用於創建“singleton”Bean,此處註意方法會被Spring攔截,不會執行方法體代碼:

java代碼:

package cn.javass.spring.chapter3;
import cn.javass.spring.chapter2.helloworld.HelloApi;
import cn.javass.spring.chapter3.bean.Printer;
public abstract class HelloImpl5 implements HelloApi {
private Printer printer;
public void sayHello() {
printer.print("setter");
createPrototypePrinter().print("prototype");
createSingletonPrinter().print("singleton");
}
public abstract Printer createPrototypePrinter();
public Printer createSingletonPrinter() {
System.out.println("該方法不會被執行,如果輸出就錯了");
return new Printer();
}
public void setPrinter(Printer printer) {
this.printer = printer;
}
}

2) 開始配置了,配置文件在(resources/chapter3/lookupMethodInject.xml),其中“prototypePrinter”是“prototype”Printer,“singletonPrinter”是“singleton”Printer,“helloApi1”是“singleton”Bean,而“helloApi2”註入了“prototype”Bean:

java代碼:

<bean id="prototypePrinter"
class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/>
<bean id="singletonPrinter"
class="cn.javass.spring.chapter3.bean.Printer" scope="singleton"/>
<bean id="helloApi1" class="cn.javass.spring.chapter3.HelloImpl5" scope="singleton">
<property name="printer" ref="prototypePrinter"/>
<lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>
<lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>
</bean>
<bean id="helloApi2" class="cn.javass.spring.chapter3.HelloImpl5" scope="prototype">
<property name="printer" ref="prototypePrinter"/>
<lookup-method name="createPrototypePrinter" bean="prototypePrinter"/>
<lookup-method name="createSingletonPrinter" bean="singletonPrinter"/>
</bean>

3)測試代碼如下:

java代碼:

package cn.javass.spring.chapter3;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.javass.spring.chapter2.helloworld.HelloApi;
public class MethodInjectTest {
@Test
public void testLookup() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("chapter3/lookupMethodInject.xml");
System.out.println("=======singleton sayHello======");
HelloApi helloApi1 = context.getBean("helloApi1", HelloApi.class);
helloApi1.sayHello();
helloApi1 = context.getBean("helloApi1", HelloApi.class);
helloApi1.sayHello();
System.out.println("=======prototype sayHello======");
HelloApi helloApi2 = context.getBean("helloApi2", HelloApi.class);
helloApi2.sayHello();
helloApi2 = context.getBean("helloApi2", HelloApi.class);
helloApi2.sayHello();
}}

其中“helloApi1”測試中,其輸出結果如下:

java代碼:

=======singleton sayHello======
setter printer: 0
prototype printer: 0
singleton printer: 0
setter printer: 1
prototype printer: 0
singleton printer: 1

首先“helloApi1”是“singleton”,通過setter註入的“printer”是“prototypePrinter”,所以它應該輸出“setter printer:0”和“setter printer:1”;而“createPrototypePrinter”方法註入了“prototypePrinter”,所以應該輸出兩次“prototype printer:0”;而“createSingletonPrinter”註入了“singletonPrinter”,所以應該輸出“singleton printer:0”和“singleton printer:1”。

而“helloApi2”測試中,其輸出結果如下:

java代碼:


=======prototype sayHello======
setter printer: 0
prototype printer: 0
singleton printer: 2
setter printer: 0
prototype printer: 0
singleton printer: 3

首先“helloApi2”是“prototype”,通過setter註入的“printer”是“prototypePrinter”,所以它應該輸出兩次“setter printer:0”;而“createPrototypePrinter”方法註入了“prototypePrinter”,所以應該輸出兩次“prototype printer:0”;而“createSingletonPrinter”註入了“singletonPrinter”,所以應該輸出“singleton printer:2”和“singleton printer:3”。

大家是否註意到“createSingletonPrinter”方法應該輸出“該方法不會被執行,如果輸出就錯了”,而實際是沒輸出的,這說明Spring攔截了該方法並使用註入的Bean替換了返回結果。

方法註入主要用於處理“singleton”作用域的Bean需要其他作用域的Bean時,采用Spring查找方法註入方式無需修改任何代碼即能獲取需要的其他作用域的Bean。

二、替換方法註入:也叫“MethodReplacer”註入,和查找註入方法不一樣的是,他主要用來替換方法體。通過首先定義一個MethodReplacer接口實現,然後如下配置來實現:

java代碼:

<replaced-method name="方法名" replacer="MethodReplacer實現">
<arg-type>參數類型</arg-type>
</replaced-method>”

1)首先定義MethodReplacer實現,完全替換掉被替換方法的方法體及返回值,其中reimplement方法重定義方法 功能,參數obj為被替換方法的對象,method為被替換方法,args為方法參數;最需要註意的是不能再 通過“method.invoke(obj, new String[]{"hehe"});” 反射形式再去調用原來方法,這樣會產生循環調用;如果返回值類型為Void,請在實現中返回null:

java代碼:

package cn.javass.spring.chapter3.bean;
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.MethodReplacer;
public class PrinterReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("Print Replacer");
//註意此處不能再通過反射調用了,否則會產生循環調用,知道內存溢出
//method.invoke(obj, new String[]{"hehe"});
return null;
}
}

2)配置如下,首先定義MethodReplacer實現,使用< replaced-method >標簽來指定要進行替換方法,屬性name指定替換的方法名字,replacer指定該方法的重新實現者,子標簽< arg-type >用來指定原來方法參數的類型,必須指定否則找不到原方法:

java代碼:

<bean id="replacer" class="cn.javass.spring.chapter3.bean.PrinterReplacer"/>
<bean id="printer" class="cn.javass.spring.chapter3.bean.Printer">
<replaced-method name="print" replacer="replacer">
<arg-type>java.lang.String</arg-type>
</replaced-method>
</bean>

3)測試代碼將輸出“Print Replacer ”,說明方法體確實被替換了:

java代碼:

@Test
public void testMethodReplacer() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("chapter3/methodReplacerInject.xml");
Printer printer = context.getBean("printer", Printer.class);
printer.print("我將被替換");
}

Spring-更多DI的知識