使用 TargetSource
實作
Spring 提供了 TargetSource
的概念,在 org.springframework.aop.TargetSource
介面中表達。此介面負責回傳實作連接點的「目標物件」。每次 AOP 代理處理方法調用時,都會請求 TargetSource
實作提供目標實例。
使用 Spring AOP 的開發人員通常不需要直接使用 TargetSource
實作,但這提供了一種強大的方式來支援池化、熱抽換和其他複雜的目標。例如,池化 TargetSource
可以為每次調用回傳不同的目標實例,透過使用池來管理實例。
如果您未指定 TargetSource
,則會使用預設實作來包裝本機物件。每次調用都會回傳相同的目標 (正如您所預期的)。
本節的其餘部分描述了 Spring 提供的標準目標來源,以及如何使用它們。
當使用自訂目標來源時,您的目標通常需要是 prototype 而不是 singleton 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
都可以與任意通知結合使用。
池化目標來源
使用池化目標來源提供了類似於無狀態 Session EJB 的程式設計模型,其中維護了相同實例的池,方法調用會轉到池中的空閒物件。
Spring 池化與 SLSB 池化的關鍵差異在於 Spring 池化可以應用於任何 POJO。與一般 Spring 一樣,此服務可以以非侵入式的方式應用。
Spring 提供了對 Commons Pool 2.2 的支援,後者提供了相當有效率的池化實作。您需要在應用程式的類別路徑中加入 commons-pool
Jar 才能使用此功能。您也可以子類別化 org.springframework.aop.target.AbstractPoolingTargetSource
以支援任何其他池化 API。
Commons Pool 1.5+ 也受支援,但自 Spring Framework 4.2 起已棄用。 |
以下列表顯示了一個範例組態
<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
) 必須是 prototype。這讓 PoolingTargetSource
實作可以建立目標的新實例,以根據需要擴充池。請參閱 AbstractPoolingTargetSource
的 javadoc 以及您想要使用的具體子類別,以取得關於其屬性的資訊。maxSize
是最基本的,並且始終保證存在。
在此情況下,myInterceptor
是需要於相同 IoC Context 中定義的攔截器名稱。但是,您不需要指定攔截器即可使用池化。如果您只想要池化而沒有其他通知,請完全不要設定 interceptorNames
屬性。
您可以組態 Spring,使其能夠將任何池化物件轉換為 org.springframework.aop.target.PoolingConfig
介面,該介面透過引介公開有關池的組態和目前大小的資訊。您需要定義類似於以下的 Advisor
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
此 Advisor 是透過呼叫 AbstractPoolingTargetSource
類別上的便利方法取得的,因此使用了 MethodInvokingFactoryBean
。此 Advisor 的名稱 (在此為 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
實作。
Prototype 目標來源
設定 "prototype" 目標來源與設定池化 TargetSource
類似。在這種情況下,每次方法調用都會建立目標的新實例。雖然在現代 JVM 中建立新物件的成本不高,但佈線新物件 (滿足其 IoC 相依性) 的成本可能更高。因此,除非有非常好的理由,否則您不應使用此方法。
為此,您可以修改先前顯示的 poolTargetSource
定義,如下所示 (為了清楚起見,我們也變更了名稱)
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的屬性是目標 Bean 的名稱。繼承在 TargetSource
實作中使用,以確保一致的命名。與池化目標來源一樣,目標 Bean 必須是 prototype 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 實例。 |