Advice
除了可以接收 JoinPoint
(非Around Advice)或 ProceedingJoinPoint
(Around Advice)参数外
还可以直接接收与切入点方法执行有关的对象
比如
- 切入点方法参数
- 切入点目标对象(
target
)- 切入点***对象(
this
)- 等。
文章目录
# 获取切入点方法参数
## 指定 [类 - 方法 - 参数]
假设我们现在有一个id为 userService
的 bean
中定义了一个 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>是 id
或 name
为 userService的findById(Integer)
的 <mark>方法</mark>
后面又加了一个 args(id)
是干什么用的呢?
-
它的作用跟
findById(Integer)
是类似的
它表示我们的<mark>切入点方法必须只接收一个参数</mark>,而且这个参数的类型是和当前定义的Advice
处理方法的参数id
是相同类型的,在上面的示例中其实就是要求是Integer
类型的; -
另外它还有一个非常重要的作用,通过这种指定后对应的
Advice
处理<mark>方法在执行时将接收到与之对应的切入点方法参数的值</mark>。
在上面的示例中笔者特意给
Advice
处理方法加了一个JoinPoint
参数是为了说明JoinPoint
、ProceedingJoinPoint
参数是可以直接定义在Advice
方法的<mark>第一个参数</mark>,并且是可以<mark>与其它接收的参数共存</mark>的。
## 指定 [类 - 参数]
其实如果我们不只是需要拦截 findById(Integer)
方法,而是需要拦截 id
为 userService
的 bean
中<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参数
我们可以看到在上述例子中我们都指定了 @Before
的 argNames
属性的值为 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
表达式中使用了name
和sex
两个参数
我们的Advice
处理方法接收两个参数,分别是sex1
和name1
- 我们希望
Pointcut
表达式中的name
参数是对应的Advice
处理方法的第二个参数,即name1
希望Pointcut
表达式中的sex
参数是对应的Advice
处理方法的第一个参数,即sex1
那么
- 我们在指定
@Before
注解的argNames
参数时必须定义name
和sex
参数与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());
}
- 否则,一般
Object target = ProceedingJoinPoint.getTarget();
获取target
target
与this
不一定相同 (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
参数绑定的就只有this
、target
、args
以及对应的注解形式加@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日星期五)