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

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

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

這是一個相當不錯的計劃,並且似乎有望奏效,直到我們嘗試實施它。我們使用自動代理的“魔術”方式,為所有帶有@Transactional的bean獲取事務代理。我們本可以更新自動代理的定義,以確保新增此異常過濾的通知(請參見TransactionProxyFactoryBean中的setPreInterceptor),但自動代理捕獲的不僅僅是服務層。

那麼這讓我們處於何種境地呢?我們可以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 社群所有即將舉行的活動。

檢視所有