当前位置: 首页 > news >正文

手写一个类似@RequestParam的注解(用来接收请求体的参数)

一、本文解决的痛点

按照大众认为的开发规范,一般post类型的请求参数应该传在请求body里面。但是我们有些post接口只需要传入一个字段,我们接受这种参数就得像下面这样单独创建一个类,类中再添加要传入的基本类型字段,配合@RequestBody来实现这种功能多少有点繁琐:

@Data
public class TextHolder {private String text;
}@PostMapping("test")
public ApiResponse test(@RequestBody TextHolder textHolder){....
}

那么我们能不能省略类的创建,实现一个类似@RequestParam的注解来实现请求体参数的直接接收呢?本文就是来解决这个问题的!

二、实现步骤

2.1定义我们这个增强版的请求体注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestBodyPlus {String value() default "";
}

2.2手写一个方法参数解析器

@Slf4j
public class RequestBodyPlusMethodHandler implements HandlerMethodArgumentResolver {public static final ThreadLocal<Map<String,Object>> requestBodyMap = new ThreadLocal<>();@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {return methodParameter.hasParameterAnnotation(RequestBodyPlus.class);}@Overridepublic Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {HttpServletRequest request  = nativeWebRequest.getNativeRequest(HttpServletRequest.class);RequestBodyPlus parameterAnnotation = methodParameter.getParameterAnnotation(RequestBodyPlus.class);String paramName = parameterAnnotation.value();if (StringUtils.isEmpty(paramName)) {paramName = methodParameter.getParameterName();}Map<String, Object> paramsMap = new HashMap<>();if (requestBodyMap.get() == null) {String requestBodyString = getRequestBodyString(request);paramsMap = JSON.parseObject(requestBodyString);// 需要把请求体Map放入ThreadLocal中,因为request中的inputStream读完一次,下次就读不了了,这也是原生的@RequestBody只能在方法参数中出现一次的原因!requestBodyMap.set(paramsMap);}else {paramsMap = requestBodyMap.get();}Object paramValue = paramsMap.get(paramName);// 有的参数需要databinder处理if (paramValue != null && webDataBinderFactory != null) {WebDataBinder binder = webDataBinderFactory.createBinder(nativeWebRequest, paramValue, paramName);paramValue = binder.convertIfNecessary(paramValue, methodParameter.getParameterType(), methodParameter);}return paramValue;}private String getRequestBodyString(final ServletRequest request){StringBuilder stringBuilder = new StringBuilder();try(InputStream inputStream = request.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));){String line = "";while((line = bufferedReader.readLine()) != null){stringBuilder.append(line);}}catch (IOException e){log.error("request的ServletInputStream转换失败",e);}finally {return stringBuilder.toString();}}}

2.3需要写一个拦截器,用来remove上面的threadLocal避免内存泄漏

public class RequestBodyPlusInterceptor implements HandlerInterceptor {@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {RequestBodyPlusMethodHandler.requestBodyMap.remove();}}

2.4需要把拦截器和参数解析器配置好才能生效

@Configuration
public class WebConfigurer extends WebMvcConfigurerAdapter {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(new RequestBodyPlusMethodHandler());}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new RequestBodyPlusInterceptor());super.addInterceptors(registry);}
}

三、使用案例

3.1测试代码:

@RestController
@RequestMapping("plus")
public class TestRequestBodyPlus {@PostMapping("one")public String test01(@RequestBodyPlus("dx") Integer dx, @RequestBodyPlus("ls") String ls, @RequestBodyPlus("jk") Date jk, @RequestBodyPlus("el") List<Long> el) {System.out.println(el);String format = "%d_______%s_______%s_________%d";return String.format(format, dx, ls, jk, el.size());}//有了下面这个方法,上面的接口的入参数就能传`2023-11-24`这种字符串@InitBinder //该注解底层的源码:RequestMappingHandlerAdapter#invokeHandlerMethodpublic void initBinder(WebDataBinder binder){SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");dateFormat.setLenient(false);binder.registerCustomEditor(Date.class,new CustomDateEditor(dateFormat,false));}//该方法的作用域仅在当前的controller,如果想全局生效,需要写在@ControllerAdvice所在的类中
}

3.2测试请求

POST localhost:8088/plus/one
{"dx" : 3,"ls" : "bbb","jk" : "2023-11-24","el" : [1,2,3,4,5]
}

3.3测试结果

在这里插入图片描述

相关文章:

  • FlinkCDC 数据同步优化及常见问题排查
  • GIT 基于master分支创建hotfix分支的操作
  • if __name__ == “__main__“
  • 五款免费可视化利器分享,助力打造数字孪生新体验!
  • redis未授权访问漏洞复现
  • K8S中的某个容器突然出现内存和CPU占用过高的情况解决办法
  • 76. 最小覆盖子串(困难)
  • 在线教育平台,easyexcel使用案例
  • WebSocket 心跳机制如何实现
  • 昇思25天学习打卡营第6天|数据变换 Transforms
  • Redis分布式锁的应用场景有哪些
  • 【多维动态规划】Leetcode 221. 最大正方形【中等】
  • AI 会淘汰程序员吗?
  • MySQL之如何处理超大分页
  • Qt绘制多线段
  • hadoop集群管理系统搭建规划说明
  • HTTP 简介
  • JavaScript 基本功--面试宝典
  • Linux Process Manage
  • Markdown 语法简单说明
  • Meteor的表单提交:Form
  • PHP面试之三:MySQL数据库
  • Python打包系统简单入门
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • supervisor 永不挂掉的进程 安装以及使用
  • Vue2.0 实现互斥
  • 电商搜索引擎的架构设计和性能优化
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 关于extract.autodesk.io的一些说明
  • 基于HAProxy的高性能缓存服务器nuster
  • 蓝海存储开关机注意事项总结
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 事件委托的小应用
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 写代码的正确姿势
  • 用mpvue开发微信小程序
  • 06-01 点餐小程序前台界面搭建
  • 《码出高效》学习笔记与书中错误记录
  • # Maven错误Error executing Maven
  • #QT(QCharts绘制曲线)
  • (003)SlickEdit Unity的补全
  • (2.2w字)前端单元测试之Jest详解篇
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (附源码)计算机毕业设计高校学生选课系统
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (学习日记)2024.01.19
  • * 论文笔记 【Wide Deep Learning for Recommender Systems】
  • .NET : 在VS2008中计算代码度量值
  • .NET Core 版本不支持的问题
  • .net 发送邮件
  • .NET 中各种混淆(Obfuscation)的含义、原理、实际效果和不同级别的差异(使用 SmartAssembly)