IoC容器8——容器擴展點

容器擴展點

通常的,一個應用開發者不須要擴展ApplicationContext的實現類。相反,Spring IoC容器能夠經過插入特殊的集成接口的實現被擴展。mysql

1 使用 BeanPostProcessor 自定義bean

BeanPostProcessor接口定義了一些回調函數,能夠經過實現它們提供本身的(覆蓋容器默認的)實例化邏輯、依賴關係解析邏輯等等。若是想在Spring容器完成實例化、配置和初始化bean後實現一些用戶邏輯,能夠插入一個或多個BeanPostProcessor實現。spring

能夠配置多個BeanPostProcessor實例,而且能夠經過設置order屬性來控制BeanPostProcessor的執行順序。只有BeanPostProcessor實現了Ordered接口才能設置這個屬性;若是本身編寫BeanPostProcessor應該考慮實現Ordered接口。sql

BeanPostProcessor操做bean或者對象的實例,所以Spring IoC容器實例化一個bean的實例而後BeanPostProcessor開始工做。數據庫

BeanPostProcessor的做用域是每一個容器一個。這與使用容器層次結構有關。若是在一個容器中定義了一個BeanPostProcessor,它僅僅「後處理」這個容器中的bean。也就是說,在一個容器中定義的bean不會被另外一個容器定義的BeanPostProcessor後處理,即便兩個容器是同一層次結構的一部分。apache

爲了改變實際的bean定義(即定義bean的藍圖),須要使用BeanFactoryPostProcessor。編程

org.springframework.beans.factory.config.BeanPostProcessor接口包括兩個回調方法。當這樣的類被註冊爲容器的後處理器,對於容器建立的每個bean實例,後處理器從容器獲取回調,在容器調用初始化方法(例如InitializingBean的afterPropertiesSet()以及任何聲明初始化方法)以前以及任何bean的初始化回調以後。後處理器能夠對bean實例進行任何操做,包括徹底忽略回調(???)。一個bean後處理器一般檢查回調接口或者用一個代理包裹一個bean。一些Spring AOP基礎設置類是bean後處理器的實現,爲了提供代理包裹邏輯。編輯器

ApplicationContext自動探測在配置元數據中定義的實現了BeanPostProcessor接口的bean。ApplicationContext註冊這些bean爲後處理器,這樣它們會在bean建立後被調用。Bean後處理器能夠像其它bean同樣在容器中發佈。ide

注意,當在配置類中使用@Bean工廠方法聲明一個BeanPostProcessor,工廠方法的返回類型應該是實現類自身或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,以明確的代表bean的後處理器屬性。不然ApplicationContext在徹底建立後不一樣經過類型自動發現它。因爲一個BeanPostProcessor須要在上下文中的其它bean實例化以前被實例化,因此這種預先類型檢測機制是必須的。函數

儘管推薦的BeanPostProcessor的註冊方法是經過ApplicationContext的自動探測機制(上文所述),可是也可使用ConfigurableBeanFactory的addBeanPostProcessor方法編程的註冊它們。這在須要註冊以前執行條件邏輯或者在層次結構中將bean後處理器拷貝到其它上下文時有用。須要注意的是經過編程方法添加BeanPostProcessor不遵照Ordered接口。這裏註冊的順序意味着執行的順序。此外編程方法註冊的後處理器老是在自動檢測方法註冊的後處理器以前執行,無論任何順序。post

實現了BeanPostProcessor接口的類是特殊的而且被容器不一樣對待。全部的BeanPostProcessor和它們的直接依賴在啓動時實例化,做爲ApplicationContext特殊啓動階段的一部分。而後,全部BeanPostProcessor以排序的方式註冊而且應用於容器中的全部其它bean。由於AOP自動代理自身也實現了BeanPostProcessor,因此BeanPostProcessor和它的直接依賴都不能被自動代理,所以不要對它們編制面。

對任何這樣的bean(使用了自動代理),能夠看到消息日誌:「Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)」。

注意,若是bean使用自動裝配或者@Resource(可能回退到自動裝配)注入到BeanPostProcessor,當搜索類型匹配的依賴候選者時,Spring也許訪問了不指望的bean,所以使得它們不能被自動代理者或者使得它們成爲其它類型的bean後處理器。例如,若是使用@Resource註釋的依賴項,其中字段/ setter名稱與bean的已聲明名稱不直接對應而且沒有使用name屬性,那麼Spring將訪問其餘bean以便按類型匹配它們。

下面的例子展現如何在ApplicationContext中編寫、註冊和使用BeanPostProcessor。

例子:Hello World,BeanPostProcessor風格

第一個例子展現基礎用法。例子展現一個用戶BeanPostProcessor實現,它當容器建立每個bean的時候調用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-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同樣進行依賴注入。上面的配置還定義了一個由Groovy腳本支持的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——Spring distribution附帶的BeanPostProcessor實現,它保證使用註解註釋的JavaBean的屬性確實依賴注入了值。

2 使用BeanFactoryPostProcessor自定義配置元數據

org.springframework.beans.factory.config.BeanFactoryPostProcessor是另外一個擴展點。這個接口的語義與BeanPostProcessor相似,但有一點主要的不一樣:BeanFactoryPostProcessor操做bean配置元數據;即Spring IoC容器容許BeanFactoryPostProcessor讀取配置元數據而且在容器實例化除BeanFactoryPostProcessor以外的任何bean以前改變它。

能夠配置多個BeanFactoryPostProcessor,能夠控制BeanFactoryPostProcessor的執行順序,經過設置order屬性。然而只有當BeanFactoryPostProcessor實現了Ordered接口才能設置order屬性。若是本身實現BeanFactoryPostProcessor,建議實現Ordered接口。

若是想要改變實際的bean實例(即根據配置元數據建立的對象),須要使用BeanPostProcessor。儘管技術上使用BeanFactoryPostProcessor來修改bean實例(例如使用BeanFactory.getBean()),這麼作致使bean過早的實例化,違反了標準的容器生命週期。這可能會致使負做用,如bypassing bean 後處理(???)。

BeanFactoryPostProcessor的做用域是每一個容器一個。這與使用容器層次結構有關。若是在一個容器中定義了一個BeanFactoryPostProcessor,它僅僅應用於這個容器中的bean定義。也就是說,在一個容器中中的bean定義不會被另外一個容器定義的BeanFactoryPostProcessor後處理,即便兩個容器是同一層次結構的一部分。

當一個bean工廠後處理器在ApplicationContext中聲明,它會被自動執行,以便對容器中的配置元數據定義進行修改。Spring包含了一些預約義的bean工廠後處理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。也可使用一個用戶定義的BeanFactoryPostProcessor,例如註冊自定義屬性編輯器(???)。

ApplicationContext自動檢測發佈在其中的實現BeanFactoryPostProcessor接口的bean。它在適當的時候把這些bean做爲bean工廠後處理器使用。能夠像其它bean同樣發佈這些後處理器bean。

與BeanPostProcessor同樣,一般不須要將BeanFactoryPostProcessor配置爲懶加載。若是是懶加載,若是沒有其它bean引用一個Bean(Factory)PostProcessor,後處理器將不會被實例化。所以,將其標註懶加載將會被忽略,而且Bean(Factory)PostProcessor會被當即實例化,甚至在<bean/>元素中將default-lazy-init屬性設置爲true。

例子:類名替換 PropertyPlaceholderConfigurer

可使用PropertyPlaceholderConfigurer能夠將bean定義的屬性外部化到單獨的使用標準Java屬性格式文件中。這麼作能夠在發佈應用時自定義面向環境的屬性例如數據庫URL、密碼,從而避免修改主XML定義文件或者容器文件的複雜性和風險。

考慮基於XML的配置元數據段,定義了使用佔位符值的DataSource。下面的例子展現了在外部Properties文件定義屬性值。在運行時,PropertyPlaceholderConfigurer被應用於元數據,替代Datasource的一些屬性。被替代的值被指定爲${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 Properties格式:

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命名空間,可使用專用的配置元素配置屬性佔位符。在location屬性中,一個或多個定位可使用逗號分隔的列表提供。

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

PropertyPlaceholderConfiguerer不只僅在指定的Properties文件中查找屬性。默認的,若是沒有在指定的配置文件中找到,它也檢查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</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

若是沒法在運行時解析爲有效的類,那麼在建立bean時會解析失敗,這是在ApplicationContext非延遲初始化Bean的preInstantiateSingletons()階段期間。

例子: PropertyOverrideConfigurer

PropertyOverrideConfigurer是另外一個bean工廠後處理器,相似PropertyPlaceholderConfigurer,可是與之不一樣的是,原始的bean定義可使用默認值或者不使用值。若是覆蓋的配置文件沒有一個bean屬性的鍵,將使用默認上下文定義。

請注意,bean定義不知道被覆蓋,因此從XML定義文件不能判斷覆蓋配置程序正在被使用。在多個PropertyOverrideConfigurer實例爲同一個bean屬性定義不一樣的值的狀況,因爲覆蓋機制會使用最後一個。

配置文件定義行使用下面的格式:

beanName.property=value

例如:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

這個示例文件能夠被用於一個包含數據源bean的容器定義中,數據源包含driver和url屬性。

也支持複合屬性名,只要出了最終被覆蓋的屬性以外的每一個組件都是非空的(通常由構造函數實例化)。在下面的例子中:

foo.fred.bob.sammy=123

foo bean的fred屬性的bob屬性的sammy屬性被設置爲純量123.

指定的覆蓋值老是字面值,它們不能被翻譯成bean引用。當XML中的bean定義的原始值指定了 bean 引用時,這個約定也適用。

使用Spring 2.5引入的context命名空間,可使用專用的配置元素配置屬性覆蓋。

<context:property-override location="classpath:override.properties"/>

3 使用FactoryBean自定義實例化邏輯

實現org.springframework.beans.factory.FactoryBean接口的對象自身就是工廠類。

FactoryBean接口是Spring IoC容器的實例化邏輯的可插入點。若是有複雜的實例化代碼,用Java代碼能夠更好的表達,而不是(可能)冗長的XML,能夠建立本身的FactoryBean,在類中編寫複雜的實例化邏輯,而後將自定義的FactoryBean插入容器中。

FactoryBean接口提供三個方法:

  • Object getObject():返回工廠建立的一個對象實例。實例可能被共享,取決於工廠返回singletons仍是prototypes。
  • boolean isSingleton():若是FactoryBean返回singleton,則返回true,不然返回false。
  • Class getObjectType():返回對象的類型,若是實現不知道類型則返回null。

FactoryBean的概念和接口在Spring Framework中的許多地方都被用到;Spring自身有超過50個FactoryBean接口的實現。

若是須要向容器獲取一個實際的FactoryBean實例自身替代它生產的bean,在調用ApplicationContext的getBean()方法時,在bean的id以前加&符號。因此,對於給定的FactoryBean,id爲myBean,調用容器的getBean("myBean")返回FactoryBean生產的對象,調用getBean("&myBean")返回FactoryBean實例自己。

相關文章
相關標籤/搜索