Advice 除了可以接收 JoinPoint (非Around Advice)或 ProceedingJoinPoint (Around Advice)参数外

还可以直接接收与切入点方法执行有关的对象

比如

  • 切入点方法参数
  • 切入点目标对象( target
  • 切入点***对象( this
  • 等。

# 获取切入点方法参数

## 指定 [类 - 方法 - 参数]

假设我们现在有一个id为 userServicebean 中定义了一个 findById(int id) 方法

我们希望定义一个 Advice 来拦截这个方法,并且把 findById() 的参数作为 Advice 处理方法的参数

<mark>即每次调用 findById() 传递的参数都将传递到 Advice 处理方法</mark>

那么我们可以如下这样定义。

@Before(value="bean(userService) && execution(* findById(java.lang.Integer)) && args(id)", argNames="id")
public void beforeWithParam( JoinPoint joinPoint , Integer id ) {

	System.out.println(this.getClass().getName()+" ID is : " + id);
	
}

上面这种定义是非常精确的定义

我们通过表达式“ bean(userService) && execution(* findById(java.lang.Integer)) ”就已经<mark>明确的指定了我们需要拦截的</mark>是 idnameuserService的findById(Integer) 的 <mark>方法</mark>

后面又加了一个 args(id) 是干什么用的呢?

  • 它的作用跟 findById(Integer) 是类似的
    它表示我们的<mark>切入点方法必须只接收一个参数</mark>,而且这个参数的类型是和当前定义的 Advice 处理方法的参数 id 是相同类型的,在上面的示例中其实就是要求是 Integer 类型的;

  • 另外它还有一个非常重要的作用,通过这种指定后对应的 Advice 处理<mark>方法在执行时将接收到与之对应的切入点方法参数的值</mark>。

在上面的示例中笔者特意给 Advice 处理方法加了一个 JoinPoint 参数是为了说明 JoinPointProceedingJoinPoint 参数是可以直接定义在 Advice 方法的<mark>第一个参数</mark>,并且是可以<mark>与其它接收的参数共存</mark>的。

## 指定 [类 - 参数]

其实如果我们不只是需要拦截 findById(Integer) 方法,而是需要拦截 iduserServicebean 中<mark>所有</mark>接收一个 int/Integer 参数的方法,那么我们可以把上面的配置简化为如下这样。

@Before(value="bean(userService) && args(id)", argNames="id")
public void beforeWithParam2(int id) {

	System.out.println(this.getClass().getName()+" ID is : " + id);
	
}

## 指定 [类 - (部分) 参数]

如果我们需要拦截的方法可能是有多个参数的,但我们<mark>只关注第一个参数</mark>,那我们可以把表达式调整为如下这样

只关注第一个参数为 int/Integer 类型的,并且在 Advice 方法中接收这个方法参数进行相应的处理。

@Before(value="bean(userService) && args(id,..)", argNames="id")
public void beforeWithParam2(int id) {
	System.out.println(this.getClass().getName()+" ID is : " + id);
}

## argNames参数

我们可以看到在上述例子中我们都指定了 @BeforeargNames 属性的值为 id ,那么这个 argNames 属性有什么作用呢?

  • argNames 属性是用于<mark>指定在表达式中 应用的参数名 与 Advice 方法参数是如何对应的</mark>
  • <mark>argNames 中指定的参数名必须与表达式中的一致,可以与 Advice 方法参数名不一致</mark>;
  • 当表达式中使用了多个参数时, argNames 中需要指定多个参数,<mark>多个参数之间以英文逗号分隔</mark>
  • 这些参数的顺序必须与对应的 Advice 方法定义的 <mark>参数 顺序是一致的</mark>。

比如下面这个示例中


@Before(value="bean(userService) && args(name, sex)", argNames="sex, name")

public void beforeWithParam3(int sex1, String name1) {
	System.out.println("sex is : " + sex1);
	System.out.println("name is : " + name1);
}
  • 我们在 Pointcut 表达式中使用了 namesex 两个参数
    我们的 Advice 处理方法接收两个参数,分别是 sex1name1
  • 我们希望 Pointcut 表达式中的 name 参数是对应的 Advice 处理方法的第二个参数,即 name1
    希望 Pointcut 表达式中的 sex 参数是对应的 Advice 处理方法的第一个参数,即 sex1

那么

  • 我们在指定 @Before 注解的 argNames 参数时必须定义 namesex 参数与 Advice 处理方法参数的关系
  • 且<mark>顺序要求与对应的处理方法的参数顺序一致</mark>
    即哪个参数是需要与 Advice 处理方法的第一个参数匹配则把哪个参数放第一位
    与第二个参数匹配的则放第二位
    (在我们的这个示例中就应该是 sex 放第一位, name 放第二位。)

如果表达式里面使用了多个参数,那么这些参数在表达式中的顺序可以与 Advice 方法对应参数的顺序不一致,

## argNames 可以省略

@Before 注解的 argNames 参数不是必须的,它只有在我们编译的字节码中不含 DEBUG 信息或 Pointcut 表达式中使用的参数名与 Advice 处理方法的参数名不一致时才需要。

所以在编译的字节码中包含 DEBUG 信息且 Advice 参数名与 Pointcut 表达式中使用的参数名一致时,我们完全可以把 argNames 参数省略。

@Before(value="bean(userService) && args(id)")
public void beforeWithParam2(int id) {
	System.out.println(this.getClass().getName()+" ID is : " + id);
}


# 获取this对象

this 对象就是 Spring 生成的 bean 的那个***对象。

如下示例就是 Advice 方法接收 this 对象

我们给 Advice 方法指定一个<mark>需要拦截的 this 对象类型</mark>的参数
<mark>然后在表达式中使用 this 类型的表达式定义</mark>

表达式中定义的对应类型指定为 Advice 方法参数。

@Before("this(userService)")
public void beforeWithParam4(IUserService userService) {
	//this对象应该是一个***对象
	System.out.println(this.getClass().getName()+"==============传递this对象: " + userService.getClass());
}

# 混合使用

我们的 Advice 方法可以同时接收多个目标方法参数,与此同时它也可以接收 this 等对象,即它们是可以混合使用的。

下面这个示例中我们就同时接收了 this 对象和目标方法 int/Interger 类型的参数。

@Before("this(userService) && args(id)")
public void beforeWithParam5(IUserService userService, int id) {
	System.out.println(this.getClass().getName()+"===========" + id + "==============" + userService.getClass());
}

# 获取target对象

获取 target 对象也比较简单,只需要把表达式改为 target 类型的表达式即可。

@Before("target(userService)")
public void beforeWithParam6(IUserService userService) {
	System.out.println(this.getClass().getName()+"==============传递target对象: " + userService.getClass());
}
  1. 否则,一般 Object target = ProceedingJoinPoint.getTarget(); 获取 target
  2. targetthis 不一定相同 (JDK实现 - 相同;CGLIB实现 - 不同)
    《AOP 原理 - - 继承对象(JDK) / 组合对象(CGLIB) 、 AspectJ-ProceedingJoinPoint-getSignature 细节和这细节可能的bug》

# 获取注解对象

当我们的 Pointcut 表达式类型是通过注解匹配时,我们也可以在 Advice 处理方法中获取匹配的注解对象

如下面这个示例,其它如使用 @target 等是类似的。

@Before("@annotation(annotation)")
public void beforeWithParam7(MyAnnotation annotation) {
	System.out.println(this.getClass().getName()+"==============传递标注在方法上的annotation: " + annotation.annotationType().getName());
}

  • @Pointcut("@annotation(com.edut.springboot.tarena.common.annotation.RequiredCache)") 这样是识别相应 annotation 的切点

# 泛型参数

有的时候我们的 Advice 方法需要接收的切入点<mark>方法参数</mark>定义的<mark>不是具体的类型,而是一个泛型</mark>,这种情况下怎么办呢?

可能你会想那我就把对应的 Advice 方法参数定义为 Object 类型就好了,反正所有的类型都可以转换为 Object 类型。
对的,这样是没有错的,<mark>但是</mark>说如果你只想拦截某种具体类型的参数调用时就可以不用把 Advice 方法参数类型定义为 Object 了,这样还得在方法体里面进行判断

我们可以<mark>直接</mark>把 Advice 方法参数类型定义为我们想拦截的方法参数类型。
比如我们有下面这样一个使用了泛型的方法定义,我们希望只有在调用 testParam 方法时传递的参数类型是 Integer 类型时才进行拦截。

	public <T> void testParam(T param);
  • 那这个时候我们就可以把我们的 Advice 的表达式定义为如下这样,前者精确定义接收方法名为 testParam ,返回类型为 void ,后者精确定义方法参数为一个 Integer 类型的参数
    其实前者也可以定义为“ execution(void testParam(Integer)) ”。

  • 看到这你可能会想
    为什么不直接把表达式定义为“ execution(void testParam(param)) ”呢?
    因为 execution 是<mark>不支持</mark> Advice <mark>方法参数绑定</mark>的,基本上支持 Advice 参数绑定的就只有 thistargetargs 以及对应的注解形式加 @annotation

@Before("execution(void testParam(..)) && args(param)")
public void beforeWithParam8(Integer param) {
	System.out.println("pointcut expression[args(param)]--------------param:" + param);
}

以上就是常用的传递参数给Advice处理方法的方式

有一些示例可能没有讲到,比如@target这种,这些其实都是类似的。
包括上面我们都是以@Before这种Advice来讲的,其实其它的Advice在接收参数的时候也是类似的。


参考文档
1、官方文档

(注:本文是基于Spring4.1.0所写,写于2017年1月20日星期五)