解决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 ""; }