前言
哈喽,大家好,今天和大家分享在重构过程中,使用RequestBodyAdviceAdapter来解决实际业务场景的一些心得,本章主要包括以下几个问题:
- 在什么场景下使用
RequestBodyAdviceAdapter
- 适用的场景
- 使用方式
- 使用中可能遇到的一些坑
1.在什么场景下使用自定义注解
在重构过程中遇到这样一种场景,用户访问管理平台的菜单列表,需要根据当前登录用户,查询到该用户所属机构的数据,比如用户张三属于A机构,那张三的列表中只能显示A机构的数据,为了防止请求数据被恶意篡改,前端是没有将机构编码传入到后台接口中,需要后台开发人员从Shiro中取出该用户的机构信息,并将机构编码作为查询条件执行后续的sql语句。
代码如下:
less
@PostMapping("list")
public Result seletList(@RequestBody QueryList vo) {
JwtObject jwtObject = (JwtObject) SecurityUtils.getSubject().getPrincipal();
String hospitalId = jwtObject.getHospitalId();
vo.setHospitalId(hospitalId);
return Result.success(service.list(vo));
}
上面这种方式可以解决问题,但是我相信应该没有人会这么写,毕竟几百个方法,总不能每个方法都写一遍吧。
除此之外,我们可以使用RequestBodyAdviceAdapter
来解决,它是 Spring MVC 中的一个组件,主要是用于拦截和处理 被@RequestBody
注解修饰的方法,就比如上面这个post方法 。
2.RequestBodyAdviceAdapter
是什么
其实这个知识点大家基本都是知道的,但是这里还是要简单的介绍一下,RequestBodyAdviceAdapter
是一个抽象类, 它的主要作用是在请求体数据被实际读取并映射到相应对象之前(以上述的seletList方法为例,就是在进入这个方法之前,还没有对vo进行赋值),对请求体数据进行预处理或增强 。
简单的理解:就是在进入selectList方法之前,对前端或者第三方传过来的参数进行预处理,处理完成后才会正式进入接口中执行业务逻辑。我之前我也是经常用这个类来完成一些场景,比如:
- 请求体数据校验:我们可以拦截请求体数据,校验请求体是否为空,是否存在指定字段等。
- 请求体数据解密/加密:这个场景我也经常用到,之前提供接口给第三方调用时,我在这里做请求参数的数据解密。
- 请求体日志记录:记录请求体日志,方便后续排查问题。
- 请求入参预处理:这个就是本章中的举例场景,对所有的入参增加机构编码
3.代码示例
- 用法一:创建子类,继承
RequestBodyAdviceAdapter
下面这种方式方式可以实现对所有被@RequestBody修饰的方法进行处理,增加orgCode变量
java
@Slf4j
@ControllerAdvice
public class ChildAdviceAdapter extends RequestBodyAdviceAdapter {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
String httpBody = resolve(inputMessage,resolveHospitalId);
if (httpBody == null) {
log.error("参数处理失败");
throw new BusinessException("参数处理失败");
}
//返回处理后的消息体
return new CustomHttpMessage(new ByteArrayInputStream(httpBody.getBytes()),inputMessage.getHeaders());
}
/**
*处理入参
* @param inputMessage 消息体
* @return 明文
*/
private String resolve(HttpInputMessage inputMessage,ResolveHospitalId resolveHospitalId) throws IOException {
InputStream encryptStream = inputMessage.getBody();
String encryptBody = StreamUtils.copyToString(encryptStream, Charset.defaultCharset());
JwtObject jwtObject = (JwtObject) SecurityUtils.getSubject().getPrincipal();
String orgCode = jwtObject.getOrgCode();
JSONObject json = JSON.parseObject(encryptBody);
json.put("orgCode",hospitalId);
return json.toJSONString();
}
}
- 用法二:配合注解使用
如果有些方法不想走这个逻辑,可以通过增加自定义注解的方式进行排除,用法也很简单
第一步,定义一个注解
less
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OrgAnnotation {
String value() default "";
}
第二步,增加方法判断是否存在注解
typescript
/**
* 是否存在注解
*
* @param methodParameter methodParameter
* @return true/false
*/
private boolean supportRequest(MethodParameter methodParameter) {
Annotation methodAnnotation = methodParameter.getMethodAnnotation(OrgAnnotation.class);
if(methodAnnotation != null){
//如果业务需要,可以在注解上定义参数,处理更多的场景
String type = methodParameter.getMethod().getAnnotation(ResolveHospitalId.class).value();
if("3".equals(businessType)){
return false;
}
}
return true;
}
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return supportRequest(methodParameter);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
.....省略
}
使用注解
less
@OrgAnnotation("3")
@PostMapping("list")
public Result seletList(@RequestBody QueryList vo) {
JwtObject jwtObject = (JwtObject) SecurityUtils.getSubject().getPrincipal();
String hospitalId = jwtObject.getHospitalId();
vo.setHospitalId(hospitalId);
return Result.success(service.list(vo));
}
4.一些注意点
- 仅适用于POST、PUT、PATCH等请求方法:也就是前面说的只能处理带有请求体的请求方法
- 请求体数据只能读取一次 : 如果在
RequestBodyAdviceAdapter
中删除掉了请求体中的数据,那么后续的业务逻辑就无法再获取请求体数据了,因此在使用时需要注意不要消费掉原始的请求体数据。 - 处理顺序的问题 : 如果配置了多个
RequestBodyAdviceAdapter
实例,这些实例的处理顺序是无法保证的 - 线程安全问题: 可能会被多个线程同时调用,因此需要注意线程安全问题,防止出现并发问题。
最后
RequestBodyAdviceAdapter
的使用是比较简单的,之所以我还是写了这一篇文章,是因为我觉得如果使用得当,用起来还是蛮方便顺手的。本章对自定义注解的使用没有做过多的介绍,下一篇我会分享在重构过程中,使用自定义注解+Aop的一些使用场景。