Spring原始碼深度解析(郝佳)-學習-原始碼解析-Spring MVC

quyixiao發表於2020-12-30

        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。
        對SpringMVC或者其他比較成熟的MVC框架而言,解析問題不外乎以下幾點。

  • 將Web頁面的請求傳給伺服器。
  • 根據不同的請求處理不同的邏輯單元。
  • 返回處理結果資料並跳轉至響應頁面。

我們首先通過一個簡單的示例來看看Spring MVC的使用

  1. 配置web.xml

        一個Web中可以沒有web.xml檔案,也就是說。web.xml檔案並不是Web工程必需的,web.xml檔案用來初始化配置資訊,比如Welcome頁面,servlet,servlet-mapping,filter,listener,啟動載入級別等,但是Spring MVC 的實現原理是通過servlet攔截所有的URL來達到控制的目的。所以web.xml的配置是必需的。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee" 
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
    <display-name>spring_tiny</display-name>

    <!--使用ContextLoaderListener配置時,需要告訴它Spring配置檔案的位置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring_1_100/config_71_80/spring75_mvc/spring75.xml</param-value>
    </context-param>
    
    <!--Spring MVC的前端控制器-->
    <!--當DispatcherServlet載入後,它將從一個XML檔案中載入Spring的應用上下文,該XML檔案的名字取決於<servlet-name></servlet-name>-->
    <!--這裡DispatcherServlet將試圖從一個叫做Spring-servlet.xml的檔案中載入應用上下文,其預設位於WEB-INF目錄下-->
    <servlet>
        <servlet-name>spring_tiny</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>spring_tiny</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
    
    <!--配置上下文載入器-->
    <!--上下文載入器載入除DispatcherServlet載入的配置檔案之外 的其他上下文配置檔案-->
    <!--最常用的上下文載入器是一個Servlet監聽器,茂名稱為ContextLoaderListener-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>com.spring_1_100.test_71_80.test75_springmvc.MyDataContextListener</listener-class>
    </listener>

    
    <!-- 自己配置描述檔案,需要多少個描述檔案就配置多少 -->
    <jsp-config>
        //<!-- 配置c描述檔案-對應c標籤,這裡的taglib-uri對應jsp中引入的uri -->
        <taglib>
            <taglib-uri>http://www.codecoord.com</taglib-uri>
            <taglib-location>/WEB-INF/c.tld</taglib-location>
        </taglib>
    </jsp-config>

</web-app>

        Spring的MVC之所以必需要配置web.xml,其實最關鍵的是要配置兩個地方。

  • contextConfigLocation:Spring的核心就是配置檔案,可以說配置檔案是Spring中必不可少的東西,而這個引數就是使Web與Spring的配置檔案相結合的一個關鍵配置。
  • DispatcherServlet:包含了Spring MVC的請求邏輯,Spring 使用此類攔截Web請求並進行相應的邏輯處理。
2. 建立Spring配置檔案spring75.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <bean id="exceptionHandler" class="com.spring_1_100.test_71_80.test75_springmvc.MyExceptionHandler"></bean>
    
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/list"/>
            <bean class="com.spring_1_100.test_71_80.test75_springmvc.MyTestInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

        InternalResourceViewResolver
是一個輔助bean,會在ModelAndView返回的檢視名前加上prefix指定的字首,再在最後加上suffix指定的字尾,例如:由於XXController返回的是ModelAndView中的檢視名是testview,故該檢視解析器將在/WEB-INF/jsp/testview.jsp處查詢檢視。

3. 建立model

        模型對於Spring MVC 來說並不是必不可少的,如果處理程式非常簡單,完全可以忽略,模型建立寺主要的目的就是承載資料,使用傳輸資料更加方便。

public class User {
    private String username;
    private Integer age;


    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
4.建立controller

        控制器用於處理Web請求,每個控制器都對應一個邏輯處理。

public class UserController extends AbstractController {


    @Override
    protected ModelAndView handleRequestInternal(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
        List<User> userList = new ArrayList<User>();
        User userA = new User("zhangsan",27);
        User userB = new User("lisi",28);
        userList.add(userA);
        userList.add(userB);
        String myData =(String) getServletContext().getAttribute("myData");

        System.out.println("===========" + myData);
        int i = 1 ;
        int j = 0 ;
        //int c = i / j ;

        return new ModelAndView("userlist","users",userList);
    }
}

        在請求的最後返回了ModelAndView型別的例項,ModelAndView類在Spring MVC 中佔有很重要的地位,控制器執行方法都必需返回一個ModelAndView,ModelAndView物件儲存了檢視及檢視顯示的模型資料,例如其中的引數如下:

  • 第一個引數userList:檢視元件的邏輯名稱,這裡檢視的邏輯名稱就是userList,檢視解析器會使用該名稱查詢實際的View物件。
  • 第二個引數user:傳遞給檢視模型的物件名稱。
  • 第三個引數userList:傳遞給檢視模型的物件的值。
5.建立檢視檔案userList.jsp
<%@ page language="java" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://www.codecoord.com" %>
<h2>imya</h2>

<c:forEach items="${users}" var="user">
    <c:out value="${user.username}"></c:out><br/>
    <c:out value="${user.age}"></c:out><br/>
</c:forEach>

        檢視檔案中用於展現請求處理結果,通過對JSTL的支援,可以很方便的展現在控制器中放入的ModelAndView中的處理結果資料。

6.建立Servlet配置檔案Spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/userlist.htm">userController</prop>
                <prop key="/helloWorldLastModified.htm">helloWorldLastModifiedCacheController</prop>
            </props>
        </property>
    </bean>

    <bean id="userController" class="com.spring_1_100.test_71_80.test75_springmvc.UserController"></bean>

    <bean name="helloWorldLastModifiedCacheController" class="com.spring_1_100.test_71_80.test75_springmvc.HelloWorldLastModifiedCacheController"></bean>

</beans>
7.新增自定義攔截器
public class MyTestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime",startTime);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long)request.getAttribute("startTime");
        request.removeAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("request time :" + (endTime -startTime));
        modelAndView.addObject("handlingTime",endTime-startTime);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
8.新增自定義異常處理器
public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        request.setAttribute("exception", ex.toString());
        request.setAttribute("exceptionStack", ex);
        return new ModelAndView("error/exception");
    }
}
9.新增自定義監聽器
public class MyDataContextListener implements ServletContextListener {


    private ServletContext context;

    // 該方法在 ServletContext 啟動後被呼叫,並準備好處理客戶端請求
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        this.context = sce.getServletContext();
        //通過這裡可以實現自己的邏輯並將結果記錄在屬性中
        context.setAttribute("myData","this is my Data");
    }


    //這個方法在ServletContext將要關閉的時候呼叫
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        this.context = null;
    }
}
10.新增快取Handler
public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified {
    private long lastModified;

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println(" cache modified 請求");
        response.getWriter().write("this");
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request) {
        if(lastModified == 0l){
            // 第一次或者邏輯變化的時候,應該重新的返回內容最新的修改時間戳
            lastModified = System.currentTimeMillis();
        }
        return lastModified;
    }
}

        因為Spring MVC 是基於Servlet的實現,所以在Web啟動的時候,伺服器會首先嚐試載入對應的Servlet中的配置檔案,而為了讓專案更加模組化,通常我們將Web部分的配置都存放於此配置檔案中。
        至此,己經完成了Spring MVC的搭建,啟動伺服器。
        idea中的如下配置:
在這裡插入圖片描述
在這裡插入圖片描述

輸入http://localhost:8080/userlist.htm
看到了伺服器返回介面,如下圖所示:
在這裡插入圖片描述

ContextLoaderListener

        對於SpringMVC功能實現分析,我們首先從web.xml開始,在web.xml檔案中我們首先配置就是在ContextLoadListener,那麼它所提供的功能有哪些呢?又是如何實現的呢?
        當使用程式設計的方式的時候,我們可以直接將Spring配置資訊作為引數傳入到Spring容器中,如
        ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
        但是在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方法,所以需要這個方法中實現我們的初始化邏輯。

public class MyDataContextListener implements ServletContextListener {


    private ServletContext context;
    
    // 該方法在 ServletContext 啟動後被呼叫,並準備好處理客戶端請求
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        this.context = sce.getServletContext();
        //通過這裡可以實現自己的邏輯並將結果記錄在屬性中
        context.setAttribute("myData","this is my Data");
    }

    //這個方法在ServletContext將要關閉的時候呼叫
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        this.context = null;
    }


}

2.註冊監聽器
        在web.xml檔案中需要註冊自定義的監聽器。

<listener>
    <listener-class>com.spring_1_100.test_71_80.test75_springmvc.MyDataContextListener</listener-class>
</listener>
3.測試

        一旦Web應用啟動的時候,我們就能在任意的Servlet或者JSP中通過現在的程式碼獲取我們的初始化引數。如下:
        String myData = (String)getServletContext().getAttribute(“myData”);

在這裡插入圖片描述

Spring中的ContextLoaderListener

        分析了ServletContextListener的使用方式後再來分析Spring中的ContextLoaderListener的實現就容易理解得多,雖然ContextLoaderListener實現的邏輯要複雜得多,但是大致的套路還是萬變不離其宗。
        ServletContext啟動後會呼叫ServletContextListener的contextInitialized方法,那麼我們就從這個函式開始進行分析。

ContextLoaderListener.java
public void contextInitialized(ServletContextEvent event) {
	//初始化WebApplicationContext
    initWebApplicationContext(event.getServletContext());
}

        這裡涉及了一個常用的類WebApplicationContext:在Web應用中,我們會用於WebApplicationContext,WebApplicationContext繼承自ApplicationContext,在ApplicationContext的基礎上又追加了一些特定的Web操作及屬性,非常類似於我們通過程式設計的方式使用Spring時使用的ClassPathXmlApplicationContext類提供的功能。繼續跟蹤程式碼。

ContextLoader.java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.class.getName() + ".ROOT") != null) {
    	//web.xml中存在多次ContextLoader的定義,丟擲異常
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        if (this.context == null) {
        	//如果WebApplicationContext為空,建立容器,從後面的程式碼分析中,我們得知,建立的是XmlWebApplicationContext物件
            this.context = createWebApplicationContext(servletContext);
        }
        //從 圖1 得知,XmlWebApplicationContext繼承了AbstractRefreshableWebApplicationContext,
        //而 AbstractRefreshableWebApplicationContext實現了ConfigurableWebApplicationContext
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
            	//上下文尚未重新整理->提供服務,例如 設定父上下文,設定應用程式上下文ID等
                if (cwac.getParent() == null) {
                	//如果容器並沒有父容器,嘗試從servlet容器注入其父容器
                    ApplicationContext parent = loadParentContext(servletContext);
                    //為容器設定父容器
                    cwac.setParent(parent);
                }
                //配置並重新整理容器
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        //在servlet容器中注入Spring容器
        servletContext.setAttribute(WebApplicationContext.class.getName() + ".ROOT", this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            //列印ContextLoaderListener的初始化時間
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}

        從後面的程式碼中分析得知,context是XmlWebApplicationContext物件,下面我們看看XmlWebApplicationContext的物件關係圖
圖1
在這裡插入圖片描述
        initWebApplicationContext函式中主要的體現了建立WebApplicationContext例項的一個功能構架,從函式中我們看到了初始化的大致步驟。

  1. WebApplicationContext存在性驗證
            在配置中只允許宣告一次ServletContextListener,多次宣告會擾亂Spring的執行邏輯,所以在這裡首先做的就是對此驗證,在Spring中如果建立了WebApplicationContext例項會記錄在ServletContext中以方便全域性呼叫,而使用的key就是WebApplicationContext.class.getName() + “.ROOT”,所以驗證的方式就是檢視ServletContext例項中是否有對應的key的屬性。
    2.建立WebApplication例項
            如果通過驗證,則Spring將建立WebApplicationContext例項的工作委託給了createWebApplicationContext函式。
ContextLoader.java
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    //例項化 XmlWebApplicationContext類,並返回
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
	//CONTEXT_CLASS_PARAM = "contextClass"
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
    	//從defaultStrategies中獲取WebApplicationContext的名稱
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
        	//反射獲取XmlWebApplicationContext的Class
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

        那麼defaultStrategies是從哪裡來的呢?其中,在ContextLoader類中有這樣的靜態程式碼塊:

ContextLoader.java
private static final Properties defaultStrategies;

static {
    try {
    	//DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    }
}

        從上面得知,contextClassName來處於當前環境變數的ContextLoader.properties檔案, 我們找到這個檔案。
在這裡插入圖片描述
        從這個檔案中,我們看到了org.springframework.web.context.WebApplicationContext對應的是org.springframework.web.context.support.XmlWebApplicationContext類名。因此上述程式碼中contextClassName對應的是XmlWebApplicationContext類。
        綜合以上的程式碼分析,在初始化的過程中,程式首先會讀取ContextLoader類的同目錄下的屬性檔案ContextLoader.properties,並根據其中的配置提取將要實現的WebApplicationContext介面的實現類,並根據這個實現類通過反射的方式進行例項的建立。
3.將例項記錄在servletContext中。
4.對映當前的類載入器與建立例項到全域性變數currentContextPerThread中。

ContextLoader.java
protected ApplicationContext loadParentContext(ServletContext servletContext) {
    ApplicationContext parentContext = null;
    //如果servlet容器中有locatorFactorySelector或parentContextKey配置,則從容器中獲取,否則返回空
    String locatorFactorySelector = servletContext.getInitParameter("locatorFactorySelector");
    String parentContextKey = servletContext.getInitParameter("parentContextKey");

    if (parentContextKey != null) {
        // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
        BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
        Log logger = LogFactory.getLog(ContextLoader.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Getting parent context definition: using parent context key of '" +
                    parentContextKey + "' with BeanFactoryLocator");
        }
        this.parentContextRef = locator.useBeanFactory(parentContextKey);
        parentContext = (ApplicationContext) this.parentContextRef.getFactory();
    }

    return parentContext;
}
ContextLoader.java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter("contextId");
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            wac.setId(WebApplicationContext.class.getName() + ":" +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
	//Spring容器設定servlet容器
    wac.setServletContext(sc);
    //從servlet容器中獲取contextConfigLocation引數,也就是
    //<context-param>
    //	<param-name>contextConfigLocation</param-name>
    //	<param-value>classpath:spring_1_100/config_71_80/spring75_mvc/spring75.xml</param-value>
	//</context-param>
	//也就是param-value標籤的值
    String configLocationParam = sc.getInitParameter("contextConfigLocation");
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    //從setConfigLocation方法得知,獲取到的環境變數是StandardServletEnvironment物件
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
	//呼叫所有實現了ApplicationContextInitializer的initialize方法
    customizeContext(sc, wac);
    //主要為IoC容器Bean的生命週期管理提供條件
    wac.refresh();
}
AbstractRefreshableConfigApplicationContext.java
public void setConfigLocation(String location) {
	//將location以 ,; \t\n 分割
    setConfigLocations(StringUtils.tokenizeToStringArray(location, 
   ",; \t\n"));
}
public void setConfigLocations(String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}

protected String resolvePath(String path) {
    return getEnvironment().resolveRequiredPlaceholders(path);
}
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        this.environment = createEnvironment();
    }
    return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
    return new StandardServletEnvironment();
}

        因為XmlWebApplicationContext繼承AbstractRefreshableWebApplicationContext類,因此最終建立環境變數是呼叫AbstractRefreshableWebApplicationContext的createEnvironment方法,最終建立了StandardServletEnvironment環境變數。
        對於StandardServletEnvironment的建立也沒有那麼簡單,在繼續分析之前我們來看看StandardServletEnvironment物件關係。
在這裡插入圖片描述
        在程式碼中尋尋覓覓。發現AbstractEnvironment竟然重寫的預設的構造方法。

AbstractEnvironment.java
public AbstractEnvironment() {
	//StandardServletEnvironment建立過程中預設呼叫了customizePropertySources方法
    customizePropertySources(this.propertySources);
    if (this.logger.isDebugEnabled()) {
        this.logger.debug(format(
                "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
    }
}
StandardServletEnvironment.java
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
    propertySources.addLast(new StubPropertySource("servletContextInitParams"));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource("jndiProperties"));
    }
    super.customizePropertySources(propertySources);
}
StandardEnvironment.java
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new MapPropertySource("systemProperties", getSystemProperties()));
    propertySources.addLast(new SystemEnvironmentPropertySource("systemProperties", getSystemEnvironment()));
}
StandardServletEnvironment.java
public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

        經過上面StandardServletEnvironment建立過程分析,我們己經知道了getPropertySources方法獲取的到的propertySources實際上是來自於StandardServletEnvironment實體化過程中的customizePropertySources方法呼叫。

public MutablePropertySources getPropertySources() {
    return this.propertySources;
}
WebApplicationContextUtils.java
public static void initServletPropertySources(
        MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {

    Assert.notNull(propertySources, "propertySources must not be null");
    //如果servletContext不為空,則用ServletContextPropertySource替換掉
    //StandardServletEnvironment類中的customizePropertySources建立的StubPropertySource
    if (servletContext != null && propertySources.contains("servletContextInitParams") &&
            propertySources.get("servletContextInitParams") instanceof StubPropertySource) {
        propertySources.replace("servletContextInitParams",
                new ServletContextPropertySource("servletContextInitParams", servletContext));
    }
    //如果servletConfig不為空,則用
    //ServletConfigPropertySource替換掉StandardServletEnvironment類中的
    //customizePropertySources方法建立的StubPropertySource
    if (servletConfig != null && propertySources.contains("servletConfigInitParams") &&
            propertySources.get("servletConfigInitParams") instanceof StubPropertySource) {
        propertySources.replace("servletConfigInitParams",
                new ServletConfigPropertySource("servletConfigInitParams", servletConfig));
    }
}
ContextLoader.java
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
            determineContextInitializerClasses(sc);

    for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
        Class<?> initializerContextClass =
                GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
        if (initializerContextClass != null) {
            Assert.isAssignable(initializerContextClass, wac.getClass(), String.format(
                    "Could not add context initializer [%s] since its generic parameter [%s] " +
                    "is not assignable from the type of application context used by this " +
                    "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
                    wac.getClass().getName()));
        }
        this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    }
	//以ApplicationContextInitializer例項上配置的Order註解作為排序條件排序,值越小,越先被呼叫
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
    	//呼叫所有的ApplicationContextInitializer的initialize方法
        initializer.initialize(wac);
    }
}
ContextLoader.java
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
        determineContextInitializerClasses(ServletContext servletContext) {

    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
            new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
	//如果想使用globalInitializerClasses引數,則可以在web.xml下配置
	//<context-param>
    //	<param-name>globalInitializerClasses</param-name>
    //	<param-value>com.spring_1_100.test_71_80.test75_springmvc.MyApplicationContextInitializer</param-value>
	//</context-param>
	//param-value標籤內容可以以,; \t\n隔開
    String globalClassNames = servletContext.getInitParameter("globalInitializerClasses");
    if (globalClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(globalClassNames, ",; \t\n")) {
            classes.add(loadInitializerClass(className));
        }
    }
	//如果想使用contextInitializerClasses引數,則可以在web.xml下配置
	//<context-param>
    //	<param-name>contextInitializerClasses</param-name>
    //	<param-value>com.spring_1_100.test_71_80.test75_springmvc.MyApplicationContextInitializer</param-value>
	//</context-param>
    String localClassNames = servletContext.getInitParameter("contextInitializerClasses");
    if (localClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(localClassNames, ",; \t\n")) {
            classes.add(loadInitializerClass(className));
        }
    }
    return classes;
}
ContextLoader.java
private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
    try {
        Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
        Assert.isAssignable(ApplicationContextInitializer.class, clazz);
        return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
    }
    catch (ClassNotFoundException ex) {
        throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
    }
}
AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1.呼叫容器準備重新整理的方法,獲取容器的當前時間,同時給容器設定同步標識
        prepareRefresh();
        
        // 2.告訴子類啟動refreshBeanFactory()方法,Bean定義資原始檔的載入從子類 的refreshBeanFactory()方法啟動
        // 在refresh()方法中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory() 啟動了Bean的註冊
        // Bean定義資源的載入,註冊過程,finishBeanFactoryInitialization() 方法是對註冊後的Bean定義中的預例項化(lazy-init=false)
        // Spring 預設進行預例項化,即為true的Bean 進行處理的地方
        // 初始化 bean ,並進行xml 檔案的讀取
        // obtainFreshBeanFactory 方法從字面的理解是獲取 BeanFactory ,之前有說過,ApplicationContext 是對 BeanFactory
        // 的功能上基礎上新增了大量的擴充套件應用,那麼 obtainFreshBeanFactory 正是實現 BeanFactory 的地方,也就是經過這個函式之後
        // ApplicationContext 就已經擁有 BeanFactory 的全部功能
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3.為 BeanFactory 進行各種功能進行填充
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 子類覆蓋方法做額外的處理 
            postProcessBeanFactory(beanFactory);    
            // 5.啟用各種 BeanFactory 處理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6.為BeanFactory註冊Post事件處理器
            // BeanPostProcessor(BeanFactory)
            // 註冊攔截 bean 建立 bean 處理器,這裡只是註冊,真正的呼叫在 getBean 時候
            registerBeanPostProcessors(beanFactory);
            
            // 7.為上下文初始化 Message源,即不同的語言的訊息體,國際化處理
            initMessageSource();
            
            // 8.初始化應用訊息廣播器,並放入到"applicationEventMulticaster" bean 中
            initApplicationEventMulticaster();
            
            // 9. 留給子類來初始化其他的 Bean
            onRefresh();

            // 10.在所有的註冊的 bean 中查詢 Listener Bean ,註冊到訊息廣播器中
            registerListeners();
            
            // 11.初始化所有剩餘的單例Bean
            finishBeanFactoryInitialization(beanFactory);
            
            // 12.完成重新整理過程,通知生命週期處理器 lifecycleProcessor 重新整理
            // 過程,同時發出 contextRefreshEvent 通知別人
            finishRefresh();
        } catch (BeansException ex) {
            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
            // 13.銷燬已經建立的bean
            destroyBeans();
            // 14.取消重新整理操作,重置容器的同步標識
            cancelRefresh(ex);
            throw ex;
        } finally {
            // 設定公共快取
            resetCommonCaches();
        }
    }
}

下面概括一下 ClassPathXmlApplicationContext 初始化的步驟,並從中解釋一下它為我們提供的功能

  1. 初始化前的準備工作,例如對系統屬性或者環境變數進行準備及驗證
    在某種情況下,專案的使用需要讀取某些系統變數,而這個變數的設定很可能會影響到系統的正確性,那麼 ClassPathXmlApplicationContext
    為我們提供的準備函式就顯得非常的必要了,它可以在 Spring 啟動的時候提前對必需的變數進行存在性驗證
  2. 初始化 beanFactory 進行 Xml 檔案的讀取
    之前有提到的 ClasspathXmlApplicationContext包含著 BeanFactory 所提供的一切特徵,在這一步驟中將會複用 BeanFActory 中的配置
    檔案讀取及解析其他的功能,這一步之後,ClassPathXmlApplicationContext 實際上就已經包含了 BeanFactory 所提供的功能,也就是可以
    進行 Bean 的提取等基礎操作了
  3. 對 BeanFactory 進行各種功能的填充
    @Qualifier 與@Autowired 應該是大家非常熟悉的註解了,那麼這兩個註冊正是這一步驟增加的支援
  4. Spring 之所以強大,除了它功能上為大家提供了便例外,還有一方面它的完美架構,開放式的架構讓使用它的程式設計師很容易根據業務需要擴充套件
    已經存在的功能,這種開放式的設定在 Spring中隨處可見,例如在配合中就提供了一個空間函式的實現,postProcessBeanFactory 來方便程式設計師
    在業務上做進步的擴充套件
  5. 啟用各種 beanFactory 的處理器
  6. 註冊攔截 bean 建立的 bean 處理器,這裡只是註冊,真正的呼叫是在 getBean時候
  7. 為上下文初始化 Message源,即對不同的語言的訊息進行國際化的處理
  8. 初始化應用的訊息廣播器,並放入到"applicationEventMulticaster" bean 中
  9. 留給子類來初始化其他的 bean
  10. 所有的註冊的 bean 中查詢 listener bean 註冊到的訊息廣播器中
  11. 初始化剩下的單例(非惰性的)
  12. 完成重新整理的過程,通知生命週期處理器 lifecycleProcessor 重新整理過程,同時發出 contextRefreshEvent 通知別人

        因為這裡涉及到整個Spring容器的初始化流程,所以就不再深入研究了,但是我們要知道的一點的就是contextConfigLocation中配置的 classpath:spring_1_100/config_71_80/spring75_mvc/spring75.xml己經被初始化到容器中。

DispatcherServlet

        在Spring中,ContextLoaderListener只是輔助功能,用於建立WebApplicationContext型別例項,而真正的邏輯實現是在DispatcherServlet中進行的,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.執行階段
        當servlet容器接收到一個請求時,servlet容器會針對這個請求建立servletRequest和servletResponse物件,然後呼叫servlet方法,並把這個引數傳遞給servlet方法,servlet方法通過servletRequest物件獲取請求資訊,並處理請求,再通過servletResponse物件生成這個請求的響應結果,然後銷燬servletRequest和servletResponse物件,我們不管請求的是post提交還是get提交,最終這個請求都會由service方法來處理。
3.銷燬階段
        當Web應用被終止時,servlet容器會先呼叫servlet物件的destory方法,然後再銷燬servlet物件,同時也會銷燬與servlet物件相關聯的servletConfig物件,我們可以在destory方法的實現中,釋放servlet所佔用的資源,如關閉資料庫連線,關閉檔案輸入輸出流等。
        servlet的框架是由兩個Java包組成,javax.servlet和javax.servlet.http,在javax.servlet包中定義了所有的servlet類都必需實現或擴充套件通用的介面和類,在javax.servlet.http包中定義了採用HTTP通訊協義的HttpServlet類。
        servlet被設計成請求驅動 ,servlet的請求可能包含多個資料項,當Web容器接收到某個servlet請求時,servlet把請求封裝成一個HttpServletRequest物件,然後把物件傳給servlet的對應的服務方法。
        HTTP的請求方式包括delete,get,options,post,put和trace,在HttpServlet類中分別提交了相應的服務方法,它們是doDelete(),doGet(),doOptions(),doPost(),doPut()和doTrace()。

servlet的使用

        我們同樣還是以簡單的servlet來快速體驗其用法。

public class MyServlet extends HttpServlet {

    public void init() {
        System.out.println("this is init method ");
    }


    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        handleLogic(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) {
        handleLogic(request, response);
    }

    private void handleLogic(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("handle myLogic ");
        ServletContext sc = getServletContext();
        RequestDispatcher rd = null;
        rd = sc.getRequestDispatcher("/index.jsp");
        try {
            rd.forward(request, response);
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

        麻雀雖小,五臟俱全,例項中包含了對init方法和get/post方法的處理,init方法保證了servlet載入的時候能做一些邏輯操作,而HttpServlet類則會幫助我們根據方法型別的不同而將邏輯引入不同的函式,在子類中我們只需要重寫對應的函式邏輯便可,如以上的程式碼重寫了doGet和doPost方法並將邏輯處理部分引導到handleLogic函式中,最後,又將頁面跳轉至index.jsp
2.新增配置
        為了使servlet能夠正常使用,需要在web.xml檔案中新增以下的配置。

<servlet>
	<servlet-name>myservlet</servlet-name>
	<servlet-class>test.servlet.MyServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>myservlet</servlet-name>
	<url-pattern>*.htm</url-pattern>
</servlet-mapping>

        配置後便可以根據對應的配置訪問相應的路徑了

DispatcherServlet的初始化

        通過上面的例項我們瞭解到,在servlet初始化的階段會呼叫其init方法,所以我們首先要檢視的是DispatcherServlet中是否重寫了init方法,我們在其父類HttpServletBean中找到該方法。

HttpServletBean.java
public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		logger.debug("Initializing servlet '" + getServletName() + "'");
	}
	
	try {
		//解析init-parm並封裝至pvs中
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		//將當前的這個servlet類轉化為一個BeanWrapper,從而能夠以Spring的方式來對init-param的值進行注入
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		//註冊自定義屬性編輯器,一旦遇到Resources型別的屬性將會使用ResourceEditor進行解析
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
		//空實現,留給子類覆蓋
		initBeanWrapper(bw);
		//屬性注入
		bw.setPropertyValues(pvs, true);
	}
	catch (BeansException ex) {
		logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
		throw ex;
	}
	//留給子類擴充套件
	initServletBean();
	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

        DispatcherServlet的初始化過程主要是通過將當前的servlet型別例項轉化為BeanWrapper型別的例項,以便使用Spring中提供的功能進行對應的屬性注入,這些屬性如contextAttribute,contextClass,nameSpace,contextConfigLocation等,都可以在web.xml檔案中以初始化引數的方式配置在servlet宣告中,DispacherServlet繼承自FrameworkServlet,FrameworkServlet類上包含對應的同名屬性,Spring會保證這些引數被注入到對應的值中,屬性注入主要包含以下的幾個步驟。

  1. 封裝及驗證初始化引數
ServletConfigPropertyValues.java
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
	throws ServletException {

	Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
			new HashSet<String>(requiredProperties) : null;

	Enumeration<String> en = config.getInitParameterNames();
	while (en.hasMoreElements()) {
		String property = en.nextElement();
		Object value = config.getInitParameter(property);
		addPropertyValue(new PropertyValue(property, value));
		if (missingProps != null) {
			missingProps.remove(property);
		}
	}
	if (missingProps != null && missingProps.size() > 0) {
		throw new ServletException(
			"Initialization from ServletConfig for servlet '" + config.getServletName() +
			"' failed; the following required properties were missing: " +
			StringUtils.collectionToDelimitedString(missingProps, ", "));
	}
}

        從程式碼中得知,封裝屬性主要對初始化的引數進行封裝,也就是servlet中配置的<init-param>中配置的封裝,當然,使用者也可能通過對requiredProperties引數的初始化來強制驗證某些屬性的必要性,這樣,在屬性封裝的過程中,一旦檢測到requiredProperties中的屬性沒有指定的初始化值,就會丟擲異常。

  1. 將當前的servlet例項轉化成BeanWrapper例項。
    PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要是用於將指定例項轉化為Spring中可以處理的BeanWrapper型別的例項。
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

  2. 註冊相對於Resource的屬性編輯器
    屬性編輯器,我們在之前的部落格中己經介紹了並且分析過其工作原理,這裡使用屬性編輯器的目的是在對當前例項(DispatcherServlet)屬性注入的過程中一旦遇到Resource型別的屬性就會使用ResourceEditor去解析。

  3. 屬性注入
    BeanWrapper為Spring中的方法,支援Spring的自動注入,其實我們最常用的屬性注入無非是contextAttribute,contextClass,nameSpace,contextCofigLocation等。

  4. servletBean的初始化

        在ContextLoaderListener載入的時候己經建立了WebApplicationContext的例項,而在這個函式中最重要的就是對這個例項進一步的補充初始化。
        繼續檢視initServletBean(),父類FrameworkServlet覆蓋了HttpServletBean中的initServletBean函式,如下:

FrameworkServlet.java
protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		this.webApplicationContext = initWebApplicationContext();
		//設計為子類覆蓋
		initFrameworkServlet();
	}
	catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}
	catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (this.logger.isInfoEnabled()) {
		long elapsedTime = System.currentTimeMillis() - startTime;
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
				elapsedTime + " ms");
	}
}

        上面的函式設定了充電器來統計初始化的執行時間,而且提供了一個擴充套件方法initFrameworkServlet()用於子類的覆蓋操作,而作為關鍵的初始化邏輯實現委託給我了initWebApplicationContext()。

WebApplicationContext的初始化

        initWebApplicationContext函式的主要工作就是建立或重新整理WebApplicationContext例項並對servlet功能所使用的變數進行初始化。

FrameworkServlet.java
protected WebApplicationContext initWebApplicationContext() {
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		//context例項在建構函式中被注入
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					cwac.setParent(rootContext);
				}
				//重新整理上下文環境
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		//根據contextAttribute屬性載入WebApplicationContext
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
		// Either the context is not a ConfigurableApplicationContext with refresh
		// support or the context injected at construction time had already been
		// refreshed -> trigger initial onRefresh manually here.
		onRefresh(wac);
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
					"' as ServletContext attribute with name [" + attrName + "]");
		}
	}
	return wac;
	}
}

        對於本函式中的初始化主要包含幾個部分。

  1. 尋找或建立對應的WebApplicationContext例項

        WebApplicationContext的尋找及建立包括以下的幾個步驟。
        通過建構函式注入進行初始化。
        當進入initWebApplicationContext函式後通過判斷this.webApplicationContext !=null後,便可以確定this.webApplicationContext是否通過建構函式來初始化的,可是讀者可能會有疑問,在initServletBean函式中明明是把建立好的例項記錄了this.webAppliationContext中:
        this.webApplicationContext = initWebApplicationContext();
        何以判斷這個引數是通過建構函式初始化,而不是通過上一次函式返回的初始化值呢?如果存在這個問題,那麼就是讀者忽略了一個問題,在Web中包含了Spring的核心邏輯是DispatcherServlet只可以被宣告一次,在Spring中己經存在驗證,所以這就確保瞭如果this.webApplicationContext !=null ?則可以直接判定this.webApplicationContext己經通過建構函式初始化了。
        通過web.xml檔案中配置的servlet引數contextAttribute來查詢ServletContext中對應的屬性,預設是WebApplicationContext.class.getName()+".ROOT",也就是在ContextLoaderListener載入時會建立WebApplicationContext例項,並將例項以WebApplicationContext.class.getName()+".ROOT"為key放入到ServletContext中,當然讀者可以重寫初始化邏輯使用自己建立的WebApplicationContext,並在servlet的配置中通過初始化引數contextAttribute指定key。

FrameworkServlet.java
protected WebApplicationContext findWebApplicationContext() {
	String attrName = getContextAttribute();
	if (attrName == null) {
		return null;
	}
	WebApplicationContext wac =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
	if (wac == null) {
		throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
	}
	return wac;
}
  1. 重新建立WebApplicationContext例項。

        如果通過以上的兩種情況沒有找到任何突破,那就沒有辦法了,只能這裡建立新的例項了。

FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
	return createWebApplicationContext((ApplicationContext) parent);
}
FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
	//獲取servlet的初始化引數contextClass,如果沒有配置預設的,則使用XmlWebApplicationContext.class
	Class<?> contextClass = getContextClass();
	if (this.logger.isDebugEnabled()) {
		this.logger.debug("Servlet with name '" + getServletName() +
				"' will try to create custom WebApplicationContext context of class '" +
				contextClass.getName() + "'" + ", using parent context [" + parent + "]");
	}
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException(
				"Fatal initialization error in servlet with name '" + getServletName() +
				"': custom WebApplicationContext class [" + contextClass.getName() +
				"] is not of type ConfigurableWebApplicationContext");
	}
	//通過反射方法例項化contextClass
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

	wac.setEnvironment(getEnvironment());
	//parent為在ContextLoaderListener中建立的例項
	//在ContextLoaderListener載入的時候初始化的WebApplicationContext型別例項
	wac.setParent(parent);
	//獲取contextConfigLocation屬性,配置的servlet初始化引數中
	wac.setConfigLocation(getContextConfigLocation());
	//初始化Spring環境包括載入配置檔案等
	configureAndRefreshWebApplicationContext(wac);

	return wac;
}
  1. configureAndRefreshWebApplicationContext

        無論是通過建構函式還是單獨建立,都會呼叫configureAndRefreshWebApplicationContext方法來對己經建立的WebApplicationContext例項進行配置及重新整理,那麼這個步驟又做了哪些工作呢?

FrameworkServlet.java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
		if (this.contextId != null) {
			wac.setId(this.contextId);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
		}
	}
	//設定ServletContext上下文
	wac.setServletContext(getServletContext());
	//設定ServletConfig
	wac.setServletConfig(getServletConfig());
	//設定名稱空間,預設是servlet-name+-servlet
	//本次的名稱空間是spring_tiny-servelt
	wac.setNamespace(getNamespace());
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
	
	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}

	postProcessWebApplicationContext(wac);
	applyInitializers(wac);
	//載入配置檔案及整合parent到wac中
	wac.refresh();
}

在這裡插入圖片描述
        無論呼叫方式如何變化,只要是使用了ApplicationContext所提供的功能最後都免不了使用公共父類AbstractApplicationContext提供的refresh()方法配置檔案載入。

protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}
SourceFilteringListener.java
public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {

	private final Object source;

	private GenericApplicationListener delegate;

	public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
		this.source = source;
		//顯然ContextRefreshListener並沒有實現GenericApplicationListener
		this.delegate = (delegate instanceof GenericApplicationListener ?
				(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
	}
}

        其他檔案的解析和初始化都己經完成,但是/WEB-INF/spring_tiny-servlet.xml檔案到目前為止都沒有被初始化,那這個檔案在哪裡被載入的呢?
在這裡插入圖片描述
        發現在loadBeanDefinitions方法中被載入,那麼這個方法是在哪裡被呼叫的呢?我們繼續向前找。通過下圖得知,原來是在呼叫obtainFreshBeanFactory方法是被初始化。
在這裡插入圖片描述
        但是還是不明確 ,到底是在Dispatcher的init方法中呼叫,還是在ContextLoaderListener的contextInitialized方法呢?我們繼續向前查詢。
在這裡插入圖片描述
        最終發現在Dispatcher的init方法內初始化。我們回到初始化載入xml這段程式碼。

XmlWebApplicationContext.java
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		for (String configLocation : configLocations) {
			//load WEB-INF/spring_tiny-servlet.xml
			reader.loadBeanDefinitions(configLocation);
		}
	}
}
AbstractRefreshableWebApplicationContext.java
public String[] getConfigLocations() {
	return super.getConfigLocations();
}
AbstractRefreshableConfigApplicationContext
protected String[] getConfigLocations() {
	return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}

protected String[] getDefaultConfigLocations() {
	if (getNamespace() != null) {
		return new String[] {"/WEB-INF/" + getNamespace() + ".xml"};
	}
	else {
		return new String[] {"/WEB-INF/applicationContext.xml"};
	}
}

        當進行DispacherServlet的init化的時候,並沒有設定configLocations,但是設定了namespace,因此在容器初始化時會載入spring_tiny-servlet.xml檔案內的bean。

AbstractApplicationContext.java
protected void finishRefresh() {
	initLifecycleProcessor();

	// Propagate refresh to lifecycle processor first.
	getLifecycleProcessor().onRefresh();
	
	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));

	// Participate in LiveBeansView MBean, if active.
	LiveBeansView.registerApplicationContext(this);
}
AbstractApplicationContext.java
@Override
public void publishEvent(ApplicationEvent event) {
	publishEvent(event, null);
}

AbstractApplicationContext.java
protected void publishEvent(Object event, ResolvableType eventType) {
	Assert.notNull(event, "Event must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Publishing event in " + getDisplayName() + ": " + event);
	}

	// Decorate event as an ApplicationEvent if necessary
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent) {
		applicationEvent = (ApplicationEvent) event;
	}
	else {
		applicationEvent = new PayloadApplicationEvent(this, event);
		if (eventType == null) {
			eventType = ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, event.getClass());
		}
	}

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}
SimpleApplicationEventMulticaster.java
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		Executor executor = getTaskExecutor();
		if (executor != null) {
			executor.execute(new Runnable() {
				@Override
				public void run() {
					invokeListener(listener, event);
				}
			});
		}
		else {
			invokeListener(listener, event);
		}
	}
}
SimpleApplicationEventMulticaster.java
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (Throwable err) {
			errorHandler.handleError(err);
		}
	}
	else {
		listener.onApplicationEvent(event);
	}
}

SourceFilteringListener.java
public void onApplicationEvent(ApplicationEvent event) {
	if (event.getSource() == this.source) {
		onApplicationEventInternal(event);
	}
}
SourceFilteringListener.java
protected void onApplicationEventInternal(ApplicationEvent event) {
	if (this.delegate == null) {
		throw new IllegalStateException(
				"Must specify a delegate object or override the onApplicationEventInternal method");
	}
	this.delegate.onApplicationEvent(event);
}
ContextRefreshListener.java
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		FrameworkServlet.this.onApplicationEvent(event);
	}
}
FrameworkServlet.java
public void onApplicationEvent(ContextRefreshedEvent event) {
	this.refreshEventReceived = true;
	onRefresh(event.getApplicationContext());
}
  1. 重新整理

        onReresh是FrameworkServlet類中提供的模板方法,在其子類DispatcherServlet進行了重寫,主要過程用於重新整理Spring在Web功能實現中所有必需使用的全域性變數,下面我們會介紹它們的初始化過程以及使用場景,而至於具體的使用細節在稍後再來做詳細的介紹。

DispatcherServlet.java
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
	//(1) 初始化MultipartResolver
	initMultipartResolver(context);
	//(2) 初始化LocaleResolver
	initLocaleResolver(context);
	//(3) 初始化ThemeResolver
	initThemeResolver(context);
	//(4) 初始化HandlerMappings
	initHandlerMappings(context);
	//(5) 初始化HandlerAdapters
	initHandlerAdapters(context);
	//(6) 初始化HandlerExceptionResolvers
	initHandlerExceptionResolvers(context);
	//(7) 初始化RequestToViewNameTranslator
	initRequestToViewNameTranslator(context);
	//(8)初始化ViewResolvers
	initViewResolvers(context);
	//(9)初始化FlashMapManager
	initFlashMapManager(context);
}
  1. 初始化MultipartResolver

        在Spring中,MultipartResolver主要用來處理檔案上傳,預設的情況下,Spring是沒有multipart處理,因為一些開發者想要自己處理他們,如果想使用Spring的multipart,則需要在Web應用上下文中新增multipart解析器,這樣,每個請求就會被檢查是否包含multipart,然而,如果請求中包含了multipart解析器,這樣,每個請求就會被檢查到是否包含multipart,然而,如果請求中包含multipart,那麼上下文中定義的MultipartResolver就會解析它,這樣請求中的multipart屬性就會像其他的屬性一樣被處理,常用配置如下:

<bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    //<!--該屬性用來配置可上傳檔案的最大位元組數-->
    <property name="maxInMemorySize" >
        <value>10000</value>
    </property>
</bean>

        當然,CommonsMultipartResolver還提供了其他功能用於幫助使用者完成上傳功能,有興趣的讀者可以看一下。
        那麼MultipartResolver就是在initMultipartResolver中被加入到DispatcherServlet中的。

DispatcherServlet.java
private void initMultipartResolver(ApplicationContext context) {
	try {
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// Default is no multipart resolver.
		this.multipartResolver = null;
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
					"': no multipart request handling provided");
		}
	}
}

        因為之前的步驟己經完成了Spring中配置檔案的解析,所以在這裡只要在配置檔案註冊過都可以通過ApplicationContext提供的getBean方法來直接獲取對應的bean,進而初始化MultipartResolver中的multipartResolver變數。

  1. 初始化LocaleResolver

        在Spring中國際化配置中一共有3種使用方式

  • 基於Url引數的配置

        通過URL引數來控制國際化,比如你在頁面上加一句< href="?locale=zh_CN" > 簡體中文</a>來控制專案中使用國際化引數,而提供這個功能就是AcceptHeaderLocaleResolver,預設的引數名為locale,注意大小寫,裡面放的就是你的提交引數,比如en_US,zh_CN之類,具體的配置如下:
        <bean id=“localeResolver” class=“org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver”></bean>

  • 基於session的配置

        它通過檢驗的使用者會話中的預置屬性來解析區域,最常用的是根據使用者本次會話過程中的語言設定決定語言各類(例如,使用者時選擇語言各類,則本次登入週期內統一使用此語言設定),如果該會話屬性不存在,它會根據accept-language HTTP 頭部確定預設的區域。
        <bean id=“localeResolver” class=“org.springframework.web.servlet.i18n.SessionLocaleResolver”></bean>

  • 基於cookie的國際化配置

        CookieLocaleResolver用於通過瀏覽器的cookie設定取得Locale物件,這種策略的應用程式不支援會話或者狀態必需儲存在客戶端時有用,配置如下:
        <bean id=“localeResolver” class=“org.springframework.web.servlet.i18n.CookieLocaleResolver”></bean>

        這3種方式都可以解決國際化問題,但是,對於LocalResolver的使用基礎是在DispatcherServlet中的初始化。

private void initLocaleResolver(ApplicationContext context) {
	try {
		this.localeResolver = context.getBean("localeResolver", LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
					"': using default [" + this.localeResolver + "]");
		}
	}
}

        提取配置檔案中設定LocaleResolver來初始化DispatcherServlet中的localeResolver屬性。

  1. 初始化ThemeResolver。

        在Web開發中經常會遇到通過主題Theme來控制網頁風格,這將進一步改善使用者體驗,簡單的說,一個主題就是一組靜態資源(比如樣式表和圖片),它們可以影響應用程式的視覺效果,Spring中的主題功能和國際化功能非常相似,Spring主題功能的構成主要包括如下內容。

  • 主題資源

        org.springframework.ui.context.ThemeSource是Spring中主題資源的介面,Spring的主題需要通過ThemeSource介面來實現存放主題資訊資源。
        org.springframework.ui.context.ResourceBundleThemeSource是ThemeSource介面預設的實現類(也是通過ResourceBoundle資源的方式定義主題),在Spring中配置如下:

<bean id=“themeSource” class=“org.springframework.ui.context.support.ResourceBundleThemeSource”>
        <property name=“basenamePrefix” value="com.test. "></property>
</bean>
        預設狀態下是在類路徑根據目錄下查詢相應的資原始檔,也可以通過basenamePrefix來制定,這樣,DispatcherServlet就會在com.test包下查詢相應的資源。

  • 主題解析器

        ThemeSource定義了一些主題資源,那麼不同的使用者使用什麼主題資源由誰定義呢?org.springframework.web.servlet.ThemeResolver是主題解析器介面,主題解析器的工作便由它的子類來完成。
        對於主題解析器的子類主要由3個比較常用的實現,以主題檔案summer.properties為例。

  1. FixedThemeResolver用於選擇一個固定的主題。
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.FixedThemeResolver”>
            <property name=“defaultThemeName” value=“summer”></property>
    </bean>
    以上的配置的作用是設定主題檔案為summper.properties,在整個專案內固定不變。

  2. CookieThemeResolver用於實現使用者所選的主題,以cookie的形式儲存在客戶端機器上,配置如下:
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.CookieThemeResolver”>
            <property name=“defaultThemeName” value=“summer”></property>
    </bean>

  3. SessionThemeResolver用於主題儲存在使用者的HTTP Session中。
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.SessionThemeResolver”>
            <property name=“defaultThemeName” value=“summer”></property>
    </bean>
    以上配置用於設定主題名稱,並且將該名稱儲存在使用者的HTTPSession中。

  4. AbstractThemeResolver是一個抽象類被SessionThemeResolver和FixedThemeResolver繼承,使用者也可以繼承它來自定義主題解析器。

  • 攔截器

        如果需要根據使用者請求來改變主題,那麼Spring提供了一個己經實現的攔截器,ThemeChangeInterceptor攔截器了,配置如下:
<bean id=“themeChangeInterceptor” class=“org.springframework.web.servlet.theme.ThemeChangeInterceptor”>
        <property name=“paramName” value=“themeName”/>
</bean>
        其中設定使用者的請求引數名為themeName,即URL為?themeName=具體的主題名稱,此外還需要在handlerMapping中配置攔截器,當然 需要在HandlerMapping中新增攔截。
<property name=“interceptors”>
        <list>
                <ref local=“themeChangeInterceptor”/>
        </list>
</property>

        瞭解了主題檔案的簡單使用方式後,再來檢視解析器的初始化工作,與其他變數的初始化工作相同,主題檔案解析器的初始化工作並沒有任何需要特別說明的地方。

private void initThemeResolver(ApplicationContext context) {
	try {
		this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" +
							this.themeResolver + "]");
		}
	}
}
  1. 初始化HandlerMapping。

        當客戶端發出Request時DispatcherServlet會將Request提交給HandlerMapping,然後HandlerMapping根據Web Application Context的配置來回傳給DispatcherServlet相應的Controller。
        在基於Spring MVC 的web應用程式中,我們可以為DispatcherServlet提供多個HandlerMapping供其使用,DispatcherServlet在先用HandlerMapping的過程中,將根據我們所指定的一系列的HandlerMapping的優先順序進行排序,然後優先使用優先順序在前的HandlerMapping,如果當前的HandlerMapping能夠返回可用的Handler,DispatcherServlet則使用當前返回的Handler進行Web請求的處理,而不再繼續詢問其他的HandlerMapping,否則,DispatcherServlet將繼續按照各個HandlerMapping的優先順序進行詢問,直到獲取一個可用的Handler為止,初始化配置如下:

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean("handlerMapping", HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
		}
	}
}

        預設情況下,Spring MVC將載入當前系統所有實現了HandlerMapping的Bean,如果只期望Spring MVC 載入指定的handlermapping時,可以修改web.xml中的DispatcherServlet的初始化引數,將detectAllHandlerMapping的設定為false:

<init-param>
        <param-name>detectAllHandlerMapping</param-name>
        <param-value>false</param-value>
</init-param>
        此時,SpringMVC 將查詢名為"handlerMapping"的bean,將作為當前系統中唯一的handlerMapping,如果沒有定義handlerMapping的話,則Spring MVC將按照org.springframework.web.servlet.DispatcherServlet所在目錄下的DispatcherServlet.properties中所定義的org.springframework.web.servlet.HandlerMapping的內容來載入預設的handlerMapping(使用者沒有自定義Strategies的情況下)。

  1. 初始化HandlerAdapters

        從名字上也能聯想到一個典型的介面卡模式的使用,在計算機程式設計中,介面卡模式將一個類的介面適配成使用者所期待的,使用介面卡,可能使用介面不相容而無法在一起工作的類協同工作,做法是將類自己的介面包裹在一個己經存在的類中,那麼在處理handler時為什麼會使用介面卡模式呢?回答這個問題我們首先來分析它的初始化邏輯。

private void initHandlerAdapters(ApplicationContext context) {
	this.handlerAdapters = null;

	if (this.detectAllHandlerAdapters) {
		// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerAdapter> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
			// We keep HandlerAdapters in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerAdapters);
		}
	}
	else {
		try {
			HandlerAdapter ha = context.getBean("handlerAdapter", HandlerAdapter.class);
			this.handlerAdapters = Collections.singletonList(ha);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerAdapter later.
		}
	}

	// Ensure we have at least some HandlerAdapters, by registering
	// default HandlerAdapters if no other adapters are found.
	if (this.handlerAdapters == null) {
		this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
		}
	}
}

        同樣在初始化的過程中涉及了一個變數detectAllHandlerAdapters,detectAllHandlerAdapters的作用和detectAllHandlerMappings類似,只不過作用的物件handlerAdaptor,亦可以通過如下配置來強制系統載入bean name 為"handlerAdapter" handlerAdapter。
<init-param>
        <param-name>detectAllHandlerAdapters</param-name>
        <param-value>false</param-value>
</init-param>

        如果無法找到相應的bean,那麼系統會嘗試載入預設介面卡。

DispatcherServlet.java
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
	String key = strategyInterface.getName();
	String value = defaultStrategies.getProperty(key);
	if (value != null) {
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		List<T> strategies = new ArrayList<T>(classNames.length);
		for (String className : classNames) {
			try {
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			catch (ClassNotFoundException ex) {
				throw new BeanInitializationException(
						"Could not find DispatcherServlet's default strategy class [" + className +
								"] for interface [" + key + "]", ex);
			}
			catch (LinkageError err) {
				throw new BeanInitializationException(
						"Error loading DispatcherServlet's default strategy class [" + className +
								"] for interface [" + key + "]: problem with class file or dependent class", err);
			}
		}
		return strategies;
	}
	else {
		return new LinkedList<T>();
	}
}

        在getDefaultStrategies函式中,Spring會嘗試從defaultStrategies中載入對應的HandlerAdapter的屬性,那麼defaultStrategies是如何被初始化的呢?
        當前類DispatcherServlet中存在這樣的一段程式碼塊。

static {
	try {
		ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
	}
}

        在系統載入的時候,defaultStrategies根據當前路徑DispatcherServlet.properties來初始化本身,檢視DispatcherServlet.propeties中對應的HandlerAdapter的屬性。

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

        由此得知,如果程式設計師開發人員並沒有在配置檔案中定義自己的介面卡,那麼Spring會預設的載入配置檔案中的3個介面卡。
        作為總控制器的派遣器servlet通過處理器對映得到處理器後,會輪詢處理介面卡模組,查詢能夠處理當前HTTP請求的處理介面卡的實現,處理器介面卡模組根據處理器對映返回的處理器型別,例如簡單的控制器型別,註解控制器型別或者遠端呼叫處理器型別,來選擇某一個適當的處理器介面卡的實現。從而適配當前的HTTP請求。

  • HTTP請求處理器介面卡(HttpRequestHandlerAdapter)。

        HTTP請求處理器介面卡僅僅支援對HTTP請求處理器的適配,它簡單的將HTTP請求物件和響應物件傳遞給HTTP請求處理器實現,它並不需要返回值,它主要應用在基於HTTP的遠端呼叫的實現上。

  • 簡單控制器處理器介面卡(SimpleControllerHandlerAdapter)

        這個實現類將HTTP請求適配到一個控制器的實現進行處理,這裡的控制器的實現是一個簡單的控制器介面的實現,簡單控制器處理器介面卡被設計成一個框架類的實現,不需要被改寫,客戶化的業務邏輯通常是在控制器介面的實現類中實現的。

  • 註解方法處理器介面卡(AnnotationMethodHandlerAdapter)。

        這個類的實現是基於註解的實現,它需要結合註解的方法對映和註解方法處理協同工作,它通過解析宣告在註解控制器的請求對映資訊來解析相應的處理器方法來處理當前的 HTTP請求,在處理的過程中,它通過反射來發現探測處理器方法的引數,呼叫處理器方法,並且對映返回值到模型和控制器物件,最後返回模型和控制器物件給作為主控制器派遣器 Servlet。

        所以我們現在基本上可以回答之前的問題了,Spring 中所使用 Handler 並沒有任何特殊的聯絡,但是為了統一處理,Spring 提供了不同的情況的介面卡。

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        request.setAttribute("exception", ex.toString());
        request.setAttribute("exceptionStack", ex);
        return new ModelAndView("error/exception");
    }
}

        初始化程式碼如下:

private void initHandlerExceptionResolvers(ApplicationContext context) {
	this.handlerExceptionResolvers = null;

	if (this.detectAllHandlerExceptionResolvers) {
		// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
				.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
			
			// We keep HandlerExceptionResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
		}
	}
	else {
		try {
			HandlerExceptionResolver her =
					context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
			this.handlerExceptionResolvers = Collections.singletonList(her);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, no HandlerExceptionResolver is fine too.
		}
	}

	// Ensure we have at least some HandlerExceptionResolvers, by registering
	// default HandlerExceptionResolvers if no other resolvers are found.
	if (this.handlerExceptionResolvers == null) {
		this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
		}
	}
}
  1. 初始化 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。
  • tripExcpetion:如果請求的包路徑包含副檔名是否要去除,預設為 true。
  • urlDecode:是否需要對 URL解碼,預設是 true,它會採用 request 指定的編碼或者 ISO-8859–1編碼對 URL進行解碼。

        當我們沒有在 Spring MVC 的配置檔案中拖動的定義一個名為 viewNameTranlator的 bean 的時候,Spring 就會為我們提供一個預設的 viewNameTranslator,即 DefaultRequestToViewNameTranslator。
        接下來看一下,當 Controller 處理器方法沒有返回邏輯檢視名稱時,DefaultRquestToViewNameTranslator 是如何約定檢視名稱的,DefaultRequstToViewNameTranslator 會獲取到請求的 URI,然後根據提供的屬性做一些改造。把改造後的結果作為檢視名稱返回,這裡以請求路徑 http://localhost/app/test/index.html為例,來說明一下 DefaultRequestToViewNameTranslator是如何工作的,該請求路徑對應的請求 URI 為/test/index.html,我們來看一下幾種情況,它分別是對應的邏輯檢視名稱是什麼?

  • prefix 和 suffix如果都存在,其他的預設值,那麼對應返回的邏輯檢視名應該是 prefixtest/indexsuffix。
  • stripLeadingSlash 和 stripExtension 都是 false,其他預設,這個時候的邏輯檢視名是/test/index.html。
  • 都採用預設的配置時,返回的邏輯檢視名稱應該是 test/index。

        如果邏輯檢視名稱跟請求路徑相同或者相關關係都是一樣的,那麼我們就可以採用 Spring 為我們事先約定好的邏輯檢視名稱返回,這可以大大的簡化我們的開發工作,而以上的功能實現關鍵屬性 viewNameTranslator,則是在 initRequestToViewNameTranslator 中完成。

private void initRequestToViewNameTranslator(ApplicationContext context) {
	try {
		this.viewNameTranslator =
				context.getBean("viewNameTranslator", RequestToViewNameTranslator.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
					"viewNameTranslator" + "': using default [" + this.viewNameTranslator +
					"]");
		}
	}
}
  1. 初始化 ViewResolvers。

        在 Spring MVC 中,當 Controller 將請求處理結果放到 ModelAndView中以後,DispacherServlet 會根據 ModelAndView 選擇合適的檢視進行渲染,那麼在 SpringMVC中是如何選擇合適的 View呢?View 物件是如何建立的呢?答案就是 ViewResolver 中,ViewResolver 介面定義了 resolverViewName 定義,根據 viewName 建立合適的型別的 View 實現。
        那麼如何配置 ViewResolver 呢?在 Spring 中,ViewResolver 作為 Spring Bean 存在,可以在 Spring 配置檔案中進行配置,例如下面的程式碼,配置 JSP相關的 viewResolver。
<bean id=“viewResolver” class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>
        <property name=“prefix” value="/WEB-INF/jsp/"></property>
        <property name=“suffix” value=".jsp"></property>
</bean>

        ViewResolvers 屬性的初始化工作在 initViewResolvers 中完成。

private void initViewResolvers(ApplicationContext context) {
	this.viewResolvers = null;
	if (this.detectAllViewResolvers) {
		// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
			// We keep ViewResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.viewResolvers);
		}
	}
	else {
		try {
			ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
			this.viewResolvers = Collections.singletonList(vr);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default ViewResolver later.
		}
	}

	// Ensure we have at least one ViewResolver, by registering
	// a default ViewResolver if no other resolvers are found.
	if (this.viewResolvers == null) {
		this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
		}
	}
}
  1. 初始化 FlashMapManager 。
            Spring MVC Flash Attrributes 提供了一個請求儲存屬性,可供其他的請求使用,在使用重定向時候非常必要,例如 Post/Redirect/Get模式,Flash attributes 在重定向之前暫存,(就像存在 session 中)以便重定向之後還能使用,並立即刪除。
            Spring MVC 有兩個主要的抽象來支援 flash attribute ,Flashmap
private void initFlashMapManager(ApplicationContext context) {
	try {
		this.flashMapManager =
				context.getBean("flashMapManager", FlashMapManager.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate FlashMapManager with name '" +
					"flashMapManager" + "': using default [" + this.flashMapManager + "]");
		}
	}
}
DispatcherServlet 的邏輯處理。

        根據之前的示例,我們知道在 HttpServlet 類中分別提供了相應的服務方法,它們是 doDelete(),doGet(),doOptions(),doPost(),doPut()和 doTrace(),它會根據請求的不同形式將程式引導至對應的函式進行處理,這幾個函式中最常用的函式無非是 doGet()和 doPost(),那麼我們就直接檢視 DispatcherServlet 中對於這兩個函式的邏輯實現。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}


@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}

        對於不同的方法,Spring 並沒有做特殊的處理,而是統一將程式再一次的引導至processRequest(request,response) 中。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	//記錄當前時間,用於計算 web請求的處理時間
	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	LocaleContext localeContext = buildLocaleContext(request);

	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

	initContextHolders(request, localeContext, requestAttributes);

	try {
		doService(request, response);
	}
	catch (ServletException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}

		if (logger.isDebugEnabled()) {
			if (failureCause != null) {
				this.logger.debug("Could not complete request", failureCause);
			}
			else {
				if (asyncManager.isConcurrentHandlingStarted()) {
					logger.debug("Leaving response open for concurrent processing");
				}
				else {
					this.logger.debug("Successfully completed request");
				}
			}
		}

		publishRequestHandledEvent(request, response, startTime, failureCause);
	}
}

        函式中己經開始對請求的處理,雖然把細節轉移到了 doService 函式中實現,但是我們還是不難看出處理請求後所做的準備與處理工作。

  1. 為了保證當前執行緒的 LocaleContext 以及 RequestAttributes 可以在當前請求後還能恢復,提取當前執行緒的兩個屬性。
  2. 根據當前 request 建立對應的 LocaleContext 和 RequestAttributes,並繫結到當前執行緒中。
  3. 委託給doService 方法進一步處理。
  4. 請求處理結束後恢復執行緒原始狀態。
  5. 請求處理結束後無論成功與否釋出事件通知。

        繼續檢視 doService 方法。

DispatcherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}

		//如果包含request,請保留請求屬性的快照,以便能夠在包含之後恢復原始屬性。
		Map<String, Object> attributesSnapshot = null;
		//如果 request 屬性中包含javax.servlet.include.request_uri
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		//request 請求中設定容器
		request.setAttribute(DispatcherServlet.class.getName() + ".CONTEXT", getWebApplicationContext());
		request.setAttribute(DispatcherServlet.class.getName() + ".LOCALE_RESOLVER", this.localeResolver);
		request.setAttribute(DispatcherServlet.class.getName() + ".THEME_RESOLVER", this.themeResolver);
		request.setAttribute(DispatcherServlet.class.getName() + ".THEME_SOURCE", getThemeSource());
		//從當前 session 中獲取 FlashMap,並作為 request 屬性儲存起來
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP", Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP", new FlashMap());
		request.setAttribute(DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER", this.flashMapManager);

		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}
	return wac;
}

        我們猜想對請求的處理至少應該包括一些諸如尋找 Handler 並頁面跳轉的邏輯處理,但是,在 doService 中我們並沒有看到想看到的邏輯,相反卻同樣是一些準備工作,但是這些準備工作是必不可少的,Spring 將己經初始化的功能輔助工具變數,比如 localeResolver,themeReesolver 等設定在 request 屬性中,而這些屬性會在接下來的處理中派上用場。
        經過層層的準備工作,終於在 doDispatch函式中看到了完整的請求處理過程。

DispatcherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			//檢測是不是檔案上傳,如果是 MultipartContent 型別的 request 
			//則轉換 request 為 MultipartHttpServletRequest 型別的 request
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			//根據 request 資訊尋找對應的 Handler
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				//如果沒有找到對應的 Handler,則通過 response反饋錯誤資訊
				noHandlerFound(processedRequest, response);
				return;
			}
			//根據當前的 handler 尋找對應的 HandlerAdapter
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			//如果當前的 handler 支援 last-modified 頭處理
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (logger.isDebugEnabled()) {
					logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			//攔截器的 preHandler 方法呼叫。
			//如果任意一個攔截器的preHandle方法返回 false,終止本次訪問
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			//真正的啟用handler 並返回檢視
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			//如果是非同步請求,也沒有沒有什麼檢視層的渲染了
			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}
			//檢視名稱轉換應用於需要新增字首字尾的情況
			applyDefaultViewName(processedRequest, mv);
			//應用所有攔截器的 postHandle 方法
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		//丟擲異常,呼叫攔截器鏈中的所有攔截器的afterCompletion方法
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Error err) {
		triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			if (mappedHandler != null) {
				//呼叫所有非同步攔截器的afterConcurrentHandlingStarted方法
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			if (multipartRequestParsed) {
				//如果存在檔案上傳,清除掉本地快取檔案
				cleanupMultipart(processedRequest);
			}
		}
	}
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//獲取攔截器鏈afterCompletion方法
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = 0; i < interceptors.length; i++) {
			HandlerInterceptor interceptor = interceptors[i];
			//如果任意一個攔截器的preHandle方法返回 false
			if (!interceptor.preHandle(request, response, this.handler)) {
				//呼叫攔截器鏈中所有攔截器的afterCompletion方法
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
	if (mv != null && !mv.hasView()) {
		mv.setViewName(getDefaultViewName(request));
	}
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = interceptors.length - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = interceptors[i];
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}
}


private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

	boolean errorView = false;

	if (exception != null) {
		if (exception instanceof ModelAndViewDefiningException) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	//如果 Handler 例項處理中返回了 view,那麼需要做頁面處理
	if (mv != null && !mv.wasCleared()) {
		//處理頁面跳轉
		render(mv, request, response);
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else {
		if (logger.isDebugEnabled()) {
			logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
					"': assuming HandlerAdapter completed request handling");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	if (mappedHandler != null) {
		//完成處理後啟用觸發器
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
	//Spring 考慮到還沒有獲得 Handler 時就丟擲異常,所以mappedHandler可能為空的情況
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }
    throw ex;
}

private void triggerAfterCompletionWithError(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, Error error) throws Exception {
	//封裝 Error
    ServletException ex = new NestedServletException("Handler processing failed", error);
    //Spring 考慮到還沒有獲得 Handler 時就丟擲Error,所以mappedHandler可能為空的情況
    if (mappedHandler != null) {
    	//呼叫攔截器鏈的所有的afterCompletion方法
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }
    throw ex;
}
觸發攔截器鏈的afterCompletion方法
HandlerExecutionChain.java
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
        throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

        上述程式碼其實也很簡單,只是獲取所有的攔截器,呼叫每個攔截器的afterCompletion方法。

void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            if (interceptors[i] instanceof AsyncHandlerInterceptor) {
                try {
                    AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
                    asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
                }
                catch (Throwable ex) {
                    logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
                }
            }
        }
    }
}

        doDispatch 函式中展示了 Spring請求處理所涉及的主要邏輯,而我們之前設定在 request 中的各種輔助屬性都被派上用場,下面回顧一下邏輯處理的全過程。

protected void cleanupMultipart(HttpServletRequest request) {
    MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
    if (multipartRequest != null) {
        this.multipartResolver.cleanupMultipart(multipartRequest);
    }
}

        因為multipartResolver的實現類有多個,那我們以 org.springframework.web.multipart.commons.CommonsMultipartResolver為例分析cleanupMultipart方法吧。

CommonsMultipartResolver.java
public void cleanupMultipart(MultipartHttpServletRequest request) {
    if (request != null) {
        try {
            cleanupFileItems(request.getMultiFileMap());
        }
        catch (Throwable ex) {
            logger.warn("Failed to perform multipart cleanup for servlet request", ex);
        }
    }
}
protected void cleanupFileItems(MultiValueMap<String, MultipartFile> multipartFiles) {
    for (List<MultipartFile> files : multipartFiles.values()) {
        for (MultipartFile file : files) {
            if (file instanceof CommonsMultipartFile) {
                CommonsMultipartFile cmf = (CommonsMultipartFile) file;
                //刪除臨時檔案
                cmf.getFileItem().delete();
                if (logger.isDebugEnabled()) {
                    logger.debug("Cleaning up multipart file [" + cmf.getName() + "] with original filename [" +
                            cmf.getOriginalFilename() + "], stored " + cmf.getStorageDescription());
                }
            }
        }
    }
}

        上述邏輯也很簡單,就是刪除檔案上傳過程中產生的臨時檔案。

MultipartContent 型別的 request 處理

        對於請求的處理,Spring 首先會考慮的是對於 Multipart 的處理,如果是 MultipartContent 型別的 request,則轉換 request 為 MultipartHttpServletRequest 型別的 request。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	//配置了multipartResolver,並且是 POST 請求,並且contextType是以 multipart/ 開頭
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
					"this typically results from an additional MultipartFilter in web.xml");
		}
		else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
			logger.debug("Multipart resolution failed for current request before - " +
					"skipping re-resolution for undisturbed error rendering");
		}
		else {
			return this.multipartResolver.resolveMultipart(request);
		}
	}
	return request;
}
根據 request 資訊尋找對應的 Handler

        在Spring 中最簡單的對映處理器配置如下:

<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/userlist.htm">userController</prop>
            <prop key="/helloWorldLastModified.htm">helloWorldLastModifiedCacheController</prop>
        </props>
    </property>
</bean>

        先看一下SimpleUrlHandlerMapping的類結構。
在這裡插入圖片描述
        我們看到SimpleUrlHandlerMapping實現了 ApplicationContextAware介面,因此,在 bean 的初始化時,會呼叫ApplicationContextAwareProcessor 的postProcessBeforeInitialization方法。在postProcessBeforeInitialization會呼叫invokeAwareInterfaces方法,接下來,我們繼續跟進程式碼

ApplicationObjectSupport.java
private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof EnvironmentAware) {
            ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
        }
        if (bean instanceof EmbeddedValueResolverAware) {
            ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
                    new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
        }
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
        }
        if (bean instanceof ApplicationEventPublisherAware) {
            ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
        }
        if (bean instanceof MessageSourceAware) {
            ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
        }
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}

WebApplicationObjectSupport.java
public final void setApplicationContext(ApplicationContext context) throws BeansException {
    if (context == null && !isContextRequired()) {
        // Reset internal context state.
        this.applicationContext = null;
        this.messageSourceAccessor = null;
    }
    else if (this.applicationContext == null) {
        // Initialize with passed-in context.
        if (!requiredContextClass().isInstance(context)) {
            throw new ApplicationContextException(
                    "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
        }
        this.applicationContext = context;
        this.messageSourceAccessor = new MessageSourceAccessor(context);
        initApplicationContext(context);
    }
    else {
        // Ignore reinitialization if same context passed in.
        if (this.applicationContext != context) {
            throw new ApplicationContextException(
                    "Cannot reinitialize with different application context: current one is [" +
                    this.applicationContext + "], passed-in one is [" + context + "]");
        }
    }
}
註冊 Handler
SimpleUrlHandlerMapping.java
public void initApplicationContext() throws BeansException {
    super.initApplicationContext();
    //註冊所有的 handler
    registerHandlers(this.urlMap);
}

        有點疑問,在SimpleUrlHandlerMapping我們明顯是用 url初始化了mappings屬性,但是這裡卻是用urlMap來註冊 Handler,下面來看看兩者之間的關係。

SimpleUrlHandlerMapping.java
public void setMappings(Properties mappings) {
	//將所有的mappings屬性複製到 urlMap
    CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
}
SimpleUrlHandlerMapping.java
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
    if (urlMap.isEmpty()) {
        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
    }
    else {
        for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
            String url = entry.getKey();
            Object handler = entry.getValue();
			//為 url 新增預設字首/
            if (!url.startsWith("/")) {
                url = "/" + url;
            }
           //去掉url前後空格
            if (handler instanceof String) {
                handler = ((String) handler).trim();
            }
            registerHandler(url, handler);
        }
    }
}

AbstractUrlHandlerMapping.java
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    //如果 handler 是字串,從容器中獲取 handler,並且handler 非延遲載入
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (getApplicationContext().isSingleton(handlerName)) {
            resolvedHandler = getApplicationContext().getBean(handlerName);
        }
    }

	//如果 url 設定多個處理器,丟擲異常
    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {
        if (mappedHandler != resolvedHandler) {
            throw new IllegalStateException(
                    "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                    "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
        }
    }
    else {
    	//設定根處理器
        if (urlPath.equals("/")) {
            if (logger.isInfoEnabled()) {
                logger.info("Root mapping to " + getHandlerDescription(handler));
            }
            setRootHandler(resolvedHandler);
        }
        //設定預設處理器
        else if (urlPath.equals("/*")) {
            if (logger.isInfoEnabled()) {
                logger.info("Default mapping to " + getHandlerDescription(handler));
            }
            setDefaultHandler(resolvedHandler);
        }
        else {
        	//設定普通處理器
            this.handlerMap.put(urlPath, resolvedHandler);
            if (logger.isInfoEnabled()) {
                logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
            }
        }
    }
}

        通過上面的分析,我們知道了 Handler 的由來,下面我們繼續跟進程式碼,看super.initApplicationContext();中又做了什麼事情呢?

AbstractHandlerMapping.java
protected void initApplicationContext() throws BeansException {
	//預留給子類實現
    extendInterceptors(this.interceptors);
    //從容器中載入所有的攔截器
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

        通過上述程式碼,我們知道了initApplicationContext方法主要用途是初始化攔截器相關的邏輯。我們來看看extendInterceptors方法,發現什麼也沒有做,也就是 Spring 預留給子類去實現的邏輯。

protected void extendInterceptors(List interceptors) {
}

        下面我們來續續跟進detectMappedInterceptors方法,看是如何從容器中獲取攔截器的。

註冊攔截器
AbstractHandlerMapping.java
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
            BeanFactoryUtils.beansOfTypeIncludingAncestors(
                    getApplicationContext(), MappedInterceptor.class, true, false).values());
}

        程式程式碼執行到這裡,
通過BeanFactoryUtils.beansOfTypeIncludingAncestors()工具類方法獲取所有MappedInterceptor型別的 Bean。
在這裡插入圖片描述
在這裡插入圖片描述

        但是讓我感覺奇怪的是MyTestInterceptor並沒有和MappedInterceptor扯上關係了啊。沒有辦法,只能來看MyTestInterceptor的例項化邏輯了。

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/list"/>
        <bean class="com.spring_1_100.test_71_80.test75_springmvc.MyTestInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

        首先,我們來看看<mvc:interceptors … />標籤的解析。

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
        registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
        registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
        registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
        registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
    }
}

        從上述程式碼中,我們看到了<interceptors />標籤的解析器為InterceptorsBeanDefinitionParser,接下來進入InterceptorsBeanDefinitionParser類,看看是如何解析的。

InterceptorsBeanDefinitionParser.java
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference pathMatcherRef = null;
        if (element.hasAttribute("path-matcher")) {
            pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
        }

        List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
        for (Element interceptor : interceptors) {
            RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
            mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
            mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

            ManagedList<String> includePatterns = null;
            ManagedList<String> excludePatterns = null;
            Object interceptorBean;
            if ("interceptor".equals(interceptor.getLocalName())) {
                includePatterns = getIncludePatterns(interceptor, "mapping");
                excludePatterns = getIncludePatterns(interceptor, "exclude-mapping");
                Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0);
                interceptorBean = parserContext.getDelegate().parsePropertySubElement(beanElem, null);
            }
            else {
                interceptorBean = parserContext.getDelegate().parsePropertySubElement(interceptor, null);
            }
            mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, includePatterns);
            mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
            mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);

            if (pathMatcherRef != null) {
                mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
            }

            String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
            parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
        }

        parserContext.popAndRegisterContainingComponent();
        return null;
    }

    private ManagedList<String> getIncludePatterns(Element interceptor, String elementName) {
        List<Element> paths = DomUtils.getChildElementsByTagName(interceptor, elementName);
        ManagedList<String> patterns = new ManagedList<String>(paths.size());
        for (Element path : paths) {
            patterns.add(path.getAttribute("path"));
        }
        return patterns;
    }

}

        上述程式碼看上去一大堆,其實原理很簡單,就是遍歷interceptors下的所有interceptor標籤,將所有的mapping中的 path解析成ManagedList<String> includePatterns,exclude-mapping中的 path解析成ManagedList excludePatterns,bean解析成interceptorBean作為MappedInterceptor建構函式的三個引數注入。下面我們來看看MappedInterceptor的建構函式 。

MappedInterceptor.java
public MappedInterceptor(String[] includePatterns, String[] excludePatterns, HandlerInterceptor interceptor) {
    this.includePatterns = includePatterns;
    this.excludePatterns = excludePatterns;
    this.interceptor = interceptor;
}

        因此在detectMappedInterceptors方法中獲取到所有的MappedInterceptor,每個MappedInterceptor的interceptor屬性就是我們自定義的攔截器com.spring_1_100.test_71_80.test75_springmvc.MyTestInterceptor,下面繼續接著看看initInterceptors的內部實現。

AbstractHandlerMapping.java
protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            if (interceptor == null) {
                throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
            }
            this.adaptedInterceptors.add(adaptInterceptor(interceptor));
        }
    }
}

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    if (interceptor instanceof HandlerInterceptor) {
        return (HandlerInterceptor) interceptor;
    }
    else if (interceptor instanceof WebRequestInterceptor) {
        return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    }
    else {
        throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    }
}

        從上述程式碼來看,在initInterceptors方法中,也沒有做過多的事情,只是將所有的interceptor加入到了adaptedInterceptors中,只是對於特殊實現了WebRequestInterceptor的攔截器做了特殊的適配轉換。接下來,我們來看 Handler 的獲取。
        在 Spring 載入的過程中,Spring 會將型別為SimpleUrlHandlerMapping 的例項載入到 this.handlerMappings 中,按照常理推斷,根據 request 提取對應的 Handler,無非就是提取當前例項中的 userController,但是 userController 為繼承自 AbstractController 型別的例項,與 HandlerExceptionChain 並無任何關聯,那麼這一步如何封裝的呢?

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//遍歷所有的 HandlerMappings,本次測試,只有SimpleUrlHandlerMapping 
	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;
}

        在之前的內容我們提到過,在系統啟動時 Spring 會將所有的對映型別的 bean 註冊到 this.handlerMappings 變數中,所以此函式的目的就是遍歷所有的 HandlerMapping,並呼叫其 getHandler 方法進行封裝處理,所以 SimpleUrlHandlerMapping 為例檢視其 getHandler 方法如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//根據 request 獲取對應的 handler
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		//獲取預設的處理器,如配置了 / 或 /* ,對應的處理器
		handler = getDefaultHandler();
	}
	//如果也沒有提供預設的 handler,則無法繼承處理返回 null
	if (handler == null) {
		return null;
	}
	//如果 handler是字串,則從容器中獲取 bean
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = getApplicationContext().getBean(handlerName);
	}

	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

        函式中首先會使用 getHandlerInternal 方法根據 request 資訊獲取對應的 Handler,如果以 SimpleUrlHandlerMapping 為例分析,那麼我們推斷此步驟提供的功能很可能是根據 URL 匹配到 Controller 並返回,當然,如果沒有找到對應的 Controller 處理器那麼程式會嘗試去查詢配置中預設的處理器,當然,當查詢的 controller 為 String型別時,那就意味著返回的配置的 bean 名稱,需要根據 bean 名稱查詢對應的 bean,最後,還通過 getHandlerExecutionChain 方法返回對應的 Handler 進行封裝,以保證滿足返回型別的匹配,下面詳細分析這個過程。

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
	//擷取用於匹配的 url 有效路徑
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	//根據路徑尋找 Handler
	Object handler = lookupHandler(lookupPath, request);
	if (handler == null) {
		Object rawHandler = null;
		if ("/".equals(lookupPath)) {
			//如果請求路徑是/,那麼使用 RootHandler 進行處理
			rawHandler = getRootHandler();
		}
		if (rawHandler == null) {
			//無法找到 handler,則使用預設的 handler
			rawHandler = getDefaultHandler();
		}
		if (rawHandler != null) {
			//根據 bean 名稱獲取對應的 bean
			if (rawHandler instanceof String) {
				String handlerName = (String) rawHandler;
				rawHandler = getApplicationContext().getBean(handlerName);
			}
			//模版方法
			validateHandler(rawHandler, request);
			handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
		}
	}
	if (handler != null && logger.isDebugEnabled()) {
		logger.debug("Mapping [" + lookupPath + "] to " + handler);
	}
	else if (handler == null && logger.isTraceEnabled()) {
		logger.trace("No handler mapping found for [" + lookupPath + "]");
	}
	return handler;
}

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
	//直接匹配的情況
	Object handler = this.handlerMap.get(urlPath);
	if (handler != null) {
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		return buildPathExposingHandler(handler, urlPath, urlPath, null);
	}
	//萬用字元匹配的處理
	List<String> matchingPatterns = new ArrayList<String>();
	for (String registeredPattern : this.handlerMap.keySet()) {
		if (getPathMatcher().match(registeredPattern, urlPath)) {
			matchingPatterns.add(registeredPattern);
		}
		else if (useTrailingSlashMatch()) {
			if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
				matchingPatterns.add(registeredPattern +"/");
			}
		}
	}
	String bestPatternMatch = null;
	Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
	if (!matchingPatterns.isEmpty()) {
		Collections.sort(matchingPatterns, patternComparator);
		if (logger.isDebugEnabled()) {
			logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
		}
		bestPatternMatch = matchingPatterns.get(0);
	}
	if (bestPatternMatch != null) {
		handler = this.handlerMap.get(bestPatternMatch);
		if (handler == null) {
			Assert.isTrue(bestPatternMatch.endsWith("/"));
			handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
		}
		
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);

		// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
		// for all of them
		Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
		for (String matchingPattern : matchingPatterns) {
			if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
				Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
				Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
				uriTemplateVariables.putAll(decodedVars);
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
		}
		return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
	}
	// No handler found...
	return null;
}

        根據 URL 獲取對應的Handler 的匹配規則程式碼實現起來雖然很長,但是並不難還是外面,考慮了直接匹配與萬用字元兩種情況,其中要提及的是 buildPathExposingHandler函式,它將 Handler 封裝成HandlerExecutionChain型別。

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
		String pathWithinMapping, Map<String, String> uriTemplateVariables) {

	HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
	chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
	if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
		chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
	}
	return chain;
}

        在函式中我們看到了通過將 Handler 以引數的形式傳入,並構建 HandlerExcutionChain 型別例項,加入了兩個攔截器,此時我們似乎己經瞭解了 Spring 這樣在番周折的目的,鏈處理機制,是 Sring 非常常用的處理方式,是 AOP中重要的組成部分,可以方便的對目標物件進行擴充套件及攔截,這是非常優秀的設計。

加入攔截器到執行鏈

        getHandlerExecutionChain函式最主要的上的就是將配置檔案中的對應的攔截器加入到執行鏈中,以保證這些攔截器可以有效的作用於目標物件。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			//匹配成功,將攔截器加入到攔截器鏈中
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

        經過之前註冊攔截器分析得知自定義攔截器都被封裝到MappedInterceptor的interceptor的屬性中,而mapping中的path屬性和exclude-mapping中的path屬性分別被封裝到MappedInterceptor的includePatterns和excludePatterns屬性中,而includePatterns和excludePatterns屬性的作用,猜猜都知道是用於匹配。下面我們來看看matches方法。

MappedInterceptor.java
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
    PathMatcher pathMatcherToUse = (this.pathMatcher != null) ? this.pathMatcher : pathMatcher;
    if (this.excludePatterns != null) {
    	//如果uri被excludePatterns匹配到的不符合攔截器條件
        for (String pattern : this.excludePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return false;
            }
        }
    }
    //如果沒有被excludePatterns匹配到,同時也沒有配置mapping,則符合攔截器條件
    if (this.includePatterns == null) {
        return true;
    }
    else {
    	//被includePatterns匹配到的,符合攔截器條件
        for (String pattern : this.includePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return true;
            }
        }
        return false;
    }
}

        上述查詢符合條件的攔截器分為四種情況。

  • uri被excludePatterns匹配上,不被攔截器攔截。
  • uri沒有被excludePatterns匹配上,並且includePatterns為空,被攔截器攔截
  • uri沒有被excludePatterns匹配,但是被includePatterns匹配,被攔截器攔截
  • uri沒有被excludePatterns匹配,也沒有被includePatterns匹配,不被攔截器攔截。

        關於匹配演算法,有興趣的小夥伴可以自行去研究一下AntPathMatcher的match方法。

如果沒有找到對應的 Handler 的錯誤處理

        每個請求都會對應一個 Handler,因為每個請求都會在後臺有相應的邏輯對應,而邏輯的實現就是在 Handler 中,所以一旦遇到沒有找到的 Handler 的情況(正常情況下如果沒有 URL 匹配的 Handler),開發人員可以設定預設的 Handler 來處理相關請求,但是如果預設的請求也未設定就會出現 Handler為空的情況,就只能通過 response 向使用者返回錯誤資訊了。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (pageNotFoundLogger.isWarnEnabled()) {
		pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
				"] in DispatcherServlet with name '" + getServletName() + "'");
	}
	if (this.throwExceptionIfNoHandlerFound) {
		ServletServerHttpRequest sshr = new ServletServerHttpRequest(request);
		throw new NoHandlerFoundException(
				sshr.getMethod().name(), sshr.getServletRequest().getRequestURI(), sshr.getHeaders());
	}
	else {
		response.sendError(HttpServletResponse.SC_NOT_FOUND);
	}
}

根據當前 Handler 尋找對應的 HandlerAdapter

        在 WebApplicationContext 的初始化過程中我們討論了 HandlerAdapters 的初始化,瞭解了預設情況下普通web 請求會交給 SimpleControllerHandlerAdapter 去處理,下面我們以 SimpleControllerHandlerAdapter 為例來分析獲取介面卡的邏輯。

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 邏輯被封裝在具體的介面卡中,進一步檢視 SimpleControllerHandlerAdapter 的 supports 方法。

public boolean supports(Object handler) {
        return (handler instanceof Controller);
}

        分析到這裡,一切都明瞭了,SimpleControllerHandlerAdapter 就是用於處理普通的 Web請求的,而且對於 Spring MVC 來說,我們會把邏輯封裝至 Controller 的子類中,例如我們之前的引導示例 UserController 就是繼承自 AbstractController,而 AbstractController 實現了 Controller 介面。

快取處理

        在研究 Spring 對快取處理的功能支援前,我們先了解一個概念,Last-Modified 快取機制。

  1. 客戶端在第一次輸入 URL 時,伺服器端會返回內容和狀態碼200,表示請求成功,同時會新增一個"Last-Modified" 的響應頭,表示此檔案在伺服器上最後更新時間,例如"Last-Modified:Web,14 Mar 2020 10:22:42 GMT" 表示最後更新時間為(2020-05-14 10:22:42)
  2. 客戶端第二次請求此 URL 時,客戶端會向伺服器傳送請求頭"If-Modified-Since",詢問伺服器該時間之後當前請求內容是否有被修改過,如果"if-Modified-Since:Web,14 Mar 2020 10:22:43 GMT" ,如果伺服器內容沒有變化,則自動返回 HTTP 304 狀態碼(只要響應頭內容為空,這樣就節省了網路的頻寬)

        Spring 提供了對 Last-Modified 機制的支援,只需要實現 LastModified介面,如下圖所示:

public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified {
    private long lastModified;

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println(" cache modified 請求");
        response.getWriter().write("<a href=''>this</a>");
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request) {
        if(lastModified == 0l){
            // 第一次或者邏輯變化的時候,應該重新的返回內容最新的修改時間戳
            lastModified = System.currentTimeMillis();
        }
        return lastModified;
    }
}

        HelloWorldLastModifiedCacheController只需要實現 LastModified 介面的 getLastModified 方法,保證當內容發生改變時返回最新修改的時間即可。
        Spring 判斷是否過期是通過判斷請求"If-Modified-Since"是否大於等於當前的 GetLastModified 方法返回的時間戳,如果是,則認為沒有被修改,上面的 Controller 與普通的 controller 並沒有什麼太大的差別。宣告如下:

        <bean name=“helloWorldLastModifiedCacheController” class=“com.spring_1_100.test_71_80.test75_springmvc.HelloWorldLastModifiedCacheController”></bean>

HandlerInterceptor 的處理

        Servlet API 定義的 servlet 過濾器可以在 servlet 處理的每個 web 請求後分別對它進行前置處理和後置處理,此外,有些時候,你可能只想處理由某些 Spring MVC 處理程式處理的 Web 請求,並在這些處理程式返回的模型屬性被傳遞到檢視之前,對它們進行一些操作。
        Spring MVC 允許你通過處理攔截器 Web 請求,進行前置處理和後置處理,處理攔截器是在 Spring的 Web 應用程式上下文配置的,因此它們可以利用各種容器特性,並引用容器中宣告的 bean,處理攔截器針對特殊處理程式對映進行註冊的。因此它只攔截通過這些處理程式對映的請求,每個處理攔截都必需實現 HandlerInterceptor介面,它包含三個需要你實現的回撥方法,preHandle(),postHandle()和 afterCompletion(),第一個和第二個方法分別是處理請求之前和之後被呼叫,第二個方法還允許訪問返回的 ModelAndView 物件,因此,可以在它裡面操作模型屬性,最後一個方法是在所有的請求處理完成後被呼叫的,如檢視呈現之後,以下是 HandlerInterceptor 的簡單實現。

MyTestInterceptor.java
public class MyTestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime",startTime);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long)request.getAttribute("startTime");
        request.removeAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("request time :" + (endTime -startTime));
        modelAndView.addObject("handlingTime",endTime-startTime);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

        在這個攔截器的 preHandler()方法中,你記錄了起始時間,並將它儲存到請求屬性中,這個方法應該返回了 true,允許 DispatcherServlet 繼續處理請求,否則,DispatcherServlet 會認為這個方法己經處理了請求了,直接將響應返回給使用者,然後在postHandler()方法中,從請求屬性中載入起始時間,並將它與當前的時間進行比較,你可以 計算出總的持續時間,然後把這個時間新增到模型中,傳遞給檢視,最後,afterCompletion()方法無事可做,空著就可以了。

邏輯處理

        對於邏輯處理其實是通過介面卡中轉呼叫 Handler 並返回檢視的,對應的程式碼如下:
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        同樣,還是以引導示例為基礎進行處理邏輯分析,之前分析過,對於普通的 Web 請求,Spring 預設是使用 SimpleControllerHandleAdapter 類進行處理的,我們進入 SimpleControllerHandlerAdapter類的 Handler 方法如下:

SimpleControllerHandlerAdapter.java
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    return ((Controller) handler).handleRequest(request, response);
}

        但是回顧示例中的 UserController,我們的邏輯是寫在 handleRequestInternal 函式中而不是 handleRequest 函式中,所以我們還需要進一步分析期間所包含的處理流程。

AbstractController.java
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    // Delegate to WebContentGenerator for checking and preparing.
    checkRequest(request);
    prepareResponse(response);

    //如果需要 session 同步
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
            	//呼叫使用者的邏輯
                return handleRequestInternal(request, response);
            }
        }
    }
	//呼叫使用者的邏輯
    return handleRequestInternal(request, response);
}

異常檢視的處理

        有時候系統執行過程中出現異常,而我們並不希望就此中斷對使用者的服務,而是至少告知客戶當前系統在邏輯的過程中出現了異常,甚至告知他們因為什麼原因導致的,Spring中的異常處理機制會幫我們完成這個事情,其實,在 Spring 主要的工作就是將邏輯引導至 HandlerExceptionResolver 類的 resolveException 方法,而 HandlerExceptionResolver 的使用,我們在講解 WebApplicationContext 的初始化的時候己經介紹過了。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		Object handler, Exception ex) throws Exception {

	// Check registered HandlerExceptionResolvers...
	ModelAndView exMv = null;
	for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
		exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
		if (exMv != null) {
			break;
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// We might still need view name translation for a plain error model...
		if (!exMv.hasView()) {
			exMv.setViewName(getDefaultViewName(request));
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
		}
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}
	throw ex;
}
根據檢視跳轉頁面

        無論是一個系統還是一個站點,最重要的工作還是與使用者進行互動,使用者作業系統後無論是下發命令成功是否都需要給使用者一個反饋,以便於使用者進行下一步的判斷,所以邏輯處理最後一定會涉及一個頁面跳轉問題。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Determine locale for request and apply it to the response.
	Locale locale = this.localeResolver.resolveLocale(request);
	response.setLocale(locale);

	View view;
	if (mv.isReference()) {
		//We need to resolve the view name.
		view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
		if (view == null) {
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
					"' in servlet with name '" + getServletName() + "'");
		}
	}
	else {
		// No need to lookup: the ModelAndView object contains the actual View object.
		view = mv.getView();
		if (view == null) {
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}

	// Delegate to the View object for rendering.
	if (logger.isDebugEnabled()) {
		logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
	}
	try {
		view.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
					getServletName() + "'", ex);
		}
		throw ex;
	}
}
解析檢視名稱

        在上文中我們提到了 DispatcherServlet 會根據 ModelAndView 選擇合適的檢視來進行渲染,而這一功能就是在 resolveViewName 函式中完成。

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;
}

        我們以org.springframework.web.servlet.view.InternalResourceViewResolver為例來分析 ViewResolver 邏輯的解析過程,其中resolveViewName 函式的實現是其父類 AbstractCachingViewResolver 中完成的。

InternalResourceViewResolver.java
public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
    	//不存在快取的情況下直接建立檢視
        return createView(viewName, locale);
    }
    else {
	    //直接從快取中提取
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Cached view [" + cacheKey + "]");
                        }
                    }
                }
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

        在父類 UrlBasedViewResolver 中重寫了 createView 函式。

protected View createView(String viewName, Locale locale) throws Exception {
    //如果當前解析器不支援當前解析器如 viewName 為空等情況
    if (!canHandle(viewName, locale)) {
        return null;
    }
    //處理字首為 redirect:xxx 的情況
    if (viewName.startsWith("redirect:")) {
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
        RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
        return applyLifecycleMethods(viewName, view);
    }
    //處理字首為forward:xxx 的情況
    if (viewName.startsWith("forward:")) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        return new InternalResourceView(forwardUrl);
    }
    // Else fall back to superclass implementation: calling loadView.
    return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
    return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
}
UrlBasedViewResolver.java
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
    //新增字首及字尾
    view.setUrl(getPrefix() + viewName + getSuffix());

    String contentType = getContentType();
    if (contentType != null) {
    	//設定ContextType
        view.setContentType(contentType);
    }

    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());

    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
        view.setExposePathVariables(exposePathVariables);
    }
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
        view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return view;
}

        通讀以上的程式碼,我們發現對於 InternalResourceViewResolver 所提供的功能主要考慮到以下的幾個方面處理。

  • 基於效率的考慮,提供了快取的支援。
  • 提供了對 redirect:xxx 和 forward:xxx 字首的支援。
  • 新增了字首及字尾,並向 View 中加入了必需的屬性設定。
頁面跳轉

        當通過 viewName 解析對對應的 View 後,就可以進一步的處理跳轉邏輯了。

AbstractView.java
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
            " and static attributes " + this.staticAttributes);
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

        在引導示例中,我們瞭解對於 ModelView的使用,可以將一些屬性直接放入其中,然後在頁面直接通過 JSTL語法或者原始的 request 獲取,這是一個很方便的也是很神奇的功能,但是實現並不是很複雜,無非是把我們要用到的屬性放入到 request 中,以便在其他的地方可以直接呼叫,而解析的這些屬性的工作就是在 createMergedOutModel 函式中完成。

InternalResourceView.java
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) {

    @SuppressWarnings("unchecked")
    Map<String, Object> pathVars = (this.exposePathVariables ?
            (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

    // Consolidate static and dynamic model attributes.
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);

    Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
        mergedModel.putAll(pathVars);
    }
    if (model != null) {
        mergedModel.putAll(model);
    }
    
    // Expose RequestContext?
    if (this.requestContextAttribute != null) {
        mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    }

    return mergedModel;
}
InternalResourceView.java
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    //將 model 中的資料以屬性的方式設定到 request中
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.include(request, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.forward(request, response);
    }
}
InternalResourceView.java
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
    for (Map.Entry<String, Object> entry : model.entrySet()) {
        String modelName = entry.getKey();
        Object modelValue = entry.getValue();
        if (modelValue != null) {
            request.setAttribute(modelName, modelValue);
            if (logger.isDebugEnabled()) {
                logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                        "] to request in view with name '" + getBeanName() + "'");
            }
        }
        else {
            request.removeAttribute(modelName);
            if (logger.isDebugEnabled()) {
                logger.debug("Removed model object '" + modelName +
                        "' from request in view with name '" + getBeanName() + "'");
            }
        }
    }
}

        原始碼分析到這裡,我相信大家對 Spring MVC 原理及原始碼有了一定的理解了,下面我們來總結關於 Spring MVC 程式碼的整個流程。

流程
1) contextInitialized:該方法在 ServletContext 啟動後被呼叫,並準備好處理客戶端請求
	1)initWebApplicationContext:初始化WebApplicationContext
		1)createWebApplicationContext:建立 WebApplicationContext
		2)configureAndRefreshWebApplicationContext:設定並重新整理 ApplicationContext 容器
			1)setId():設定 id
			2)setServletContext():設定 servletContext
			3)setConfigLocation():設定configLocation
			4)initPropertySources():初始化屬性資源
				1)replace(servletContextInitParams):替換掉環境中的servletContextInitParams
				2)replace(servletConfigInitParams):替換掉環境變數中的servletConfigInitParams
			5)customizeContext():載入servlet 中的所有contextInitializerClasses和globalInitializerClasses並呼叫他的initialize方法。
			6)refresh:呼叫容器的重新整理方法,初始化容器
2)init(): ServletContext 啟動後呼叫DispatcherServlet初始化方法
	1)ServletConfigPropertyValues():解析init-parm並封裝至pvs中
		1)registerCustomEditor():註冊資源編輯器
		2)initBeanWrapper():預留給子類 
		3)initServletBean():初始化 serviceBean
			1)initWebApplicationContext():初始化WebApplicationContext
				1)setParent():設定父容器
				2)configureAndRefreshWebApplicationContext():配置並重新整理ApplicationContext 容器
					1)setId():設定 id
					2)setServletContext():設定 Servlet容器
					3)setServletConfig():設定 setvlet 配置
					4)setNamespace():設定名稱空間
					5)addApplicationListener():新增 Application 監聽器ContextRefreshListener
					6)initPropertySources():初始化屬性資源
					7)postProcessWebApplicationContext():預留給子類 
					8)applyInitializers():呼叫配置的所有的contextInitializerClasses的initialize方法。
					9)refresh():重新整理容器
						1)finishRefresh():重新整理完成的相關處理
							1)initStrategies():初始化Servlet 容器相關策略
								1)initMultipartResolver(context):初始化MultipartResolver
								2)initLocaleResolver(context):初始化LocaleResolver
								3)initThemeResolver(context):初始化ThemeResolver
								4)initHandlerMappings(context):初始化HandlerMappings
								5)initHandlerAdapters(context):初始化HandlerAdapters
								6)initHandlerExceptionResolvers(context):初始化HandlerExceptionResolvers
								7)initRequestToViewNameTranslator(context):初始化RequestToViewNameTranslator
								8)initViewResolvers(context):初始化ViewResolvers
								9)initFlashMapManager(context):初始化FlashMapManager

3)doGet/doPost:Servlet 的GET 或 POST方法
	1)processRequest(request, response):處理 request 請求。
		1)doService():開始處理
			1)doDispatch():do 處理
				1)checkMultipart():如果是檔案上傳,將request 轉化為MultipartParsingResult
				2)getHandler():獲取 uri 處理器
				3)noHandlerFound():無處理器,直接終止
				5)getHandlerAdapter():將 handler 轉化為HandlerAdapter
				6)applyPreHandle():呼叫攔截器鏈的所有preHandle 方法。
				7)handle():呼叫直在的業務邏輯
					1)handleRequest():處理 request 請求。
						1)handleRequestInternal():呼叫實際業務邏輯
				8)applyDefaultViewName():如果當前請求沒有對應的檢視,設定預設檢視
				9)applyPostHandle():呼叫攔截器鏈的postHandle方法
				10)processDispatchResult:結果排程
					1)processHandlerException:異常結果排程
					2)render():render 檢視
					3)triggerAfterCompletion():呼叫攔截器鏈的afterCompletion方法。
				11)triggerAfterCompletion():如果丟擲異常,呼叫攔截器鏈的afterCompletion方法。
				12)triggerAfterCompletionWithError():如果丟擲 Error,呼叫攔截器鏈的afterCompletion方法。
				13)applyAfterConcurrentHandlingStarted():非同步請求處理
				14)cleanupMultipart:如果是檔案上傳,刪除臨時檔案
思考?

        相信大家對 SpringMVC 的原始碼有了一定的理解了,我們來看一個例子。

1.建立 Controller
//處理登入請求的後端控制器

//注意:@RequestParam註解中的required註解對錶單提交中的屬性是沒有用的,就算不填它也會預設為空字串,它只對GET請求中
//在url後加的key-value的屬性有限制作用

@Controller
@RequestMapping(value = {"/test"})
public class UserController {

    //如果是GET方法請求的話,就直接給使用者返回登入的頁面,此頁面表單請求的方法為POST
    @RequestMapping(value = {"/login"},method = {RequestMethod.GET})
    public ModelAndView LoginGet(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("index");
        return modelAndView;
    }

    private static final String CURRENT_USER = "Now_user";
    // http://localhost:8080/test/login.htm?userName=zhangsan&password=lizi
    @RequestMapping(value = {"login"},method = {RequestMethod.POST})
    //讓請求的url後面必須跟上一個叫做userName的屬性,是使用者的使用者名稱
    public ModelAndView LoginPost(@RequestParam(value = "userName") String userName,
                                  //請求的url後必須跟上password屬性,為使用者當前的密碼
                                  @RequestParam(value = "password") String password,
                                  //Spring MVC框架整合了Servlet請求響應等一系列引數,可以在有需要的時候使用
                                  HttpServletRequest request, HttpServletResponse response,
                                  HttpSession session, RedirectAttributes redirectAttributes) {

        //這裡是和後端互動的程式碼,如果是使用者登入的話就在資料庫中查詢對應的使用者資訊
        if (userName.isEmpty() || password.isEmpty()) {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("error", "使用者名稱或密碼為空");
            modelAndView.setViewName("index");
            return modelAndView;
        }


        //到了這裡就說明使用者登入成功了
        System.out.println("===========================:" + userName + "密碼是:" + password);
        //使用session進行會話跟蹤
        session.setAttribute(CURRENT_USER, userName);

        //建立模型與檢視類,返回給前端控制器
        ModelAndView modelAndView = new ModelAndView();

        //重定向的時候,因為是客戶端重新的請求,所以引數是不會被傳到重定向頁面的
        //所以使用此方法,可以把屬性放到一個叫做FlashMap的Map中
        redirectAttributes.addFlashAttribute("userName", userName);
        redirectAttributes.addFlashAttribute("password", password);


        redirectAttributes.addAttribute("uname", userName);
        redirectAttributes.addAttribute("pwd", password);

        //使用重定向的時候不能寫jsp的名字,要寫url對映的路徑
        modelAndView.setViewName("redirect:/test/main");
        return modelAndView;
    }

}
@Controller
@RequestMapping(value = {"/test"})
public class MainController {

    @RequestMapping(value = {"/main"}, method = {RequestMethod.GET})
    public ModelAndView GetMain(
            HttpServletRequest request, HttpSession session) {

        ModelAndView modelAndView = new ModelAndView();

        //對FlashMap中的引數進行提取,有兩種方法
        //第一種,使用RequestContextUtils(請求工具包),因為在重定向後FlashMap會把表單中的屬性
        //放在重定向新的請求中,所以可以獲得請求中的FlashMap
        Map<String, ?> map = RequestContextUtils.getInputFlashMap(request);
        //把FlashMap直接放入模型,傳給前端控制器
        modelAndView.addAllObjects(map);
        //檢視名傳入
        modelAndView.setViewName("test/main");
        System.out.println("==========================get ");

        return modelAndView;

    }


    //第二種:使用@ModelAttribute註解
    //因為FlashMap是處理這個url的初始化資料模型,所以可以通過這個註解拿到FlashMap的屬性
    @RequestMapping(value = {"/main"}, method = {RequestMethod.POST})
    public String PostMain(@ModelAttribute(value = "userName") String userName,
                           @ModelAttribute(value = "password") String password) {
        return "test/main";
    }
}
2.建立web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
    <display-name>spring_tiny</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring_101_200/config_181_190/spring185.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>spring_tiny</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring_tiny</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 自己配置描述檔案,需要多少個描述檔案就配置多少 -->
    <jsp-config>
        <!-- 配置c描述檔案-對應c標籤,這裡的taglib-uri對應jsp中引入的uri -->
        <taglib>
            <taglib-uri>http://www.codecoord.com</taglib-uri>
            <taglib-location>/WEB-INF/c.tld</taglib-location>
        </taglib>
    </jsp-config>
</web-app>
3.建立 Spring配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <mvc:annotation-driven />
    <context:component-scan base-package="com.spring_101_200.test_181_190.test_185_spring_mvc"></context:component-scan>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>
4.建立 index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>主介面</title>
</head>
<body>

<script>
    function Logout() {
        <%
        session.invalidate();
        %>
    }
</script>
<form id="login" name="login" method ="post"  action="/test/login.htm">
    <p>使用者名稱:<input id="userName" name="userName" type="text" /></p>  <!--使用者名稱文字框-->
    <p>密 碼:<input id="password" name="password" type="text" /></p>                     <!--密碼文字框-->
    <p><button id="subLogin"  name ="subLogin" type="submit" value="提交" >提交</button></p><!--提交按鈕-->
</form>

</body>
</html>
5.建立 main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>主介面</title>
</head>
<body>

<script>
    function Logout() {
        <%
        session.invalidate();
        %>
    }
</script>

<p>登入成功</p>
你的使用者名稱為:<p>${userName}</p>
你的密碼為:<p>${password}</p>
<a href="login" οnclick="Logout">退出</a>
</body>
</html>
登陸介面

在這裡插入圖片描述

重定向介面

在這裡插入圖片描述
        聰明的讀者,你覺得 Spring原始碼對於上例是如何實現的呢?

本文的github地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_71_80/test75_springmvc

相關文章