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
|
下面的切面类中定义了Before
、 Around
、 AfterReturning
、Afer
四种增强处理,并分别在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("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增强:执行目标方法之后,模拟结束事务..."); if (rvt != null && rvt instanceof Integer) rvt = (Integer) rvt * (Integer) rvt; return rvt; } @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( 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("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()); } }
|
从上面的代码可以看出,在Before
、 Around
、 AfterReturning
、After
四种增强处理中,其实都可通过相同的代码来访问
被增强的目标对象、目标方法和方法的参数,
但只有Around
增强处理可以改变方法参数.
被上面切面类处理的目标类还是前面的Hellolmpl
、 WorldImpl
类,主程序获取它们的实例,并执行它们的方法,执行结束将看到如下所示的执行效果:
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
| 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
提供了如下两种解决方案。
- 让切面类实现
org.springframework.core.Ordered
接口,实现该接口只需实现一个int getOrder()
方法,该方法的返回值越小,则优先级越高
。
- 直接使用
@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 { @AfterReturning( returning = "rvt", pointcut = "execution(* org.crazyit.app.service.impl.*.*(..)) && args(arg0,arg1)") 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
方法)中定义ag0
、arg1
两个形参,这两个形参的类型可以随意指定,但一旦指定了这两个形参的类型,这样两个形参的类型会用于限制目标方法。
例如access
方法声明arg0
、arg1
的类型都是String
,这会限制目标方法必须带两个String
类型的参数。
本示例的主程序还是先通过Sprin
容器获取HelloImpl
和Worldlmpl
两个组件,然后调用这两个组件的方法。编译、运行该程序,将看到如下所示的效果。
1 2 3 4 5 6 7
| 执行Hello组件的addUser添加用户:孙悟空 调用目标方法第1个参数为:孙悟空 调用目标方法第2个参数为:7788 获取目标方法返回值:20 模拟记录日志功能... 执行Hello组件的deleteUser删除用户:1 执行World组件的bar()方法
|
从上述运行结果可以看出,使用args
表达式有如下两个作用。
- 提供了一种简单的方式来访问目标方法的参数
- 对切入表达式增加额外的限制。
除此之外,使用args
表达式时还可使用如下形式:args(name,age,..)
,这表明在增强处理方法中可通过name
、age
来访问目标方法的参数。注意上面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
类型,则该方法就可匹配该切入点。
原文链接: 8.4.5 基于注解的 零配置 方式 7. 访问目标方法的参数