SpringMVC解析JSON请求数据小析
来源:互联网 发布:女装淘宝店招牌 编辑:程序博客网 时间:2024/05/01 19:49
这几年都在搞前后端分离、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方法,我们进入到这个方法中看看这个方法中都干了一些什么事:
@Overrideprotected 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 necessarymav = 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]的地方。方法中的代码如下:
@Overrideprotected <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方法,代码如下:
@Overridepublic 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请求数据
- Json解析网络请求数据
- SpringMVC请求json数据报406错误
- SpringMVC处理Ajax请求(请求数据为 json 格式字符串)
- Android网络请求与JSON解析和数据抓取小实例
- springmvc之json数据的解析
- springmvc解析和输出json数据
- SpringMvc 服务端发布json数据+android客户端解析json数据
- unity3d请求json数据并解析
- [Unity3d]u3d请求json数据并解析
- IOS 请求数据解析 XML 和 JSON
- extjs前台请求json数据,并解析
- iOS 网络数据请求和Json 解析
- http请求返回并解析json数据
- unity请求json数据并解析
- URL请求并解析JSON数据
- http请求返回并解析json数据
- c++ 深入理解拷贝与赋值及其他合成函数
- 短信备注
- C/C++——一些与输入有关的istream类成员函数
- java io 输入流
- LinkedHashMap和HashMap的比较使用
- SpringMVC解析JSON请求数据小析
- 100个经典的动态规划方程
- 关于 常用的Banner一些简单用法 站在巨人的肩膀上
- Android 适配的点点滴滴
- POJ1002--487-3279
- 对activity的提前处理写的PrefUtil类
- ZOJ
- 将activity设置成窗口模式
- Ant构建工具