SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(四)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(四)

有两个@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格式的参数值转化为具体类型的对象。


目录
相关文章
|
11月前
SpringMVC常见组件之HandlerMethodArgumentResolver解析-2
SpringMVC常见组件之HandlerMethodArgumentResolver解析-2
54 0
|
11月前
|
算法 Java API
SpringMVC常见组件之HandlerMethodArgumentResolver解析-1
SpringMVC常见组件之HandlerMethodArgumentResolver解析-1
78 0
|
缓存 数据格式 Java
SpringMVC之分析HandlerMethodArgumentResolver请求对应处理器方法参数的解析过程(一)
在我们做Web开发的时候,会提交各种数据格式的请求,而我们的后台也会有相应的参数处理方式。SpringMVC就为我们提供了一系列的参数解析器,不管你是要获取Cookie中的值,Header中的值,JSON格式的数据,URI中的值。
2411 0
|
前端开发 API 数据安全/隐私保护
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(三)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(三)
198 0
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(三)
|
JSON 前端开发 JavaScript
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(一)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(一)
274 0
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(一)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(七)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(七)
128 0
|
XML Java 数据格式
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(六)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(六)
114 0
|
XML Java 数据格式
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(五)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(五)
157 0
|
XML JSON Java
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(二)
SpringMVC - @RequestJson之HandlerMethodArgumentResolver 从入门到青铜(二)
130 0
|
6月前
|
设计模式 前端开发 JavaScript
Spring MVC(一)【什么是Spring MVC】
Spring MVC(一)【什么是Spring MVC】