1. 程式人生 > >Spring Ioc 容器擴充套件點2 BeanFactoryPostProcessor自定義元資料配置

Spring Ioc 容器擴充套件點2 BeanFactoryPostProcessor自定義元資料配置

這個介面的語義與BeanPostProcessor類似,但有一處不同,BeanFactoryPostProcessor操作bean的元資料配置,也就是說,Spring IOC容器允許BeanFactoryPostProcessor來讀取元資料配置並在容器例項化任何bean(除了BeanFactoryPostProcessor)之前修改他。

開發者可以配置多個BeanFactoryPostProcessor例項,也可以通過設定order屬性來控制執行順序。

注意:如果想要修改實際的bean例項,需要使用BeanPostProcessor,在BeanFactoryPostProcessor中使用這些Bean的例項雖然在技術上是可行的,但是這個會將bean過早的例項化,違反了標準的容器生命週期,同時也會導致一些問題,例如繞過bean的後置處理。

BeanFactoryPostProcessor會在整個容器內起作用,所以他僅僅與正在使用的容器相關。

但在ApplicationContext中宣告時,bean工廠後置處理器會自動被執行,這樣就可以對定義在容器中的元資料進行修改,Spring包含了一些預定義的bean工廠後置處理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。自定義的BeanFactoryPostProcessor也可以使用,例如註冊自定義的屬性編輯器。

例子:類名替換PropertyPlaceholderConfigurer

這樣做可以使部署應用程式的人能夠制定特定於環境的屬性,如資料庫URL和密碼,而無需修改容器的主XML定義檔案。

從使用了標準JavaProperties格式的bean定義的分離檔案中,開發者可以使用PropertyPlaceholderConfigurer來具體化屬性值。這樣做可以使部署應用程式的使用者能夠定製特定於環境的屬性,如資料庫URL和密碼,而無需修改容器的主要XML定義檔案,從而降低了檔案的複雜性和風險

考慮一下這個基於XML的元資料配置程式碼片段,這裡的DataSource使用了佔位符來定義,這個示例展示了從properties檔案中配置屬性的方法,在執行時,PropertyPlaceholderConfigurer就會用於元資料為資料來源替換指定屬性,指定屬性的值換做為${property-name}形式中的佔位符,這裡運用了Ant/log4j/JSPEL的風格

<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="dirverClassName" value="${j}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

而真正的值是來自於標準的Java Properties格式的檔案:

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

在上面的例子中,字串${jdbc.username}在執行時會被sa替換,其它佔位符也一樣,所有匹配的屬性檔案都會使用鍵值替換佔位符,PropertyPlaceholderConfigurer在bean定義的屬性中會檢查佔位符,此外,對佔位符還可以自定義字首和字尾。

PropertyPlaceholderConfigurer不僅僅會檢視在Properties檔案中指定的屬性,預設情況下,如果他在指定的屬性檔案中找不到屬性,那麼他會檢查Java System的屬性,開發者可以通過設定systemPropertiesMode屬性,使用下面三個整數的其中一個來自定義這種行為:

never(0)從不檢查系統屬性

fallback(1)如果沒有在指定的屬性檔案中解析到屬性,那麼就檢查系統屬性(預設)

override(2)在檢查指定的屬性檔案之前,首先去檢查系統屬性,允許系統屬性覆蓋其它任意的屬性資源

你可以使用PropertyPlaceholderConfigurer來替換類名,在開發者在執行時需要選擇某個特定的實現類時,這個很有用。

例子:修改屬性的值

<?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="person" class="com.test.entity.Person">
        <property name="name" value="fang"></property>
        <property name="age" value="10"></property>
        <property name="birthday" value="10"></property>
    </bean>

    <bean id="beanFactoryPostTest" class="com.test.config.BeanFactoryPostTest"></bean>
</beans>
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private Integer age;
    private String name;
    private String birthday;

}
public class BeanFactoryPostTest implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanStr = beanFactory
                .getBeanDefinitionNames();
        for (String beanName : beanStr) {
            if ("person".equals(beanName)) {
                BeanDefinition beanDefinition = beanFactory
                        .getBeanDefinition(beanName);
                MutablePropertyValues m = beanDefinition.getPropertyValues();
                if (m.contains("birthday")) {
                    try {
                        m.addPropertyValue("birthday", "世界和平");
                    } catch (Exception e) {
                        System.out.println("錯誤");
                    }
                }
            }
        }
    }
}
@GetMapping("/test")
    public Object get() {
        ApplicationContext factory = new ClassPathXmlApplicationContext("classpath:Test.xml");
        return factory.getBean("person", Person.class);
    }

可以看到person的birthday的屬性值被修改了,使用BeanPostProcessor裡面也能夠實現此功能。

例子 PropertyOverrideConfigurer

PropertyOverrideConfigurer 是另一種bean工廠後置處理器,類似於PropertyPlaceholderConfigurer但是與其不同的是,對於所有的bean屬性,原始定義可能有預設值也可能沒有值,如果一個properties覆蓋檔案沒有特定的bean屬性,這就會使用預設的上下文定義。

注意bean定義是不知道是否被覆蓋的,所以從XML定義檔案中不能馬上看到那個配置檔案正在被使用,在擁有多個PropertyOverrideConfigurer例項的情況下,為相同的bean的屬性定義不同的值,基於覆蓋機制,只會最後一個生效。