Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理

 

对于spring框架来说,最重要的两大特性就是AOP 和IOC。

以前一直都知道有这两个东西,在平时做的项目中也常常会涉及到这两块,像spring的事务管理什么的,在看了些源码后,才知道原来事务管理也是用的AOP来实现的。对于IOC的话,平时接触的就更多了,什么autowired,resource各种注解,就是IOC的各种应用。

一直我也想着能有机会自己动手写个aop的小DEMO,不过一直没机会,想到了许多,在网上一搜,基本上都已经有了。今天想到一个用于对service方法进行拦截的功能点,今天决定用springBoot的工程来实现一下。

功能点描述:对某个service的方法执行前,获取出入参,对入参的参数进行修改,将参数进行替换。然后在这个方法执行完毕后,再对其返回结果进行修改。主要就是对一个方法装饰一下。说到装饰,第一想到的是采用装饰器模式来实现,但装饰器模式需要对整个代码的结构进行一些修改,为了达到对以前的代码不进行任何接触,且装饰器模式的局限性较小,所以最好还是用spring的AOP来实现这种对代码无任何侵入的功能。

service的代码如下:

@Service

public class TestServiceImpl implements TestService {

private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override

public ResultVO getResultData(ParamVO paramVO) {

return process(paramVO);

}

private ResultVO process(ParamVO paramVO) {

logger.info("----->input INFO:{}", paramVO);

ResultVO resultVO = new ResultVO();

resultVO.setCode(200);

resultVO.setData(Arrays.asList("123", "456", "789"));

resultVO.setMessage("OK!!!!!!!! and your inputParam is" + paramVO.toString());

logger.info("---->return INFO:{}", resultVO.toString());

return resultVO;

}

其中入参为paramVO,代码如下:

public class ParamVO {

private String inputParam;

private String inputParam2;

//getter and setter

}

返回的参数ResutVO,代码如下:

  1.  
    public class ResultVO {
  2.  
    private Integer code;
  3.  
    private String message;
  4.  
    private Object data;
  5.  
    //getter and setter
  6.  
    }

其调用的入口为一个controller,代码如下:

  1.  
    @RequestMapping(value = "test")
  2.  
    @RestController
  3.  
    public class TestController {
  4.  
    @Resource
  5.  
    private TestService testService;
  6.  
     
  7.  
    @GetMapping(value = "getResult")
  8.  
    public ResultVO getResult(ParamVO paramVO) {
  9.  
    ResultVO resultData = testService.getResultData(paramVO);
  10.  
    return resultData;
  11.  
    }

在正常情况下,按照如上的代码进行调用将返回如下的信息:

通过返回的信息可以看到,入参是我们在请求参数传入的inputParam=111和inputParam2=2220

现在要做的就是把入参的参数通过AOP来拦截,并进行修改。对于返回值,也进行一下修改。

首先让工程引入AOP的包:

  1.  
    <!-- AOP -->
  2.  
    <dependency>
  3.  
    <groupId>org.springframework.boot</groupId>
  4.  
    <artifactId>spring-boot-starter-aop</artifactId>
  5.  
    </dependency>

然后定义一个Aspect,并指定一个切入点,配置要进行哪些方法的拦截

这里只针对TestSevice这个接口下的getResultData进行拦截

  1.  
    private final String ExpGetResultDataPonit = "execution(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";
  2.  
     
  3.  
    //定义切入点,拦截servie包其子包下的所有类的所有方法
  4.  
    // @Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")
  5.  
    //拦截指定的方法,这里指只拦截TestService.getResultData这个方法
  6.  
    @Pointcut(ExpGetResultDataPonit)
  7.  
    public void excuteService() {
  8.  
     
  9.  
    }

对于切入点的配置表达式,可以在网上自行搜索,网上也有许多

在指定了切入点后,就可以对这个切入点excuteService()这个点进行相应的操作了。

可以配置@Before  @After 等来进行相应的处理,其代表的意思分别是前置与后置,就是下面代码这个意思

  1.  
    @Override
  2.  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3.  
    Object result;
  4.  
    try {
  5.  
    //@Before
  6.  
    result = method.invoke(target, args);
  7.  
    //@After
  8.  
    return result;
  9.  
    } catch (InvocationTargetException e) {
  10.  
    Throwable targetException = e.getTargetException();
  11.  
    //@AfterThrowing
  12.  
    throw targetException;
  13.  
    } finally {
  14.  
    //@AfterReturning
  15.  
    }
  16.  
    }

由于要对入参和最终返回结果进行处理,所以选择Before和AfterReturning,原来以为after也可以,但看了下,它好像并不能拿到这个方法的返回值,而AfterReturning是一定可以的

拦截后,对应的处理代码如下:

  1.  
    //执行方法前的拦截方法
  2.  
    @Before("excuteService()")
  3.  
    public void doBeforeMethod(JoinPoint joinPoint) {
  4.  
    System.out.println("我是前置通知,我将要执行一个方法了");
  5.  
    //获取目标方法的参数信息
  6.  
    Object[] obj = joinPoint.getArgs();
  7.  
    for (Object argItem : obj) {
  8.  
    System.out.println("---->now-->argItem:" + argItem);
  9.  
    if (argItem instanceof ParamVO) {
  10.  
    ParamVO paramVO = (ParamVO) argItem;
  11.  
    paramVO.setInputParam("666666");
  12.  
    }
  13.  
    System.out.println("---->after-->argItem:" + argItem);
  14.  
    }
  15.  
    }
  16.  
     
  17.  
    /**
  18.  
    * 后置返回通知
  19.  
    * 这里需要注意的是:
  20.  
    * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
  21.  
    * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
  22.  
    * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
  23.  
    */
  24.  
    @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
  25.  
    public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
  26.  
    System.out.println("第一个后置返回通知的返回值:" + keys);
  27.  
    if (keys instanceof ResultVO) {
  28.  
    ResultVO resultVO = (ResultVO) keys;
  29.  
    String message = resultVO.getMessage();
  30.  
    resultVO.setMessage("通过AOP把值修改了 " + message);
  31.  
    }
  32.  
    System.out.println("修改完毕-->返回方法为:" + keys);
  33.  
    }

然后再请求一下之前的请求

从这里可以看出,通过AOP的拦截,已经把对应的值修改了,入参inputParam由111改成了666666,返回结果message也加上了几个字

除了用Before和AfterReturning外,还可以用环绕来实现同样的功能,如:

  1.  
    /**
  2.  
    * 环绕通知:
  3.  
    * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
  4.  
    * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
  5.  
    */
  6.  
    @Around(ExpGetResultDataPonit)
  7.  
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
  8.  
    System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
  9.  
    processInputArg(proceedingJoinPoint.getArgs());
  10.  
    try {//obj之前可以写目标方法执行前的逻辑
  11.  
    Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
  12.  
    processOutPutObj(obj);
  13.  
    return obj;
  14.  
    } catch (Throwable throwable) {
  15.  
    throwable.printStackTrace();
  16.  
    }
  17.  
    return null;
  18.  
    }
  19.  
     
  20.  
    /**
  21.  
    * 处理返回对象
  22.  
    */
  23.  
    private void processOutPutObj(Object obj) {
  24.  
    System.out.println("OBJ 原本为:" + obj.toString());
  25.  
    if (obj instanceof ResultVO) {
  26.  
    ResultVO resultVO = (ResultVO) obj;
  27.  
    resultVO.setMessage("哈哈,我把值修改了" + resultVO.getMessage());
  28.  
    System.out.println(resultVO);
  29.  
    }
  30.  
    }
  31.  
     
  32.  
    /**
  33.  
    * 处理输入参数
  34.  
    *
  35.  
    * @param args 入参列表
  36.  
    */
  37.  
    private void processInputArg(Object[] args) {
  38.  
    for (Object arg : args) {
  39.  
    System.out.println("ARG原来为:" + arg);
  40.  
    if (arg instanceof ParamVO) {
  41.  
    ParamVO paramVO = (ParamVO) arg;
  42.  
    paramVO.setInputParam("654321");
  43.  
    }
  44.  
    }
  45.  
    }

这样写,也可以达到相同的目的

切面代码完整如下:

  1.  
    package com.haiyang.onlinejava.complier.aspect;
  2.  
     
  3.  
    import com.haiyang.onlinejava.complier.vo.ParamVO;
  4.  
    import com.haiyang.onlinejava.complier.vo.ResultVO;
  5.  
    import org.aspectj.lang.JoinPoint;
  6.  
    import org.aspectj.lang.ProceedingJoinPoint;
  7.  
    import org.aspectj.lang.annotation.*;
  8.  
    import org.springframework.context.annotation.Configuration;
  9.  
     
  10.  
    @Configuration
  11.  
    @Aspect
  12.  
    public class ServiceAspect {
  13.  
     
  14.  
    private final String ExpGetResultDataPonit = "execution(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";
  15.  
     
  16.  
    //定义切入点,拦截servie包其子包下的所有类的所有方法
  17.  
    // @Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")
  18.  
    //拦截指定的方法,这里指只拦截TestService.getResultData这个方法
  19.  
    @Pointcut(ExpGetResultDataPonit)
  20.  
    public void excuteService() {
  21.  
     
  22.  
    }
  23.  
     
  24.  
    //执行方法前的拦截方法
  25.  
    // @Before("excuteService()")
  26.  
    public void doBeforeMethod(JoinPoint joinPoint) {
  27.  
    System.out.println("我是前置通知,我将要执行一个方法了");
  28.  
    //获取目标方法的参数信息
  29.  
    Object[] obj = joinPoint.getArgs();
  30.  
    for (Object argItem : obj) {
  31.  
    System.out.println("---->now-->argItem:" + argItem);
  32.  
    if (argItem instanceof ParamVO) {
  33.  
    ParamVO paramVO = (ParamVO) argItem;
  34.  
    paramVO.setInputParam("666666");
  35.  
    }
  36.  
    System.out.println("---->after-->argItem:" + argItem);
  37.  
    }
  38.  
    }
  39.  
     
  40.  
    /**
  41.  
    * 后置返回通知
  42.  
    * 这里需要注意的是:
  43.  
    * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
  44.  
    * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
  45.  
    * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
  46.  
    */
  47.  
    // @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
  48.  
    public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
  49.  
    System.out.println("第一个后置返回通知的返回值:" + keys);
  50.  
    if (keys instanceof ResultVO) {
  51.  
    ResultVO resultVO = (ResultVO) keys;
  52.  
    String message = resultVO.getMessage();
  53.  
    resultVO.setMessage("通过AOP把值修改了 " + message);
  54.  
    }
  55.  
    System.out.println("修改完毕-->返回方法为:" + keys);
  56.  
    }
  57.  
     
  58.  
     
  59.  
    /**
  60.  
    * 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
  61.  
    */
  62.  
    // @After("excuteService()")
  63.  
    public void doAfterAdvice(JoinPoint joinPoint) {
  64.  
     
  65.  
    System.out.println("后置通知执行了!!!!");
  66.  
    }
  67.  
     
  68.  
     
  69.  
     
  70.  
     
  71.  
    /**
  72.  
    * 环绕通知:
  73.  
    * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
  74.  
    * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
  75.  
    */
  76.  
    @Around(ExpGetResultDataPonit)
  77.  
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
  78.  
    System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
  79.  
    processInputArg(proceedingJoinPoint.getArgs());
  80.  
    try {//obj之前可以写目标方法执行前的逻辑
  81.  
    Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
  82.  
    processOutPutObj(obj);
  83.  
    return obj;
  84.  
    } catch (Throwable throwable) {
  85.  
    throwable.printStackTrace();
  86.  
    }
  87.  
    return null;
  88.  
    }
  89.  
     
  90.  
    /**
  91.  
    * 处理返回对象
  92.  
    */
  93.  
    private void processOutPutObj(Object obj) {
  94.  
    System.out.println("OBJ 原本为:" + obj.toString());
  95.  
    if (obj instanceof ResultVO) {
  96.  
    ResultVO resultVO = (ResultVO) obj;
  97.  
    resultVO.setMessage("哈哈,我把值修改了" + resultVO.getMessage());
  98.  
    System.out.println(resultVO);
  99.  
    }
  100.  
    }
  101.  
     
  102.  
    /**
  103.  
    * 处理输入参数
  104.  
    *
  105.  
    * @param args 入参列表
  106.  
    */
  107.  
    private void processInputArg(Object[] args) {
  108.  
    for (Object arg : args) {
  109.  
    System.out.println("ARG原来为:" + arg);
  110.  
    if (arg instanceof ParamVO) {
  111.  
    ParamVO paramVO = (ParamVO) arg;
  112.  
    paramVO.setInputParam("654321");
  113.  
    }
  114.  
    }
  115.  
    }
  116.  
     
  117.  
     
  118.  
    }

如不进行@Before和@AfterReturing的注释,最终的结果如下:

控制台打印的日志为:

通过查看打印的结果,我们可以知道@Around @Before  @After  @AfterReturning这几个注解的执行顺序为:

Around
AroundBefore
  before
  method.invoke()
  AroundAfter
      After
AfterReturning

主要参考:http://blog.csdn.net/zknxx/article/details/53240959

Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理的更多相关文章

  1. spring boot aop打印http请求回复日志包含请求体

    一.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  2. Spring Boot中扩展XML请求和响应的支持

    在Spring Boot中,我们大多时候都只提到和用到了针对HTML和JSON格式的请求与响应处理.那么对于XML格式的请求要如何快速的在Controller中包装成对象,以及如何以XML的格式返回一 ...

  3. 【spring boot】spring boot中使用@RestController不起作用,不返回json,依旧去找访问接口的请求地址对应的页面

    问题描述: spring boot中使用@RestController不起作用,不返回json,依旧去找访问接口的请求地址对应的页面 表现结果: 1>使用postman测试接口,表现为返回是40 ...

  4. Spring Boot AOP解析

    Spring Boot AOP 面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. AOP(Aspec ...

  5. Spring boot AOP 记录请求日志

    如何将所有的通过url的请求参数以及返回结果都输出到日志中? 如果在controller的类中每个方法名都写一个log输出肯定是不明智的选择. 使用spring的AOP功能即可完成. 1. 在pom. ...

  6. 玩转spring boot——AOP与表单验证

    AOP在大多数的情况下的应用场景是:日志和验证.至于AOP的理论知识我就不做赘述.而AOP的通知类型有好几种,今天的例子我只选一个有代表意义的“环绕通知”来演示. 一.AOP入门 修改“pom.xml ...

  7. Spring Boot AOP 扫盲,实现接口访问的统一日志记录

    AOP 是 Spring 体系中非常重要的两个概念之一(另外一个是 IoC),今天这篇文章就来带大家通过实战的方式,在编程猫 SpringBoot 项目中使用 AOP 技术为 controller 层 ...

  8. spring boot中 使用http请求

    因为项目需求,需要两个系统之间进行通信,经过一番调研,决定使用http请求. 服务端没有什么好说的,本来就是使用web 页面进行访问的,所以spring boot启动后,controller层的接口就 ...

  9. 【轮询】【ajax】【js】【spring boot】ajax超时请求:前端轮询处理超时请求解决方案 + spring boot服务设置接口超时时间的设置

    场景描述: ajax设置timeout在本机测试有效,但是在生产环境等外网环境无效的问题 1.ajax的timeout属性设置 前端请求超时事件[网络连接不稳定时候,就无效了] var data = ...

随机推荐

  1. 从未被Google过 #NerverBeenGoogled

    我相信大家都用Google搜索互联网上的东西,Google会跟踪你搜索的所有内容,但是你或许不知道,他们也记录着从未被Google过的内容.我有个清单,这些是有史以来从未被Google过的一些东西1. ...

  2. 基于Docker实现MySQL主从复制

    前言 MySQL的主从复制是实现应用的高性能,高可用的基础.对于数据库读操作较密集的应用,通过使数据库请求负载均衡分配到不同MySQL服务器,可有效减轻数据库压力.当遇到MySQL单点故障中,也能在短 ...

  3. composer install 出现“Please provide a valid cache path”

    本文背景:通过deployer部署PHP项目[deployer部署工具:https://deployer.org/] 问题:Php 的laravel框架中执行 composer install 后, ...

  4. debian apt-get 代理

    一:在 /etc/apt/目录下建立 apt.conf 文件增加如下 Acquire::http::Proxy "http://proxyxxxxxxx:port"; 注意格式:最 ...

  5. LR性能测试分析流程

    LR性能测试分析流程 一.     判断测试结果的有效性 (1)在整个测试场景的执行过程中,测试环境是否正常. (2)测试场景的设置是否正确.合理. (3)测试结果是否直接暴露出系统的一些问题. (4 ...

  6. Linux CentOs基本命令

    基本操作(命令模式下) yy --复制光标所在行 nyy --n为数字,复制光标所在向下n行 p --粘贴到光标的下一行 P --贴在光标的上一行 G --光标移到文件末尾 gg --光标移到文件头 ...

  7. react知识点

    http://www.cocoachina.com/webapp/20150721/12692.html http://blog.csdn.net/slandove/article/details/5 ...

  8. 201871010117-石欣钰《面向对象程序设计(java)》第十一周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...

  9. LeetCode 139. Word Break单词拆分 (C++)

    题目: Given a non-empty string s and a dictionary wordDict containing a list of non-emptywords, determ ...

  10. Windows下找到JVM占用资源高的线程

    与linux下top命令直接显示进程下线程资源占用不同,Windows下默认任务管理器只能显示出进程的资源占用,jconsle等工具也只能显示出java进程资源占用,无法显示出进程能具体线程的资源占用 ...