這幾年都在搞前后端分離、RESTful風(fēng)格,我們項(xiàng)目中也在這樣用。前幾天有人遇到了解析JSON格式的請求數(shù)據(jù)的問題,然后說了一下解析的方式,今天就寫篇文章簡單的分析一下后臺(tái)對于JSON格式請求數(shù)據(jù)是怎么解析的。
公司主營業(yè)務(wù):成都做網(wǎng)站、網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)建站是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)建站推出昭陽免費(fèi)做網(wǎng)站回饋大家。
先把例子的代碼貼出來:
前端
<input type="button" value="測試JSON數(shù)據(jù)" 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>
后臺(tái)處理代碼如下:
@RequestMapping(value ="testJson") public String testJson(@RequestBody Map name, HttpServletRequest request){ System.out.println(name); return "jsonp"; }
這里需要注意的是:要在參數(shù)對象上加上@RequestBody注解,這個(gè)一定不能少,后臺(tái)在接收J(rèn)SON數(shù)據(jù)的時(shí)候一定要用自定義的對象或者M(jìn)ap對象去接收,不要用JDK中的簡單對象(String/Integer/Long)來接收。
接下來我再把抓出來的http請求貼一下:
Content-Type:application/json
這里需要注意的是:Request Payload中的格式一定要和上圖一致,其他格式SpringMVC會(huì)解析不出來。
OK,如上的代碼就可以搞定一個(gè)JSON請求數(shù)據(jù)的解析了。下面我們來分析一下SpringMVC是怎么處理JSON請求的。
SpringMVC處理請求的簡單時(shí)序圖如下:
正常情況下,一個(gè)請求在SpringMVC中一般會(huì)調(diào)用doDispatch這個(gè)方法,我們進(jìn)入到這個(gè)方法中直接跳到
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
這一行,這一行上面的內(nèi)容我們以后再找機(jī)會(huì)分析。
ha.handle這個(gè)方法會(huì)調(diào)用org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter中的handle方法,這個(gè)方法里面很簡單,就是調(diào)用了handleInternal這個(gè)方法,代碼如下:
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
而handleInternal這個(gè)方法調(diào)用的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter中的handleInternal方法,我們進(jìn)入到這個(gè)方法中看看這個(gè)方法中都干了一些什么事:
@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中是不是要求同步執(zhí)行 HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) {//同步執(zhí)行方法調(diào)用 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);//這三個(gè)invokeHandlerMethod調(diào)用的是同一個(gè)方法 }//緩存的設(shè)置 if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
在上面的這個(gè)方法中我們需要關(guān)注的是invokeHandlerMethod這個(gè)方法。invokeHandlerMethod這個(gè)方法有點(diǎn)復(fù)雜,這個(gè)方法中干了很多的事,像創(chuàng)建數(shù)據(jù)驗(yàn)證類、創(chuàng)建方法處理類、模型視圖容器等。在這里我們先忽略這些,直接跳到
invocableMethod.invokeAndHandle(webRequest, mavContainer);
這里。這個(gè)方法在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod中。在這個(gè)方法中我們只關(guān)注第一句話:
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
invokeForRequest這個(gè)方法在org.springframework.web.method.support.InvocableHandlerMethod中,同樣在這個(gè)方法中我們也只關(guān)注第一句話:
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
getMethodArgumentValues從這個(gè)方法名我們可以看出來這個(gè)方法是獲取方法參數(shù)值的,這個(gè)類和上面的方法在同一個(gè)類中。我們進(jìn)到這個(gè)方法中看一下:
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //獲取參數(shù)對象數(shù)組 方法中的參數(shù)都在這個(gè)對象數(shù)組中存放著。 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());//獲取參數(shù)的類型(處理參數(shù)中的泛型) args[i] = resolveProvidedArgument(parameter, providedArgs);//如果提供了參數(shù)的值的話,直接返回 if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { //(1) 支持的參數(shù)類型 try { args[i] = this.argumentResolvers.resolveArgument( // (2) 給參數(shù)賦值、校驗(yàn)的一些操作 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)的地方。這個(gè)地方是給方法中的參數(shù)匹配一個(gè)合適的解析器。這個(gè)方法的真正調(diào)用的是
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver這個(gè)方法。
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);//如果緩存中已經(jīng)存在了,則從緩存中取 if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {//遍歷所有的參數(shù)解析器 if (logger.isTraceEnabled()) { logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + parameter.getGenericParameterType() + "]"); } if (methodArgumentResolver.supportsParameter(parameter)) {//匹配合適的參數(shù)解析器并放入到緩存中 result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
那SpringMVC種提供了多少參數(shù)解析器呢?看下圖所示:
大概有30來個(gè),瞬間覺得SpringMVC好強(qiáng)大啊,給人一種無論你在Header里、Cookie里、Body里、還是Path里,無論是什么類型的參數(shù)我都能給你解析了的霸氣。我們這里的匹配到的參數(shù)解析器是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor這個(gè)類。我們接著來看上面代碼中的(2)。resolveArgument這個(gè)方法真的調(diào)用的就是RequestResponseBodyMethodProcessor這個(gè)類中的resolveArgument的方法。我們進(jìn)入到這個(gè)方法中看一下:
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //這里是對參數(shù)的解析賦值 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());//[1] String name = Conventions.getVariableNameForParameter(parameter); //獲取參數(shù)校驗(yàn)的具體類 WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter);//進(jìn)行參數(shù)校驗(yàn) if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return arg; }
我們重點(diǎn)看上面代碼中[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); //從請求輸入流中解析出參數(shù)的值 Object arg = readWithMessageConverters(inputMessage, methodParam, paramType); if (arg == null) { if (checkRequired(methodParam)) {//校驗(yàn)參數(shù)是不是必須的 throw new HttpMessageNotReadableException("Required request body is missing: " + methodParam.getMethod().toGenericString()); } } return arg; }
我們重點(diǎn)要看的是org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters方法。
這個(gè)方法很長,在這個(gè)方法中會(huì)獲取ContentType、參數(shù)的類型、Method、重新封裝Request等等的操作。我們需要關(guān)注這三行代碼:
inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType); body = genericConverter.read(targetType, contextClass, inputMessage);[1] body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
為參數(shù)賦值的是[1]這行代碼。這里調(diào)用的是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);//按照J(rèn)ava的類型,為參數(shù)賦值 }
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]調(diào)用Jackson中的方法,解析Body的內(nèi)容,賦值為java的類型 } catch (IOException ex) { throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex); } }
this.objectMapper.readValue這個(gè)方法會(huì)掉到Jackson相關(guān)的jar中。再往下跟的話還有很深,說實(shí)在的里面有很多的方法我還沒看明白,所以我們就不繼續(xù)往下走了。這個(gè)方法中大致干的事是按照相應(yīng)的編碼讀取HTTP請求中請求體里的內(nèi)容,由于是JSON格式的,所以又會(huì)把JSON格式的數(shù)據(jù)轉(zhuǎn)換為傳入進(jìn)去的Java類型對象。
后記:
如果我們知道請求格式是JSON的話,我們可以自己寫一個(gè)簡單的請求體的解析,但是在項(xiàng)目中最好別這樣做。代碼如下:
@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"; }
請求信息如下:
后臺(tái)輸出結(jié)果如下:
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
網(wǎng)站欄目:SpringMVC解析JSON請求數(shù)據(jù)問題解析
文章起源:http://jinyejixie.com/article4/gpgcoe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)公司、品牌網(wǎng)站設(shè)計(jì)、虛擬主機(jī)、網(wǎng)站建設(shè)、網(wǎng)站內(nèi)鏈、營銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)