有两个@RequestBody,一执行,结果抛错:
{ "status": 400, "error": "Bad Request", "exception": "org.springframework.http.converter.HttpMessageNotReadableException", "message": "I/O error while reading input message; nested exception is java.io.IOException: Stream closed" }
400通常是输入参数错误,错误原因:从上文对@RequestBody的解析过程的分析来看,这个参数实际上是将输入流的body体作为一个整体进行转换,而body整体只有一份,解析完成之后会关闭输入流,所以第二个参数book2的解析就会抛错。
当前,解决此类的方案有两种:
1、@RequestBody List<Book> books
2、@RequestBody MultiObject books
不管是哪一种,其实都是将众多的对象组成一个,因为在springmvc的一个方法中只能有一个@RequestBody,这被称为单体限制。其实在有些场景下,我就是想实现多个@RequestBody这样的功能,该怎么办?(我在实现kspringfox框架的时候,就遇到了这样的诉求:kspringfox是一个扩展了springfox的框架,主要实现了对dubbo接口的文档化,以及将dubbo接口透明的转为rest接口供我们调用的功能)。
开始划重点…前方高能!
自定义spring参数注解 - 打破@RequestBody单体限制实现方案
支持数据
实体
集合
数组
MapWapper(Map的一个包装器,通过getInnerMap获取真实Map)
零、依赖JAR包
<dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
一、自定义spring的参数注解
首先自定义一个类似于@RequestBody的注解:@RequestJson
自定义注解很简单:@Target指明注解应用于参数上;@Retention指明注解应用于运行时。
package com.luxsuen.requestjson.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface RequestJson { String value(); boolean required() default true; String defaultValue() default ""; }
二、编写spring的参数注解解析器
package com.luxsuen.requestjson.resolver; import com.alibaba.fastjson.JSON; import com.jayway.jsonpath.JsonPath; import com.luxsuen.requestjson.common.Const; import com.luxsuen.requestjson.util.IOUtil; import com.luxsuen.requestjson.util.MapWrapper; import net.minidev.json.JSONArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.*; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class RequestJsonHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * @Author Lux Sun * @Description: 判断是否支持,若支持,则调用 resolveArgument * @Param: [methodParameter] * @Return: boolean */ @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(Const.ANNOTATION_CLASS); } /** * @Author Lux Sun * @Description: 重写 resolveArgument,核心函数:分解参数功能 * @Param: [methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory] * @Return: java.lang.Object */ @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { String body = getRequestBody(nativeWebRequest); String annotationKey = null; Object matchObj = null; try { // 获取注解 value annotationKey = methodParameter.getParameterAnnotation(Const.ANNOTATION_CLASS).value(); int midBracket = body.indexOf('['); int bigBracket = body.indexOf('{'); // 匹配[...{}...]、[...]、{...}类型 if(bigBracket > midBracket && midBracket != -1 || midBracket != -1 && bigBracket == -1) { // 此时的annotationKey取什么都可以,因为下面一定会解析出属于JSONArray类型,那时才是真正的key body = "{\"" + annotationKey + "\":" + body + "}"; } // 默认只读第一层{...}中key(至此一定是{...}结构) matchObj = JsonPath.read(body, annotationKey); if (methodParameter.getParameterAnnotation(Const.ANNOTATION_CLASS).required() && matchObj == null) { throw new Exception(annotationKey + "不能为空"); } else if(matchObj instanceof LinkedHashMap && ((Map)matchObj).size()==0) // { key: {} } { throw new Exception("JSON对象为空"); } else if(matchObj instanceof JSONArray && ((List)matchObj).size()==0) // { key: [] } { throw new Exception("JSONArray为空"); } } catch (Exception e) { // 情况1:required == false && 找不到 key 对应的 json(无论Body是否为空) if (!methodParameter.getParameterAnnotation(Const.ANNOTATION_CLASS).required()) { return null; } // 情况2:required == true && 找不到 key 对应的 json(无论Body是否为空) e.printStackTrace(); return null; } // Map2JsonString String matchJsonStr = JSON.toJSONString(matchObj); // methodParameter.getGenericParameterType() 返回参数的完整类型(带泛型)(web层形参的类型) final Type type = methodParameter.getGenericParameterType(); // 判断转换类型 if(MapWrapper.class.isAssignableFrom(methodParameter.getParameterType())){ // Map类型(使用自己新封装的JSONObject充当Map) if(matchObj instanceof JSONArray) // 匹配JSONArray类型,前面需要加一个key,组装成JSONObject类型格式 { matchJsonStr = "{\"" + annotationKey + "\":" + matchJsonStr + "}"; } MapWrapper mapWrapper = new MapWrapper(JSON.parseObject(matchJsonStr)); return mapWrapper; } else { // Number、BigDecimal/BigInteger、Boolean、String、Entity/List<Entity> Object rsObj = null; try { rsObj = JSON.parseObject(matchJsonStr, type); } catch (Exception e) { // 情况:形参类型与JSON解析后的类型不匹配 e.printStackTrace(); } return rsObj; } } /** * @Author Lux Sun * @Description: 获取请求包的Body内容 * @Param: [nativeWebRequest] * @Return: java.lang.String */ private String getRequestBody(NativeWebRequest nativeWebRequest) { HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class); String jsonBody = (String) servletRequest.getAttribute(Const.JSON_REQUEST_BODY); if (jsonBody == null) { try { // jsonBody = IOUtils.toString(in); // closed stream 问题 jsonBody = IOUtil.inputStream2Str(servletRequest.getInputStream(),"UTF-8"); servletRequest.setAttribute(Const.JSON_REQUEST_BODY, jsonBody); } catch (IOException e) { e.printStackTrace(); } } return jsonBody; } }
注意:
1、supportsParameter方法指明RequestJsonHandlerMethodArgumentResolver只处理带有@RequestJson注解的参数。
2、resolveArgument方法对入参进行解析:首先通过JsonPath获取对应参数的参数值(json串),然后获取参数的完整类型(带泛型),最后使用fastjson解析器将json格式的参数值转化为具体类型的对象。