Spring源碼深度解析之Spring MVChtml
Spring框架提供了構建Web應用程序的全功能MVC模塊。經過策略接口,Spring框架是高度可配置的,並且支持多種視圖技術,例如JavaServer pages(JSP)技術、Velocity、Tiles、iText和POI。Spring MVC框架並不知道使用的視圖,因此不會強迫您只使用JSP技術。Spring MVC分離了控制器、模型對象、分派器以及處理程序對象的角色,這種分離讓它們更容易進行定製。前端
Spring的MVC是基於Servlet功能實現的。經過實現Servlet接口的DispatcherServlet來封裝其核心功能實現,經過將請求分派給處理程序,同時帶有可配置的處理程序映射、視圖解析、本地語言、主題解析以及上載文件的支持。默認的處理程序是很是簡單的Controller接口,只有一個方法ModelAndView handleRequest(request, response)。Spring提供了一個控制器層次結構,能夠派生子類。若是應用程序須要處理用戶輸入表單,那麼能夠繼承AbstractFormController。若是須要把多頁輸入處理到一個表單,那麼能夠繼承AbstractWizardFormController。java
Spring MVC或者其餘比較成熟的MVC框架而言,解決的問題無外乎如下幾點:ios
(1)將Web頁面的請求傳給服務器。web
(2)根據不一樣的請求處理不一樣的邏輯單元。spring
(3)返回處理結果數據並跳轉到響應的頁面。數據庫
咱們先經過一個簡單示例來快速回顧Spring MVC的使用。apache
1、 Spring MVC快速體驗編程
(一)配置web.xml瀏覽器
一個Web中能夠沒有web.xml文件,也就是說,web.xml文件並非Web工廠必須的。web.xml文件用來初始化配置信息:好比Welcome頁面、servlet、servlet-mapping、filter、listener、啓動加載級別等。可是,Spring MVC的實現原理就是經過Servlet攔截全部URL來達到控制的目的,因此web.xml的配置是必須的。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app id="WebApp_ID" version="2.5" xmlns="http://java.sun.com/xml/ns/j2ee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 5 <display-name>Springmvc</display-name> 6 7 <!--使用ContextLoaderListener配置時,須要告訴他Spring配置文件的位置--> 8 <context-param> 9 <param-name>contextConfigLocation</param-name> 10 <param-value>classpath:applicationContext.xml</param-value> 11 </context-param> 12 13 <!--Spring MVC的前端控制器--> 14 <!--當DispatcherServlet載入後,它將從一個XML文件中載入Spring的應用上下文,該XMl文件的名字取決於<servlet-name>--> 15 <!--這裏DispatcherServlet將試圖從一個叫作Springmvc-servlet.xml的文件中載入應用上下文,其默認位於WEB-INF目錄下--> 16 <servlet> 17 <servlet-name>Springmvc</servlet-name> 18 <servlet-class>org.Springframework.web.servlet.DispatcherServlet</servlet-class> 19 <load-on-startup>1</load-on-startup> 20 </servlet> 21 <servlet-mapping> 22 <servlet-name>Springmvc</servlet-name> 23 <url-pattern>*.htm</url-pattern> 24 </servlet-mapping> 25 26 <!--配置上下文載入器--> 27 <!--上下文載入器載入除DispatcherServlet載入的配置文件以外的其它上下文配置文件--> 28 <!--最經常使用的上下文載入器是一個Servlet監聽器,其名稱爲ContextLoaderListener--> 29 <listener> 30 <listener-class>org.Springframework.web.context.ContextLoaderListener</listener-class> 31 </listener> 32 33 </web-app>
Spring的MVC之因此必需要配置web.xml,其實最關鍵的是要配置兩個地方。
(1)contextConfigLocation:Spring的核心就是配置文件,能夠說配置文件是Spring中必不可少的東西,而這個參數就是使Web與Spring的配置文件相結合的一個關鍵配置。
(2)DispatcherServlet:包含了SpringMVC的請求邏輯,Spring使用此類攔截Web請求並進行相應的邏輯處理。
(二)建立Spring配置文件applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmls:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:tx="http://www.springframework.org/schema/tx" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 7 http://www.springframework.org/schema/tx 8 http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 9 10 <bean id="viewResolver" class="org.springframework.web.servlet.view.Internal Resource ViewResolver"> 11 <property name="prefix" value="/WEB-INF/jsp/"/> 12 <property name="suffix" value=".jsp"/> 13 </bean> 14 15 </beans>
InternalResourceViewResolver是一個輔助Bean,會在ModelAndView返回的視圖名前加上prefix指定的前綴,再在最後加上suffix指定的後綴,例如:因爲XXController返回的ModelAndView中的視圖名師testview。故該視圖解析器將在/WEB-INF/jsp/testview.jsp處查找視圖。
(三)建立model
模型對於Spring MVC來講是必不可少,若是處理程序很是簡單,徹底能夠忽略。模型建立的主要目的就是承載數據,使數據傳輸更加方便。
1 public class User{ 2 private String username; 3 private Integer age; 4 5 public String getUsername(){ 6 return username; 7 } 8 public void setUsername(String username){ 9 this.username = username; 10 } 11 12 public Integer getAge() { 13 return age; 14 } 15 public void setAge(Integer age){ 16 this.age = age; 17 } 18 }
(四)建立Controller
控制器用於處理Web請求,每一個控制器都對應着一個邏輯處理。
1 public class UserController extends AbstractController{ 2 @Override 3 protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception{ 4 List<User> userList = new ArrayList<User>(); 5 User userA = new User(); 6 User userB = new User(); 7 userA.setUsername("張三"); 8 userA.setAge(27); 9 userB.setUsername("李四"); 10 userB.setAge(37); 11 userList.add(userA); 12 userList.add(userB); 13 14 return new ModelAndView("userlist", "users", userList); 15 } 16 }
在請求的最後返回了ModelAndView類型的實例。ModelAndView類在Spring MVC中佔有很重要的地位。控制器執行方法都必須返回一個ModelAndView,ModelAndView對象保存了視圖以及視圖顯示的模型數據,例如其中的參數以下:
第一個參數userlist:視圖組件的邏輯名稱。這裏視圖的邏輯名稱就是userlist,視圖解析器會使用該名稱查找實際的View對象。
第二個參數users:傳遞給視圖的,模型對象的名稱。
第三個參數userList:傳遞給視圖的,模型對象的值。
(五)建立視圖文件userlist.jsp
1 <%@ page language="java" pageEncoding="UTF-8" %> 2 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 3 <h2>This is SpringMVC demo page</h2> 4 <c:forEach item="${users}" var="user"> 5 <c:out value="${user.username}"/><br/> 6 <c:out value="${usr.age}"/><br/> 7 </c:forEach>
視圖文件用於展示請求處理結果,經過對JSTL的支持,能夠很方便地展示在控制器中放入ModelAndView中處理結果數據。
(六)建立Servlet配置文件Spring-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" 4 xmlns:tx="http://www.springframework.org/schema/tx" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 7 http://www.springframework.org/schema/tx 8 http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 9 10 <bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 11 <property name="mapping"> 12 <props> 13 <prop key="/usrerlist.htm">userController</prop> 14 </props> 15 </property> 16 </bean> 17 18 <!--這裏的 id="userController對應的是<bean id="simpleUrlMapping">中的<prop>裏面的value"--> 19 <bean id="userController" class="test.controller.UserController"/> 20 21 </beans>
由於Spring MVC是基於Servlet的實現,因此在Web啓動的時候,服務器會首先嚐試加載對應於Servlet的配置文件,而爲了讓項目更加模塊化,一般咱們講Web部分的配置都存放於此配置文件中。
至此,已經完成了Spring MVC的搭建,啓動服務器,輸入網址http://localhost:8080/Springmvc/userlist.htm。會看到服務器器的返回界面。
二 、ContextLoaderListener
對於Spring MVC功能實現的分析,咱們首先從web.xml開始,在web.xml文件中咱們首先配置的就是contextLoaderListener,那麼它所提供的功能有哪些又是如何實現的呢?
當使用編程方式的時候咱們能夠直接將Spring配置信息做爲參數傳入Spring容器中,如:
ApplicationContext ac = new ClassPathXmlApplicationContext(「applicationContext.xml」);
可是在Web下,咱們須要更多的是與Web環境相互結合,一般的辦法是將路徑以context-param的方式註冊並使用ContextLoaderListener進行監聽讀取。
ContextLoaderListener的做用就是啓動Web容器時,自動裝配ApplicationContext的配置信息。由於它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啓動容器時,就會默認執行它實現的方法,使用ServletContextListener接口,開發者可以在爲客戶端請求提供服務以前向ServletContext中添加任意的對象。這個對象在ServletContext啓動的時候被初始化,而後在ServletContext整個運行期間都是可見的。
每個Web應用都有一個ServletContext與之相關聯。ServletContext對象在應用啓動時被建立,在應用關閉的時候被銷燬。ServletContext在全局範圍內有效,相似於應用中一個全局變量。
在ServletContextListener中的核心邏輯即是初始化WebApplicationContext實例並存放至ServletContext中。
(一)ServletContextListener的使用
正式分析代碼前咱們一樣仍是先了解ServletContextListener的使用。
(1)建立自定義ServletContextListener。
首先咱們建立ServletContextListener,目標是在系統啓動時添加自定義的屬性,以便於在全局範圍內能夠隨時調用。系統啓動的時候會調用ServletContextListener實現類的contextInitialized方法,因此須要在這個方法中實現咱們的初始化邏輯。
1 public class MyDataContextListener implements ServletContextListener{ 2 3 private ServletContext context = null; 4 5 public MyDataContextListener(){ 6 7 } 8 9 //該方法在ServletContext啓動以後被調用,並準備好處理客戶端的請求 10 public void contextInitialized(ServletContextEvent event){ 11 this.context = event.getServletContext(); 12 //經過你能夠實現本身的邏輯並將結果記錄在屬性中 13 context = setAttribut("myData", "this is myData"); 14 } 15 16 //這個方法在ServletContext將要關閉的時候調用 17 public void contextDestroyed(ServletContextEvent event){ 18 this.context = null; 19 } 20 }
(2)註冊監聽器。
在web.xml文件中須要註冊自定義的監聽器。
1 <listener> 2 com.test.MyDataContextListener 3 </listener>
(3)測試。
一旦Web應用啓動的時候,咱們就能在任意的Servlet或者JSP中經過下面的方式獲取咱們初始化參數,以下:String myData = (String) getServletContext().getAttribut(「myData」);
(二)Spring中的ContextLoaderListener
分析了ServletContextListener的使用方式後再來分析Spring中的ContextLoaderListener的實現方式就容易理解得多,雖然ContextLoaderListener實現的邏輯要複雜得多,可是大體的套路仍是萬變不離其宗。
ServletContext啓動後會調用ServletContextListener的contextInitialized方法,那麼,咱們就從這個函數開始進行分析。
1 @Override 2 public void contextInitialized(ServletContextEvent event) { 3 //該方法在其父類ContextLoader中定義 4 initWebApplicationContext(event.getServletContext()); 5 }
這裏涉及了一個經常使用類WebApplicationContext:在Web應用中,咱們會用到WebApplicationContext,WebApplicationContext繼承自ApplicationContext,在ApplicationContext的基礎上又追加了一些特定於Web的操做及屬性。很是相似於咱們經過編程方式使用Spring時使用的ClassPathXmlApplicationContext類提供的功能。繼續跟蹤代碼:
1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 2 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 3 //web.xml中存在屢次ContextLoader的定義 4 throw new IllegalStateException( 5 "Cannot initialize context because there is already a root application context present - " + 6 "check whether you have multiple ContextLoader* definitions in your web.xml!"); 7 } 8 9 servletContext.log("Initializing Spring root WebApplicationContext"); 10 Log logger = LogFactory.getLog(ContextLoader.class); 11 if (logger.isInfoEnabled()) { 12 logger.info("Root WebApplicationContext: initialization started"); 13 } 14 long startTime = System.currentTimeMillis(); 15 16 try { 17 // Store context in local instance variable, to guarantee that 18 // it is available on ServletContext shutdown. 19 if (this.context == null) { 20 //初始化context 21 this.context = createWebApplicationContext(servletContext); 22 } 23 if (this.context instanceof ConfigurableWebApplicationContext) { 24 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; 25 if (!cwac.isActive()) { 26 // The context has not yet been refreshed -> provide services such as 27 // setting the parent context, setting the application context id, etc 28 if (cwac.getParent() == null) { 29 // The context instance was injected without an explicit parent -> 30 // determine parent for root web application context, if any. 31 ApplicationContext parent = loadParentContext(servletContext); 32 cwac.setParent(parent); 33 } 34 configureAndRefreshWebApplicationContext(cwac, servletContext); 35 } 36 } 37 //記錄在servletContext中 38 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 39 40 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 41 if (ccl == ContextLoader.class.getClassLoader()) { 42 currentContext = this.context; 43 } 44 else if (ccl != null) { 45 currentContextPerThread.put(ccl, this.context); 46 } 47 48 if (logger.isInfoEnabled()) { 49 long elapsedTime = System.currentTimeMillis() - startTime; 50 logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms"); 51 } 52 53 return this.context; 54 } 55 catch (RuntimeException | Error ex) { 56 logger.error("Context initialization failed", ex); 57 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 58 throw ex; 59 } 60 }
initWebApplicationContext函數主要是體現了建立WebApplicationContext實例的一個功能架構,從函數中咱們看到了初始化的大體步驟。
(1)WebApplicationContext存在性的驗證。
在配置中只容許申明一次ServletContextListener,屢次申明會擾亂Spring的執行邏輯,因此這裏首先要作的就是對比驗證,在Spring中若是建立WebApplicationContext;實例會記錄在ServletContext中以方便全局調用,而使用的key就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,因此驗證的方式就是查看ServletContext實例中是否有對應key的屬性。
(2)建立WebApplicationContext實例。
若是經過驗證,則Spring將建立WebApplicationContext實例的工做委託給了createWebApplicationContext函數。
1 protected WebApplicationContext createWebApplicationContext(ServletContext sc) { 2 Class<?> contextClass = determineContextClass(sc); 3 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { 4 throw new ApplicationContextException("Custom context class [" + contextClass.getName() + 5 "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); 6 } 7 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); 8 }
createWebApplicationContext函數又調用了determineContextClass方法,繼續:
1 protected Class<?> determineContextClass(ServletContext servletContext) { 2 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); 3 if (contextClassName != null) { 4 try { 5 return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); 6 } 7 catch (ClassNotFoundException ex) { 8 throw new ApplicationContextException( 9 "Failed to load custom context class [" + contextClassName + "]", ex); 10 } 11 } 12 else { 13 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); 14 try { 15 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); 16 } 17 catch (ClassNotFoundException ex) { 18 throw new ApplicationContextException( 19 "Failed to load default context class [" + contextClassName + "]", ex); 20 } 21 } 22 }
其中,在ContextLoader類中有這樣的靜態代碼塊:
1 static { 2 // Load default strategy implementations from properties file. 3 // This is currently strictly internal and not meant to be customized 4 // by application developers. 5 try { 6 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); 7 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); 8 } 9 catch (IOException ex) { 10 throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); 11 } 12 }
根據以上靜態的代碼塊內容,咱們推斷在當前類ContextLoader一樣目錄下一定會存在屬性文件ContextLoader.properties(在..\spring-web\src\main\resources\org\springframework\web\context目錄下),查看後果真存在,內容以下:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
綜合以上代碼分析,在初始化過程當中,程序首先會讀取ContextLoader類的同目錄下的屬性文件ContextLoader.properties。並根據其中的配置提取將要實現WebApplicationContext接口的實現類,並根據這個實現類經過反射的方式進行實例的建立。
(3)將實例記錄在servletContext中。
(4)映射當前的類加載器與建立的實例到全局變量currentContextPerThread中。
3、 DispatcherServlet
在Spring中,ContextLoaderListener只是輔助功能,用於建立WebApplicationContext類型實例,而真正的邏輯實際上是在DispatcherServlet(package org.springframework.web.servlet)中進行的,DispatcherServlet是實現servlet的接口的實現類。
servlet是一個Java編寫的程序,此程序是基於HTTP協議的,在服務器端運行的(如Tomcat),是按照servlet規範編寫的一個Java類。主要是處理客戶端的請求並將其結果發送到客戶端。servlet的生命週期是由servlet的容器來控制的,它能夠分爲3個階段:初始化、運行和銷燬。
(1)初始化階段
servlet容器加載servlet類,把servlet類的.class文件中的數據讀到內存中。
servlet容器建立一個ServletConfig對象,ServletConfig對象包含了servlet的初始化配置信息。
servlet容器建立一個servlet對象。
servlet容器調用servlet對象的init方法機芯初始化化。
(2)運行階段
當servelt容器接收到一個請求是,servelt容器會針對這個請求建立serveltRequest和ServletResponse對象,而後調用service方法。並把這兩個參數傳遞給service方法。service方法經過servletRequest對象得到請求的信息。並處理請求。再經過servletResponse對象生成這個請求的響應結果。而後銷燬servletRequest和ServletResponse對象。咱們無論這個請求是post提交的仍是get提交的,最終這個請求都會由service方法來處理。
(3)銷燬階段。
當Web應用被終止時,servlet容器會先調用servlet對象的destroy方法,而後在銷燬servlet對象,同時也會銷燬與servlet對象相關聯的servletConfig對象。咱們能夠在destroy方法的實現中,釋放servlet所佔用的資源,如關閉數據庫鏈接,關閉文件輸入輸出流等。
servlet的框架是由兩個Java包組成:javax.servlet和javax.servlet.http。在javax.servlet包中定義了全部的servlet類都必須實現或拓展的通用接口和類。在javax.servlet.http包中定義了採用HTTP通訊協議的HttpServlet類。
servlet類被設計成請求驅動,servlet的請求可能包含多個數據項,當Web容器接收到某個servlet請求是,servelt把請求封裝成一個HTTPServletRequest對象,而後把對象傳給servlet的對象的服務方法。
HTTP的請求方式包括delete、get、options、post、put和trace。在HttpServlet類中分別提供了相應的服務方法,它們是doDelete()、doGet()、doOptions()、doPost()、doPut()和doTrace()。
(一)servlet的使用
咱們一樣仍是以最簡單的servlet來快速體驗其用法。
(1)創建servlet
1 public class Myservlet extends HttpServlet{ 2 public void init(){ 3 System.out.println("this is a method"); 4 } 5 6 public void doGet(HttpServletRequest request, HttpServletResponse response){ 7 handleLogic(request, response); 8 } 9 10 public void doPost(HttpServletRequest request, HttpServletResponse response){ 11 handleLogic(request, response); 12 } 13 14 public void handleLogic(HttpServletRequest request, HttpServletResponse response){ 15 System.out.println("handle myLogic"); 16 ServletContext sc = getServletContext(); 17 18 RequestDispatcher rd = null; 19 20 rd = sc.getRequestDispatcher("/index.jsp"); 21 try { 22 rd.forward(request, response); 23 } catch (ServletException | IOException e) { 24 e.printStackTrace(); 25 } 26 } 27 }
麻雀雖小,五臟俱全。實例中包含了對init方法和get/post方法的處理,init方法包裝在servlet加載的時候能作一些邏輯操做,而HttpServlet類則會幫助咱們根據方法類型的不一樣而將邏輯引入不一樣的函數。在子類中咱們只須要重寫對應的函數邏輯即可,如以上代碼重寫了doGet和doPost方法並將邏輯處理部分引導至handleLogic函數中,最後,又將頁面跳轉至index.jsp。
(2)添加配置
爲了是servlet可以正常使用,須要在web.xml文件中添加如下配置:
1 <servlet> 2 <servlet-name>myservlet</servlet-name> 3 <servlet-name>test.servlet.MyServlet</servlet-name> 4 <load-on-startup>1</load-on-startup> 5 </servlet> 6 7 <servlet-mapping> 8 <servlet-name>myservlet</servlet-name> 9 <url-pattern>*.htm</url-pattern> 10 </servlet-mapping>
配置後即可以根據對應的配置訪問響應的路徑了。
(二)DispatcherServlet的初始化
經過上面的實例咱們瞭解到,在servlet初始化階段會調用器init方法,因此咱們首先要查看在DispatcherServlet中是否重寫了init方法。咱們再其父類HttpServletBean(package org.springframework.web.servlet)中找到了該方法。
1 @Override 2 public final void init() throws ServletException { 3 4 // Set bean properties from init parameters. 5 //解析init-param並封裝在pvs中 6 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); 7 if (!pvs.isEmpty()) { 8 try { 9 //將當前的這個Servlet類轉化成一個BeanWrapper,從而可以以Spring的方式來對init-param的值進行注 10 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); 11 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); 12 //註冊自定義屬性編輯器,一旦遇到Resource類型的屬性將會使用ResourceEditor進行解析 13 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); 14 //空實現,留給子類覆蓋 15 initBeanWrapper(bw); 16 //屬性注入 17 bw.setPropertyValues(pvs, true); 18 } 19 catch (BeansException ex) { 20 if (logger.isErrorEnabled()) { 21 logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); 22 } 23 throw ex; 24 } 25 } 26 27 // Let subclasses do whatever initialization they like. 28 //留給子類擴展 29 initServletBean(); 30 }
DispatchServlet的初始化過程主要是經過將當前的servlet類型實例轉換成BeanWrapper類型實例,以便使用Spring中提供的注入功能進行對應屬性的注入。這些屬性如contextAttribut、contextClass、nameSpace、contextConfigLocation等,均可以在web.xml文件中以初始化參數的方式配置在servelt的聲明中。DispatcherServlet繼承自FrameworkServlet,FrameServlet類上包含對應的同名屬性,Spring會保證這些參數被注入到對應的值中。屬性注入主要包含如下幾個步驟:
(1)封裝及驗證初始化參數
ServletConfigPropertyValues除了封裝屬性外還有對屬性驗證的功能。
1 public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) 2 throws ServletException { 3 4 Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? 5 new HashSet<>(requiredProperties) : null); 6 7 Enumeration<String> paramNames = config.getInitParameterNames(); 8 while (paramNames.hasMoreElements()) { 9 String property = paramNames.nextElement(); 10 Object value = config.getInitParameter(property); 11 addPropertyValue(new PropertyValue(property, value)); 12 if (missingProps != null) { 13 missingProps.remove(property); 14 } 15 } 16 17 // Fail if we are still missing properties. 18 if (!CollectionUtils.isEmpty(missingProps)) { 19 throw new ServletException( 20 "Initialization from ServletConfig for servlet '" + config.getServletName() + 21 "' failed; the following required properties were missing: " + 22 StringUtils.collectionToDelimitedString(missingProps, ", ")); 23 } 24 }
從代碼中得知,封裝屬性主要是對初始化的參數進行封裝,也就是servlet中配置的<init-param>中配置的封裝。固然,用戶能夠同對requiredProperties參數的初始化來強制驗證某些屬性的必要性。這樣,在屬性封裝的過程當中,一旦檢測到requiredProperties中的屬性沒有指定初始值,就會拋出異常。
(2)將當前servlet實例轉化成BeanWrapper實例
PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用於將指定實例轉化成Spring中能夠處理的BeanWrapper類型的實例。
(3)註冊相對於Resource的屬性編輯器
屬性編輯器,咱們再上文中已經介紹並分析過其原理。這裏使用屬性編輯器的目的是在對當前實例(DispatcherServlet)屬性注入過程當中一旦遇到Resource類型的屬性就會使用ResourceEditor去解析。
(4)屬性注入
BeanWrapper爲Spring中的方法,支持Spring的自動注入。其實咱們最經常使用的屬性注入無非是contextAttribut、contextClass、nameSpace、contextConfigLocation等屬性。
(5)servletBean的初始化
在ContextLoaderListener加載的時候已經建立了WebApplicationContext實例,而在這個函數中最重要的就是對這個實例進行進一步的補充初始化。
繼續查看initServletBean()。父類FrameworkServlet覆蓋了HttpServletBean中的initServletBean函數,以下:
1 protected final void initServletBean() throws ServletException { 2 getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'"); 3 if (logger.isInfoEnabled()) { 4 logger.info("Initializing Servlet '" + getServletName() + "'"); 5 } 6 long startTime = System.currentTimeMillis(); 7 8 try { 9 this.webApplicationContext = initWebApplicationContext(); 10 //設爲子類覆蓋 11 initFrameworkServlet(); 12 } 13 catch (ServletException | RuntimeException ex) { 14 logger.error("Context initialization failed", ex); 15 throw ex; 16 } 17 18 if (logger.isDebugEnabled()) { 19 String value = this.enableLoggingRequestDetails ? 20 "shown which may lead to unsafe logging of potentially sensitive data" : 21 "masked to prevent unsafe logging of potentially sensitive data"; 22 logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + 23 "': request parameters and headers will be " + value); 24 } 25 26 if (logger.isInfoEnabled()) { 27 logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); 28 } 29 }
上面的函數設計了計時器來統計初始化的執行時間,並且提供了一個擴展方法initFrameworkServlet()用於子類的覆蓋操做。而做爲關鍵的初始化邏輯實現委託給了initWebApplicationContext()。
(三)WebApplicationContext的初始化
initWebApplicationContext函數的主要工做就是建立或者刷新WebApplicationContext實例並對servlet功能所使用額變量進行初始化。
1 protected WebApplicationContext initWebApplicationContext() { 2 WebApplicationContext rootContext = 3 WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 4 WebApplicationContext wac = null; 5 6 if (this.webApplicationContext != null) { 7 // A context instance was injected at construction time -> use it 8 //context實例在構造函數中被注入 9 wac = this.webApplicationContext; 10 if (wac instanceof ConfigurableWebApplicationContext) { 11 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; 12 if (!cwac.isActive()) { 13 // The context has not yet been refreshed -> provide services such as 14 // setting the parent context, setting the application context id, etc 15 if (cwac.getParent() == null) { 16 // The context instance was injected without an explicit parent -> set 17 // the root application context (if any; may be null) as the parent 18 cwac.setParent(rootContext); 19 } 20 //刷新上下文環境 21 configureAndRefreshWebApplicationContext(cwac); 22 } 23 } 24 } 25 if (wac == null) { 26 // No context instance was injected at construction time -> see if one 27 // has been registered in the servlet context. If one exists, it is assumed 28 // that the parent context (if any) has already been set and that the 29 // user has performed any initialization such as setting the context id 30 //根據contextAttribute屬性加載WebApplicationContext 31 wac = findWebApplicationContext(); 32 } 33 if (wac == null) { 34 // No context instance is defined for this servlet -> create a local one 35 wac = createWebApplicationContext(rootContext); 36 } 37 38 if (!this.refreshEventReceived) { 39 // Either the context is not a ConfigurableApplicationContext with refresh 40 // support or the context injected at construction time had already been 41 // refreshed -> trigger initial onRefresh manually here. 42 synchronized (this.onRefreshMonitor) { 43 onRefresh(wac); 44 } 45 } 46 47 if (this.publishContext) { 48 // Publish the context as a servlet context attribute. 49 String attrName = getServletContextAttributeName(); 50 getServletContext().setAttribute(attrName, wac); 51 } 52 53 return wac; 54 }
對應本函數中的初始化主要包含幾個部分。
(1)尋找或建立對應的WebApplicationContext實例
WebApplicationContext的尋找和建立包括如下幾個步驟。
(1.1)經過構造函數的注入進行初始化。
當進入initWebApplicationContext函數後經過判斷this.webApplicationContext != null後,即可以肯定this.webApplicationContext是不是經過構造函數來初始化的。但是有讀者可能會有疑問,在initServletBean函數中明明是把建立好的實例記錄在了this.webApplicationContext中:
this.webApplicationContext = initWebApplicationContext();
何以判斷這個參數就是經過構造函數初始化,而不是經過上一次的函數返回值初始化的呢?若是存在這個問題,那麼就是讀者忽略了一個問題:在Web中包含SpringWeb的核心邏輯DispatcherServlet只能夠被聲明一次,在Spring中已經存在驗證,因此這就確保了若是this.webApplicationContext != null,則能夠直接斷定this.webApplicationContext已經經過構造函數初始化了。
(1.2)經過contextAttribute進行初始化。
經過在web.xml文件中配置的servlet參數contextAttribut來 查找ServletContext中對應的屬性,默認爲WebApplicationContext.class.getName()+」.ROOT」,也就是在ContextLoaderListener加載時會建立WebApplicationContext實例,並將實例以WebApplicationContext.class.getName()+」.ROOT」爲key放入ServletContext中,固然讀者能夠重寫初始化邏輯使用本身建立的WebApplicationContext,並在servlet的配置中經過初始化參數contextAttribut指定能夠key。
1 protected WebApplicationContext findWebApplicationContext() { 2 String attrName = getContextAttribute(); 3 if (attrName == null) { 4 return null; 5 } 6 WebApplicationContext wac = 7 WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); 8 if (wac == null) { 9 throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); 10 } 11 return wac; 12 }
(1.3)從新建立WebApplicationContext實例。
若是經過以上兩種方式並無找到任何突破,那就沒有辦法了,只能在這裏從新建立新的實例了。
1 protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) { 2 return createWebApplicationContext((ApplicationContext) parent); 3 }
1 protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { 2 //獲取servlet的初始化參數contextClass,若是沒有配置默認爲XMLWebApplication.class 3 Class<?> contextClass = getContextClass(); 4 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { 5 throw new ApplicationContextException( 6 "Fatal initialization error in servlet with name '" + getServletName() + 7 "': custom WebApplicationContext class [" + contextClass.getName() + 8 "] is not of type ConfigurableWebApplicationContext"); 9 } 10 //經過反射方式實例化contextClass 11 ConfigurableWebApplicationContext wac = 12 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); 13 14 wac.setEnvironment(getEnvironment()); 15 //parent爲在ContextloaderListener中建立的實例,在ContextLoaderListener加載的時候初始化的WebApplicationContext類型實例 16 wac.setParent(parent); 17 String configLocation = getContextConfigLocation(); 18 if (configLocation != null) { 19 //獲取contextConfigLocation屬性,配置在servlet初始化參數中 20 wac.setConfigLocation(configLocation); 21 } 22 //初始化Spring環境包括加載配置文件等 23 configureAndRefreshWebApplicationContext(wac); 24 25 return wac; 26 }
(2)configureAndRefreshWebApplicationContext
不管是經過構造函數注入仍是單首創建,都免不了會調用configureAndRefreshWebApplicationContext方法來對已經建立的WebApplicationContext實例進行配置及刷新,那麼這個步驟又作了哪些工做呢?
1 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { 2 if (ObjectUtils.identityToString(wac).equals(wac.getId())) { 3 // The application context id is still set to its original default value 4 // -> assign a more useful id based on available information 5 if (this.contextId != null) { 6 wac.setId(this.contextId); 7 } 8 else { 9 // Generate default id... 10 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + 11 ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); 12 } 13 } 14 15 wac.setServletContext(getServletContext()); 16 wac.setServletConfig(getServletConfig()); 17 wac.setNamespace(getNamespace()); 18 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); 19 20 // The wac environment's #initPropertySources will be called in any case when the context 21 // is refreshed; do it eagerly here to ensure servlet property sources are in place for 22 // use in any post-processing or initialization that occurs below prior to #refresh 23 ConfigurableEnvironment env = wac.getEnvironment(); 24 if (env instanceof ConfigurableWebEnvironment) { 25 ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); 26 } 27 28 postProcessWebApplicationContext(wac); 29 applyInitializers(wac); 30 //加載配置文件及整合parent到wac 31 wac.refresh(); 32 }
(3)刷新
onRefresh是FrameworkServlet類中提供的模板方法,在其子類DispatcherServlet中進行了重寫,主要用於刷新Spring在Web功能實現中所必須使用的全局變量。下面咱們會介紹它們的初始化過程以及使用場景,而至於具體的使用細節會在稍後的章節中再作詳細的介紹。
1 /** 2 * This implementation calls {@link #initStrategies}. 3 */ 4 @Override 5 protected void onRefresh(ApplicationContext context) { 6 initStrategies(context); 7 } 8 9 /** 10 * Initialize the strategy objects that this servlet uses. 11 * <p>May be overridden in subclasses in order to initialize further strategy objects. 12 */ 13 protected void initStrategies(ApplicationContext context) { 14 //(1)初始化MultipartResolver 15 initMultipartResolver(context); 16 //(2)初始化LocaleResolver 17 initLocaleResolver(context); 18 //(3)初始化ThemeResolver 19 initThemeResolver(context); 20 //(4)初始化HandlerMappings 21 initHandlerMappings(context); 22 //(5)初始化HandlerAdapters 23 initHandlerAdapters(context); 24 //(6)初始化HandlerExceptionResolvers 25 initHandlerExceptionResolvers(context); 26 //(7)初始化RequestToViewNameTranslator 27 initRequestToViewNameTranslator(context); 28 //(8)初始化ViewResolvers 29 initViewResolvers(context); 30 //(9)初始化FlashMapManager 31 initFlashMapManager(context); 32 }
(3.1)初始化MultipartResolver
在Spring中,MultipartResolver主要用來處理文件上傳。默認狀況下,Spring是沒有Multipart處理的,由於一些開發者想要本身處理它們。若是想使用Spring的multipart,則須要在Web應用的上下文中添加multipart解析器。這樣,每一個請求就會被檢查是否包含multipart。然而,若是請求中包含multipart,那麼上下文中定義的MultipartResolver就會解析它,這樣請求的multipart屬性就會像其餘屬性同樣被處理。經常使用配置以下:
1 <bean id="multipartResolver" class="org.Springframework.web.multipart.commons.CommonsMultipartResolver"> 2 <!--該屬性用來配置可上傳文件的最大byte數--> 3 <proterty name="maximumFileSize"><value>100000</value></proterty> 4 </bean>
固然,CommonsMultipartResolver還提供了其餘功能用於幫助用戶完成上傳功能,有興趣的讀者能夠進一步查看。
那麼MultipartResolver就是在initMultipartResolver中被加入到DispatcherServlet中的。
1 private void initMultipartResolver(ApplicationContext context) { 2 try { 3 this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); 4 if (logger.isTraceEnabled()) { 5 logger.trace("Detected " + this.multipartResolver); 6 } 7 else if (logger.isDebugEnabled()) { 8 logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName()); 9 } 10 } 11 catch (NoSuchBeanDefinitionException ex) { 12 // Default is no multipart resolver. 13 this.multipartResolver = null; 14 if (logger.isTraceEnabled()) { 15 logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared"); 16 } 17 } 18 }
由於以前的步驟已經完成了Spring中配置文件的解析,因此在這裏只要在配置文件註冊過均可以經過ApplicationContext提供的getBean方法來直接獲取對應的bean,進而初始化MultipartResolver中的MultipartResolver變量。
(3.2)初始化LocalResolver
在Spring國際化配置中一共有3種使用方式。
1. 基於URL參數的配置
經過URL參數來控制國際化,好比你在頁面上加一句<a href=」?locale=zh_CN」>簡體中文</a>來控制項目中使用的國際化參數。而提供這個功能的就是AcceptHeaderLocalResolver,默認的參數名爲local,注意大小寫。裏面放的就是你的提交參數,好比en_US、zh_CN之類的,具體配置以下:
<bean id=」localResolver」 class=」org.Springframework.web.servlet.i18n.AcceptHeaderLocalResolver」/>
2. 基於session的配置
它經過檢驗用戶會話中國預置的屬性來解析區域。最經常使用的是根據用戶本次會話過程當中的語言設定決定語言種類(例如,用戶登陸時選擇語言種類,則這次登陸週期內統一使用此語言設定),若是會話屬性不存在,它會根據accept-language HTTP頭部肯定默認區域。
<bean id=」localResolver」 class=」org.Springframework.web.servlet.i18n.SessionLocaleResolver」>
3. 基於Cookie的國際化配置。
CookieLocalResolver用於經過瀏覽器的cookie設置取得Locale對象。這種策略再應用程序不支持會話或者狀態必須保存在客戶端時有用,配置以下:
<bean id=」localResolver」 class=」org.Springframework.web.servlet.i18n.CookieLocalResolver」>
這3種方式均可以解決國際化的問題,可是,對於LocalResolver的使用基礎是在DispatcherServlet中初始化的。
1 private void initLocaleResolver(ApplicationContext context) { 2 try { 3 this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); 4 if (logger.isTraceEnabled()) { 5 logger.trace("Detected " + this.localeResolver); 6 } 7 else if (logger.isDebugEnabled()) { 8 logger.debug("Detected " + this.localeResolver.getClass().getSimpleName()); 9 } 10 } 11 catch (NoSuchBeanDefinitionException ex) { 12 // We need to use the default. 13 this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); 14 if (logger.isTraceEnabled()) { 15 logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME + 16 "': using default [" + this.localeResolver.getClass().getSimpleName() + "]"); 17 } 18 } 19 }
提取配置文件中設置的LocalResolver來初始化DispatcherServlet中的localeResolver屬性。
(3.3)初始化ThemeResolver。
在Web開發中常常會遇到經過主題Theme來控制網頁風格。這將進一步改善用戶體驗。簡單的說,一個主題就是一組靜態資源(好比樣式表和圖片)。它們能夠影響程序的視覺效果。Spring中的主題功能和國際化功能很是相似。構成Spring主題功能主要包括以下內容。
1. 主題資源
org.Springframework.ui.context.ThemeSource是Spring中主題資源的接口。Spring的主題須要經過ThemeSource接口來實現存放主題信息的資源。
org.Springframework.ui.context.support.ResourceBundleThemeSource是ThemeSource接口默認實現類(也就是經過ResourceBundle資源的方式定義主題。)在Spring中的配置以下:
1 <bean id="themeSource" class="org.Springframework.ui.context.support.ResourceBundleThemeSource"> 2 <proterty name="basenamePrefix" value="com.text."></proterty> 3 </bean>
默認狀態下是在類路徑根目錄下查找相應的資源文件。也能夠經過basenamePrefix來制定。這樣,DispatcherServlet就會在com.test包下查找資源文件。
2. 主題解析器。
ThemeSource定義了一些主題資源,那麼不一樣的用戶使用什麼主題資源由誰定義呢?org.Springframework.web.servlet.ThemeResolver是主題解析器的接口,主題解析器的工做即是由它的子類來完成的。
對於主題解析器的子類主要有3個比較經常使用的實現。以主題文件summer.properties爲例。
①FixedThemeResolver用於選擇一個固定的主題。
1 <bean id="themeSource" class="org.Springframework.web.servlet.theme.FixedThemeResolver"> 2 <proterty name="defaultThemeName" value="summer"/> 3 </bean>
以上配置的做用是設置主題文件爲summer.properties,在整個項目內固定不變。
②CookieThemeResolver用於實現用戶所選的主題,以cookie的形式存放在客戶端的機器上,配置以下:
1 <bean id="themeSource" class="org.Springframework.web.servlet.theme. CookieThemeResolver"> 2 <proterty name="defaultThemeName" value="summer"/> 3 </bean>
③SessionThemeResolver用於主題保存在用戶的HTTP session中。
1 <bean id="themeSource" class="org.Springframework.web.servlet.theme. SessionThemeResolver"> 2 <proterty name="defaultThemeName" value="summer"/> 3 </bean>
以上配置用於設置主題名稱,而且將該名稱保存在用戶的HttpSession中。
④AbstractThemeResolver是一個抽象類被SessionThemeResolver和FixedThemeResolver繼承,用戶也能夠繼承它來自定義主題解析器。
3. 攔截器。
若是須要根據用戶請求來改變主題,那麼Spring提供了一個已經實現的攔截器ThemeChangeInterceptor攔截器了,配置以下:
1 <bean id="themeChangeInterceptor" class="org.Springframework.web.servlet.theme.ThemeChangeInterceptor"> 2 <proterty name="paramName" value="themeName"/> 3 </bean>
其中設置用戶請求參數名爲themeName,即URL爲?themeName=具體的主題名稱。此外,還須要在handlerMapping中配置攔截器。固然須要在handleMapping中添加攔截器。
1 <property name="interceptors"> 2 <list> 3 <ref local="themeChangeInterceptor"/> 4 </list> 5 </property>
瞭解了主題文件的簡單使用方式後,再來查看解析器的初始化工做。與其餘變量的初始化工做相同,主題文件解析器的初始化工做並無任何特別須要說明的地方。
1 private void initThemeResolver(ApplicationContext context) { 2 try { 3 this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class); 4 if (logger.isTraceEnabled()) { 5 logger.trace("Detected " + this.themeResolver); 6 } 7 else if (logger.isDebugEnabled()) { 8 logger.debug("Detected " + this.themeResolver.getClass().getSimpleName()); 9 } 10 } 11 catch (NoSuchBeanDefinitionException ex) { 12 // We need to use the default. 13 this.themeResolver = getDefaultStrategy(context, ThemeResolver.class); 14 if (logger.isTraceEnabled()) { 15 logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME + 16 "': using default [" + this.themeResolver.getClass().getSimpleName() + "]"); 17 } 18 } 19 }
(3.4)初始化HandlerMapping
當客戶端發出Request時DispatcherServlet會將Request提交給HandlerMapping,而後HandlerMapping根據WebApplicationContext的配置來回傳給DispatcherServlet響應的Controller。
在基於SpringMVC的Web應用程序中,咱們能夠爲DispatcherServlet提供多個HandlerMapping供其使用。DispatcherServlet在選用HandlerMapping的過程當中,將根據咱們所指定的一系列HandlerMapping的優先級進行排序,而後優先使用優先級在簽名的HandlerMapping。若是當前的HandlerMapping可以返回可用的Handler,DispatcherServlet則使用當前返回的Handler進行Web請求的處理,而再也不繼續詢問其餘的HandlerMapping。不然,DispatcherServlet將繼續按照各個HandlerMapping的優先級進行詢問,直到獲取一個可用的Handler爲止。初始化配置以下:
1 private void initHandlerMappings(ApplicationContext context) { 2 this.handlerMappings = null; 3 4 if (this.detectAllHandlerMappings) { 5 // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. 6 Map<String, HandlerMapping> matchingBeans = 7 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); 8 if (!matchingBeans.isEmpty()) { 9 this.handlerMappings = new ArrayList<>(matchingBeans.values()); 10 // We keep HandlerMappings in sorted order. 11 AnnotationAwareOrderComparator.sort(this.handlerMappings); 12 } 13 } 14 else { 15 try { 16 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); 17 this.handlerMappings = Collections.singletonList(hm); 18 } 19 catch (NoSuchBeanDefinitionException ex) { 20 // Ignore, we'll add a default HandlerMapping later. 21 } 22 } 23 24 // Ensure we have at least one HandlerMapping, by registering 25 // a default HandlerMapping if no other mappings are found. 26 if (this.handlerMappings == null) { 27 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); 28 if (logger.isTraceEnabled()) { 29 logger.trace("No HandlerMappings declared for servlet '" + getServletName() + 30 "': using default strategies from DispatcherServlet.properties"); 31 } 32 } 33 }
默認狀況下,SpringMVC將加載當前系統中全部實現了HandlerMapping接口的bean。若是隻指望SpringMVC加載指定的handlerMapping時,可用修改web.xml中的DispatcherServlet的初始參數,將detectAllHandlerMappings的值設置爲false:
1 <init-param> 2 <param-name>detectAllHandlerMappings</param-name> 3 <param-value>false</param-value> 4 </init-param>
此時,SpringMVC將查找名爲「HandlerMapping」的bean,並做爲當前系統中惟一的handlerMapping。若是沒有定義HandlerMapping的話,則SpringMVC將按照org.Springframework.web.servlet.DispatcherServlet所在目錄下的DispatcherServlet.properties中所定義的org.Springframework.web.servlet.HandlerMapping的內容來加載默認的handlerMapping(用戶沒有自定義Strategies的狀況下)。
(3.5)初始化HandlerAdapters
從名字也能聯想到這是一個典型的適配器模式的使用,在計算機編程中,適配器模式將一個類的接口適配成用戶所期待的。使用適配器,可使接口不兼容而沒法在一塊兒工做的類協同工做。作飯是將類本身的接口包裹在一個已存在的類中。那麼在處理handler時爲何會使用適配器模式呢?回答這個問題咱們首先要分析它的初始化邏輯。
1 private void initHandlerAdapters(ApplicationContext context) { 2 this.handlerAdapters = null; 3 4 if (this.detectAllHandlerAdapters) { 5 // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts. 6 Map<String, HandlerAdapter> matchingBeans = 7 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); 8 if (!matchingBeans.isEmpty()) { 9 this.handlerAdapters = new ArrayList<>(matchingBeans.values()); 10 // We keep HandlerAdapters in sorted order. 11 AnnotationAwareOrderComparator.sort(this.handlerAdapters); 12 } 13 } 14 else { 15 try { 16 HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); 17 this.handlerAdapters = Collections.singletonList(ha); 18 } 19 catch (NoSuchBeanDefinitionException ex) { 20 // Ignore, we'll add a default HandlerAdapter later. 21 } 22 } 23 24 // Ensure we have at least some HandlerAdapters, by registering 25 // default HandlerAdapters if no other adapters are found. 26 if (this.handlerAdapters == null) { 27 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); 28 if (logger.isTraceEnabled()) { 29 logger.trace("No HandlerAdapters declared for servlet '" + getServletName() + 30 "': using default strategies from DispatcherServlet.properties"); 31 } 32 } 33 }
一樣在初始化的過程當中涉及了一個變量detectAllHandlerAdapters,detectAllHandlerAdapters的做用和detectAllHandlerMapping相似,只不過做用對象爲handlerAdapter。亦可經過以下配置來強制系統只加載bean name爲「handlerAdapter」的handlerAdapter。
1 <init-param> 2 <param-name>detectAllHandlerAdapters</param-name> 3 <param-value>false</param-value> 4 </init-param>
若是沒法找到對應的bean,那麼系統會嘗試加載默認的適配器。
1 @SuppressWarnings("unchecked") 2 protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { 3 String key = strategyInterface.getName(); 4 String value = defaultStrategies.getProperty(key); 5 if (value != null) { 6 String[] classNames = StringUtils.commaDelimitedListToStringArray(value); 7 List<T> strategies = new ArrayList<>(classNames.length); 8 for (String className : classNames) { 9 try { 10 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); 11 Object strategy = createDefaultStrategy(context, clazz); 12 strategies.add((T) strategy); 13 } 14 catch (ClassNotFoundException ex) { 15 throw new BeanInitializationException( 16 "Could not find DispatcherServlet's default strategy class [" + className + 17 "] for interface [" + key + "]", ex); 18 } 19 catch (LinkageError err) { 20 throw new BeanInitializationException( 21 "Unresolvable class definition for DispatcherServlet's default strategy class [" + 22 className + "] for interface [" + key + "]", err); 23 } 24 } 25 return strategies; 26 } 27 else { 28 return new LinkedList<>(); 29 } 30 }
在getDefaultStrategies函數中,Spring會嘗試從defaultStrategies中加載對應的HandlerAdapter的屬性。那麼defaultStrategies是如何初始化的呢?
在當期類DispatcherServlet中存在這樣一段初始化代碼塊:
1 static { 2 // Load default strategy implementations from properties file. 3 // This is currently strictly internal and not meant to be customized 4 // by application developers. 5 try { 6 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); 7 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); 8 } 9 catch (IOException ex) { 10 throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); 11 } 12 }
在系統加載的時候,defaultStrategies根據當前路徑DispatcherServlet.properties來初始化自己,查看DispatcherServlet.properties中對應於HandlerAdapter的屬性:
1 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ 2 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ 3 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ 4 org.springframework.web.servlet.function.support.HandlerFunctionAdapter
由此得知,若是程序開發人員沒有再配置文件中定義本身的適配器,那麼Spring會默認加載配置文件中的適配器。
做爲總控制器的派遣器servlet經過處理器映射獲得處理器後,會輪詢處理器適配器模塊,查找可以處理當前HTTP請求的處理器適配器的實現,處理器適配器模塊根據處理器映射發揮的處理器類型,例如簡單的控制器類型、註解控制器類型或者遠程調用處理器類型,來選擇某一個適當的處理器適配器的實現,從而適配當前的HTTP請求。
1. HTTP請求處理器適配器(HTTPRequestHandlerAdapter)。
HTTP請求處理器適配器僅僅支持對HTTP請求處理器的適配。它簡單的將HTTP請求對象和響應對象傳遞給HTTP請求處理器的實現,它並不須要返回值。它主要應用在基於HTTP的遠程調用的實現上。
2. 簡單控制器適配器(SimpleControllerHandlerAdapter)。
這個實現類將HTTP請求適配到一個控制器的實現進行處理。這裏控制器的實現是一個簡單的控制器接口的實現。簡單控制器處理器適配器被設計成一個框架類的實現,不須要被改寫,客戶化的業務邏輯一般是在控制器接口的實現類中實現的。
3. 註解方法處理器適配器(AnnotationMethodHandlerAdapte)。
這個類的實現是基於註解的實現,它須要結合註解方法映射和註解方法處理器協同工做。它經過解析聲明在註解控制器的請求映射信息來解析相應的處理器方法來處理當前的HTTP請求。在處理的過程當中,它經過反射來發現探測處理器方法的參數,調用處理器方法,而且映射返回值到模型和控制器對象,最後返回模型和控制器對象給做爲主控制器的派遣器Servlet.
因此咱們如今基本能夠回答以前的問題了,Spring找那個所使用的Handler並無任何特殊的聯繫,可是爲了統一處理,Spring提供了不一樣狀況下的適配器。
(3.6)初始化HandlerExceptionResolver。
基於HandlerExceptionResolver接口的異常處理,使用這種方式只須要實現resolveException方法,該方法返回一個ModelAndView對象,在方法內部對異常的類型進行判斷,而後嘗試生成對應的ModelAndView對象,若是該方法返回了null,則Spring會繼續尋找其餘的實現了HandlerExceptionResolver接口的bean。換句話說,Spring會搜索全部註冊在其環境中的實現了HandlerExceptionResolver接口的bean,逐個執行,直到返回了一個ModelAndView對象。
1 import javax.servlet.http.HttpServletRequest; 2 import javax.servlet.http.HttpServletResponse; 3 4 import org.apache.commons.logging.Log; 5 import org.apache.commons.logging.LogFactory; 6 import org.Springframework.stereotype.Componet; 7 import org.Springframwwork.web.servlet.HandlerExceptionResolver; 8 import org.Springframework.web.servlet.ModelAndView; 9 10 @component 11 public class ExceptionHandler implements HandlerExceptionResolver{ 12 private static final Log = logs = LogFactory.getLog(ExceptionHandler.class); 13 14 @override 15 public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object obj, Exception exception){ 16 request.setAttribute("exception", exception.toString()); 17 request.setAttribute("exceptionStack", exception); 18 logs.error(exception.toString(), exception); 19 return new ModelAndView("error/exception"); 20 } 21 }
這個類必須聲明到Spring中去,讓Spring管理它,在Spring的配置文件applicationContext.xml中增長如下內容:
<bean id="exceptionHandler" class="com.test.exception.MyExceptionHandler"/>
初始化代碼以下:
1 private void initHandlerExceptionResolvers(ApplicationContext context) { 2 this.handlerExceptionResolvers = null; 3 4 if (this.detectAllHandlerExceptionResolvers) { 5 // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. 6 Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils 7 .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); 8 if (!matchingBeans.isEmpty()) { 9 this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); 10 // We keep HandlerExceptionResolvers in sorted order. 11 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); 12 } 13 } 14 else { 15 try { 16 HandlerExceptionResolver her = 17 context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); 18 this.handlerExceptionResolvers = Collections.singletonList(her); 19 } 20 catch (NoSuchBeanDefinitionException ex) { 21 // Ignore, no HandlerExceptionResolver is fine too. 22 } 23 } 24 25 // Ensure we have at least some HandlerExceptionResolvers, by registering 26 // default HandlerExceptionResolvers if no other resolvers are found. 27 if (this.handlerExceptionResolvers == null) { 28 this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); 29 if (logger.isTraceEnabled()) { 30 logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() + 31 "': using default strategies from DispatcherServlet.properties"); 32 } 33 } 34 }
(3.7)初始化RequestToViewNameTranslator。
當Controller處理器方法沒有返回一個View對象或邏輯視圖名稱,而且在該方法中沒有直接往response的輸出流裏面寫數據的時候,Spring就會採用約定好的方式提供一個邏輯視圖名稱。這個邏輯視圖名稱是經過Spring定義的org.Springframework.web.servlet.RequestToViewNameTranslator接口的getViewName方法來實現的,咱們能夠實現本身RequestToViewNameTranslator接口來約定好沒有返回視圖名稱的時候如何肯定視圖名稱。Spring已經給咱們提供了一個它本身的實現,那就是org.Springframework.web.servlet.view.DefaultRequestToViewNameTranslator。
在介紹DefaultRequestToViewNameTranslator是如何約定視圖名稱以前,先來看一下它支持用戶定義的屬性。
prefix:前綴,表示約定好的視圖名稱須要加上的前綴,默認是空串。
suffix:後綴,表示約定好的視圖名稱須要加上的後綴,默認是空串。
separator:分隔符,默認是斜槓「/」。
stripLeadingSlash:若是首字符是分隔符,是否要去除,默認是true。
stripTrailingSlash:若是最後一個字符是分隔符,是否要去除,默認是true。
stripExtension:若是請求路徑包含擴展名是否要去除,默認是true。
urlDecode:是否須要對URL編碼,默認是ture。它會採用request指定的編碼或者ISO-8859-1編碼對URL進行解碼。
當咱們沒有再SpringMVC的配置文件中手動的定義一個名爲viewNameTranlator的Bean的時候,Spring就會爲咱們提供一個默認的viewNameTranslator,即DefaultRequestToViewNameTranslator。
接下來看一下,當Controller處理器方法沒有返回邏輯視圖名稱時,DefaultRequestToViewNameTranslator是如何約定視圖名稱的。DefaultRequestToViewNameTranslator會獲取到請求的URL,而後根據提供的屬性作一些改造,把改造以後的結果做爲視圖名稱返回。這裏以請求路徑http://localhost/app/test/index.html爲例,來講明一下DefaultRequestToViewNameTranslator是如何工做的。該請求路徑對應的請求URI爲/test/index.html,咱們來看如下幾種狀況,它分別對應的邏輯視圖名稱是什麼。
prefix和suffix若是都存在,其餘爲默認值,那麼對應返回的邏輯視圖名稱應該是prefixtest/indexsuffix。
stripLeadingSlash和stripExtension都爲false,其餘爲默認,這時候對應的邏輯視圖名稱是/product/index.html。
都採用默認配置時,返回的邏輯視圖名稱應該是product/index。
若是邏輯視圖名稱跟請求路徑相同或者相關關係都是同樣的,那麼咱們就能夠採用Spring爲咱們事先約定好的邏輯視圖名稱返回,這能夠大大簡化咱們的開發工做,而以上功能實現的關鍵屬性viewNameTranslator,則是在initRequestToViewNameTranslator中完成的。
1 private void initRequestToViewNameTranslator(ApplicationContext context) { 2 try { 3 this.viewNameTranslator = 4 context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class); 5 if (logger.isTraceEnabled()) { 6 logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName()); 7 } 8 else if (logger.isDebugEnabled()) { 9 logger.debug("Detected " + this.viewNameTranslator); 10 } 11 } 12 catch (NoSuchBeanDefinitionException ex) { 13 // We need to use the default. 14 this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class); 15 if (logger.isTraceEnabled()) { 16 logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + 17 "': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]"); 18 } 19 } 20 }
(3.8)初始化ViewResolvers。
在SpringMVC中,當Controller將請求處理結果放入到ModelAndView中之後,DispatcherServlet會根據ModelAndView選擇合適的視圖進行渲染。那麼在SpringMVC中是如何選擇合適的View的呢?View對象是如何建立的呢?答案就在ViewResolver中。ViewResolver接口定義了resolverViewName方法,根據ViewName建立合適類型的View實現。
那麼如何配置ViewResolver呢?在Spring中,ViewResolver做爲Spring Bean存在,能夠在Spring配置文件中進行配置,例以下面的代碼,配置了JSP相關的viewResolver。
1 <bean class="org.Springframework.web.servlet.view.InternalResourceViewResolver"> 2 <property name="prefix" value="/WEB-INF/views/"/> 3 <property name="suffix" value=".jsp"/> 4 </bean>
ViewResolver屬性的初始化工做在initViewResolver中完成。
1 private void initViewResolvers(ApplicationContext context) { 2 this.viewResolvers = null; 3 4 if (this.detectAllViewResolvers) { 5 // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. 6 Map<String, ViewResolver> matchingBeans = 7 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); 8 if (!matchingBeans.isEmpty()) { 9 this.viewResolvers = new ArrayList<>(matchingBeans.values()); 10 // We keep ViewResolvers in sorted order. 11 AnnotationAwareOrderComparator.sort(this.viewResolvers); 12 } 13 } 14 else { 15 try { 16 ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); 17 this.viewResolvers = Collections.singletonList(vr); 18 } 19 catch (NoSuchBeanDefinitionException ex) { 20 // Ignore, we'll add a default ViewResolver later. 21 } 22 } 23 24 // Ensure we have at least one ViewResolver, by registering 25 // a default ViewResolver if no other resolvers are found. 26 if (this.viewResolvers == null) { 27 this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); 28 if (logger.isTraceEnabled()) { 29 logger.trace("No ViewResolvers declared for servlet '" + getServletName() + 30 "': using default strategies from DispatcherServlet.properties"); 31 } 32 } 33 }
(3.9)初始化FlashMapManager。
SpringMVC Flash attributes提供了一個請求存儲屬性,可供其餘請求使用。在使用重定向時候很是必要,例如Post/Redirect/Get模式。Flash attribute在重定向以前暫存(就像存在session中)以便重定向以後還能使用,並當即刪除。
SpringMVC有兩個主要的抽象來支持Flash attributes。FlashMap用於保持Flash attributes,而FlashMapManager用於存儲、檢索、管理FlashMap實例。
Flash attributes支持默認開啓(「on」)並不須要顯示啓用,它永遠不會致使HTTP Session的建立。這兩個FlashMap實例均可以經過靜態方法RequestContextUtils從SpringMVC的任何位置訪問。
FlashMapManager的初始化在initFlashMapManager中完成。
1 private void initFlashMapManager(ApplicationContext context) { 2 try { 3 this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); 4 if (logger.isTraceEnabled()) { 5 logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName()); 6 } 7 else if (logger.isDebugEnabled()) { 8 logger.debug("Detected " + this.flashMapManager); 9 } 10 } 11 catch (NoSuchBeanDefinitionException ex) { 12 // We need to use the default. 13 this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class); 14 if (logger.isTraceEnabled()) { 15 logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME + 16 "': using default [" + this.flashMapManager.getClass().getSimpleName() + "]"); 17 } 18 } 19 }
4、 DispatcherServlet的處理邏輯
根據以前的示例,咱們知道在HttpServlet類中分別提供了相應的服務方法,他們是doDelete()、doGet()、doOptions()、doGet()、doPut()和doTrace(),它會根據請求的不一樣形式將程序引導至對應的函數進行處理。這幾個函數中最經常使用的函數無非就是doGet()和doGet(),那麼咱們就直接查看DispatcherServlet中對於這兩個函數的實現邏輯(在其父類FrameworkServlet中實現)。
1 protected final void doGet(HttpServletRequest request, HttpServletResponse response) 2 throws ServletException, IOException { 3 4 processRequest(request, response); 5 }
1 protected final void doPost(HttpServletRequest request, HttpServletResponse response) 2 throws ServletException, IOException { 3 4 processRequest(request, response); 5 }
對於不一樣的方法,Spring並無作特殊的處理,而是統一將程序再一次地引導至processRequest(request, response)中。
1 protected final void processRequest(HttpServletRequest request, HttpServletResponse response) 2 throws ServletException, IOException { 3 4 //記錄當前時間,用於計算web請求的處理時間 5 long startTime = System.currentTimeMillis(); 6 Throwable failureCause = null; 7 8 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); 9 LocaleContext localeContext = buildLocaleContext(request); 10 11 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); 12 ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); 13 14 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 15 asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); 16 17 initContextHolders(request, localeContext, requestAttributes); 18 19 try { 20 doService(request, response); 21 } 22 catch (ServletException | IOException ex) { 23 failureCause = ex; 24 throw ex; 25 } 26 catch (Throwable ex) { 27 failureCause = ex; 28 throw new NestedServletException("Request processing failed", ex); 29 } 30 31 finally { 32 resetContextHolders(request, previousLocaleContext, previousAttributes); 33 if (requestAttributes != null) { 34 requestAttributes.requestCompleted(); 35 } 36 logResult(request, response, failureCause, asyncManager); 37 publishRequestHandledEvent(request, response, startTime, failureCause); 38 } 39 }
函數中已經開始了對請求的處理,雖然吧細節轉移到了doService函數中實現,可是咱們不難看出處理請求先後所作的準備與處理工做。
(1)爲了保證當前線程的LocalContext以及RequestAttribute能夠在當前請求後還能恢復,提取當前線程的兩個屬性。
(2)根據當前request建立對應的LocalContext和RequestAttributes,並綁定到當前線程。
(3)委託給doService方法進一步處理。
(4)請求處理結束後恢復線程到原始狀態。
(5)請求處理結束後不管成功與否發佈事件通知。
繼續查看doService方法。
1 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 logRequest(request); 3 4 // Keep a snapshot of the request attributes in case of an include, 5 // to be able to restore the original attributes after the include. 6 Map<String, Object> attributesSnapshot = null; 7 if (WebUtils.isIncludeRequest(request)) { 8 attributesSnapshot = new HashMap<>(); 9 Enumeration<?> attrNames = request.getAttributeNames(); 10 while (attrNames.hasMoreElements()) { 11 String attrName = (String) attrNames.nextElement(); 12 if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { 13 attributesSnapshot.put(attrName, request.getAttribute(attrName)); 14 } 15 } 16 } 17 18 // Make framework objects available to handlers and view objects. 19 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); 20 request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 21 request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); 22 request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); 23 24 if (this.flashMapManager != null) { 25 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); 26 if (inputFlashMap != null) { 27 request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); 28 } 29 request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 30 request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); 31 } 32 33 try { 34 doDispatch(request, response); 35 } 36 finally { 37 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 38 // Restore the original attribute snapshot, in case of an include. 39 if (attributesSnapshot != null) { 40 restoreAttributesAfterInclude(request, attributesSnapshot); 41 } 42 } 43 } 44 }
咱們猜測對請求處理至少應該包括一些諸如尋找Handler並頁面跳轉之類的邏輯處理,可是,在doService中咱們並無看到想看到的邏輯,相反卻一樣是一些準備工做,可是這些準備工做確實必不可少的。Spring將已經初始化的功能輔助工具變量,好比localeResolver、ThemeResolver等設置在request屬性中,而這些屬性會在接下來額處理中派上用場。
通過層層的準備工做,終於在doDispatch函數中看到了完整的請求處理過程。
1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 HttpServletRequest processedRequest = request; 3 HandlerExecutionChain mappedHandler = null; 4 boolean multipartRequestParsed = false; 5 6 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 7 8 try { 9 ModelAndView mv = null; 10 Exception dispatchException = null; 11 12 try { 13 //若是是MultipartContent類型的request則轉換request爲MultipartHttpServletRequest類型的request 14 processedRequest = checkMultipart(request); 15 multipartRequestParsed = (processedRequest != request); 16 17 // Determine handler for the current request. 18 //根據request信息尋找對應的Handler 19 mappedHandler = getHandler(processedRequest); 20 if (mappedHandler == null) { 21 //若是沒有找到對應的handler則經過response反饋錯誤信息 22 noHandlerFound(processedRequest, response); 23 return; 24 } 25 26 // Determine handler adapter for the current request. 27 //根據當前的handler尋找對應的HandlerAdapter 28 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 29 30 // Process last-modified header, if supported by the handler. 31 //若是當前handler支持last-modified頭處理 32 String method = request.getMethod(); 33 boolean isGet = "GET".equals(method); 34 if (isGet || "HEAD".equals(method)) { 35 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 36 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 37 return; 38 } 39 } 40 41 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 42 return; 43 } 44 45 // Actually invoke the handler. 46 //真正的激活handler並返回視圖 47 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 48 49 if (asyncManager.isConcurrentHandlingStarted()) { 50 return; 51 } 52 53 applyDefaultViewName(processedRequest, mv); 54 mappedHandler.applyPostHandle(processedRequest, response, mv); 55 } 56 catch (Exception ex) { 57 dispatchException = ex; 58 } 59 catch (Throwable err) { 60 // As of 4.3, we're processing Errors thrown from handler methods as well, 61 // making them available for @ExceptionHandler methods and other scenarios. 62 dispatchException = new NestedServletException("Handler dispatch failed", err); 63 } 64 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 65 } 66 catch (Exception ex) { 67 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 68 } 69 catch (Throwable err) { 70 triggerAfterCompletion(processedRequest, response, mappedHandler, 71 new NestedServletException("Handler processing failed", err)); 72 } 73 finally { 74 if (asyncManager.isConcurrentHandlingStarted()) { 75 // Instead of postHandle and afterCompletion 76 if (mappedHandler != null) { 77 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); 78 } 79 } 80 else { 81 // Clean up any resources used by a multipart request. 82 if (multipartRequestParsed) { 83 cleanupMultipart(processedRequest); 84 } 85 } 86 } 87 }
doDispatch函數中展現了Spring請求處理所涉及的主要邏輯,而咱們以前設置在request中的各類輔助屬性也都有被派上了用場。下面回顧一下邏輯處理的全過程。
(一)MultipartContent類型的request處理
對於請求的處理,Spring首先要考慮的是對於Multipart的處理,若是是MultipartContent類型的request,則轉換request爲MultipartHttpServletRequest類型的request。
1 protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { 2 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { 3 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { 4 if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { 5 logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); 6 } 7 } 8 else if (hasMultipartException(request)) { 9 logger.debug("Multipart resolution previously failed for current request - " + 10 "skipping re-resolution for undisturbed error rendering"); 11 } 12 else { 13 try { 14 return this.multipartResolver.resolveMultipart(request); 15 } 16 catch (MultipartException ex) { 17 if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { 18 logger.debug("Multipart resolution failed for error dispatch", ex); 19 // Keep processing error dispatch with regular request handle below 20 } 21 else { 22 throw ex; 23 } 24 } 25 } 26 } 27 // If not returned before: return original request. 28 return request; 29 }
(二) 根據request信息尋找對應的handler
在Spring中最簡單的映射處理器配置以下:
1 <bean id="simpleUrlMapping" class="org.Springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 2 <property name="mapping"> 3 <props> 4 <prop key="/userlist.htm">userController</prop> 5 </props> 6 </property> 7 </bean>
在Spring的加載過程當中,Spring會將類型爲SimpleUrlHandlerMapping的實例加載到this.handlerMappings中,按照常理推斷,根據request提取對應的Handler,無非就是提取當前實例中的userController,可是userController爲繼承自AbstractController類型實例,與HandlerExecutionChain並沒有任何關聯,那麼進一步是如何封裝的呢?
1 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 2 if (this.handlerMappings != null) { 3 for (HandlerMapping mapping : this.handlerMappings) { 4 HandlerExecutionChain handler = mapping.getHandler(request); 5 if (handler != null) { 6 return handler; 7 } 8 } 9 } 10 return null; 11 }
在以前的內容中咱們提過,在系統啓動時Spring會將全部的映射類型的bean註冊到this.handlerMappings變量中,因此此函數的目的就是遍歷全部的HandlerMapping,並調用其getHandler方法進行封裝處理。以SimpleUrlHandlerMapping爲例查看其getHandler方法。
函數中首先會使用getHandlerInternal方法根據request信息獲取到對應的Handler,若是以SimpleUrlHandlerMapping爲例分析,那麼咱們推斷此步驟提供的功能極可能就是根據URL找到匹配的Controller並返回,固然若是沒有找到對應的Controller處理器那麼程序會嘗試去查找配置中的默認處理器,固然,當查找的Controller爲String類型時,那就意味着返回的是配置的bean名稱,須要根據bean名稱查找對應的bean,最後,還要經過getHandlerExecutionChain方法對返回的Handler進行封裝,以保證知足返回類型的匹配。下面詳細分析這個過程。
1. 根據request查找對應的Handler
首先從根據request查找對應的Handler開始分析。
1 protected Object getHandlerInternal(HttpServletRequest request) throws Exception { 2 //截取用於匹配的url有效路徑 3 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); 4 request.setAttribute(LOOKUP_PATH, lookupPath); 5 //根據路徑尋找Handler 6 Object handler = lookupHandler(lookupPath, request); 7 if (handler == null) { 8 // We need to care for the default handler directly, since we need to 9 // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. 10 Object rawHandler = null; 11 if ("/".equals(lookupPath)) { 12 //若是請求的路徑僅僅是「/」,那麼使用RootHandler進行處理 13 rawHandler = getRootHandler(); 14 } 15 if (rawHandler == null) { 16 //沒法找到handler則使用默認的handler 17 rawHandler = getDefaultHandler(); 18 } 19 if (rawHandler != null) { 20 //根據beanName獲取對應的bean 21 // Bean name or resolved handler? 22 if (rawHandler instanceof String) { 23 String handlerName = (String) rawHandler; 24 rawHandler = obtainApplicationContext().getBean(handlerName); 25 } 26 //模板方法 27 validateHandler(rawHandler, request); 28 handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); 29 } 30 } 31 return handler; 32 }
1 protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { 2 // Direct match? 3 //直接匹配狀況的處理 4 Object handler = this.handlerMap.get(urlPath); 5 if (handler != null) { 6 // Bean name or resolved handler? 7 if (handler instanceof String) { 8 String handlerName = (String) handler; 9 handler = obtainApplicationContext().getBean(handlerName); 10 } 11 validateHandler(handler, request); 12 return buildPathExposingHandler(handler, urlPath, urlPath, null); 13 } 14 15 // Pattern match? 16 //通配符匹配的處理 17 List<String> matchingPatterns = new ArrayList<>(); 18 for (String registeredPattern : this.handlerMap.keySet()) { 19 if (getPathMatcher().match(registeredPattern, urlPath)) { 20 matchingPatterns.add(registeredPattern); 21 } 22 else if (useTrailingSlashMatch()) { 23 if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) { 24 matchingPatterns.add(registeredPattern + "/"); 25 } 26 } 27 } 28 29 String bestMatch = null; 30 Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath); 31 if (!matchingPatterns.isEmpty()) { 32 matchingPatterns.sort(patternComparator); 33 if (logger.isTraceEnabled() && matchingPatterns.size() > 1) { 34 logger.trace("Matching patterns " + matchingPatterns); 35 } 36 bestMatch = matchingPatterns.get(0); 37 } 38 if (bestMatch != null) { 39 handler = this.handlerMap.get(bestMatch); 40 if (handler == null) { 41 if (bestMatch.endsWith("/")) { 42 handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); 43 } 44 if (handler == null) { 45 throw new IllegalStateException( 46 "Could not find handler for best pattern match [" + bestMatch + "]"); 47 } 48 } 49 // Bean name or resolved handler? 50 if (handler instanceof String) { 51 String handlerName = (String) handler; 52 handler = obtainApplicationContext().getBean(handlerName); 53 } 54 validateHandler(handler, request); 55 String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); 56 57 // There might be multiple 'best patterns', let's make sure we have the correct URI template variables 58 // for all of them 59 Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); 60 for (String matchingPattern : matchingPatterns) { 61 if (patternComparator.compare(bestMatch, matchingPattern) == 0) { 62 Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); 63 Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); 64 uriTemplateVariables.putAll(decodedVars); 65 } 66 } 67 if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) { 68 logger.trace("URI variables " + uriTemplateVariables); 69 } 70 return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); 71 } 72 73 // No handler found... 74 return null; 75 }
根據URL獲取對應Handler的匹配規則代碼實現起來雖然很長,可是並不難理解,考慮了直接匹配與通配符兩種狀況。其中要說起的是buildPathExposingHandler函數,它將Handler封裝成了HandlerExecutionChain類型。
1 protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, 2 String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { 3 4 HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); 5 chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); 6 if (!CollectionUtils.isEmpty(uriTemplateVariables)) { 7 chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); 8 } 9 return chain; 10 }
在函數中咱們看到了經過將Handler以參數形式傳入,並構建HandlerExcutionChain類型實例,加入了兩個攔截器。此時咱們彷佛已經瞭解了Spring這樣大費周折的目的。鏈處理機制,是Spring中很是經常使用的處理方式,是AOP中的重要組成部分,能夠方便地對目標對象進行擴展及攔截,這是很是優秀的設計。
2. 加入攔截器到執行鏈
將配置中的對應攔截器加入到執行鏈找中,以保證這些攔截器能夠有效的做用於目標對象。
1 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 2 if (this.handlerMappings != null) { 3 for (HandlerMapping mapping : this.handlerMappings) { 4 HandlerExecutionChain handler = mapping.getHandler(request); 5 if (handler != null) { 6 return handler; 7 } 8 } 9 } 10 return null; 11 }
(三)沒找到對應的Handler的錯誤處理
每一個請求都應該對應着一個Handler,由於每一個請求都會在後臺有相應的邏輯對應,而邏輯的實現就是在Handler中,因此一旦遇到沒有找到Handler的狀況(正常狀況下若是沒有URL匹配的Handler,開發人員能夠設置默認的Handler來處理請求,可是若是默認請求也未設置就會出現Handler爲空的狀況),就只能經過response向用戶返回錯誤信息。
1 protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 if (pageNotFoundLogger.isWarnEnabled()) { 3 pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request)); 4 } 5 if (this.throwExceptionIfNoHandlerFound) { 6 throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), 7 new ServletServerHttpRequest(request).getHeaders()); 8 } 9 else { 10 response.sendError(HttpServletResponse.SC_NOT_FOUND); 11 } 12 }
(四)根據當前的Handler尋找對應的HandlerAdapter
在WebApplicationContext的初始化過程當中咱們討論了HandlerAdapter的初始化,瞭解了在默認的狀況下普通的Web請求會交給SimpleControllerHandlerAdapter去處理。下面咱們以SimpleControllerHandlerAdapter爲例來分析獲取適配器的邏輯。
1 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { 2 if (this.handlerAdapters != null) { 3 for (HandlerAdapter adapter : this.handlerAdapters) { 4 if (adapter.supports(handler)) { 5 return adapter; 6 } 7 } 8 } 9 throw new ServletException("No adapter for handler [" + handler + 10 "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); 11 }
經過上面的函數咱們瞭解到,對於獲取適配器的邏輯無非就是遍歷全部的適配器來選擇合適的適配器並返回它,而某個適配器是否適用於當前的Handler邏輯被封裝在具體的適配器中。進一步查看SimpleControllerHandlerAdapter的supports方法。
1 @Override 2 public boolean supports(Object handler) { 3 return (handler instanceof Controller); 4 }
分析到這裏,一切已經明瞭,SimpleControllerHandlerAdapter就是用於處理普通的Web請求的,並且對於SpringMVC來講,咱們會把邏輯封裝至Controller的子類中,例如咱們以前的引導示例UserController就是繼承自AbstractController,而AbstractController實現了Controller接口。
(五)緩存處理
在研究Spring對緩存處理的功能支持前,咱們先來了解一個概念:Last-Modified緩存機制。
(1)在客戶端第一次輸入URL時,服務器會返回內容和狀態碼200,表示請求成功,同時會添加一個「Last-Modified」的響應頭,表示此文件在服務器上的最後更新時間,例如,「Last-Modified:Wed, 14 Mar 2012 10:22:42 GMT」表示最後更新時間爲(2012-03-14 10:22)。
(2)客戶端第二次請求此URL時,客戶端會向服務器發送請求頭」If-Modified-Since」。詢問服務器該時間以後當前請求內容是否有被修改過,如」 If-Modified-Since: Wed, 14 Mar 2012 10:22:42 GMT」,若是服務器端的內容沒有變化,則自動返回HTTP 304狀態碼(只要響應頭,內容爲空,這樣就節省了網絡帶寬)。
Spring提供的對Last-Modified機制的支持,只須要實現LastModified接口,以下所示:
1 public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified{ 2 private long lastModified; 3 protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception{ 4 //點擊後再次請求當前頁面 5 resp.getWrite().write("<a href=''>this</a>"); 6 return null; 7 } 8 9 public long getLastModified(HttpServletRequest request){ 10 if(lastModified == 0L){ 11 //第一次或者邏輯有變化的時候,應該從新返回內容最新修改的時間戳 12 lastModified = System.currentTimeMillis(); 13 } 14 return lastModified; 15 } 16 }
HelloWorldLastModifiedCacheController只須要實現LastModified接口的getLastModified方法,保證當內容發生改變時返回最新的修改時間便可。
Spring判斷是否過時,經過判斷請求」If-Modified-Since」是否大於等於當前的getLastModified方法的時間戳。若是是,則認爲沒有修改。上面的Controller與普通的Controller並沒有太大的差異,聲明以下:
<bean name="/helloLastModified" class="com.test.controller.HelloWorldLastModifiedCacheController"/>
(六)HandlerInterceptor的處理
Servlet API定義的servlet過濾器能夠在servelt處理每一個Web請求的先後分別對它進行前置處理和後置處理。此外,有些時候,你可能只想處理由某些SpringMVC處理程序處理的Web請求,並在這些處理程序返回的模型屬性被傳遞到視圖以前,對它們進行一些操做。
SpringMVC容許你經過處理攔截Web請求,進行前置處理和後置處理。處理攔截是在Spring的Web應用程序上下文中配置的,所以它們能夠利用各類容器特性,並引用容器中聲明的任何bean。處理攔截是針對特殊的處理程序映射進行註冊的,所以它只攔截經過這些處理程序映射的請求。每一個處理攔截都必須實現HandlerInterceptor接口,它包含三個須要你實現的回調方法:preHandle()、postHandle()和afterCompletion()。第一個和第二個方法分別是在處理程序處理請求以前和以後被調用的。第二個方法還容許訪問返回的ModelAndView對象,所以能夠在裏面操做以前和以後被調用。第二個方法還容許訪問返回的ModelAndView對象,所以能夠在它裏面操做模型屬性。最後一個方法是在全部請求處理完成以後被調用的(如視圖呈現以後),如下是HandlerInterceptor的簡單實現:
1 public class MyTestInterceptor implements HandlerInterceptor{ 2 public boolean preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ 3 long startTime = System.currentTimeMillis(); 4 request.setAttribute("startTime", startTime); 5 return true; 6 } 7 8 public void postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{ 9 long startTime = (Long)request.getAtribute("startTime"); 10 request.removeAttribute("startTime"); 11 long endTime = System.currentTimeMillis(); 12 modelAndView.addObject("handlingTime", endTime-startTime); 13 } 14 15 public void afterCompletion((HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{ 16 } 17 }
在這個攔截器的preHandler()方法中,你記錄了起始時間,並將它保存到請求屬性中。這個方法應該返回true,運行DispatcherServlet繼續處理請求。不然DispatcherServlet會認爲這個方法已經處理了請求,直接將響應返回給用戶。而後,在postHandler()方法中,從請求屬性中加載起始時間,並將它與當前時間進行比較。你能夠計算總的持續時間,而後把這個時間添加到模型中,傳遞給視圖。最後afterCompletion()方法無事可作,空着就能夠了。
(七)邏輯處理
對於邏輯處理實際上是經過適配器中轉調用Handler並返回視圖的,對應代碼:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
一樣,仍是以引導示例爲基礎進行處理邏輯分析,以前分析過,對於普通的Web請求,Spring默認使用SimpleControllerHandlerAdapter類進行處理,咱們進入SimpleControllerHandlerAdapter類的handle方法以下:
1 public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) 2 throws Exception { 3 4 return ((Controller) handler).handleRequest(request, response); 5 }
可是回顧引導示例中的UserController,咱們的邏輯是寫在handleRequestInternal函數中而不是handleRequest函數,因此咱們還須要進一步分析這期間所包含的處理流程。
(八)異常視圖的處理
有時候系統運行過程當中出現異常,而咱們並不但願就此中斷對用戶的服務。而是至少告知客戶當前系統在處理邏輯的過程當中出現了異常,甚至告知他們由於什麼緣由致使的。Spring中的異常處理機制會幫咱們完成這個工做。其實,這裏Spring主要的工做就是將邏輯引導至HandlerExceptionResolver類的resolveException方法,而HandlerExceptionResolver的使用。咱們再講解WebApplicationContext的初始化的時候已經介紹過了。
1 protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{ 2 //Check resistered handlerExceptionResolvers... 3 ModelAndView exMv = null; 4 for (HandlerExceptionResolver handlerExceptionResolver:this.handlerExceptionResolvers){ 5 exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); 6 if (exMv != null){ 7 break; 8 } 9 } 10 if (exMv != null) { 11 if (exMv.isEmpty()){ 12 return null; 13 } 14 //We might still need view name translation for a plain error model.. 15 if (!exMv.hasView()){ 16 exMv.setViewName(getDefaultViewName(request)); 17 } 18 if (logger.isDebugEnabled()){ 19 logger.debug("Handler execution resulted in exception - forwarding to resolved error view:" + exMv, ex); 20 } 21 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); 22 return exMv; 23 } 24 throw ex; 25 }
(九)根據視圖跳轉頁面
不管一個系統仍是一個站點,最重要的工做都是和用戶進行交互,用戶操做系統後不管下發的命令是否成功都須要給用戶一個反饋,以便於用戶進行下一步的判斷。因此,在邏輯處理的最後必定會涉及一個頁面的跳轉的問題。
1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 2 // Determine locale for request and apply it to the response 3 Local local = this.localResolver.resolveLocale(request); 4 response.setLocale(locale); 5 6 View view; 7 if (mv.isReference()){ 8 //We need to resolve the view name 9 view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); 10 if (view == null) { 11 throw new ServletException("Could not resolve view with name'" + mv.getViewName() + "'in servlet with name '" + getServletName() + "'"); 12 } 13 } 14 else { 15 //No need to lookup:the ModelAndView object contains the actual View objects. 16 view = mv.getView(); 17 if (view == null) { 18 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'" ); 19 } 20 } 21 22 // Delegate to the View object for rendering 23 if (logger.isDebugEnable()){ 24 logger.debug("Rendering view [" + view "] in DispatcherServlet with name '" + getServletName() + "'"); 25 } 26 view.render(mv.getModelInternal(), request, response); 27 }
1. 解析視圖名稱
在上文中咱們提到DispatcherServlet會根據ModelAndView選擇合適的視圖來進行渲染,而這一個功能是在resolveViewName函數中完成的。
1 protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, 2 Locale locale, HttpServletRequest request) throws Exception { 3 4 if (this.viewResolvers != null) { 5 for (ViewResolver viewResolver : this.viewResolvers) { 6 View view = viewResolver.resolveViewName(viewName, locale); 7 if (view != null) { 8 return view; 9 } 10 } 11 } 12 return null; 13 }
咱們以org.Springframework.web.servlet.view.InternalResourceViewResolver爲例分析ViewResolver邏輯的解析過程,其中resolveViewName是在其父類AbstractCachingViewResolver(package org.springframework.web.servlet.view)中完成的。
1 public View resolveViewName(String viewName, Locale locale) throws Exception { 2 if (!isCache()) { 3 //不存在緩存的狀況下直接建立視圖 4 return createView(viewName, locale); 5 } 6 else { 7 //直接從緩存中提取 8 Object cacheKey = getCacheKey(viewName, locale); 9 View view = this.viewAccessCache.get(cacheKey); 10 if (view == null) { 11 synchronized (this.viewCreationCache) { 12 view = this.viewCreationCache.get(cacheKey); 13 if (view == null) { 14 // Ask the subclass to create the View object. 15 view = createView(viewName, locale); 16 if (view == null && this.cacheUnresolved) { 17 view = UNRESOLVED_VIEW; 18 } 19 if (view != null && this.cacheFilter.filter(view, viewName, locale)) { 20 this.viewAccessCache.put(cacheKey, view); 21 this.viewCreationCache.put(cacheKey, view); 22 } 23 } 24 } 25 } 26 else { 27 if (logger.isTraceEnabled()) { 28 logger.trace(formatKey(cacheKey) + "served from cache"); 29 } 30 } 31 return (view != UNRESOLVED_VIEW ? view : null); 32 } 33 }
在父類URLBasedViewResolver中重寫了createView函數。
1 protected View createView(String viewName, Locale locale) throws Exception { 2 // If this resolver is not supposed to handle the given view, 3 // return null to pass on to the next resolver in the chain. 4 //若是當前解析器不支持當前給定的view,如viewName爲空等狀況 5 if (!canHandle(viewName, locale)) { 6 return null; 7 } 8 9 // Check for special "redirect:" prefix. 10 //處理前綴爲redirect:xx的狀況 11 if (viewName.startsWith(REDIRECT_URL_PREFIX)) { 12 String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); 13 RedirectView view = new RedirectView(redirectUrl, 14 isRedirectContextRelative(), isRedirectHttp10Compatible()); 15 String[] hosts = getRedirectHosts(); 16 if (hosts != null) { 17 view.setHosts(hosts); 18 } 19 return applyLifecycleMethods(REDIRECT_URL_PREFIX, view); 20 } 21 22 // Check for special "forward:" prefix. 23 //處理前綴爲forward:xx的狀況 24 if (viewName.startsWith(FORWARD_URL_PREFIX)) { 25 String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); 26 InternalResourceView view = new InternalResourceView(forwardUrl); 27 return applyLifecycleMethods(FORWARD_URL_PREFIX, view); 28 } 29 30 // Else fall back to superclass implementation: calling loadView. 31 return super.createView(viewName, locale); 32 }
1 protected AbstractUrlBasedView buildView(String viewName) throws Exception { 2 Class<?> viewClass = getViewClass(); 3 Assert.state(viewClass != null, "No view class"); 4 5 AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); 6 //添加前綴和後綴 7 view.setUrl(getPrefix() + viewName + getSuffix()); 8 view.setAttributesMap(getAttributesMap()); 9 10 String contentType = getContentType(); 11 if (contentType != null) { 12 //設置ContenTye 13 view.setContentType(contentType); 14 } 15 16 String requestContextAttribute = getRequestContextAttribute(); 17 if (requestContextAttribute != null) { 18 view.setRequestContextAttribute(requestContextAttribute); 19 } 20 21 Boolean exposePathVariables = getExposePathVariables(); 22 if (exposePathVariables != null) { 23 view.setExposePathVariables(exposePathVariables); 24 } 25 Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); 26 if (exposeContextBeansAsAttributes != null) { 27 view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); 28 } 29 String[] exposedContextBeanNames = getExposedContextBeanNames(); 30 if (exposedContextBeanNames != null) { 31 view.setExposedContextBeanNames(exposedContextBeanNames); 32 } 33 34 return view; 35 }
通讀以上代碼,咱們發現對於InternalResourceViewResolver所提供的解析功能主要考慮到了如下幾個方面的處理:
基於效率的考慮,提供了緩存的支持。
提供了對redirect:xx和forward:xx前綴的支持。
添加了前綴及後綴,並向View中加入了必須的屬性設置。
2. 頁面跳轉
當經過viewName解析到對應的View後,就能夠進一步處理跳轉邏輯了。
1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 2 // Determine locale for request and apply it to the response. 3 Locale locale = 4 (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); 5 response.setLocale(locale); 6 7 View view; 8 String viewName = mv.getViewName(); 9 if (viewName != null) { 10 // We need to resolve the view name. 11 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); 12 if (view == null) { 13 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + 14 "' in servlet with name '" + getServletName() + "'"); 15 } 16 } 17 else { 18 // No need to lookup: the ModelAndView object contains the actual View object. 19 view = mv.getView(); 20 if (view == null) { 21 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + 22 "View object in servlet with name '" + getServletName() + "'"); 23 } 24 } 25 26 // Delegate to the View object for rendering. 27 if (logger.isTraceEnabled()) { 28 logger.trace("Rendering view [" + view + "] "); 29 } 30 try { 31 if (mv.getStatus() != null) { 32 response.setStatus(mv.getStatus().value()); 33 } 34 view.render(mv.getModelInternal(), request, response); 35 } 36 catch (Exception ex) { 37 if (logger.isDebugEnabled()) { 38 logger.debug("Error rendering view [" + view + "]", ex); 39 } 40 throw ex; 41 } 42 }
本文摘自《Spring源碼深度解析》SpringMVC,做者:郝佳。本文代碼基於的Spring版本爲5.2.4.BUILD-SNAPSHOT,和原書代碼部分會略有不一樣。
拓展閱讀:
Spring框架之beans源碼徹底解析
Spring框架之AOP源碼徹底解析
Spring框架之jms源碼徹底解析
Spring框架之spring-web http源碼徹底解析
Spring框架之spring-web web源碼徹底解析