爱上 Spring 2.0 的另一个理由:拦截器组合

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

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

现在这是一个非常好的计划,并且似乎可以正常工作,直到我们尝试实现它。我们使用神奇的方式自动代理任何带有 @Transactional 的 bean 以获得我们的事务代理。我们可以更新该自动代理的定义,以确保添加此异常过滤的 advice(想想 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 社区中所有即将举行的活动。

查看全部