`
jeff2008
  • 浏览: 25997 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Spring interceptor -- 古老的传说

    博客分类:
  • Java
阅读更多

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中配置(推荐这种方式)。 下面将讨论所有代理创建,包括“自动代理创建者”,选择代理创建以正确地处理导入和有状态的混入。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics