javascript
springmvc 传对象报400_那么火的SpringMVC到底有什么过人之处呢
先簡單聊聊SpringMVC
如果你們玩知乎,很可能會看到我的身影。我經常會去知乎水回答。在知乎有很多初學者都會問的一個問題:「我學習SpringMVC需要什么樣的基礎」
我一定會讓他們先學Servlet,再學SpringMVC的。雖然說我們在現實開發中幾乎不會寫原生Servlet的代碼了,但我始終認為學完Servlet再學SpringMVC,對理解SpringMVC是有好處的。
三歪題外話:我當時在學SpringMVC之前其實已經接觸過另外一個web框架(當然了Servlet也是學了的),那就是「大名鼎鼎」的Struts2。只要是Struts2有的功能,SpringMVC都會有。
當時初學Struts2的時候用的是XML配置的方式去開發的,再轉到SpringMVC注解的時候,覺得SpringMVC真香。
Struts2在2020年已經不用學了,學SpringMVC的基礎是Servlet,只要Servlet基礎還行,上手SpringMVC應該不成問題。
從Servlet到SpringMVC,你會發現SpringMVC幫我們做了很多的東西,我們的代碼肯定是沒以前多了。
Servlet:
我們以前可能需要將傳遞進來的參數手動封裝成一個Bean,然后繼續往下傳:
SpringMVC:
現在SpringMVC自動幫我們將參數封裝成一個Bean
Servlet:
以前我們要導入其他的jar包去手動處理文件上傳的細節:
SpringMVC:
現在SpringMVC上傳文件用一個MultipartFile對象都給我們封裝好了
........
說白了,在Servlet時期我們這些活都能干,只不過SpringMVC把很多東西都給屏蔽了,于是我們用起來就更加舒心了。
在學習SpringMVC的時候實際上也是學習這些功能是怎么用的而已,并不會太難。這次整理的SpringMVC電子書其實也是在講SpringMVC是如何使用的
- 比如說傳遞一個日期字符串來,SpringMVC默認是不能轉成日期的,那我們可以怎么做來實現。
- SpringMVC的文件上傳是怎么使用的
- SpringMVC的攔截器是怎么使用的
- SpringMVC是怎么對參數綁定的
- ......
現在「電子書」已經放出來了,但是別急,重頭戲在后面。顯然,通過上面的電子書是可以知道SpringMVC是怎么用的。
但是這在面試的時候人家是不會問你SpringMVC的一些用法的,而SpringMVC面試問得最多的就是:SpringMVC請求處理的流程是怎么樣的。
其實也很簡單,流程就是下面這張圖:
再簡化一點,可以發現流程不復雜
在面試的時候甚至能一句話就講完了,但這夠嗎,這是面試官想要的嗎?那肯定不是。那我們想知道SpringMVC是做了什么嗎?想的吧(不管你們想不想,反正三歪想看)。
由于想要主流程更加清晰一點,我會在源碼添加部分注釋以及刪減部分的代碼
以@ResponseBody和@RequestBody的Controller代碼講解為主,這是線上環境用得最多的
DispatcherServlet源碼
首先我們看看DispatcherServlet的類結構,可以清楚地發現實際DispatcherServlet就是Servlet接口的一個子類(這也就是為什么網上這么多人說DispatcherServlet的原理實際上就是Servlet)
我們在DispatcherServlet類上可以看到很多熟悉的成員變量(組件),所以看下來,我們要的東西,DispatcherServlet可全都有。
//?文件處理器private?MultipartResolver?multipartResolver;
//?映射器
private?List?handlerMappings;//?適配器private?List?handlerAdapters;//?異常處理器private?List?handlerExceptionResolvers;//?視圖解析器private?List?viewResolvers;
然后我們會發現它們在initStrategies()上初始化:
protected?void?initStrategies(ApplicationContext?context)?{??initMultipartResolver(context);
??initLocaleResolver(context);
??initThemeResolver(context);
??initHandlerMappings(context);
??initHandlerAdapters(context);
??initHandlerExceptionResolvers(context);
??initRequestToViewNameTranslator(context);
??initViewResolvers(context);
??initFlashMapManager(context);
}
請求進到DispatcherServlet,其實全部都會打到doService()方法上。我們看看這個doService()方法做了啥:
protected?void?doService(HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?{??
??//?設置一些上下文...(省略一大部分)
??request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE,?new?FlashMap());
??request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE,?this.flashMapManager);
??try?{
??????//?調用doDispatch
???doDispatch(request,?response);
??}
??finally?{
???if?(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted())?{
????if?(attributesSnapshot?!=?null)?{
?????restoreAttributesAfterInclude(request,?attributesSnapshot);
????}
???}
??}
?}
所以請求會走到doDispatch(request, response);里邊,我們再進去看看:
protected?void?doDispatch(HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?{???HttpServletRequest?processedRequest?=?request;
???HandlerExecutionChain?mappedHandler?=?null;
???boolean?multipartRequestParsed?=?false;
???WebAsyncManager?asyncManager?=?WebAsyncUtils.getAsyncManager(request);
???try?{
??????ModelAndView?mv?=?null;
??????Exception?dispatchException?=?null;
??????try?{
?????????//?檢查是不是文件上傳請求
?????????processedRequest?=?checkMultipart(request);
?????????multipartRequestParsed?=?(processedRequest?!=?request);
?????????//?找到HandlerExecutionChain
?????????mappedHandler?=?getHandler(processedRequest);
?????????if?(mappedHandler?==?null?||?mappedHandler.getHandler()?==?null)?{
????????????noHandlerFound(processedRequest,?response);
????????????return;
?????????}
?????????//?得到對應的hanlder適配器
?????????HandlerAdapter?ha?=?getHandlerAdapter(mappedHandler.getHandler());
?????????//?攔截前置處理
?????????if?(!mappedHandler.applyPreHandle(processedRequest,?response))?{
????????????return;
?????????}
?????????//?真實處理請求
?????????mv?=?ha.handle(processedRequest,?response,?mappedHandler.getHandler());
?????????//?視圖解析器處理
?????????applyDefaultViewName(processedRequest,?mv);
????????
?????????//?攔截后置處理
?????????mappedHandler.applyPostHandle(processedRequest,?response,?mv);
??????}
??????catch?(Exception?ex)?{
?????????dispatchException?=?ex;
??????}
???}
}
這里的流程跟我們上面的圖的流程幾乎是一致的了。我們從源碼可以知道的是,原來SpringMVC的攔截器是在MappingHandler的時候一齊返回的,返回的是一個HandlerExecutionChain對象。這個對象也不難,我們看看:
public?class?HandlerExecutionChain?{?private?static?final?Log?logger?=?LogFactory.getLog(HandlerExecutionChain.class);
??//?真實的handler
?private?final?Object?handler;
??//?攔截器List
?private?HandlerInterceptor[]?interceptors;
?private?List?interceptorList;private?int?interceptorIndex?=?-1;
}
OK,整體的流程我們是已經看完了,順便要不我們去看看它是怎么找到handler的?三歪帶著你們沖!我們點進去getHandler()后,發現它就把默認實現的Handler遍歷一遍,然后選出合適的:
protected?HandlerExecutionChain?getHandler(HttpServletRequest?request)?throws?Exception?{?//?遍歷一遍默認的Handler實例,選出合適的就返回
??for?(HandlerMapping?hm?:?this.handlerMappings)?{
????HandlerExecutionChain?handler?=?hm.getHandler(request);
????if?(handler?!=?null)?{
??????return?handler;
????}
??}
??return?null;
}
再進去getHandler里邊看看唄,里邊又有幾層,我們最后可以看到它根據路徑去匹配,走到了lookupHandlerMethod這么一個方法
protected?HandlerMethod?lookupHandlerMethod(String?lookupPath,?HttpServletRequest?request)?throws?Exception?{??List?matches?=?new?ArrayList();//?獲取路徑
??List?directPathMatches?=?this.mappingRegistry.getMappingsByUrl(lookupPath);//?對匹配的排序,找到最佳匹配的if?(!matches.isEmpty())?{
???Comparator?comparator?=?new?MatchComparator(getMappingComparator(request));
???Collections.sort(matches,?comparator);if?(logger.isTraceEnabled())?{
????logger.trace("Found?"?+?matches.size()?+?"?matching?mapping(s)?for?["?+
??????lookupPath?+?"]?:?"?+?matches);
???}
???Match?bestMatch?=?matches.get(0);if?(matches.size()?>?1)?{if?(CorsUtils.isPreFlightRequest(request))?{return?PREFLIGHT_AMBIGUOUS_MATCH;
????}
????Match?secondBestMatch?=?matches.get(1);if?(comparator.compare(bestMatch,?secondBestMatch)?==?0)?{
?????Method?m1?=?bestMatch.handlerMethod.getMethod();
?????Method?m2?=?secondBestMatch.handlerMethod.getMethod();throw?new?IllegalStateException("Ambiguous?handler?methods?mapped?for?HTTP?path?'"?+
???????request.getRequestURL()?+?"':?{"?+?m1?+?",?"?+?m2?+?"}");
????}
???}
???handleMatch(bestMatch.mapping,?lookupPath,?request);return?bestMatch.handlerMethod;
??}else?{return?handleNoMatch(this.mappingRegistry.getMappings().keySet(),?lookupPath,?request);
??}
?}
找攔截器大概也是上面的一個過程,于是我們就可以順利拿到HandlerExecutionChain了,找到HandlerExecutionChain后,我們是先去拿對應的HandlerAdaptor。我們也去看看里邊做了什么:
//?遍歷HandlerAdapter實例,找到個合適的返回protected?HandlerAdapter?getHandlerAdapter(Object?handler)?throws?ServletException?{
??for?(HandlerAdapter?ha?:?this.handlerAdapters)?{
???if?(ha.supports(handler))?{
????return?ha;
???}
??}
?}
我們看一個常用HandlerAdapter實例RequestMappingHandlerAdapter,會發現他會初始化很多的參數解析器,其實我們經常用的@ResponseBody解析器就被內置在里邊:
private?List?getDefaultArgumentResolvers()?{??List?resolvers?=?new?ArrayList();
??resolvers.add(new?MatrixVariableMethodArgumentResolver());
??resolvers.add(new?MatrixVariableMapMethodArgumentResolver());
??resolvers.add(new?ServletModelAttributeMethodProcessor(false));//?ResponseBody?Requestbody解析器
??resolvers.add(new?RequestResponseBodyMethodProcessor(getMessageConverters(),?this.requestResponseBodyAdvice));
??resolvers.add(new?RequestPartMethodArgumentResolver(getMessageConverters(),?t//?等等return?resolvers;
?}
得到HandlerAdaptor后,隨之而行的就是攔截器的前置處理,然后就是真實的mv = ha.handle(processedRequest, response, mappedHandler.getHandler())。
這里邊嵌套了好幾層,我就不一一貼代碼了,我們會進入ServletInvocableHandlerMethod#invokeAndHandle方法,我們看一下這里邊做了什么:
public?void?invokeAndHandle(ServletWebRequest?webRequest,???ModelAndViewContainer?mavContainer,?Object...?providedArgs)?throws?Exception?{
???//?處理請求
??Object?returnValue?=?invokeForRequest(webRequest,?mavContainer,?providedArgs);
??setResponseStatus(webRequest);
??if?(returnValue?==?null)?{
???if?(isRequestNotModified(webRequest)?||?hasResponseStatus()?||?mavContainer.isRequestHandled())?{
????mavContainer.setRequestHandled(true);
????return;
???}
??}
???//..?
??mavContainer.setRequestHandled(false);
??try?{
??????//?處理返回值
???this.returnValueHandlers.handleReturnValue(
?????returnValue,?getReturnValueType(returnValue),?mavContainer,?webRequest);
??}
?}
處理請求的方法我們進去看看invokeForRequest
public?Object?invokeForRequest(NativeWebRequest?request,?ModelAndViewContainer?mavContainer,???Object...?providedArgs)?throws?Exception?{
??
???//?得到參數
??Object[]?args?=?getMethodArgumentValues(request,?mavContainer,?providedArgs);
??
???//?調用方法
??Object?returnValue?=?doInvoke(args);
??if?(logger.isTraceEnabled())?{
???logger.trace("Method?["?+?getMethod().getName()?+?"]?returned?["?+?returnValue?+?"]");
??}
??return?returnValue;
?}
我們看看它是怎么處理參數的,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????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))?{
????try?{
?????args[i]?=?this.argumentResolvers.resolveArgument(
???????parameter,?mavContainer,?request,?this.dataBinderFactory);
?????continue;
????}
????//.....
??}
??return?args;
?}
這些參數解析器實際上在HandlerAdaptor內置的那些,這里不好放代碼,所以我截個圖吧:
針對于RequestResponseBodyMethodProcessor解析器我們看看里邊做了什么:
public?Object?resolveArgument(MethodParameter?parameter,?ModelAndViewContainer?mavContainer,???NativeWebRequest?webRequest,?WebDataBinderFactory?binderFactory)?throws?Exception?{
????//?通過Converters對參數轉換
??Object?arg?=?readWithMessageConverters(webRequest,?parameter,?parameter.getGenericParameterType());
??String?name?=?Conventions.getVariableNameForParameter(parameter);
??WebDataBinder?binder?=?binderFactory.createBinder(webRequest,?arg,?name);
??//?...
??mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX?+?name,?binder.getBindingResult());
??return?arg;
?}
再進去readWithMessageConverters里邊看看:
protected??Object?readWithMessageConverters(HttpInputMessage?inputMessage,?MethodParameter?param,???Type?targetType)?throws?IOException,?HttpMediaTypeNotSupportedException,?HttpMessageNotReadableException?{//?...處理請求頭try?{
???inputMessage?=?new?EmptyBodyCheckingHttpInputMessage(inputMessage);//?HttpMessageConverter實例去對參數轉換for?(HttpMessageConverter>?converter?:?this.messageConverters)?{
????Class>?converterType?=?(Class>)?converter.getClass();if?(converter?instanceof?GenericHttpMessageConverter)?{
?????GenericHttpMessageConverter>?genericConverter?=?(GenericHttpMessageConverter>)?converter;if?(genericConverter.canRead(targetType,?contextClass,?contentType))?{if?(logger.isDebugEnabled())?{
???????logger.debug("Read?["?+?targetType?+?"]?as?\""?+?contentType?+?"\"?with?["?+?converter?+?"]");
??????}if?(inputMessage.getBody()?!=?null)?{
???????inputMessage?=?getAdvice().beforeBodyRead(inputMessage,?param,?targetType,?converterType);
???????body?=?genericConverter.read(targetType,?contextClass,?inputMessage);
???????body?=?getAdvice().afterBodyRead(body,?inputMessage,?param,?targetType,?converterType);
??????}else?{
???????body?=?null;
???????body?=?getAdvice().handleEmptyBody(body,?inputMessage,?param,?targetType,?converterType);
??????}break;
?????}
????}//...各種判斷return?body;
?}
看到這里,有沒有看不懂,想要退出的感覺了??別慌,三歪帶你們看看這份熟悉的配置:
?<bean?class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
??<property?name="messageConverters">
???<list>
????<ref?bean="jacksonMessageConverter"?/>
???list>
??property>
?bean>
?<bean?id="jacksonMessageConverter"class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
??<property?name="supportedMediaTypes">
???<list>
????<value>text/html;charset=UTF-8value>
????<value>application/json;charset=UTF-8value>
????<value>application/x-www-form-urlencoded;charset=UTF-8value>
???list>
??property>
??<property?name="objectMapper"?ref="jacksonObjectMapper"?/>
?bean>
?<bean?id="jacksonObjectMapper"?class="com.fasterxml.jackson.databind.ObjectMapper"?/>
我們在SpringMVC想要使用@ResponseBody返回JSON格式都會在配置文件上配置上面的配置,RequestMappingHandlerAdapter這個適配器就是上面所說的那個,內置了RequestResponseBodyMethodProcessor解析器,然后MappingJackson2HttpMessageConverter實際上就是HttpMessageConverter接口的實例
然后在返回的時候也經過HttpMessageConverter去將參數轉換后,寫給HTTP響應報文。轉換的流程大致如圖所示:
img視圖解析器后面就不貼了,大概的流程就如上面的源碼,我再畫個圖來加深一下理解吧:
最后
SpringMVC我們使用的時候非常簡便,在內部實際上幫我們做了很多(有各種的HandlerAdaptor),SpringMVC的請求流程面試的時候還是面得很多的,還是可以看看源碼它幫我們做了什么,過一遍可能會發現自己能看懂以前的配置了。
關注我
覺得有點東西就點一下“贊和在看”吧!感謝大家的支持了!總結
以上是生活随笔為你收集整理的springmvc 传对象报400_那么火的SpringMVC到底有什么过人之处呢的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 油管螺纹尺寸对照表_yt15硬质合金刀片
- 下一篇: sq工程师是做什么的_算法工程师为什么成