走在前沿
VMware 提供培训和认证,助您快速提升技能。
了解更多最近我参与了一个项目,该项目有一个 Swing 客户端通过 RMI 与服务层进行通信。服务层标注了事务,并且一切看起来都运行良好。但是,每当我们在 Hibernate DAO 层遇到异常时,Spring 都会将异常转换为运行时异常,并将其一直传播到堆栈顶端,并跨越 RMI 连接作为 RemoteException 传播。每当异常被反序列化时,客户端都会出现一个异常(与 RemoteException 分开)。我们决定简单地引入一个切面。任何继承 ServiceAccessException 的异常都将传递给客户端,而其他任何异常都将转换为 FilteredServiceAccessException(ServiceAccessException 的子类),然后抛出。这导致了一些内容丢失,因此我们确保在服务器端记录原始异常(以便在需要时有用),并让客户端显示一个通用对话框,以便用户大致了解发生了什么。
现在,这是一个非常好的计划,并且似乎可以按计划进行,直到我们尝试实现它。我们正在使用自动代理任何标注了 @Transactional 的 Bean 的神奇方式来获取我们的事务代理。我们可以更新该自动代理的定义,以确保添加了用于此异常过滤的建议(想想 setPreInterceptor 在 TransactionProxyFactoryBean 中),但自动代理捕获的不仅仅是服务层。
那么这让我们处于什么境地呢?我们可以 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。
重要的是要知道,这不会影响任何已经存在的尝试执行相同操作的实现。这只会让所有那些一直在等待“最后负责时刻”来解决此问题的人的生活更轻松。 :)
附注:与上一篇文章一样,我包含了来自本示例的 项目存档,以便您在需要时查看其余代码。