容器擴(kuò)展點(diǎn)
通常,應(yīng)用程序開發(fā)人員不需要對(duì)ApplicationContext 實(shí)現(xiàn)類進(jìn)行子類化。相反,可以通過插入特殊集成接口的實(shí)現(xiàn)來擴(kuò)展Spring IoC容器。接下來的幾節(jié)描述了這些集成接口。
自定義BeanBeanPostProcessor
BeanPostProcessor接口定義了回調(diào)方法,您可以實(shí)現(xiàn)這些回調(diào)方法,以提供自己的(或覆蓋容器的默認(rèn)值)實(shí)例化邏輯,依賴關(guān)系解析邏輯等。
如果您想在Spring容器完成實(shí)例化,配置和初始化bean之后實(shí)現(xiàn)一些自定義邏輯,則可以插入一個(gè)或多個(gè)自定義BeanPostProcessor實(shí)現(xiàn)。
您可以配置多個(gè)BeanPostProcessor實(shí)例,并且可以BeanPostProcessor通過設(shè)置order屬性來控制這些實(shí)例的執(zhí)行順序。僅當(dāng)BeanPostProcessor實(shí)現(xiàn)Ordered 接口時(shí)才可以設(shè)置此屬性。如果您編寫自己的代碼BeanPostProcessor,則也應(yīng)該考慮實(shí)現(xiàn)該Ordered接口。
BeanPostProcessor實(shí)例在bean(或?qū)ο螅?shí)例上運(yùn)行。也就是說,Spring IoC容器實(shí)例化一個(gè)bean實(shí)例,然后BeanPostProcessor 實(shí)例執(zhí)行其工作。
BeanPostProcessor實(shí)例是按容器劃分作用域的。如果BeanPostProcessor在一個(gè)容器中定義一個(gè),它將僅對(duì)該容器中的bean進(jìn)行后處理。換句話說,一個(gè)容器中定義的bean不會(huì)被BeanPostProcessor另一個(gè)容器中的定義進(jìn)行后處理,即使這兩個(gè)容器是同一層次結(jié)構(gòu)的一部分也是如此。
要更改實(shí)際的bean定義(即,定義bean的藍(lán)圖),您需要使用a BeanFactoryPostProcessor。
org.springframework.beans.factory.config.BeanPostProcessor接口恰好由兩個(gè)回調(diào)方法組成。當(dāng)此類被注冊(cè)為容器的后處理器時(shí),對(duì)于容器創(chuàng)建的每個(gè)bean實(shí)例,后處理器都會(huì)在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何聲明的init方法)被使用之前從容器獲得回調(diào)。
后處理器可以對(duì)bean實(shí)例執(zhí)行任何操作,包括完全忽略回調(diào)。Bean后處理器通常檢查回調(diào)接口,或者可以用代理包裝Bean。一些Spring AOP基礎(chǔ)結(jié)構(gòu)類被實(shí)現(xiàn)為bean后處理器,以提供代理包裝邏輯。
ApplicationContext自動(dòng)檢測(cè)實(shí)現(xiàn)該BeanPostProcessor接口的配置元數(shù)據(jù)中定義的所有bean 。ApplicationContext將這些Bean注冊(cè)為后處理器,以便以后在創(chuàng)建Bean時(shí)調(diào)用它們。Bean后處理器可以與其他Bean相同的方式部署在容器中。
以編程方式注冊(cè)BeanPostProcessor實(shí)例
雖然推薦的BeanPostProcessor注冊(cè)方法是通過 ApplicationContext自動(dòng)檢測(cè)(如前所述),但是您可以ConfigurableBeanFactory使用addBeanPostProcessor 方法通過編程方式對(duì)它們進(jìn)行注冊(cè)。但是請(qǐng)注意,以BeanPostProcessor編程方式添加的實(shí)例不遵守該Ordered接口。在這里,注冊(cè)的順序決定了執(zhí)行的順序。還要注意,以BeanPostProcessor編程方式注冊(cè)的實(shí)例始終在通過自動(dòng)檢測(cè)注冊(cè)的實(shí)例之前進(jìn)行處理,而不考慮任何明確的順序。
以下示例顯示了如何在中編寫,注冊(cè)和使用BeanPostProcessor實(shí)例ApplicationContext。
第一個(gè)示例說明了基本用法。該示例顯示了一個(gè)自定義 BeanPostProcessor實(shí)現(xiàn),該實(shí)現(xiàn)調(diào)用toString()容器創(chuàng)建每個(gè)bean 的方法并將其輸出到系統(tǒng)控制臺(tái)。
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
@Component
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
使用BeanFactoryPostProcessor自定義配置元數(shù)據(jù)
我們要看的下一個(gè)擴(kuò)展點(diǎn)是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。該接口的語義與BeanPostProcessor相似,但有一個(gè)主要區(qū)別:
BeanFactoryPostProcessor對(duì)Bean配置元數(shù)據(jù)進(jìn)行操作。也就是說,Spring IoC容器允許BeanFactoryPostProcessor讀取配置元數(shù)據(jù),有可能在容器實(shí)例化之前更改它。當(dāng)然,除了BeanFactoryPostProcessor本身的實(shí)例。
您可以配置多個(gè)BeanFactoryPostProcessor實(shí)例,并且可以BeanFactoryPostProcessor通過設(shè)置order屬性來控制這些實(shí)例的運(yùn)行順序。但是,只有在BeanFactoryPostProcessor實(shí)現(xiàn)Ordered接口的情況下 才能設(shè)置此屬性。如果您編寫自己的代碼BeanFactoryPostProcessor,則也應(yīng)該考慮實(shí)現(xiàn)該Ordered接口。
將BeanFactoryPostProcessor聲明為Bean時(shí),它將自動(dòng)執(zhí)行,以便將更改應(yīng)用于定義容器的配置元數(shù)據(jù)。Spring包含許多預(yù)定義的BeanFactoryPostProcessor,例如PropertyOverrideConfigurer和 PropertySourcesPlaceholderConfigurer。您還可以使用自定義BeanFactoryPostProcessor?,例如注冊(cè)自定義屬性編輯器。
示例:類名替換 PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer,在單獨(dú)的文件中外部化Bean定義中的屬性值。這樣做使部署應(yīng)用程序的人員可以自定義特定于環(huán)境的屬性,例如數(shù)據(jù)庫URL和密碼。
考慮以下基于XML的配置元數(shù)據(jù)片段,其中DataSource 定義了帶有占位符的值:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</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>
該示例顯示了從外部Properties文件配置的屬性。在運(yùn)行時(shí),將a PropertySourcesPlaceholderConfigurer應(yīng)用于替換數(shù)據(jù)源的某些屬性的元數(shù)據(jù)。將要替換的值指定為形式的占位符,該形式${property-name}遵循Ant和log4j和JSP EL樣式。
實(shí)際值來自標(biāo)準(zhǔn)Java Properties格式的另一個(gè)文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,${jdbc.username}在運(yùn)行時(shí)將字符串替換為值“ sa”,并且其他與屬性文件中的鍵匹配的占位符值也適用。在PropertySourcesPlaceholderConfigurer為大多數(shù)屬性和bean定義的屬性占位符檢查。此外,您可以自定義占位符前綴和后綴。
在PropertySourcesPlaceholderConfigurer不僅將查找在屬性Properties 指定的文件。默認(rèn)情況下,如果無法在指定的屬性文件中找到屬性,則會(huì)檢查Spring Environment屬性和常規(guī)Java System屬性。
示例: PropertyOverrideConfigurer
如果覆蓋 Properties文件沒有某個(gè)bean屬性的條目,則使用默認(rèn)的上下文定義。注意,bean定義不知道會(huì)被覆蓋,因此從XML定義文件中不能立即看出正在使用覆蓋配置器。如果有多個(gè)PropertyOverrideConfigurer實(shí)例為同一bean屬性定義了不同的值,則由于覆蓋機(jī)制,最后一個(gè)實(shí)例將生效。
屬性文件配置行采用以下格式:
beanName.property = value
以下清單顯示了格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
該示例文件可與包含定義為dataSource具有driver和url屬性的bean的容器定義一起使用 。
只要路徑的每個(gè)組成部分(最終屬性被覆蓋)之外的所有組成部分都已經(jīng)為非空(可能是由構(gòu)造函數(shù)初始化),則也支持復(fù)合屬性名。在以下示例中,bean sammy的bobproperty的fredproperty屬性tom設(shè)置為標(biāo)量值123:
tom.fred.bob.sammy = 123
自定義實(shí)例化邏輯FactoryBean
FactoryBean接口是可插入Spring IoC容器的實(shí)例化邏輯的一點(diǎn)。如果您擁有復(fù)雜的初始化代碼,而不是(可能)冗長的XML量,可以用Java更好地表達(dá),則可以創(chuàng)建自己的代碼 FactoryBean,在該類中編寫復(fù)雜的初始化,然后將自定義FactoryBean插入容器。
FactoryBean界面提供了三種方法:
Object getObject():返回此工廠創(chuàng)建的對(duì)象的實(shí)例。實(shí)例可以共享,具體取決于該工廠是否返回單例或原型。
boolean isSingleton():true如果FactoryBean返回單例或false其他,則返回 。
Class getObjectType():返回getObject()方法返回的對(duì)象類型,或者null如果類型未知,則返回該對(duì)象類型。
Spring框架中的許多地方都使用了FactoryBean概念和接口。BeanSpring附帶了50多種Factory接口實(shí)現(xiàn)。
當(dāng)您需要向容器詢問FactoryBean本身而不是由其產(chǎn)生的bean的實(shí)際實(shí)例時(shí),請(qǐng)?jiān)谡{(diào)用的方法時(shí)在該bean的id前面加上“&”符號(hào)(&)。