環境:Spring5.3.10
通常,應用程序開發人員不需要對ApplicationContext實現類進行子類化。相反,SpringIOC容器可以通過插入特殊集成接口的實現來擴展。
使用BeanPostProcessor自定義bean
BeanPostProcessor接口定義回調方法,你可以實現這些方法來提供自己的(或覆蓋容器的默認)實例化邏輯、依賴項解析邏輯等。如果希望在Spring容器完成bean的實例化、配置和初始化后實現一些自定義邏輯,可以編寫一個或多個自定義BeanPostProcessor實現。
你可以配置多個BeanPostProcessor實例,并且可以通過設置order屬性來控制這些BeanPostProcessor實例的運行順序。僅當BeanPostProcessor實現Ordered接口時,才能設置此屬性。如果編寫自己的BeanPostProcessor,也應該考慮實現有序接口。
BeanPostProcessor實例對bean(或對象)實例進行操作。也就是說,SpringIoC容器實例化一個bean實例,然后BeanPostProcessor實例執行它們的工作。
BeanPostProcessor實例的作用域為每個容器。這僅在使用容器層次結構時才相關。如果在一個容器中定義BeanPostProcessor,它將只對該容器中的bean進行后期處理。換句話說,一個容器中定義的bean不會被另一個容器中定義的BeanPostProcessor后處理,即使兩個容器都是同一層次結構的一部分。
要更改實際的bean定義(即定義bean的藍圖),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定義配置元數據中所述。
org.springframework.beans.factory.config.BeanPostProcessor接口正好由兩個回調方法組成。當此類類在容器中注冊為后處理器時,對于容器創建的每個bean實例,后處理器在調用容器初始化方法(如InitializingBean.afterPropertiesSet()或任何聲明的init方法)之前都會從容器中獲取回調,在任何bean初始化回調之后。后處理器可以對bean實例執行任何操作,包括完全忽略回調。bean后處理器通常檢查回調接口,或者用代理包裝bean。一些SpringAOP基礎設施類被實現為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方法以編程方式針對可配置的BeanFactory注冊它們。當您需要在注冊之前評估條件邏輯,甚至在層次結構中跨上下文復制bean后處理器時,這非常有用。但是,請注意,以編程方式添加的BeanPostProcessor實例不遵循有序接口。在這里,登記的順序決定了執行的順序。還請注意,以編程方式注冊的BeanPostProcessor實例總是在通過自動檢測注冊的實例之前進行處理,而不考慮任何顯式順序。
BeanPostProcessor實例和AOP自動代理
實現BeanPostProcessor接口的類是特殊的,容器會對它們進行不同的處理。作為ApplicationContext特殊啟動階段的一部分,所有直接引用的BeanPostProcessor實例和bean都在啟動時實例化。接下來,以排序方式注冊所有BeanPostProcessor實例,并將其應用于容器中的所有其他bean。因為AOP自動代理是作為BeanPostProcessor本身實現的,所以無論是BeanPostProcessor實例還是它們直接引用的Bean都不符合自動代理的條件,因此,它們沒有編織方面。
對于任何這樣的bean,您都應該看到一條信息性日志消息:bean someBean不符合由所有BeanPostProcessor接口處理的條件(例如:不符合自動代理的條件)。
如果您使用autowiring或@Resource(可能會返回到autowiring)將bean連接到BeanPostProcessor中,Spring在搜索類型匹配的依賴項候選項時可能會訪問意外的bean,因此,使它們不符合自動代理或其他類型的bean后處理的條件。例如,如果你有一個用@Resource注釋的依賴項,其中字段或setter名稱不直接對應于bean的聲明名稱,并且沒有使用name屬性,那么Spring將訪問其他bean以按類型匹配它們。
示例:
該示例顯示了一個自定義BeanPostProcessor實現,該實現在容器創建每個bean時調用其toString()方法,并將結果字符串打印到系統控制臺。
@Component
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// 只需按原樣返回實例化的bean
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // 我們可能會在這里返回任何對象引用。。。
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
AutowiredAnnotationBeanPostProcessor
將回調接口或注釋與自定義BeanPostProcessor實現結合使用是擴展Spring IOC容器的常用方法。Spring的
AutowiredAnnotationBeanPostProcessor就是一個例子 — BeanPostProcessor實現。
使用BeanFactoryPostProcessor自定義配置元數據
下一個擴展點是
org.springframework.beans.factory.config.BeanFactoryPostProcessor。此接口的語義與BeanPostProcessor的語義相似,但有一個主要區別:BeanFactoryPostProcessor操作bean配置元數據。也就是說,SpringIoC容器允許BeanFactoryPostProcessor讀取配置元數據,并在容器實例化除BeanFactoryPostProcessor實例之外的任何Bean之前可能對其進行更改。
你可以配置多個BeanFactoryPostProcessor實例,并且可以通過設置order屬性來控制這些BeanFactoryPostProcessor實例的運行順序。但是,僅當BeanFactoryPostProcessor實現有序接口時,才能設置此屬性。如果編寫自己的BeanFactoryPostProcessor,也應該考慮實現有序接口。
如果你想要更改實際的bean實例(即,從配置元數據創建的對象),那么您需要使用BeanPostProcessor(前面在使用BeanPostProcessor定制bean中描述)。雖然在技術上可以在BeanFactoryPostProcessor中使用bean實例(例如,通過使用BeanFactory.getBean()),但這樣做會導致過早的bean實例化,違反標準容器生命周期。這可能會導致負面的副作用,例如繞過bean后處理。
此外,BeanFactoryPostProcessor實例的作用域為每個容器。這僅在使用容器層次結構時才相關。如果在一個容器中定義BeanFactoryPostProcessor,則它僅應用于該容器中的bean定義。一個容器中的Bean定義不會由另一個容器中的BeanFactoryPostProcessor實例進行后處理,即使兩個容器都是同一層次結構的一部分。
bean工廠后處理器在ApplicationContext中聲明時自動運行,以便對定義容器的配置元數據應用更改。Spring包括許多預定義的bean factory后處理器,如
PropertyOverrideConfiguler和PropertySourcePlaceHolderConfigurer。你還可以使用自定義BeanFactoryPostProcessor — 例如,注冊自定義屬性編輯器。
ApplicationContext自動檢測部署到其中實現BeanFactoryPostProcessor接口的任何Bean。它在適當的時候將這些bean用作bean工廠的后處理器。你可以像部署任何其他bean一樣部署這些后處理器bean。
屬性占位符替換PropertySourcesPlaceholderConfigurer
通過使用標準JAVA屬性格式,你可以使用
PropertySourcesPlaceholderConfigurer將bean定義中的屬性值外部化到單獨的文件中。這樣做使部署應用程序的人員能夠自定義特定于環境的屬性,例如數據庫URL和密碼,而無需修改容器的主XML定義文件的復雜性或風險。
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<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>
運行時,
PropertySourcesPlaceholderConfigurer應用于替換數據源某些屬性的元數據。要替換的值被指定為形式${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
自定義BeanFactoryPostProcessor
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 獲取Person的BeanDefinition對象,修改屬性name的值。
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("person") ;
beanDefinition.getPropertyValues().addPropertyValue("name", "CNM") ;
System.out.println("BeanFactoryPostProcessor") ;
}
}
使用FactoryBean自定義實例化邏輯
你可以為本身就是工廠的對象實現
org.springframework.beans.factory.FactoryBean接口。
FactoryBean接口是Spring IoC容器實例化邏輯的一個可插拔點。如果你的復雜初始化代碼更好地用Java表示,而不是(可能)冗長的XML,那么你可以創建自己的FactoryBean,在該類中編寫復雜的初始化,然后將自定義FactoryBean插入容器。
FactoryBean<T> 接口提供三種方法:
- T getObject(): 返回此工廠創建的對象的實例。實例可能是共享的,這取決于此工廠返回的是單例還是原型。
- boolean isSingleton(): 如果此FactoryBean返回Singleton,則返回true,否則返回false。此方法的默認實現返回true。
- Class<?> getObjectType(): 返回getObject()方法返回的對象類型,如果類型事先未知,則返回null。
FactoryBean概念和接口在Spring框架中的許多地方都有使用。超過50個FactoryBean接口的實現與Spring本身一起提供。
當你需要向容器請求一個實際的FactoryBean實例本身而不是它生成的bean時,在調用ApplicationContext的getBean()方法時,用符號(&)作為bean id的前綴。因此,對于id為myBean的給定FactoryBean,在容器上調用getBean("myBean")將返回FactoryBean的產品,而調用getBean("&myBean")將返回FactoryBean實例本身。
完畢!!!