天天看點

Spring5參考指南:容器擴充

文章目錄

    • BeanPostProcessor自定義bean
    • BeanFactoryPostProcessor自定義配置中繼資料
    • 使用FactoryBean自定義執行個體化邏輯

Spring提供了一系列的接口來提供對Spring容器的擴充功能。下面我們一一介紹。

前面一篇文章我們在自定義bean中提到,可以實作Spring的InitializingBean和DisposableBean接口來實作自定義bean的生命周期。如果是容器級别的,Spring提供了更加強大的BeanPostProcessor,來實作在容器級對Bean的擴充。

BeanPostProcessor接口定義了兩個方法:

default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}      

該方法在調用容器初始化方法(如InitializingBean.afterPropertiesSet()或任何聲明的init方法)之前,以及在任何bean初始化之後,被調用。

default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}      

該方法在容器初始化方法之後被調用。

BeanPostProcessor可以配置多個,如果想控制多個BeanPostProcessor的順序,可以實作Ordered接口,來定義他們的順序。

雖然BeanPostProcessor是通過ApplicationContext自動檢測的,你也可通過ConfigurableBeanFactory的addBeanPostProcessor來手動注冊。手動注冊則其Ordered失效,以手動注冊的先後為準。

還要注意,以程式設計方式注冊的BeanPostProcessor執行個體總是在注冊為自動檢測的執行個體之前進行處理,而不接收任何顯式排序。

所有BeanPostProcessor執行個體和這些執行個體直接引用的bean都在啟動時執行個體化,因為AOP自動代理是作為BeanPostProcessor本身實作的,是以BeanPostProcessor執行個體和它們直接引用的bean都不符合自動代理的條件。

下面是一個調用的例子:

    <bean id="beanA" class="com.flydean.beans.BeanA"/>
    <bean id="beanB" class="com.flydean.beans.BeanB"/>

    <bean class="com.flydean.beans.InstantiationTracingBeanPostProcessor"/>      

調用實作:

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-post-processor.xml");
        BeanA beanA = (BeanA) ctx.getBean("beanA");
        System.out.println(beanA);
    }      

BeanFactoryPostProcessor接口的語義與BeanPostProcessor的語義相似,但有一個主要差別:BeanFactoryPostProcessor對Bean配置中繼資料進行操作。也就是說,Spring IOC容器允許BeanFactoryPostProcessor讀取配置中繼資料,并可能在容器執行個體化BeanFactoryPostProcessor執行個體以外的任何bean之前對其進行更改。

BeanFactoryPostProcessor也可以配置多個,并通過實作Ordered接口來确定執行順序。BeanFactoryPostProcessor定義了一個方法:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;      

通過該方法可以擷取到可配置的beanFactory進而對bean定義進行修改。

Spring提供了很多預定義的bean工廠後處理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。下面我們通過例子來說明怎麼使用。

PropertyOverrideConfigurer類名替換

PropertyPlaceholderConfigurer主要用于從外部的Property檔案讀取屬性,用來替換定義好的配置,這樣做可以使部署應用程式的人員自定義特定于環境的屬性,如資料庫URL和密碼,而不必為容器修改一個或多個XML定義主檔案進而增加複雜性或風險。

下面是配置的XML檔案:

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:jdbc.properties"/>
    </bean>

    <bean id="dataSource" destroy-method="close"
          class="com.flydean.beans.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檔案中。在運作時,使用PropertyPlaceholderConfigurer将中繼資料替換成DataSource中的某些屬性。要替換的值被指定為${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      

PropertyOverrideConfigurer屬性覆寫

PropertyOverrideConfigurer可以用來覆寫Bean屬性的預設值,或者設定新的值。我們看一個例子:

    <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
        <property name="locations" value="classpath:override.properties"/>
        <property name="properties">
            <value>beanOverride.url=com.something.DefaultStrategy</value>
        </property>
    </bean>
    <bean name="beanOverride" class="com.flydean.beans.BeanOverride"/>      

對應的類是:

@Data
public class BeanOverride {

    private String name="beanA";
    private String url="http://www.163.com";

}      

它的預設屬性會被覆寫。

FactoryBean接口提供3個方法:

  • Object getObject(): 傳回工廠建立的執行個體,該執行個體可能是被共享的, 取決于該執行個體是單例還是多例模式。
  • boolean isSingleton():判斷FactoryBean傳回的是單例還是多例。
  • Class getObjectType():傳回getObject() 方法傳回的類型,如果提前不知道類型,那麼傳回null。

我們可以實作FactoryBean接口來自定義Bean的實作邏輯。

public class BeanFactoryBean implements FactoryBean {

    @Resource
    private BeanA beanA;

    @Override
    public Object getObject() throws Exception {
        return beanA;
    }

    @Override
    public Class<?> getObjectType() {
        return BeanA.class;
    }
}      

下面是其配置:

    <context:annotation-config/>
    <bean id="beanA" class="com.flydean.beans.BeanA"/>

    <bean id="beanFactoryBean" class="com.flydean.beans.BeanFactoryBean"/>      

如何使用?

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-factory.xml");
        BeanFactoryBean beanFactoryBean = (BeanFactoryBean) ctx.getBean("&beanFactoryBean");
        System.out.println(beanFactoryBean.getObject());
        System.out.println(beanFactoryBean.getObjectType());

        BeanA beanA=(BeanA)ctx.getBean("beanFactoryBean");
        System.out.println(beanA);
    }      

當需要向容器請求實際的FactoryBean執行個體本身而不是它生成的bean時,在調用ApplicationContext的getbean()方法時,在bean的ID前面加上符号(&)。是以,對于ID為beanFactoryBean的給定FactoryBean,在容器上調用getBean(“beanFactoryBean”)傳回FactoryBean生成的bean,而調用getBean(“&beanFactoryBean”)則傳回FactoryBean執行個體本身。

  • 區塊鍊從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特币等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程式員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

繼續閱讀