这几年都在搞前后端分离、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数据交互处理示例详解
# 的是
# 在这个
# 都在
# 是怎么
# 干了
# 跳到
# 需要注意
# 这一行
# 第一句话
# 类中
# 法会
# 在这里
# 走了
# 有很多
# 我都
# 就不
# 说了
# 你在
# 遍历
# 要在
相关文章:
如何在香港免费服务器上快速搭建网站?
相亲简历制作网站推荐大全,新相亲大会主持人小萍萍资料?
猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?
建站之星如何通过成品分离优化网站效率?
早安海报制作网站推荐大全,企业早安海报怎么每天更换?
手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?
Swift中swift中的switch 语句
建站10G流量真的够用吗?如何应对访问高峰?
实现点击下箭头变上箭头来回切换的两种方法【推荐】
黑客如何利用漏洞与弱口令入侵网站服务器?
如何快速搭建个人网站并优化SEO?
七夕网站制作视频,七夕大促活动怎么报名?
如何零基础在云服务器搭建WordPress站点?
C++如何将C风格字符串(char*)转换为std::string?(代码示例)
天河区网站制作公司,广州天河区如何办理身份证?需要什么资料有预约的网站吗?
建站之星微信建站一键生成小程序+多端营销系统
建站之星安装提示数据库无法连接如何解决?
如何快速辨别茅台真假?关键步骤解析
PHP正则匹配日期和时间(时间戳转换)的实例代码
制作营销网站公司,淘特是干什么用的?
如何获取免费开源的自助建站系统源码?
学校免费自助建站系统:智能生成+拖拽设计+多端适配
专业制作网站的公司哪家好,建立一个公司网站的费用.有哪些部分,分别要多少钱?
微信小程序制作网站有哪些,微信小程序需要做网站吗?
建站之家VIP精选网站模板与SEO优化教程整合指南
c++怎么使用类型萃取type_traits_c++ 模板元编程类型判断【方法】
如何零基础开发自助建站系统?完整教程解析
网站制作免费,什么网站能看正片电影?
长沙做网站要多少钱,长沙国安网络怎么样?
建站之星好吗?新手能否轻松上手建站?
制作旅游网站html,怎样注册旅游网站?
高端建站三要素:定制模板、企业官网与响应式设计优化
青岛网站建设如何选择本地服务器?
官网自助建站平台指南:在线制作、快速建站与模板选择全解析
网站设计制作企业有哪些,抖音官网主页怎么设置?
建站主机核心功能解析:服务器选择与网站搭建流程指南
北京企业网站设计制作公司,北京铁路集团官方网站?
惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?
唐山网站制作公司有哪些,唐山找工作哪个网站最靠谱?
西安市网站制作公司,哪个相亲网站比较好?西安比较好的相亲网站?
微信推文制作网站有哪些,怎么做微信推文,急?
如何选择香港主机高效搭建外贸独立站?
常州自助建站费用包含哪些项目?
如何在Golang中引入测试模块_Golang测试包导入与使用实践
如何在Golang中指定模块版本_使用go.mod控制版本号
如何在企业微信快速生成手机电脑官网?
南京做网站制作公司,南京哈发网络有限公司,公司怎么样,做网页美工DIV+CSS待遇怎么样?
建站之星如何取消后台验证码生成?
香港服务器选型指南:免备案配置与高效建站方案解析
深圳网站制作平台,深圳市做网站好的公司有哪些?
*请认真填写需求信息,我们会在24小时内与您取得联系。