官方文檔:
在Spring核心的1.8章節
使用BeanPostProcessor自定義Bean
BeanPostProcessor 接口定義了您可以實現的回調方法,以提供您自己的(或覆蓋容器的默認)實例化邏輯,依賴關系解析邏輯等。如果要在Spring容器完成實例化,配置和初始化bean之后實現某些自定義邏輯,則可以插入一個或多個 BeanPostProcessor 實現。
您可以配置多個 BeanPostProcessor 實例,並且可以通過設置 order 屬性來控制這些 BeanPostProcessor 實例的執行順序。你可以設置僅當BeanPostProcessor 實現 Ordered 接口時才具有此屬性。如果你自己編寫 BeanPostProcessor ,你也應該考慮實現 Ordered 接口。有關更多詳細信息,請參閱 BeanPostProcessor 和 Ordered 接口的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 接口。有關更多詳細信息,請參閱 BeanFactoryPostProcessor 和 Ordered 接口的javadoc。
如果要更改實際的bean實例(即從配置元數據創建的對象),則需要使用
BeanPostProcessor。雖然技術上可以在BeanFactoryPostProcessor中使用bean實例(例如,通過使用BeanFactory.getBean()),但這樣做會導致過早的bean實例化,從而違反標准容器生命周期。這可能會導致負面影響,例如繞過bean后期處理。
此外, BeanFactoryPostProcessor 實例的范圍是每個容器的范圍。僅當您使用容器層次結構時,這才有意義。如果在一個容器中定義BeanFactoryPostProcessor ,則它僅應用於該容器中的bean定義。即使兩個容器都是同一層次結構的一部分,一個容器中的Bean定義也不會被另一個容器中的BeanFactoryPostProcessor 實例進行后處理。
Bean工廠后處理器在 ApplicationContext 中聲明時會自動執行,以便將更改應用於定義容器的配置元數據。 Spring包含許多預定義的bean工廠后處理器,例如 PropertyOverrideConfigurer 和 PropertyPlaceholderConfigurer 。您還可以使用自定義 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 且具有 driver 和 url 屬性的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); } }

