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. leetcode - 链表两两元素交换 + 判断链表有无环

    链表两两元素交换 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表. 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换. 示例: 给定 1->2->3->4, 你 ...

  2. Error: EACCES: permission denied, mkdir

    今天在全局安装飞冰的时候,出现标题的错误 想到是权限不够的问题,其实飞冰官网也有解决的办法,就是更改npm的默认路径.我之前就是用了更改默认路径的方法,然后后来又恢复了默认路径,所以还是用默认路径加权 ...

  3. windows10下Bad owner or permissions on .ssh/config的解决办法

    方法很简单,亲测有效. 1.进入如下路径C:\Users\用户名\.ssh,你会看到有config这个文件 2.右击config,属性→安全→高级→禁止继承→删除所有继承(忘了全称了,大概这个意思)→ ...

  4. true false

    #include<stdio.h> int main(void) { /* true 1 false 0 */ printf( == ); printf( > ); printf( ...

  5. Lp距离, L1范数, 和L2范数(转载)

    范式可以理解成距离 转载自: https://blog.csdn.net/hanhuili/article/details/52079590 内容如下: 由此可见,L2其实就是欧式距离.工程上,往往不 ...

  6. TCP四次握手断开连接(十一)

    建立连接非常重要,它是数据正确传输的前提:断开连接同样重要,它让计算机释放不再使用的资源.如果连接不能正常断开,不仅会造成数据传输错误,还会导致套接字不能关闭,持续占用资源,如果并发量高,服务器压力堪 ...

  7. Excel 日期和时间函数

    1.TODAY和NOW函数 today和now函数 日期可以进行加减运算 2.提取日期和时间的函数 公式=Year() 公式=month() 公式=day() 公式=hour() 公式=minute( ...

  8. ionic-环境搭建-入门

    环境搭建 1.官方推荐: npm install -g cordova ionic 使用npm国内安装小坑,下载慢,还是失败 2.先安装cnpm,使用淘宝镜像:: npm install -g cnp ...

  9. [Android] Windows 7下 Android studio 安装 Genymotion 来调试 Android 遇到的问题总结

    一.下载相关软件 1.Android studio  3.1.4 官网下载地址: https://dl.google.com/dl/android/studio/install/3.1.4.0/and ...

  10. C++对象布局

    <C++应用程序性能优化><深度探索C++对象模型>笔记 #include<iostream> using namespace std; class student ...