Spring 2.0 的另一个亮点:拦截器组合

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

最近我参与了一个项目,该项目有一个 Swing 客户端通过 RMI 与服务层进行通信。服务层标注了事务,并且一切看起来都运行良好。但是,每当我们在 Hibernate DAO 层遇到异常时,Spring 都会将异常转换为运行时异常,并将其一直传播到堆栈顶端,并跨越 RMI 连接作为 RemoteException 传播。每当异常被反序列化时,客户端都会出现一个异常(与 RemoteException 分开)。我们决定简单地引入一个切面。任何继承 ServiceAccessException 的异常都将传递给客户端,而其他任何异常都将转换为 FilteredServiceAccessExceptionServiceAccessException 的子类),然后抛出。这导致了一些内容丢失,因此我们确保在服务器端记录原始异常(以便在需要时有用),并让客户端显示一个通用对话框,以便用户大致了解发生了什么。

现在,这是一个非常好的计划,并且似乎可以按计划进行,直到我们尝试实现它。我们正在使用自动代理任何标注了 @Transactional 的 Bean 的神奇方式来获取我们的事务代理。我们可以更新该自动代理的定义,以确保添加了用于此异常过滤的建议(想想 setPreInterceptorTransactionProxyFactoryBean 中),但自动代理捕获的不仅仅是服务层。

那么这让我们处于什么境地呢?我们可以 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 社区中所有即将举行的活动。

查看全部