?
Dokumen ini menggunakan Manual laman web PHP Cina Lepaskan
Spring框架的IoC容器被設(shè)計(jì)為可擴(kuò)展的。通常我們并不需要子類化各個(gè)BeanFactory
或ApplicationContext
實(shí)現(xiàn)類。而通過(guò)plugin各種集成接口實(shí)現(xiàn)來(lái)進(jìn)行擴(kuò)展。下面幾節(jié)專門描述這些不同的集成接口。
我們關(guān)注的第一個(gè)擴(kuò)展點(diǎn)是BeanPostProcessor
接口。它定義了幾個(gè)回調(diào)方法,實(shí)現(xiàn)該接口可提供自定義(或默認(rèn)地來(lái)覆蓋容器)的實(shí)例化邏輯、依賴解析邏輯等。如果你想在Spring容器完成bean的實(shí)例化、配置和其它的初始化后執(zhí)行一些自定義邏輯,你可以插入一個(gè)或多個(gè)的BeanPostProcessor
實(shí)現(xiàn)。
如果配置了多個(gè)BeanPostProcessor
,那么可以通過(guò)設(shè)置'order'
屬性來(lái)控制BeanPostProcessor
的執(zhí)行次序(僅當(dāng)BeanPostProcessor
實(shí)現(xiàn)了Ordered
接口時(shí),你才可以設(shè)置此屬性,因此在編寫自己的BeanPostProcessor
實(shí)現(xiàn)時(shí),就得考慮是否需要實(shí)現(xiàn)Ordered
接口);請(qǐng)參考BeanPostProcessor
和Ordered
接口的JavaDoc以獲取更詳細(xì)的信息。
BeanPostProcessor
可以對(duì)bean(或?qū)ο螅┑?span id="wjcelcm34c" class="emphasis">多個(gè)實(shí)例進(jìn)行操作;也就是說(shuō),Spring IoC容器會(huì)為你實(shí)例化bean,然后BeanPostProcessor
去處理它。
如果你想修改實(shí)際的bean定義,則會(huì)用到BeanFactoryPostProcessor
(詳情見第?3.7.2?節(jié) “用BeanFactoryPostProcessor
定制配置元數(shù)據(jù)”)。
BeanPostProcessor
的作用域是容器級(jí)的,它只和所在容器有關(guān)。如果你在容器中定義了BeanPostProcessor
,它僅僅對(duì)此容器中的bean進(jìn)行后置處理。BeanPostProcessor
將不會(huì)對(duì)定義在另一個(gè)容器中的bean進(jìn)行后置處理,即使這兩個(gè)容器都處在同一層次上。
org.springframework.beans.factory.config.BeanPostProcessor
接口有兩個(gè)回調(diào)方法可供使用。當(dāng)一個(gè)該接口的實(shí)現(xiàn)類被注冊(cè)(如何使這個(gè)注冊(cè)生效請(qǐng)見下文)為容器的后置處理器(post-processor)后,對(duì)于由此容器所創(chuàng)建的每個(gè)bean實(shí)例在初始化方法(如afterPropertiesSet和任意已聲明的init方法)調(diào)用前,后置處理器都會(huì)從容器中分別獲取一個(gè)回調(diào)。后置處理器可以隨意對(duì)這個(gè)bean實(shí)例執(zhí)行它所期望的動(dòng)作,包括完全忽略此回調(diào)。一個(gè)bean后置處理器通常用來(lái)檢查標(biāo)志接口,或者做一些諸如將一個(gè)bean包裝成一個(gè)proxy的事情;一些Spring AOP的底層處理也是通過(guò)實(shí)現(xiàn)bean后置處理器來(lái)執(zhí)行代理包裝邏輯。
重要的一點(diǎn)是,BeanFactory
和ApplicationContext
對(duì)待bean后置處理器稍有不同。ApplicationContext
會(huì)自動(dòng)檢測(cè)在配置文件中實(shí)現(xiàn)了BeanPostProcessor
接口的所有bean,并把它們注冊(cè)為后置處理器,然后在容器創(chuàng)建bean的適當(dāng)時(shí)候調(diào)用它。部署一個(gè)后置處理器同部署其他的bean并沒(méi)有什么區(qū)別。而使用BeanFactory
實(shí)現(xiàn)的時(shí)候,bean 后置處理器必須通過(guò)下面類似的代碼顯式地去注冊(cè):
ConfigurableBeanFactory factory = new XmlBeanFactory(...);
// now register any needed BeanPostProcessor
instances
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);
// now start using the factory
因?yàn)轱@式注冊(cè)的步驟不是很方便,這也是為什么在各種Spring應(yīng)用中首選ApplicationContext
的一個(gè)原因,特別是在使用BeanPostProcessor
時(shí)。
BeanPostProcessors
和AOP自動(dòng)代理(auto-proxying)實(shí)現(xiàn)了BeanPostProcessor
接口的類是特殊的, 會(huì)被容器特別對(duì)待. 所有 BeanPostProcessors
和直接引用的bean 會(huì)作為ApplicationContext
一部分在啟動(dòng)時(shí)初始化, 然后所有的BeanPostProcessors
會(huì)注冊(cè)入一個(gè)列表并應(yīng)用于之后的bean。AOP自動(dòng)代理實(shí)現(xiàn)了BeanPostProcessor
,所以BeanPostProcessors
或bean的直接引用不會(huì)被自動(dòng)代理(因此不會(huì)被aspects"織入")。
對(duì)這些bean來(lái)說(shuō),你可能看到下面的日志信息:“Bean 'foo' is not eligible for getting processed by all BeanPostProcessors (如:不能被auto_proxying)”
關(guān)于如何在ApplicationContext
中編寫、注冊(cè)并使用BeanPostProcessor
,會(huì)在接下的例子中演示。
第一個(gè)實(shí)例似乎不太吸引人,但是它適合用來(lái)闡述BeanPostProcessor
的基本用法。我們所有的工作是編寫一個(gè)BeanPostProcessor
的實(shí)現(xiàn),它僅僅在容器創(chuàng)建每個(gè)bean時(shí)調(diào)用bean的toString()
方法并且將結(jié)果打印到系統(tǒng)控制臺(tái)。它是沒(méi)有很大的用處,但是可以讓我們對(duì)BeanPostProcessor
有一個(gè)基本概念。
下面是BeanPostProcessor
具體實(shí)現(xiàn)類的定義:
package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.BeansException; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; // we could potentially return any object reference here... } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } }
<?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:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean ('messenger') is instantiated, this custom
BeanPostProcessor
implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
注意InstantiationTracingBeanPostProcessor
是如此簡(jiǎn)單,甚至沒(méi)有名字,由于被定義成一個(gè)bean,因而它跟其它的bean沒(méi)什么兩樣(上面的配置中也定義了由Groovy腳本支持的bean,Spring2.0動(dòng)態(tài)語(yǔ)言支持的細(xì)節(jié)請(qǐng)見第?24?章 動(dòng)態(tài)語(yǔ)言支持
)。
下面是測(cè)試代碼:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.scripting.Messenger; public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml"); Messenger messenger = (Messenger) ctx.getBean("messenger"); System.out.println(messenger); } }
上面程序執(zhí)行時(shí)的輸出將是(或象)下面這樣:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
在Spring的BeanPostProcessor
實(shí)現(xiàn)中調(diào)用標(biāo)志接口或使用注解是擴(kuò)展Spring IoC容器的常用方法。對(duì)于注解的用法詳見第?25.3.1?節(jié) “@Required
”,這里沒(méi)有做深入的說(shuō)明。通過(guò)定制BeanPostProcessor
實(shí)現(xiàn),可以使用注解來(lái)指定各種JavaBean屬性值并在發(fā)布的時(shí)候被注入相應(yīng)的bean中。
我們將看到的下一個(gè)擴(kuò)展點(diǎn)是org.springframework.beans.factory.config.BeanFactoryPostProcessor
。這個(gè)接口跟BeanPostProcessor
類似,BeanFactoryPostProcessor
可以對(duì)bean的定義(配置元數(shù)據(jù))進(jìn)行處理。也就是說(shuō),Spring IoC容器允許BeanFactoryPostProcessor
在容器實(shí)際實(shí)例化任何其它的bean之前讀取配置元數(shù)據(jù),并有可能修改它。
如果你愿意,你可以配置多個(gè)BeanFactoryPostProcessor
。你還能通過(guò)設(shè)置'order'
屬性來(lái)控制BeanFactoryPostProcessor
的執(zhí)行次序(僅當(dāng)BeanFactoryPostProcessor
實(shí)現(xiàn)了Ordered
接口時(shí)你才可以設(shè)置此屬性,因此在實(shí)現(xiàn)BeanFactoryPostProcessor
時(shí),就應(yīng)當(dāng)考慮實(shí)現(xiàn)Ordered
接口);請(qǐng)參考BeanFactoryPostProcessor
和Ordered
接口的JavaDoc以獲取更詳細(xì)的信息。
如果你想改變實(shí)際的bean實(shí)例(例如從配置元數(shù)據(jù)創(chuàng)建的對(duì)象),那么你最好使用BeanPostProcessor
(見上面第?3.7.1?節(jié) “用BeanPostProcessor
定制bean”中的描述)
同樣地,BeanFactoryPostProcessor
的作用域范圍是容器級(jí)的。它只和你所使用的容器有關(guān)。如果你在容器中定義一個(gè)BeanFactoryPostProcessor
,它僅僅對(duì)此容器中的bean進(jìn)行后置處理。BeanFactoryPostProcessor
不會(huì)對(duì)定義在另一個(gè)容器中的bean進(jìn)行后置處理,即使這兩個(gè)容器都是在同一層次上。
bean工廠后置處理器可以手工(如果是BeanFactory
)或自動(dòng)(如果是ApplicationContext
)地施加某些變化給定義在容器中的配置元數(shù)據(jù)。Spring自帶了許多bean工廠后置處理器,比如下面將提到的PropertyResourceConfigurer
和PropertyPlaceholderConfigurer
以及BeanNameAutoProxyCreator
,它們用于對(duì)bean進(jìn)行事務(wù)性包裝或者使用其他的proxy進(jìn)行包裝。BeanFactoryPostProcessor
也能被用來(lái)添加自定義屬性編輯器。
在一個(gè)BeanFactory
中,應(yīng)用BeanFactoryPostProcessor
的過(guò)程是手工的,如下所示:
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties
file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
因?yàn)轱@式注冊(cè)的步驟不是很方便,這也是為什么在不同的Spring應(yīng)用中首選ApplicationContext
的原因,特別是在使用BeanFactoryPostProcessor
時(shí)。
ApplicationContext
會(huì)檢測(cè)部署在它之上實(shí)現(xiàn)了BeanFactoryPostProcessor
接口的bean,并在適當(dāng)?shù)臅r(shí)候會(huì)自動(dòng)調(diào)用bean工廠后置處理器。部署一個(gè)后置處理器同部屬其他的bean并沒(méi)有什么區(qū)別。
正如BeanPostProcessor
的情況一樣,請(qǐng)不要將BeanFactoryPostProcessors
標(biāo)記為延遲加載。如果你這樣做,Spring容器將不會(huì)注冊(cè)它們,自定義邏輯就無(wú)法實(shí)現(xiàn)。如果你在<beans/>
元素的定義中使用了'default-lazy-init'
屬性,請(qǐng)確信你的各個(gè)BeanFactoryPostProcessor
標(biāo)記為'lazy-init="false"'
。
PropertyPlaceholderConfigurer
是個(gè)bean工廠后置處理器的實(shí)現(xiàn),可以將BeanFactory
定義中的一些屬性值放到另一個(gè)單獨(dú)的標(biāo)準(zhǔn)Java Properties
文件中。這就允許用戶在部署應(yīng)用時(shí)只需要在屬性文件中對(duì)一些關(guān)鍵屬性(例如數(shù)據(jù)庫(kù)URL,用戶名和密碼)進(jìn)行修改,而不用對(duì)主XML定義文件或容器所用文件進(jìn)行復(fù)雜和危險(xiǎn)的修改。
考慮下面的XML配置元數(shù)據(jù)定義,它用占位符定義了DataSource
。我們?cè)谕獠康?code class="classname">Properties文件中配置一些相關(guān)的屬性。在運(yùn)行時(shí),我們?yōu)樵獢?shù)據(jù)提供一個(gè)PropertyPlaceholderConfigurer
,它將會(huì)替換dataSource的屬性值。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/jdbc.properties</value> </property> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
實(shí)際的值來(lái)自于另一個(gè)標(biāo)準(zhǔn)Java Properties
格式的文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
在Spring 2.5中,context
名字空間可能采用單一元素屬性占位符的方式(多個(gè)路徑提供一個(gè)逗號(hào)分隔的列表)
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
如果在指定的Properties
文件中找不到你想使用的屬性,它還會(huì)在Java的System
類屬性中查找。這個(gè)行為可以通過(guò)設(shè)置systemPropertiesMode
屬性來(lái)定制,它有三個(gè)值:讓配置一直覆蓋、讓它永不覆蓋及讓它僅僅在屬性文件中找不到該屬性時(shí)才覆蓋。請(qǐng)參考PropertiesPlaceholderConfigurer
的JavaDoc以獲得更多的信息。
PropertyPlaceholderConfigurer
可以在必須在運(yùn)行時(shí)選擇一個(gè)特性實(shí)現(xiàn)類時(shí)可以用來(lái)替代類名。例如:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.foo.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果類在運(yùn)行時(shí)無(wú)法變?yōu)橛行?。則這個(gè)bean會(huì)創(chuàng)建失?。ó?dāng)對(duì)非延遲實(shí)例化bean執(zhí)行ApplicationContext
的preInstantiateSingletons()
方法的情況下)。
另一個(gè)bean工廠后置處理器PropertyOverrideConfigurer
類似于PropertyPlaceholderConfigurer
。但是與后者相比,前者對(duì)于bean屬性可以有缺省值或者根本沒(méi)有值。如果起覆蓋作用的Properties
文件沒(méi)有某個(gè)bean屬性的內(nèi)容,那么將使用缺省的上下文定義。
bean工廠并不會(huì)意識(shí)到被覆蓋,所以僅僅察看XML定義文件并不能立刻知道覆蓋配置是否被使用了。在多個(gè)PropertyOverrideConfigurer
實(shí)例中對(duì)一個(gè)bean屬性定義了不同的值時(shí),最后定義的值將被使用(由于覆蓋機(jī)制)。
Properties文件的配置應(yīng)該是如下的格式:
beanName.property=value
An example properties file might look like this:
一個(gè)properties文件可能是下面這樣的:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
這個(gè)示例文件可用在這樣一個(gè)bean容器:包含一個(gè)名為dataSource的bean,并且這個(gè)bean有driver和url屬性。
注意它也支持組合的屬性名稱,只要路徑中每個(gè)組件除了最后要被覆蓋的屬性外全都是非空的(比如通過(guò)構(gòu)造器來(lái)初始化),在下例中:
foo.fred.bob.sammy=123
... the sammy
property of the
bob
property of the fred
property of the foo
bean is being set to the scalar
value 123
.
foo
bean的fred
屬性的bob
屬性的sammy
屬性被設(shè)置為數(shù)值123。
工廠bean需要實(shí)現(xiàn)org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是插入到Spring IoC容器用來(lái)定制實(shí)例化邏輯的一個(gè)接口點(diǎn)。如果你有一些復(fù)雜的初始化代碼用Java可以更好來(lái)表示,而不是用(可能)冗長(zhǎng)的XML,那么你就可以創(chuàng)建你自己的FactoryBean
,并在那個(gè)類中寫入復(fù)雜的初始化動(dòng)作,然后把你定制的FactoryBean
插入容器中。
FactoryBean
接口提供三個(gè)方法:
Object getObject()
:返回一個(gè)由這個(gè)工廠創(chuàng)建的對(duì)象實(shí)例。這個(gè)實(shí)例可能被共享(取決于isSingleton()的返回值是singleton或prototype)。
boolean isSingleton()
:如果要讓這個(gè)FactoryBean創(chuàng)建的對(duì)象實(shí)例為singleton則返回true,否則返回false。
Class getObjectType()
:返回通過(guò)getObject()方法返回的對(duì)象類型,如果該類型無(wú)法預(yù)料則返回null。
在Spring框架中FactoryBean
的概念和接口被用于多個(gè)地方;在本文寫作時(shí),Spring本身提供的FactoryBean
接口實(shí)現(xiàn)超過(guò)了50個(gè)。
最后,有時(shí)需要向容器請(qǐng)求一個(gè)真實(shí)的FactoryBean
實(shí)例本身,而不是它創(chuàng)建的bean。這可以通過(guò)在FactoryBean
(包括ApplicationContext
)調(diào)用getBean
方法時(shí)在bean id前加'&'
(沒(méi)有單引號(hào))來(lái)完成。因此對(duì)于一個(gè)假定id為myBean
的FactoryBean
,在容器上調(diào)用getBean("myBean")
將返回FactoryBean
創(chuàng)建的bean實(shí)例,但是調(diào)用getBean("&myBean")
將返回FactoryBean
本身的實(shí)例。