核心類與接口
- DispatcherServlet 前置控制器
- HandlerMapping 請求映射(到Controller)
- HandlerAdapter 請求映射(到Controller類的方法上)
- Controller 控制器
- HandlerIntercepter 攔截器
- ViewResolver 視圖映射
- View 視圖處理
啟動過程
Spring MVC啟動過程大致分為兩個過程:
- ContextLoaderListener初始化,讀取context-param中的contextConfigLocation指定的配置文件,創建ROOT Context,通過調用繼承自ContextLoader的initWebApplicationContext方法實例化Spring IoC容器,並將此容器實例注冊到ServletContext中
- ContextLoaderListener初始化完畢後,開始初始化web.xml中配置的DispatcherServlet,DispatcherServlet的初始化又包括了視圖管理器、異常處理器、映射管理等等;
1 /** 2 * Initialize the strategy objects that this servlet uses. 3 * <p>May be overridden in subclasses in order to initialize further strategy objects. 4 */ 5 protected void initStrategies(ApplicationContext context) { 6 initMultipartResolver(context); 7 initLocaleResolver(context); 8 initThemeResolver(context); 9 initHandlerMappings(context); 10 initHandlerAdapters(context); 11 initHandlerExceptionResolvers(context); 12 initRequestToViewNameTranslator(context); 13 initViewResolvers(context); 14 initFlashMapManager(context); 15 }
ContextLoaderListener初始化的是 WebApplicationContext, 創建後可以從ServletContext中獲取,WebApplicationContext是應用程序內共享的,最多只有一個,如果尋求簡單也可以不初始化此容器。與之不同 DispatcherServlet可以有多個,並共享一個WebApplicationContext容器,每一個DispatcherServlet有不同的配置,控制不同的WEB訪問。一般將 DAO、Service 層Bean共享的放在ContextLoaderListener配置的容器中,將WEB層的Bean放在特定的DispatcherServlet配置的容器中。
SpringMVC利用Spring的注入特性初始化資源文件,只需要調用setPropertyValues方法就可將contextConfigLocation屬性設置到對應實例中,也就是以依賴注入的方式初始化屬性。
時序圖如下(盜圖):
請求處理流程
官網上的圖
涉及到核心類與接口的過程描述:
客戶端浏覽器發送http請求,被`DispatcherServlet`捕獲,調用關鍵的doDispatch方法,遍歷所有注冊為`Controller`的bean,為請求尋找關聯映射,其中遍歷查找的函數getHandler和getHandlerAdapter的源碼:
1 /** 2 * Return the HandlerExecutionChain for this request. 3 * <p>Tries all handler mappings in order. 4 * @param request current HTTP request 5 * @return the HandlerExecutionChain, or {@code null} if no handler could be found 6 */ 7 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 8 for (HandlerMapping hm : this.handlerMappings) { 9 if (logger.isTraceEnabled()) { 10 logger.trace( 11 "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); 12 } 13 HandlerExecutionChain handler = hm.getHandler(request); 14 if (handler != null) { 15 return handler; 16 } 17 } 18 return null; 19 } 20 21 22 /** 23 * Return the HandlerAdapter for this handler object. 24 * @param handler the handler object to find an adapter for 25 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error. 26 */ 27 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { 28 for (HandlerAdapter ha : this.handlerAdapters) { 29 if (logger.isTraceEnabled()) { 30 logger.trace("Testing handler adapter [" + ha + "]"); 31 } 32 if (ha.supports(handler)) { 33 return ha; 34 } 35 } 36 throw new ServletException("No adapter for handler [" + handler + 37 "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); 38 }
找到匹配的映射後`HandlerAdapter`會依次調用preHandle、handle(返回ModelAndView)、postHandle方法,所有步驟完成後調用processDispatchResult函數處理結果,並返回View給客戶端。postDispatchResult函數和其中調用的render函數源碼如下:
1 /** 2 * Handle the result of handler selection and handler invocation, which is 3 * either a ModelAndView or an Exception to be resolved to a ModelAndView. 4 */ 5 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 6 HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { 7 8 boolean errorView = false; 9 10 if (exception != null) { 11 if (exception instanceof ModelAndViewDefiningException) { 12 logger.debug("ModelAndViewDefiningException encountered", exception); 13 mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 14 } 15 else { 16 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 17 mv = processHandlerException(request, response, handler, exception); 18 errorView = (mv != null); 19 } 20 } 21 22 // Did the handler return a view to render? 23 if (mv != null && !mv.wasCleared()) { 24 render(mv, request, response); 25 if (errorView) { 26 WebUtils.clearErrorRequestAttributes(request); 27 } 28 } 29 else { 30 if (logger.isDebugEnabled()) { 31 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 32 "': assuming HandlerAdapter completed request handling"); 33 } 34 } 35 36 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 37 // Concurrent handling started during a forward 38 return; 39 } 40 41 if (mappedHandler != null) { 42 mappedHandler.triggerAfterCompletion(request, response, null); 43 } 44 } 45 46 47 /** 48 * Render the given ModelAndView. 49 * <p>This is the last stage in handling a request. It may involve resolving the view by name. 50 * @param mv the ModelAndView to render 51 * @param request current HTTP servlet request 52 * @param response current HTTP servlet response 53 * @throws ServletException if view is missing or cannot be resolved 54 * @throws Exception if there's a problem rendering the view 55 */ 56 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 57 // Determine locale for request and apply it to the response. 58 Locale locale = this.localeResolver.resolveLocale(request); 59 response.setLocale(locale); 60 61 View view; 62 if (mv.isReference()) { 63 // We need to resolve the view name. 64 view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); 65 if (view == null) { 66 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + 67 "' in servlet with name '" + getServletName() + "'"); 68 } 69 } 70 else { 71 // No need to lookup: the ModelAndView object contains the actual View object. 72 view = mv.getView(); 73 if (view == null) { 74 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + 75 "View object in servlet with name '" + getServletName() + "'"); 76 } 77 } 78 79 // Delegate to the View object for rendering. 80 if (logger.isDebugEnabled()) { 81 logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); 82 } 83 try { 84 if (mv.getStatus() != null) { 85 response.setStatus(mv.getStatus().value()); 86 } 87 view.render(mv.getModelInternal(), request, response); 88 } 89 catch (Exception ex) { 90 if (logger.isDebugEnabled()) { 91 logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + 92 getServletName() + "'", ex); 93 } 94 throw ex; 95 } 96 }
這就是一個完整的處理http請求的過程。盜圖一張:
時序圖如下(來源:http://neoremind.com/2016/02/springmvc%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B8%B8%E7%94%A8%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/):
配置實例
這裡放的是最簡單的配置,可以通過這個簡單的配置實例回顧一下上面的過程。
目錄結構
-SpringMVCDemo
-src
-me.cyan
-WelcomeController
-web
-WEB-INF
-applicationContext.xml
-dispatcher-servlet.xml
-web.xml
-index.jsp
-pom.xml
pom.xml
引入的包
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <groupId>me.cyan</groupId> 6 <artifactId>SpringMVCDemo</artifactId> 7 <name>SpringMVCDemo</name> 8 <packaging>war</packaging> 9 <version>1.0.0</version> 10 11 <properties> 12 <spring-version>4.2.6.RELEASE</spring-version> 13 </properties> 14 15 <dependencies> 16 <dependency> 17 <groupId>org.springframework</groupId> 18 <artifactId>spring-context</artifactId> 19 <version>${spring-version}</version> 20 </dependency> 21 <dependency> 22 <groupId>org.springframework</groupId> 23 <artifactId>spring-core</artifactId> 24 <version>${spring-version}</version> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework</groupId> 28 <artifactId>spring-web</artifactId> 29 <version>${spring-version}</version> 30 </dependency> 31 <dependency> 32 <groupId>org.springframework</groupId> 33 <artifactId>spring-beans</artifactId> 34 <version>${spring-version}</version> 35 </dependency> 36 <dependency> 37 <groupId>commons-logging</groupId> 38 <artifactId>commons-logging</artifactId> 39 <version>1.2</version> 40 </dependency> 41 <dependency> 42 <groupId>org.springframework</groupId> 43 <artifactId>spring-webmvc</artifactId> 44 <version>${spring-version}</version> 45 </dependency> 46 <dependency> 47 <groupId>org.springframework</groupId> 48 <artifactId>spring-aop</artifactId> 49 <version>${spring-version}</version> 50 </dependency> 51 <dependency> 52 <groupId>org.springframework</groupId> 53 <artifactId>spring-expression</artifactId> 54 <version>${spring-version}</version> 55 </dependency> 56 </dependencies> 57 </project>
web.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 5 version="3.1"> 6 7 <!--配置文件路徑--> 8 <context-param> 9 <param-name>contextConfigLocation</param-name> 10 <param-value>/WEB-INF/applicationContext.xml</param-value> 11 </context-param> 12 <listener> 13 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 14 </listener> 15 16 <!--SpringMVC核心servlet--> 17 <servlet> 18 <servlet-name>dispatcher</servlet-name> 19 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 20 <load-on-startup>1</load-on-startup> 21 </servlet> 22 <servlet-mapping> 23 <servlet-name>dispatcher</servlet-name> 24 <url-pattern>/</url-pattern> 25 </servlet-mapping> 26 </web-app>
dispatcher-servlet.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 6 7 <!-- 默認的注解映射的支持 --> 8 <mvc:annotation-driven /> 9 10 <!-- 自動掃描的包名 --> 11 <context:component-scan base-package="me.cyan" /> 12 13 </beans>
WelcomeController
1 package me.cyan; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.bind.annotation.ResponseBody; 6 7 /** 8 * Created by cyan on 16/5/23. 9 */ 10 11 @Controller 12 public class welcomeController { 13 14 @RequestMapping("/hello") 15 public @ResponseBody String sayhello(){ 16 return "hello Spring MVC!"; 17 } 18 }
運行結果
文章出自:http://www.cnblogs.com/verlen11/p/5521536.html