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. 车间如何数字化?MES系统来助力

    对于生产过程复杂多变的离散制造企业而言,面临重重考验:生产作业计划频繁变更,制造工艺复杂,在生产过程中的临时插单.材料短缺等现象.通过MES制造执行管理解决方案,搭建协同管理平台,加强控制力.执行力和 ...

  2. GIC , SPI , PPI (窝窝科技的文章题目改了下)【转】

    转自:https://www.cnblogs.com/tureno/articles/6403408.html 转载于:  http://www.wowotech.net/irq_subsystem/ ...

  3. c# 第9节 数据类型之值类型

    本节内容: 1:数据类型是什么 1:数据类型是什么 2:数据类型--值类型 3:值类型和引用类型的区分 画图现象: 3:值类型的种类 整数: 浮点数: 字符:

  4. 虚拟机使用配置固定IP

    首先打开虚拟机 打开xshell5连接虚拟机(比较方便,这里默认设置过Linux的ip,只是不固定) 输入ifconfig,可以查看网管相关配置信息: 然后输入    vi /etc/sysconfi ...

  5. 【Spring IoC】依赖注入DI(四)

    平常的Java开发中,程序员在某个类中需要依赖其它类的方法.通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理. Spring提出了依赖注入的思想,即依赖类不由程 ...

  6. CF306C White, Black and White Again

    CF306C White, Black and White Again 洛谷评测传送门 题目描述 Polycarpus is sure that his life fits the descripti ...

  7. (转)Tomcat与Jetty区别

    一.简介 Tomcat: Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache.Sun和其他一些公司及个人共 ...

  8. RGB颜色查询

    RGB颜色速查表   #FFFFFF   #FFFFF0   #FFFFE0   #FFFF00   #FFFAFA   #FFFAF0   #FFFACD   #FFF8DC   #FFF68F   ...

  9. [LeetCode] 730. Count Different Palindromic Subsequences 计数不同的回文子序列的个数

    Given a string S, find the number of different non-empty palindromic subsequences in S, and return t ...

  10. 使用IDEA+vue.js+easyUI的demo

    最近,随便写了几个开发的小例子,自己总结下,留个纪念. 例子1:使用EasyUI做了一个简单界面,服务器和客户端在一起. @Controller @RequestMapping("/demo ...