最近在看Spring AOP整合AspectJ的源码时,由于Pointcut的expression中的args配置不太熟悉,而args是除了execution外最常用的配置,因而搜索网上以及官方的文档,加上实例测试做一些总结。
在Spring的官方文档,对args的定义如下
args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
说明args中指定的是方法的入参类型, 而Spring AOP会匹配当前执行的方法的入参类型是否为args中给定的类型。
1.XML配置
根据Spring文档的指导,我们来看下XML中args的配置。
<aop:pointcut expression="execution(* com.lcifn.spring.aop.bean.*.*(..)) and args(String,java.util.Date,..)"/>
这个pointcut匹配的就是com.lcifn.spring.aop.bean包下任意类,且方法的第一个参数为String类型,第二个参数为java.util.Date类型。对于java.lang包下的类直接指定类名即可,而其他的则需要指定包名,而..表示方法可以不止两个参数。
这个pointcut能匹配的方法如下
public String seeMovie(String movie, Date date, String personName){}
如果需要在Advice的方法中引入当前执行方法的参数,我们就需要使用arg-names,它是配置于advice级别的属性。我们延续上面的例子,配置一个环绕增强,并且引入前两个参数。
public Object aroundIntercept(ProceedingJoinPoint pjp, String music, Date date){}
此时的xml配置依照猜测来看会这样配置
<aop:config proxy-target-class="true">
<aop:pointcut id="pointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(..)) and args(String,java.util.Date,..)"/>
<aop:aspect ref="advice">
<aop:around method="aroundIntercept" pointcut-ref="pointcut" arg-names="String,java.util.Date"/>
</aop:aspect>
</aop:config>
但是运行测试后提示arg-names中的java.util.Date非法,也就是不能使用点分隔符。来看正确的配置
<aop:config proxy-target-class="true">
<aop:pointcut id="pointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(..)) and args(str,date,..)"/>
<aop:aspect ref="advice">
<aop:around method="aroundIntercept" pointcut-ref="pointcut" arg-names="str,date"/>
</aop:aspect>
</aop:config>
arg-names中的变量必须同args中的一致,这是肯定的,那会有人问,args中原来配置的是参数的类型,现在没有了类型,还有用吗?其实此时的类型匹配已经转移到advice类中的方法的参数上了。可以看到上面的aroundIntercept方法中,后两个参数不是都有类型吗,Spring通过此种方式实现了advice在运行时接收原始方法的参数。很麻烦是不是,而且特别容易出错,我做测试的时候都要反反复复改几遍。XML不好用,我们来看看注解的方式。
2.注解
还是以环绕增强举例,通过@Aspect标注切面类
@Component
[@Aspect](https://my.oschina.net/aspect)
public class AspectJAnnotationBrowserAroundAdvice {
@Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(..))")
private void pointcut(){
}
@Around(value="pointcut()")
public Object aroundIntercept(ProceedingJoinPoint pjp) throws Throwable{
Object retVal = pjp.proceed();
return retVal;
}
}
注解方式的AOP一般都会使用component-scan方式,因而要在切面类上加上@Component注解。@Pointcut一般放在一个空方法上,其值为切入点表达式;@Around标识环绕增强,value值为切入点的方法名,或者直接写切入点表达式也可以。
我们需要使用args配置,从而在增强方法中调用方法运行时的参数值,来看下其配置
@Component
@Aspect
public class AspectJAnnotationArgsBrowserAroundAdvice {
@Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(..)) && args(music,date,..)")
private void pointcut(String music, Date date){
}
@Around(value="pointcut(music,date)")
public Object aroundIntercept(ProceedingJoinPoint pjp, String music, Date date) throws Throwable{
Object retVal = pjp.proceed();
return retVal;
}
}
args中使用昵称方式,且必须同切入点方法pointcut中的参数名称一致(为什么能匹配到参数名称将在下一章重点介绍)。目标方法参数的类型则由pointcut的参数类型及顺序来决定,这同xml配置中一致。
而增强方法引用切入点方法时,则必须将切入点方法中的参数带上,如上述代码中的@Around(value="pointcut(music,date)")
。对于增强方法中引用目标对象中方法参数时,参数名称则可以随意定义,不需要同切入点方法参数一致。
如果你很任性,在@Around中就不想用之前的参数名称,怎么办?使用@Around中的argNames属性,它同xml配置中的arg-names相对应。
@Around(value="pointcut(music,datetime)",argNames="music,datetime")
public Object aroundIntercept(ProceedingJoinPoint pjp, String musi, Date date) throws Throwable{
Object retVal = pjp.proceed();
return retVal;
}
通过在argNames中定义参数名的昵称,从而在value中引用的pointcut方法即可使用昵称。但是我任务此举也绝对是任性的人才会玩的,比如我就是个任性的人,不然怎么会测试这种配置:-D。
3.args和execution定义方法入参类型的区别
对于args中定义方法入参类型的方式,我之前对它和直接在execution表达式中定义方法入参类型,这两种方式,感到很疑惑。
来看下execution表达式定义方法入参类型的方式
<aop:pointcut id="pointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(String,java.util.Date,..))"/>
对应的增强方法则是
public Object aroundIntercept(ProceedingJoinPoint pjp){}
经过我的反复测试,execution定义方法入参类型同args的方式有以下的区别:
- execution定义方法入参类型只能根据入参类型匹配方法,而不能获取参数值。因而必须使用参数类型,如String,java.util.Date,不能使用args方式的昵称方式,且增强方法中不能引入方法参数。而args则是动态切入的,在每次方法执行时匹配一次,并获取参数值可供增强方法调用。
- execution定义的方法入参类型是完全匹配,即定义的是java.util.Date,只能完全匹配,其子类java.sql.Date也是匹配失败。如果同时匹配子类,可使用+后缀符,即(String, java.util.Date+,..)可以匹配到java.sql.Date。而args的方式默认就是匹配自身及子类,即args(String, java.util.Date,..)是可以匹配到java.sql.Date的。
execution定义方法入参类型的注解方式则是沿用xml的规则
@Component
@Aspect
public class AspectJAnnotationArgsBrowserAroundAdvice {
@Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(String,java.util.Date+,..))")
private void pointcut(){
}
@Around(value="pointcut()")
public Object aroundIntercept(ProceedingJoinPoint pjp) throws Throwable{
Object retVal = pjp.proceed();
return retVal;
}
}
在切入点方法pointcut中也是不允许定义参数,而仅仅通过execution表达式中定义的方法入参类型进行匹配。
4.总结
对于args和arg-names,除非有比较特殊的需求,不然基本也不会使用。作为任性的我,看到网上一波波介绍Spring expression表达式时都会copy这两个的用法,就在看源码的同时特意对它们进行了测试,因此留下这篇给自己和他人以后万一使用时做个参考。
然而在我测试的过程,另一个问题引起了我的关注(上文中有提到),在@Pointcut中定义args(music,date)时,pointcut方法里的两个参数的名称也必须同其一致,不然则抛错。这引起我的很大兴趣,因为在之前也看过一些博文,对于java中的方法参数名正常的方式是获取不到的,那此处是怎么获取到方法参数名的呢,请见下一章。