解决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等请求的问 ...
随机推荐
- Leetcode数组-二分法
Leetcode数组-二分法 二分法学习地址 二分法 704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 targe ...
- C# 利用Autofac批量接口注入依赖【学习记录】
背景: 本人在一位大佬的Colder框架中看到了这个接口注入,然后呢就想学习一下ioc思想与di设计模式.此写法给我的感觉就是 非常的 优雅 ,优雅永不过时.关于接口注入的概念和ioc和di具体是什么 ...
- 在 TypeScript 中,extends
extends 是一个关键字,用于指定类型参数的约束.它在类型参数的声明中使用,以确保类型参数满足特定的条件. 具体来说,extends 后面可以跟随一个类型,表示类型参数必须是该类型的子类型.在泛型 ...
- DBEAVER 23.0.2 调整SQL编辑器字体大小 ver:20240112
DBEAVER 23.0.2 调整SQL编辑器字体大小 ver:20240112 版本是:23.0.2. 菜单-窗口-首选项.用户界面-外观-颜色和字体.展开 DBeaver Font."M ...
- (五)基于selenium实现12306模拟登陆
这里介绍一款强大验证码识别平台:超级鹰 - 超级鹰:http://www.chaojiying.com/about.html - 注册:普通用户 - 登录:普通用户 - 题分查询:充值 - 创建一个软 ...
- work12
day12 一.请简述集合框架. 集合是单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List和java.util.Set.其中,List的 ...
- 行为型模式(Behavioer Pattern)
行为型设计模式 行为型模式定义了系统中对象之间的交互与通信,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责,包括对系统中较为复杂的流程的控制. 在软件系统运行时对象并不是孤立存在的,它 ...
- Android 7.0 开机时间优化
原文参考(有删改):https://www.jianshu.com/p/6dba42c022a9 问题描述 开机时间相对参考机过慢,大约慢15s左右.Android 系统7.0. 问题分析 开机问题涉 ...
- 记一次难忘的json反序列化问题排查经历
前言 最近我在做知识星球中的商品秒杀系统,昨天遇到了一个诡异的json反序列化问题,感觉挺有意思的,现在拿出来跟大家一起分享一下,希望对你会有所帮助. 案发现场 我最近在做知识星球中的商品秒杀系统,写 ...
- python基础-入门必备知识
1 标识符 标识符是编程时使用的名字,用于给变量.函数.语句块等命名,Python 中标识符由字母.数字.下划线组成,不能以数字开头,区分大小写. 以下划线开头的标识符有特殊含义,单下划线开头的标识符 ...