?
このドキュメントでは、 php中國語ネットマニュアル リリース
Spring框架的IoC容器被設(shè)計為可擴(kuò)展的。通常我們并不需要子類化各個BeanFactory
或ApplicationContext
實現(xiàn)類。而通過plugin各種集成接口實現(xiàn)來進(jìn)行擴(kuò)展。下面幾節(jié)專門描述這些不同的集成接口。
我們關(guān)注的第一個擴(kuò)展點是BeanPostProcessor
接口。它定義了幾個回調(diào)方法,實現(xiàn)該接口可提供自定義(或默認(rèn)地來覆蓋容器)的實例化邏輯、依賴解析邏輯等。如果你想在Spring容器完成bean的實例化、配置和其它的初始化后執(zhí)行一些自定義邏輯,你可以插入一個或多個的BeanPostProcessor
實現(xiàn)。
如果配置了多個BeanPostProcessor
,那么可以通過設(shè)置'order'
屬性來控制BeanPostProcessor
的執(zhí)行次序(僅當(dāng)BeanPostProcessor
實現(xiàn)了Ordered
接口時,你才可以設(shè)置此屬性,因此在編寫自己的BeanPostProcessor
實現(xiàn)時,就得考慮是否需要實現(xiàn)Ordered
接口);請參考BeanPostProcessor
和Ordered
接口的JavaDoc以獲取更詳細(xì)的信息。
BeanPostProcessor
可以對bean(或?qū)ο螅┑?span id="wjcelcm34c" class="emphasis">多個實例進(jìn)行操作;也就是說,Spring IoC容器會為你實例化bean,然后BeanPostProcessor
去處理它。
如果你想修改實際的bean定義,則會用到BeanFactoryPostProcessor
(詳情見第?3.7.2?節(jié) “用BeanFactoryPostProcessor
定制配置元數(shù)據(jù)”)。
BeanPostProcessor
的作用域是容器級的,它只和所在容器有關(guān)。如果你在容器中定義了BeanPostProcessor
,它僅僅對此容器中的bean進(jìn)行后置處理。BeanPostProcessor
將不會對定義在另一個容器中的bean進(jìn)行后置處理,即使這兩個容器都處在同一層次上。
org.springframework.beans.factory.config.BeanPostProcessor
接口有兩個回調(diào)方法可供使用。當(dāng)一個該接口的實現(xiàn)類被注冊(如何使這個注冊生效請見下文)為容器的后置處理器(post-processor)后,對于由此容器所創(chuàng)建的每個bean實例在初始化方法(如afterPropertiesSet和任意已聲明的init方法)調(diào)用前,后置處理器都會從容器中分別獲取一個回調(diào)。后置處理器可以隨意對這個bean實例執(zhí)行它所期望的動作,包括完全忽略此回調(diào)。一個bean后置處理器通常用來檢查標(biāo)志接口,或者做一些諸如將一個bean包裝成一個proxy的事情;一些Spring AOP的底層處理也是通過實現(xiàn)bean后置處理器來執(zhí)行代理包裝邏輯。
重要的一點是,BeanFactory
和ApplicationContext
對待bean后置處理器稍有不同。ApplicationContext
會自動檢測在配置文件中實現(xiàn)了BeanPostProcessor
接口的所有bean,并把它們注冊為后置處理器,然后在容器創(chuàng)建bean的適當(dāng)時候調(diào)用它。部署一個后置處理器同部署其他的bean并沒有什么區(qū)別。而使用BeanFactory
實現(xiàn)的時候,bean 后置處理器必須通過下面類似的代碼顯式地去注冊:
ConfigurableBeanFactory factory = new XmlBeanFactory(...);
// now register any needed BeanPostProcessor
instances
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);
// now start using the factory
因為顯式注冊的步驟不是很方便,這也是為什么在各種Spring應(yīng)用中首選ApplicationContext
的一個原因,特別是在使用BeanPostProcessor
時。
BeanPostProcessors
和AOP自動代理(auto-proxying)實現(xiàn)了BeanPostProcessor
接口的類是特殊的, 會被容器特別對待. 所有 BeanPostProcessors
和直接引用的bean 會作為ApplicationContext
一部分在啟動時初始化, 然后所有的BeanPostProcessors
會注冊入一個列表并應(yīng)用于之后的bean。AOP自動代理實現(xiàn)了BeanPostProcessor
,所以BeanPostProcessors
或bean的直接引用不會被自動代理(因此不會被aspects"織入")。
對這些bean來說,你可能看到下面的日志信息:“Bean 'foo' is not eligible for getting processed by all BeanPostProcessors (如:不能被auto_proxying)”
關(guān)于如何在ApplicationContext
中編寫、注冊并使用BeanPostProcessor
,會在接下的例子中演示。
第一個實例似乎不太吸引人,但是它適合用來闡述BeanPostProcessor
的基本用法。我們所有的工作是編寫一個BeanPostProcessor
的實現(xiàn),它僅僅在容器創(chuàng)建每個bean時調(diào)用bean的toString()
方法并且將結(jié)果打印到系統(tǒng)控制臺。它是沒有很大的用處,但是可以讓我們對BeanPostProcessor
有一個基本概念。
下面是BeanPostProcessor
具體實現(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
是如此簡單,甚至沒有名字,由于被定義成一個bean,因而它跟其它的bean沒什么兩樣(上面的配置中也定義了由Groovy腳本支持的bean,Spring2.0動態(tài)語言支持的細(xì)節(jié)請見第?24?章 動態(tài)語言支持
)。
下面是測試代碼:
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í)行時的輸出將是(或象)下面這樣:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
我們將看到的下一個擴(kuò)展點是org.springframework.beans.factory.config.BeanFactoryPostProcessor
。這個接口跟BeanPostProcessor
類似,BeanFactoryPostProcessor
可以對bean的定義(配置元數(shù)據(jù))進(jìn)行處理。也就是說,Spring IoC容器允許BeanFactoryPostProcessor
在容器實際實例化任何其它的bean之前讀取配置元數(shù)據(jù),并有可能修改它。
如果你愿意,你可以配置多個BeanFactoryPostProcessor
。你還能通過設(shè)置'order'
屬性來控制BeanFactoryPostProcessor
的執(zhí)行次序(僅當(dāng)BeanFactoryPostProcessor
實現(xiàn)了Ordered
接口時你才可以設(shè)置此屬性,因此在實現(xiàn)BeanFactoryPostProcessor
時,就應(yīng)當(dāng)考慮實現(xiàn)Ordered
接口);請參考BeanFactoryPostProcessor
和Ordered
接口的JavaDoc以獲取更詳細(xì)的信息。
如果你想改變實際的bean實例(例如從配置元數(shù)據(jù)創(chuàng)建的對象),那么你最好使用BeanPostProcessor
(見上面第?3.7.1?節(jié) “用BeanPostProcessor
定制bean”中的描述)
同樣地,BeanFactoryPostProcessor
的作用域范圍是容器級的。它只和你所使用的容器有關(guān)。如果你在容器中定義一個BeanFactoryPostProcessor
,它僅僅對此容器中的bean進(jìn)行后置處理。BeanFactoryPostProcessor
不會對定義在另一個容器中的bean進(jìn)行后置處理,即使這兩個容器都是在同一層次上。
bean工廠后置處理器可以手工(如果是BeanFactory
)或自動(如果是ApplicationContext
)地施加某些變化給定義在容器中的配置元數(shù)據(jù)。Spring自帶了許多bean工廠后置處理器,比如下面將提到的PropertyResourceConfigurer
和PropertyPlaceholderConfigurer
以及BeanNameAutoProxyCreator
,它們用于對bean進(jìn)行事務(wù)性包裝或者使用其他的proxy進(jìn)行包裝。BeanFactoryPostProcessor
也能被用來添加自定義屬性編輯器。
在一個BeanFactory
中,應(yīng)用BeanFactoryPostProcessor
的過程是手工的,如下所示:
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);
因為顯式注冊的步驟不是很方便,這也是為什么在不同的Spring應(yīng)用中首選ApplicationContext
的原因,特別是在使用BeanFactoryPostProcessor
時。
ApplicationContext
會檢測部署在它之上實現(xiàn)了BeanFactoryPostProcessor
接口的bean,并在適當(dāng)?shù)臅r候會自動調(diào)用bean工廠后置處理器。部署一個后置處理器同部屬其他的bean并沒有什么區(qū)別。
正如BeanPostProcessor
的情況一樣,請不要將BeanFactoryPostProcessors
標(biāo)記為延遲加載。如果你這樣做,Spring容器將不會注冊它們,自定義邏輯就無法實現(xiàn)。如果你在<beans/>
元素的定義中使用了'default-lazy-init'
屬性,請確信你的各個BeanFactoryPostProcessor
標(biāo)記為'lazy-init="false"'
。
PropertyPlaceholderConfigurer
是個bean工廠后置處理器的實現(xiàn),可以將BeanFactory
定義中的一些屬性值放到另一個單獨的標(biāo)準(zhǔn)Java Properties
文件中。這就允許用戶在部署應(yīng)用時只需要在屬性文件中對一些關(guān)鍵屬性(例如數(shù)據(jù)庫URL,用戶名和密碼)進(jìn)行修改,而不用對主XML定義文件或容器所用文件進(jìn)行復(fù)雜和危險的修改。
考慮下面的XML配置元數(shù)據(jù)定義,它用占位符定義了DataSource
。我們在外部的Properties
文件中配置一些相關(guān)的屬性。在運行時,我們?yōu)樵獢?shù)據(jù)提供一個PropertyPlaceholderConfigurer
,它將會替換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>
實際的值來自于另一個標(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
名字空間可能采用單一元素屬性占位符的方式(多個路徑提供一個逗號分隔的列表)
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
如果在指定的Properties
文件中找不到你想使用的屬性,它還會在Java的System
類屬性中查找。這個行為可以通過設(shè)置systemPropertiesMode
屬性來定制,它有三個值:讓配置一直覆蓋、讓它永不覆蓋及讓它僅僅在屬性文件中找不到該屬性時才覆蓋。請參考PropertiesPlaceholderConfigurer
的JavaDoc以獲得更多的信息。
PropertyPlaceholderConfigurer
可以在必須在運行時選擇一個特性實現(xiàn)類時可以用來替代類名。例如:
<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ōu)橛行Аt這個bean會創(chuàng)建失?。ó?dāng)對非延遲實例化bean執(zhí)行ApplicationContext
的preInstantiateSingletons()
方法的情況下)。
另一個bean工廠后置處理器PropertyOverrideConfigurer
類似于PropertyPlaceholderConfigurer
。但是與后者相比,前者對于bean屬性可以有缺省值或者根本沒有值。如果起覆蓋作用的Properties
文件沒有某個bean屬性的內(nèi)容,那么將使用缺省的上下文定義。
bean工廠并不會意識到被覆蓋,所以僅僅察看XML定義文件并不能立刻知道覆蓋配置是否被使用了。在多個PropertyOverrideConfigurer
實例中對一個bean屬性定義了不同的值時,最后定義的值將被使用(由于覆蓋機制)。
Properties文件的配置應(yīng)該是如下的格式:
beanName.property=value
An example properties file might look like this:
一個properties文件可能是下面這樣的:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
這個示例文件可用在這樣一個bean容器:包含一個名為dataSource的bean,并且這個bean有driver和url屬性。
注意它也支持組合的屬性名稱,只要路徑中每個組件除了最后要被覆蓋的屬性外全都是非空的(比如通過構(gòu)造器來初始化),在下例中:
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需要實現(xiàn)org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是插入到Spring IoC容器用來定制實例化邏輯的一個接口點。如果你有一些復(fù)雜的初始化代碼用Java可以更好來表示,而不是用(可能)冗長的XML,那么你就可以創(chuàng)建你自己的FactoryBean
,并在那個類中寫入復(fù)雜的初始化動作,然后把你定制的FactoryBean
插入容器中。
FactoryBean
接口提供三個方法:
Object getObject()
:返回一個由這個工廠創(chuàng)建的對象實例。這個實例可能被共享(取決于isSingleton()的返回值是singleton或prototype)。
boolean isSingleton()
:如果要讓這個FactoryBean創(chuàng)建的對象實例為singleton則返回true,否則返回false。
Class getObjectType()
:返回通過getObject()方法返回的對象類型,如果該類型無法預(yù)料則返回null。
在Spring框架中FactoryBean
的概念和接口被用于多個地方;在本文寫作時,Spring本身提供的FactoryBean
接口實現(xiàn)超過了50個。
最后,有時需要向容器請求一個真實的FactoryBean
實例本身,而不是它創(chuàng)建的bean。這可以通過在FactoryBean
(包括ApplicationContext
)調(diào)用getBean
方法時在bean id前加'&'
(沒有單引號)來完成。因此對于一個假定id為myBean
的FactoryBean
,在容器上調(diào)用getBean("myBean")
將返回FactoryBean
創(chuàng)建的bean實例,但是調(diào)用getBean("&myBean")
將返回FactoryBean
本身的實例。