Java架構之Spring MVC 啓動過程源碼分析

Java架構之Spring MVC 啟動過程源碼分析

從源碼層面上對Spring mvc的初始化過程進行分析,一起揭開Spring mvc的真實面紗,也許我們都已經學會使用spring mvc,或者說對spring mvc的原理在理論上已經能倒背如流。在開始之前,這可能需要你掌握Java EE的一些基本知識,比如說我們要先學會Java EE 的Servlet技術規範,因為Spring mvc框架實現,底層是遵循Servlet規範的。

一、前置知識

大家都知道,我們在使用spring mvc時通常會在 web.xml 文件中做如下配置:

web.xml

<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>contextConfigLocationparam-name

>

<param-value>

classpath:applicationContext.xml

param-value>

context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>

listener>

<servlet>

<servlet-name>dispatcherservlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet

servlet-class>

<init-param>

<param-name>contextConfigLocationparam-name>

<param-value>classpath:applicationContext-mvc.xmlparam-value>

init-param>

<load-on-startup>1load-on-startup>

servlet>

<servlet-mapping>

<servlet-name>dispatcherservlet-name>

<url-pattern>/url-pattern>

servlet-mapping>

web-app>

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

DispatcherServlet

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

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

二、Spring MVC web 上下文啟動源碼分析

假設現在我們把上面 web.xml 文件中的 1 給去掉,那麼默認tomcat啟動時只會初始化spring web上下文,也就是說只會加載到 applicationContext.xml 這個文件,對於 applicationContext-mvc.xml 這個配置文件是加載不到的, 1 的意思就是讓 DispatcherServlet 延遲到使用的時候( 也就是處理請求的時候 )再做初始化。

我們已經知道spring web是基於 servlet 標準去封裝的,那麼很明顯,servlet怎麼初始化, WebApplicationContext web上下文就應該怎麼初始化。我們先看看 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);

}

}

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

  • 1、如果spring web容器還沒有創建,那麼就創建一個全新的spring web容器,並且該容器為root根容器,下面第三節講到的servlet spring web容器是在此根容器上創建起來的
  • 2、配置並刷新容器

上面代碼註釋說到默認創建的上下文容器是 XmlWebApplicationContext ,為什麼不是其他web上下文呢?為啥不是下面上下文的任何一種呢?

Java架構之Spring MVC 啟動過程源碼分析

我們可以跟進去 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 即可看到,如下圖:

Java架構之Spring MVC 啟動過程源碼分析

總結:

spring mvc啟動流程大致就是從一個叫 ContextLoaderListener 開始的,它是一個servlet監聽器,能夠被web容器發現並加載,初始化監聽器 ContextLoaderListener 之後,接著就是根據配置如 contextConfigLocation 和 contextClass 創建web容器了,如果你不指定 contextClass 參數值,則默認創建的spring web容器類型為 XmlWebApplicationContext ,最後一步就是根據你配置的 contextConfigLocation 文件路徑去配置並刷新容器了。

三、 DispatcherServlet 控制器的初始化

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

DispatcherServlet ,就是一個servlet,一個用來處理request請求的servlet,它是spring mvc的核心,所有的請求都經過它,並由它指定後續操作該怎麼執行,咋一看像一扇門,因此我管它叫“閘門”。在我們繼續之前,我們應該共同遵守一個常識,那就是-------無論是監聽器還是servlet,都是servlet規範組件,web服務器都可以發現並加載它們。

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

Java架構之Spring MVC 啟動過程源碼分析

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

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

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

Java架構之Spring MVC 啟動過程源碼分析

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

接著我們跟進 FrameworkServlet 的 initServletBean() 方法:

Java架構之Spring MVC 啟動過程源碼分析

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

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

總結:

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

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

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

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

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

上面每一步如果存在相應的組件,當然前提是你在項目中有做相關的配置,則會執行你配置的組件,最後響應請求。因此明白大致的流程之後,如果你想調試一個request,那麼你完全可以在 dispatcherServlet 類的 doDispatch 方法中打個斷點,跟完代碼之後你就會發現其實大致流程就差不多了。

四、後話

本文的工程是基於傳統的web.xml加載web項目,當然在spring mvc中我們也可以完全基於註解的方式進行配置,我們可以通過實現 WebApplicationInitializer 來創建自己的web啟動器,也可以通過繼承 AbstractAnnotationConfigDispatcherServletInitializer 來創建相應的spring web容器(包括上面說到的根容器和servlet web容器),最後通過繼承 WebMvcConfigurationSupport 再一步進行自定義配置(相關攔截器,bean等)

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


分享到:


相關文章: