?
このドキュメントでは、 php中國(guó)語(yǔ)ネットマニュアル リリース
典型的企業(yè)應(yīng)用不會(huì)只由單一的對(duì)象(或Spring的術(shù)語(yǔ)bean)組成。毫無疑問,即使最簡(jiǎn)單的系統(tǒng)也需要多個(gè)對(duì)象共同來展示給用戶一個(gè)整體的應(yīng)用。接下來的的內(nèi)容除了闡述如何單獨(dú)定義一系列bean外,還將描述如何讓這些bean對(duì)象一起協(xié)同工作來實(shí)現(xiàn)一個(gè)完整的真實(shí)應(yīng)用。
依賴注入(DI)背后的基本原理是對(duì)象之間的依賴關(guān)系(即一起工作的其它對(duì)象)只會(huì)通過以下幾種方式來實(shí)現(xiàn):構(gòu)造器的參數(shù)、工廠方法的參數(shù),或給由構(gòu)造函數(shù)或者工廠方法創(chuàng)建的對(duì)象設(shè)置屬性。因此,容器的工作就是創(chuàng)建bean時(shí)注入那些依賴關(guān)系。相對(duì)于由bean自己來控制其實(shí)例化、直接在構(gòu)造器中指定依賴關(guān)系或者類似服務(wù)定位器(Service Locator)模式這3種自主控制依賴關(guān)系注入的方法來說,控制從根本上發(fā)生了倒轉(zhuǎn),這也正是控制反轉(zhuǎn)(Inversion of Control, IoC) 名字的由來。
應(yīng)用DI原則后,代碼將更加清晰。而且當(dāng)bean自己不再擔(dān)心對(duì)象之間的依賴關(guān)系(甚至不知道依賴的定義指定地方和依賴的實(shí)際類)之后,實(shí)現(xiàn)更高層次的松耦合將易如反掌。DI主要有兩種注入方式,即Setter注入和構(gòu)造器注入
。基于構(gòu)造器的DI通過調(diào)用帶參數(shù)的構(gòu)造器來實(shí)現(xiàn),每個(gè)參數(shù)代表著一個(gè)依賴。此外,還可通過給stattic
工廠方法傳參數(shù)來構(gòu)造bean。接下來的介紹將認(rèn)為給構(gòu)造器傳參與給靜態(tài)
工廠方法傳參是類似的。下面展示了只能使用構(gòu)造器參數(shù)來注入依賴關(guān)系的例子。請(qǐng)注意,這個(gè)類并沒有什么特別之處。
public class SimpleMovieLister { // theSimpleMovieLister
has a dependency on aMovieFinder
private MovieFinder movieFinder; // a constructor so that the Spring container can 'inject' aMovieFinder
public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually 'uses' the injectedMovieFinder
is omitted... }
構(gòu)造器參數(shù)解析根據(jù)參數(shù)類型進(jìn)行匹配,如果bean的構(gòu)造器參數(shù)類型定義非常明確,那么在bean被實(shí)例化的時(shí)候,bean定義中構(gòu)造器參數(shù)的定義順序就是這些參數(shù)的順序,依次進(jìn)行匹配,比如下面的代碼
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
上述例子中由于構(gòu)造參數(shù)非常明確(這里我們假定 Bar
和 Baz
之間不存在繼承關(guān)系)。因此下面的配置即使沒有明確指定構(gòu)造參數(shù)順序(和類型),也會(huì)工作的很好。
<beans> <bean name="foo" class="x.y.Foo"> <constructor-arg> <bean class="x.y.Bar"/> </constructor-arg> <constructor-arg> <bean class="x.y.Baz"/> </constructor-arg> </bean> </beans>
我們?cè)賮砜戳硪粋€(gè)bean,該bean的構(gòu)造參數(shù)類型已知,匹配也沒有問題(跟前面的例子一樣)。但是當(dāng)使用簡(jiǎn)單類型時(shí),比如<value>true<value>
,Spring將無法知道該值的類型。不借助其他幫助,他將無法僅僅根據(jù)參數(shù)類型進(jìn)行匹配,比如下面的這個(gè)例子:
package examples; public class ExampleBean { // No. of years to the calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
針對(duì)上面的場(chǎng)景可以通過使用'type'
屬性來顯式指定那些簡(jiǎn)單類型的構(gòu)造參數(shù)的類型,比如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
我們還可以通過index
屬性來顯式指定構(gòu)造參數(shù)的索引,比如下面的例子:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
通過使用索引屬性不但可以解決多個(gè)簡(jiǎn)單屬性的混淆問題,還可以解決有可能有相同類型的2個(gè)構(gòu)造參數(shù)的混淆問題了,注意index是從0開始。
通過調(diào)用無參構(gòu)造器或無參static
工廠方法實(shí)例化bean之后,調(diào)用該bean的setter方法,即可實(shí)現(xiàn)基于setter的DI。
下面的例子將展示只使用setter注入依賴。注意,這個(gè)類并沒有什么特別之處,它就是普通的Java類。
public class SimpleMovieLister { // theSimpleMovieLister
has a dependency on theMovieFinder
private MovieFinder movieFinder; // a setter method so that the Spring container can 'inject' aMovieFinder
public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually 'uses' the injectedMovieFinder
is omitted... }
BeanFactory
對(duì)于它所管理的bean提供兩種注入依賴方式(實(shí)際上它也支持同時(shí)使用構(gòu)造器注入和Setter方式注入依賴)。需要注入的依賴將保存在BeanDefinition
中,它能根據(jù)指定的PropertyEditor
實(shí)現(xiàn)將屬性從一種格式轉(zhuǎn)換成另外一種格式。然而,大部份的Spring用戶并不需要直接以編程的方式處理這些類,而是采用XML的方式來進(jìn)行定義,在內(nèi)部這些定義將被轉(zhuǎn)換成相應(yīng)類的實(shí)例,并最終得到一個(gè)Spring IoC容器實(shí)例。
處理bean依賴關(guān)系通常按以下步驟進(jìn)行:
根據(jù)定義bean的配置(文件)創(chuàng)建并初始化BeanFactory
實(shí)例(大部份的Spring用戶使用支持XML格式配置文件的BeanFactory
或ApplicationContext
實(shí)現(xiàn))。
每個(gè)bean的依賴將以屬性、構(gòu)造器參數(shù)、或靜態(tài)工廠方法參數(shù)的形式出現(xiàn)。當(dāng)這些bean被實(shí)際創(chuàng)建時(shí),這些依賴也將會(huì)提供給該bean。
每個(gè)屬性或構(gòu)造器參數(shù)既可以是一個(gè)實(shí)際的值,也可以是對(duì)該容器中另一個(gè)bean的引用。
每個(gè)指定的屬性或構(gòu)造器參數(shù)值必須能夠被轉(zhuǎn)換成特定的格式或構(gòu)造參數(shù)所需的類型。默認(rèn)情況下,Spring會(huì)以String類型提供值轉(zhuǎn)換成各種內(nèi)置類型,比如int
、long
、String
、boolean
等。
Spring會(huì)在容器被創(chuàng)建時(shí)驗(yàn)證容器中每個(gè)bean的配置,包括驗(yàn)證那些bean所引用的屬性是否指向一個(gè)有效的bean(即被引用的bean也在容器中被定義)。然而,在bean被實(shí)際創(chuàng)建之前,bean的屬性并不會(huì)被設(shè)置。對(duì)于那些singleton類型和被設(shè)置為提前實(shí)例化的bean(比如ApplicationContext
中的singleton bean)而言,bean實(shí)例將與容器同時(shí)被創(chuàng)建。而另外一些bean則會(huì)在需要的時(shí)候被創(chuàng)建,伴隨著bean被實(shí)際創(chuàng)建,作為該bean的依賴bean以及依賴bean的依賴bean(依此類推)也將被創(chuàng)建和分配。
通常情況下,你可以信賴Spring,它會(huì)在容器加載時(shí)發(fā)現(xiàn)配置錯(cuò)誤(比如對(duì)無效bean的引用以及循環(huán)依賴)。Spring會(huì)在bean創(chuàng)建時(shí)才去設(shè)置屬性和依賴關(guān)系(只在需要時(shí)創(chuàng)建所依賴的其他對(duì)象)。這意味著即使Spring容器被正確加載,當(dāng)獲取一個(gè)bean實(shí)例時(shí),如果在創(chuàng)建bean或者設(shè)置依賴時(shí)出現(xiàn)問題,仍然會(huì)拋出一個(gè)異常。因缺少或設(shè)置了一個(gè)無效屬性而導(dǎo)致拋出一個(gè)異常的情況的確是存在的。因?yàn)橐恍┡渲脝栴}而導(dǎo)致潛在的可見性被延遲,所以在默認(rèn)情況下,ApplicationContext
實(shí)現(xiàn)中的bean采用提前實(shí)例化的singleton模式。在實(shí)際需要之前創(chuàng)建這些bean將帶來時(shí)間與內(nèi)存的開銷。而這樣做的好處就是ApplicationContext
被加載的時(shí)候可以盡早的發(fā)現(xiàn)一些配置的問題。不過用戶也可以根據(jù)需要采用延遲實(shí)例化來替代默認(rèn)的singleton模式。
如果撇開循環(huán)依賴不談,當(dāng)協(xié)作bean被注入到依賴bean時(shí),協(xié)作bean必須在依賴bean之前完全配置好。例如bean A對(duì)bean B存在依賴關(guān)系,那么Spring IoC容器在調(diào)用bean A的setter方法之前,bean B必須被完全配置,這里所謂完全配置的意思就是bean將被實(shí)例化(如果不是采用提前實(shí)例化的singleton模式),相關(guān)的依賴也將被設(shè)置好,而且所有相關(guān)的lifecycle方法(如IntializingBean的init方法以及callback方法)也將被調(diào)用。
首先是一個(gè)用XML格式定義的Setter DI例子。相關(guān)的XML配置如下:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested <ref/>
element -->
<property name="beanOne"><ref bean="anotherExampleBean"/></property>
<!-- setter injection using the neater 'ref' attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
正如你所看到的,bean類中的setter方法與xml文件中配置的屬性是一一對(duì)應(yīng)的。接著是構(gòu)造器注入的例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested <ref/>
element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater 'ref' attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } }
如你所見,在xml bean定義中指定的構(gòu)造器參數(shù)將被用來作為傳遞給類ExampleBean
構(gòu)造器的參數(shù)。
現(xiàn)在來研究一個(gè)替代構(gòu)造器的方法,采用static
工廠方法返回對(duì)象實(shí)例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { // a private constructor private ExampleBean(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }
請(qǐng)注意,傳給static
工廠方法的參數(shù)由constructor-arg
元素提供,這與使用構(gòu)造器注入時(shí)完全一樣。而且,重要的是,工廠方法所返回的實(shí)例的類型并不一定要與包含static
工廠方法的類類型一致。盡管在此例子中它的確是這樣。非靜態(tài)的實(shí)例工廠方法與此相同(除了使用factory-bean
屬性替代class
屬性外),因而不在此細(xì)述。
正如前面章節(jié)所提到的,bean的屬性及構(gòu)造器參數(shù)既可以引用容器中的其他bean,也可以是內(nèi)聯(lián)(inline)bean。在spring的XML配置中使用<property/>
和<constructor-arg/>
元素定義。
<value/>
元素通過人可以理解的字符串來指定屬性或構(gòu)造器參數(shù)的值。正如前面所提到的,JavaBean PropertyEditor
將用于把字符串從java.lang.String
類型轉(zhuǎn)化為實(shí)際的屬性或參數(shù)類型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String)
call -->
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/mydb</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>masterkaoli</value>
</property>
</bean>
<property/>
和<constructor-arg/>
元素中也可以使用'value'
屬性,這樣會(huì)使我們的配置更簡(jiǎn)潔,比如下面的配置:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String)
call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
Spring團(tuán)隊(duì)更傾向采用屬性方式(使用<value/>
元素)來定義value值。當(dāng)然我們也可以按照下面這種方式配置一個(gè)java.util.Properties
實(shí)例:
<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>
看到什么了嗎?如果采用上面的配置,Spring容器將使用JavaBean PropertyEditor
把<value/>
元素中的文本轉(zhuǎn)換為一個(gè)java.util.Properties
實(shí)例。由于這種做法的簡(jiǎn)單,因此Spring團(tuán)隊(duì)在很多地方也會(huì)采用內(nèi)嵌的<value/>
元素來代替value
屬性。
idref
元素用來將容器內(nèi)其它bean的id傳給<constructor-arg/>
或 <property/>
元素,同時(shí)提供錯(cuò)誤驗(yàn)證功能。
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
上述bean定義片段完全地等同于(在運(yùn)行時(shí))以下的片段:
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean" /> </bean>
第一種形式比第二種更可取的主要原因是,使用idref
標(biāo)記允許容器在部署時(shí) 驗(yàn)證所被引用的bean是否存在。而第二種方式中,傳給client
bean的targetName
屬性值并沒有被驗(yàn)證。任何的輸入錯(cuò)誤僅在client
bean實(shí)際實(shí)例化時(shí)才會(huì)被發(fā)現(xiàn)(可能伴隨著致命的錯(cuò)誤)。如果client
bean 是prototype類型的bean,則此輸入錯(cuò)誤(及由此導(dǎo)致的異常)可能在容器部署很久以后才會(huì)被發(fā)現(xiàn)。
此外,如果被引用的bean在同一XML文件內(nèi),且bean名字就是bean id,那么可以使用local
屬性,此屬性允許XML解析器在解析XML文件時(shí)對(duì)引用的bean進(jìn)行驗(yàn)證。
<property name="targetName">
<!-- a bean with an id of 'theTargetBean
' must exist; otherwise an XML exception will be thrown -->
<idref local="theTargetBean"/>
</property>
上面的例子中,與在ProxyFactoryBean
bean定義中使用<idref/>元素指定AOP interceptor的相同之處在于:如果使用<idref/>元素指定攔截器名字,可以避免因一時(shí)疏忽導(dǎo)致的攔截器ID拼寫錯(cuò)誤。
在<constructor-arg/>
或<property/>
元素內(nèi)部還可以使用ref
元素。該元素用來將bean中指定屬性的值設(shè)置為對(duì)容器中的另外一個(gè)bean的引用。如前所述,該引用bean將被作為依賴注入,而且在注入之前會(huì)被初始化(如果是singleton bean則已被容器初始化)。盡管都是對(duì)另外一個(gè)對(duì)象的引用,但是通過id/name指向另外一個(gè)對(duì)象卻有三種不同的形式,不同的形式將決定如何處理作用域及驗(yàn)證。
第一種形式也是最常見的形式是通過使用<ref/>
標(biāo)記指定bean
屬性的目標(biāo)bean,通過該標(biāo)簽可以引用同一容器或父容器內(nèi)的任何bean(無論是否在同一XML文件中)。XML 'bean
'元素的值既可以是指定bean的id
值也可以是其name
值。
<ref bean="someBean"/>
第二種形式是使用ref的local
屬性指定目標(biāo)bean,它可以利用XML解析器來驗(yàn)證所引用的bean是否存在同一文件中。local
屬性值必須是目標(biāo)bean的id屬性值。如果在同一配置文件中沒有找到引用的bean,XML解析器將拋出一個(gè)例外。如果目標(biāo)bean是在同一文件內(nèi),使用local方式就是最好的選擇(為了盡早地發(fā)現(xiàn)錯(cuò)誤)。
<ref local="someBean"/>
第三種方式是通過使用ref的parent
屬性來引用當(dāng)前容器的父容器中的bean。parent
屬性值既可以是目標(biāo)bean的id
值,也可以是name
屬性值。而且目標(biāo)bean必須在當(dāng)前容器的父容器中。使用parent屬性的主要用途是為了用某個(gè)與父容器中的bean同名的代理來包裝父容器中的一個(gè)bean(例如,子上下文中的一個(gè)bean定義覆蓋了他的父bean)。
<!-- in the parent context --> <bean id="accountService" class="com.foo.SimpleAccountService"> <!-- insert dependencies as required as here --> </bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of the 'parent'
bean
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <-- notice how we refer to the parent bean
</property>
<!-- insert other configuration and dependencies as required as here -->
</bean>
所謂的內(nèi)部bean(inner bean)是指在一個(gè)bean的<property/>
或 <constructor-arg/>
元素中使用<bean/>
元素定義的bean。內(nèi)部bean定義不需要有id或name屬性,即使指定id 或 name屬性值也將會(huì)被容器忽略。
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
注意:內(nèi)部bean中的scope
標(biāo)記及id
或name
屬性將被忽略。內(nèi)部bean總是匿名的且它們總是prototype模式的。同時(shí)將內(nèi)部bean注入到包含該內(nèi)部bean之外的bean是不可能的。
通過<list/>
、<set/>
、<map/>
及<props/>
元素可以定義和設(shè)置與Java Collection
類型對(duì)應(yīng)List
、Set
、Map
及Properties
的值。
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties
) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <!-- results in a setSomeList(java.util.List
) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map
) call --> <property name="someMap"> <map> <entry> <key> <value>an entry</value> </key> <value>just some string</value> </entry> <entry> <key> <value>a ref</value> </key> <ref bean="myDataSource" /> </entry> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
注意:map的key或value值,或set的value值還可以是以下元素:
bean | ref | idref | list | set | map | props | value | null
從2.0開始,Spring IoC容器將支持集合的合并。這樣我們可以定義parent-style和child-style的<list/>
、<map/>
、<set/>
或<props/>
元素,子集合的值從其父集合繼承和覆蓋而來;也就是說,父子集合元素合并后的值就是子集合中的最終結(jié)果,而且子集合中的元素值將覆蓋父集全中對(duì)應(yīng)的值。
請(qǐng)注意,關(guān)于合并的這部分利用了parent-child bean機(jī)制。此內(nèi)容將在后面介紹,不熟悉父子bean的讀者可參見第?3.6?節(jié) “bean定義的繼承”。
Find below an example of the collection merging feature:
下面的例子展示了集合合并特性:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the *child* collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
在上面的例子中,child
bean的adminEmails
屬性的<props/>
元素上使用了merge=true
屬性。當(dāng)child
bean被容器實(shí)際解析及實(shí)例化時(shí),其 adminEmails
將與父集合的adminEmails
屬性進(jìn)行合并。
administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk
注意到這里子bean的Properties
集合將從父<props/>
繼承所有屬性元素。同時(shí)子bean的support
值將覆蓋父集合的相應(yīng)值。
對(duì)于<list/>
、<map/>
及<set/>
集合類型的合并處理都基本類似,在某個(gè)方面<list/>
元素比較特殊,這涉及到List
集合本身的語(yǔ)義學(xué),就拿維護(hù)一個(gè)有序
集合中的值來說,父bean的列表內(nèi)容將排在子bean列表內(nèi)容的前面。對(duì)于Map
、Set
及Properties
集合類型沒有順序的概念,因此作為相關(guān)的Map
、Set
及Properties
實(shí)現(xiàn)基礎(chǔ)的集合類型在容器內(nèi)部沒有排序的語(yǔ)義。
最后需要指出的一點(diǎn)就是,合并功能僅在Spring 2.0(及隨后的版本中)可用。不同的集合類型是不能合并(如map
和 list
是不能合并的),否則將會(huì)拋出相應(yīng)的Exception
。merge
屬性必須在繼承的子bean中定義,而在父bean的集合屬性上指定的merge
屬性將被忽略。
你若有幸在使用Java5 或Java 6,那么你可以使用強(qiáng)類型集合(支持泛型)。比如,聲明一個(gè)只能包含String
類型元素的Collection
。假若使用Spring來給bean注入強(qiáng)類型的Collection
,那就可以利用Spring的類型轉(zhuǎn)換能,當(dāng)向強(qiáng)類型Collection
中添加元素前,這些元素將被轉(zhuǎn)換。
public class Foo { private Map<String, Float> accounts; public void setAccounts(Map<String, Float> accounts) { this.accounts = accounts; } }
<beans> <bean id="foo" class="x.y.Foo"> <property name="accounts"> <map> <entry key="one" value="9.99"/> <entry key="two" value="2.75"/> <entry key="six" value="3.99"/> </map> </property> </bean> </beans>
在foo
bean的accounts
屬性被注入之前,通過反射,利用強(qiáng)類型Map<String, Float>
的泛型信息,Spring的底層類型轉(zhuǎn)換機(jī)制將會(huì)把各種value元素值轉(zhuǎn)換為Float
類型,因此字符串9.99、2.75
及3.99
就會(huì)被轉(zhuǎn)換為實(shí)際的Float
類型。
<null/>
用于處理null
值。Spring會(huì)把屬性的空參數(shù)當(dāng)作空字符串
處理。以下的xml片斷將email屬性設(shè)為空字符串
。
<bean class="ExampleBean"> <property name="email"><value/></property> </bean>
這等同于Java代碼:
exampleBean.setEmail("")
。
而null
值則可以使用<null>
元素可用來表示。例如:
<bean class="ExampleBean"> <property name="email"><null/></property> </bean>
上述的配置等同于Java代碼:exampleBean.setEmail(null)
。
配置元數(shù)據(jù)冗長(zhǎng)不是什么好事情,因此我們將通過下面的方式來對(duì)配置進(jìn)行“減肥”,第一種做法就是通過使用<property/>
來定義值和對(duì)其他bean的引用,另一個(gè)做法就是采用不同的屬性定義格式。
<property/>
、<constructor-arg/>
及<entry/>
元素都支持value
屬性(attribute),它可以用來替代內(nèi)嵌的<value/>
元素。因而,以下的代碼:
<property name="myProperty"> <value>hello</value> </property>
<constructor-arg> <value>hello</value> </constructor-arg>
<entry key="myKey"> <value>hello</value> </entry>
等同于:
<property name="myProperty" value="hello"/>
<constructor-arg value="hello"/>
<entry key="myKey" value="hello"/>
The <property/>
and
<constructor-arg/>
elements support a
similar shortcut 'ref'
attribute which may be
used instead of a full nested <ref/>
element. Therefore, the following:
<property/>
和<constructor-arg/>
支持類似ref
的簡(jiǎn)寫屬性,它可用來替代整個(gè)內(nèi)嵌的<ref/>
元素。因而,以下的代碼:
<property name="myProperty"> <ref bean="myBean"> </property>
<constructor-arg> <ref bean="myBean"> </constructor-arg>
等同于:
<property name="myProperty" ref="myBean"/>
<constructor-arg ref="myBean"/>
注意,盡管存在等同于<ref bean="xxx">
元素的簡(jiǎn)寫形式,但并沒有<ref local="xxx"
>的簡(jiǎn)寫形式,為了對(duì)當(dāng)前xml中bean的引用,你只能使用完整的形式。
最后,map中entry元素的簡(jiǎn)寫形式為key
/key-ref
和 value
/value-ref
屬性,因而,以下的代碼:
<entry> <key> <ref bean="myKeyBean" /> </key> <ref bean="myValueBean" /> </entry>
等同于:
<entry key-ref="myKeyBean" value-ref="myValueBean"/>
再次強(qiáng)調(diào),只有<ref bean="xxx">
元素的簡(jiǎn)寫形式,沒有<ref local="xxx"
>的簡(jiǎn)寫形式。
給XML配置文件"減肥"的另一個(gè)選擇就是使用p名稱空間,從 2.0開始,Spring支持使用名稱空間的可擴(kuò)展配置格式。這些名稱空間都是基于一種XML Schema定義。事實(shí)上,我們所看到的所有bean
的配置格式都是基于一個(gè) XML Schema文檔。
特定的名稱空間并不需要定義在一個(gè)XSD文件中,它只在Spring內(nèi)核中存在。我們所說的p名稱空間就是這樣,它不需要一個(gè)schema定義,與我們前面采用<property/>
元素定義bean的屬性不同的是,當(dāng)我們采用了p名稱空間,我們就可以在bean
元素中使用屬性(attribute)來描述bean的property值。
下面的兩段XML配置文件中都是用來定義同一個(gè)bean:一個(gè)采用的是標(biāo)準(zhǔn)的XML格式,一個(gè)是采用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-2.5.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="foo@bar.com/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/> </beans>
從上面的bean定義中,我們采用p名稱空間的方式包含了一個(gè)叫email的屬性,而Spring會(huì)知道我們的bean包含了一個(gè)屬性(property)定義。我們前面說了,p名稱空間是不需要schema定義的,因此屬性(attribute)的名字就是你bean的property的名字。
This next example includes two more bean definitions that both have a reference to another bean:
下面的例子包含了兩個(gè)bean定義,它們都引用了另一個(gè)bean
<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-2.5.xsd"> <bean name="john-classic" class="com.example.Person"> <property name="name" value="John Doe"/> <property name="spouse" ref="jane"/> </bean> <bean name="john-modern" class="com.example.Person" p:name="John Doe" p:spouse-ref="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean> </beans>
As you can see, this example doesn't only include a property
value using the p-namespace, but also uses a special format to
declare property references. Whereas the first bean definition uses
<property name="spouse" ref="jane"/>
to
create a reference from bean john
to bean
jane
, the second bean definition uses
p:spouse-ref="jane"
as an attribute to do the
exact same thing. In this case 'spouse
' is the
property name whereas the '-ref
' part indicates
that this is not a straight value but rather a reference to another
bean.
上面的例子不僅使用p名稱空間包含了一個(gè)屬性(property)值,而且使用了一個(gè)特殊的格式聲明了一個(gè)屬性引用。在第一個(gè)bean定義中使用了<property name="spouse" ref="jane"/>
來建立beanjohn
到beanjane
的引用,而第二個(gè)bean定義則采用p:spouse-ref="jane"
屬性(attribute)的方式達(dá)到了同樣的目的。在這個(gè)例子中,"spouse
"是屬性(property)名,而"-ref
“則用來說明該屬性不是一個(gè)具體的值而是對(duì)另外一個(gè)bean的引用。
需要注意的是,p名稱空間沒有標(biāo)準(zhǔn)的XML格式定義靈活,比如說,bean的屬性名是以Ref
結(jié)尾的,那么采用p名稱空間定義就會(huì)導(dǎo)致沖突,而采用標(biāo)準(zhǔn)的XML格式定義則不會(huì)出現(xiàn)這種問題。這里我們提醒大家在項(xiàng)目中還是仔細(xì)權(quán)衡來決定到底采用那種方式,同時(shí)也可以在團(tuán)隊(duì)成員都理解不同的定義方式的基礎(chǔ)上,在項(xiàng)目中根據(jù)需要同時(shí)選擇三種定義方式。
當(dāng)設(shè)置bean的組合屬性時(shí),除了最后一個(gè)屬性外,只要其他屬性值不為null
,組合或嵌套屬性名是完全合法的。例如,下面bean的定義:
<bean id="foo" class="foo.Bar"> <property name="fred.bob.sammy" value="123" /> </bean>
foo
bean有個(gè)fred
屬性,此屬性有個(gè)bob
屬性,而bob
屬性又有個(gè)sammy
屬性,最后把sammy
屬性設(shè)置為123
。為了讓此定義能工作, foo
的fred
屬性及fred
的bob
屬性在bean被構(gòu)造后都必須非空,否則將拋出NullPointerException
異常。
多數(shù)情況下,一個(gè)bean對(duì)另一個(gè)bean的依賴最簡(jiǎn)單的做法就是將一個(gè)bean設(shè)置為另外一個(gè)bean的屬性。在xml配置文件中最常見的就是使用
<ref/>
元素。在少數(shù)情況下,有時(shí)候bean之間的依賴關(guān)系并不是那么的直接(例如,當(dāng)類中的靜態(tài)塊的初始化被時(shí),如數(shù)據(jù)庫(kù)驅(qū)動(dòng)的注冊(cè))。depends-on
屬性可以用于當(dāng)前bean初始化之前顯式地強(qiáng)制一個(gè)或多個(gè)bean被初始化。下面的例子中使用了depends-on
屬性來指定一個(gè)bean的依賴。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />
若需要表達(dá)對(duì)多個(gè)bean的依賴,可以在'depends-on'
中將指定的多個(gè)bean名字用分隔符進(jìn)行分隔,分隔符可以是逗號(hào)、空格及分號(hào)等。下面的例子中使用了'depends-on'
來表達(dá)對(duì)多個(gè)bean的依賴。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </bean> <bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
“depends-on
”屬性不僅用來指定初始化時(shí)的依賴,同時(shí)也用來指定相應(yīng)的銷毀時(shí)的依賴(該依賴只針對(duì)singletonbean)。depends-on
屬性中指定的依賴bean會(huì)在相關(guān)bean銷毀之前被銷毀,從而可以讓用戶控制銷毀順序。
ApplicationContext
實(shí)現(xiàn)的默認(rèn)行為就是在啟動(dòng)時(shí)將所有singleton
bean提前進(jìn)行實(shí)例化。提前實(shí)例化意味著作為初始化過程的一部分,ApplicationContext
實(shí)例會(huì)創(chuàng)建并配置所有的singleton bean。通常情況下這是件好事,因?yàn)檫@樣在配置中的任何錯(cuò)誤就會(huì)即刻被發(fā)現(xiàn)(否則的話可能要花幾個(gè)小時(shí)甚至幾天)。
有時(shí)候這種默認(rèn)處理可能并不是你想要的。如果你不想讓一個(gè)singleton bean在ApplicationContext
初始化時(shí)被提前實(shí)例化,那么可以將bean設(shè)置為延遲實(shí)例化。一個(gè)延遲初始化bean將告訴IoC 容器是在啟動(dòng)時(shí)還是在第一次被用到時(shí)實(shí)例化。
在XML配置文件中,延遲初始化將通過<bean/>
元素中的lazy-init
屬性來進(jìn)行控制。例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
當(dāng)ApplicationContext
實(shí)現(xiàn)加載上述配置時(shí),設(shè)置為lazy
的bean將不會(huì)在ApplicationContext
啟動(dòng)時(shí)提前被實(shí)例化,而not.lazy
卻會(huì)被提前實(shí)例化。
需要說明的是,如果一個(gè)bean被設(shè)置為延遲初始化,而另一個(gè)非延遲初始化的singleton bean依賴于它,那么當(dāng)ApplicationContext
提前實(shí)例化singleton bean時(shí),它必須也確保所有上述singleton 依賴bean也被預(yù)先初始化,當(dāng)然也包括設(shè)置為延遲實(shí)例化的bean。因此,如果Ioc容器在啟動(dòng)的時(shí)候創(chuàng)建了那些設(shè)置為延遲實(shí)例化的bean的實(shí)例,你也不要覺得奇怪,因?yàn)槟切┭舆t初始化的bean可能在配置的某個(gè)地方被注入到了一個(gè)非延遲初始化singleton bean里面。
在容器層次上通過在<beans/>
元素上使用'default-lazy-init'
屬性來控制延遲初始化也是可能的。如下面的配置:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
Spring IoC容器可以自動(dòng)裝配(autowire)相互協(xié)作bean之間的關(guān)聯(lián)關(guān)系。因此,如果可能的話,可以自動(dòng)讓Spring通過檢查BeanFactory
中的內(nèi)容,來替我們指定bean的協(xié)作者(其他被依賴的bean)。autowire一共有五種類型。由于autowire可以針對(duì)單個(gè)bean進(jìn)行設(shè)置,因此可以讓有些bean使用autowire,有些bean不采用。autowire的方便之處在減少或者消除屬性或構(gòu)造器參數(shù)的設(shè)置,這樣可以給我們的配置文件減減肥![2] 在xml配置文件中,可以在<bean/>
元素中使用autowire屬性指定:
表?3.2.?Autowiring modes
模式 | 說明 |
---|---|
no | ? |
byName |
根據(jù)屬性名自動(dòng)裝配。此選項(xiàng)將檢查容器并根據(jù)名字查找與屬性完全一致的bean,并將其與屬性自動(dòng)裝配。例如,在bean定義中將autowire設(shè)置為by name,而該bean包含master屬性(同時(shí)提供setMaster(..)方法),Spring就會(huì)查找名為 |
byType |
如果容器中存在一個(gè)與指定屬性類型相同的bean,那么將與該屬性自動(dòng)裝配。如果存在多個(gè)該類型的bean,那么將會(huì)拋出異常,并指出不能使用byType方式進(jìn)行自動(dòng)裝配。若沒有找到相匹配的bean,則什么事都不發(fā)生,屬性也不會(huì)被設(shè)置。如果你不希望這樣,那么可以通過設(shè)置 |
constructor |
與byType的方式類似,不同之處在于它應(yīng)用于構(gòu)造器參數(shù)。如果在容器中沒有找到與構(gòu)造器參數(shù)類型一致的bean,那么將會(huì)拋出異常。 |
autodetect |
通過bean類的自省機(jī)制(introspection)來決定是使用constructor還是byType方式進(jìn)行自動(dòng)裝配。如果發(fā)現(xiàn)默認(rèn)的構(gòu)造器,那么將使用byType方式。 |
如果直接使用property
和constructor-arg
注入依賴的話,那么將總是
覆蓋自動(dòng)裝配。而且目前也不支持簡(jiǎn)單類型的自動(dòng)裝配,這里所說的簡(jiǎn)單類型包括基本類型、String
、Class
以及簡(jiǎn)單類型的數(shù)組(這一點(diǎn)已經(jīng)被設(shè)計(jì),將考慮作為一個(gè)功能提供)。byType和constructor自動(dòng)裝配模式也可用于數(shù)組和指定類型的集合。在這種情況下容器中的所有匹配的自動(dòng)裝配對(duì)象將被用于滿足各種依賴。對(duì)于key值類型為 String
的強(qiáng)類型Map也可以被自動(dòng)裝配。一個(gè)自動(dòng)裝配的Map的value值將由所匹配類型的bean所填充。而Map的key值則是相應(yīng)的bean的名字。
自動(dòng)裝配還可以與依賴檢查結(jié)合使用,這樣依賴檢查將在自動(dòng)裝配完成之后被執(zhí)行。
理解自動(dòng)裝配的優(yōu)缺點(diǎn)是很重要的。其中優(yōu)點(diǎn)包括:
自動(dòng)裝配能顯著減少配置的數(shù)量。不過,采用bean模板(見這里)也可以達(dá)到同樣的目的。
自動(dòng)裝配可以使配置與java代碼同步更新。例如,如果你需要給一個(gè)java類增加一個(gè)依賴,那么該依賴將被自動(dòng)實(shí)現(xiàn)而不需要修改配置。因此強(qiáng)烈推薦在開發(fā)過程中采用自動(dòng)裝配,而在系統(tǒng)趨于穩(wěn)定的時(shí)候改為顯式裝配的方式。
自動(dòng)裝配的一些缺點(diǎn):
盡管自動(dòng)裝配比顯式裝配更神奇,但是,正如上面所提到的,Spring會(huì)盡量避免在裝配不明確的時(shí)候進(jìn)行猜測(cè),因?yàn)檠b配不明確可能出現(xiàn)難以預(yù)料的結(jié)果,而且Spring所管理的對(duì)象之間的關(guān)聯(lián)關(guān)系也不再能清晰的進(jìn)行文檔化。
對(duì)于那些根據(jù)Spring配置文件生成文檔的工具來說,自動(dòng)裝配將會(huì)使這些工具沒法生成依賴信息。
另一個(gè)問題需要注意的是,當(dāng)根據(jù)類型進(jìn)行自動(dòng)裝配的時(shí)候,容器中可能存在多個(gè)bean定義跟自動(dòng)裝配的setter方法和構(gòu)造器參數(shù)類型匹配。雖然對(duì)于數(shù)組、集合以及Map,不存在這個(gè)問題,但是對(duì)于單值依賴來說,就會(huì)存在模棱兩可的問題。如果bean定義不唯一,裝配時(shí)就會(huì)拋出異常,面對(duì)這種場(chǎng)景我們有幾個(gè)方案進(jìn)行選擇:第一個(gè)方案就是棄自動(dòng)裝配而改用顯式裝配;第二個(gè)方案就是在bean定義中通過設(shè)置'autowire-candidate'
屬性為'false'
來將該bean排除在自動(dòng)裝配候選名單之外(詳情見接下來的章節(jié));第三個(gè)方案是通過在bean定義中設(shè)置'primary'
屬性為'true'
來將該bean設(shè)置為首選自動(dòng)裝配bean。最后,對(duì)于使用Java 5的用戶來說,可能會(huì)使用注解的形式來配置bean,關(guān)于這方面的內(nèi)容可見第?3.11?節(jié) “基于注解(Annotation-based)的配置”。
但決定是否使用自動(dòng)裝配式時(shí),沒有絕對(duì)的對(duì)錯(cuò)??紤]項(xiàng)目的實(shí)際是最好的辦法。比如項(xiàng)目通常不使用自動(dòng)裝配,那么使用它來僅僅裝配2個(gè)bean定義是很讓人困惑的。
你也可以針對(duì)單個(gè)bean設(shè)置其是否為被自動(dòng)裝配對(duì)象。當(dāng)采用XML格式配置bean時(shí),<bean/>
元素的 autowire-candidate
屬性可被設(shè)為false
,這樣容器在查找自動(dòng)裝配對(duì)象時(shí)將不考慮該bean。
另一個(gè)做法就是使用對(duì)bean名字進(jìn)行模式匹配來對(duì)自動(dòng)裝配進(jìn)行限制。其做法是在<beans/>
元素的'default-autowire-candidates'
屬性中進(jìn)行設(shè)置。比如,將自動(dòng)裝配限制在名字以'Repository'結(jié)尾的bean,那么可以設(shè)置為"*Repository“。對(duì)于多個(gè)匹配模式則可以使用逗號(hào)進(jìn)行分隔。注意,如果在bean定義中的'autowire-candidate'
屬性顯式的設(shè)置為'true'
或 'false'
,那么該容器在自動(dòng)裝配的時(shí)候優(yōu)先采用該屬性的設(shè)置,而模式匹配將不起作用。
對(duì)于那些從來就不會(huì)被其它bean采用自動(dòng)裝配的方式來注入的bean而言,這是有用的。不過這并不意味著被排除的bean自己就不能使用自動(dòng)裝配來注入其他bean,它是可以的,或者更準(zhǔn)確地說,應(yīng)該是它不會(huì)被考慮作為其他bean自動(dòng)裝配的候選者。
Spring除了能對(duì)容器中bean的依賴設(shè)置進(jìn)行檢查外,還可以檢查bean定義中實(shí)際屬性值的設(shè)置,當(dāng)然也包括采用自動(dòng)裝配方式設(shè)置屬性值的檢查。
當(dāng)需要確保bean的所有屬性值(或者屬性類型)被正確設(shè)置的時(shí)候,那么這個(gè)功能會(huì)非常有用。當(dāng)然,在很多情況下,bean類的某些屬性會(huì)具有默認(rèn)值,或者有些屬性并不會(huì)在所有場(chǎng)景下使用,因此這項(xiàng)功能會(huì)存在一定的局限性。就像自動(dòng)裝配一樣,依賴檢查也可以針對(duì)每一個(gè)bean進(jìn)行設(shè)置。依賴檢查默認(rèn)為not,它有幾種不同的使用模式,在xml配置文件中,可以在bean定義中為dependency-check
屬性使用以下幾種值:
表?3.3.?依賴檢查方式
模式 | 說明 |
---|---|
none |
沒有依賴檢查,如果bean的屬性沒有值的話可以不用設(shè)置。 |
simple |
對(duì)于原始類型及集合(除協(xié)作者外的一切東西)執(zhí)行依賴檢查 |
object |
僅對(duì)協(xié)作者執(zhí)行依賴檢查 |
all |
對(duì)協(xié)作者,原始類型及集合執(zhí)行依賴檢查 |
假若你在使用Java 5,可以采用源代碼級(jí)的注解(annotations)來進(jìn)行配置,關(guān)于這方面的內(nèi)容可以在第?25.3.1?節(jié) “@Required
”這一節(jié)找到。
在大部分情況下,容器中的bean都是singleton類型的。如果一個(gè)singleton bean要引用另外一個(gè)singleton bean,或者一個(gè)非singleton bean要引用另外一個(gè)非singleton bean時(shí),通常情況下將一個(gè)bean定義為另一個(gè)bean的property值就可以了。不過對(duì)于具有不同生命周期的bean來說這樣做就會(huì)有問題了,比如在調(diào)用一個(gè)singleton類型bean A的某個(gè)方法時(shí),需要引用另一個(gè)非singleton(prototype)類型的bean B,對(duì)于bean A來說,容器只會(huì)創(chuàng)建一次,這樣就沒法在需要的時(shí)候每次讓容器為bean A提供一個(gè)新的的bean B實(shí)例。
上述問題的一個(gè)解決辦法就是放棄控制反轉(zhuǎn)。通過實(shí)現(xiàn)BeanFactoryAware
接口(見這里)讓bean A能夠感知bean 容器,并且在需要的時(shí)候通過使用getBean("B")
方式(見這里)向容器請(qǐng)求一個(gè)新的bean B實(shí)例??聪孪旅孢@個(gè)例子,其中故意使用了這種方法:
// a class that uses a stateful Command-style class to perform some processing package fiona.apple; // lots of Spring-API imports import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; public class CommandManager implements BeanFactoryAware { private BeanFactory beanFactory; public Object process(Map commandState) { // grab a new instance of the appropriateCommand
Command command = createCommand(); // set the state on the (hopefully brand new)Command
instance command.setState(commandState); return command.execute(); } // theCommand
returned here could be an implementation that executes asynchronously, or whatever protected Command createCommand() { return (Command) this.beanFactory.getBean("command"); // notice the Spring API dependency } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }
上面的例子顯然不是最好的,因?yàn)闃I(yè)務(wù)代碼和Spring Framework產(chǎn)生了耦合。方法注入,作為Spring IoC容器的一種高級(jí)特性,可以以一種干凈的方法來處理這種情況。
Lookup方法注入利用了容器的覆蓋受容器管理的bean方法的能力,從而返回指定名字的bean實(shí)例。在上述場(chǎng)景中,Lookup方法注入適用于原型bean。Lookup方法注入的內(nèi)部機(jī)制是Spring利用了CGLIB庫(kù)在運(yùn)行時(shí)生成二進(jìn)制代碼功能,通過動(dòng)態(tài)創(chuàng)建Lookup方法bean的子類而達(dá)到復(fù)寫Lookup方法的目的。
如果你看下上個(gè)代碼段中的代碼(CommandManager
類),Spring容器動(dòng)態(tài)覆蓋了createCommand()
方法的實(shí)現(xiàn)。你的CommandManager
類不會(huì)有一點(diǎn)對(duì)Spring的依賴,在下面這個(gè)例子中也是一樣的:
package fiona.apple; // no more Spring imports! public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriateCommand
interface Command command = createCommand(); // set the state on the (hopefully brand new)Command
instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
在包含被注入方法的客戶類中(此處是CommandManager
),此方法的定義必須按以下形式進(jìn)行:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象
的,動(dòng)態(tài)生成的子類會(huì)實(shí)現(xiàn)該方法。否則,動(dòng)態(tài)生成的子類會(huì)覆蓋類里的具體方法。讓我們來看個(gè)例子:
<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="command" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!--commandProcessor
usesstatefulCommandHelper
--> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="command"/> </bean>
在上面的例子中,標(biāo)識(shí)為commandManager的bean在需要一個(gè)新的command bean實(shí)例時(shí),會(huì)調(diào)用createCommand
方法。重要的一點(diǎn)是,必須將command
部署為prototype。當(dāng)然也可以指定為singleton,如果是這樣的話,那么每次將返回相同的command
bean實(shí)例!
請(qǐng)注意,為了讓這個(gè)動(dòng)態(tài)子類得以正常工作,需要把CGLIB的jar文件放在classpath里。另外,Spring容器要子類化的類不能是final
的,要覆蓋的方法也不能是final
的。同樣的,要測(cè)試一個(gè)包含抽象
方法的類也稍微有些不同,你需要自己編寫它的子類提供該抽象
方法的樁實(shí)現(xiàn)。最后,作為方法注入目標(biāo)的bean不能是序列化的(serialized)。
有興趣的讀者也許已經(jīng)發(fā)現(xiàn)ServiceLocatorFactoryBean
(在org.springframework.beans.factory.config
包里)的用法和ObjectFactoryCreatingFactoryBean
的有些相似,不同的是它允許你指定自己的lookup接口,不一定非要用Spring的lookup接口,比如ObjectFactory
。要詳細(xì)了解這種方法請(qǐng)參考ServiceLocatorFactoryBean
的Javadocs(它的確減少了對(duì)Spring的耦合)。
比起Lookup 方法注入來,還有一種很少用到的方法注入形式,該注入能使用bean的另一個(gè)方法實(shí)現(xiàn)去替換自定義的方法。除非你真的需要該功能,否則可以略過本節(jié)。
當(dāng)使用基于XML配置元數(shù)據(jù)文件時(shí),可以在bean定義中使用replaced-method
元素來達(dá)到用另一個(gè)方法來取代已有方法的目的??紤]下面的類,我們將覆蓋computeValue方法:
public class MyValueCalculator { public String computeValue(String input) { // some real code... } // some other methods... }
實(shí)現(xiàn)org.springframework.beans.factory.support.MethodReplacer
接口的類提供了新的方法定義。
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
下面的bean定義中指定了要配置的原始類和將要覆寫的方法:
<bean id="myValueCalculator class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
在<replaced-method/>
元素內(nèi)可包含一個(gè)或多個(gè)<arg-type/>
元素,這些元素用來標(biāo)明被覆寫的方法簽名。只有被覆寫(override)的方法存在重載(overload)的情況(同名的多個(gè)方法變體)才會(huì)使用方法簽名。為了方便,參數(shù)的類型字符串可以采用全限定類名的簡(jiǎn)寫。例如,下面的字符串都表示參數(shù)類型為java.lang.String
。
java.lang.String String Str
參數(shù)的個(gè)數(shù)通常足夠用來區(qū)別每個(gè)可能的選擇,這個(gè)捷徑能減少很多鍵盤輸入的工作,它允許你只輸入最短的匹配參數(shù)類型的字符串。
[2] 參見第?3.3.1?節(jié) “注入依賴”