RequestBodyAdviceAdapter 在重构中的实战应用与注意事项

前言

哈喽,大家好,今天和大家分享在重构过程中,使用RequestBodyAdviceAdapter来解决实际业务场景的一些心得,本章主要包括以下几个问题:

  1. 在什么场景下使用 RequestBodyAdviceAdapter
  2. 适用的场景
  3. 使用方式
  4. 使用中可能遇到的一些坑
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的一些使用场景。

相关推荐
BD_Marathon2 小时前
【Flink】部署模式
java·数据库·flink
鼠鼠我捏,要死了捏5 小时前
深入解析Java NIO多路复用原理与性能优化实践指南
java·性能优化·nio
ningqw5 小时前
SpringBoot 常用跨域处理方案
java·后端·springboot
你的人类朋友5 小时前
vi编辑器命令常用操作整理(持续更新)
后端
superlls5 小时前
(Redis)主从哨兵模式与集群模式
java·开发语言·redis
胡gh5 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
一只叫煤球的猫6 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
uzong6 小时前
技术人如何对客做好沟通(上篇)
后端
叫我阿柒啊7 小时前
Java全栈工程师面试实战:从基础到微服务的深度解析
java·redis·微服务·node.js·vue3·全栈开发·电商平台
颜如玉7 小时前
Redis scan高位进位加法机制浅析
redis·后端·开源