SpringMVC解析JSON請求數(shù)據(jù)問題解析
這幾年都在搞前后端分離、RESTful風(fēng)格,我們項目中也在這樣用。前幾天有人遇到了解析JSON格式的請求數(shù)據(jù)的問題,然后說了一下解析的方式,今天就寫篇文章簡單的分析一下后臺對于JSON格式請求數(shù)據(jù)是怎么解析的。
先把例子的代碼貼出來:
前端
<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>
后臺處理代碼如下:
@RequestMapping(value ="testJson")
public String testJson(@RequestBody Map name, HttpServletRequest request){
System.out.println(name);
return "jsonp";
}
這里需要注意的是:要在參數(shù)對象上加上@RequestBody注解,這個一定不能少,后臺在接收J(rèn)SON數(shù)據(jù)的時候一定要用自定義的對象或者M(jìn)ap對象去接收,不要用JDK中的簡單對象(String/Integer/Long)來接收。
接下來我再把抓出來的http請求貼一下:
Content-Type:application/json

這里需要注意的是:Request Payload中的格式一定要和上圖一致,其他格式SpringMVC會解析不出來。
OK,如上的代碼就可以搞定一個JSON請求數(shù)據(jù)的解析了。下面我們來分析一下SpringMVC是怎么處理JSON請求的。
SpringMVC處理請求的簡單時序圖如下:

正常情況下,一個請求在SpringMVC中一般會調(diào)用doDispatch這個方法,我們進(jìn)入到這個方法中直接跳到
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
這一行,這一行上面的內(nèi)容我們以后再找機會分析。
ha.handle這個方法會調(diào)用org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter中的handle方法,這個方法里面很簡單,就是調(diào)用了handleInternal這個方法,代碼如下:
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
而handleInternal這個方法調(diào)用的是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter中的handleInternal方法,我們進(jìn)入到這個方法中看看這個方法中都干了一些什么事:
@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);//這三個invokeHandlerMethod調(diào)用的是同一個方法
}//緩存的設(shè)置
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
在上面的這個方法中我們需要關(guān)注的是invokeHandlerMethod這個方法。invokeHandlerMethod這個方法有點復(fù)雜,這個方法中干了很多的事,像創(chuàng)建數(shù)據(jù)驗證類、創(chuàng)建方法處理類、模型視圖容器等。在這里我們先忽略這些,直接跳到
invocableMethod.invokeAndHandle(webRequest, mavContainer);
這里。這個方法在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod中。在這個方法中我們只關(guān)注第一句話:
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
invokeForRequest這個方法在org.springframework.web.method.support.InvocableHandlerMethod中,同樣在這個方法中我們也只關(guān)注第一句話:
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
getMethodArgumentValues從這個方法名我們可以看出來這個方法是獲取方法參數(shù)值的,這個類和上面的方法在同一個類中。我們進(jìn)到這個方法中看一下:
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//獲取參數(shù)對象數(shù)組 方法中的參數(shù)都在這個對象數(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ù)賦值、校驗的一些操作
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)的地方。這個地方是給方法中的參數(shù)匹配一個合適的解析器。這個方法的真正調(diào)用的是
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver這個方法。
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來個,瞬間覺得SpringMVC好強大啊,給人一種無論你在Header里、Cookie里、Body里、還是Path里,無論是什么類型的參數(shù)我都能給你解析了的霸氣。我們這里的匹配到的參數(shù)解析器是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor這個類。我們接著來看上面代碼中的(2)。resolveArgument這個方法真的調(diào)用的就是RequestResponseBodyMethodProcessor這個類中的resolveArgument的方法。我們進(jìn)入到這個方法中看一下:
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ù)校驗的具體類
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);//進(jìn)行參數(shù)校驗
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);
//從請求輸入流中解析出參數(shù)的值
Object arg = readWithMessageConverters(inputMessage, methodParam, paramType);
if (arg == null) {
if (checkRequired(methodParam)) {//校驗參數(shù)是不是必須的
throw new HttpMessageNotReadableException("Required request body is missing: " +
methodParam.getMethod().toGenericString());
}
}
return arg;
}
我們重點要看的是org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters方法。
這個方法很長,在這個方法中會獲取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這個方法會掉到Jackson相關(guān)的jar中。再往下跟的話還有很深,說實在的里面有很多的方法我還沒看明白,所以我們就不繼續(xù)往下走了。這個方法中大致干的事是按照相應(yīng)的編碼讀取HTTP請求中請求體里的內(nèi)容,由于是JSON格式的,所以又會把JSON格式的數(shù)據(jù)轉(zhuǎn)換為傳入進(jìn)去的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";
}
請求信息如下:

后臺輸出結(jié)果如下:

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot結(jié)合mybatis操作事務(wù)配置的處理
在操作數(shù)據(jù)庫的時候,經(jīng)常會使用事務(wù)的處理,本文主要介紹了springboot結(jié)合mybatis操作事務(wù)配置的處理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
Java遞歸遍歷樹形結(jié)構(gòu)的實現(xiàn)代碼
這篇文章主要介紹了Java遞歸遍歷樹形結(jié)構(gòu)的相關(guān)資料,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2016-03-03
將一個數(shù)組按照固定大小進(jìn)行拆分成數(shù)組的方法
下面小編就為大家?guī)硪黄獙⒁粋€數(shù)組按照固定大小進(jìn)行拆分成數(shù)組的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11
SpringBoot整合Freemarker實現(xiàn)頁面靜態(tài)化的詳細(xì)步驟
這篇文章主要介紹了SpringBoot整合Freemarker實現(xiàn)頁面靜態(tài)化,第一步要創(chuàng)建項目添加依賴,本文分步驟給大家詳細(xì)講解,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-10-10
關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性
這篇文章主要介紹了關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性,需要的朋友可以參考下2023-07-07
Java通過導(dǎo)出超大Excel文件解決內(nèi)存溢出問題
導(dǎo)出excel是咱Java開發(fā)的必備技能,下面這篇文章主要給大家介紹了關(guān)于Java通過導(dǎo)出超大Excel文件解決內(nèi)存溢出問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09

