aop——advice

  本篇介绍Advice, 原著包含六个小节。

  第一小节介绍Advice的基本概念。

  第二小节比较Advice与普通的方法。

  第三小节介绍Advice的三种类型。

  第四,第五小节介绍收集上下文的方式。其中第四小节使用关键字与non-kinded注解的方式。第五小节通过reflection API的方式。

  第六小节总结本章,略。

  核心知识点是Advice与收集上下文。

1、 advice

1.1   基本概念

Advice的组成元素有三个部分。

  1. 类型:冒号之前的部分,用于指定Advice的类型,即before,after,around。
  2. pointcut:是Advice的核心组成部分,用于筛选业务模块。
  3. body:Advice的方法体,包含一些可执行的代码。

例如第二章节的示例

before() : secureAccess(){
	System.out.println("Checking and authenticating user");
	authenticator.authenticate();
}

  其中

  • before()为Advice声明,
  • secureAccess为Pointcut
  • 大括号内部的代码为Advice body。

  advice的类型有三种。

  1. Before: 在业务模块之前执行,
  2. After: 在业务模块之后执行,根据业务方法的执行情形分为以下三种情况:
    • After (finally):无论业务方法执行结果如何,始终执行。它是默认值
    • After returning(type):当业务方法正常返回时,执行,其中returning为关键字,type表示返回值的类型
    • After throwing(type):当业务方法抛出异常时,执行,其中throwing为关键字,type表示异常的类型。

  3. Around:业务模块的方法被包裹在Advice的方法体中。例如Spring的事务处理

  pointcut在第三章中已介绍。

  body类似于普通的Java方法。

  Advice与Java方法基本相同,区别在于

  • Advice的名称是可选的
  • 不能被程序调用,而是由系统自动调用
  • 没有修饰符
  • Before,After类型的Advice没有返回值
  • 除this之外,有其他的上下文关键字,thisJoinPoint,thisJoinPointStaticPart,thisEnclosingJoinPointStaticPart
  • proceed()代表业务方法。

关键点是核心关键字和方法,例如thisJoinPoint,proceed()。

1.2    使用场景

1.2.1    before

在业务方法之前执行,例如第二章节中的用户校验功能。它的格式为:before()  pointcut: {// Advice方法体}

由于在业务方法之前执行,所以before Advice抛出异常之后,业务方法不会执行。它收集的上下文也有限,例如没有返回值,参数信息等等。

1.2.2   after (finally)

  在业务方法之后执行,若After Advice的类型为finally时,finally可以省略。无论业务方法的何种情形,它都会执行,相当于try,catch,finally中的finally代码块。它通常可以用来计算方法的运行时间。

1.2.3   after returning

  在业务方法正常运行结束之后执行,若方法运行时抛出异常,它不会执行。在使用时returning关键字不可省略,若想收集方法的返回值,可以使用after returning(type)的格式,其中type为返回值类型,当是基本数据类型,会自动返回其包装类。

1.2.4   after throwing

  在业务方法抛出异常之后执行,若方法正常结束,不会执行。在使用时,throwing关键字不可省略,若想收集抛出异常的信息,可以使用after throwing(type)的格式,其中type为异常类型,它是Throwable的子类。

1.2.5   around advice

业务方法的代码被包裹在Advice方法体之内,它是最常使用的一种Advice,主要应用场景有

Perform additional logic before and after the advised join point(for example,profiling).

等价于代理模式,在之前或之后插入公共代码,例如日志,安全校验等。

Bypass the original operation and perform some alternative logic(for example,caching).

等业务结束之后,额外添加一些功能,例如缓存。

Surround the operation with a try/catch block to perform an exception handling policy.

捕获特定的异常。

在我们日常的开发场景中会经常遇到,例如Spring的事务。显示方法的运行时间等。

2、收集上下文

上下文的信息虽然会随着不同join point而不同,但是无外乎两种类型

  1. Join point中的对象。对象的信息又由两个部分组成,对象的类型(type of object),对象的实例(ObjectIdentifier)。
  2. Join point中的注解。注解的信息又由三个部分组成,注解的类型,注解的参数,注解上的注解,例如@Retention,@Target等。

注:注解的@Retenntion的value值必须是RetentionPolicy.RUNTIME,即在运行时注解还存在。

收集方式有两种,non-kinded Pointcut,reflection API方式。

2.1   non-kinded pointcut

2.1.1   概念

当收集上下文的对象信息,使用

  1. this(obj):
    • 收集this关键字指向的对象,当this关键字不存在时,返回null。例如在静态代码块,静态方法中,它返回null。

   2. target(obj):

    • 当为method join point时,若是方法调用(call),target收集方法调用者,例如user.setName(),此时target为user对象。若是方法执行(execution),它收集的是this关键字,与this(obj)相同。
    • 当为se join point时,它收集的对象的实例,与this(obj)收集的上下文信息相同

   3.args(obj):

    • 当为method join point时,它收集方法的参数。
    • 当为constructor join point时,它收集构造器的参数
    • 当为exception join point,异常代码块时,它收集catch()的参数
    • 当为 get & set join point时,若为get,收集字段的值,若为set,收集字段的新值。

  当收集上下文的注解信息时,使用

  1. @this(obj):首先返回this(obj)收集的对象,@this(obj)收集该对象类型上的注解
  2. @Target(obj):首先返回target(obj)收集的对象,@Target(obj)收集该对象类型上的注解
  3. @args(obj):首先返回args(obj)收集的参数,@args收集该参数上的注解。
  4. @within():the type enclosing the matching join point上的注解
  5. @withincode():the method enclosing the matching join point上的注解。
  6. @annotation():
    • 当为method join point时,收集方法上的注解
    • 当为constructor join point时,收集构造器上的注解
    • 当为get & set时,收集字段上的注解
    • 当为异常时,收集异常参数的注解。

  2.1.2   示例

步骤如下:

  第一步:使用non-kinded point,将收集的参数传递给Pointcut,例如

pointcut testThis(User user) : execution(@Test public * ch4.User.test(..)) && this(user);

  第二步:在advice中使用pointcut时,获取pointcut中的参数。

void around(User user) : testThis(user){
   System.out.println(user.toString());
}

  参数传递的过程为non kinded pointcut --- > pointcut -----> advice。上述示例中为This(user) ----> pointcut (User user ) -------> around(User user)

2.2      reflection API

2.2.1   类结构

  

   JoinPoint:动态信息,图中看出,四个部分,getThis收集方法的this关键字,getTarget收集方法的调用者,getArgs()收集方法的参数,getStaticPart返回JoinPoint.StaticPart对象。

  JoinPoint.StaticPart:静态信息,三个部分,getKind返回Join point的类型。getSignature返回签名,参考Pointcut表达式中的签名。getSourceLocation返回资源的信息,行号等等,通常不会用到。

  Signature:它对应JoinPoint的类型,CatchClauseSignature对应异常,FieldSignature对应的是字段,MethodSignature对应方法,ConsturetorSignature对应构造器,InitializerSignature对应的是初始化和pre-initialize。AdviceSignature对应的是Advice。

  2.2.2  示例

在Advice的方法中添加JoinPoint参数即可。

void around(JoinPoint jp){
	// 获取调用者
	Object caller = jp.getTarget();
	// 获取this关键字
	Object thisObj = jp.getThis();
	// 获取参数
	Object[] params = jp.getArgs();
	// 获取签名
	Signature sign = jp.getSignature();
	// 如果是代码类型的签名,非字段签名
	if(sign instanceof CodeSignature) {
		CodeSignature codeSign = (CodeSignature)sign;
		// 获取参数的名称
		String[] paramNames = codeSign.getParameterNames();
		// 打印参数的名称和值
		for(int i=0; i <params.length; i++) {
			System.out.println(paramNames[i] + " : " + params[i]);
		}
	}
}

  至此,本篇内容结束。

posted @ 2021-04-27 14:06  蜗牛旅行1899  阅读(222)  评论(0编辑  收藏  举报