只有光頭才能變強。文本已收錄至個人GitHub精選文章,歡迎Star:https://github.com/ZhongFuCheng3y/3yhtml
這篇SpringMVC
被催了好久了,這陣子因爲作整合系統的事,因此很是很是地忙。這週末早早就回了公司肝這篇文章了。java
若是關注三歪的同窗會發現,三歪最近寫的不少文章都是結合了現有的系統去寫的。這些問題都是真實開發場景會遇到的、用的上的,這些案例對未工做的同窗幫助應該仍是蠻大的。git
很少BB了,仍是進入今天的正題吧「SpringMVC」github
若是大家玩知乎,極可能會看到個人身影。我常常會去知乎水回答。在知乎有不少初學者都會問的一個問題:「我學習SpringMVC須要什麼樣的基礎」web
我必定會讓他們先學Servlet,再學SpringMVC的。雖說咱們在現實開發中幾乎不會寫原生Servlet的代碼了,但我始終認爲學完Servlet再學SpringMVC,對理解SpringMVC是有好處的。面試
三歪題外話:我當時在學SpringMVC以前其實已經接觸過另一個web框架(固然了Servlet也是學了的),那就是「大名鼎鼎」的Struts2。只要是Struts2有的功能,SpringMVC都會有。當時初學Struts2的時候用的是XML配置的方式去開發的,再轉到SpringMVC註解的時候,以爲SpringMVC真香。算法
Struts2在2020年已經不用學了,學SpringMVC的基礎是Servlet,只要Servlet基礎還行,上手SpringMVC應該不成問題。spring
從Servlet到SpringMVC,你會發現SpringMVC幫咱們作了不少的東西,咱們的代碼確定是沒之前多了。json
Servlet:後端
咱們之前可能須要將傳遞進來的參數手動封裝成一個Bean,而後繼續往下傳:
SpringMVC:
如今SpringMVC自動幫咱們將參數封裝成一個Bean
Servlet:
之前咱們要導入其餘的jar
包去手動處理文件上傳的細節:
SpringMVC:
如今SpringMVC上傳文件用一個MultipartFile對象都給咱們封裝好了
........
說白了,在Servlet時期咱們這些活都能幹,只不過SpringMVC把不少東西都給屏蔽了,因而咱們用起來就更加舒心了。
在學習SpringMVC的時候實際上也是學習這些功能是怎麼用的而已,並不會太難。此次整理的SpringMVC電子書其實也是在講SpringMVC是如何使用的
如今「電子書」已經放出來了,可是別急,重頭戲在後面。顯然,經過上面的電子書是能夠知道SpringMVC是怎麼用的。
可是這在面試的時候人家是不會問你SpringMVC的一些用法的,而SpringMVC面試問得最多的就是:SpringMVC請求處理的流程是怎麼樣的。
其實也很簡單,流程就是下面這張圖:
再簡化一點,能夠發現流程不復雜
在面試的時候甚至能一句話就講完了,但這夠嗎,這是面試官想要的嗎?那確定不是。那咱們想知道SpringMVC是作了什麼嗎?想的吧(無論大家想不想,反正三歪想看)。
因爲想要主流程更加清晰一點,我會在源碼添加部分註釋以及刪減部分的代碼
以@ResponseBody和@RequestBody的Controller代碼講解爲主,這是線上環境用得最多的
首先咱們看看DispatcherServlet的類結構,能夠清楚地發現實際DispatcherServlet就是Servlet接口的一個子類(這也就是爲何網上這麼多人說DispatcherServlet的原理實際上就是Servlet)
咱們在DispatcherServlet類上能夠看到不少熟悉的成員變量(組件),因此看下來,咱們要的東西,DispatcherServlet可全都有。
// 文件處理器 private MultipartResolver multipartResolver; // 映射器 private List<HandlerMapping> handlerMappings; // 適配器 private List<HandlerAdapter> handlerAdapters; // 異常處理器 private List<HandlerExceptionResolver> handlerExceptionResolvers; // 視圖解析器 private List<ViewResolver> 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<HandlerInterceptor> 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<Match> matches = new ArrayList<Match>(); // 獲取路徑 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); // 對匹配的排序,找到最佳匹配的 if (!matches.isEmpty()) { Comparator<Match> 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<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); 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 < 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)) { 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 <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter param, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { // ...處理請求頭 try { inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage); // HttpMessageConverter實例去對參數轉換 for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) 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; }
看到這裏,有沒有看不懂,想要退出的感受了??別慌,三歪帶大家看看這份熟悉的配置:
<!-- 啓動JSON返回格式 --> <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-8</value> <value>application/json;charset=UTF-8</value> <value>application/x-www-form-urlencoded;charset=UTF-8</value> </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響應報文。轉換的流程大體如圖所示:
視圖解析器後面就不貼了,大概的流程就如上面的源碼,我再畫個圖來加深一下理解吧:
SpringMVC咱們使用的時候很是簡便,在內部實際上幫咱們作了不少(有各類的HandlerAdaptor),SpringMVC的請求流程面試的時候仍是面得不少的,仍是能夠看看源碼它幫咱們作了什麼,過一遍可能會發現本身能看懂之前的配置了。
參考資料:
若是你們想要實時關注我更新的文章以及分享的乾貨的話,微信搜索Java3y。