服务器之家

服务器之家 > 正文

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

时间:2020-09-09 13:17     来源/作者:木叶之荣

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

先把例子的代码贴出来:

前端

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>

后台处理代码如下:

?
1
2
3
4
5
@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请求贴一下:

?
1
content-type:application/json

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

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

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

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

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

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

?
1
mv = ha.handle(processedrequest, response, mappedhandler.gethandler());

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

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

?
1
2
3
4
5
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方法,我们进入到这个方法中看看这个方法中都干了一些什么事:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@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这个方法有点复杂,这个方法中干了很多的事,像创建数据验证类、创建方法处理类、模型视图容器等。在这里我们先忽略这些,直接跳到

?
1
invocablemethod.invokeandhandle(webrequest, mavcontainer);

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

?
1
object returnvalue = invokeforrequest(webrequest, mavcontainer, providedargs);

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

?
1
object[] args = getmethodargumentvalues(request, mavcontainer, providedargs);

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  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这个方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的方法。我们进入到这个方法中看一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  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]的地方。方法中的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  @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等等的操作。我们需要关注这三行代码:

?
1
2
3
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方法,代码如下:

?
1
2
3
4
5
6
7
@override
public object read(type type, class<?> contextclass, httpinputmessage inputmessage)
    throws ioexception, httpmessagenotreadableexception {
/获取java中的类型
  javatype javatype = getjavatype(type, contextclass);
  return readjavatype(javatype, inputmessage);//按照java的类型,为参数赋值
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的话,我们可以自己写一个简单的请求体的解析,但是在项目中最好别这样做。代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

标签:

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享 2020-04-07
最新idea2020注册码永久激活(激活到2100年)
最新idea2020注册码永久激活(激活到2100年) 2020-07-29
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址 2020-08-12
返回顶部