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 = ...
随机推荐
- vue路由跳转传参的两种方法
路由跳转: this.$router.push({ name: '工单列表', params: {p_camera_dev_name: 'xxx'} }); 使二级菜单呈点击状态: $('[index ...
- 【IDE_IntelliJ IDEA】idea主题设置
参考博文: IDEA 炫酷的主题字体颜色设置 idea主题下载
- re 模块详解
1.re 模块 regex 1.查找 :findall 意思"匹配所有,每一项都是列表的组成元素" 有返回值 import re ret=re.findall("\d+& ...
- RST复位报文
复位报文段: 一些特殊情况,TCP一端向另一端发送复位报文,以通知对方关闭链接或者重新建立链接. 产生复位报文的三种情况: 1. 当客户端访问一个不存在的端口时,目标主机会给客户端发送一个复位报文段. ...
- Window包管理工具scoop
自定义安装路径安装 scoop安装应用路径 目标目录是D:\Program Files\Scoop,在PowerShell命令控制台中运行: [environment]::setEnvironment ...
- springboot配置spring security 静态资源不能访问
在springboot整合spring security 过程中曾遇到下面问题:(spring boot 2.0以上版本 spring security 5.x (spring secur ...
- 微信小程序音频背景播放
由于微信小程序官方将音频的样式固定死了,往往再工作中和UI设计师设计出来的样式不符,故一般都采用背景音频播放来实现自定义的UI样式的音频播放,即使用官网API提供的BackgroundAudioMan ...
- ASP.NET开发实战——(四)ASP.NET MVC是如何运行的?它的生命周期是什么?
前面的文章我们使用ASP.NET MVC创建了个博客应用,那么它是如何工作的呢?我们都知道ASP.NET的程序需要部署到IIS上才能够通过浏览器来访问,那么IIS与ASP.NET MVC程序之间又是如 ...
- js数组检测
数组检测 检测constructor v.constructor === Array 缺点: let arr = [] console.log(arr.constructor === Array); ...
- Codeforces Round #576 (Div. 1)
Preface 闲来无事打打CF,就近找了场Div1打打 这场感觉偏简单,比赛时艹穿的人都不少,也没有3000+的题 两三个小时就搞完了吧(F用随机水过去了) A. MP3 题意不好理解,没用翻译看了 ...