javascript
Spring核心技术(七)——Spring容器的扩展
本文將討論如何關于在Spring生命周期中擴展Spring中的Bean功能。
容器的擴展
通常來說,開發者不需要通過繼承ApplicationContext來實現自己的子類擴展功能。但是Spring IoC容器確實可以通過實現接口來增加一些功能。下面將描述一下這些接口。
通過BeanPostProcessor定義Bean
BeanPostProcessor接口定義了一些回調方法,開發者可以通過實現來自己的實例化邏輯,依賴解析邏輯等等。如果開發者只是想在Spring容器完成了實例化,配置以及初始化Bean之后來做一些操作的話,可以通過使用BeanPostProcessor來做到。
開發者可以配置多個BeanPostProcessor實例,開發者可以通過配置order屬性來指定配置的BeanPostProcessor的執行順序。當然,想要配置順序必須同時要實現Ordered接口。如果開發者寫了自己的BeanPostProcessor,開發者最好同時考慮實現Ordered接口。如果想了解更多的信息,可以參考BeanPostProcessor以及Ordered接口的javadoc。
BeanPostProcessors是操作Bean實例的,換言之,Spring IoC容器必須先初始化好Bean,然后BeanPostProcessors才開始工作。
BeanPostProcessors作用范圍是基于容器的。當然,只有當開發者使用容器的層級的時候才是需要考慮的。如果開發者在容器中定義了一個BeanPostProcessor,這個實例只會在它所在的容器來處理Bean初始化以后的操作。換言之,一個容器中的Bean不會被另一個容器中定義BeanPostProcessor的在初始化以后進行后續處理,甚至就算兩個容器同屬同一個容器的部分。
org.springframework.beans.factory.config.BeanPostProcessor接口包含了2個回調方法。當這個接口的實例注冊到容器當中時,對于每一個由容器創建的實例,這個后置處理器都會在容器開始進行初始化之前獲得回調的調用。后置處理器可以針對Bean實例采取任何的操作,包括完全無視回調函數。Bean的后置處理器通常檢查回調接口或者將Bean用代理包裝一下。一些諸如Spring AOP代理的基礎類都是通過Bean的后續處理器來實現的。
ApplicationContext會自動檢查配置的Bean是否有實現BeanPostProcessor接口,ApplicationContext會將這些Bean注冊為后續處理器,這樣這些后續處理器就會在Bean創建之后調用。Bean的后續處理器就像其他的Bean一樣,由容器管理的。
需要注意的是,在配置類的工廠方法上使用@Bean注解的時候,工廠方法的返回值應該是實現類本身,或者至少為org.springframework.beans.factory.config.BeanPostProcessor接口,來明確表明這個Bean是一個后置處理器。否則,ApplicationContext是無法在自動創建這個Bean的時候來知道它的屬于后置處理器的。因為一個Bean后續處理器是需要按順序來處理容器中的其他的Bean的,所以需要盡早嚴格檢查類型。
盡管Spring團隊推薦的注冊Bean的后續處理的方式是通過ApplicationContext的自動檢查,但是Spring也支持通過編程的方式,通過addBeanPostProcessor方法。這種方式有的時候也很有用,當需要在注冊前執行一些條件判斷的時候,或者在結構化的上下文中復制Bean后續處理器的時候尤其有效。需要注意的是,通過編程實現的BeanPostProcessors是會忽略掉Ordered接口的:由編程注冊的BeanPostProcessors總是在自動檢查到的BeanPostProcessors之前來執行的,而回忽略掉明確的順序定義。
容器會特殊對待那些實現了BeanPostProcessor接口的類。所有的BeanPostProcessors和他們所引用的Bean都會在啟動時直接初始化,作為ApplicationContext啟動的一個特殊階段。然后,所有的BeanPostProcessors會按順序注冊并應用到容器中的Bean。因為AOP自動的代理就是通過BeanPostProcessor來實現的,無論是BeanPostProcessor或者是它本身引用的Bean都不是自動代理的,也不是這樣注入到Bean中的。對于這樣的Bean,開發者應該注意到類似于info的日志信息Bean不適合由所有的BeanPostProcessor接口處理。
需要注意的是,一旦開發者在BeanPostProcessor使用自動裝載或者@Resource裝載了Bean,Spring也許通過類型匹配進入了不被期望的Bean,因此令這些Bean無法自動代理或者后續處理。比如,如果開發者的Bean有注解@Resource的依賴注入而不是直接通過名字屬性來進行依賴注入,那么Spring就會通過類型匹配來找到匹配的依賴。
下面的例子描述了如何寫,注冊和使用BeanPostProcessors。
例子:Hello World BeanPostProcessor方式
第一個例子闡述了基本的用法。例子中展示了BeanPostProcessor的自定義實現,在Bean創建之后調用了toString()方法并打印到控制臺。
參考如下代碼:
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-ispublic 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/langhttp://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 customBeanPostProcessor implementation will output the fact to the system console--><bean class="scripting.InstantiationTracingBeanPostProcessor"/></beans>從XML的配置中可以發現,InstantiationTracingBeanPostProcessor只是簡單的定義成了一個Bean。它甚至連一個名字都沒有,但是它是一個Bean,所以仍然可以進行依賴注入。
下面的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例子:RequiredAnnotationBeanPostProcessor
使用回調接口或者注解和BeanPostProcessor實現是常見的來擴展Spring IoC容器的方式。Spring中的一個例子就是RequiredAnnotationBeanPostProcessor就是用來確保JavaBean的標記有注解的屬性確實注入了依賴。
通過BeanFactoryPostProcessor定義配置元數據
下一個擴展是org.springframework.beans.factory.config.BeanFactoryPostProcessor。語義上來說,這個接口有些類似BeanPostProcessor,只是有一個很大的區別:BeanFactoryPostProcessor操作Bean配置元數據,也就是說,Spring允許BeanFactoryPostProcessor來讀取配置源數據,并且可能會在容器實例初始話Bean之前就改變配置元數據。
如前文所述,開發者可以配置多個BeanFactoryPostProcessors,而且開發者可以控制其具體執行的順序。當然,配置順序是必須實現Ordered接口的。如果實現了自己的BeanFactoryPostProcessor,開發者也應該考慮實現Ordered接口。可以參考BeanFactoryPostProcessor和Ordered接口的javadoc來了解更多細節。
如果開發者希望改變實際的Bean實例(比如由配置元數據創建的對象),那么你需要使用BeanPostProcessor。但是BeanPostProcessor是可能通過BeanFactoryPostProcessor來和Bean實例在技術上進行協同工作的(比如,通過使用BeanFactory.getBean()函數),這樣做會引起一些不成熟的Bean初始化問題,破壞容器標準的標準生命周期。這種行為可能帶來負面的行為,比如繞過Bean的后續處理流程等等。同時,BeanFactoryPostProcessors作用范圍是基于容器的。當然僅僅在使用容器層級的時候才相關。如果開發者在容器中定義了一個BeanFactoryPostProcessor,那么其配置只會應用到那個配置的容器之中。另一個容器中的Bean定義將不會通過BeanFactoryPostProcessors進行后續處理,就算兩個容器都是同一層級中的一部分也是一樣。
當在ApplicationContext中聲明了后續處理器,Bean的后續處理器就會自動的執行,來實現在配置中定義的行為。Spring包括一些預定義好的后續處理器都可以使用,比如PropertyOverrideConfigurer和PropertyPlaceholderCOnfigurer,也能夠使用自定義的BeanFactoryPostProcessor,比如,來注冊自定義屬性編輯器。
ApplicationContext會自動的檢查容器中實現了BeanFactoryPostProcessor接口的Bean。然后合適的時候將其用作工廠的后續處理器。開發者可以像其他的Bean一樣來部署后續處理器。
如果使用BeanPostProcessor,開發者通常不會配置BeanFactoryPostProcessors來延遲初始化。因為如果沒有其他的Bean引用Bean(Factory)PostProcessor,那么后續處理器根本不會實例初始化。所以,令其延遲初始化配置將被Spring無視掉,而且Bean(Factory)PostProcessor將無論是否配置延遲初始化都會被實例化,就算在<beans />配置了default-lazy-init屬性為true。
例子:類名替換BeanFactoryPostProcessor
開發者使用PropertyPlaceholderConfigurer來從使用標準JavaProperties格式的不同文件中來具體化屬性的值。通過這個類,讓開發者可以將應用部署到不同的環境使用不同的屬性,比如數據庫的URLs或者是用戶名密碼等,而不需要修改XML的定義。
參考下面的XML配置,使用DataSource占位符定義了數據庫的一些配置。這個例子也展示了配置的屬性通過額外的Properties文件來配置。在運行時,PropertyPlaceholderConfigurer應用到元數據上,將會替換掉DataSource的一些屬性。替換的值,就是占位符的名字,這些屬性配置是基于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>而實際占位符的值是配置在另一個文件中的,使用的是JavaProperties的格式,如下:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root因此,字符串${jdbc.username}在運行時會被替換為sa,其他的占位符也會依次被替換掉。PropertyPlaceholderConfigurer會在Bean定義中檢查占位符,而且,占位符的前綴后綴也能夠自定義。
Spring 2.5后引入了context命名空間,可以專用的配置元素來配置屬性占位符。多個地址可以使用逗號分隔符。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>PropertyPlaceholderConfigurer不僅僅查看開發者指定的Properties文件。默認的話,它如果不能再指定的屬性文件中找到屬性的話,仍然會檢查Java的System配置。開發者可以通過配置systemPropertiesMode屬性來修改這個行為,這個屬性有如下三種值:
- nerver(0):從不檢查系統屬性
- fallback(1):如果沒有從指定的屬性文件中找到特定的屬性時檢查系統屬性,這個是默認值。
- override(2):優先查找系統屬性,而后才試著檢查指定配置文件。系統的屬性會覆蓋指定文件的屬性。
可以查閱PropertyPlaceholderConfigurer的Javadocs來了解更多的信息。
開發者可以使用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</value></property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>如果類不能夠在運行時解析,那么就會在創建Bean實例的時候失敗。
例子:PropertyOverrideConfigurer
PropertyOverrideConfigurer,是另一個bean的后置處理器,有些類似于PropertyPlaceholderConfigurer,但是區別于后者,原有的定義可能有默認值,或者沒有任何值。如果覆蓋的Properties文件沒有一個確切的Bean屬性,就使用默認的定義。
需要注意的是,Bean定義本身是不會知道被覆蓋的,所以,從XML的定義上很難直觀看到配置被覆蓋了。如果配置了多個PropertyOverrideConfigurer實例定義了不同的值,那么基于覆蓋的機制,最后一個配置會生效。
屬性文件的配置類似如下:
beanName.property=value比如:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb上面的實例文件,可以用于一個名為dataSource的Bean,包含著driver和url的屬性。
復合的配飾名稱也是支持的,但是要求每一個路徑上的組件,需要是非空的,比如:
foo.fred.bob.sammy=123指定的覆蓋的值總是字面值;而不能是其他的Bean依賴,這一約定也會應用到原來的值特指Bean引用的情況下。
在Spring 2.5引入的context命名空間中,也可以指定配置覆蓋的文件屬性如下:
<context:property-override location="classpath:override.properties"/>自定義FactoryBean的初始化邏輯
一些對象本身類似于工廠的可以考慮實現org.springframework.beans.factory.FactoryBean接口。
FactoryBean接口是一種類似于Spring IoC容器的插件化的邏輯。如果當開發者的代碼有復雜的初始化代碼,在配置上使用Java代碼比XML更有效時,開發者可以考慮創建自己的FacotoryBean對象,將復雜的初始化操作放到類中,將自定義的FactoryBean擴展到容器中。
FacotryBean接口提供如下三個方法:
- Object getObject():返回一個工廠創建的對象。實例可被共享,取決于返回Bean的作用域為原型還是單例。
- boolean isSingleton():如果FactoryBean返回單例,為True,否則為False
- Class getObjectType():返回由getObject()方法返回的對象的類型,如果對象類型未知,返回null。
FactoryBean概念和接口廣泛用預Spring框架,Spring本身就有多于50個FactoryBean的實現。
當開發者需要一個FactoryBean實例而不是其產生的Bean的時候,在調用ApplicationContext的getBean()方法時,在其id之前加上&符號。也就是說,對于一個給定的FactoryBean,其id為myBean,調用getBean("myBean")返回其產生的Bean對象,而調用getBean("&myBean")返回FactoryBean實例本身。
轉載于:https://www.cnblogs.com/qitian1/p/6461559.html
總結
以上是生活随笔為你收集整理的Spring核心技术(七)——Spring容器的扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 工厂模式
- 下一篇: linux_2.6内核内存缓冲与I/O调