Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理
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,代码如下:
- public class ResultVO {
- private Integer code;
- private String message;
- private Object data;
- //getter and setter
- }
其调用的入口为一个controller,代码如下:
- @RequestMapping(value = "test")
- @RestController
- public class TestController {
- @Resource
- private TestService testService;
- @GetMapping(value = "getResult")
- public ResultVO getResult(ParamVO paramVO) {
- ResultVO resultData = testService.getResultData(paramVO);
- return resultData;
- }
在正常情况下,按照如上的代码进行调用将返回如下的信息:
通过返回的信息可以看到,入参是我们在请求参数传入的inputParam=111和inputParam2=2220
现在要做的就是把入参的参数通过AOP来拦截,并进行修改。对于返回值,也进行一下修改。
首先让工程引入AOP的包:
- <!-- AOP -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
然后定义一个Aspect,并指定一个切入点,配置要进行哪些方法的拦截
这里只针对TestSevice这个接口下的getResultData进行拦截
- private final String ExpGetResultDataPonit = "execution(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";
- //定义切入点,拦截servie包其子包下的所有类的所有方法
- // @Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")
- //拦截指定的方法,这里指只拦截TestService.getResultData这个方法
- @Pointcut(ExpGetResultDataPonit)
- public void excuteService() {
- }
对于切入点的配置表达式,可以在网上自行搜索,网上也有许多
在指定了切入点后,就可以对这个切入点excuteService()这个点进行相应的操作了。
可以配置@Before @After 等来进行相应的处理,其代表的意思分别是前置与后置,就是下面代码这个意思
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Object result;
- try {
- //@Before
- result = method.invoke(target, args);
- //@After
- return result;
- } catch (InvocationTargetException e) {
- Throwable targetException = e.getTargetException();
- //@AfterThrowing
- throw targetException;
- } finally {
- //@AfterReturning
- }
- }
由于要对入参和最终返回结果进行处理,所以选择Before和AfterReturning,原来以为after也可以,但看了下,它好像并不能拿到这个方法的返回值,而AfterReturning是一定可以的
拦截后,对应的处理代码如下:
- //执行方法前的拦截方法
- @Before("excuteService()")
- public void doBeforeMethod(JoinPoint joinPoint) {
- System.out.println("我是前置通知,我将要执行一个方法了");
- //获取目标方法的参数信息
- Object[] obj = joinPoint.getArgs();
- for (Object argItem : obj) {
- System.out.println("---->now-->argItem:" + argItem);
- if (argItem instanceof ParamVO) {
- ParamVO paramVO = (ParamVO) argItem;
- paramVO.setInputParam("666666");
- }
- System.out.println("---->after-->argItem:" + argItem);
- }
- }
- /**
- * 后置返回通知
- * 这里需要注意的是:
- * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
- * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
- * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
- */
- @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
- public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
- System.out.println("第一个后置返回通知的返回值:" + keys);
- if (keys instanceof ResultVO) {
- ResultVO resultVO = (ResultVO) keys;
- String message = resultVO.getMessage();
- resultVO.setMessage("通过AOP把值修改了 " + message);
- }
- System.out.println("修改完毕-->返回方法为:" + keys);
- }
然后再请求一下之前的请求
从这里可以看出,通过AOP的拦截,已经把对应的值修改了,入参inputParam由111改成了666666,返回结果message也加上了几个字
除了用Before和AfterReturning外,还可以用环绕来实现同样的功能,如:
- /**
- * 环绕通知:
- * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
- * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
- */
- @Around(ExpGetResultDataPonit)
- public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
- System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
- processInputArg(proceedingJoinPoint.getArgs());
- try {//obj之前可以写目标方法执行前的逻辑
- Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
- processOutPutObj(obj);
- return obj;
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
- return null;
- }
- /**
- * 处理返回对象
- */
- private void processOutPutObj(Object obj) {
- System.out.println("OBJ 原本为:" + obj.toString());
- if (obj instanceof ResultVO) {
- ResultVO resultVO = (ResultVO) obj;
- resultVO.setMessage("哈哈,我把值修改了" + resultVO.getMessage());
- System.out.println(resultVO);
- }
- }
- /**
- * 处理输入参数
- *
- * @param args 入参列表
- */
- private void processInputArg(Object[] args) {
- for (Object arg : args) {
- System.out.println("ARG原来为:" + arg);
- if (arg instanceof ParamVO) {
- ParamVO paramVO = (ParamVO) arg;
- paramVO.setInputParam("654321");
- }
- }
- }
这样写,也可以达到相同的目的
切面代码完整如下:
- package com.haiyang.onlinejava.complier.aspect;
- import com.haiyang.onlinejava.complier.vo.ParamVO;
- import com.haiyang.onlinejava.complier.vo.ResultVO;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.context.annotation.Configuration;
- @Configuration
- @Aspect
- public class ServiceAspect {
- private final String ExpGetResultDataPonit = "execution(* com.haiyang.onlinejava.complier.service.TestService.getResultData(..))";
- //定义切入点,拦截servie包其子包下的所有类的所有方法
- // @Pointcut("execution(* com.haiyang.onlinejava.complier.service..*.*(..))")
- //拦截指定的方法,这里指只拦截TestService.getResultData这个方法
- @Pointcut(ExpGetResultDataPonit)
- public void excuteService() {
- }
- //执行方法前的拦截方法
- // @Before("excuteService()")
- public void doBeforeMethod(JoinPoint joinPoint) {
- System.out.println("我是前置通知,我将要执行一个方法了");
- //获取目标方法的参数信息
- Object[] obj = joinPoint.getArgs();
- for (Object argItem : obj) {
- System.out.println("---->now-->argItem:" + argItem);
- if (argItem instanceof ParamVO) {
- ParamVO paramVO = (ParamVO) argItem;
- paramVO.setInputParam("666666");
- }
- System.out.println("---->after-->argItem:" + argItem);
- }
- }
- /**
- * 后置返回通知
- * 这里需要注意的是:
- * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
- * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
- * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
- */
- // @AfterReturning(value = ExpGetResultDataPonit, returning = "keys")
- public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
- System.out.println("第一个后置返回通知的返回值:" + keys);
- if (keys instanceof ResultVO) {
- ResultVO resultVO = (ResultVO) keys;
- String message = resultVO.getMessage();
- resultVO.setMessage("通过AOP把值修改了 " + message);
- }
- System.out.println("修改完毕-->返回方法为:" + keys);
- }
- /**
- * 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
- */
- // @After("excuteService()")
- public void doAfterAdvice(JoinPoint joinPoint) {
- System.out.println("后置通知执行了!!!!");
- }
- /**
- * 环绕通知:
- * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
- * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
- */
- @Around(ExpGetResultDataPonit)
- public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
- System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
- processInputArg(proceedingJoinPoint.getArgs());
- try {//obj之前可以写目标方法执行前的逻辑
- Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
- processOutPutObj(obj);
- return obj;
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
- return null;
- }
- /**
- * 处理返回对象
- */
- private void processOutPutObj(Object obj) {
- System.out.println("OBJ 原本为:" + obj.toString());
- if (obj instanceof ResultVO) {
- ResultVO resultVO = (ResultVO) obj;
- resultVO.setMessage("哈哈,我把值修改了" + resultVO.getMessage());
- System.out.println(resultVO);
- }
- }
- /**
- * 处理输入参数
- *
- * @param args 入参列表
- */
- private void processInputArg(Object[] args) {
- for (Object arg : args) {
- System.out.println("ARG原来为:" + arg);
- if (arg instanceof ParamVO) {
- ParamVO paramVO = (ParamVO) arg;
- paramVO.setInputParam("654321");
- }
- }
- }
- }
如不进行@Before和@AfterReturing的注释,最终的结果如下:
控制台打印的日志为:
通过查看打印的结果,我们可以知道@Around @Before @After @AfterReturning这几个注解的执行顺序为:
Around
AroundBefore
before
method.invoke()
AroundAfter
After
AfterReturning
Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理的更多相关文章
- spring boot aop打印http请求回复日志包含请求体
一.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...
- Spring Boot中扩展XML请求和响应的支持
在Spring Boot中,我们大多时候都只提到和用到了针对HTML和JSON格式的请求与响应处理.那么对于XML格式的请求要如何快速的在Controller中包装成对象,以及如何以XML的格式返回一 ...
- 【spring boot】spring boot中使用@RestController不起作用,不返回json,依旧去找访问接口的请求地址对应的页面
问题描述: spring boot中使用@RestController不起作用,不返回json,依旧去找访问接口的请求地址对应的页面 表现结果: 1>使用postman测试接口,表现为返回是40 ...
- Spring Boot AOP解析
Spring Boot AOP 面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. AOP(Aspec ...
- Spring boot AOP 记录请求日志
如何将所有的通过url的请求参数以及返回结果都输出到日志中? 如果在controller的类中每个方法名都写一个log输出肯定是不明智的选择. 使用spring的AOP功能即可完成. 1. 在pom. ...
- 玩转spring boot——AOP与表单验证
AOP在大多数的情况下的应用场景是:日志和验证.至于AOP的理论知识我就不做赘述.而AOP的通知类型有好几种,今天的例子我只选一个有代表意义的“环绕通知”来演示. 一.AOP入门 修改“pom.xml ...
- Spring Boot AOP 扫盲,实现接口访问的统一日志记录
AOP 是 Spring 体系中非常重要的两个概念之一(另外一个是 IoC),今天这篇文章就来带大家通过实战的方式,在编程猫 SpringBoot 项目中使用 AOP 技术为 controller 层 ...
- spring boot中 使用http请求
因为项目需求,需要两个系统之间进行通信,经过一番调研,决定使用http请求. 服务端没有什么好说的,本来就是使用web 页面进行访问的,所以spring boot启动后,controller层的接口就 ...
- 【轮询】【ajax】【js】【spring boot】ajax超时请求:前端轮询处理超时请求解决方案 + spring boot服务设置接口超时时间的设置
场景描述: ajax设置timeout在本机测试有效,但是在生产环境等外网环境无效的问题 1.ajax的timeout属性设置 前端请求超时事件[网络连接不稳定时候,就无效了] var data = ...
随机推荐
- Django 简单评论实现
创建项目 django_comment 和应用 app01 修改 urls.py 文件 from django.contrib import admin from django.urls import ...
- Django 练习班级管理系统一
创建项目 user_manager 和 app为 app01 models.py 为 from django.db import models # Create your models here. c ...
- Nginx01(Nginx简介)
一:序言 Nginx是lgor Sysoev为俄罗斯访问量第二的rambler.ru站点设计开发的.从2004年发布至今,凭借开源的力量,已经接近成熟与完善. 二:Nginx常用功能 1.Http代理 ...
- Python环境安装与基础语法(2)——数据类型、标识符、语言分类
高级语言发展 结构化语言:以顺序(步骤化),分支,循环描述问题 面向对象语言:接近人类的认知,万物抽象成对象,对象间的关系抽象成类和继承 程序=数据结果+算法 Python解释器 CPython:由C ...
- DVWA之CSRF
CSRF:跨站请求伪造攻击 Security:Low 级别分析 核心代码 输入数据,以便Burp代理获得请求参数 这里可以将第一行拿出来进行构造链接, http://202.100.10 ...
- 201871010105-曹玉中《面向对象程序设计(java)》第七周学习总结
201871010105-曹玉中<面向对象程序设计(java)>第七周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这 ...
- 获取 Docker 容器的 IP 地址
docker inspect --format='{{.NetworkSettings.IPAddress}}' yourContainerIdOrContainerName
- JDOJ 1958 机器分配
JDOJ 1958: 机器分配 Description 某总公司拥有高效生产设备M台,准备分给下属的N个分公司.各分公司若获得这些设备,可以为总公司提供一定的盈利.问:如何分配这M台设备才能使国家得到 ...
- USACO Cow Cars
洛谷 P2909 [USACO08OPEN]牛的车Cow Cars https://www.luogu.org/problemnew/show/P2909 JDOJ 2584: USACO 2008 ...
- linux的cpu使用率
linux 上一个核占满是 100%,双核机器占满整个 CPU 是 200%