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

作者:木叶之荣 时间:2023-06-02 21:41:32 

这几年都在搞前后端分离、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

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

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

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

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

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

正常情况下,一个请求在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种提供了多少参数解析器呢?看下图所示:

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

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

大概有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";
}

请求信息如下:

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

后台输出结果如下:

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

来源:http://blog.csdn.net/zknxx/article/details/68952518

标签:spring,mvc,json
0
投稿

猜你喜欢

  • Java使用EasyExcel动态添加自增序号列

    2023-11-17 01:54:37
  • IDEA利用自带Axis工具和wsdl文件反向生成服务端客户端代码图文详解

    2021-12-06 20:02:12
  • Spring Boot 员工管理系统超详细教程(源码分享)

    2022-04-18 13:32:26
  • C#判断ip地址是否可以ping的通

    2021-08-29 10:53:13
  • 解决idea中yml文件不识别的问题

    2021-12-07 02:09:28
  • Mybatis-Plus设置全局或者局部ID自增的实现

    2021-11-27 00:30:03
  • Java 用Prometheus搭建实时监控系统过程详解

    2023-09-06 12:07:40
  • Java计时器StopWatch实现方法代码实例

    2021-07-25 13:43:42
  • C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信

    2021-10-21 09:08:21
  • React Native与Android 原生通信的方法

    2021-08-11 19:02:56
  • C#将Word转换成PDF方法汇总(基于Office和WPS)

    2021-09-12 22:52:58
  • Mybatis逆向工程运行代码实例

    2021-06-22 04:54:59
  • C#实现图表中鼠标移动并显示数据

    2022-10-30 20:45:30
  • Android 获取屏幕的多种宽高信息的示例代码

    2021-07-10 06:07:10
  • 三种Java打印PDF文档的实例代码

    2022-01-23 09:29:25
  • Java中对list map根据map某个key值进行排序的方法

    2023-09-04 17:10:03
  • Java字符串编码解码性能提升的技巧分享

    2022-01-30 08:45:13
  • 简单分析Java的求值策略原理

    2022-03-22 19:14:00
  • Java并发编程之浅谈ReentrantLock

    2022-08-25 10:46:02
  • spring异步service中处理线程数限制详解

    2021-09-02 23:20:12
  • asp之家 软件编程 m.aspxhome.com