【Spring MVC】Spring MVC啓動過程源碼分析

一、Servlet開場

    servlet現在已經算是很基礎的內容了,如果還沒有看過、分析過servlet的源碼,可能就要out了。

    servlet的生命週期通過三個方法init、service、destory來構建,這些方法不用你調用,servlet容器會自動調用。servlet接口裏面定義了這些規範,但是在接口實現的抽象類——無協議通用的GenericServelt——中有一些小改動,重載了一個不包含參數的init方法,並且建議你在開發自己的servlet的時候,重寫這個不包含參數的方法,GenericServlet是Servlet接口的抽象子類,他並沒有實現service方法,而是將這個抽象方法的實現放到子類中完成;再看下一層實現了HTTP協議的子類HttpServlet,他實現了這個public的service方法,但是還重載了一個protected的service方法。

    到這裏,Spring MVC啓動過程中很多有意思的東西就可以漸漸浮出水面了,Spring MVC的啓動,依靠DispatcherServelt,這貨繼承了HttpServlet。

    1. 關於運行:如果根據jsp/servlet的開發規範來看,他應該重寫protected service模板方法中給出的doXxx方法,否則調用拋出異常,但是用過Spring MVC之後,你發現並不是這樣的,那麼在HttpServlet中public service轉調protected service的邏輯必然被重寫了(沒有必要重寫public service的邏輯,public service將無協議轉成了HTTP協議),至於爲什麼重寫的是protected的service方法而不是public的,回顧重寫的規則再看看Spring MVC servlet層級中的FramworkServlet源碼,兩相驗證即可;

    2. 關於啓動:使用Spring MVC,只需要配置DispatcherServlet即可,可以不需要再配置Spring web包中的ContextLoaderListner(配置或不配置都可以,Spring MVC源碼邏輯中有相關部分的體現,源碼會在後續分析中給出),那麼Spring容器在哪裏啓動呢?這一切必然和Spring MVC重寫GenericServlet無參的init方法有關;

    3. 關於銷燬:通過Spring web包的ContextLoaderListener將Spring接入web,可以知道當容器銷燬的時候需要將Spring的東西從servlet容器中清理掉,既然Spring MVC可以不配置ContextLoaderListner,那麼必然要在destory方法中做些什麼來釋放資源等等;


    DispatcherServlet終究還是一個servlet,因此以servlet的視角來審視他再合理不過了。


二、啓動過程

1. 鋪墊

    a. 當servlet實例被創建以後,servlet容器會首先調用其init方法;

    b. Spring MVC中servlet的繼承體系:DispatcherServlet extends FramworkServlet extends HttpServletBean extends HttpServlet extends GenericServlet implements Servlet;

2. 啓動過程簡要分析


    轉到HttpServletBean中,查看init方法。



    到FraworkServlet中才漸漸接近容器啓動的核心方法,其實從servlet的取名就能看出,FramworkServlet類,包含了整個框架的啓動邏輯。

protected WebApplicationContext initWebApplicationContext() {
	// 1-------------
	// 如果配置了ContextLoaderListener,這裏就能夠拿到XmlWebApplicationContext的實例
	// 也就表明,Spring容器隨着ServletContext的啓動已經啓動過了
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	// 2-------------
	// 對於屬性webApplicationContext爲不爲null,我嘗試追了一下源碼,啓動中這個屬性應該是位null的
	WebApplicationContext wac = null;
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 3-------------
	// 從WebApplicationContextUtils工具類獲取applicationcontext,和方法第一句代碼一樣
	if (wac == null) {
		// No context instance was injected at construction time -> see if one
		// has been registered in the servlet context. If one exists, it is assumed
		// that the parent context (if any) has already been set and that the
		// user has performed any initialization such as setting the context id
		wac = findWebApplicationContext();
	}
	// 4-------------
	// 上述找了一輪,沒找到,那麼就要開始創建了
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);
	}
	// 5-------------
	// 到這裏,纔是Spring MVC的初始化過程,此時會跳轉到DispatcherServlet來進行
	// 處理器映射器、處理器適配器、試圖解析器等等Spring MVC組件都是在這裏完成初始化
	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~4步都是Spring容器初始化的過程,直到第五步,纔是Spring MVC組件初始化的過程。

    第一步,正如之前所說,Spring通過Spring-web包接入web,實際上是配置ContextLoaderListener來使得Spring容器隨着web容器的啓動而啓動,而使用Spring MVC之後,是否還需要配置ContextLoaderListener呢?

    對於Spring接入web的源碼和原理分析,建議先看一看,也是一個很有意思的過程,參看點擊打開鏈接點擊打開鏈接

    對於是否需要配置ContextLoaderListener,實際上配不配都可以,配置的話關聯一下能用,不配置的話第四步會創建,兩種方式實現的差異在於如果沒有配置ContextLoadlistener,那麼就在第四步中創建一個用;如果配置了ContextLoaderListener,那麼就用ContextLoaderListener方式創建出來的ApplicationContext。當然,這是在一般情況下,也就是FrameworkServlet的屬性this.webApplicationContext沒有被注入的情況下。要追蹤this.webApplication是否被注入,從對象初始化的角度來追查,在web.xml中配置的DispatcherServlet的創建,用的是默認的構造器(自己寫個類繼承一下然後配置可驗證),也就是說在大多數場景下this.webApplicationContext不會在構造的時候被注入。實際上這一段的邏輯我自己也沒有完全走通,代碼做什麼很簡單,但是爲什麼這麼做的邏輯沒有看明白。通過以下列表來羅列可能出現的情形並追蹤,可以知道配置與否對系統造成的影響(一般場景是第二和第四列的內容)。

是否配置ContextLoaderListener 是否注入this.webApplictcationContext 此時DispatcherServlet中ApplicationContext的情況
是,則rootContext有值 是,則wac有值 rootContext成爲wac的parent
是,則rootContext有值 否,wac=null rootContext成爲wac的parent
否,rootContext=null 是,則wac有值 使用wac
否,rootContext=null 否,wac=null 創建並使用wac

    第二步,判斷WebApplicationContext webApplicationContext是否爲null,不爲null,則會在做一些設置之後重啓容器,wac.refresh()。實際上,通過配置DispatcherServlet來看,使用的是DispatcherServlet默認構造方法,是不需要傳遞參數,改構造功能方法中默認super(),調用FrameworkServlet的默認構造方法,改默認構造不會實例化這個屬性,因此,這個屬性還是爲null。參看下圖,整個FramworkServlet中使用到該屬性的代碼。


    無論是否配置ContextLoaderListener,都不會通過set方法或構造函數注入該屬性,因此第二步的邏輯似乎永遠不會被執行,我也沒想到相關的場景能觸發這一段邏輯的執行,沒想明白這一段邏輯的觸發,有大神能解答,感激不敬。

    關於這一段邏輯,和ContextLoaderListener中極度相似,簡直就是copy and paste,只是我沒能找到觸發的場景,即this.webApplicationContext不爲null。

    第三步,這一部的邏輯和第一部一個造型,都是從WebApplicationContextUtils中獲取wac,和第二步的關係承上啓下。


    第四步,正常情況會走這一步的,這段代碼的邏輯是這樣的,wac=createWebApplicationContext(null);,FramworkServlet中對象初始化的時候,就完成了contextClass=XmlWebApplicationContext.class,除非你在web.xml中配置DispatcherServlet時另行平直了contextClass,否則默認會通過反射來實例化XmlWebApplicationContext實例,然後再現實的調用refresh方法啓動Spring容器。


    進入configureAndRefreshWebApplicationContext方法,第一感覺就是粘貼複製,和ContextLoaderListener的代碼一毛一樣,最主要的是顯式的調用了refresh來啓動或刷新Spring容器。

    第五步,這一步,纔是所謂的Spring MVC的主要邏輯,跳轉到DispatcherServlet中執行。


    這裏就是正式的初始化Spring MVC的各個組件,在此之前,DispatcherServlet有一個靜態代碼塊,加載了Spring MVC中組件的初始化策略,也就是在initStrategies方法中,除了initMultipartResolver沒有默認策略之外,其他八個都有默認的初始化策略,而對於initMultipartResolver,需要顯示的配置對應的bean來支持文件上傳。


    Spring MVC默認的初始化策略,即DispatcherServlet.properties的內容。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

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

    到這裏,基本已經能用了,但是還有最後一步,就是要將創建出來的XmlWebApplicationContext放置到全應用都可以訪問的地方,即ServletContext,但是這裏存放的key和ContextLoadListener中存放的key是不一樣的,因此如果沒有配置ContextLoaderListener方式啓動Servlet容器,那麼通過WebApplicationContextUtils是拿不到東西的,但是可以直接從ServletContext中獲取。


    至此,無論是Spring容器還是Spring MVC組件都已經全部啓動完畢,整個應用可以對外提供服務了。

三、銷燬


    銷燬方法最終調用applicationcontext的close邏輯進行Spring容器的銷燬,這裏就不再介紹了。


四、補充

    如何拿到ApplicationContext對象。

    方式一:如果只配置了DispatcherServlet,則可以先獲取ServletContext,然後通過key=FrameworkServlet.SERVLET_CONTEXT_PREFIX+"DispatcherServlet在web.xml在配置的名字";

    方式二:如果既配置了ContextLoaderListener,又配置了DispatcherServlet,表現層的ApplicationContext通過方式一可以獲取,業務層的ApplicationContext(即表現層Context的parent),可以通過WebApplicationContextUtils.getWebApplicationContext(getServletContext());獲取;

    方式三:request.getAttribute(DispatcherServelt.WEB_APPLICATION_CONTEXT_ATTRIBUTE);,不過該方法只能獲取表現層的,獲取到ApplicationContext之後,也可以獲取業務層的parent;


附註:

    本文如有錯漏,煩請不吝指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章