(11)容器的拓展點

容器的擴展

通常來說,開發(fā)者不需要通過繼承ApplicationContext來實現(xiàn)自己的子類擴展功能。但是Spring IoC容器確實可以通過實現(xiàn)接口來增加一些功能。下面將描述一下這些接口。
接下來主要分是哪個部分來講解:

1.通過 BeanPostProcessor后置處理器來對一個bean(可以是監(jiān)控bean或者增加bean)
2.通過BeanFactoryPostProcessor工廠后置bean處理器來定義配置元數(shù)據(jù)(容器實例初始話Bean之前就改變配置元數(shù)據(jù))
3.自定義FactoryBean的初始化邏輯

1.通過 BeanPostProcessor后置處理器來對一個bean

BeanPostProcessor接口定義了一些回調(diào)方法,開發(fā)者可以通過實現(xiàn)來自己的實例化邏輯,依賴解析邏輯等等。如果開發(fā)者只是想在Spring容器完成了實例化,配置以及初始化Bean之后來做一些操作的話,可以通過使用BeanPostProcessor來做到

開發(fā)者可以配置多個BeanPostProcessor實例,開發(fā)者可以通過配置order屬性來指定配置的BeanPostProcessor的執(zhí)行順序。當(dāng)然,想要配置順序必須同時要實現(xiàn)Ordered接口。如果開發(fā)者寫了自己的BeanPostProcessor,開發(fā)者最好同時考慮實現(xiàn)Ordered接口

BeanPostProcessors是操作Bean實例的,換言之,Spring IoC容器必須先初始化好Bean,然后BeanPostProcessors才開始工作。
BeanPostProcessors作用范圍是基于容器的。當(dāng)然,只有當(dāng)開發(fā)者使用容器的層級的時候才是需要考慮的。如果開發(fā)者在容器中定義了一個BeanPostProcessor,這個實例只會在它所在的容器來處理Bean初始化以后的操作。換言之,一個容器中的Bean不會被另一個容器中定義BeanPostProcessor的在初始化以后進行后續(xù)處理,甚至就算兩個容器同屬同一個容器的部分。

org.springframework.beans.factory.config.BeanPostProcessor接口包含了2個回調(diào)方法。當(dāng)這個接口的實例注冊到容器當(dāng)中時,對于每一個由容器創(chuàng)建的實例,這個后置處理器都會在容器開始進行初始化之前獲得回調(diào)的調(diào)用。后置處理器可以針對Bean實例采取任何的操作,包括完全無視回調(diào)函數(shù)。Bean的后置處理器通常檢查回調(diào)接口或者將Bean用代理包裝一下。一些諸如Spring AOP代理的基礎(chǔ)類都是 通過Bean的后續(xù)處理器來實現(xiàn)的。

ApplicationContext會自動檢查配置的Bean是否有實現(xiàn)BeanPostProcessor接口,ApplicationContext會將這些Bean注冊為后續(xù)處理器,這樣這些后續(xù)處理器就會在Bean創(chuàng)建之后調(diào)用。Bean的后續(xù)處理器就像其他的Bean一樣,由容器管理的。

盡管Spring團隊推薦的注冊Bean的后續(xù)處理的方式是通過ApplicationContext的自動檢查,但是Spring也支持通過編程的方式,通過addBeanPostProcessor方法。這種方式有的時候也很有用,當(dāng)需要在注冊前執(zhí)行一些條件判斷的時候,或者在結(jié)構(gòu)化的上下文中復(fù)制Bean后續(xù)處理器的時候尤其有效。需要注意的是,通過編程實現(xiàn)的BeanPostProcessors是會忽略掉Ordered接口的:由編程注冊的BeanPostProcessors總是在自動檢查到的BeanPostProcessors之前來執(zhí)行的,而回忽略掉明確的順序定義。

容器會特殊對待那些實現(xiàn)了BeanPostProcessor接口的類。所有的BeanPostProcessors和他們所引用的Bean都會在啟動時直接初始化作為ApplicationContext啟動的一個特殊階段。然后,所有的BeanPostProcessors會按順序注冊并應(yīng)用到容器中的Bean

下面通過一個例子來演示一下

/**
 * @Project: spring
 * @description:  模擬一個普通的bean
 * @author: sunkang
 * @create: 2018-09-16 15:06
 * @ModificationHistory who      when       What
 **/
public class BeanDemo{
    public BeanDemo() {
        System.out.println("beanDemo已經(jīng)初始化了");
    }
}
/**
 * 模擬 BeanPostProcessor來監(jiān)控其他bean,進行相應(yīng)的處理
 */
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor,Ordered {

    //在bean初始化之后調(diào)用
    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("beforeBean ''" + beanName + "'' created : " + bean.toString());
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("afterBean ''" + beanName + "'' created : " + bean.toString());
        return bean;
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

在spring-extensionPoint.xml的配置中如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="beanPostProcessor" class="com.spring.extensionPoint.InstantiationTracingBeanPostProcessor" />

    <bean class="com.spring.extensionPoint.BeanDemo"/>
</beans>

測試的演示:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");

測試結(jié)果如下:可以發(fā)現(xiàn)BeanPostProcessor的兩個回調(diào)方法在容器構(gòu)造方法之后進行執(zhí)行的

beanDemo已經(jīng)初始化了
beforeBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
afterBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe

spring的例子:RequiredAnnotationBeanPostProcessor
使用回調(diào)接口或者注解和BeanPostProcessor實現(xiàn)是常見的來擴展Spring IoC容器的方式。Spring中的一個例子就是RequiredAnnotationBeanPostProcessor就是用來確保JavaBean的標(biāo)記有注解的屬性確實注入了依賴

2.通過BeanFactoryPostProcessor定義配置元數(shù)據(jù)

org.springframework.beans.factory.config.BeanFactoryPostProcessor。語義上來說,這個接口有些類似BeanPostProcessor,只是有一個很大的區(qū)別:BeanFactoryPostProcessor操作Bean配置元數(shù)據(jù),也就是說,Spring允許BeanFactoryPostProcessor來讀取配置源數(shù)據(jù),并且可能會在容器實例初始話Bean之前就改變配置元數(shù)據(jù)

如前文所述,開發(fā)者可以配置多個BeanFactoryPostProcessors,而且開發(fā)者可以控制其具體執(zhí)行的順序。當(dāng)然,配置順序是必須實現(xiàn)Ordered接口的。如果實現(xiàn)了自己的BeanFactoryPostProcessor,開發(fā)者也應(yīng)該考慮實現(xiàn)Ordered接口

在ApplicationContext中聲明了后續(xù)處理器,Bean的后續(xù)處理器就會自動的執(zhí)行,來實現(xiàn)在配置中定義的行為。Spring包括一些預(yù)定義好的后續(xù)處理器都可以使用,比如PropertyOverrideConfigurerPropertyPlaceholderCOnfigurer,也能夠使用自定義的BeanFactoryPostProcessor,比如,來注冊自定義屬性編輯器。

例子,類名替換BeanFactoryPostProcessor
下面就開始演示一下

/**
 * @Project: spring
 * @description:   模擬PropertyPlaceholderConfigurer來進行測試
 * @author: sunkang
 * @create: 2018-09-16 16:15
 * @ModificationHistory who      when       What
 **/
public class DataSourceConfig {
    private String driverClassName;

    private  String url;

    private String username;

    private String password;

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "DataSourceConfig{" +
                "driverClassName='" + driverClassName + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

在extensionPoint目錄下放置datasouce.properties文件,文件內(nèi)容如下

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

在spring-extensionPoint.xml配置如下

  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:extensionPoint/datasouce.properties"/>
    </bean>
  <bean id="dataSource"  class="com.spring.extensionPoint.DataSourceConfig">
        <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>

測試方法:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
 DataSourceConfig config=   context.getBean("dataSource",DataSourceConfig.class);
System.out.println(config);

測試結(jié)果如下:

DataSourceConfig{driverClassName='com.mysql.jdbc.Driver', url='jdbc:mysql:mydb', username='sa', password='root'}

Spring 2.5后引入了context命名空間,可以專用的配置元素來配置屬性占位符。多個地址可以使用逗號分隔符。

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

PropertyPlaceholderConfigurer不僅僅查看開發(fā)者指定的Properties文件。默認的話,它如果不能再指定的屬性文件中找到屬性的話,仍然會檢查Java的System配置。開發(fā)者可以通過配置systemPropertiesMode屬性來修改這個行為,這個屬性有如下三種值:
nerver(0):從不檢查系統(tǒng)屬性
fallback(1):如果沒有從指定的屬性文件中找到特定的屬性時檢查系統(tǒng)屬性,這個是默認值
override(2):優(yōu)先查找系統(tǒng)屬性,而后才試著檢查指定配置文件。系統(tǒng)的屬性會覆蓋指定文件的屬性。

接下來看看PropertyPlaceholderConfigurer的源碼,先看下PropertyPlaceholderConfigurer的繼承關(guān)系,發(fā)現(xiàn)有beanFactoryPostProcessor


image.png

在PropertyResourceConfigurer實現(xiàn)了BeanFactoryPostProcessor,那么postProcessBeanFactory就是主入口了,后面的流程有興趣可以自己看看。

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            //從特定的文件加載屬性到mergedProps中
            Properties mergedProps = mergeProperties();
            
            // Convert the merged properties, if necessary.
            convertProperties(mergedProps);

            //這里的處理會properties屬性,會生成一個解析器,然后注入到容器中,裝載beand的時候再來回調(diào)這個解析器
            // Let the subclass process the properties.
            processProperties(beanFactory, mergedProps);
        }
        catch (IOException ex) {
            throw new BeanInitializationException("Could not load properties", ex);
        }
    }

另外一個例子是PropertyOverrideConfigurer
PropertyOverrideConfigurer,是另一個bean的后置處理器,有些類似于PropertyPlaceholderConfigurer,但是區(qū)別于后者,原有的定義可能有默認值,或者沒有任何值。如果覆蓋的Properties文件沒有一個確切的Bean屬性,就使用默認的定義。

覆蓋的屬性如下,注意但是要求每一個路徑上的組件,需要是非空的,也就是dataSource該組件不能為空

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

在具體的xml配置如下,那么dataSource.driverClassName和url將被覆蓋,沒有覆蓋的值保持原樣

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

在Spring 2.5引入的context命名空間中,也可以指定配置覆蓋的文件屬性如下:

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

3.自定義FactoryBean的初始化邏輯

FactoryBean接口是一種類似于Spring IoC容器的插件化的邏輯。如果當(dāng)開發(fā)者的代碼有復(fù)雜的初始化代碼,在配置上使用Java代碼比XML更有效時,開發(fā)者可以考慮創(chuàng)建自己的FacotoryBean對象,將復(fù)雜的初始化操作放到類中,將自定義的FactoryBean擴展到容器中。

FacotryBean接口提供如下三個方法

Object getObject():返回一個工廠創(chuàng)建的對象。實例可被共享,取決于返回Bean的作用域為原型還是單例。
boolean isSingleton():如果FactoryBean返回單例,為True,否則為False
Class getObjectType():返回由getObject()方法返回的對象的類型,如果對象類型未知,返回null。
FactoryBean概念和接口廣泛用預(yù)Spring框架,Spring本身就有多于50個FactoryBean的實現(xiàn)。

當(dāng)開發(fā)者需要一個FactoryBean實例而不是其產(chǎn)生的Bean的時候,在調(diào)用ApplicationContext的getBean()方法時,在其id之前加上&符號。也就是說,對于一個給定的FactoryBean,其id為myBean,調(diào)用getBean("myBean")返回其產(chǎn)生的Bean對象,而調(diào)用getBean("&myBean")返回FactoryBean實例本身。

下面進行例子演示:

/**
 * @Project: spring
 * @description:  說明是一個factroyBean    是一個bean,但是是一個工廠bean
 * @author: sunkang
 * @create: 2018-09-16 16:34
 * @ModificationHistory who      when       What
 **/
public class FactoryBeanDemo implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        return "factoryBean";
    }

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

在spring-extensionPoint.xml具體的配置如下:

    <bean id="factoryBean" class="com.spring.extensionPoint.FactoryBeanDemo"/>

測試如下:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
//當(dāng)其id為factoryBean,調(diào)用getBean("factoryBean")返回其產(chǎn)生的Bean對象,
//而調(diào)用getBean("&factoryBean")返回FactoryBean實例本身。
FactoryBeanDemo factoryBeanDemo =   context.getBean("&factoryBean",FactoryBeanDemo.class);
System.out.println("得到工廠:factory"+factoryBeanDemo);
//得到bean對象,其實是工廠bean調(diào)用了getObject方法產(chǎn)生的對象
System.out.println("得到具體的bean:"+context.getBean("factoryBean"));

結(jié)果如下: 結(jié)果很顯然

得到工廠:factorycom.spring.extensionPoint.FactoryBeanDemo@5025a98f
得到具體的bean:factoryBean
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容