爱上 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 社区所有即将举行的活动。

查看所有