springmvc開門篇之源碼解讀ContextLoaderListener

前言: springmvc是什麼,能作什麼,即便是新手,也應該有所瞭解,俺這裏就不一一贅述了。java

首先咱們須要引入javaee的一些概念,爲何要引入它呢?不明覺厲,無論是什麼樣的web框架,那麼這玩意是基礎。不用懷疑。因此無論如何變化,咱們要謹記,是跟javaee的東西打交道。git

須要你們自行掌握的概念有: servlet容器,包括ServletContext,Listener,ServletConfig。 session,request這些就不說了。github

工欲善其事,必先利其器。因此咱們須要先折騰一個springmvc的工程。我這裏使用maven模板去建立。web

 

選擇這一個模板spring

  • org.jetbrains.idea.maven.model.MavenArchetype

 

工程建立完後咱們須要引入對應的jar包。我這裏按照最小化配置來。express

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>4.3.2.RELEASE</version>

</dependency>

 

加入webmvc(ps: 這裏使用了webmvc,它裏面又內置了好幾個。包括咱們熟悉的servlet, spring-beans,context等等)api

具體詳見webmvc的pom。這裏再也不累述,咱們繼續配置spring-mvc

最後加完的jar包有這麼多,實際上由於servlet-api是provided的 因此這裏沒有出現,但並不意味着他不存在。。tomcat

在web.xml中添加session

<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>spring</servlet-name>

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

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

</servlet>



<servlet-mapping>

<servlet-name>spring</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

在resources目錄中建立 applicationContext, 添加掃包(這裏不掃描Controller註解的)

<!-- configure auto scan -->

<context:component-scan base-package="org.spring">

<context:exclude-filter type="annotation"

expression="org.springframework.stereotype.Controller" />

</context:component-scan>

在WEB-INF中建立spring-servlet.xml,加入掃描Controller的掃包配置和激活RequestMappingHanlder適配器

<context:component-scan base-package="org.spring.*.controller" />

<mvc:annotation-driven />

到此咱們的工程已經能夠在容器中跑起來了。固然,上面的並非重點,接下來纔是重頭戲。跟着咱們的代碼飛吧。

啓動工程,進入 ContextLoaderListener , 咱們會發現他是實現了繼承自ContextLoader,實現的是ServletContextListener接口。

這裏多說一句。

有好奇的小夥伴就會問,繼承ContextLoader 的做用是什麼呀?

且聽我一一道來。

ContextLoaderListener他的主要做用是在web應用程序啓動時載入ioc容器。具體詳見下面會分解。而ContextLoader正是對ioc容器初始化進行了資源加載等操做,屬於靈魂級的類

ok, 咱們繼續說監聽器。

咱們打開ServletContextListener接口。發現以下方法, 見名知意。init開頭的就是初始化執行的動做,另一個則是銷燬

銷燬暫且不論,初始化纔是咱們的重頭戲。咱們看到他最後執行了這樣的操做。其實就是調用了ContextLoader.initWebApplicationContext(ServletContext servletContext) 方法。具體源碼以下

廢話很少說,咱們先看initWebApplicationContext(event.getServletContext()); 作了什麼

根源:

首先建立一個抽象的ClassPathResource,這玩意有點相似於java的File對象,是一個抽象的東東。

而後將其load進來。咱們看看這個properties文件中的內容。

# Default WebApplicationContext implementation class for ContextLoader.

# Used as fallback when no explicit context implementation has been specified as context-param.

# Not meant to be customized by application developers.



org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

能夠看得出來,這是一個ApplicationContext的實現類。這就是spring真正的上下文類。咱們看看他的繼承體系結構圖

進入正題。開始建立它

進入createWebApplicationContext。

這個時候從servletContext中去取contextClass 是空。那麼他從配置文件中根據這個類名去獲取對應的value值。也就是org.springframework.web.context.support.XmlWebApplicationContext 而後使用Class.forName的方式將其加載

而後拿到加載完畢的類,使用BeanUtils.instantiateClass(contextClass); 初始化

等同於Object object = new Object()這種方式。由於它使用的是反射。

 

這裏把上面的流程屢一下。。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

// 若是上下文中存在 WebApplicationContext 則拋異常。說明WebApplicationContext已經被初始化過了。

if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {

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 {

// Store context in local instance variable, to guarantee that

// it is available on ServletContext shutdown.

if (this.context == null) {

// 這裏去初始化上下文。具體流程上面已經有講到

this.context = createWebApplicationContext(servletContext);

}

// 咱們已知 從properties中讀取到的類爲XmlWebApplicationContext 且 XmlWebApplicationContext 是 ConfigurableWebApplicationContext的子類,因此這個判斷會進去

if (this.context instanceof ConfigurableWebApplicationContext) {

// 強轉

ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;

// cwac還沒有被激活,目前尚未進行配置文件加載

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

// determine parent for root web application context, if any.

ApplicationContext parent = loadParentContext(servletContext);

cwac.setParent(parent);

}

// 加載配置文件。

configureAndRefreshWebApplicationContext(cwac, servletContext);

}

}

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 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;

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;

}

}

而後咱們進入加載配置文件那一段

有IOC經驗的朋友應該知道,只有真正刷新IOC容器的時候纔是IOC容器真正初始化並註冊各類bean的時候。這裏再也不贅述。後續文章會對ioc容器進行覆蓋式轟炸。。這裏只須要知道概念就OK。

 

總結: 經過講解ContextLoaderListener,咱們會發現這個東西實際上是將javaEE的容器和spring的IOC整合到了一塊兒,以後會把建立完成的上下文對象塞入ServletContext上下文中,注意這裏的ServletContext是JavaEE的上下文。也就是說spring本身的上下文在javaee的上下文中只是一個屬性。。。至於爲何是這樣呢。你們一想就明白了。由於javaee是最外層的規範,而咱們的web容器(tomcat, jetty等)實現了這一規範。而spring呢,是須要依託於javaee容器來運行。那麼, 就意味着, Spring的IOC容器對於javaEE來說只是一個屬性。。沒錯。因此javaEE的容器就是spring容器的一個屬性。。

 

本文就寫到了這裏,但願能幫到你們,謝謝。若是對本文有疑問的請你們留言給我,我會及時回覆。

最後打個小廣告,歡迎你們加入qq羣:77174608 一塊兒討論學習。。

 

文章代碼已上傳至GitHub: 點我進入

gitee地址 : 點此進入

相關文章
相關標籤/搜索