Spring中的AOP(五)——在Advice方法中获取目标方法的参数

Easter79
• 阅读 498

获取目标方法的信息

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

  • Object[] getArgs:返回目标方法的参数

  • Signature getSignature:返回目标方法的签名

  • Object getTarget:返回被织入增强处理的目标对象

  • Object getThis:返回AOP框架为目标对象生成的代理对象

    注意:当使用@Around处理时,我们需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。

    下面的切面类(依然放在com.abc.advice包中)中定义了Before、Around、AfterReturning和After 4中增强处理,并分别在4种增强处理种访问被织入增强处理的目标方法、目标方法的参数和被织入增强处理的目标对象等:

package com.abc.advice;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AdviceTest {
    @Around("execution(* com.abc.service.*.many*(..))")
    public Object process(ProceedingJoinPoint point) throws Throwable {
        System.out.println("@Around:执行目标方法之前...");
        //访问目标方法的参数:
        Object[] args = point.getArgs();
        if (args != null && args.length > 0 && args[0].getClass() == String.class) {
            args[0] = "改变后的参数1";
        }
        //用改变后的参数执行目标方法
        Object returnValue = point.proceed(args);
        System.out.println("@Around:执行目标方法之后...");
        System.out.println("@Around:被织入的目标对象为:" + point.getTarget());
        return "原返回值:" + returnValue + ",这是返回结果的后缀";
    }
    
    @Before("execution(* com.abc.service.*.many*(..))")
    public void permissionCheck(JoinPoint point) {
        System.out.println("@Before:模拟权限检查...");
        System.out.println("@Before:目标方法为:" + 
                point.getSignature().getDeclaringTypeName() + 
                "." + point.getSignature().getName());
        System.out.println("@Before:参数为:" + Arrays.toString(point.getArgs()));
        System.out.println("@Before:被织入的目标对象为:" + point.getTarget());
    }
    
    @AfterReturning(pointcut="execution(* com.abc.service.*.many*(..))", 
        returning="returnValue")
    public void log(JoinPoint point, Object returnValue) {
        System.out.println("@AfterReturning:模拟日志记录功能...");
        System.out.println("@AfterReturning:目标方法为:" + 
                point.getSignature().getDeclaringTypeName() + 
                "." + point.getSignature().getName());
        System.out.println("@AfterReturning:参数为:" + 
                Arrays.toString(point.getArgs()));
        System.out.println("@AfterReturning:返回值为:" + returnValue);
        System.out.println("@AfterReturning:被织入的目标对象为:" + point.getTarget());
        
    }
    
    @After("execution(* com.abc.service.*.many*(..))")
    public void releaseResource(JoinPoint point) {
        System.out.println("@After:模拟释放资源...");
        System.out.println("@After:目标方法为:" + 
                point.getSignature().getDeclaringTypeName() + 
                "." + point.getSignature().getName());
        System.out.println("@After:参数为:" + Arrays.toString(point.getArgs()));
        System.out.println("@After:被织入的目标对象为:" + point.getTarget());
    }
}

    在AdviceManager类中增加以下内容:

//将被AdviceTest的各种方法匹配
public String manyAdvices(String param1, String param2) {
    System.out.println("方法:manyAdvices");
    return param1 + " 、" + param2;
}

在com.abc.main.AOPTest中加入方法的调用,触发切点:

String result = manager.manyAdvices("aa", "bb");
System.out.println("Test方法中调用切点方法的返回值:" + result);

    下面是执行结果:

@Around:执行目标方法之前...
@Before:模拟权限检查...
@Before:目标方法为:com.abc.service.AdviceManager.manyAdvices
@Before:参数为:[改变后的参数1, bb]
@Before:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
方法:manyAdvices
@Around:执行目标方法之后...
@Around:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
@After:模拟释放资源...
@After:目标方法为:com.abc.service.AdviceManager.manyAdvices
@After:参数为:[改变后的参数1, bb]
@After:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
@AfterReturning:模拟日志记录功能...
@AfterReturning:目标方法为:com.abc.service.AdviceManager.manyAdvices
@AfterReturning:参数为:[改变后的参数1, bb]
@AfterReturning:返回值为:原返回值:改变后的参数1 、 bb,这是返回结果的后缀
@AfterReturning:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
Test方法中调用切点方法的返回值:原返回值:改变后的参数1 、bb,这是返回结果的后缀

    从结果中可以看出:在任何一个织入的增强处理中,都可以获取目标方法的信息。另外,Spring AOP采用和AspectJ一样的有限顺序来织入增强处理:在“进入”连接点时,最高优先级的增强处理将先被织入(所以给定的两个Before增强处理中,优先级高的那个会先执行);在“退出”连接点时,最高优先级的增强处理会最后被织入(所以给定的两个After增强处理中,优先级高的那个会后执行)。当不同的切面中的多个增强处理需要在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这些增强处理。如果应用需要指定不同切面类里的增强处理的优先级,Spring提供了如下两种解决方案:

  • 让切面类实现org.springframework.core.Ordered接口:实现该接口只需要实现一个int getOrder()方法,该方法返回值越小,优先级越高

  • 直接使用@Order注解来修饰一个切面类:使用这个注解时可以配置一个int类型的value属性,该属性值越小,优先级越高

    优先级高的切面类里的增强处理的优先级总是比优先级低的切面类中的增强处理的优先级高。例如:优先级为1的切面类Bean1包含了@Before,优先级为2的切面类Bean2包含了@Around,虽然@Around优先级高于@Before,但由于Bean1的优先级高于Bean2的优先级,因此Bean1中的@Before先被织入。

    同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理,没有办法指定它们的织入顺序。如果确实需要保证它们以固有的顺序被织入,则可以考虑将多个增强处理压缩为一个增强处理;或者将不同增强处理重构到不同切面中,通过在切面级别上定义顺序。

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

package com.abc.advice;

import java.util.Date;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AccessArgAdviceTest {
    @AfterReturning(
            pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)",
            returning="returnValue")
    public void access(Date time, Object returnValue, String name) {
        System.out.println("目标方法中的参数String = " + name);
        System.out.println("目标方法中的参数Date = " + time);
        System.out.println("目标方法的返回结果returnValue = " + returnValue);
    }
}

    上面的程序中,定义pointcut时,表达式中增加了args(time, name)部分,意味着可以在增强处理方法(access方法)中定义time和name两个属性——这两个形参的类型可以随意指定,但一旦指定了这两个参数的类型,则这两个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二个参数类型为name的方法(方法参数个数和类型若有不同均不匹配)。

    注意,在定义returning的时候,这个值(即上面的returning="returnValue"中的returnValue)作为增强处理方法的形参时,位置可以随意,即:如果上面access方法的签名可以为

public void access(Date time, Object returnValue, String name)

    也可以为

public void access(Object returnValue, Date time, String name)

    还可以为

public void access(Date time, String name, Object returnValue)

    只需要满足另外的参数名的顺序和pointcut中args(param1, param2)的顺序相同即可。我们在AdviceManager中定义一个方法,该方法的第一个参数为Date类型,第二个参数为String类型,该方法的执行将触发上面的access方法,如下:

//将被AccessArgAdviceTest的access方法匹配
public String accessAdvice(Date d, String n) {
    System.out.println("方法:accessAdvice");
    return "aa";
}

    在AOPTest中增加调用这个accessAdvice方法并执行,下面是输出结果:

Spring中的AOP(五)——在Advice方法中获取目标方法的参数

    从执行结果可以看出,使用args表达式有如下两个作用:

  • 提供了一种简单的方式来访问目标方法的参数

  • 可用于对切入点表达式作额外的限制

    除此之外,使用args表达式时,还可以使用如下形式:args(param1, param2, ..),注意args参数中后面的两个点,它表示可以匹配更多参数。在例子args(param1, param2, ..)中,表示目标方法只需匹配前面param1和param2的类型即可。

《Spring中的AOP系列三、四、五》的代码在这里:点击下载,欢迎留言提意见。

【未完,待续】

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
kenx kenx
3年前
SpringBoot AOP中JoinPoint的用法和通知切点表达式
前言上一篇文章讲解了springbootaop初步完整的使用和整合这一篇讲解他的接口方法和类JoinPoint和ProceedingJoinPoint对象1.JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.2.ProceedingJoin
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
SpringMvc接受特殊符号参数被转义
WEB开发时,在前端通过get/post方法传递参数的时候 如果实参附带特殊符号,后端接收到的值中特殊符号就会被转义例如该请求: http://localhost:10001/demo/index.do?name张三(1)注:中文()不会出现此种情况后台就收到的实际name值为:  张三&40;1&41;&40;其实为h
Easter79 Easter79
3年前
Spring中的AOP(四)——基于Annotation的配置方式(二)
使用After增强处理    Spring还提供了一个After增强处理,它与AfterReturning有点类似,但也有区别:AfterReturning增强处理只有在目标方法正确完成后才会被织入After增强处理不管目标方法如何结束(正确还是异常),它都会被织入    正是因为这个特点,因此Aft
Wesley13 Wesley13
3年前
AOP的基本术语
通知、增强(Advice)通知就是你想要的功能,在特定的连接点,AOP框架执行的动作。Spring切面可以应用5种类型的通知:前置通知(Before):在目标方法被调用之前调用通知功能;后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;返回通知(Afterreturni
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k