Spring AOP中args和arg-names的使用

最近在看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配置依照猜测来看会这样配置

  1.  
    <aop:config proxy-target-class="true">
  2.  
    <aop:pointcut id="pointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(..)) and args(String,java.util.Date,..)"/>
  3.  
    <aop:aspect ref="advice">
  4.  
    <aop:around method="aroundIntercept" pointcut-ref="pointcut" arg-names="String,java.util.Date"/>
  5.  
    </aop:aspect>
  6.  
    </aop:config>

但是运行测试后提示arg-names中的java.util.Date非法,也就是不能使用点分隔符。来看正确的配置

  1.  
    <aop:config proxy-target-class="true">
  2.  
    <aop:pointcut id="pointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(..)) and args(str,date,..)"/>
  3.  
    <aop:aspect ref="advice">
  4.  
    <aop:around method="aroundIntercept" pointcut-ref="pointcut" arg-names="str,date"/>
  5.  
    </aop:aspect>
  6.  
    </aop:config>

arg-names中的变量必须同args中的一致,这是肯定的,那会有人问,args中原来配置的是参数的类型,现在没有了类型,还有用吗?其实此时的类型匹配已经转移到advice类中的方法的参数上了。可以看到上面的aroundIntercept方法中,后两个参数不是都有类型吗,Spring通过此种方式实现了advice在运行时接收原始方法的参数。很麻烦是不是,而且特别容易出错,我做测试的时候都要反反复复改几遍。XML不好用,我们来看看注解的方式。

2.注解

还是以环绕增强举例,通过@Aspect标注切面类

  1.  
    @Component
  2.  
    [@Aspect](https://my.oschina.net/aspect)
  3.  
    public class AspectJAnnotationBrowserAroundAdvice {
  4.  
    @Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(..))")
  5.  
    private void pointcut(){
  6.  
     
  7.  
    }
  8.  
     
  9.  
    @Around(value="pointcut()")
  10.  
    public Object aroundIntercept(ProceedingJoinPoint pjp) throws Throwable{
  11.  
    Object retVal = pjp.proceed();
  12.  
    return retVal;
  13.  
    }
  14.  
    }

注解方式的AOP一般都会使用component-scan方式,因而要在切面类上加上@Component注解。@Pointcut一般放在一个空方法上,其值为切入点表达式;@Around标识环绕增强,value值为切入点的方法名,或者直接写切入点表达式也可以。

我们需要使用args配置,从而在增强方法中调用方法运行时的参数值,来看下其配置

  1.  
    @Component
  2.  
    @Aspect
  3.  
    public class AspectJAnnotationArgsBrowserAroundAdvice {
  4.  
     
  5.  
    @Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(..)) && args(music,date,..)")
  6.  
    private void pointcut(String music, Date date){
  7.  
     
  8.  
    }
  9.  
     
  10.  
    @Around(value="pointcut(music,date)")
  11.  
    public Object aroundIntercept(ProceedingJoinPoint pjp, String music, Date date) throws Throwable{
  12.  
    Object retVal = pjp.proceed();
  13.  
    return retVal;
  14.  
    }
  15.  
    }

args中使用昵称方式,且必须同切入点方法pointcut中的参数名称一致(为什么能匹配到参数名称将在下一章重点介绍)。目标方法参数的类型则由pointcut的参数类型及顺序来决定,这同xml配置中一致。

而增强方法引用切入点方法时,则必须将切入点方法中的参数带上,如上述代码中的@Around(value="pointcut(music,date)")。对于增强方法中引用目标对象中方法参数时,参数名称则可以随意定义,不需要同切入点方法参数一致。

如果你很任性,在@Around中就不想用之前的参数名称,怎么办?使用@Around中的argNames属性,它同xml配置中的arg-names相对应。

  1.  
    @Around(value="pointcut(music,datetime)",argNames="music,datetime")
  2.  
    public Object aroundIntercept(ProceedingJoinPoint pjp, String musi, Date date) throws Throwable{
  3.  
    Object retVal = pjp.proceed();
  4.  
    return retVal;
  5.  
    }

通过在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的方式有以下的区别:

  1. execution定义方法入参类型只能根据入参类型匹配方法,而不能获取参数值。因而必须使用参数类型,如String,java.util.Date,不能使用args方式的昵称方式,且增强方法中不能引入方法参数。而args则是动态切入的,在每次方法执行时匹配一次,并获取参数值可供增强方法调用。
  2. 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的规则

  1.  
    @Component
  2.  
    @Aspect
  3.  
    public class AspectJAnnotationArgsBrowserAroundAdvice {
  4.  
     
  5.  
    @Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(String,java.util.Date+,..))")
  6.  
    private void pointcut(){
  7.  
     
  8.  
    }
  9.  
     
  10.  
    @Around(value="pointcut()")
  11.  
    public Object aroundIntercept(ProceedingJoinPoint pjp) throws Throwable{
  12.  
    Object retVal = pjp.proceed();
  13.  
    return retVal;
  14.  
    }
  15.  
    }

在切入点方法pointcut中也是不允许定义参数,而仅仅通过execution表达式中定义的方法入参类型进行匹配。

4.总结

对于args和arg-names,除非有比较特殊的需求,不然基本也不会使用。作为任性的我,看到网上一波波介绍Spring expression表达式时都会copy这两个的用法,就在看源码的同时特意对它们进行了测试,因此留下这篇给自己和他人以后万一使用时做个参考。

然而在我测试的过程,另一个问题引起了我的关注(上文中有提到),在@Pointcut中定义args(music,date)时,pointcut方法里的两个参数的名称也必须同其一致,不然则抛错。这引起我的很大兴趣,因为在之前也看过一些博文,对于java中的方法参数名正常的方式是获取不到的,那此处是怎么获取到方法参数名的呢,请见下一章。

转载于:https://my.oschina.net/u/2377110/blog/1525709

posted on 2022-07-15 17:38  1450811640  阅读(725)  评论(0编辑  收藏  举报