领先一步
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。
重要的是要知道,这不应该影响任何已经存在且试图做同样事情的实现。这应该只是让那些一直在等待“最后负责时刻”来解决这个问题的人生活更轻松。:)
附言:和上一篇文章一样,我包含了本示例的项目存档,以便您在需要时可以查看其余代码。