6.1 兩種后處理器
Spring 框架提供了很好的擴展性,除了可以與各種第三方框架良好整合外,其IoC容器也允許開發者進行擴展。這種擴展并不是通過實現BeanFactory或ApplicationContext的子類,而是通過兩個后處理器對IoC容器進行擴展。Spring提供了兩種常用的后處理器:
?? ● Bean后處理器,這種后處理器會對容器中特定的Bean進行定制,例如功能的??? 加強。
?? ● 容器后處理器,這種后處理器對IoC容器進行特定的后處理。
下面將介紹這兩種常用的后處理器以及兩種后處理器相關知識。
6.1.1 Bean后處理器
Bean后處理器是一種特殊的Bean,這種特殊的Bean并不對外提供服務,它無須id屬性,但它負責對容器中的其他Bean執行后處理,例如為容器中的目標Bean生成代理。這種Bean可稱為Bean后處理器,它在Bean實例創建成功后,對其進行進一步的加強???? 處理。
Bean后處理器必須實現BeanPostProcessor接口。
BeanPostProcessor接口包含兩個方法:
?? ● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個參數是系統即將初始化的Bean實例,第二個參數是Bean實例的名字。
?? ● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,該方法的第一個參數是系統剛完成初始化的Bean實例,第二個參數是Bean實例的名字。
實現該接口的Bean必須實現這兩個方法,這兩個方法會對容器的Bean進行后處理。兩個方法會在目標Bean初始化之前和初始化之后分別調用。這兩個方法用于對系統完成的默認初始化進行加強。
注意:Bean后處理器是對IoC容器一種極好的擴展,Bean后處理器可以對容器中的Bean進行后處理,這種后處理完全由開發者決定。
下面將定義一個簡單的Bean后處理器,該Bean后處理器將對容器中其他Bean進行后處理。Bean后處理器的代碼如下:
//自定義Bean后處理器,負責后處理容器中所有的Bean
public class MyBeanPostProcessor implements BeanPostProcessor
{
??? //在初始化bean之前,調用該方法
??? public Object postProcessBeforeInitialization(Object bean , String
??? beanName)throws BeansException
??? {
??????? //僅僅打印一行字符串
??????? System.out.println("系統正在準備對" + beanName + "進行初始化...");
??????? return bean;
??? }
??? //在初始化bean之后,調用該方法
??? public Object postProcessAfterInitialization(Object bean , String
??? beanName)throws BeansException
??? {
??????? System.out.println("系統已經完成對" + beanName + "的初始化");
??????? //如果系統剛完成初始化的bean是Chinese
??????? if (bean instanceof Chinese)
??????? {
??????????? //為Chinese實例設置name屬性
??????????? Chinese c = (Chinese)bean;
??????????? c.setName("wawa");
????? ?? }
??????? return bean;
??? }
}
下面是Chinese的源代碼,該類實現了InitializingBean接口,還額外提供了一個初始化方法,這兩個方法都由Spring容器控制回調。
public class Chinese implements Person,InitializingBean
{
??? private Axe axe;
??? private String name;
??? public Chinese()
??? {
??????? System.out.println("Spring實例化主調bean:Chinese實例...");
??? }
??? public void setAxe(Axe axe)
??? {
??????? System.out.println("Spring執行依賴關系注入...");
??????? this.axe = axe;
??? }
??? public void setName(String name)
??? {
??????? this.name = name;
??? }
??? public void useAxe()
??? {
??????? System.out.println(name + axe.chop());
??? }
??? public void init()
??? {
??????? System.out.println("正在執行初始化方法?? init...");
??? }
?? public void afterPropertiesSet() throws Exception
??? {
?????? System.out.println("正在執行初始化方法 afterPropertiesSet...");
??? }
}
配置文件如下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring 配置文件的dtd>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
??? "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- Spring配置文件的根元素 -->
<beans>
??? <!-- 配置bean后處理器,可以沒有id屬性,此處id屬性為了后面引用 -->
??? <bean id="beanPostProcessor" class="lee.MyBeanPostProcessor"/>
??? <bean id="steelAxe" class="lee.SteelAxe"/>
??? <bean id="chinese" class="lee.Chinese" init-method="init">
??????? <property name="axe" ref="steelAxe"/>
??? </bean>
</beans>
本應用的chinese具有兩個初始化方法:
?? ● init-method指定初始化方法。
?? ● 實現InitializingBean接口,提供了afterPropertiesSet初始化方法。
MyBeanPostProcessor類實現了BeanPostProcessor接口,并實現了該接口的兩個方法,這兩個方法分別在初始化方法調用之前和之后得到回調。
注意:上面的配置文件配置Bean后處理器時,依然為Bean處理器指定了id屬性,指定id屬性是為了方便程序通過該id屬性訪問Bean后處理器。大部分時候,程序無須手動訪問該Bean后處理器,因此無須為其指定id屬性。
主程序如下:
public class BeanTest
{
??? public static void main(String[] args)throws Exception
??? {
??????? //CLASSPATH路徑下的bean.xml文件創建Resource對象
??????? ClassPathResource isr = new ClassPathResource("bean.xml");
??????? //以Resource對象作為參數,創建BeanFactory的實例
??????? XmlBeanFactory factory = new XmlBeanFactory(isr);
??????? //獲取Bean后處理器實例
??????? MyBeanPostProcessor beanProcessor =
??????????? (MyBeanPostProcessor)factory.getBean("beanPostProcessor");
??????? //注冊BeanPostProcessor實例
??????? factory.addBeanPostProcessor(beanProcessor);
??????? System.out.println("程序已經實例化BeanFactory...");
??????? Person p = (Person)factory.getBean("chinese");
??????? System.out.println("程序中已經完成了chinese bean的實例化...");
??????? p.useAxe();
??? }
}
如果使用BeanFactory作為Spring容器,必須手動注冊Bean后處理器,因此在程序中先獲取Bean后處理器實例,然后手動注冊——這就是在配置文件中指定Bean后處理器id屬性的原因。通過BeanFactory的addBeanPostProcessor可以注冊BeanPostProcessor實例。程序執行結果如下:
[java] 程序已經實例化BeanFactory...
[java] Spring實例化主調bean:Chinese實例...
[java] Spring實例化依賴bean:SteelAxe實例...
[java] 系統正在準備對steelAxe進行初始化...
[java] 系統已經完成對steelAxe的初始化
[java] Spring執行依賴關系注入...
[java] 系統正在準備對chinese進行初始化...
[java] 正在執行初始化方法 afterPropertiesSet...
[java] 正在執行初始化方法?? init...
[java] 系統已經完成對chinese的初始化
[java] 程序中已經完成了chinese bean的實例化...
[java] wawa鋼斧砍柴真快
在配置文件中配置chinese實例時,并未指定name屬性值。但程序執行時,name屬性有了值,這就是Bean后處理器完成的,在Bean后處理器中判斷Bean是否是Chinese實例,然后設置它的name屬性。
容器中一旦注冊了Bean后處理器,Bean后處理器會自動啟動,在容器中每個Bean創建時自動工作,完成加入Bean后處理器需要完成的工作。
實現BeanPostProcessor接口的Bean后處理器可對Bean進行任何操作,包括完全忽略這個回調。BeanPostProcessor通常用來檢查標記接口或將Bean包裝成一個Proxy的事情。Spring的很多工具類,就是通過Bean后處理器完成的。
從主程序中看到,采用BeanFactory作為Spring容器時,必須手動注冊BeanPost- Processor。而對于ApplicationContext,則無須手動注冊。ApplicationContext可自動檢測到容器中的Bean后處理器,自動注冊。Bean后處理器會在Bean實例創建時,自動啟動。即主程序采用如下代碼,效果完全一樣:
public class BeanTest
{
??? public static void main(String[] args)throws Exception
??? {
??????? ApplicationContext ctx = new ClassPathXmlApplicationContext
??????? ("bean.xml");
??????? Person p = (Person)factory.getBean("chinese");
??????? System.out.println("程序中已經完成了chinese bean的實例化...");
??????? p.useAxe();
??? }
}
使用ApplicationContext作為容器,無須手動注冊BeanPostProcessor。因此,如果需要使用Bean后處理器,Spring容器建議使用ApplicationContext,而不是BeanFactory。
6.1.2 Bean后處理器的用處
上一節介紹了一個簡單的Bean后處理器,上面的Bean后處理器負責對容器中的Chinese Bean進行后處理,不管Chinese Bean如何初始化,總是將Chinese Bean的name屬性設置為wawa。這種后處理看起來作用并不是特別大。
實際上,Bean后處理器完成的工作更加實際,例如生成Proxy。Spring框架本身提供了大量的Bean后處理器,這些后處理器負責對容器中的Bean進行后處理。
下面是Spring提供的兩個常用的后處理器:
?? ● BeanNameAutoProxyCreator,根據Bean實例的name屬性,創建Bean實例的代理。
?? ● DefaultAdvisorAutoProxyCreator,根據提供的Advisor,對容器中所有的Bean實例創建代理。
上面提供的兩個Bean后處理器,都用于根據容器中配置的攔截器創建目標Bean代理,目標代理就在目標Bean的基礎上修改得到。
注意:如果需要對容器中某一批Bean進行特定的處理,可以考慮使用Bean后處理器。
6.1.3 容器后處理器
除了上面提供的Bean后處理器外,Spring還提供了一種容器后處理器。Bean后處理器負責后處理容器生成的所有Bean,而容器后處理器則負責后處理容器本身。
容器后處理器必須實現BeanFactoryPostProcessor接口。實現該接口必須實現如下一個方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
實現該方法的方法體就是對Spring容器進行的處理,這種處理可以對Spring容器進行任意的擴展,當然也可以對Spring容器不進行任何處理。
類似于BeanPostProcessor,ApplicationContext可自動檢測到容器中的容器后處理器,并且自動注冊容器后處理器。但若使用BeanFactory作為Spring容器,則必須手動注冊后處理器。
下面定義了一個容器后處理器,這個容器后處理器實現BeanFactoryPostProcessor接口,但并未對Spring容器進行任何處理,只是打印出一行簡單的信息。該容器后處理器的代碼如下:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
??? //容器后處理器對容器進行的處理在該方法中實現
??? public void postProcessBeanFactory(ConfigurableListableBeanFactory
??? beanFactory)
??????? throws BeansException
??? {
??????? System.out.println("程序對Spring所做的BeanFactory的初始化沒有意
??????? 見...");
??? }
}
將該Bean作為普通Bean部署在容器中,然后使用ApplicationContext作為容器,容器會自動調用BeanFactoryPostProcessor處理Spring容器。程序執行效果如下:
[java] 程序對Spring所做的BeanFactory的初始化沒有意見...
實現BeanFactoryPostProcessor接口的Bean后處理器不僅可對BeanFactory執行后處理,也可以對ApplicationContext容器執行后處理。容器后處理器還可用來注冊額外的屬性編輯器。
注意:Spring沒有提供ApplicationContextPostProcessor。也就是說,對于Application- Context容器,一樣使用BeanFactoryPostProcessor作為容器后處理器。
Spring已提供如下兩個常用的容器后處理器,包括:
?? ● PropertyResourceConfigurer,屬性占位符配置器。
?? ● PropertyPlaceHolderConfigurer,另一種屬性占位符配置器。
下面將詳細介紹這兩種常用的容器后處理器。
6.1.4 屬性占位符配置器
Spring提供了PropertyPlaceholderConfigurer,它是一個容器后處理器,負責讀取Java屬性文件里的屬性值,并將這些屬性值設置到Spring容器定義中。
通過使用PropertyPlaceholderConfigurer后處理器,可以將Spring配置文件中的部分設置放在屬性文件中設置。這種配置方式當然有其優勢:可以將部分相似的配置(如數據庫的urls、用戶名和密碼)放在特定的屬性文件中,如果只需要修改這部分配置,則無須修改Spring配置文件,修改屬性文件即可。
下面的配置文件配置了PropertyPlaceholderConfigurer后處理器,在配置數據源Bean時,使用了屬性文件中的屬性值。配置文件的代碼如下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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 -->
??? <bean id="propertyConfigurer"
??????? class="org.springframework.beans.factory.config.
??????? PropertyPlaceholderConfigurer">
??????? <!-- locations屬性指定屬性文件的位置 -->
??????? <property name="locations">
??????????? <list>
??????????????? <value>dbconn.properties</value>
??????????????? <!-- 如果有多個屬性文件,依次在下面列出來 -->
??????????? </list>
??????? </property>
??? </bean>
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="${jdbc.driverClassName}"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="${jdbc.url}"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="${jdbc.username}"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="${jdbc.password}"/>
??? </bean>
</beans>
在上面的配置文件中,配置driverClass和jdbcUrl等信息時,并未直接設置這些屬性的屬性值,而是設置了${jdbc.driverClassName}和${jdbc.url}屬性值。這表明Spring容器將從propertyConfigurer指定屬性文件中搜索這些key對應的value,并為該Bean的屬性值設置這些value值。
如前所述,ApplicationContext會自動檢測部署在容器的容器后處理器,無須額外的注冊,容器自動注冊。因此,只需提供如下Java Properties文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/j2ee
jdbc.username=root
jdbc.password=32147
通過這種方法,可從主XML配置文件中分離出部分配置信息。如果僅需要修改數據庫連接屬性,則無須修改主XML配置文件,只需要修改屬性文件即可。采用屬性占位符的配置方式,可以支持使用多個屬性文件。通過這種方式,可將配置文件分割成多個屬性文件,從而降低修改配置的風險。
注意:對于數據庫連接等信息集中的配置,可以將其配置在Java屬性文件中,但不要過多地將Spring配置信息抽離到Java屬性文件中,否則可能會降低Spring配置文件的可讀性。
6.1.5 另一種屬性占位符配置器(PropertyOverrideConfigurer)
PropertyOverrideConfigurer是Spring提供的另一個容器后處理器,這個后處理器的額作用與上面介紹的容器后處理器作用大致相同。但也存在些許差別:PropertyOverride- Configurer使用的屬性文件用于覆蓋XML配置文件中的定義。即PropertyOverride- Configurer允許XML配置文件中有默認的配置信息。
如果PropertyOverrideConfigurer的屬性文件有對應配置信息,XML文件中的配置信息被覆蓋;否則,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的屬性文件,應是如下的格式:
beanName.property=value
beanName是屬性占位符試圖覆蓋的Bean名,property是試圖覆蓋的屬性名。看如下配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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。ApplictionContext能自動識別
??? PropertyPlaceholderConfigurer Bean -->
??? <bean id="propertyOverrider"
??????? class="org.springframework.beans.factory.config.
??????? PropertyOverrideConfigurer">
??????? <property name="locations">
??????????? <list>
??? ??????????? <value>dbconn.properties</value>
??????????????? <!-- 如果有多個屬性文件,依次在下面列出來 -->
??????????? </list>
??????? </property>
??? </bean>
??? <!-- 定義數據源Bean,使用C3P0數據源實現 -->
??? <bean id="dataSource" class="com.mchange.v2.c3p0.
??? ComboPooledDataSource" destroy-method="close">
??????? <!-- 指定連接數據庫的驅動 -->
??????? <property name="driverClass" value="dd"/>
??????? <!-- 指定連接數據庫的URL -->
??????? <property name="jdbcUrl" value="xx"/>
??????? <!-- 指定連接數據庫的用戶名 -->
??????? <property name="user" value="dd"/>
??????? <!-- 指定連接數據庫的密碼 -->
??????? <property name="password" value="xx"/>
??? </bean>
</beans>
上面的配置文件中,指定數據源Bean的各種屬性值時,只是隨意指定了幾個屬性值,很明顯通過這幾個屬性值無法連接到數據庫服務。
但因為Spring容器中部署了一個PropertyOverrideConfigurer的容器后處理器,而且Spring容器使用ApplicationContext作為容器,它會自動檢測容器中的容器后處理器,無須額外的注冊,容器自動注冊該后處理器。
PropertyOverrideConfigurer后處理器讀取dbconn.properties文件中的屬性,用于覆蓋目標Bean的屬性。因此,如果屬性文件中有dataSource Bean屬性的設置,則配置文件中指定的屬性值將沒有任何作用。
dbconn.properties屬性文件如下:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://wonder:3306/j2ee
dataSource.username=root
dataSource.password=32147
注意屬性文件的格式必須是:
beanName.property=value
也就是說,dataSource必須是容器中真實存在的bean名,否則程序將出錯。
注意:程序無法知道BeanFactory定義是否被覆蓋。僅僅通過察看XML配置文件,無法知道配置文件的配置信息是否被覆蓋。如有多個PorpertyOverrideConfigurer對同一Bean屬性定義了覆蓋,最后一個覆蓋獲勝。
posted on 2009-07-19 10:12
jadmin 閱讀(119)
評論(0) 編輯 收藏