前言

Advice 直译为通知,也有人翻译为 “增强处理”,不过一般的增强器是带有Advisor的类。

前言:
  日常开发中,我们常常需要对@RequestBody的参数进行各种处理,例如加解密、打印日志,这些东西我们可以用到RequestBodyAdvice 和 ResponseBodyAdvice来对请求前后进行处理,本质上他俩都是AOP拦截器。
  
  RequestBodyAdvice 和 ResponseBodyAdvice都需要搭配@RestControllerAdvice@ControllerAdvice使用。

1、RequestBodyAdvice

1.1 使用场景

在实际项目中 , 往往需要对请求参数做一些统一的操作 , 例如参数的过滤 , 字符的编码 , 第三方的解密等等 , Spring提供了RequestBodyAdvice一个全局的解决方案 , 免去了我们在Controller处理的繁琐。

而且在前后端分离的项目中,前端与后台一般会约定好固定的参数和响应的数据结构,一般传统的做法是在 Controller层方法直接接收ApiRequest参数和直接返回ApiResult的实例:

在参数中传入ApiRequest对象,然后手动获取业务参数data进行处理
每个接口手动生成ApiResult对象并返回。

这一部分工作其实是重复也无太多意义的,那么有没有一种方法可以自动做到 我们只关注 ApiRequest.data和 ApiResult.data,让程序自动将参数传入到 业务参数data中和 控制层方法只返回 data,程序自动封装成ApiResult并返回呢?那么今天的主人公 RequestBodyAdvice,ResponseBodyAdvice就登场了。

1.2 作用范围

RequestBodyAdvice仅对使用了@RqestBody注解的生效 , 因为它原理上还是AOP , 所以GET方法是不会操作的。

RequestBodyAdvice是SpringMVC4.2提供的一个接口,它允许请求体被读取并转换为对象,并将处理结果对象作为@RequestBody参数或者 @HttpEntity方法参数。由此可见,它的作用范围为:

  • 使用@RequestBody进行标记的参数
  • 参数为HttpEntity

1.3 提供的方法

该方法返回true时,才会进去下面的系列方法

// 是否开启拦截 true开启 false不开启
boolean supports(MethodParameter methodParameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType);
}

body数据读取之前调用,一般在此方法中对body数据进行修改

 //请求体解析前处理
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;

body读取之后操作,一般直接返回原实例

//请求体解析后处理
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

当body为empty时操作

//处理没有参数
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

1.4 实现步骤

  1. 编写一个实现类实现RequestBodyAdvice接口
  2. 分别实现对应的方法
  3. 实现类上添加注解标记:ControllerAdvice

1.5 实例

Controller控制器

@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {@RequestMapping("/query")public String queryById(@RequestBody User user) {log.info("请求参数=={}", user.toString());log.info("响应参数=={}", "id的h1样式");return "<h1>" + user.toString() + "<h1>";}
}

创建一个拦截类,实现RequestBodyAdvice接口

@Slf4j
@ControllerAdvice
public class RequestInterceptor implements RequestBodyAdvice {// 是否开启拦截 true开启 false不开启@Overridepublic boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {// 方法上是否有RequestAdvice注解RequestAdvice requestAdvice = methodParameter.getMethodAnnotation(RequestAdvice.class);if (requestAdvice == null) {// 类上是否有RequestAdvice注解requestAdvice = methodParameter.getDeclaringClass().getAnnotation(RequestAdvice.class);}return requestAdvice != null;}//请求体解析前处理@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {// 常见的业务仓场景有: 1 记录日志 2 内容加密解密 3 是否开启分页功能log.info("拦截到的请求参数为 = {}",methodParameter.toString());Method method=methodParameter.getMethod();log.info("请求体读取前={}==>{}==>{}==>{}",method.getDeclaringClass().getSimpleName(),method.getName(),type.toString(),aClass.getName());//return new XHttpInputMessage(httpInputMessage, "UTF-8");// 设置utf8编码String bodyStr = IOUtils.toString(httpInputMessage.getBody(), StandardCharsets.UTF_8);return new HttpInputMessage() {@Overridepublic InputStream getBody() throws IOException {ApiRequest<Object> request = JsonUtils.json2Obj(bodyStr, new TypeReference<ApiRequest<Object>>() {});String body = bodyStr;if (request != null && request.getData() != null) {body = JsonUtils.obj2Json(request.getData());}return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));}@Overridepublic HttpHeaders getHeaders() {return httpInputMessage.getHeaders();}};}//请求体解析后处理@Overridepublic Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {Method method=methodParameter.getMethod();log.info("请求体读取后={}.{}:{}",method.getDeclaringClass().getSimpleName(),method.getName(),o.toString());return body;}//处理没有参数@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {Method method=methodParameter.getMethod();log.info("没有参数={}.{}",method.getDeclaringClass().getSimpleName(),method.getName());return body;}
}

上面实例中,supports添加了支持条件:当控制层方法或者类上有标记注解 @RequestAdvice注解时,才会进入其他相关方法。当不需要任何限制时,supports直接返回true即可。

2、ResponseBodyAdvice

2.1 使用场景

ResponseBodyAdvice可以在注解@ResponseBody将返回值处理成相应格式之前操作返回值。实现这个接口即可完成相应操作。可用于对response 数据的一些统一封装或者加密等操作。

2.2 作用范围

ResponseBodyAdvice是SpringMVC4.1提供的一个接口,它允许在 执行 @ResponseBody后自定义返回数据,或者将返回@ResponseEntity的 Controller Method在写入主体前使用HttpMessageConverter进行自定义操作。由此可见,它的作用范围为:

  • 使用@ResponseBody注解进行标记
  • 返回@ResponseEntity

2.3 提供的方法

该方法返回true时,才会进去下面的 beforeBodyWrite方法。该方法可以添加一些判断条件,比如 方法上有 xxx 注解的才会生效等等。

boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

body写入前的操作。

@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response);

2.4 实现步骤

  1. 编写一个实现类实现ResponseBodyAdvice接口
  2. 重写supportsboforeBodyWrite
  3. 实现类上添加注解标记:ControllerAdvice

2.5 实例

添加一个响应拦截类

@RestControllerAdvice
public class BaseResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {ResponseAdvice responseAdvice = returnType.getMethodAnnotation(ResponseAdvice.class);if (responseAdvice == null) {responseAdvice = returnType.getDeclaringClass().getAnnotation(ResponseAdvice.class);}return responseAdvice != null;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {// 遇到feign接口之类的请求, 不应该再次包装,应该直接返回// 上述问题的解决方案: 可以在feign拦截器中,给feign请求头中添加一个标识字段, 表示是feign请求// 在此处拦截到feign标识字段, 则直接放行 返回body.System.out.println("响应拦截成功");//如果直接响应字符串返回,则会报类型转换异常.if(body instanceof String){return JSONUtil.toJsonStr(BaseResponse.ok(o));}if (body instanceof BaseResponse) {return body;} else if (body == null) {return BaseResponse.ok();} else {return BaseResponse.ok(body);}}
}

上面实例中,supports添加了支持条件:当控制层方法或者类上有标记注解@ResponseAdvice注解时,才会进入beforeBodyWrite方法。当不需要任何限制时,supports直接返回true即可。

添加一个返回包装类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseResponse<T> {private T data;private int status = 200;private String message;private long srvTime = System.currentTimeMillis();public BaseResponse(String message) {this.message = message;}public BaseResponse<T> setData(T data) {this.data = data;return this;}public static <T> BaseResponse<T> ok() {return new BaseResponse<>("操作成功");}public static <T> BaseResponse<T> ok(T data) {return new BaseResponse<T>("操作成功").setData(data);}}

添加控制类

@ResponseAdvice
@RestController
@RequestMapping("/hello")
public class HelloWorld {// 此处数据从数据库中查询, 案例中也可以使用伪数据代替@Autowiredprivate UserMapper userMapper;// {@code ResponseEntity} 案列@GetMapping("/one")@ResponseBodypublic User one() {List<User> users = userMapper.selectAll();System.out.println(users.get(0));return users.get(0);}// {@code @ResponseBody}  案列@GetMapping("/list")@ResponseBodypublic List<User> list() {List<User> users = userMapper.selectAll();System.out.println(users);return users;}
}  

注意:如果直接响应字符串返回,则会报类型转换异常.所以会在拦截器中进行转换。

其他

上述只是针对 公共参数、公共返回这一种情况对 RequestBodyAdvice、ResponseBodyAdvice进行了说明,当然这两种接口不止这一种应用场景,比如对参数或者返回进行加解密都可以使用这两种接口进行实现。具体使用场景用户可根据实际情况进行使用。

参考

关于Spring中ResponseBodyAdvice的使用
使用RequestBodyAdvice、ResponseBodyAdvice优化程序请求与响应

在spring中使用RequestBodyAdvice 和 ResponseBodyAdvice增强器相关推荐

  1. RequestBodyAdvice 和 ResponseBodyAdvice增强器使用

    前言:   日常开发中,我们常常需要对@RequestBody的参数进行各种处理,例如加解密.打印日志,这些东西我们可以用到RequestBodyAdvice 和 ResponseBodyAdvice ...

  2. SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密(采用 RSA 算法 ),“船新版本”!

    一.前言: 数据是企业的第四张名片,企业级开发中少不了数据的加密传输.为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余 ...

  3. 动态代理以及对应Spring中AOP源码分析

    AOP(面向切面编程)在Spring中是被广泛应用的(例如日志,事务,权限等),而它的基本原理便是动态代理. 我们知道动态代理有两种:基于JDK的动态代理以及基于CGlib动态代理.以下是两种动态代理 ...

  4. Spring中配置DataSource数据源的几种选择

    Spring中配置DataSource数据源的几种选择 在Spring框架中有如下3种获得DataSource对象的方法: 从JNDI获得DataSource. 从第三方的连接池获得DataSourc ...

  5. 详解设计模式在Spring中的应用

    设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆. 今天,螃蟹在IT学习者网站就设计模式的内在价值做一番探讨,并以spring为例进行讲解,只有领略了 ...

  6. Spring中利用applicationContext.xml文件实例化对象和调用方法

    Spring中实例化对象和调用方法入门 1.jar包和xml的准备 已上传至百度云盘,链接: https://pan.baidu.com/s/1CY0xQq3GLK06iX7tVLnp3Q 提取码: ...

  7. 【spring 5】AOP:spring中对于AOP的的实现

    在前两篇博客中,介绍了AOP实现的基础:静态代理和动态代理,这篇博客介绍spring中AOP的实现. 一.采用Annotation方式 首先引入jar包:aspectjrt.jar && ...

  8. Spring中WebApplicationContext

    ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用"容器"来表述它更容易理解一 些,ApplicationContext则是& ...

  9. Spring中使用Schedule调度

    在spring中两种办法使用调度,以下使用是在spring4.0中. 一.基于application配置文件,配置入下: 1 <bean id="jobDetail" cla ...

最新文章

  1. 从上往下 流式布局_揭秘做好网站结构优化的4步(下)
  2. jmeter 非gui 模式跑jmx
  3. 高质量JAVA代码编写规范
  4. 后台备份20080917
  5. 请问基友,基金转换需要多长时间?
  6. 境外自助游服务平台澳乐网获戈壁千万级投资
  7. java 打牌游戏_java代码-----实现4个人打牌游戏的相关代码。线程
  8. 用户故事与敏捷方法—发布计划
  9. 操作系统原理课程 期末考试复习重点
  10. ❤️肝下25万字的《决战Linux到精通》笔记,你的Linux水平将从入门到入魔❤️【建议收藏】
  11. 终于发现路由器里的广告秘密
  12. 数据结构如何申请一个空间的队列_如何用鞋柜来作为隔断,隔出一个玄关空间...
  13. 视频监控录像机默认端口 34567 修改为37420
  14. 屏幕旋转后字体会变大问题
  15. 网络安全绝地求生-面试题
  16. 字母异位词分组-LeetCode49
  17. 2022年湖南省中医执业医师考试第二单元中医诊断学(一)
  18. android sdk 环境签名,SDK接入必备常识——keystore签名文件详解
  19. H+ Se7en WebUI
  20. 【附源码例】快捷指令实现调出iOS隐藏应用程序-原理解析

热门文章

  1. 用数据分析的指标拆解思路如何从股市了解市场
  2. 教妹学Java(四):Hello World
  3. 【项目】手把手带你用 SpringBoot、Uniapp、MySql 开发一个简单的活动报名项目
  4. 世说新言---谈机甲题材游戏(转)
  5. JVM(三)GC垃圾回收以及四种GC算法
  6. Java利用中国网建SMS短信通平台发送手机短信
  7. 编辑器组件PDF-XChange Editor更新了,增加新的语言
  8. Android之底部弹框
  9. 聪明人的几个思维方式
  10. 如何让centos7虚拟机联网