BeanPostProcessor和BeanFactoryPostProcessor的區別


官方文檔:

在Spring核心的1.8章節

使用BeanPostProcessor自定義Bean

BeanPostProcessor 接口定義了您可以實現的回調方法,以提供您自己的(或覆蓋容器的默認)實例化邏輯,依賴關系解析邏輯等。如果要在Spring容器完成實例化,配置和初始化bean之后實現某些自定義邏輯,則可以插入一個或多個 BeanPostProcessor 實現。

您可以配置多個 BeanPostProcessor 實例,並且可以通過設置 order 屬性來控制這些 BeanPostProcessor 實例的執行順序。你可以設置僅當BeanPostProcessor 實現 Ordered 接口時才具有此屬性。如果你自己編寫 BeanPostProcessor ,你也應該考慮實現 Ordered 接口。有關更多詳細信息,請參閱 BeanPostProcessorOrdered 接口的javadoc。

BeanPostProcessor 實例在bean(或對象)實例上運行。也就是說,Spring IoC容器實例化一個bean實例,然后 BeanPostProcessor 實例完成它們的工作。

BeanPostProcessor 實例的范圍是每個容器的范圍。僅當您使用容器層次結構時,這才是相關的。如果在一個容器中定義 BeanPostProcessor ,則它僅對該容器中的bean進行后處理。換句話說,在一個容器中定義的bean不會被另一個容器中定義的 BeanPostProcessor 進行后處理,即使兩個容器都是同一層次結構的一部分。

要更改實際的bean定義(即定義bean的藍圖),您需要使用 BeanFactoryPostProcessor 

org.springframework.beans.factory.config.BeanPostProcessor 接口恰好包含兩個回調方法。當這樣的類被注冊為帶有容器的后處理器時,對於容器創建的每個bean實例,后處理器在容器初始化方法之前從容器中獲取回調(例如 InitializingBean.afterPropertiesSet() ,在任何聲明的 init 之后)方法)被調用,並在任何bean初始化后回調。后處理器可以對bean實例執行任何操作,包括完全忽略回調。 bean后處理器通常檢查回調接口,或者它可以用代理包裝bean。一些Spring AOP基礎結構類實現為bean后處理器,以便提供代理包裝邏輯。

ApplicationContext 自動檢測在實現 BeanPostProcessor 接口的配置元數據中定義的任何bean。 ApplicationContext 將這些bean注冊為后處理器,以便稍后在創建bean時調用它們。 Bean后處理器可以以與任何其他bean相同的方式部署在容器中。

請注意,在配置類上使用 @Bean factory方法聲明 BeanPostProcessor 時,工廠方法的返回類型應該是實現類本身或至少是org.springframework.beans.factory.config.BeanPostProcessor 接口,清楚地表明該bean的后處理器性質。否則, ApplicationContext 無法在完全創建之前按類型自動檢測它。由於需要提前實例化 BeanPostProcessor 以便應用於上下文中其他bean的初始化,因此這種早期類型檢測至關重要。

以編程方式注冊 BeanPostProcessor 實例 雖然 BeanPostProcessor 注冊的推薦方法是通過 ApplicationContext 自動檢測(如前所述),但您可以使用 addBeanPostProcessor 方法以編程方式對ConfigurableBeanFactory 注冊它們。當您需要在注冊前評估條件邏輯或甚至跨層次結構中的上下文復制Bean post處理器時,這非常有用。但請注意,以編程方式添加的BeanPostProcessor 實例不尊重 Ordered 接口。這里,注冊的順序決定了執行的順序。另請注意,以編程方式注冊的 BeanPostProcessor 實例始終在通過自動檢測注冊的實例之前處理,而不管任何顯式排序。

BeanPostProcessor 實例和AOP自動代理

實現 BeanPostProcessor 接口的類是特殊的,容器會對它們進行不同的處理。作為 ApplicationContext 特殊啟動階段的一部分,它們直接引用的所有BeanPostProcessor 實例和bean都會在啟動時實例化。接下來,所有 BeanPostProcessor 實例都以排序方式注冊,並應用於容器中的所有其他bean。因為AOP自動代理是作為 BeanPostProcessor 本身實現的,所以 BeanPostProcessor 實例和它們直接引用的bean都不符合自動代理的條件,因此沒有將方面編入其中。

對於任何此類bean,您應該看到一條信息性日志消息: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

如果您通過使用自動裝配或 @Resource (可能會回退到自動裝配)將bean連接到 BeanPostProcessor ,則Spring可能會在搜索類型匹配依賴項候選項時訪問意外的bean,從而使它們不符合自動代理或其他類型的 beans 后處理。例如,如果您有一個使用 @Resource 注釋的依賴項,其中字段或setter名稱不直接對應於bean的聲明名稱且未使用name屬性,則Spring會訪問其他bean以按類型匹配它們。

以下示例顯示如何在 ApplicationContext 中編寫,注冊和使用 BeanPostProcessor 實例。

示例:Hello World,BeanPostProcessor樣式

第一個例子說明了基本用法。該示例顯示了一個自定義 BeanPostProcessor 實現,它調用容器創建的每個bean的 toString() 方法,並將生成的字符串打印到系統控制台。

以下清單顯示了自定義 BeanPostProcessor 實現類定義:

package scripting;
​
import org.springframework.beans.factory.config.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;
    }
}

 

以下 beans 元素使用 InstantiationTracingBeanPostProcessor

<?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。Spring動態語言支持在 Headers 為 Dynamic Language Support 的章節中有詳細說明。)

以下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 : [email protected]
[email protected]

 

示例:RequiredAnnotationBeanPostProcessor

將回調接口或注釋與自定義 BeanPostProcessor 實現結合使用是擴展Spring IoC容器的常用方法。一個例子是Spring的RequiredAnnotationBeanPostProcessor - 一個帶有Spring發行版的 BeanPostProcessor 實現,它確保用(任意)注釋標記的bean上的JavaBean屬性實際上(配置為)依賴注入值。

使用BeanFactoryPostProcessor自定義配置元數據

我們看到的下一個擴展點是 org.springframework.beans.factory.config.BeanFactoryPostProcessor 。此接口的語義類似於 BeanPostProcessor 的語義,但有一個主要區別: BeanFactoryPostProcessor 對bean配置元數據進行操作。也就是說,Spring IoC容器允許 BeanFactoryPostProcessor 讀取配置元數據,並可能在容器實例化除 BeanFactoryPostProcessor 實例之外的任何bean之前更改它。

您可以配置多個 BeanFactoryPostProcessor 實例,並且可以通過設置 order 屬性來控制這些 BeanFactoryPostProcessor 實例的運行順序。但是,如果BeanFactoryPostProcessor 實現 Ordered 接口,則只能設置此屬性。如果你自己編寫 BeanFactoryPostProcessor ,你也應該考慮實現 Ordered 接口。有關更多詳細信息,請參閱 BeanFactoryPostProcessorOrdered 接口的javadoc。

如果要更改實際的bean實例(即從配置元數據創建的對象),則需要使用 BeanPostProcessor 。雖然技術上可以在 BeanFactoryPostProcessor 中使用bean實例(例如,通過使用 BeanFactory.getBean() ),但這樣做會導致過早的bean實例化,從而違反標准容器生命周期。這可能會導致負面影響,例如繞過bean后期處理。

此外, BeanFactoryPostProcessor 實例的范圍是每個容器的范圍。僅當您使用容器層次結構時,這才有意義。如果在一個容器中定義BeanFactoryPostProcessor ,則它僅應用於該容器中的bean定義。即使兩個容器都是同一層次結構的一部分,一個容器中的Bean定義也不會被另一個容器中的BeanFactoryPostProcessor 實例進行后處理。

Bean工廠后處理器在 ApplicationContext 中聲明時會自動執行,以便將更改應用於定義容器的配置元數據。 Spring包含許多預定義的bean工廠后處理器,例如 PropertyOverrideConfigurerPropertyPlaceholderConfigurer 。您還可以使用自定義 BeanFactoryPostProcessor - 例如,注冊自定義屬性編輯器。

ApplicationContext 會自動檢測部署到其中的任何實現 BeanFactoryPostProcessor 接口的bean。它在適當的時候使用這些bean作為bean工廠后處理器。您可以像處理任何其他bean一樣部署這些后處理器bean。

BeanPostProcessor 一樣,您通常不希望為延遲初始化配置 BeanFactoryPostProcessor 。如果沒有其他bean引用 Bean(Factory)PostProcessor ,則該后處理器根本不會被實例化。因此,將忽略將其標記為延遲初始化,即使您在 <beans /> 元素的聲明上將 default-lazy-init 屬性設置為 true ,也會急切地實例化Bean(Factory)PostProcessor

示例:類名替換PropertyPlaceholderConfigurer

您可以使用標准Java Properties 格式在單獨的文件中使用 PropertyPlaceholderConfigurer 來外部化bean定義中的屬性值。這樣做可以使部署應用程序的人員自定義特定於環境的屬性,例如數據庫URL和密碼,而不會出現修改主XML定義文件或容器文件的復雜性或風險。

請考慮以下基於XML的配置元數據片段,其中定義了帶有占位符值的 DataSource

<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>

 

該示例顯示了從外部 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

 

因此, ${jdbc.username} 字符串在運行時被替換為值'sa',並且同樣適用於與屬性文件中的鍵匹配的其他占位符值。 PropertyPlaceholderConfigurer 檢查bean定義的大多數屬性和屬性中的占位符。此外,您可以自定義占位符前綴和后綴。

使用Spring 2.5中引入的 context 命名空間,您可以使用專用配置元素配置屬性占位符。您可以在 location 屬性中以逗號分隔列表的形式提供一個或多個位置,如以下示例所示:

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

PropertyPlaceholderConfigurer 不僅在您指定的 Properties 文件中查找屬性。默認情況下,如果它在指定的屬性文件中找不到屬性,它還會檢查JavaSystem 屬性。您可以通過使用以下三個受支持的整數值之一設置configurer的 systemPropertiesMode 屬性來自定義此行為:

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

  • fallback (1):如果在指定的屬性文件中無法解析,則檢查系統屬性。這是默認值。

  • override (2):在嘗試指定的屬性文件之前,首先檢查系統屬性。這使系統屬性可以覆蓋任何其他屬性源。

有關更多信息,請參閱 PropertyPlaceholderConfigurer javadoc。

您可以使用 PropertyPlaceholderConfigurer 替換類名,這在您必須在運行時選擇特定實現類時有時很有用。以下示例顯示了如何執行此操作:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean><bean id="serviceStrategy" class="${custom.strategy.class}"/>

 

如果在運行時無法將類解析為有效類,則在將要創建bean時,bean的解析將失敗,這在非延遲初始化bean的 preInstantiateSingletons() 階段期間會失敗。

示例:PropertyOverrideConfigurer

PropertyOverrideConfigurer ,另一個bean工廠后處理器,類似於 PropertyPlaceholderConfigurer ,但與后者不同,原始定義可以具有默認值或根本沒有值用於bean屬性。如果重寫的 Properties 文件沒有某個bean屬性的條目,則使用默認的上下文定義。

請注意,bean定義不知道被覆蓋,因此從XML定義文件中可以立即看出正在使用覆蓋配置器。如果多個 PropertyOverrideConfigurer 實例為同一個bean屬性定義了不同的值,則由於覆蓋機制,最后一個獲勝。

屬性文件配置行采用以下格式:

beanName.property=value

以下清單顯示了格式的示例:

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

 

此示例文件可以與包含名為 dataSource 且具有 driverurl 屬性的bean的容器定義一起使用。

也支持復合屬性名稱,只要路徑的每個組件(重寫的最終屬性除外)都已經非空(可能由構造函數初始化)。在以下示例中, tom bean的 fred 屬性的 bob屬性的 sammy 屬性設置為標量值 123

tom.fred.bob.sammy=123

使用Spring 2.5中引入的 context 命名空間,可以使用專用配置元素配置屬性覆蓋,如以下示例所示:

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

 代碼:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor...postProcessBeanFactory...");
        int count = beanFactory.getBeanDefinitionCount();
        String[] names = beanFactory.getBeanDefinitionNames();
        System.out.println("當前BeanFactory中有"+count+" 個Bean");
        System.out.println(Arrays.asList(names));
    }

}
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor...postProcessBeanFactory...");
        int count = beanFactory.getBeanDefinitionCount();
        String[] names = beanFactory.getBeanDefinitionNames();
        System.out.println("當前BeanFactory中有"+count+" 個Bean");
        System.out.println(Arrays.asList(names));
    }

}

測試結果:

 

 

@Component
public class MyBeanPostProcessor implements BeanPostProcessor,Ordered {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
        return bean;
    }

    @Override
    public int getOrder() {
        return 2;
    }

}
@Configuration
@ComponentScan(value="com.atguigu.bean")
public class MainConfig3 {
    
    //給容器中注冊一個Bean;類型為返回值的類型,id默認是用方法名作為id
        @Bean("person")
        public Person person01(){
            return new Person("lisi", 20);
        }

}

測試:

public class MainTest {
    
    @SuppressWarnings("resource")
    public static void main(String[] args) {
//        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//        Person bean = (Person) applicationContext.getBean("person");
//        System.out.println(bean);
        
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
        
    
    }

}

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM