在spring中使用RequestBodyAdvice 和 ResponseBodyAdvice增强器
前言
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 实现步骤
- 编写一个实现类实现
RequestBodyAdvice
接口 - 分别实现对应的方法
- 实现类上添加注解标记:
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 实现步骤
- 编写一个实现类实现
ResponseBodyAdvice
接口 - 重写
supports
和boforeBodyWrite
- 实现类上添加注解标记:
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增强器相关推荐
- RequestBodyAdvice 和 ResponseBodyAdvice增强器使用
前言: 日常开发中,我们常常需要对@RequestBody的参数进行各种处理,例如加解密.打印日志,这些东西我们可以用到RequestBodyAdvice 和 ResponseBodyAdvice ...
- SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密(采用 RSA 算法 ),“船新版本”!
一.前言: 数据是企业的第四张名片,企业级开发中少不了数据的加密传输.为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余 ...
- 动态代理以及对应Spring中AOP源码分析
AOP(面向切面编程)在Spring中是被广泛应用的(例如日志,事务,权限等),而它的基本原理便是动态代理. 我们知道动态代理有两种:基于JDK的动态代理以及基于CGlib动态代理.以下是两种动态代理 ...
- Spring中配置DataSource数据源的几种选择
Spring中配置DataSource数据源的几种选择 在Spring框架中有如下3种获得DataSource对象的方法: 从JNDI获得DataSource. 从第三方的连接池获得DataSourc ...
- 详解设计模式在Spring中的应用
设计模式作为工作学习中的枕边书,却时常处于勤说不用的尴尬境地,也不是我们时常忘记,只是一直没有记忆. 今天,螃蟹在IT学习者网站就设计模式的内在价值做一番探讨,并以spring为例进行讲解,只有领略了 ...
- Spring中利用applicationContext.xml文件实例化对象和调用方法
Spring中实例化对象和调用方法入门 1.jar包和xml的准备 已上传至百度云盘,链接: https://pan.baidu.com/s/1CY0xQq3GLK06iX7tVLnp3Q 提取码: ...
- 【spring 5】AOP:spring中对于AOP的的实现
在前两篇博客中,介绍了AOP实现的基础:静态代理和动态代理,这篇博客介绍spring中AOP的实现. 一.采用Annotation方式 首先引入jar包:aspectjrt.jar && ...
- Spring中WebApplicationContext
ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用"容器"来表述它更容易理解一 些,ApplicationContext则是& ...
- Spring中使用Schedule调度
在spring中两种办法使用调度,以下使用是在spring4.0中. 一.基于application配置文件,配置入下: 1 <bean id="jobDetail" cla ...
最新文章
- 从上往下 流式布局_揭秘做好网站结构优化的4步(下)
- jmeter 非gui 模式跑jmx
- 高质量JAVA代码编写规范
- 后台备份20080917
- 请问基友,基金转换需要多长时间?
- 境外自助游服务平台澳乐网获戈壁千万级投资
- java 打牌游戏_java代码-----实现4个人打牌游戏的相关代码。线程
- 用户故事与敏捷方法—发布计划
- 操作系统原理课程 期末考试复习重点
- ❤️肝下25万字的《决战Linux到精通》笔记,你的Linux水平将从入门到入魔❤️【建议收藏】
- 终于发现路由器里的广告秘密
- 数据结构如何申请一个空间的队列_如何用鞋柜来作为隔断,隔出一个玄关空间...
- 视频监控录像机默认端口 34567 修改为37420
- 屏幕旋转后字体会变大问题
- 网络安全绝地求生-面试题
- 字母异位词分组-LeetCode49
- 2022年湖南省中医执业医师考试第二单元中医诊断学(一)
- android sdk 环境签名,SDK接入必备常识——keystore签名文件详解
- H+ Se7en WebUI
- 【附源码例】快捷指令实现调出iOS隐藏应用程序-原理解析