AspectJ——定义通知

Posted KLeonard

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AspectJ——定义通知相关的知识,希望对你有一定的参考价值。

定义通知

切入点定义了你对哪些连接点感兴趣,通知则定义了当遇到这些连接点时要做什么。通过块包含直观的Java代码,它看起来非常像Java方法,只不过不能从应用程序中调用它。

0.前置通知

前面的大部分示例都使用了before()前置通知,它在触发它的连接点之前执行通知。关于它的用法这里不再赘述。这里只贴一个之前的切面例子:

package Test10;

public aspect WithincodeAspect 
    pointcut withinCodePointcut(): withincode(* Main.main(*));

    before(): withinCodePointcut()
        System.out.println();
        System.out.println(thisJoinPoint.getKind());
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    

1.环绕通知

around()环绕通知在触发它的连接点周围执行通知。它是一种强大的构造,它指示AspectJ应该运行通知,而不是指示连接点触发它。这允许它重写应用程序中的原始逻辑。

我们在Test12包下做测试,首先创建Service类,该类具有三个方法。如下:

package Test12;

public class Service 
    public int add(int a, int b) 
        return a + b;
    

    public double square(double a) 
        return a * a;
    

    public String upper(String string) 
        return string.toUpperCase();
    

三个方法分别是加法运算、平方运算以及将字符串转换成全大写的运算。

接着我们在主方法中测试它,Main类如下:

package Test12;

public class Main 
    public static void main(String[] args) 
        Service service = new Service();
        System.out.println("service.add(1, 2) = " + service.add(1, 2));
        System.out.println("service.square(6) = " + service.square(6));
        System.out.println("service.upper(\\"Gavin\\") = " + service.upper("Gavin"));
    

如果不添加任何切面,此时的运行结果如下:

结果正如我们所料,完全正确。

此时我们添加切面AroundAspect,如下:

package Test12;

public aspect AroundAspect 
    pointcut addPointcut(): call(* add(..));

    pointcut squarePointcut(double value): call(* square(double)) && args(value);

    pointcut upperPointcut(String value): call(* upper(String)) && args(value);

    int around():addPointcut()
        System.out.println(thisJoinPoint + " 执行之前...");
        int result = proceed();
        System.out.println("结果是:" + result);
        System.out.println(thisJoinPoint + " 执行之后...");
        return result;
    

    double around(double value): squarePointcut(value)
        System.out.println();
        System.out.println("接收到的参数是:" + value);
        return proceed(value * 2);
    

    String around(String value): upperPointcut(value)
        System.out.println();
        System.out.println("接收到的参数是:" + value);
        return value;
    

该切面分别针对Service类中的三个方法调用定义了三个切入点,并且使用around()为这三个切入点定义了环绕通知。

在通知体内,proceed()方法表示执行原来的方法(也就是被切入的方法)。对于addPointcut,我们直接调用了无参数的proceed()方法,它表示按照原来的参数执行原来的方法,结果是不变的。对于squarePointcut,我们接收了原来的参数,并且调用了proceed(value * 2),将原来的参数扩大两倍再传给原来的方法,很显然,结果被我们改变了。而对于upperPointcut,我们干脆就没有调用proceed()方法,直接返回了一个值给它。

执行结果如下:

对比之前的运行结果,我们发现现在的结果完全改变了。这充分证明了,around()环绕通知可以改变原来方法的运行逻辑。

需要注意的是:

  1. around()通知必须具有执行的返回值,如果不需要值,那么它可以是void的。
  2. 使用around()通知时,性能是需要考虑的事项。在AspectJ中使用around()通知会有性能损耗,有可能的话,优先考虑结合使用before()after()通知。

2.后置通知

后置通知包括三种:

  1. 普通的后置通知,其在连接点之后无条件执行通知。
  2. 成功返回的后置通知,其必须从连接点成功返回,才会在该连接点之后执行通知。
  3. 异常返回的后置通知,连接点出现异常,才会在该连接点之后执行通知。

2.0.普通的后置通知

普通的后置通知即after()通知,其无论连接点是成功返回还是异常返回,都会执行通知。它与before()相对应,一个在前,一个在后而已。关于它的使用,这里不再赘述。这里只贴一个之前的切面例子:

package Test6;

public aspect ConstructorAspect 

    pointcut staticInitializationPointcut(): staticinitialization(SuperService+);

    after(): staticInitializationPointcut()
        System.out.println();
        System.out.println("staticInitializationPointcut在这里...");
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    

2.1.成功返回的后置通知

成功返回的后置通知,仅在特定的连接点成功返回时,才会在该连接点之后执行通知。其使用after() returning,如果你想接收连接点的返回值,也可以使用after() returning(变量类型 变量名字)

比如下述切面,使用了成功返回的后置通知:

public aspect TestAspect
    pointcut afterPointcut(): call(* add(..));

    after() returning: afterPointcut()
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    

在前面的例子中,我们对属性的获取,即get切入点使用了后置通知,并在通知中使用了获取到的属性值。如下:

package Test7;

public aspect FieldAspect 
    pointcut getNamePointcut(): get(String Service.*name);

    after() returning(String value):getNamePointcut()
        System.out.println();
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
        System.out.println("访问的属性值是:" + value);
    

2.2.异常返回的后置通知

异常返回的后置通知仅当特定的连接点引发了一个异常时,才会在该连接点之后执行通知。其使用after() throwing,或者如果你想在通知中使用异常对象,也可以使用after() throwing(异常类型 变量名字)来接收异常对象。

下述切面使用了异常返回的后置通知:

public aspect TestAspect
    pointcut afterPointcut(): call(* add(..));

    after() throwing: afterPointcut()
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    

以上是关于AspectJ——定义通知的主要内容,如果未能解决你的问题,请参考以下文章

AspectJ——定义通知

AspectJ——定义通知

AspectJ注解

Spring AOP的切入点表达式

Spring4.2通过切点来选择连接点

AspectJ的注解开发AOP:切点定义