使用 TargetSource 實現

Spring 提供了 TargetSource 的概念,它透過 org.springframework.aop.TargetSource 介面表示。此介面負責返回實現連線點的“目標物件”。每次 AOP 代理處理方法呼叫時,都會向 TargetSource 實現請求一個目標例項。

使用 Spring AOP 的開發者通常不需要直接使用 TargetSource 實現,但這提供了一種強大的方式來支援池化、熱插拔和其他複雜的 targets。例如,一個池化 TargetSource 可以透過使用一個池來管理例項,為每次呼叫返回一個不同的目標例項。

如果您未指定 TargetSource,則使用預設實現來包裝本地物件。每次呼叫都會返回同一個目標(如您所期望的)。

本節的其餘部分描述了 Spring 提供的標準目標源以及如何使用它們。

使用自定義目標源時,您的目標通常需要是原型而非單例 bean 定義。這允許 Spring 在需要時建立新的目標例項。

熱插拔目標源

org.springframework.aop.target.HotSwappableTargetSource 允許在呼叫者保持對其引用的同時切換 AOP 代理的目標。

更改目標源的目標會立即生效。HotSwappableTargetSource 是執行緒安全的。

您可以使用 HotSwappableTargetSource 上的 swap() 方法來更改目標,如下例所示:

  • Java

  • Kotlin

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)

以下示例顯示了所需的 XML 定義

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
	<constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="targetSource" ref="swapper"/>
</bean>

上述 swap() 呼叫會更改可交換 bean 的目標。持有該 bean 引用的客戶端並不知道此更改,但會立即開始命中新的目標。

儘管此示例未新增任何通知(使用 TargetSource 不需要新增通知),但任何 TargetSource 都可以與任意通知結合使用。

池化目標源

使用池化目標源提供了一種類似於無狀態會話 EJB 的程式設計模型,其中維護一個相同的例項池,方法呼叫會發送到池中空閒的物件。

Spring 池化與 SLSB 池化的一個關鍵區別在於,Spring 池化可以應用於任何 POJO。與 Spring 通常情況一樣,此服務可以以非侵入式方式應用。

Spring 為 Commons Pool 2 提供了支援,它提供了一個相當高效的池化實現。您需要將 commons-pool Jar 放在應用程式的類路徑中才能使用此功能。您還可以繼承 org.springframework.aop.target.AbstractPoolingTargetSource 來支援任何其他池化 API。

以下列表顯示了一個示例配置

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
		scope="prototype">
	... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
	<property name="targetBeanName" value="businessObjectTarget"/>
	<property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="targetSource" ref="poolTargetSource"/>
	<property name="interceptorNames" value="myInterceptor"/>
</bean>

請注意,目標物件(在前面的示例中為 businessObjectTarget)必須是原型。這允許 PoolingTargetSource 實現建立新的目標例項,以根據需要擴充套件池。有關其屬性的資訊,請參閱 AbstractPoolingTargetSource 的 javadoc 和您希望使用的具體子類。maxSize 是最基本的,並且始終保證存在。

在這種情況下,myInterceptor 是一個攔截器的名稱,需要在相同的 IoC 上下文中定義。但是,您不必指定攔截器來使用池。如果您只想進行池化而不需要其他通知,則完全不要設定 interceptorNames 屬性。

您可以配置 Spring,使其能夠將任何池化物件強制轉換為 org.springframework.aop.target.PoolingConfig 介面,該介面透過引入公開有關池配置和當前大小的資訊。您需要定義一個類似於以下的切面

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
	<property name="targetObject" ref="poolTargetSource"/>
	<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

此切面是透過呼叫 AbstractPoolingTargetSource 類上的便利方法獲得的,因此使用了 MethodInvokingFactoryBean。此切面的名稱(此處為 poolConfigAdvisor)必須在公開池化物件的 ProxyFactoryBean 的攔截器名稱列表中。

強制轉換定義如下

  • Java

  • Kotlin

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
池化無狀態服務物件通常不是必需的。我們不認為它應該是預設選擇,因為大多數無狀態物件天生就是執行緒安全的,並且如果資源被快取,例項池化會存在問題。

透過使用自動代理可以實現更簡單的池化。您可以設定任何自動代理建立器使用的 TargetSource 實現。

原型目標源

設定“原型”目標源類似於設定池化 TargetSource。在這種情況下,每次方法呼叫都會建立一個新的目標例項。雖然在現代 JVM 中建立新物件的成本不高,但連線新物件(滿足其 IoC 依賴項)的成本可能更高。因此,如果沒有非常充分的理由,您不應使用此方法。

為此,您可以按如下方式修改前面顯示的 poolTargetSource 定義(為清晰起見,我們還更改了名稱):

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
	<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

唯一的屬性是目標 bean 的名稱。TargetSource 實現中使用了繼承來確保命名的一致性。與池化目標源一樣,目標 bean 必須是原型 bean 定義。

ThreadLocal 目標源

如果您需要為每個傳入請求(即每個執行緒)建立一個物件,ThreadLocal 目標源會很有用。ThreadLocal 的概念提供了一個 JDK 範圍的設施,可以透明地將資源與執行緒一起儲存。設定 ThreadLocalTargetSource 與其他型別的目標源的解釋基本相同,如下例所示:

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
	<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
在多執行緒和多類載入器環境中錯誤使用 ThreadLocal 例項會帶來嚴重問題(可能導致記憶體洩漏)。您應該始終考慮將 ThreadLocal 包裝在其他類中,並且永遠不要直接使用 ThreadLocal 本身(除了在包裝類中)。此外,您應該始終記住正確設定和取消設定(後者涉及呼叫 ThreadLocal.remove())執行緒本地的資源。無論如何都應該取消設定,因為不取消設定可能會導致問題行為。Spring 的 ThreadLocal 支援為您完成了這項工作,並且應該始終優先於不帶其他適當處理程式碼的 ThreadLocal 例項。
© . This site is unofficial and not affiliated with VMware.