解决SpringMVC/SpringBoot @RequestBody无法注入基本数据类型
我们都知道SpringMVC使用 @RequestBody 注解可以接收请求content-type 为 application/json 格式的消息体。但是我们必须使用实体对象,Map或者直接用String类型去接收数据。
否则SpringMVC会直接把整个json字符串注入到参数中,此时用String类型的参数是可以接收的,但是用Integer,Long等其他类型会报JSON转换异常。
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.Integer`
所以当我们只需要一个参数的请求时,要么不适用JSON格式,要么写一个实体对象,要么用map。因为项目一般会统一消息体,所以导致接收JSON参数非常麻烦。不过好在SpringMVC提供了HandlerMethodArgumentResolver这个让我们自定义参数解析器的接口。只要我们实现该接口,并添加到spring容器中即可。
不足的地方:因为 HandlerMethodArgumentResolver 解析的参数是一个一个来的,而且因为httpServletRequest中的流只能读取一次,所以现在只能在一个参数的方法中使用,如果需要支持多个参数,需要对流进行处理。
准备工作
定义一个注解,用于标识接口参数和附带信息
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonBasicParam { /**
* 字段名
*/
String name() default ""; /**
* 是否必传
*/
boolean required() default true; /**
* 参数为空提示信息
*/
String message() default "参数不能为空"; /**
* 默认值
*/
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
实现逻辑
定义一个类实现 HandlerMethodArgumentResolver接口
注意事项:@Slf4j 是lombok的注解,相当于在类里面加入private final Logger logger = LoggerFactory.getLogger(当前类名.class);个人习惯用这种方法输出日志。使用的是阿里的fastjson解析json字符串,所以需要导入fastjson包,或者自己用其他json框架也行。
@Slf4j
public class JsonParamProvider implements HandlerMethodArgumentResolver { /**
* 请求body格式
*/
private static final String CONTENT_TYPE = "application/json"; /**
* 判断是否是需要我们解析的参数类型
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(JsonBasicParam.class);
} /**
* 真正解析的方法
*/
@Override
public Object resolveArgument(@NonNull MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
return null;
}
String contentType = request.getContentType();
if (StringUtils.isEmpty(contentType) || !contentType.toLowerCase().contains(CONTENT_TYPE)) {
throw new BaseException("只支持content-type为application/json的请求");
}
JSONObject jsonObject;
try (BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(),
StandardCharsets.UTF_8))) {
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
jsonObject = JSON.parseObject(responseStrBuilder.toString());
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BaseException("json格式异常");
}
JsonBasicParam jsonParam = methodParameter.getParameterAnnotation(JsonBasicParam.class);
if (jsonParam == null) {
return null;
}
//获取参数名
String paramName = jsonParam.name();
//注解没有给定参数名字,默认取参数名称
if (StringUtils.isEmpty(paramName)) {
paramName = methodParameter.getParameter().getName();
}
String paramType = methodParameter.getParameter().getType().getSimpleName();
if (jsonObject != null && jsonObject.containsKey(paramName)) {
String data = String.valueOf(jsonObject.get(paramName));
if (jsonParam.required() && StringUtils.isEmpty(data)) {
throw new BaseException(jsonParam.message());
}
return initValue(paramType, data);
}
return null;
} /**
* 给基本类型参数注入值
*
* @param type 类型
* @param data 值
* @return java.lang.Object
*/
private Object initValue(String type, String data) {
try {
if ("int".equalsIgnoreCase(type) || "integer".equalsIgnoreCase(type)) {
return Integer.valueOf(data);
} else if ("double".equalsIgnoreCase(type)) {
return Double.valueOf(data);
} else if ("long".equalsIgnoreCase(type)) {
return Long.valueOf(data);
}
} catch (NumberFormatException e) {
log.error(e.getMessage(), e);
throw new BaseException("数据类型错误");
}
return data;
}
}
使用
默认参数名称,必传,不传则提示message里面的内容
@PostMapping("test1")
public String test1(@JsonBasicParam(message = "testId不能为空") Integer testId) {
return "";
}
name指定参数名称
@PostMapping("test1")
public String test1(@JsonBasicParam(name = "testId",message = "testId不能为空") Integer id) {
return "";
}
必传,不传则提示默认的不能为空
public String test1(@JsonBasicParam Integer id) {
return "";
}
可以不传
@PostMapping("test1")
public String test1(@JsonBasicParam(name = "testId",message = "testId不能为空",required = false) Integer testId) {
return "";
}
不传默认值为 1
public String test1(@JsonBasicParam(name = "testId",message = "testId不能为空",required = false,defaultValue = "1") Integer testId) {
return "";
}
解决SpringMVC/SpringBoot @RequestBody无法注入基本数据类型的更多相关文章
- 在Springmvc普通类@Autowired注入request为null解决方法
在Springmvc普通类@Autowired注入request为null解决方法 在类中加入以下注入request对象的代码,运行时发现request为null,注入失败.在@Controlle ...
- Spring SpringMVC SpringBoot SpringCloud 注解整理大全
Spring SpringMVC SpringBoot SpringCloud 注解整理 才开的博客所以放了一篇以前整理的文档,如果有需要添加修改的地方欢迎指正,我会修改的φ(๑˃∀˂๑)♪ Spri ...
- PHP开发中常见的安全问题详解和解决方法(如Sql注入、CSRF、Xss、CC等
页面导航: 首页 → 网络编程 → PHP编程 → php技巧 → 正文内容 PHP安全 PHP开发中常见的安全问题详解和解决方法(如Sql注入.CSRF.Xss.CC等) 作者: 字体:[增加 减小 ...
- 解决SpringMVC拦截器中Request数据只能读取一次的问题
解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...
- spring springMvc spring-boot spring-cloud分别是什么
本文来源于:克己习礼成仁 的<spring springMvc spring-boot spring-cloud分别是什么> 什么是spring 关于spring的定义无论是从官方还是 ...
- Ajax json交互和SpringMVC中@RequestBody
Ajax json交互和SpringMVC中@RequestBody 标签: 背景 自己提供出去得接口中参数设置为@RequestBody VipPromotionLog vipPromotionLo ...
- 开发者说:Sentinel 流控功能在 SpringMVC/SpringBoot 上的实践
从用户的视角来感受一个开源项目的成长,是我们推出「开发者说」专栏的初衷,即在开发者进行开源项目选型时,提供更为立体的项目信息.专栏所有内容均来自作者原创/投稿,本文是「开发者说」的第6篇,作者 Jas ...
- SpringMVC处理脚本,SQL注入问题
SpringMVC处理脚本,SQL注入问题(写的不好勿喷,互相学习) 使用 Filter 来过滤浏览器发出的请求,对每个URI参数请求过滤些关键字,替换成安全的字符.所有请求的 getParamete ...
- 正确理解springboot的常用注入方式
springboot的属性注入 以注入dataSource为例1.springboot默认读取的文件是放在resources目录下的名为application.properties或applicati ...
- 解决SpringMVC put,patch,delete请求数据拿不到的问题
解决SpringMVC put,patch,delete请求参数拿不到的问题 废话不多说,核心代码如下: 在web.xml中添加如下代码 <!-- 解决web端不能put,delete等请求的问 ...
随机推荐
- MQTT服务器连接不上的问题
问题描述 环境:阿里云服务器Ubuntu 22.04.3 LTS,安装mosquitto后,在虚拟机端订阅消息出现报错(以前用阿里云Ubuntu20.04 LTS的服务器装上就能用),以下服务器ip是 ...
- c#WinFrom自定义图表仪表控件-频谱
这是为客户定制的一个频谱图表控件,先看下成品效果,gif较大,略等片刻 开发步骤分析: 1.界面有多个间距不等的线分割的区域,每个区域的值范围不同,我们就需要把每个区域定义出来,方便我们操作的时候来计 ...
- vue3 KeepAlive
在Vue.js 3中,<keep-alive> 是一个抽象组件,用于保留其子组件状态,防止在切换组件时销毁它们.这对于在页面间切换时保留组件的状态或避免重复渲染特定组件非常有用.<k ...
- 讯飞有一个可以根据描述文本自动生成PPT的AI接口,有趣
文档:https://www.xfyun.cn/doc/spark/PPTGeneration.html 价格方面提供了免费1000点的额度,生成一次是10点,正好100次,如果要购买的话最低要购买1 ...
- SDL3 入门(2):第一个窗口
在上一篇文章中我们已经利用 SDL 的日志接口实现了简单的字符串输出,实际上是解决了开发环境搭建问题,接下来我们将在已有代码的基础上继续开发,实现第一个窗口的创建和背景色绘制. 初始化 首先设置日志输 ...
- Centos7部署FytSoa项目至Docker——第二步:安装Mysql、Redis
FytSoa项目地址:https://gitee.com/feiyit/FytSoaCms 部署完成地址:http://82.156.127.60:8001/ 先到腾讯云申请一年的云服务器,我买的是一 ...
- 泛型模板化设计DEMO
泛型模板化设计DEMO 1. 定义Result泛型类 package com.example.core.mydemo.java.fanxing; public class Result<T> ...
- Python str 转 b’二进制串
用raw_unique_escape来编码无义意的二进制串 bytes(strtext, encoding='raw_unique_escape')
- .NET 中使用RabbitMQ初体验
在.NET Core中使用RabbitMQ 前言 逛园子的时候看到一篇.NET 学习RabbitMq的文章(视频地址和文章地址放在文章底部了),写的不错,我也来实现一下. 我是把RabbitMQ放在服 ...
- P9196 题解
来一份线性时间的题解. 考虑先解决前缀限制,显然可以直接把字符串和询问全部搬到 Trie 树上,问题就变成了查询一个子树内满足后缀限制的字符串数量. 接着考虑 Trie 树合并,具体地,把后缀限制以及 ...