容器的擴(kuò)展
通常來(lái)說(shuō)商佛,開(kāi)發(fā)者不需要通過(guò)繼承ApplicationContext來(lái)實(shí)現(xiàn)自己的子類擴(kuò)展功能。但是Spring IoC容器確實(shí)可以通過(guò)實(shí)現(xiàn)接口來(lái)增加一些功能狰腌。下面將描述一下這些接口击纬。
接下來(lái)主要分是哪個(gè)部分來(lái)講解:
1.通過(guò) BeanPostProcessor后置處理器來(lái)對(duì)一個(gè)bean(
可以是監(jiān)控bean或者增加bean
)
2.通過(guò)BeanFactoryPostProcessor工廠后置bean處理器來(lái)定義配置元數(shù)據(jù)(容器實(shí)例初始話Bean之前就改變配置元數(shù)據(jù)
)
3.自定義FactoryBean的初始化邏輯
1.通過(guò) BeanPostProcessor后置處理器來(lái)對(duì)一個(gè)bean
BeanPostProcessor接口定義了一些回調(diào)方法球恤,開(kāi)發(fā)者可以通過(guò)實(shí)現(xiàn)來(lái)自己的實(shí)例化邏輯哀九,依賴解析邏輯等等
剿配。如果開(kāi)發(fā)者只是想在Spring容器完成了實(shí)例化搅幅,配置以及初始化Bean之后來(lái)做一些操作的話阅束,可以通過(guò)使用BeanPostProcessor來(lái)做到
。
開(kāi)發(fā)者可以配置多個(gè)BeanPostProcessor實(shí)例茄唐,開(kāi)發(fā)者可以通過(guò)配置order屬性來(lái)指定配置的BeanPostProcessor的執(zhí)行順序
息裸。當(dāng)然,想要配置順序必須同時(shí)要實(shí)現(xiàn)Ordered接口
沪编。如果開(kāi)發(fā)者寫(xiě)了自己的BeanPostProcessor呼盆,開(kāi)發(fā)者最好同時(shí)考慮實(shí)現(xiàn)Ordered接口
BeanPostProcessors是操作Bean實(shí)例的
,換言之蚁廓,Spring IoC容器必須先初始化好Bean访圃,然后BeanPostProcessors才開(kāi)始工作
。
BeanPostProcessors作用范圍是基于容器的
相嵌。當(dāng)然腿时,只有當(dāng)開(kāi)發(fā)者使用容器的層級(jí)的時(shí)候才是需要考慮的。如果開(kāi)發(fā)者在容器中定義了一個(gè)BeanPostProcessor饭宾,這個(gè)實(shí)例只會(huì)在它所在的容器來(lái)處理Bean初始化以后的操作
批糟。換言之,一個(gè)容器中的Bean不會(huì)被另一個(gè)容器中定義BeanPostProcessor的在初始化以后進(jìn)行后續(xù)處理看铆,甚至就算兩個(gè)容器同屬同一個(gè)容器的部分徽鼎。
org.springframework.beans.factory.config.BeanPostProcessor
接口包含了2個(gè)回調(diào)方法
。當(dāng)這個(gè)接口的實(shí)例注冊(cè)到容器當(dāng)中時(shí)弹惦,對(duì)于每一個(gè)由容器創(chuàng)建的實(shí)例否淤,這個(gè)后置處理器都會(huì)在容器開(kāi)始進(jìn)行初始化之前獲得回調(diào)的調(diào)用。后置處理器可以針對(duì)Bean實(shí)例采取任何的操作棠隐,包括完全無(wú)視回調(diào)函數(shù)叹括。Bean的后置處理器通常檢查回調(diào)接口或者將Bean用代理包裝一下
。一些諸如Spring AOP代理的基礎(chǔ)類都是 通過(guò)Bean的后續(xù)處理器來(lái)實(shí)現(xiàn)的
宵荒。
ApplicationContext會(huì)自動(dòng)檢查配置的Bean是否有實(shí)現(xiàn)BeanPostProcessor接口汁雷,ApplicationContext會(huì)將這些Bean注冊(cè)為后續(xù)處理器
,這樣這些后續(xù)處理器就會(huì)在Bean創(chuàng)建之后調(diào)用
报咳。Bean的后續(xù)處理器就像其他的Bean一樣侠讯,由容器管理的
。
盡管Spring團(tuán)隊(duì)推薦的注冊(cè)Bean的后續(xù)處理的方式是通過(guò)ApplicationContext的自動(dòng)檢查暑刃,但是
Spring也支持通過(guò)編程的方式厢漩,通過(guò)addBeanPostProcessor方法
。這種方式有的時(shí)候也很有用岩臣,當(dāng)需要在注冊(cè)前執(zhí)行一些條件判斷的時(shí)候溜嗜,或者在結(jié)構(gòu)化的上下文中復(fù)制Bean后續(xù)處理器的時(shí)候尤其有效宵膨。需要注意的是,通過(guò)編程實(shí)現(xiàn)的BeanPostProcessors是會(huì)忽略掉Ordered接口的
:由編程注冊(cè)的BeanPostProcessors總是在自動(dòng)檢查到的BeanPostProcessors之前來(lái)執(zhí)行的炸宵,而回忽略掉明確的順序定義辟躏。
容器會(huì)特殊對(duì)待那些實(shí)現(xiàn)了BeanPostProcessor接口的類。所有的BeanPostProcessors和他們所引用的Bean都會(huì)在啟動(dòng)時(shí)直接初始化
土全,作為ApplicationContext啟動(dòng)的一個(gè)特殊階段
捎琐。然后,所有的BeanPostProcessors會(huì)按順序注冊(cè)并應(yīng)用到容器中的Bean
下面通過(guò)一個(gè)例子來(lái)演示一下
/**
* @Project: spring
* @description: 模擬一個(gè)普通的bean
* @author: sunkang
* @create: 2018-09-16 15:06
* @ModificationHistory who when What
**/
public class BeanDemo{
public BeanDemo() {
System.out.println("beanDemo已經(jīng)初始化了");
}
}
/**
* 模擬 BeanPostProcessor來(lái)監(jiān)控其他bean,進(jìn)行相應(yīng)的處理
*/
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor,Ordered {
//在bean初始化之后調(diào)用
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
System.out.println("beforeBean ''" + beanName + "'' created : " + bean.toString());
return bean;
}
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
System.out.println("afterBean ''" + beanName + "'' created : " + bean.toString());
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
在spring-extensionPoint.xml的配置中如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanPostProcessor" class="com.spring.extensionPoint.InstantiationTracingBeanPostProcessor" />
<bean class="com.spring.extensionPoint.BeanDemo"/>
</beans>
測(cè)試的演示:
ApplicationContext context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
測(cè)試結(jié)果如下:可以發(fā)現(xiàn)BeanPostProcessor的兩個(gè)回調(diào)方法在容器構(gòu)造方法之后進(jìn)行執(zhí)行的
beanDemo已經(jīng)初始化了
beforeBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
afterBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
spring的例子:
RequiredAnnotationBeanPostProcessor
使用回調(diào)接口或者注解和BeanPostProcessor實(shí)現(xiàn)是常見(jiàn)的來(lái)擴(kuò)展Spring IoC容器的方式裹匙。Spring中的一個(gè)例子就是RequiredAnnotationBeanPostProcessor就是用來(lái)確保JavaBean的標(biāo)記有注解的屬性確實(shí)注入了依賴
瑞凑。
2.通過(guò)BeanFactoryPostProcessor定義配置元數(shù)據(jù)
org.springframework.beans.factory.config.BeanFactoryPostProcessor
。語(yǔ)義上來(lái)說(shuō)概页,這個(gè)接口有些類似BeanPostProcessor
籽御,只是有一個(gè)很大的區(qū)別:BeanFactoryPostProcessor操作Bean配置元數(shù)據(jù),也就是說(shuō)惰匙,Spring允許BeanFactoryPostProcessor來(lái)讀取配置源數(shù)據(jù)技掏,并且可能會(huì)在容器實(shí)例初始話Bean之前就改變配置元數(shù)據(jù)
。
如前文所述徽曲,開(kāi)發(fā)者可以配置多個(gè)BeanFactoryPostProcessors零截,而且開(kāi)發(fā)者可以控制其具體執(zhí)行的順序。當(dāng)然秃臣,配置順序是必須實(shí)現(xiàn)Ordered接口的涧衙。如果實(shí)現(xiàn)了自己的BeanFactoryPostProcessor,開(kāi)發(fā)者也應(yīng)該考慮實(shí)現(xiàn)Ordered接口
在ApplicationContext中聲明了后續(xù)處理器奥此,Bean的后續(xù)處理器就會(huì)自動(dòng)的執(zhí)行弧哎,來(lái)實(shí)現(xiàn)在配置中定義的行為。Spring包括一些預(yù)定義好的后續(xù)處理器都可以使用稚虎,比如PropertyOverrideConfigurer
和PropertyPlaceholderCOnfigurer
撤嫩,也能夠使用自定義的BeanFactoryPostProcessor
,比如蠢终,來(lái)注冊(cè)自定義屬性編輯器
序攘。
例子,類名替換BeanFactoryPostProcessor
下面就開(kāi)始演示一下
/**
* @Project: spring
* @description: 模擬PropertyPlaceholderConfigurer來(lái)進(jìn)行測(cè)試
* @author: sunkang
* @create: 2018-09-16 16:15
* @ModificationHistory who when What
**/
public class DataSourceConfig {
private String driverClassName;
private String url;
private String username;
private String password;
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "DataSourceConfig{" +
"driverClassName='" + driverClassName + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
在extensionPoint目錄下放置datasouce.properties文件寻拂,文件內(nèi)容如下
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
在spring-extensionPoint.xml配置如下
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:extensionPoint/datasouce.properties"/>
</bean>
<bean id="dataSource" class="com.spring.extensionPoint.DataSourceConfig">
<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>
測(cè)試方法:
ApplicationContext context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
DataSourceConfig config= context.getBean("dataSource",DataSourceConfig.class);
System.out.println(config);
測(cè)試結(jié)果如下:
DataSourceConfig{driverClassName='com.mysql.jdbc.Driver', url='jdbc:mysql:mydb', username='sa', password='root'}
Spring 2.5后引入了context命名空間程奠,可以專用的配置元素來(lái)配置屬性占位符。多個(gè)地址可以使用逗號(hào)分隔符祭钉。
<context:property-placeholder location="classpath:extensionPoint/override.properties"/>
PropertyPlaceholderConfigurer不僅僅查看開(kāi)發(fā)者指定的Properties文件瞄沙。默認(rèn)的話,它如果不能再指定的屬性文件中找到屬性的話,仍然會(huì)檢查Java的System配置
距境。開(kāi)發(fā)者可以通過(guò)配置systemPropertiesMode
屬性來(lái)修改這個(gè)行為申尼,這個(gè)屬性有如下三種值:
nerver(0)
:從不檢查系統(tǒng)屬性
fallback(1)
:如果沒(méi)有從指定的屬性文件中找到特定的屬性時(shí)檢查系統(tǒng)屬性,這個(gè)是默認(rèn)值
垫桂。
override(2)
:優(yōu)先查找系統(tǒng)屬性师幕,而后才試著檢查指定配置文件。系統(tǒng)的屬性會(huì)覆蓋指定文件的屬性伪货。
接下來(lái)看看PropertyPlaceholderConfigurer的源碼们衙,先看下PropertyPlaceholderConfigurer的繼承關(guān)系钾怔,發(fā)現(xiàn)有beanFactoryPostProcessor
在PropertyResourceConfigurer實(shí)現(xiàn)了BeanFactoryPostProcessor碱呼,那么postProcessBeanFactory就是主入口了,后面的流程有興趣可以自己看看宗侦。
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
//從特定的文件加載屬性到mergedProps中
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
//這里的處理會(huì)properties屬性,會(huì)生成一個(gè)解析器愚臀,然后注入到容器中,裝載beand的時(shí)候再來(lái)回調(diào)這個(gè)解析器
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
另外一個(gè)例子是PropertyOverrideConfigurer
PropertyOverrideConfigurer矾利,是另一個(gè)bean的后置處理器
姑裂,有些類似于PropertyPlaceholderConfigurer,但是區(qū)別于后者男旗,原有的定義可能有默認(rèn)值舶斧,或者沒(méi)有任何值。如果覆蓋的Properties文件沒(méi)有一個(gè)確切的Bean屬性察皇,就使用默認(rèn)的定義
茴厉。
覆蓋的屬性如下,注意但是要求每一個(gè)路徑上的組件什荣,需要是非空的矾缓,也就是dataSource該組件不能為空
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
在具體的xml配置如下,那么dataSource.driverClassName和url將被覆蓋,沒(méi)有覆蓋的值保持原樣
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations" value="classpath:extensionPoint/override.properties"/>
</bean>
在Spring 2.5引入的context命名空間中稻爬,也可以指定配置覆蓋的文件屬性如下:
<context:property-override location="classpath:extensionPoint/override.properties"/>
3.自定義FactoryBean的初始化邏輯
FactoryBean接口是一種類似于Spring IoC容器的插件化的邏輯
嗜闻。如果當(dāng)開(kāi)發(fā)者的代碼有復(fù)雜的初始化代碼,在配置上使用Java代碼比XML更有效時(shí)桅锄,開(kāi)發(fā)者可以考慮創(chuàng)建自己的FacotoryBean對(duì)象琉雳,將復(fù)雜的初始化操作放到類中,將自定義的FactoryBean擴(kuò)展到容器中
友瘤。
FacotryBean接口提供如下三個(gè)方法
:
Object getObject()
:返回一個(gè)工廠創(chuàng)建的對(duì)象翠肘。實(shí)例可被共享,取決于返回Bean的作用域?yàn)樵瓦€是單例商佑。
boolean isSingleton()
:如果FactoryBean返回單例锯茄,為T(mén)rue,否則為False
Class getObjectType()
:返回由getObject()方法返回的對(duì)象的類型,如果對(duì)象類型未知肌幽,返回null晚碾。
FactoryBean概念和接口廣泛用預(yù)Spring框架,Spring本身就有多于50個(gè)FactoryBean的實(shí)現(xiàn)
喂急。
當(dāng)開(kāi)發(fā)者需要一個(gè)FactoryBean實(shí)例而不是其產(chǎn)生的Bean的時(shí)候格嘁,在調(diào)用ApplicationContext的getBean()方法時(shí),在其id之前加上&符號(hào)廊移。也就是說(shuō)糕簿,對(duì)于一個(gè)給定的FactoryBean,其id為myBean狡孔,調(diào)用getBean("myBean")返回其產(chǎn)生的Bean對(duì)象懂诗,而調(diào)用getBean("&myBean")返回FactoryBean實(shí)例本身
。
下面進(jìn)行例子演示:
/**
* @Project: spring
* @description: 說(shuō)明是一個(gè)factroyBean 是一個(gè)bean苗膝,但是是一個(gè)工廠bean
* @author: sunkang
* @create: 2018-09-16 16:34
* @ModificationHistory who when What
**/
public class FactoryBeanDemo implements FactoryBean {
@Override
public Object getObject() throws Exception {
return "factoryBean";
}
@Override
public Class<?> getObjectType() {
return String.class;
}
}
在spring-extensionPoint.xml具體的配置如下:
<bean id="factoryBean" class="com.spring.extensionPoint.FactoryBeanDemo"/>
測(cè)試如下:
ApplicationContext context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
//當(dāng)其id為factoryBean殃恒,調(diào)用getBean("factoryBean")返回其產(chǎn)生的Bean對(duì)象,
//而調(diào)用getBean("&factoryBean")返回FactoryBean實(shí)例本身辱揭。
FactoryBeanDemo factoryBeanDemo = context.getBean("&factoryBean",FactoryBeanDemo.class);
System.out.println("得到工廠:factory"+factoryBeanDemo);
//得到bean對(duì)象离唐,其實(shí)是工廠bean調(diào)用了getObject方法產(chǎn)生的對(duì)象
System.out.println("得到具體的bean:"+context.getBean("factoryBean"));
結(jié)果如下: 結(jié)果很顯然
得到工廠:factorycom.spring.extensionPoint.FactoryBeanDemo@5025a98f
得到具體的bean:factoryBean