首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

Spring interceptor - 古老的相传

2012-10-09 
Spring interceptor -- 古老的传说1 Spring的通知类型现在让我们看看Spring AOP是如何处理通知的。1.1. 通

Spring interceptor -- 古老的传说

1 Spring的通知类型

现在让我们看看Spring AOP是如何处理通知的。

1.1. 通知的生命周期

Spring的通知可以跨越多个被通知对象共享,或者每个被通知对象有自己的通知。这分别对应 per-class或per-instance 通知。

Per-class通知使用最为广泛。它适合于通用的通知,如事务adisor。它们不依赖被代理 的对象的状态,也不添加新的状态。它们仅仅作用于方法和方法的参数。

Per-instance通知适合于导入,来支持混入(mixin)。在这种情况下,通知添加状态到 被代理的对象。

可以在同一个AOP代理中混合使用共享和per-instance通知。

1.2. Spring中通知类型

Spring提供几种现成的通知类型并可扩展提供任意的通知类型。让我们看看基本概念和标准的通知类型。

1.2.1. Interception around advice

Spring中最基本的通知类型是interception around advice .

Spring使用方法拦截器的around通知是和AOP联盟接口兼容的。实现around通知的 类需要实现接口MethodInterceptor:

public interface MethodInterceptor extends Interceptor {

??? Object invoke(MethodInvocation invocation) throws Throwable;

}

invoke()方法的MethodInvocation 参数暴露将被调用的方法、目标连接点、AOP代理和传递给被调用方法的参数。 invoke()方法应该返回调用的结果:连接点的返回值。

一个简单的MethodInterceptor实现看起来如下:

public class DebugInterceptor implements MethodInterceptor {

??? public Object invoke(MethodInvocation invocation) throws Throwable {

??????? System.out.println("Before: invocation=[" + invocation + "]");

??????? Object rval = invocation.proceed();

??????? System.out.println("Invocation returned");

??????? return rval;

??? }

}

注意MethodInvocation的proceed()方法的调用。这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大部分拦截器会调用这个方法,并返回它的返回值。但是, 一个MethodInterceptor,和任何around通知一样,可以返回不同的值或者抛出一个异常,而 不调用proceed方法。但是,没有好的原因你要这么做。

MethodInterceptor提供了和其他AOP联盟的兼容实现的交互能力。这一节下面 要讨论的其他的通知类型实现了AOP公共的概念,但是以Spring特定的方式。虽然使用特定 通知类型有很多优点,但如果你可能需要在其他的AOP框架中使用,请坚持使用MethodInterceptor around通知类型。注意目前切入点不能和其它框架交互操作,并且AOP联盟目前也没有定义切入 点接口。

1.2.2. Before通知

Before通知是一种简单的通知类型。 这个通知不需要一个MethodInvocation对象,因为它只在进入一个方法前被调用。

Before通知的主要优点是它不需要调用proceed() 方法, 因此没有无意中忘掉继续执行拦截器链的可能性。

MethodBeforeAdvice接口如下所示。 (Spring的API设计允许成员变量的before通知,虽然一般的对象都可以应用成员变量拦截,但Spring 有可能永远不会实现它)。

public interface MethodBeforeAdvice extends BeforeAdvice {

??? void before(Method m, Object[] args, Object target) throws Throwable;

}

注意返回类型是void。 Before通知可以在连接点执行之前 插入自定义的行为,但是不能改变返回值。如果一个before通知抛出一个异常,这将中断拦截器 链的进一步执行。这个异常将沿着拦截器链后退着向上传播。如果这个异常是unchecked的,或者 出现在被调用的方法的签名中,它将会被直接传递给客户代码;否则,它将被AOP代理包装到一个unchecked 的异常里。

下面是Spring中一个before通知的例子,这个例子计数所有正常返回的方法:

public class CountingBeforeAdvice implements MethodBeforeAdvice {

??? private int count;

??? public void before(Method m, Object[] args, Object target) throws Throwable {

??????? ++count;

??? }

??? public int getCount() {

??????? return count;

??? }

}

Before通知可以被用于任何类型的切入点。

1.2.3. Throws通知

如果连接点抛出异常,Throws通知 在连接点返回后被调用。Spring提供强类型的throws通知。注意这意味着 org.springframework.aop.ThrowsAdvice接口不包含任何方法: 它是一个标记接口,标识给定的对象实现了一个或多个强类型的throws通知方法。这些方法形式 如下:

afterThrowing([Method], [args], [target], subclassOfThrowable)

只有最后一个参数是必需的。这样从一个参数到四个参数,依赖于通知是否对方法和方法 的参数感兴趣。下面是throws通知的例子。

如果抛出RemoteException异常(包括子类), 这个通知会被调用

public class RemoteThrowsAdvice implements ThrowsAdvice {

??? public void afterThrowing(RemoteException ex) throws Throwable {

??????? // Do something with remote exception

??? }

}

如果抛出ServletException异常, 下面的通知会被调用。和上面的通知不一样,它声明了四个参数,所以它可以访问被调用的方法,方法的参数和目标对象:

public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

??? public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {

??????? // Do something will all arguments

??? }

}

最后一个例子演示了如何在一个类中使用两个方法来同时处理 RemoteException和ServletException 异常。任意个数的throws方法可以被组合在一个类中。

public static class CombinedThrowsAdvice implements ThrowsAdvice {

??? public void afterThrowing(RemoteException ex) throws Throwable {

??????? // Do something with remote exception

??? }

??? public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {

??????? // Do something will all arguments

??? }

}

Throws通知可被用于任何类型的切入点。

1.2.4. After Returning通知

Spring中的after returning通知必须实现 org.springframework.aop.AfterReturningAdvice 接口,如下所示:

public interface AfterReturningAdvice extends Advice {

??? void afterReturning(Object returnValue, Method m, Object[] args, Object target)

??????????? throws Throwable;

}

After returning通知可以访问返回值(不能改变)、被调用的方法、方法的参数和目标对象。

下面的after returning通知统计所有成功的没有抛出异常的方法调用:

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

??? private int count;

??? public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {

??????? ++count;

??? }

??? public int getCount() {

??????? return count;

??? }

}

这方法不改变执行路径。如果它抛出一个异常,这个异常而不是返回值将被沿着拦截器链向上抛出。

After returning通知可被用于任何类型的切入点。

1.2.5. Introduction通知

Spring将introduction通知看作一种特殊类型的拦截通知。

Introduction需要实现IntroductionAdvisor, 和IntroductionInterceptor接口:

public interface IntroductionInterceptor extends MethodInterceptor {

??? boolean implementsInterface(Class intf);

}

继承自AOP联盟MethodInterceptor接口的 invoke()方法必须实现导入:也就是说,如果被调用的方法是在 导入的接口中,导入拦截器负责处理这个方法调用,它不能调用proceed() 方法。

Introduction通知不能被用于任何切入点,因为它只能作用于类层次上,而不是方法。你可以只用InterceptionIntroductionAdvisor来实现导入通知,它有下面的方法:

public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

??? ClassFilter getClassFilter();

??? IntroductionInterceptor getIntroductionInterceptor();

??? Class[] getInterfaces();

}

这里没有MethodMatcher,因此也没有和导入通知关联的 切入点。只有类过滤是合乎逻辑的。

getInterfaces()方法返回advisor导入的接口。

让我们看看一个来自Spring测试套件中的简单例子。我们假设想要导入下面的接口到一个 或者多个对象中:

public interface Lockable {

??? void lock();

??? void unlock();

??? boolean locked();

}

这个例子演示了一个mixin。我们想要能够 将被通知对象类型转换为Lockable,不管它们的类型,并且调用lock和unlock方法。如果我们调用 lock()方法,我们希望所有setter方法抛出LockedException异常。这样我们能添加一个方面使的对象不可变,而它们不需要知道这一点:这是一个很好的AOP例 子。

首先,我们需要一个做大量转化的IntroductionInterceptor。 在这里,我们继承 org.springframework.aop.support.DelegatingIntroductionInterceptor 实用类。我们可以直接实现IntroductionInterceptor接口,但是大多数情况下 DelegatingIntroductionInterceptor是最合适的。

DelegatingIntroductionInterceptor的设计是将导入 委托到真正实现导入接口的接口,隐藏完成这些工作的拦截器。委托可以使用构造方法参数 设置到任何对象中;默认的委托就是自己(当无参数的构造方法被使用时)。这样在下面的例子里,委托是DelegatingIntroductionInterceptor的子类 LockMixin。给定一个委托(默认是自身)的 DelegatingIntroductionInterceptor实例寻找被这个委托(而不 是IntroductionInterceptor)实现的所有接口,并支持它们中任何一个导入。子类如 LockMixin也可能调用suppressInterflace(Class intf) 方法隐藏不应暴露的接口。然而,不管IntroductionInterceptor 准备支持多少接口,IntroductionAdvisor将控制哪个接口将被实际 暴露。一个导入的接口将隐藏目标的同一个接口的所有实现。

这样,LockMixin继承DelegatingIntroductionInterceptor 并自己实现Lockable。父类自动选择支持导入的Lockable,所以我们不需要指定它。用这种方法我们可以导入任意数量的接口。

注意locked实例变量的使用。这有效地添加额外的状态到目标 对象。

public class LockMixin extends DelegatingIntroductionInterceptor

??? implements Lockable {

??? private boolean locked;

??? public void lock() {

??????? this.locked = true;

??? }

??? public void unlock() {

??????? this.locked = false;

??? }

??? public boolean locked() {

??????? return this.locked;

??? }

??? public Object invoke(MethodInvocation invocation) throws Throwable {

??????? if (locked() && invocation.getMethod().getName().indexOf("set") == 0)

??????????? throw new LockedException();

??????? return super.invoke(invocation);

??? }

}

通常不要需要改写invoke()方法:实现 DelegatingIntroductionInterceptor就足够了,如果是导入的方法, DelegatingIntroductionInterceptor实现会调用委托方法, 否则继续沿着连接点处理。在现在的情况下,我们需要添加一个检查:在上锁状态下不能调用setter方法。

所需的导入advisor是很简单的。只有保存一个独立的 LockMixin实例,并指定导入的接口,在这里就是 Lockable。一个稍微复杂一点例子可能需要一个导入拦截器(可以 定义成prototype)的引用:在这种情况下,LockMixin没有相关配置,所以我们简单地 使用new来创建它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

??? public LockMixinAdvisor() {

??????? super(new LockMixin(), Lockable.class);

??? }

}

我们可以非常简单地使用这个advisor:它不需要任何配置。(但是,有一点 是必要的:就是不可能在没有IntroductionAdvisor 的情况下使用IntroductionInterceptor。) 和导入一样,通常 advisor必须是针对每个实例的,并且是有状态的。我们会有不同的的LockMixinAdvisor 每个被通知对象,会有不同的LockMixin。 advisor组成了被通知对象的状态的一部分。

和其他advisor一样,我们可以使用 Advised.addAdvisor() 方法以编程地方式使用这种advisor,或者在XML中配置(推荐这种方式)。 下面将讨论所有代理创建,包括“自动代理创建者”,选择代理创建以正确地处理导入和有状态的混入。

热点排行