容器擴充套件點
通常,應用開發者無需繼承 ApplicationContext
實現類。相反,可以透過插入特殊的整合介面的實現來擴充套件 Spring IoC 容器。接下來的幾節將介紹這些整合介面。
使用 BeanPostProcessor
定製 Bean
BeanPostProcessor
介面定義了回撥方法,你可以實現這些方法來提供你自己的(或覆蓋容器預設的)例項化邏輯、依賴解析邏輯等。如果你想在 Spring 容器完成例項化、配置和初始化 Bean 之後實現一些自定義邏輯,你可以插入一個或多個自定義的 BeanPostProcessor
實現。
你可以配置多個 BeanPostProcessor
例項,並透過設定 order
屬性來控制這些 BeanPostProcessor
例項的執行順序。只有當 BeanPostProcessor
實現 Ordered
介面時,才能設定此屬性。如果你編寫自己的 BeanPostProcessor
,也應考慮實現 Ordered
介面。更多詳細資訊,請參閱 BeanPostProcessor
和 Ordered
介面的 javadoc。另請參閱關於以程式設計方式註冊 BeanPostProcessor
例項的說明。
要修改實際的 Bean 定義(即定義 Bean 的藍圖),你需要使用 |
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
工廠方法宣告 BeanPostProcessor
時,工廠方法的返回型別應該是實現類本身或至少是 org.springframework.beans.factory.config.BeanPostProcessor
介面,以清楚地表明該 Bean 的後處理器性質。否則,ApplicationContext
在完全建立它之前無法透過型別自動檢測到它。由於 BeanPostProcessor
需要在上下文初始化其他 Bean 之前儘早例項化,因此這種早期型別檢測至關重要。
以程式設計方式註冊 雖然推薦的 BeanPostProcessor 例項BeanPostProcessor 註冊方法是透過 ApplicationContext 自動檢測(如前所述),但你可以透過呼叫 ConfigurableBeanFactory 的 addBeanPostProcessor 方法以程式設計方式註冊它們。這在你需要在註冊前評估條件邏輯或甚至在層級結構中的上下文之間複製 Bean 後處理器時非常有用。但是請注意,以程式設計方式新增的 BeanPostProcessor 例項不遵循 Ordered 介面。在這裡,註冊的順序決定了執行順序。另請注意,以程式設計方式註冊的 BeanPostProcessor 例項總是先於透過自動檢測註冊的例項處理,無論是否有任何明確的排序。 |
BeanPostProcessor 例項和 AOP 自動代理實現 對於任何此類 Bean,你應該會看到一條資訊日誌訊息: 如果你透過自動裝配或 |
以下示例展示瞭如何在 ApplicationContext
中編寫、註冊和使用 BeanPostProcessor
例項。
示例:Hello World,BeanPostProcessor
風格
第一個示例演示了基本用法。示例展示了一個自定義 BeanPostProcessor
實現,它在容器建立每個 Bean 時呼叫其 toString()
方法並將結果字串列印到系統控制檯。
以下清單顯示了自定義 BeanPostProcessor
實現類的定義
-
Java
-
Kotlin
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;
}
}
package scripting
import org.springframework.beans.factory.config.BeanPostProcessor
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
// simply return the instantiated bean as-is
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
return bean // we could potentially return any object reference here...
}
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
println("Bean '$beanName' created : $bean")
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://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 動態語言支援在題為動態語言支援的章節中有詳細介紹。)
以下 Java 應用執行前面的程式碼和配置
-
Java
-
Kotlin
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 = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
val messenger = ctx.getBean<Messenger>("messenger")
println(messenger)
}
前面應用的輸出類似於以下內容
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
使用 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 例項(即從配置元資料建立的物件),那麼你需要使用 此外, |
Bean 工廠後處理器在 ApplicationContext
中宣告時會自動執行,以便對定義容器的配置元資料應用更改。Spring 包含一些預定義的 Bean 工廠後處理器,例如 PropertyOverrideConfigurer
和 PropertySourcesPlaceholderConfigurer
。你也可以使用自定義的 BeanFactoryPostProcessor
,例如註冊自定義的屬性編輯器。
ApplicationContext
會自動檢測部署到其中的、實現 BeanFactoryPostProcessor
介面的任何 Bean。它在適當的時候將這些 Bean 用作 Bean 工廠後處理器。你可以像部署任何其他 Bean 一樣部署這些後處理器 Bean。
與 BeanPostProcessor 類似,你通常不希望為 BeanFactoryPostProcessor 配置延遲初始化。如果沒有其他 Bean 引用 Bean(Factory)PostProcessor ,該後處理器根本不會被例項化。因此,將其標記為延遲初始化將被忽略,即使你在 <beans /> 元素的宣告中將 default-lazy-init 屬性設定為 true ,Bean(Factory)PostProcessor 也會被立即例項化。 |
示例:類名替換 PropertySourcesPlaceholderConfigurer
你可以使用 PropertySourcesPlaceholderConfigurer
將 Bean 定義中的屬性值使用標準的 Java Properties
格式外部化到單獨的檔案中。這樣做可以使部署應用程式的人員定製環境特定的屬性(例如資料庫 URL 和密碼),而無需修改容器的主要 XML 定義檔案或檔案,從而降低複雜性和風險。
考慮以下基於 XML 的配置元資料片段,其中定義了一個帶有佔位符值的 DataSource
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<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
檔案配置的屬性。在執行時,PropertySourcesPlaceholderConfigurer
應用於元資料,替換 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' 替換,對於屬性檔案中匹配鍵的其他佔位符值也是如此。PropertySourcesPlaceholderConfigurer
會檢查 Bean 定義的大多數屬性和特性中的佔位符。此外,你可以自定義佔位符字首和字尾。
隨著 Spring 2.5 引入的 context
名稱空間,你可以使用專用的配置元素來配置屬性佔位符。你可以在 location
屬性中提供一個或多個位置,用逗號分隔,如下例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不僅會在你指定的 Properties
檔案中查詢屬性。預設情況下,如果在指定的屬性檔案中找不到某個屬性,它會檢查 Spring Environment
屬性和常規的 Java System
屬性。
對於給定的應用程式,只需要定義一個這樣的元素,其中包含其所需的屬性。只要佔位符語法 ( 如果你需要對用於替換的屬性來源進行模組化,則不應建立多個屬性佔位符。相反,你應該建立自己的 |
你可以使用
如果類在執行時無法解析為有效的類,則在即將建立 bean 時(對於非延遲初始化的 bean,這發生在 |
示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer
是另一個 bean 工廠後處理器,它類似於 PropertySourcesPlaceholderConfigurer
,但與後者不同的是,原始定義可以為 bean 屬性設定預設值或完全沒有值。如果覆蓋的 Properties
檔案沒有某個 bean 屬性的條目,則使用預設的上下文定義。
請注意,bean 定義並不知道自己被覆蓋了,因此從 XML 定義檔案上並不能立即看出正在使用覆蓋配置器。如果存在多個 PropertyOverrideConfigurer
例項為同一個 bean 屬性定義了不同的值,則最後一個會生效,這是由覆蓋機制決定的。
屬性檔案的配置行採用以下格式:
beanName.property=value
以下清單顯示了該格式的一個示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
這個示例檔案可以用於一個容器定義,該定義包含一個名為 dataSource
的 bean,該 bean 具有 driverClassName
和 url
屬性。
還支援複合屬性名稱,前提是除了最終要被覆蓋的屬性之外,路徑中的每個元件都已非空(presumably 通常由建構函式初始化)。在下面的示例中,tom
bean 的 fred
屬性的 bob
屬性的 sammy
屬性被設定為標量值 123
。
tom.fred.bob.sammy=123
指定的覆蓋值始終是字面值。它們不會被轉換為 bean 引用。此約定也適用於 XML bean 定義中的原始值指定 bean 引用的情況。 |
隨著 Spring 2.5 引入的 context
名稱空間,可以使用專用的配置元素來配置屬性覆蓋,如下例所示:
<context:property-override location="classpath:override.properties"/>
使用 FactoryBean
自定義例項化邏輯
對於本身就是工廠的物件,你可以實現 org.springframework.beans.factory.FactoryBean
介面。
FactoryBean
介面是 Spring IoC 容器例項化邏輯的一個可插拔點。如果你有複雜的初始化程式碼,並且這些程式碼用 Java 表達比用(潛在的)冗長 XML 表達更好,你可以建立自己的 FactoryBean
,在該類內部編寫複雜的初始化邏輯,然後將自定義的 FactoryBean
插入到容器中。
FactoryBean<T>
介面提供了三個方法:
-
T getObject()
: 返回此工廠建立的物件例項。根據此工廠返回的是單例還是原型,例項可能是共享的。 -
boolean isSingleton()
: 如果此FactoryBean
返回單例,則返回true
,否則返回false
。此方法的預設實現返回true
。 -
Class<?> getObjectType()
: 返回getObject()
方法返回的物件型別,如果型別提前未知則返回null
。
FactoryBean
概念和介面在 Spring Framework 中許多地方都有使用。Spring 自身就包含了 50 多個 FactoryBean
介面的實現。
當你需要容器返回實際的 FactoryBean
例項本身而不是它產生的 bean 時,在呼叫 ApplicationContext
的 getBean()
方法時,在 bean 的 id
前加上和號符號 (&
)。因此,對於 id
為 myBean
的給定 FactoryBean
,在容器上呼叫 getBean("myBean")
會返回 FactoryBean
的產品(即由 FactoryBean
建立的 bean),而呼叫 getBean("&myBean")
則返回 FactoryBean
例項本身。