1.8。容器擴展點
一般,應用程序開發人員不須要對ApplicationContext 實現類進行子類化。相反,能夠經過插入特殊集成接口的實現來擴展Spring IoC容器。接下來的幾節描述了這些集成接口。spring
1.8.1。自定義bean實現BeanBeanPostProcessor接口
BeanPostProcessor接口定義了回調方法,您能夠實現這些回調方法來修改默認的bean實例化的邏輯,依賴關係解析邏輯等。
若是您想在Spring容器完成實例化,配置和初始化bean以後實現一些自定義邏輯,則能夠插入一個或多個自定義BeanPostProcessor。sql
您能夠配置多個BeanPostProcessor實例,而且能夠BeanPostProcessor經過實現Ordered 接口設置order屬性來控制這些實例的運行順序。數據庫
@Component public class MyBeanPostProcessor implements BeanPostProcessor, Ordered { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public int getOrder() { return 0; } }
BeanPostProcessor實例操做的是bean的實例。 也就是說,Spring IoC容器實例化一個bean實例, 而後使用BeanPostProcessor對這些實例進行處理加工。 BeanPostProcessor實例是按容器劃分做用域的。 僅在使用容器層次結構時,這纔有意義。 若是BeanPostProcessor在一個容器中定義一個,它將僅對該容器中的bean進行後處理。 換句話說,一個容器中定義的bean不會被BeanPostProcessor另外一個容器中的定義進行後處理, 即便這兩個容器是同一層次結構的一部分也是如此。 BeanPostProcessor修改的是bean實例化以後的內容, 若是要更改實際的bean定義(即bean definition) 您須要使用 BeanFactoryPostProcessor接口.
org.springframework.beans.factory.config.BeanPostProcessor接口剛好由兩個回調方法組成。
當此類被註冊爲容器的post-processor時,對於容器建立的每一個bean實例,post-processor都會在任何bean實例化以後而且在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何聲明的init方法)被使用以前調用。
post-processor能夠對bean實例執行任何操做,也能夠徹底忽略回調。
post-processor一般檢查回調接口,或者能夠用代理包裝Bean。
一些Spring AOP基礎結構類被實現爲post-processor,以提供代理包裝邏輯。apache
ApplicationContext自動檢測實現BeanPostProcessor接口全部bean,注意是要註冊成bean,僅僅實現接口是不能夠的。
請注意,經過使用@Bean工廠方法聲明BeanPostProcessor時,工廠方法的返回類型應該是實現類自己或至少是org.springframework.beans.factory.config.BeanPostProcessor 接口,以清楚地代表該bean的post-processor性質。
不然,ApplicationContext沒法在徹底建立以前按類型自動檢測它。
因爲BeanPostProcessor須要提早實例化以便應用於上下文中其餘bean的初始化,所以這種早期類型檢測相當重要。編程
@Bean public BeanPostProcessor myBeanPostProcessor(){ return new MyBeanPostProcessor(); }
以編程方式註冊BeanPostProcessor實例 雖然推薦的BeanPostProcessor註冊方法是經過ApplicationContext自動檢測, 可是您能夠ConfigurableBeanFactory使用addBeanPostProcessor方法經過編程方式對它們進行註冊。 當您須要在註冊以前評估條件邏輯(好比應用場景是xxx條件才註冊,xxx條件不註冊時), 甚至須要跨層次結構的上下文複製Bean post-processor時,這將很是有用。 可是請注意,以BeanPostProcessor編程方式添加的實例不遵照該Ordered接口。 在這裏,註冊的順序決定了執行的順序。 還要注意,以BeanPostProcessor編程方式註冊的實例老是在經過自動檢測註冊的實例以前進行處理, 而不考慮任何明確的順序。
BeanPostProcessor 實例和AOP自動代理 實現BeanPostProcessor接口的類是特殊的,而且容器對它們的處理方式有所不一樣。 BeanPostProcessor它們直接引用的全部實例和bean在啓動時都會實例化, 做爲ApplicationContext的特殊啓動階段的一部分。 接下來,BeanPostProcessor以排序方式註冊全部實例,並將其應用於容器中的全部其餘bean。 可是由於AOP自動代理的實現是經過BeanPostProcessor接口, 因此在AOP的BeanPostProcessor接口實例化以前的 BeanPostProcessor實例或BeanPostProcessor實例直接引用的bean都沒有資格進行自動代理。 而且對於任何此類bean都沒有任何處理切面的BeanPostProcessor指向他們。 您應該看到一條參考性日誌消息: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)。 這條消息的意思大概就是說這個bean沒有獲得全部BeanPostProcessor的處理 下面分析一下這條日誌的邏輯:咱們不用AOP的BeanPostProcessor用AutowiredAnnotationBeanPostProcessor來看這個狀況 首先這條日誌是在BeanPostProcessorChecker類中打印的, 這個類自己就實現了BeanPostProcessor, Spring容器增長這個processor的代碼以下: //獲取全部的BeanPostProcessor類型的bean //第一個true表示包括非單例的bean //第二個false表示僅查找已經實例化完成的bean,若是是factory-bean則不算入內 String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); //當前beanFactory內的全部post-processor數 + 1 + postBeanNames的數量 //這個數量在後續有個判斷 //beanFactory.getBeanPostProcessorCount() 系統內置processor //1 就是BeanPostProcessorChecker //postProcessorNames.length 就是能掃描到的processor //這個數量之和就是目前系統能看到的全部processor //還有的就多是解析完了某些bean又新增了processor那個不算在內 int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; //add BeanPostProcessorChecker 進入beanPostProcessor鏈 beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); BeanPostProcessorChecker中判斷並打印上邊那條日誌的方法以下: @Override public Object postProcessAfterInitialization(Object bean, String beanName) { //若是當前bean不是postProcessor的實例 //而且不是內部使用的bean //而且this.beanFactory.getBeanPostProcessorCount()小於剛纔相加的值 //三個都知足纔會打印那行日誌 if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) && this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) { if (logger.isInfoEnabled()) { logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() + "] is not eligible for getting processed by all BeanPostProcessors " + "(for example: not eligible for auto-proxying)"); } } return bean; } //當前beanName不爲空,而且對應的bean是容器內部使用的bean則返回true private boolean isInfrastructureBean(@Nullable String beanName) { if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) { BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName); return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE); } return false; } 在看Spring createBean時遍歷postProcessor的代碼 @Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; } 就是經過這麼一個循環來執行後置方法applyBeanPostProcessorsAfterInitialization,前置方法也是這樣的 如今假設咱們有一個自定義的beanPostProcessor裏面須要注入一個咱們自定義的beanA, 那麼在beanPostProcessor被實例化的時候確定會要求注入咱們自定義的beanA, 那麼如今就有多種狀況了: 1.咱們用的set或者構造器注入那beanA會被實例化並注入 2.若是咱們用的@Autowired,當咱們自定義的beanPostProcessor實例化 在AutowiredAnnotationBeanPostProcessor實例化以前,那麼beanA都沒法被注入值 若是在以後,則仍是能夠被注入值 可是這兩種狀況都會打印這行日誌 Bean 'beanA' of type [org.springframework.beanA] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
如下示例顯示瞭如何在ApplicationContext中編寫,註冊和使用BeanPostProcessor實例。app
示例:Hello World,BeanPostProcessor-style
第一個示例演示了基本用法。示例展現了一個自定義BeanPostProcessor實現,它在容器建立每一個bean時調用該bean的toString()方法,並將結果字符串打印到系統控制檯。框架
下面的清單顯示了自定義的BeanPostProcessor實現類定義:編輯器
package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // 只需按原樣返回實例化的bean public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; // 咱們能夠返回任何對象引用 } public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } }
如下beans元素使用InstantiationTracingBeanPostProcessor:ide
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang https://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> <!-- 當上述bean (messenger)被實例化時,這個自定義的BeanPostProcessor實現將事實輸出到系統控制檯 --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans>
請注意實例化tracingbeanpostprocessor是如何定義的。它甚至沒有名稱,並且,由於它是一個bean,因此能夠像其餘bean同樣進行依賴注入。post
下面的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 = ctx.getBean("messenger", Messenger.class); System.out.println(messenger); } }
前面的應用程序的輸出相似於如下內容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
示例: RequiredAnnotationBeanPostProcessor
將回調接口或註解與自定義BeanPostProcessor實現結合使用是擴展Spring IoC容器的一種常見方法。
一個例子是Spring的AutowiredAnnotationBeanPostProcessor——一個隨Spring發行版附帶的BeanPostProcessor實現,它確保被註解(@Autowired,@Value, @Inject等註解)註釋的屬性會被注入一個bean實例。
1.8.2。自定義配置元數據BeanFactoryPostProcessor
咱們要看的下一個擴展點是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。
該接口與BeanPostProcessor主要區別在於:BeanFactoryPostProcessor對Bean配置元數據進行操做。
也就是說,Spring IoC容器容許BeanFactoryPostProcessor讀取配置元數據,並有可能在容器實例化實例任何bean以前更改元數據。
您能夠配置多個BeanFactoryPostProcessor實例,而且能夠BeanFactoryPostProcessor經過設置order屬性來控制這些實例的運行順序。可是,僅當BeanFactoryPostProcessor實現 Ordered接口時才能設置此屬性。
若是但願更改實際bean實例(從配置元數據建立的對象),則須要使用BeanPostProcessor。 儘管在BeanFactoryPostProcessor中使用bean實例在技術上是可行的(例如,經過使用BeanFactory.getBean()), 可是這樣作會致使過早的bean實例化,違反標準的容器生命週期。 這可能會致使負面的反作用,好比繞過bean的後處理。 另外,BeanFactoryPostProcessor實例的做用域爲每一個容器。 這隻有在使用容器層次結構時纔有用。 若是您在一個容器中定義了BeanFactoryPostProcessor,那麼它只應用於該容器中的bean定義。 一個容器中的Bean定義不會被另外一個容器中的BeanFactoryPostProcessor實例進行後處理,即便這兩個容器屬於同一層次結構。
當BeanFactoryPostProcessor在ApplicationContext中聲明時,它將自動運行,以便對定義容器的配置元數據應用更改。
Spring包括許多預約義的bean工廠後處理器,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。
您還可使用自定義BeanFactoryPostProcessor例如,用於註冊自定義屬性編輯器。
ApplicationContext自動檢測部署其中實現BeanFactoryPostProcessor接口的任何bean。在適當的時候,這些bean會被bean factory post-processors來使用。
你也能夠像部署任何其餘bean同樣部署這些自定義的bean factory post-processors。
示例:PropertySourcesPlaceholderConfigurer
您可使用PropertySourcesPlaceholderConfigurer使用標準的Java屬性格式將bean定義中的屬性值外部化到單獨的文件中。這樣,部署應用程序的人員就能夠自定義特定於環境的屬性,好比數據庫url和密碼,而無需修改主XML定義文件或容器文件的複雜性或風險。
考慮如下基於xml的配置元數據片斷,其中定義了具備佔位符值的數據源:
<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文件配置的屬性。
在運行時,將 PropertySourcesPlaceholderConfigurer應用於替換數據源的某些屬性的元數據。將要替換的值指定爲形式的佔位符,該形式${property-name}遵循Ant和log4j和JSP EL樣式。
實際值來自標準Java Properties格式的另外一個文件:
jdbc.driverClassName = org.hsqldb.jdbcDriver jdbc.url = jdbc:hsqldb:hsql://production:9002 jdbc.username = sa jdbc.password = root
所以,${jdbc.username}在運行時將字符串替換爲值「sa」,而且其餘與屬性文件中的鍵匹配的佔位符值也適用。
在PropertySourcesPlaceholderConfigurer爲大多數屬性和bean定義的屬性佔位符檢查。此外,您能夠自定義佔位符前綴和後綴。
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="locations" value="classpath:jdbc.properties"/> //自定義前綴後綴 <property name="placeholderPrefix" value="${"/> <property name="placeholderSuffix" value="}"/> </bean>
1.8.3。自定義實例化邏輯FactoryBean
您能夠org.springframework.beans.factory.FactoryBean爲自己就是工廠的對象實現接口。
該FactoryBean接口是可插入Spring IoC容器的實例化邏輯的一點。
若是您有複雜的初始化代碼,而不是(可能)冗長的XML,能夠用Java更好地表達,則以建立本身的代碼 FactoryBean, 在該類中編寫複雜的初始化,而後將自定義FactoryBean插入容器。
該FactoryBean界面提供了三種方法:
- Object getObject():返回此工廠建立的對象的實例。實例能夠共享,具體取決於該工廠是否返回單例或原型。
- boolean isSingleton():true若是FactoryBean返回單例或false其餘則返回 。
- Class getObjectType():返回getObject()方法返回的對象類型,或者null若是類型未知,則返回該對象類型。
FactoryBeanSpring框架中的許多地方都使用了該概念和接口。Spring附帶了50多種FactoryBean接口實現。Spring中的瞭解的少,可是Mybatis的MybatisSqlSessionFactoryBean很出名。
當您須要向容器詢問FactoryBean自己而不是由它產生的bean的實際實例時,請在調用的方法時在該bean的id前面加上「&」符號(&)。
所以,對於給定id爲myBean的一個FactoryBean ,調用getBean("myBean")返回的是FactoryBean生成的實例,getBean("&myBean")返回的是FactoryBean自己。
public class MyFactoryBean implements FactoryBean<MyBean> { @Override public MyBean getObject() throws Exception { return new MyBean(); } @Override public Class<?> getObjectType() { return MyBean.class; } } <bean id="myFactoryBean" class="org.springframework.example.factoryBean.MyFactoryBean"/> getBean("myFactoryBean") 返回的是MyBean實例 getBean("&myFactoryBean") 返回的是MyFactoryBean實例