一般來說,一個應用開發者不需要繼承ApplicationContext實現類。取而代之的是,Spring IoC容器可以通過插入特殊的整合接口的實現來進行擴展。下面的幾點將要講述這些整合的接口。
1.使用BeanPostProcessor來定制bean
BeanPostProcessor接口定義了你可以實現的回調方法去提供自己的實例化邏輯、依賴方案邏輯等等。如果你想在Spring 容器完成實例化配置和初始化一個bean之后實現一個自定義邏輯,你可以插入一個或者多個BeanPostProcessor實現。
你可以配置多個BeanPostProcessor實例,你可以通過設置order屬性來控制這些BeanPostProcessor運行的順序。如果你寫自己的BeanPostProcessor你應該考慮實現ordered接口。
注意:BeanPostProcessors作用在一個bean實例上,也就是說,Spring IOC容器實例化一個bean實例然后BeanPostProcessors會在他們上面起作用。BeanPostProcessor在每一個容器中都是受限制域的。如果你使用容器分層結構的話這個是唯一有關系的。如果你在一個容器中定義一個BeanPostProcessor接口,它將知識處理在容器中的bean。換句話說,在一個容器中定義的bean並不能被定義在其他容器中的BeanPostProcessor來處理,即使兩個容器時同一個層次的一部分。為了改變實際的bean定義,你需要使用一個BeanFactoryPostProcessor。
org.springframework.beans.factory.config.BeanPostProcessor 由兩個回調方法組成。當這樣的類在容器中被注冊為一個post-processor時,對於任何一個在容器中創建的bean實例,post-processor從容器中獲得一個回調,在容器初始化方面之前的兩個方法(afterPropertiesSet和init)也被稱為在任何bean初始化回調。post-processor能夠對任何一個實例進行操作,包括完全忽略回調。一個bean post-processor一般會將會檢查回調接口或者是用一個代理來封裝bean。為了提供一個代理封裝邏輯一些Spring AOP框架類作為bean post-processor來實現。
一個ApplicationContext能夠發現實現了BeanPostProcessor接口的定義在配置中的任何bean。ApplicationContext注冊這些bean作為post-processor以便他們在bean創建后可以被調用。bean post-processor就像其他bean一樣被部署在容器中。
注意當在一個配置類中使用@Bean工廠方法聲明一個BeanPostProcessor的時候,工廠方法的返回類型應該實現類自己或者至少是org.springframework.beans.factory.cofig.BeanPostProcessor接口,明確的表明了那個bean的是post-processor。否則,ApplicationContext在完全創建它之前不會通過類型自動發現它,因為為了在上下文中引用到其他bean的初始化一個BeanPostProcessor需要去實例化早些,而且這個早一點的聲明是很關鍵的。
下面就是表明在一個ApplicationContext中定義注冊和使用BeanPostProcessors.
1.1例子:Hello world,BeanPostProcessor風格的
第一個例子只是簡單地使用。這個例子表明一個自定義的BeanPostProcessor實現當它被容器創建的時候調用了toString()方法,並且打印出結果到控制台。
BeanPostProcessor實現類的定義:
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.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.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一樣DI。
下面的簡單Java程序可以允許前面的代碼和配置:
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);
}
}
上面的程序輸出結果:
Bean messenger created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
1.2 例子:RequiredAnnotationBeanPostProcessor
使用回調接口或者注解和一個自定義的BeanPostProcessor實現聯合在一起是常用的方法用來擴展Spring Ioc容器。這個例子是Spring的conjunction-一個BeanPostProcessor實現,它可以保證在bean中JavaBean屬性被標記注解的能夠用一個值來進行依賴注入。
2.使用BeanFactoryPostProcessor來自定義配置數據
下面我們來看下org.springframework.beans.factory.config.BeanFactoryPostProcessor。這個接口的語法和那些BeanPostProcessor類似,有一點不同是:BeanFactoryPostProcessor作用在bean配置數據上;也就是說SpringIOC容器運行一個BeanFactoryPostProcessor去讀取配置數據並且在實例化任何不適BeanFactoryPostProcessor的bean之前修改它的配置。
你可以配置多個BeanFactoryPostProcessors,而且你通過設置orde的順序來控制BeanFactoryPostProcessors運行的順序。但是,如果BeanFactoryPostProcessors實現了ordered接口你才能看到這個屬性。你可以寫自己的BeanFactoryPostProcessors,你應該考慮實現ordered接口。
一個bean工廠post-processor會被自動運行當在一個ApplicationContext內部聲明的時候,這樣做是為了將改變應用到定義容器的配置數據中。SPring包括很多提前定義好的bean 工廠 post-processors,比如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。一個自定義的BeanFactoryPostProcessor也是能夠被使用的,例如為了注冊定義的的屬性編輯器。
一個ApplicationContext能夠自動發現實現了BeanFactoryPostProcessor接口的部署在它里面的任何bean。它使用這些bean作為bean 工廠 post-processor在適當的時機。你可以
2.1例子:類名稱代替PropertyPlaceholderConfigurer
你能夠從使用標准Java 屬性格式的定義在其他文件中的bean定義中使用PropertyPlaceholderConfigurer去具體化屬性值。這樣做,可以使用戶在部署應用的時候自定義環境變量的屬性,比如數據庫url 和密碼,而不用修改XML定義的文件。
看下面的基於XML格式配置的片段,這里面DataSource使用placeholder值來定義的。這個例子表明從外部屬性文件中配置的屬性使用。在運行的時候,一個PropertyPlaceholderConfigurer被應用在將替換數據源屬性的元數據。需要替換的值需要被指明是placeholders的格式${property-name},這個和Ant / log4j / JSP EL 風格類似。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/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>
在其他文件中標准Java屬性格式的實際值:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,字符串 ${jdbc.username}在運行時被替換成值sa,這個也會應用在屬性文件中其他的匹配的值上。PropertyPlaceholderConfigurer在大部分的屬性和bean定義的 屬性中會檢查placeholders的。更多的話placeholders的前綴和后綴也能夠被自定義。
在Spring 2.5中被引用的context 命名空間,這樣可以使用指定的配置元素來定義屬性placeholders。一個多個位置可以用逗號分隔成一個列表的位置屬性。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer不僅僅尋找你知道的屬性文件中的屬性。默認情況下,它還好檢查Java系統屬性如果在指定的屬性文件中找不到的屬性的話。你可以通過設置配置器的systemPropertiesMode為下面三個之一支持的屬性整數值:
-
never(0):永遠不要檢查系統屬性
-
fallback(1):如果在指定的屬性文件中沒有可供解析的話檢查系統屬性值。這個是默認選項。
-
override(2):在你查詢指定的屬性文件之前首先查詢系統屬性。這樣運行系統屬性能夠覆蓋任何其他的屬性源。
注意:你可以使用PropertyPlaceholderConfigurer來代替類名,這個在你必須取一個特別的實現類時是很有用的。例如:
<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</v alue> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果在運行時類不能夠解析為一個合法的類,當它將要被創建時bean的解析就會失敗,這個是在一個非懶加載bean的在一個ApplicationContext的preInstantiateSingletons()的相位之間。
2.2例子:PropertyOverrideConfigurer
PropertyOverrideConfigurer,另外的bean 工廠 post-processor,和PropertyPaceholderConfigurer類似,但是又不像,因為原始的定義有默認值或者對於bean屬性沒有值。如果一個覆蓋的Properties文件對於特定的bean屬性沒有輸入值的話,缺省的上下文定義就會被使用了。
注意Bean定義不知道被覆蓋了,所以在覆蓋的配置在被使用的時候,XML定義的文件不是立即就知道的。在有多個PropertyOverrideConfigurer實例的清下中,可以為同一個bean屬性值定義不同的值,最后一個起作用,這個取決於覆蓋機制。
屬性文件配置像下面的格式:
beanName.property=value
例如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
這個例子在一個包含一個叫做數據源的bean的容器定義中使用,數據源包括驅動和url屬性。
混合屬性名字也是支持的,只要每一個路徑中的組件希望的最后的屬性被覆蓋的不是非空。這個例子有:
foo.fred.bob.sammy=123
foo bean的fred屬性的 bob屬性的 sammy屬性值是123。
使用Spring 2.5中的context命名空間,可以去配置屬性覆蓋通過一個致命的配置元素:
<context:property-override location="classpath:override.properties"/>
3.使用一個工廠bean來定制實例化邏輯
實現了org.springframework.beans.factory.FactoryBean接口的對象都是他們自己的工廠。
FactoryBean接口可以插入到Sring IOC 容器實例化邏輯的一個點。如果你有復雜的處理話代碼,那么最好在Java代碼中表示出來不要使用大量冗雜的XML,你可以創建自己的FactoryBean,寫類內部寫負責的初始化,然后將自己定制的FactoryBean放入容器中。
FactoryBean接口提供了三個方法:
- Object getObject() : 返回這個工廠茶UN該敬愛年的 對象實例。這個實例能夠被共享,這個取決於工廠返回的是單例還是原型。
- boolean isSingleton() :如果FactoryBean返回單例就是返回true否則就是false;
- Class getObjectType() : 返回通過getObject() 方法返回的對象類型,如果類型是未知的那么會返回null.
FactoryBeande 的接口在Spring框架中的很多地方都用到了,Spring自己有查過50個FactoryBean的接口實現。
當你需要去向容器請求一個它自己的真正的FactoryBean實例而不是它產生的bean,在bean的 id前面加上&在調用ApplicationContext的getBean()方法時,加入現在給定一個FactoryBean的id是myBean,調用getBean("myBean")將返回FactoryBean的產生的對象;但是調用getBean("&myBean")將會返回FactoryBean自己的實例