容器擴充套件點
通常,應用程式開發者無需繼承 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 自動檢測(如前所述),但您可以使用 addBeanPostProcessor 方法將它們程式設計式地註冊到 ConfigurableBeanFactory。這在您需要在註冊之前評估條件邏輯,甚至在層次結構中跨上下文複製 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。)
以下 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 透過使用標準 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 定義的大多數屬性和屬性中的佔位符。此外,您可以自定義佔位符字首、字尾、預設值分隔符和跳脫字元。此外,可以透過 JVM 系統屬性(或透過 SpringProperties 機制)設定 spring.placeholder.escapeCharacter.default 屬性來全域性更改或停用預設跳脫字元。
使用 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 且具有 driverClassName 和 url 屬性的 Bean 的容器定義一起使用。
也支援複合屬性名,只要路徑的每個元件(除了最終被覆蓋的屬性)都已非空(可能由建構函式初始化)。在以下示例中,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 框架的許多地方都有使用。Spring 本身就包含了 50 多個 FactoryBean 介面的實現。
當您需要向容器請求實際的 FactoryBean 例項本身而不是它生成的 Bean 時,在呼叫 ApplicationContext 的 getBean() 方法時,請在 Bean 的 id 前加上 & 符號(&)。因此,對於一個 id 為 myBean 的給定 FactoryBean,在容器上呼叫 getBean("myBean") 返回 FactoryBean 的產品,而呼叫 getBean("&myBean") 返回 FactoryBean 例項本身。