Spring基礎五:容器功能擴展

Spring容器不僅提供了強大的配置方法,還提供了容器功能擴展點,用戶可以基於這些擴展點代碼可以增強容器的功能。實際上,Spring很多模塊,比如AOP,對註解的支持等,都是基於該機制實現的。

通過BeanPostProcessor定製Bean

BeanPostProcessor定義了一個能對Bean的初始化進行定製的接口。如果我們實現一個BeanPostProcessor,並註冊到容器,就能對bean的初始化和配置過程進行干預。

先來看看實現BeanPostProcessor的簡單例子:

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

回調方法postProcessBeforeInitialization發生在bean的初始化回調被調用(見上一節)之前,該方法返回bean的實例,在這裏我們可以給bean創建一個proxy返回給容器(這正是Spring AOP的實現方式)。回調方法postProcessAfterInitialization發生在bean的初始化方法被調用之後。

由於我們拿到了bean的引用,理論上,我們可以對bean做任何操作。

註冊BeanPostProcessor

BeanPostProcessor本身就是一個bean,因此它的定義方法與其他的bean沒有任何區別,一行簡單的定義即可:<bean class="scripting.InstantiationTracingBeanPostProcessor"/>

容器如果發現某個bean是一個BeanPostProcessor,那麼會提前初始化它。如果不是通過xml,而是通過java配置代碼來定義bean,返回的類型(Type)必須實現了BeanPostProcessor,否則容器無法識別這是一個BeanPostProcessor:

public class ConfigurationClass {
	@Bean
	public Type postProcessor() {
		.....
	}
}

我們還可以調用發context.getBeanFactory().addBeanPostProcessor()動態地註冊BeanPostProcessor。

BeanPostProcessor執行順序

如果有多個BeanPostProcessor,那麼可以通過實現Order接口來定義他們的相對執行順序。

如果是context.getBeanFactory().addBeanPostProcessor()添加的BeanPostProcessor,那麼那麼即使實現了Order接口也會被忽略,而是按註冊的順序執行;而且他們的順序在所有靜態BeanPostProcessor之前。

由於BeanPostProcessor也是一個bean,它自然可以依賴其他bean,這樣一來被依賴的bean將不會被任何BeanPostProcessor處理。這種情形是否有問題,取決於具體的業務場景,但還是避開微妙。Spring此時可能打印一條日誌:“Bean xxxx is not eligible for getting processed by all BeanPostProcessor interfaces”。

BeanPostProcessor的作用域

BeanPostProcessor只作用於它所在所在的context,對父子Context都不會起作用。

通過BeanFactoryPostProcessor定製配置數據

BeanFactoryPostProcessor的原理和BeanPostProcessor有點類似,不同的是,BeanPostProcessor處理bean實例,而BeanFactoryPostProcessor處理bean的定義。

BeanFactoryPostProcessor自身也是以bean的形式被定義,多個BeanFactoryPostProcessor實例也通過Order接口來排序;BeanFactoryPostProcessor僅對本容器上下文自作用,不會跨越上下文父子層。

BeanFactoryPostProcessor在容器讀取配置後自動執行,從而可以對容器配置做一些修改。Spring內部有很多此類處理器,比如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer,前者執行bean屬性的覆蓋,後者負責將屬性佔位符替換成最終值。

PropertyPlaceholderConfigurer

這是大家用得最多的一個processor,用法如下:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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>

//jdbc.properties文件的內容
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

PropertyPlaceholderConfigurer檢查所有bean的定義,將佔位符(${jdbc.url})替換成屬性文件裏的值(jdbc.url=jdbc:hsqldb:hsql://production:9002)。如果屬性文件裏面沒有定義該佔位符,那麼還會去系統屬性(system properties)裏面去找(可以通過設置PropertyPlaceholderConfigurer的systemPropertiesMode來改變這種行爲)。

PropertyOverrideConfigurer

定義方式和上面類似,所使用屬性文件的類似以下:

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

這個屬性文件的意思是dataSource這個bean的driverClassName屬性重寫爲com.mysql.jdbc.Driver

示例代碼

定義一個Bean,有兩個屬性,一個有Setter方法,一個被@Tag(自定義註解)註釋。

public class ExampleBean {
    @Tag
    private String tagA;
    private String tagB;
    
    public void print() {
        System.out.println("tagA = " + tagA);
        System.out.println("tagB = " + tagB);
    }

    public void setTagB(String tagB) {
        this.tagB = tagB;
    }

定義了一個BeanPostProcessor,它遍歷bean的所有方法定義,如果發現某個屬性有@Tag註解,則注入一個值:

 public class BeanPostProcessor implements org.springframework.beans.factory.config.BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field f : fields) {
            if (f.getAnnotation(Tag.class) != null) {
                f.setAccessible(true);
                try {
                    f.set(bean, "BeanPostProcessor賦值");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }
}

定義一個BeanFactoryProcessor,它定位ExampleBean的definition,並注入一個屬性定義:

public class BeanFactoryPostProcessor implements org.springframework.beans.factory.config.BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("exampleBean");
        beanDefinition.getPropertyValues().addPropertyValue("tagB","BeanFactoryPostProcessor賦值");
    }
}
}

完整的示例代碼在這裏,請看子工程postprocessor_xml。

FactoryBean

如果bean的初始化過程比較複雜,很難用xml配置來表達,可以定義一個FactoryBean,顧名思義它自己也是一個bean,但是它的使命是創建另一個bean。

FactoryBean接口包含以下方法:
1、Object getObject() 返回工廠創建的bean實例;
2、boolean isSingleTon() bean是否單例;
3、Class getObjectType() 返回bean的類型;

如你提供一個名叫myBean的FactoryBean的xml定義,那麼當調用容器的getBean("myBean")方法,返回的工廠創建的bean;如果調用getBean("&myBean"),返回的是工廠自身。

總結

BeanPostProcessor和BeanFactoryProcessor是我們擴展Spring容器功能的利器,前者干預bean的創建,後者干預bean的定義。Spring全家桶的各功能模塊,大量使用了這兩個機制,充分體現了Spring的優秀設計。有興趣的人可以在Spring源碼裏找找BeanPostProcessor的實現類,會發現Spring那些給我們帶來便利的功能背後,其實都是一個個BeanPostProcessor實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章