Spring MVC框架在工做中常常用到,配置簡單,使用起來也很方便,不少書籍和博客都有介紹其處理流程,可是,對於其原理,老是似懂非懂的樣子。咱們作技術,須要作到知其然,還要知其因此然。今天咱們結合源碼來深刻了解一下Spring MVC的處理流程。java
以上流程圖是Spring MVC的處理流程(參考:spring-mvc-flow-with-example),原做者對流程的解釋以下:git
Step 1: First request will be received by DispatcherServlet. Step 2: DispatcherServlet will take the help of HandlerMapping and get to know the Controller class name associated with the given request. Step 3: So request transfer to the Controller, and then controller will process the request by executing appropriate methods and returns ModelAndView object (contains Model data and View name) back to the DispatcherServlet. Step 4: Now DispatcherServlet send the model object to the ViewResolver to get the actual view page. Step 5: Finally DispatcherServlet will pass the Model object to the View page to display the result.
針對以上流程,這裏須要更加詳細一點:github
一、請求被web 容器接收,而且根據contextPath將請求發送給DispatcherServletweb
二、DispatcherServlet接收到請求後,會設置一些屬性(localeResolver、themeResolver等等),在根據request在handlerMappings中查找對應的HandlerExecutionChain;而後根據HandlerExecutionChain中的handler來找到HandlerAdapter,而後經過反射來調用handler中的對應方法(RequestMapping對應的方法)spring
三、handler就是對應的controller,調用controller中的對應方法來進行業務邏輯處理,返回ModelAndView(或者邏輯視圖名稱)apache
四、ViewResolver根據邏輯視圖名稱、視圖先後綴,來獲取實際的邏輯視圖瀏覽器
五、獲取實際視圖以後,就會使用model來渲染視圖,獲得用戶實際看到的視圖,而後返回給客戶端。spring-mvc
咱們運行一個小樣例(github地址:https://github.com/yangjianzhou/spring-mvc-demo)來了解Spring MVC處理流程,項目結構以下:緩存
web.xml配置以下:tomcat
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>smart</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>smart</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
smart-servlet.xml的內容以下:
<context:component-scan base-package="com.iwill.mvc"/> <!-- 在使用Excel PDF的視圖時,請先把這個視圖解析器註釋掉,不然產生視圖解析問題--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="100" p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="/WEB-INF/views/" p:suffix=".jsp"/>
UserController.java的代碼以下:
package com.iwill.mvc; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/user") public class UserController { Logger logger = Logger.getLogger(UserController.class); @RequestMapping("register") public String register() { logger.info("invoke register"); return "user/register"; } @RequestMapping(method = RequestMethod.POST) public ModelAndView createUser(User user) { ModelAndView mav = new ModelAndView(); mav.setViewName("user/createSuccess"); mav.addObject("user", user); return mav; } }
DispatcherServlet的類繼承關係以下:
能夠看出,DispatcherServlet是一個HttpServlet,所以,它能夠處理http請求。
在瀏覽器中輸入http://localhost:8080/spring-mvc-demo/user/register,由於在web服務器上配置了spring-mvc-demo的contextPath爲spring-mvc-demo,因此/spring-mvc-demo/user/register的請求就會被DispatcherServlet處理,請求處理路徑以下:
請求由tomcat傳遞給了DispatcherServlet了,DispatcherServlet接收後,就開始本身的特殊處理了。
紅框中是Spring MVC本身特有的邏輯,主要是與視圖、主題有關。
接下來的主要處理邏輯在org.springframework.web.servlet.DispatcherServlet#doDispatch中:
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; // 根據request在handlerMappings中獲取HandlerExecutionChain mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } //根據handler在handlerAdapters中獲取HandlerAdapter 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 (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { //適配器調用實際的handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //邏輯視圖名轉換爲物理視圖名,並進行視圖渲染 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
首先獲取HandlerExecutionChain(入口:mappedHandler = getHandler(processedRequest, false);),方法以下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
以後就是根據handler獲取HandlerAdapter(入口:HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())):
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
handler適配器調用handler的方法(入口:mv = ha.handle(processedRequest, response, mappedHandler.getHandler())):
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ServletHandlerMethodResolver methodResolver = getMethodResolver(handler); Method handlerMethod = methodResolver.resolveHandlerMethod(request); ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver); ServletWebRequest webRequest = new ServletWebRequest(request, response); ExtendedModelMap implicitModel = new BindingAwareModelMap(); Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel); ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest); methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest); return mav; }
經過Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel)會調用底層的方法:
紅框中,經過反射調用UserController的register方法。這樣請求就被傳遞到了實際的controller方法了。
UserController#register處理後,就返回邏輯視圖名:user/register。在org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodInvoker#getModelAndView中,就會將String轉化爲ModelAndView:
在org.springframework.web.servlet.DispatcherServlet#render中,就會將邏輯視圖ModelAndView轉化物理視圖。
resolveViewName就是使用ViewResolver來獲取物理視圖名:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; }
物理視圖名會被緩存,不須要重複解析,提升性能。
這裏就是prefix和suffix的用途了,用於定位實際視圖。
獲取到了物理視圖以後,就進行視圖渲染了。
針對jsp格式的視圖,咱們配置的view是org.springframework.web.servlet.view.JstlView,渲染過程就是將model中的值set到request的attribute中,以後就是使用jsp本身的規則來顯示jsp文件就好。