使用 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 例項。 |