内容简介:定义一个接收前端的实体类MyBeanVo开始以为Java中的可变参数Object...,在调用方法时,既可以传单个参数,又可以传多个参数,但是不能解决。因为可变参数实际上是Object[]数组传单个参数时报错: "exception":"org.springframework.http.converter.HttpMessageNotReadableException","message":"JSON parse error: Can not deserialize instance of com.tes
-
场景一:前后端对接接口,增删改查,一开始请求的参数,基本是单条数据,json格式基本是{"key":"value"},产品后续扩展,传参变成批量操作json格式为[xxx,xxx]或者[{"key":"value"}],此时后端修改原接口的接收对象为数组的话,前后端灰度发布,就会存在旧版本不兼容
-
场景二:产品的客户端,可能由web端,PC端,App端组成,例如当某个接口的参数结构改造为数组时,web端更新了,而App和PC端未更新,就存在不兼容其他端
解决思路
- 新增接口
-
优点:不影响旧接口,影响范围小
-
缺点:重复的代码,后期存在无用的接口
- 前后端一开始约定数组的请求参数
-
优点:比较根本解决问题
-
缺点:程序员的缺点,不是所有 程序员 都能预先判断接口参数的类型
- 绝大多数情况是遇到问题解决问题,思路是后端拦截处理接收的请求参数,校验正确后,统一将json数据封装为一个通用对象或者数组
- 优点:只需重构原先的接口即可兼容两种情况
- 缺点:需要自定义json的解析,解析不好会报json反序化失败
上代码
以下是尝试用三种方法解决以上场景的过程
定义一个接收前端的实体类MyBeanVo
package com.test.config; public class MyBeanVo { String value = ""; public String getValue() { return value; } public void setValue(String value) { this.value = value; } } 复制代码
可变参数(不能解决)
开始以为 Java 中的可变参数Object...,在调用方法时,既可以传单个参数,又可以传多个参数,但是不能解决。因为可变参数实际上是Object[]数组
@RestController public class MyController { @PostMapping("/hello") public String test(@RequestBody MyBeanVo... param) { MyBeanVo vo = param[0]; return vo.getValue(); } } 复制代码
传单个参数时报错: "exception":"org.springframework.http.converter.HttpMessageNotReadableException","message":"JSON parse error: Can not deserialize instance of com.test.config.MyBean[] out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.test.config.MyBean[] out of START_OBJECT token\n at [Source: java.io.PushbackInputStream@24e6f1b2; line: 1, column: 1]"
原因:前端的参数(单个数据)无法解析为MyBean[],而这涉及到了Json的反序列化
自定义反序列化
方案一
定义一个批量实体类
package com.test.config; import java.util.List; public class BatchVo<T> { List<T> list; public List<T> getList() { return list; } public void setList(List<T> list) { this.list = list; } } 复制代码
@JsonComponent注解会自动注入到spring中,反序列化BatchVo<MyBeanVo>时会自动执行deserialize方法,但是有个弊端,JsonDeserializer<T>的T必须是具体类型,不能携带泛型,不同参数就有不同的Vo,要针对不同的Vo都写一个自定义反序化的类就很麻烦
package com.test.config; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import org.springframework.boot.jackson.JsonComponent; import java.io.IOException; import java.util.ArrayList; @JsonComponent public class MyJsonDeserializer extends JsonDeserializer<BatchVo<MyBeanVo>> { @Override public BatchVo<MyBeanVo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); BatchVo vo = new BatchVo<MyBeanVo>(); String str = treeNode.toString(); // 前端传参是数组 if (treeNode.isArray()) { vo.list = JSONObject.parseArray(str, MyBeanVo.class); } // 前端传参是单个数据 if (treeNode.isObject()) { vo.list = new ArrayList(); vo.list.add(JSONObject.parseObject(str, MyBeanVo.class)); } return vo; } } 复制代码
绑定的参数必须加@RequestBody,不然反序列化无法走MyJsonDeserializer的deserialize方法
@RestController public class MyController { @PostMapping("/hello") public String test(@RequestBody BatchVo<MyBeanVo>param) { MyBeanVo vo = param.getList().get(0); return vo.getValue(); } } 复制代码
发起请求:POST localhost:8080/hello
body参数:[{"value":"hello world"}] 或者 {"value":"hello world"}
返回皆为:hello world
分析:明显这种设计除非MyBean可以设计得很强大、很通用,可以接收前端所有的请求参数。要不然每个Vo类都需要写一个实现JsonDeserializer的反序化列解析类,或者每次都需要在contrller层做Json的再次反序列化。这样的实现变得繁琐,增加代码量
方案二
自定参数解析器自定义参数解析器
package com.test.config; import com.alibaba.fastjson.JSON; import org.apache.commons.io.IOUtils; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Type; import java.util.List; public class RequestBodyArgumentResolver implements HandlerMethodArgumentResolver { /** * 只拦截BatchBody注解且为数组的请求参数 * 每个mapping的方法只会执行一次此方法 */ public boolean supportsParameter(MethodParameter methodParameter) { Class paramType = methodParameter.getParameterType(); boolean isArray = paramType.isArray(); boolean isList = paramType.isAssignableFrom(List.class); boolean hasAnnotation = methodParameter.hasParameterAnnotation(BatchBody.class); return hasAnnotation && (isArray || isList); } /** * 通过了supportsParameter校验的mapping方法每次都会执行此方法 */ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { String json = getRequestBodyString(nativeWebRequest); Type type = methodParameter.getGenericParameterType(); Object obj = JSON.parseObject(json, type); return obj; } /** * 格式化json数据,统一为数组形式 * 解析json字符串需做得更完善,例如校验json格式是否正确 */ private String getRequestBodyString(NativeWebRequest webRequest) throws IOException { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); String json = IOUtils.toString(request.getInputStream(), "UTF-8").trim(); if (json.startsWith("{") && json.endsWith("}")) { return "[" + json + "]"; } if (json.startsWith("[") && json.endsWith("]")) { return json; } return null; } } 复制代码
将RequestBodyArgumentResolver注册到WebMvcConfigurerAdapter当中。
package com.test.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; @Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new RequestBodyArgumentResolver()); super.addArgumentResolvers(argumentResolvers); } } 复制代码
定义mapping接口,在参数上加上注解@BatchBody
@RestController public class MyController { @PostMapping("/hello2") public String test2(@BatchBody MyBeanVo[] param) { MyBeanVo vo = param[0]; return vo.getValue(); } @PostMapping("/hello3") public String test3(@BatchBody List<MyBeanVo> param) { MyBeanVo vo = param.get(0); return vo.getValue(); } @PostMapping("/hello4") public String test4(@BatchBody MyBeanVo... param) { MyBeanVo vo = param[0]; return vo.getValue(); } } 复制代码
传入参数{"value":"hello world"}或者[{"value":"hello world"}]
返回皆为:hello world
可以完美兼容数组,集合,可变参数(实际是数组)
分析:RequestBodyArgumentResolver解析Json字符串,需要检测格式是否正确,需要兼容单个数据和批量数据的参数,只需要把该参数改成List/数组[]/可变参数,再在前面加上@BatchBody注解即可实现,service层和dao层要设计为批量的传参
总结
SpringMVC提供了很多自定义拦截/过滤器的接口和类,注册到配置类中,为开发者提供了方便的api,能满足开发中的大多数场景的需求,其扩展性真的做得很赞。同时,我们在设计一个接口,一个函数,多考虑其扩展和接入场景,让每个函数变得更健壮,先设计再编码,减少试错的成本
以上所述就是小编给大家介绍的《后台接收Json请求参数兼容数组和单个对象》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- php取出数组单个值的方法
- hibernate多对多,单个修改很伤神
- 巧断梯度:单个loss实现GAN模型
- Oracle 11g 起停RAC中单个节点
- 使用单个Windows命令从文件中提取N行
- c# – 正确的.NET方式实现单个实例应用程序
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。