容器擴充套件點

通常,應用程式開發者無需繼承 ApplicationContext 實現類。相反,Spring IoC 容器可以透過插入特殊整合介面的實現來擴充套件。接下來的幾節將描述這些整合介面。

透過使用 BeanPostProcessor 自定義 Bean

BeanPostProcessor 介面定義了回撥方法,您可以實現這些方法來提供您自己的(或覆蓋容器預設的)例項化邏輯、依賴解析邏輯等。如果您希望在 Spring 容器完成 Bean 的例項化、配置和初始化之後實現一些自定義邏輯,您可以插入一個或多個自定義的 BeanPostProcessor 實現。

您可以配置多個 BeanPostProcessor 例項,並且可以透過設定 order 屬性來控制這些 BeanPostProcessor 例項的執行順序。只有當 BeanPostProcessor 實現 Ordered 介面時,您才能設定此屬性。如果您編寫自己的 BeanPostProcessor,您也應該考慮實現 Ordered 介面。有關更多詳細資訊,請參閱 BeanPostProcessorOrdered 介面的 Javadoc。另請參閱關於 BeanPostProcessor 例項的程式設計式註冊 的說明。

BeanPostProcessor 例項操作 Bean(或物件)例項。也就是說,Spring IoC 容器例項化一個 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。一些 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 自動代理

實現 BeanPostProcessor 介面的類是特殊的,並且被容器以不同的方式對待。所有 BeanPostProcessor 例項以及它們直接引用的 Bean 都在啟動時例項化,作為 ApplicationContext 特殊啟動階段的一部分。接下來,所有 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,因此使它們不符合自動代理或其他型別的 Bean 後處理的條件。例如,如果您有一個用 @Resource 註解的依賴項,其中欄位或 setter 名稱與 Bean 的宣告名稱不直接對應,並且沒有使用 name 屬性,Spring 會訪問其他 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

示例:AutowiredAnnotationBeanPostProcessor

結合自定義 BeanPostProcessor 實現使用回撥介面或註解是擴充套件 Spring IoC 容器的常見方式。一個例子是 Spring 的 AutowiredAnnotationBeanPostProcessor —— 一個隨 Spring 釋出提供的 BeanPostProcessor 實現,它自動裝配註解欄位、setter 方法和任意配置方法。

使用 BeanFactoryPostProcessor 自定義配置元資料

我們接下來要看的擴充套件點是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。該介面的語義與 BeanPostProcessor 相似,但有一個主要區別:BeanFactoryPostProcessor 操作的是 Bean 配置元資料。也就是說,Spring IoC 容器允許 BeanFactoryPostProcessor 讀取配置元資料並可能在容器例項化除 BeanFactoryPostProcessor 例項之外的任何 Bean 之前 更改它。

您可以配置多個 BeanFactoryPostProcessor 例項,並且可以透過設定 order 屬性來控制這些 BeanFactoryPostProcessor 例項的執行順序。但是,只有當 BeanFactoryPostProcessor 實現 Ordered 介面時,您才能設定此屬性。如果您編寫自己的 BeanFactoryPostProcessor,您也應該考慮實現 Ordered 介面。有關更多詳細資訊,請參閱 BeanFactoryPostProcessorOrdered 介面的 Javadoc。

如果您想更改實際的 Bean 例項(即從配置元資料建立的物件),那麼您需要使用 BeanPostProcessor(前面在 使用 BeanPostProcessor 自定義 Bean 中描述)。雖然在 BeanFactoryPostProcessor 中處理 Bean 例項(例如,透過使用 BeanFactory.getBean())在技術上是可行的,但這樣做會導致 Bean 過早例項化,從而違反標準容器生命週期。這可能會導致負面副作用,例如繞過 Bean 後處理。

此外,BeanFactoryPostProcessor 例項是按容器作用域的。這僅在您使用容器層次結構時才相關。如果您在一個容器中定義一個 BeanFactoryPostProcessor,它只應用於該容器中的 Bean 定義。一個容器中的 Bean 定義不會被另一個容器中的 BeanFactoryPostProcessor 例項後處理,即使這兩個容器是同一層次結構的一部分。

當一個 Bean 工廠後處理器在 ApplicationContext 中宣告時,它會自動執行,以便對定義容器的配置元資料應用更改。Spring 包含了許多預定義的 Bean 工廠後處理器,例如 PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。您還可以使用自定義的 BeanFactoryPostProcessor —— 例如,註冊自定義屬性編輯器。

ApplicationContext 會自動檢測其中部署的任何實現 BeanFactoryPostProcessor 介面的 Bean。它會在適當的時候將這些 Bean 用作 Bean 工廠後處理器。您可以像部署任何其他 Bean 一樣部署這些後處理器 Bean。

BeanPostProcessor 類似,您通常不希望將 BeanFactoryPostProcessor 配置為延遲初始化。如果沒有其他 Bean 引用 Bean(Factory)PostProcessor,則該後處理器將完全不會被例項化。因此,將其標記為延遲初始化將被忽略,並且即使您在 <beans /> 元素的宣告上將 default-lazy-init 屬性設定為 trueBean(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 屬性進行檢查。

對於給定的應用程式,應該只定義一個這樣的元素,幷包含它所需的屬性。只要它們具有不同的佔位符語法(${…​}),就可以配置多個屬性佔位符。

如果您需要將用於替換的屬性源模組化,則不應建立多個屬性佔位符。相反,您應該建立自己的 PropertySourcesPlaceholderConfigurer Bean,它會收集要使用的屬性。

您可以使用 PropertySourcesPlaceholderConfigurer 替換類名,這在您必須在執行時選擇特定實現類時有時很有用。以下示例展示瞭如何做到這一點

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
	<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,在 ApplicationContextpreInstantiateSingletons() 階段),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 且具有 driverClassNameurl 屬性的 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 時,在呼叫 ApplicationContextgetBean() 方法時,請在 Bean 的 id 前加上 & 符號(&)。因此,對於一個 idmyBean 的給定 FactoryBean,在容器上呼叫 getBean("myBean") 返回 FactoryBean 的產品,而呼叫 getBean("&myBean") 返回 FactoryBean 例項本身。

© . This site is unofficial and not affiliated with VMware.