Spring MVC 啓動過程源碼分析

今天小編嘗試從源碼層面上對Spring mvc的初始化過程進行分析,一塊兒揭開Spring mvc的真實面紗,也許咱們都已經學會使用spring mvc,或者說對spring mvc的原理在理論上已經能滾瓜爛熟。在開始以前,這可能須要你掌握Java EE的一些基本知識,好比說咱們要先學會Java EE 的Servlet技術規範,由於Spring mvc框架實現,底層是遵循Servlet規範的。前端

在開始源碼分析以前,咱們可能須要一個簡單的案例工程,不慌,小編已經安排好了:java

樣例工程下載地址 : github.com/SmallerCode…git

那下面就讓咱們開始吧!github

1、前置知識

你們都知道,咱們在使用spring mvc時一般會在web.xml文件中作以下配置:web

web.xmlspring

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    
    
    <!-- 上下文參數,在監聽器中被使用 -->
    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>
        	classpath:applicationContext.xml
        </param-value>
    </context-param>
    
    
    <!-- 監聽器配置 -->
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- 前端控制器配置 -->
    <servlet>
    	<servlet-name>dispatcher</servlet-name>
    	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	<init-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:applicationContext-mvc.xml</param-value>
    	</init-param>
    	<load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    	<servlet-name>dispatcher</servlet-name>
    	<url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
複製代碼

上面的配置總結起來有幾點內容,分別是:spring-mvc

  • 一、配置Spring Web上下文監聽器,該監聽器同時是Spring mvc啓動的入口,至於爲何,後面第二節將會講到
  • 二、前端控制器DispatcherServlet,該控制器是Spring mvc處理各類請求的入口及處理器

當咱們將spring mvc應用部署到tomcat時,當你不配置任何的context-paramlistener參數,只配置一個DispatcherServlet時,那麼tomcat在啓動的時候是不會初始化spring web上下文的,換句話說,tomcat是不會初始化spring框架的,由於你並無告訴它們spring的配置文件放在什麼地方,以及怎麼去加載。因此listener監聽器幫了咱們這個忙,那麼爲何配置監聽器以後就能夠告訴tomcat怎麼去加載呢?由於listener是實現了servlet技術規範的監聽器組件,tomcat在啓動時會先加載web.xml中是否有servlet監聽器存在,有則啓動它們。ContextLoaderListener是spring框架對servlet監聽器的一個封裝,本質上仍是一個servlet監聽器,因此會被執行,但因爲ContextLoaderListener源碼中是基於contextConfigLocationcontextClass兩個配置參數去加載相應配置的,所以就有了咱們配置的context-param參數了,servlet標籤裏的初始化參數也是一樣的道理,即告訴web服務器在啓動的同時把spring web上下文(WebApplicationContext)也給初始化了。tomcat

上面講了下tomcat加載spring mvc應用的大體流程,接下來將從源碼入手分析啓動原理。bash

2、Spring MVC web 上下文啓動源碼分析

假設如今咱們把上面web.xml文件中的<load-on-startup>1</load-on-startup>給去掉,那麼默認tomcat啓動時只會初始化spring web上下文,也就是說只會加載到applicationContext.xml這個文件,對於applicationContext-mvc.xml這個配置文件是加載不到的,<load-on-startup>1</load-on-startup>的意思就是讓DispatcherServlet延遲到使用的時候(也就是處理請求的時候)再作初始化。服務器

咱們已經知道spring web是基於servlet標準去封裝的,那麼很明顯,servlet怎麼初始化,WebApplicationContextweb上下文就應該怎麼初始化。咱們先看看ContextLoaderListener的源碼是怎樣的。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    // 初始化方法
    @Override
    public void contextInitialized(ServletContextEvent event) {
    	initWebApplicationContext(event.getServletContext());
    }
    // 銷燬方法
    @Override
    public void contextDestroyed(ServletContextEvent event) {
    	closeWebApplicationContext(event.getServletContext());
    	ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
複製代碼

ContextLoaderListener類實現了ServletContextListener,本質上是一個servlet監聽器,tomcat將會優先加載servlet監聽器組件,並調用contextInitialized方法,在contextInitialized方法中調用initWebApplicationContext方法初始化Spring web上下文,看到這煥然大悟,原來Spring mvc的入口就在這裏,哈哈~~~趕忙跟進去initWebApplicationContext方法看看吧!

initWebApplicationContext()方法:

// 建立web上下文,默認是XmlWebApplicationContext
if (this.context == null) {
    this.context = createWebApplicationContext(servletContext);
}

if (this.context instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    // 若是該容器尚未刷新過
    if (!cwac.isActive()) {
    	if (cwac.getParent() == null) {
    		ApplicationContext parent = loadParentContext(servletContext);
    		cwac.setParent(parent);
    	}
    	// 配置並刷新容器
    	configureAndRefreshWebApplicationContext(cwac, servletContext);
    }
}
複製代碼

上面的方法只作了兩件事:

  • 一、若是spring web容器尚未建立,那麼就建立一個全新的spring web容器,而且該容器爲root根容器,下面第三節講到的servlet spring web容器是在此根容器上建立起來的
  • 二、配置並刷新容器

上面代碼註釋說到默認建立的上下文容器是XmlWebApplicationContext,爲何不是其餘web上下文呢?爲啥不是下面上下文的任何一種呢?

咱們能夠跟進去createWebApplicationContext後就能夠發現默認是從一個叫ContextLoader.properties文件加載配置的,該文件的內容爲:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
複製代碼

具體實現爲:

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 自定義上下文,不然就默認建立XmlWebApplicationContext
    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 {
        // 從屬性文件中加載類名,也就是org.springframework.web.context.support.XmlWebApplicationContext
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
        	return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
        	throw new ApplicationContextException(
        			"Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}
複製代碼

上面能夠看出其實咱們也能夠自定義spring web的上下文的,那麼怎麼去指定咱們自定義的上下文呢?答案是經過在web.xml中指定contextClass參數,所以第一小結結尾時說contextClass參數和contextConfigLocation很重要~~至於contextConfigLocation參數,咱們跟進configureAndRefreshWebApplicationContext便可看到,以下圖:

總結:

spring mvc啓動流程大體就是從一個叫ContextLoaderListener開始的,它是一個servlet監聽器,可以被web容器發現並加載,初始化監聽器ContextLoaderListener以後,接着就是根據配置如contextConfigLocationcontextClass建立web容器了,若是你不指定contextClass參數值,則默認建立的spring web容器類型爲XmlWebApplicationContext,最後一步就是根據你配置的contextConfigLocation文件路徑去配置並刷新容器了。

3、DispatcherServlet控制器的初始化

好了,上面咱們簡單地分析了Spring mvc容器初始化的源碼,咱們永遠不會忘記,咱們默認建立的容器類型爲XmlWebApplicationContext,固然咱們也不會忘記,在web.xml中,咱們還有一個重要的配置,那就是DispatcherServlet。下面咱們就來分析下DispatcherServlet的初始化過程。

DispatcherServlet,就是一個servlet,一個用來處理request請求的servlet,它是spring mvc的核心,全部的請求都通過它,並由它指定後續操做該怎麼執行,咋一看像一扇門,所以我管它叫「閘門」。在咱們繼續以前,咱們應該共同遵照一個常識,那就是-------不管是監聽器仍是servlet,都是servlet規範組件,web服務器均可以發現並加載它們。

下面咱們先看看DispatcherServlet的繼承關係:

看到這咱們是否是一目瞭然了,DispatcherServlet繼承了HttpServlet這個類,HttpServlet是servlet技術規範中專門用於處理http請求的servlet,這就不難解釋爲何spring mvc會將DispatcherServlet做爲統一請求入口了。

由於一個servlet的生命週期是init()->service()->destory(),那麼DispatcherServlet怎麼初始化呢?看上面的繼承圖,咱們進到HttpServletBean去看看。

果不其然,HttpServletBean類中有一個init()方法,HttpServletBean是一個抽象類,init()方法以下:

能夠看出方法採用final修飾,由於final修飾的方法是不能被子類繼承的,也就是子類沒有一樣的init()方法了,這個init方法就是DispatcherServlet的初始化入口了。

接着咱們跟進FrameworkServletinitServletBean()方法:

在方法中將會初始化不一樣於第一小節的web容器,請記住,這個新的spring web 容器是專門爲dispactherServlet服務的,並且這個新容器是在第一小節根ROOT容器的基礎上建立的,咱們在<servlet>標籤中配置的初始化參數被加入到新容器中去。

至此,DispatcherSevlet的初始化完成了,聽着有點矇蔽,但其實也是這樣,上面的分析僅僅只圍繞一個方法,它叫init(),全部的servlet初始化都將調用該方法。

總結:

dispactherServlet的初始化作了兩件事情,第一件事情就是根據根web容器,也就是咱們第一小節建立的XmlWebApplicationContext,而後建立一個專門爲dispactherServlet服務的web容器,第二件事情就是將你在web.xml文件中對dispactherServlet進行的相關配置加載到新容器當中。

3、每一個request調用請求經歷了哪些過程

其實說到這纔是dispatcherServlet控制器的核心所在,由於web框架無非就是接受請求,處理請求,而後響應請求。固然了,若是dispactherServlet只是單純地接受處理而後響應請求,那未免太弱了,所以spring設計者加入了許許多多的新特性,好比說攔截器、消息轉換器、請求處理映射器以及各類各樣的Resolver,所以spring mvc很是強大。

dispatcherServlet類不作相關源碼分析,由於它就是一個固定的執行步驟,什麼意思呢?一個request進來,大體就經歷這樣的過程:

接受請求 -----> 是否有各類各樣的處理器Handler -------> 是否有消息轉換器HandlerAdapter --------> 響應請求

上面每一步若是存在相應的組件,固然前提是你在項目中有作相關的配置,則會執行你配置的組件,最後響應請求。所以明白大體的流程以後,若是你想調試一個request,那麼你徹底能夠在dispatcherServlet類的doDispatch方法中打個斷點,跟完代碼以後你就會發現其實大體流程就差很少了。

4、後話

本文的工程是基於傳統的web.xml加載web項目,固然在spring mvc中咱們也能夠徹底基於註解的方式進行配置,咱們能夠經過實現WebApplicationInitializer來建立本身的web啓動器,也能夠經過繼承AbstractAnnotationConfigDispatcherServletInitializer來建立相應的spring web容器(包括上面說到的根容器和servlet web容器),最後經過繼承WebMvcConfigurationSupport再一步進行自定義配置(相關攔截器,bean等)

感謝你的閱讀,期待與你共同進步,歡迎下方發表評論~~~

相關文章
相關標籤/搜索