8.4.5 基于注解的 零配置 方式 7. 访问目标方法的参数

8.4.5 基于注解的 零配置 方式 7. 访问目标方法的参数

访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。

JoinPoint类的方法

JoinPoint里包含了如下几个常用的方法。

方法 描述
Object[] getArgs() 返回执行目标方法时的参数。
Signature getSignature() 返回被增强的方法的相关信息
Object getTarget() 返回被织入增强处理的目标对象
Object getThis() 返回AOP框架为目标对象生成的代理对象。

通过使用这些方法就可访问到目标方法的相关信息。

当使用Around增加处理时,需要将第一个参数定义为ProceedingJoinPoint类型,该类型是JoinPoint类型的子类。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\JoinPoint
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─FourAdviceTest.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面的切面类中定义了BeforeAroundAfterReturningAfer四种增强处理,并分别在4种增强处理中访问被织入增强处理的目标方法执行目标方法的参数访问被织入增强处理的目标对象等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.*;
import org.aspectj.lang.*;
import java.util.Arrays;

// 定义一个切面
@Aspect
public class FourAdviceTest
{
// 定义Around增强处理
@Around("execution(* org.crazyit.app.service.impl.*.*(..))")
public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable
{
System.out.println("Around增强:执行目标方法之前,模拟开始事务...");
// 访问执行目标方法的参数
Object[] args = jp.getArgs();
// 当执行目标方法的参数存在,
// 且第一个参数是字符串参数
if (args != null && args.length > 0
&& args[0].getClass() == String.class)
{
// 修改目标方法调用参数的第一个参数
args[0] = "【增加的前缀】" + args[0];
}
// 执行目标方法,并保存目标方法执行后的返回值
Object rvt = jp.proceed(args);
System.out.println("Around增强:执行目标方法之后,模拟结束事务...");
// 如果rvt的类型是Integer,将rvt改为它的平方
if (rvt != null && rvt instanceof Integer)
rvt = (Integer) rvt * (Integer) rvt;
return rvt;
}
// 定义Before增强处理
@Before("execution(* org.crazyit.app.service.impl.*.*(..))")
public void authority(JoinPoint jp)
{
System.out.println("Before增强:模拟执行权限检查");
// 返回被织入增强处理的目标方法
System.out.println(
"Before增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
// 访问执行目标方法的参数
System.out
.println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("Before增强:被织入增强处理的目标对象为:" + jp.getTarget());
}
// 定义AfterReturning增强处理
@AfterReturning(
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..))",
returning = "rvt"
)
public void log(JoinPoint jp, Object rvt)
{
System.out.println("AfterReturning增强:获取目标方法返回值:" + rvt);
System.out.println("AfterReturning增强:模拟记录日志功能...");
// 返回被织入增强处理的目标方法
System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println(
"AfterReturning增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("AfterReturning增强:被织入增强处理的目标对象为:" + jp.getTarget());
}

// 定义After增强处理
@After("execution(* org.crazyit.app.service.impl.*.*(..))")
public void release(JoinPoint jp)
{
System.out.println("After增强:模拟方法结束后的释放资源...");
// 返回被织入增强处理的目标方法
System.out.println(
"After增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("After增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("After增强:被织入增强处理的目标对象为:" + jp.getTarget());
}
}

从上面的代码可以看出,在BeforeAroundAfterReturningAfter四种增强处理中,其实都可通过相同的代码来访问被增强的目标对象、目标方法和方法的参数,
但只有Around增强处理可以改变方法参数.

被上面切面类处理的目标类还是前面的HellolmplWorldImpl类,主程序获取它们的实例,并执行它们的方法,执行结束将看到如下所示的执行效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Around增强:执行目标方法之前,模拟开始事务...
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:addUser
Before增强:目标方法的参数为:[【增加的前缀】孙悟空, 7788]
Before增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
执行Hello组件的addUser添加用户:【增加的前缀】孙悟空
Around增强:执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的释放资源...
After增强:被织入增强处理的目标方法为:addUser
After增强:目标方法的参数为:[【增加的前缀】孙悟空, 7788]
After增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
AfterReturning增强:获取目标方法返回值:400
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:addUser
AfterReturning增强:目标方法的参数为:[【增加的前缀】孙悟空, 7788]
AfterReturning增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
Around增强:执行目标方法之前,模拟开始事务...
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:deleteUser
Before增强:目标方法的参数为:[1]
Before增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
执行Hello组件的deleteUser删除用户:1
Around增强:执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的释放资源...
After增强:被织入增强处理的目标方法为:deleteUser
After增强:目标方法的参数为:[1]
After增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
AfterReturning增强:获取目标方法返回值:null
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:deleteUser
AfterReturning增强:目标方法的参数为:[1]
AfterReturning增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.HelloImpl@128d2484
Around增强:执行目标方法之前,模拟开始事务...
Before增强:模拟执行权限检查
Before增强:被织入增强处理的目标方法为:bar
Before增强:目标方法的参数为:[]
Before增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.WorldImpl@7cc0cdad
执行World组件的bar()方法
Around增强:执行目标方法之后,模拟结束事务...
After增强:模拟方法结束后的释放资源...
After增强:被织入增强处理的目标方法为:bar
After增强:目标方法的参数为:[]
After增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.WorldImpl@7cc0cdad
AfterReturning增强:获取目标方法返回值:null
AfterReturning增强:模拟记录日志功能...
AfterReturning增强:被织入增强处理的目标方法为:bar
AfterReturning增强:目标方法的参数为:[]
AfterReturning增强:被织入增强处理的目标对象为:org.crazyit.app.service.impl.WorldImpl@7cc0cdad

织入增强处理的顺序

Spring AOP采用和AspectJ一样的优先顺序来织入增强处理:

  • 在”进入“连接点时,具有最高优先级的增强处理将先被织入(所以在给定的两个Before增强处理中,优先级高的那个会先执行)。
  • 在”退出“连接点时,具有最高优先级的增强处理会最后被织入(所以在给定的两个After增强处理中,优先级高的那个会后执行).

##不同切面类的增强处理 默认以随机顺序 织入同一个连接点 ##
当不同切面里的两个增强处理需要在同一个连接点被织入时, Spring AOP将以随机的顺序来织入这两个增强处理。

通过优先级为 不同切面类的增强处理 设置织入顺序

如果应用需要指定不同切面类里增强处理的优先级, Spring提供了如下两种解决方案。

  1. 让切面类实现org.springframework.core.Ordered接口,实现该接口只需实现一个int getOrder()方法,该方法的返回值越小,则优先级越高
  2. 直接使用@Order注解来修饰一个切面类,使用@Order注解时可指定一个int型的value属性,该属性值越小,则优先级越高。

同一个切面类例 同类型的 增强处理 默认以随机顺序织入

同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时, Spring AOP将以随机的顺序来织入这两个增强处理,程序没有办法控制它们的织入顺序。

如果确实需要保证它们以固有的顺序被织入,则可考虑将多个增强处理压缩成一个增强处理;或者将不同的增强处理重构到不同的切面类中,通过在切面类级别上进行排序。

args切入点表达式

如果只需要访问目标方法的参数, Spring还提供了一种更简单的方法:可以在程序中使用args切入点表达式来绑定目标方法的参数。如果在一个args表达式中指定了一个或多个参数,则该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Args
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
├─aspect\
│ └─AccessArgAspect.java
└─service\
├─Hello.java
├─impl\
│ ├─HelloImpl.java
│ └─WorldImpl.java
└─World.java

下面定义一个切面类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.crazyit.app.aspect;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AccessArgAspect
{
// 下面的args(arg0,arg1)会限制目标方法必须有2个形参
@AfterReturning(
returning = "rvt",
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..)) && args(arg0,arg1)")
// 此处指定arg0、arg1为String类型
// 则args(arg0,arg1)还要求目标方法的两个形参都是String类型
public void access(Object rvt, String arg0, String arg1)
{
System.out.println("调用目标方法第1个参数为:" + arg0);
System.out.println("调用目标方法第2个参数为:" + arg1);
System.out.println("获取目标方法返回值:" + rvt);
System.out.println("模拟记录日志功能...");
}
}

上面程序中的pointcut属性定义了切入点表达式,但该切入点表达式增加了&&args(arg0,arg1)部分,这意味着可以在增强处理方法(access方法)中定义ag0arg1两个形参,这两个形参的类型可以随意指定,但一旦指定了这两个形参的类型,这样两个形参的类型会用于限制目标方法。
例如access方法声明arg0arg1的类型都是String,这会限制目标方法必须带两个String类型的参数
本示例的主程序还是先通过Sprin容器获取HelloImplWorldlmpl两个组件,然后调用这两个组件的方法。编译、运行该程序,将看到如下所示的效果。

1
2
3
4
5
6
7
执行Hello组件的addUser添加用户:孙悟空
调用目标方法第1个参数为:孙悟空
调用目标方法第2个参数为:7788
获取目标方法返回值:20
模拟记录日志功能...
执行Hello组件的deleteUser删除用户:1
执行World组件的bar()方法

从上述运行结果可以看出,使用args表达式有如下两个作用。

  1. 提供了一种简单的方式来访问目标方法的参数
  2. 对切入表达式增加额外的限制。

除此之外,使用args表达式时还可使用如下形式:args(name,age,..),这表明在增强处理方法中可通过nameage来访问目标方法的参数。注意上面arg表达式括号中的两个点,它表示可匹配更多参数——如果该args表达式对应的增强处理方法签名为:

1
2
3
4
5
6
7
@AfterReturning(
returning = "rvt",
pointcut = "execution(* org.crazyit.app.service.impl.*.*(..)) && args(name,age,..)")
public void doSomething(String name,int age,Date birth)
{

}

这意味着只要目标方法的第一个参数是String类型,第二个参数是int类型,则该方法就可匹配该切入点。