容器擴充點
一般來說,應用程式開發人員不需要子類別化 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 後處理器通常會檢查回呼介面,或者它可能會用 Proxy 包裝 Bean。一些 Spring AOP 基礎結構類別實作為 Bean 後處理器,以便提供 Proxy 包裝邏輯。
ApplicationContext
會自動偵測在組態元資料中定義的任何實作 BeanPostProcessor
介面的 Bean。ApplicationContext
將這些 Bean 註冊為後處理器,以便稍後在 Bean 建立時可以呼叫它們。Bean 後處理器可以像任何其他 Bean 一樣部署在容器中。
請注意,當在組態類別上使用 @Bean
工廠方法宣告 BeanPostProcessor
時,工廠方法的傳回類型應該是實作類別本身,或至少是 org.springframework.beans.factory.config.BeanPostProcessor
介面,清楚地表明該 Bean 的後處理器性質。否則,ApplicationContext
無法在完全建立它之前按類型自動偵測它。由於 BeanPostProcessor
需要盡早實例化,以便應用於 Context 中其他 Bean 的初始化,因此這種早期類型偵測至關重要。
以程式設計方式註冊 雖然 BeanPostProcessor 實例BeanPostProcessor 註冊的建議方法是透過 ApplicationContext 自動偵測(如前所述),但您可以使用 addBeanPostProcessor 方法以程式設計方式針對 ConfigurableBeanFactory 註冊它們。當您需要在註冊之前評估條件邏輯,甚至在階層中的 Context 之間複製 Bean 後處理器時,這可能會很有用。但是請注意,以程式設計方式新增的 BeanPostProcessor 實例不遵守 Ordered 介面。在這裡,註冊順序決定了執行順序。另請注意,以程式設計方式註冊的 BeanPostProcessor 實例始終在透過自動偵測註冊的實例之前處理,無論任何明確的排序。 |
BeanPostProcessor 實例和 AOP 自動 Proxy實作 對於任何此類 Bean,您應該會看到一則資訊記錄訊息: 如果您使用自動裝配或 |
以下範例示範如何在 ApplicationContext
中編寫、註冊和使用 BeanPostProcessor
實例。
範例:Hello World,BeanPostProcessor
樣式
第一個範例說明基本用法。該範例顯示了一個自訂 BeanPostProcessor
實作,該實作在容器建立每個 Bean 時呼叫該 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 Script 支援的 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
讀取組態元資料,並有可能在容器實例化任何 Bean 之前 變更它,除了 BeanFactoryPostProcessor
實例之外。
您可以組態多個 BeanFactoryPostProcessor
實例,並且可以透過設定 order
屬性來控制這些 BeanFactoryPostProcessor
實例的執行順序。但是,只有當 BeanFactoryPostProcessor
實作 Ordered
介面時,您才能設定此屬性。如果您編寫自己的 BeanFactoryPostProcessor
,您也應該考慮實作 Ordered
介面。如需更多詳細資訊,請參閱 BeanFactoryPostProcessor
和 Ordered
介面的 Javadoc。
如果您想變更實際的 Bean 實例(即從組態元資料建立的物件),那麼您需要改為使用 此外, |
當 Bean 工廠後處理器在 ApplicationContext
內宣告時會自動執行,以便將變更應用於定義容器的組態元資料。Spring 包含許多預定義的 Bean 工廠後處理器,例如 PropertyOverrideConfigurer
和 PropertySourcesPlaceholderConfigurer
。您也可以使用自訂 BeanFactoryPostProcessor
— 例如,註冊自訂屬性編輯器。
ApplicationContext
會自動偵測部署到其中的任何 Bean,這些 Bean 實作了 BeanFactoryPostProcessor
介面。它會在適當的時機將這些 Bean 作為 Bean 工廠後處理器使用。您可以像部署任何其他 Bean 一樣部署這些後處理器 Bean。
與 BeanPostProcessor 類似,您通常不希望為延遲初始化配置 BeanFactoryPostProcessor 。如果沒有其他 Bean 參考 Bean(Factory)PostProcessor ,則該後處理器根本不會被實例化。因此,標記為延遲初始化將被忽略,即使您在 <beans /> 元素的宣告中將 default-lazy-init 屬性設定為 true ,Bean(Factory)PostProcessor 仍會急切地實例化。 |
範例:類別名稱替換 PropertySourcesPlaceholderConfigurer
您可以使用 PropertySourcesPlaceholderConfigurer
,透過使用標準 Java Properties
格式,從個別檔案中外部化 Bean 定義的屬性值。這樣做可讓部署應用程式的人員自訂特定環境的屬性,例如資料庫 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
屬性。
也支援複合屬性名稱,只要路徑的每個元件(除了要覆寫的最終屬性)都已經是非 null 值(可能是由建構子初始化)。在以下範例中,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
前面加上 and 符號 (&
)。因此,對於 id
為 myBean
的給定 FactoryBean
,在容器上調用 getBean("myBean")
會傳回 FactoryBean
的產品,而調用 getBean("&myBean")
會傳回 FactoryBean
實例本身。