領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多最近我正在做一個專案,該專案有一個Swing客戶端,透過RMI與服務層進行通訊。服務層被標記為事務性的,一切似乎都執行良好。然而,每當我們在Hibernate DAO層遇到異常時,Spring都會將異常轉換為執行時異常,並將其一直傳播到棧頂,並透過RMI連線作為RemoteException。每當異常被反序列化時,客戶端就會出現一個異常(獨立於RemoteException)。我們決定簡單地引入一個切面。任何繼承自ServiceAccessException的異常都將被傳遞給客戶端,而其他任何異常都將被轉換為FilteredServiceAccessException(ServiceAccessException的子類),然後丟擲。這導致了一些內容丟失,所以我們確保在伺服器上記錄原始異常,以便它可能有用,並讓客戶端顯示一個通用對話方塊,以便使用者大致知道發生了什麼。
這是一個相當不錯的計劃,並且似乎有望奏效,直到我們嘗試實施它。我們使用自動代理的“魔術”方式,為所有帶有@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。
重要的是要知道,這不應該影響任何已經存在且試圖做同樣事情的實現。這應該只是讓那些一直在等待“最後負責時刻”來解決這個問題的人生活更輕鬆。:)
附言:和上一篇文章一樣,我包含了本示例的專案存檔,以便您在需要時可以檢視其餘程式碼。