SpringMVC解析JSON请求数据问题解析

发布时间 - 2026-01-11 00:36:12    点击率:

这几年都在搞前后端分离、RESTful风格,我们项目中也在这样用。前几天有人遇到了解析JSON格式的请求数据的问题,然后说了一下解析的方式,今天就写篇文章简单的分析一下后台对于JSON格式请求数据是怎么解析的。

先把例子的代码贴出来:

前端

<input type="button" value="测试JSON数据" onclick="testJSON()" /> 
<script type="text/javascript"> 
  function testJSON() { 
    $.ajax({ 
      type: "POST", 
      url: "/testJson", 
      contentType: "application/json", 
      dataType: "json", 
      data: JSON.stringify({"name":"张三"}), 
      success: function (jsonResult) { 
        alert(jsonResult); 
      } 
    }); 
  } 
</script> 

后台处理代码如下:

@RequestMapping(value ="testJson") 
public String testJson(@RequestBody Map name, HttpServletRequest request){ 
  System.out.println(name); 
  return "jsonp"; 
} 

这里需要注意的是:要在参数对象上加上@RequestBody注解,这个一定不能少,后台在接收JSON数据的时候一定要用自定义的对象或者Map对象去接收,不要用JDK中的简单对象(String/Integer/Long)来接收。

接下来我再把抓出来的http请求贴一下:

Content-Type:application/json 

这里需要注意的是:Request Payload中的格式一定要和上图一致,其他格式SpringMVC会解析不出来。

OK,如上的代码就可以搞定一个JSON请求数据的解析了。下面我们来分析一下SpringMVC是怎么处理JSON请求的。

SpringMVC处理请求的简单时序图如下:

正常情况下,一个请求在SpringMVC中一般会调用doDispatch这个方法,我们进入到这个方法中直接跳到

mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 

这一行,这一行上面的内容我们以后再找机会分析。

ha.handle这个方法会调用org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter中的handle方法,这个方法里面很简单,就是调用了handleInternal这个方法,代码如下:

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) 
    throws Exception { 
 
  return handleInternal(request, response, (HandlerMethod) handler); 
} 

而handleInternal这个方法调用的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter中的handleInternal方法,我们进入到这个方法中看看这个方法中都干了一些什么事:

@Override 
protected ModelAndView handleInternal(HttpServletRequest request, 
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { 
 
  ModelAndView mav; 
  checkRequest(request);//检查是不是所支持的请求类型、是不是要求session 
 
  // Execute invokeHandlerMethod in synchronized block if required. 
  if (this.synchronizeOnSession) {//session中是不是要求同步执行 
    HttpSession session = request.getSession(false); 
    if (session != null) { 
      Object mutex = WebUtils.getSessionMutex(session); 
      synchronized (mutex) {//同步执行方法调用 
        mav = invokeHandlerMethod(request, response, handlerMethod); 
      } 
    } 
    else { 
      // No HttpSession available -> no mutex necessary 
      mav = invokeHandlerMethod(request, response, handlerMethod); 
    } 
  } 
  else { 
    // No synchronization on session demanded at all... 
    mav = invokeHandlerMethod(request, response, handlerMethod);//这三个invokeHandlerMethod调用的是同一个方法 
  }//缓存的设置 
  if (!response.containsHeader(HEADER_CACHE_CONTROL)) { 
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { 
      applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); 
    } 
    else { 
      prepareResponse(response); 
    } 
  } 
 
  return mav; 
} 

在上面的这个方法中我们需要关注的是invokeHandlerMethod这个方法。invokeHandlerMethod这个方法有点复杂,这个方法中干了很多的事,像创建数据验证类、创建方法处理类、模型视图容器等。在这里我们先忽略这些,直接跳到

invocableMethod.invokeAndHandle(webRequest, mavContainer); 

这里。这个方法在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod中。在这个方法中我们只关注第一句话:

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 

invokeForRequest这个方法在org.springframework.web.method.support.InvocableHandlerMethod中,同样在这个方法中我们也只关注第一句话:

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); 

getMethodArgumentValues从这个方法名我们可以看出来这个方法是获取方法参数值的,这个类和上面的方法在同一个类中。我们进到这个方法中看一下:

  private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, 
      Object... providedArgs) throws Exception { 
//获取参数对象数组 方法中的参数都在这个对象数组中存放着。 
    MethodParameter[] parameters = getMethodParameters(); 
    Object[] args = new Object[parameters.length]; 
    for (int i = 0; i < parameters.length; i++) { 
      MethodParameter parameter = parameters[i]; 
      parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); 
      GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());//获取参数的类型(处理参数中的泛型) 
      args[i] = resolveProvidedArgument(parameter, providedArgs);//如果提供了参数的值的话,直接返回 
      if (args[i] != null) { 
        continue; 
      } 
      if (this.argumentResolvers.supportsParameter(parameter)) { //(1) 支持的参数类型 
        try { 
          args[i] = this.argumentResolvers.resolveArgument( // (2) 给参数赋值、校验的一些操作 
              parameter, mavContainer, request, this.dataBinderFactory); 
          continue; 
        } 
        catch (Exception ex) { 
          if (logger.isDebugEnabled()) { 
            logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); 
          } 
          throw ex; 
        } 
      } 
      if (args[i] == null) { 
        String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); 
        throw new IllegalStateException(msg); 
      } 
    } 
    return args; 
  } 

我们先来看看上面的代码中(1)的地方。这个地方是给方法中的参数匹配一个合适的解析器。这个方法的真正调用的是
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver这个方法。

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { 
  HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);//如果缓存中已经存在了,则从缓存中取 
  if (result == null) { 
    for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {//遍历所有的参数解析器 
      if (logger.isTraceEnabled()) { 
        logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + 
            parameter.getGenericParameterType() + "]"); 
      } 
      if (methodArgumentResolver.supportsParameter(parameter)) {//匹配合适的参数解析器并放入到缓存中 
        result = methodArgumentResolver; 
        this.argumentResolverCache.put(parameter, result); 
        break; 
      } 
    } 
  } 
  return result; 
} 

那SpringMVC种提供了多少参数解析器呢?看下图所示:

大概有30来个,瞬间觉得SpringMVC好强大啊,给人一种无论你在Header里、Cookie里、Body里、还是Path里,无论是什么类型的参数我都能给你解析了的霸气。我们这里的匹配到的参数解析器是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor这个类。我们接着来看上面代码中的(2)。resolveArgument这个方法真的调用的就是RequestResponseBodyMethodProcessor这个类中的resolveArgument的方法。我们进入到这个方法中看一下:

  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { 
//这里是对参数的解析赋值 
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());//[1] 
    String name = Conventions.getVariableNameForParameter(parameter); 
//获取参数校验的具体类 
    WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); 
    if (arg != null) { 
      validateIfApplicable(binder, parameter);//进行参数校验 
      if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { 
        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); 
      } 
    } 
    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); 
 
    return arg; 
  } 

我们重点看上面代码中[1]的地方。方法中的代码如下:

  @Override 
  protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, 
      Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { 
//获取请求对象 
    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); 
    ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); 
//从请求输入流中解析出参数的值 
    Object arg = readWithMessageConverters(inputMessage, methodParam, paramType); 
    if (arg == null) { 
      if (checkRequired(methodParam)) {//校验参数是不是必须的 
        throw new HttpMessageNotReadableException("Required request body is missing: " + 
            methodParam.getMethod().toGenericString()); 
      } 
    } 
    return arg; 
  } 

我们重点要看的是org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters方法。

这个方法很长,在这个方法中会获取ContentType、参数的类型、Method、重新封装Request等等的操作。我们需要关注这三行代码:

inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType); 
body = genericConverter.read(targetType, contextClass, inputMessage);[1] 
body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType); 

为参数赋值的是[1]这行代码。这里调用的是org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter中的read方法,代码如下:

@Override 
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) 
    throws IOException, HttpMessageNotReadableException { 
/获取Java中的类型 
  JavaType javaType = getJavaType(type, contextClass); 
  return readJavaType(javaType, inputMessage);//按照Java的类型,为参数赋值 
} 
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) { 
  try { 
    if (inputMessage instanceof MappingJacksonInputMessage) { 
      Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView(); 
      if (deserializationView != null) { 
        return this.objectMapper.readerWithView(deserializationView).forType(javaType). 
            readValue(inputMessage.getBody()); 
      } 
    } 
    return this.objectMapper.readValue(inputMessage.getBody(), javaType);//[1]调用Jackson中的方法,解析Body的内容,赋值为java的类型 
  } 
  catch (IOException ex) { 
    throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex); 
  } 
} 

this.objectMapper.readValue这个方法会掉到Jackson相关的jar中。再往下跟的话还有很深,说实在的里面有很多的方法我还没看明白,所以我们就不继续往下走了。这个方法中大致干的事是按照相应的编码读取HTTP请求中请求体里的内容,由于是JSON格式的,所以又会把JSON格式的数据转换为传入进去的Java类型对象。

后记:

如果我们知道请求格式是JSON的话,我们可以自己写一个简单的请求体的解析,但是在项目中最好别这样做。代码如下:

@RequestMapping(value ="testJson") 
public String testJson(HttpServletRequest request){ 
  try { 
    InputStream inputStream = request.getInputStream(); 
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
    byte[] bytes = new byte[1024]; 
    int flag = 0; 
    while ((flag = inputStream.read(bytes)) > 0){ 
      byteArrayOutputStream.write(bytes,0,flag); 
    } 
    System.out.println(new String(byteArrayOutputStream.toByteArray(),request.getCharacterEncoding())); 
  } catch (IOException e) { 
    e.printStackTrace(); 
  } 
  return "jsonp"; 
} 

请求信息如下:

后台输出结果如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# spring  # mvc  # 解析json  # json请求  # SpringMVC返回json数据的三种方式  # SpringMVC接收与响应json数据的几种方式  # 解决SpringMvc后台接收json数据中文乱码问题的几种方法  # springMVC返回复杂的json格式数据方法  # SpringMVC中Json数据交互处理示例详解  # 的是  # 在这个  # 都在  # 是怎么  # 干了  # 跳到  # 需要注意  # 这一行  # 第一句话  # 类中  # 法会  # 在这里  # 走了  # 有很多  # 我都  # 就不  # 说了  # 你在  # 遍历  # 要在 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Thinkphp 中 distinct 的用法解析  免费网站制作appp,免费制作app哪个平台好?  重庆市网站制作公司,重庆招聘网站哪个好?  Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案  图册素材网站设计制作软件,图册的导出方式有几种?  QQ浏览器网页版登录入口 个人中心在线进入  实例解析angularjs的filter过滤器  如何在腾讯云服务器上快速搭建个人网站?  安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出  东莞市网站制作公司有哪些,东莞找工作用什么网站好?  Laravel API资源(Resource)怎么用_格式化Laravel API响应的最佳实践  高防服务器租用首荐平台,企业级优惠套餐快速部署  如何为不同团队 ID 动态生成多个独立按钮  香港服务器网站生成指南:免费资源整合与高速稳定配置方案  如何基于云服务器快速搭建个人网站?  jQuery中的100个技巧汇总  太平洋网站制作公司,网络用语太平洋是什么意思?  如何在阿里云完成域名注册与建站?  Laravel怎么配置自定义表前缀_Laravel数据库迁移与Eloquent表名映射【步骤】  网站页面设计需要考虑到这些问题  详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)  Laravel如何使用Service Container和依赖注入?(代码示例)  Laravel DB事务怎么使用_Laravel数据库事务回滚操作  大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?  html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】  高端建站如何打造兼具美学与转化的品牌官网?  html5怎么画眼睛_HT5用Canvas或SVG画眼球瞳孔加JS控制动态【绘制】  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?  佛山网站制作系统,佛山企业变更地址网上办理步骤?  JS实现鼠标移上去显示图片或微信二维码  Java解压缩zip - 解压缩多个文件或文件夹实例  Linux网络带宽限制_tc配置实践解析【教程】  如何用低价快速搭建高质量网站?  用v-html解决Vue.js渲染中html标签不被解析的问题  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  浅析上传头像示例及其注意事项  Laravel Eloquent性能优化技巧_Laravel N+1查询问题解决  非常酷的网站设计制作软件,酷培ai教育官方网站?  Laravel全局作用域是什么_Laravel Eloquent Global Scopes应用指南  如何在橙子建站中快速调整背景颜色?  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  微信小程序制作网站有哪些,微信小程序需要做网站吗?  Midjourney怎样加参数调细节_Midjourney参数调整技巧【指南】  网站制作企业,网站的banner和导航栏是指什么?  浅谈Javascript中的Label语句  如何使用 jQuery 正确渲染 Instagram 风格的标签列表  国美网站制作流程,国美电器蒸汽鍋怎么用官方网站?  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  Java类加载基本过程详细介绍