愛上 Spring 2.0 的另一個理由:攔截器組合

工程 | Ben Hale | 2006 年 4 月 9 日 | ...

最近我在一個專案上工作,該專案有一個透過 RMI 與服務層通訊的 Swing 客戶端。服務層標有事務,一切似乎都執行良好。但是,每次我們在 Hibernate DAO 層遇到異常時,Spring 都會將該異常轉換為執行時異常,並將其作為 RemoteException 一直向上堆疊傳播,並跨 RMI 連線傳播。每當異常被反序列化時,客戶端上都會出現一個異常(與 RemoteException 分開)。我們決定簡單地引入一個切面。任何繼承自 ServiceAccessException 的異常都會傳遞到客戶端,而其他任何異常都將被轉換為 FilteredServiceAccessExceptionServiceAccessException 的子類)然後被丟擲。這導致了一些內容的丟失,因此我們確保在伺服器上記錄原始異常,以便它有用,並讓客戶端顯示一個通用對話方塊,以便使用者大致瞭解發生了什麼。

現在這是一個非常好的計劃,並且似乎可以正常工作,直到我們嘗試實現它。我們使用神奇的方式自動代理任何帶有 @Transactional 的 bean 以獲得我們的事務代理。我們可以更新該自動代理的定義,以確保新增此異常過濾的 advice(想想 setPreInterceptorTransactionProxyFactoryBean 中),但該自動代理捕獲的不僅僅是服務層。

那麼我們該怎麼辦呢?我們可以 A) 顯式宣告 TransactionProxyFactoryBean 的每次使用,B) 建立兩組不同的自動代理,並使它們彼此互斥,或者 C) 現在忽略該要求,並希望發生一些神奇的事情。由於該產品離消費者還有六個月的時間,並且我儘量遵循 Jeremy Miller 向我介紹的“最後的責任時刻”原則,我決定推遲這個問題,選擇 A 作為我的備用計劃(最好沒有魔法,而不是兩倍的魔法)。

瞧,Spring 2.0 解決了我的問題。我怎麼也找不到我在哪裡讀到過,但從 2.0 的一個里程碑開始,當一個 bean 被代理時,代理工廠現在可以檢測到該 bean 已經有一個代理,並且只是將預期的攔截器新增為另一個攔截器(如果您知道它在哪裡,請將連結留在評論中)。這意味著我可以只使用新的魔法 (tx:annotation-driven) 並簡單地新增一個帶有我想要的正確切入點的切面,而不必擔心事務代理和 AOP 代理被混淆。不太確定這是怎麼回事? 來看看這個例子。首先是一個介面和實現。


package interceptorcombiningexample;

import org.springframework.transaction.annotation.Transactional;

@Transactional
public interface ExampleTarget {

	void exampleMethod();

}

package interceptorcombiningexample;

public class DefaultExampleTarget implements ExampleTarget {

	public void exampleMethod() {
	}
}

請注意,該介面標記為 @Transactional。稍後我們將使用它來獲得一些神奇的自動代理。接下來我們將看看 bean 定義。


<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

	<tx:annotation-driven />

	<aop:config>
		<aop:aspect id="exampleAspect" ref="exampleAdvice">
			<aop:before method="exampleAdvice"
				pointcut="execution(* interceptorcombiningexample.ExampleTarget.exampleMethod())" />
		</aop:aspect>
	</aop:config>

	<bean id="exampleAdvice"
		class="interceptorcombiningexample.ExampleAdvice" />

	<bean id="exampleTarget"
		class="interceptorcombiningexample.DefaultExampleTarget" />

	<bean id="transactionManager"
		class="interceptorcombiningexample.DummyTransactionManager" />

</beans>

您會注意到我們設定了註釋驅動的事務,這將自動在我們的 DefaultExampleTarget 周圍構建一個代理。此外,我們定義了另一個應該需要代理相同 DefaultExampleTarget bean 的切面。最後,讓我們看看我們的可執行類。


package interceptorcombiningexample;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InterceptorCombiningExample {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext(
				"classpath:interceptorcombiningexample/applicationContext.xml");

		ExampleTarget target = (ExampleTarget) ctx.getBean("exampleTarget");
		if (target instanceof Advised) {
			Advised advised = (Advised) target;
			System.out
					.println("Advisor count: " + advised.getAdvisors().length);
			for (Advisor advisor : advised.getAdvisors()) {
				System.out.println("Advisor type: "
						+ advisor.getAdvice().getClass().getName());
			}
		}
	}

}

這個類利用了 Spring 代理機制的一個很好的小特性。任何 Spring 建立的代理都可以轉換為 Advised 介面。此介面將使您可以訪問代理中的所有攔截器。當我們繼續執行這個類時,輸出顯示

Advisor count: 3
Advisor type: org.springframework.aop.interceptor.ExposeInvocationInterceptor
Advisor type: org.springframework.transaction.interceptor.TransactionInterceptor
Advisor type: org.springframework.aop.aspectj.AspectJMethodBeforeAdvice

由此我們可以看到,不僅 TransactionInterceptor 包含在代理中,而且 AspectJMethodBeforeAdvice 也包含在代理中。

重要的是要知道這不應該影響任何已經存在的嘗試做同樣事情的實現。 這應該只是讓所有一直在等待“最後的責任時刻”來解決這個問題的人的生活更輕鬆。 :)

附:正如在上一篇文章中一樣,我包含了一個來自此示例的 專案存檔,以便您可以根據需要檢視其餘程式碼。

獲取 Spring 新聞資訊

透過 Spring 新聞資訊保持聯絡

訂閱

更上一層樓

VMware 提供培訓和認證,以加速您的進步。

瞭解更多

獲得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

檢視 Spring 社群中所有即將舉行的活動。

檢視全部