1. 程式人生 > >這一次搞懂SpringMVC原理

這一次搞懂SpringMVC原理

@[toc] # 前言 前面幾篇文章,學習了Spring IOC、Bean例項化過程、AOP、事務的原始碼和設計思想,瞭解了Spring的整體執行流程,但如果是web開發,那麼必不可少的還有Spring MVC,本篇主要分析在請求呼叫過程中SpringMVC的實現原理,通過本篇要搞懂它是怎麼解決請求、引數、返回值對映等問題的。 # 正文 ## 請求入口 我們都知道前端呼叫後端介面時,都會通過**Servlet**進行轉發,而**Servlet**的宣告週期包含下面四個階段: - 例項化(new) - 初始化(init) - 執行(service呼叫doGet/doPost) - 銷燬(destroy) 前兩個階段在Spring啟動階段就做好了(init根據配置可能是第一次請求時才會呼叫),銷燬是服務關閉的時候進行,本文主要分析的就是請求執行階段。我們知道SpringMVC的核心就是**DispatcherServlet**,該類是對**Servlet**的擴充套件,所以直接從該類的**service**方法開始,但在此類中沒有**service**方法,那肯定是在其父類中,我們先來看看其繼承體系: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200615201159713.png#pic_center) 逐個往上找,在**FrameworkServlet**方法中就有一個**service**方法: ```java protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (httpMethod == HttpMethod.PATCH || httpMethod == null) { processRequest(request, response); } else { super.service(request, response); } } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } ``` 但其主要還是呼叫父類**HttpServlet**中的方法,而該類又會根據不同的請求方式會調到子類中,最後的核心方法就是**DispatcherServlet**中的**doDispatch**方法: ```java 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); //這個方法很重要,重點看 // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } //獲取跟HandlerMethod匹配的HandlerAdapter物件 // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //前置過濾器,如果為false則直接返回 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //呼叫到Controller具體方法,核心方法呼叫,重點看看 // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); //中置過濾器 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //檢視渲染及後置過濾器執行 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } ``` MVC的所有處理邏輯都在這個方法中,先總結一下這個方法的實現邏輯,首先根據請求的url拿到快取中的**HandlerMethod**物件和**執行鏈**物件,**HandlerMethod**中封裝了controller物件、方法物件和方法引數等資訊,**執行鏈**則是包含了一個個**HandlerInterceptor**攔截器;然後再通過**HandlerMethod**拿到對應的**HandlerAdapter**,這個物件的作用就是去適配我們的controller;準備工作做完後,首先會執行**前置過濾**,如果被攔截則直接返回,否則就去呼叫controller中的方法執行我們的業務邏輯並返回一個**ModelView**物件;接著執行**中置過濾器**,以及處理全域性異常捕獲器捕獲到異常;最後進行**檢視渲染**返回並執行**後置過濾器**進行資源釋放等工作。 以上就是MVC的整體執行流程,下面就逐個來分析,首先進入**getHandler**方法: ```java protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //handlerMappering例項 if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { //獲取HandlerMethod和過濾器鏈的包裝類 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; } ``` 是委託給**HandlerMapping**物件的,這是一個介面,主要的實現類是**RequestMappingHandlerMapping**,同樣先來看看其繼承體系: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200615203440128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2w2MTA4MDAz,size_16,color_FFFFFF,t_70#pic_center) 這個類是管理請求和處理類之間的對映關係的,你是否疑惑它是在哪裡例項化的呢?下面先來看看MVC元件的初始化。 ## 元件初始化 這裡我以自動化配置的註解方式說明,Spring提供了一個@EnableWebMvc,通過前面的學習我們知道在這個註解中必定匯入了一個配置類,點進去可以看到是**DelegatingWebMvcConfiguration**,這個類就是負責MVC的元件和擴充套件實現的初始化,其本身我們先不看,先看其父類**WebMvcConfigurationSupport**,這個類我們應該不陌生,要做一些自定義擴充套件時就需要繼承該類(如攔截器Interceptor),同樣作用的類還有**WebMvcConfigurerAdapter**,這個類是對前者**相對安全**的擴充套件,為什麼是相對安全呢?因為繼承前者會導致自動配置失效,而使用後者則不必擔心此問題,只需要在類上加上@EnableWebMvc註解。 在**WebMvcConfigurationSupport**中我們可以看到很多@Bean標註的方法,也就是mvc元件的例項化,這裡主要看看**requestMappingHandlerMapping**,其餘的可自行閱讀理解,也就是一些Bean的註冊: ```java public RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); mapping.setOrder(0); mapping.setInterceptors(getInterceptors()); mapping.setContentNegotiationManager(mvcContentNegotiationManager()); mapping.setCorsConfigurations(getCorsConfigurations()); ......省略 return mapping; } ``` 這裡主要看**getInterceptors**方法如何獲取攔截器的: ```java protected final Object[] getInterceptors() { if (this.interceptors == null) { InterceptorRegistry registry = new InterceptorRegistry(); //鉤子方法,需要自己定義 addInterceptors(registry); registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService())); registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider())); this.interceptors = registry.getInterceptors(); } return this.interceptors.toArray(); } ``` 第一次進來會呼叫**addInterceptors**新增攔截器,這是一個模板方法,在子類**DelegatingWebMvcConfiguration**中實現: ```java private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); protected void addInterceptors(InterceptorRegistry registry) { this.configurers.addInterceptors(registry); } public void addInterceptors(InterceptorRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addInterceptors(registry); } } ``` 可以看到最終是呼叫**WebMvcConfigurer**的**addInterceptors**方法,也就是我們對**WebMvcConfigurerAdapter**的自定義擴充套件。看到這裡我們應該明白了MVC的元件是如何新增到IOC容器中的,但是**DispatcherServlet**又是怎麼獲取到它們的呢?回到之前的程式碼中,在**DispatcherServlet**這個類中有一個**onRefresh**方法,這個方法又呼叫了**initStrategies**方法完成了MVC九大元件的註冊: ```java protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); } private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts