手写一个类似@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<>();

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(RequestBodyPlus.class);
    }

    @Override
    public 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 {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestBodyPlusMethodHandler.requestBodyMap.remove();
    }

}

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

@Configuration
public class WebConfigurer extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new RequestBodyPlusMethodHandler());
    }


    @Override
    public 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#invokeHandlerMethod
    public 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测试结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/766134.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

清爽一夏,羊大师伴你健康运动,引领活力生活!

在这个绚烂多彩的夏日&#xff0c;让我们携手踏上一段清爽与健康并行的旅程。阳光炽热&#xff0c;万物生长&#xff0c;正是释放活力、追求健康的最佳时节。“清爽一夏&#xff0c;健康运动引领活力生活&#xff01;”这不仅是一句口号&#xff0c;更是我们向美好生活发出的诚…

模电-运放的供电

模电-运放的供电 Fang XS.1452512966qq.com如果有错误&#xff0c;希望被指出&#xff0c;学习技术的路难免会磕磕绊绊量的积累引起质的变化注&#xff1a;本文章为唐老师讲电赛视频的个人笔记 运放的供电 所有的运放都可以单电源和双电源供电&#xff1b;采用双电源供电的运…

Linux_fileio实现copy文件

参考韦东山老师教程&#xff1a;https://www.bilibili.com/video/BV1kk4y117Tu?p12 目录 1. 通过read方式copy文件2. 通过mmap映射方式copy文件 1. 通过read方式copy文件 copy文件代码&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <…

012-GeoGebra基础篇-构造圆的切线

前边文章对于基础内容已经悉数覆盖了&#xff0c;这一篇我就不放具体的细节&#xff0c;若有需要可以复刻一下 目录 一、成品展示二、算式内容三、正确性检查五、文章最后 一、成品展示 二、算式内容 A(0,0) B(3,0) c: Circle(A,B) C(5,4) sSegment(A,C) DMidpoint(s) d: Circ…

Java日期时间

java.util包提供Date类&#xff0c;该类有两个构造函数&#xff1a; Date()使用当前日期和时间初始化对象Date(long millisec)从1970年1月1日起的毫秒数 获取当前日期时间 import java.util.Date;public class DateDemo{public static void main(String[] args){//初始化Date…

开发笔记:vue3+ts+vant 卡片数据分页,下拉加载,卡片左滑可删除

效果&#xff1a; 实现 使用vantui组件 van-swipe-cell van-card &#xff08;商品卡片&#xff09; 核心代码 const currentPage ref(1) const pageSize ref(4) const totalSize ref(10) const loading ref(false) const finished ref(false) const refreshing ref(…

Java 微信小程序自建平台开发票保存到微信卡包

Java 微信小程序自建平台开发票保存到微信卡包 1 获取Access token2 获取自身的开票平台识别码3 设置商户联系方式4 获取授权页ticket5 获取授权页链接6 小程序打开授权页7 收取授权完成事件推送8 创建发票卡券模板9 上传PDF10 将电子发票卡券插入用户卡包 1 获取Access token …

shopify后台设置为中文

shopify官网&#xff1a;https://link.juejin.cn/?targethttps%3A%2F%2Fshopify.dev%2F 1、点击右上角头像 2、选择个人资料 3、找到Language设置 开发者 1、创建开发商店 2、 3、点击左侧导航在线商店&#xff0c;点击右上角查看你的商店。在线商店链接为https://[商店名…

RK3568驱动指南|第十五篇 I2C-第181章使用GPIO模拟I2C驱动

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

阿里云再次突发故障,高可用形同虚设?

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

轻松拯救手机数据,数据恢复软件推荐这8款!

在现代生活中&#xff0c;手机已成为我们不可或缺的工具&#xff0c;承载着大量重要的个人和工作数据。然而&#xff0c;意外删除、系统崩溃、设备损坏等情况可能导致数据丢失&#xff0c;给我们带来极大的困扰。幸运的是&#xff0c;随着科技的发展&#xff0c;各种手机数据恢…

文生图功能介绍

Stable Diffusion WebUI&#xff08;SD WebUI&#xff09;及文生图功能介绍 一、引言 随着人工智能技术的飞速发展&#xff0c;AI绘画作为一种新兴的艺术形式&#xff0c;逐渐走入人们的视野。Stable Diffusion WebUI&#xff08;简称SD WebUI&#xff09;作为AI绘画领域的重…

[附源码]最新springboot线上电商|前后端分离|界面简洁

一. 前言 今天小编给大家带来了一款可学习&#xff0c;可商用的&#xff0c;线上电商的网站源码&#xff0c;支持二开&#xff0c;无加密。代码的后端是SpringBoot技术栈&#xff08;非jsp&#xff09;&#xff0c;前端是Angular。如果您需要定制需求请找小编。 文章第六小节…

英灵神殿mac能玩吗 英灵神殿对电脑配置要求《英灵神殿》新手攻略查询 PD虚拟机能玩英灵神殿吗

近年来&#xff0c;随着《英灵神殿》&#xff08;Valheim&#xff09;游戏的火热&#xff0c;越来越多的玩家被其独特的北欧神话题材和丰富的生存挑战所吸引。然而&#xff0c;对于Mac用户来说&#xff0c;如何在Mac平台上运行这款游戏可能是一个问题。此外&#xff0c;作为一名…

编译原理3-自底向上的语法分析

自底向上分析 &#xff0c;就是自左至右扫描输入串&#xff0c;自底向上进 行分析&#xff1b;通过反复查找当前句型的 句柄&#xff0c; 并使 用产生式规则 将找到的句柄归约为相应的非终结符 。逐步进行“ 归约 ”&#xff0c;直到至文法的开始符号&#xff1b; 对于规范推导…

【unity实战】在Unity中使用有限状态机制作一个敌人AI

最终效果 文章目录 最终效果前言有限状态机的主要作用和意义素材下载逻辑图敌人动画配置优雅的代码文件目录状态机代码定义敌人不同状态切换创建敌人效果更多的敌人参考源码完结 前言 有限状态机以前的我嗤之以鼻&#xff0c;现在的我逐帧分析。其实之前我就了解过有限状态机&…

day03-主页模块-修改密码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.获取用户资料在Vuex中共享登录成功跳转到主页之后&#xff0c;可以获取用户资料&#xff0c;获取的资料在Vuex中共享&#xff0c;这样用户就可以很方便的获取该信…

Leetcode刷题笔记 | 二叉树基本性质 | 一天的题量 | 5道题目 | 深度优先搜索 | 广度优先搜索 | 递归 | 遍历

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本期毛毛张分享的是LeetCode关于二叉树&#x1f332;的性质的一些基础题&#xff0c;做这些题目的本质还是遍历二叉树&#x1f3c3;‍➡️的过程&#…

计算机组成原理 | 储存子系统(1)概述

三级储存体系 物理与虚拟存储器 &#xff08;抽象逻辑模型&#xff09; 存储器类型 存储器的速度指标

中国民间网络外交组织(CCND)

中国民间网络外交组织Chinese Civil Network Diplomacy简称(CCDN) 是由中国网民建立起来的一个网络外交组织&#xff0c;深度贯彻党的主张和网民意志的统一&#xff0c;为保护中国中华优秀传统文化&#xff0c;民族自信&#xff0c;国家安全&#xff0c;民族利益&#xff0c;社…