spring學(xué)習(xí):spring與springmvc父子容器【云圖智聯(lián)】

1.spring和springmvc父子容器概念介紹

在spring和springmvc進行整合的時候,一般情況下我們會使用不同的配置文件來配置spring和springmvc,因此我們的應(yīng)用中會存在至少2個ApplicationContext實例,由于是在web應(yīng)用中,因此最終實例化的是ApplicationContext的子接口WebApplicationContext妓盲。如下圖所示:

?上圖中顯示了2個WebApplicationContext實例,為了進行區(qū)分,分別稱之為:Servlet WebApplicationContext垫言、Root WebApplicationContext。 其中:

Servlet WebApplicationContext:這是對J2EE三層架構(gòu)中的web層進行配置倾剿,如控制器(controller)筷频、視圖解析器(view resolvers)等相關(guān)的bean。通過spring mvc中提供的DispatchServlet來加載配置前痘,通常情況下凛捏,配置文件的名稱為spring-servlet.xml。

Root WebApplicationContext:這是對J2EE三層架構(gòu)中的service層芹缔、dao層進行配置坯癣,如業(yè)務(wù)bean,數(shù)據(jù)源(DataSource)等最欠。通常情況下示罗,配置文件的名稱為applicationContext.xml。在web應(yīng)用中芝硬,其一般通過ContextLoaderListener來加載蚜点。

以下是一個web.xml配置案例:

<?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/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">??


????<!—創(chuàng)建Root?WebApplicationContext-->

????<context-param>??

????????<param-name>contextConfigLocation</param-name>??

????????<param-value>/WEB-INF/spring/applicationContext.xml</param-value>??

????</context-param>??


????<listener>??

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

????</listener>??


????<!—創(chuàng)建Servlet?WebApplicationContext-->

????<servlet>??

????????<servlet-name>dispatcher</servlet-name>??

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

????????<init-param>??

????????????<param-name>contextConfigLocation</param-name>??

????????????<param-value>/WEB-INF/spring/spring-servlet.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>

在上面的配置中:

1.?ContextLoaderListener會被優(yōu)先初始化時,其會根據(jù)<context-param>元素中contextConfigLocation參數(shù)指定的配置文件路徑吵取,在這里就是"/WEB-INF/spring/applicationContext.xml”禽额,來創(chuàng)建WebApplicationContext實例。 并調(diào)用ServletContext的setAttribute方法,將其設(shè)置到ServletContext中脯倒,屬性的key為”org.springframework.web.context.WebApplicationContext.ROOT”实辑,最后的”ROOT"字樣表明這是一個 Root WebApplicationContext。

2.DispatcherServlet在初始化時藻丢,會根據(jù)<init-param>元素中contextConfigLocation參數(shù)指定的配置文件路徑剪撬,即"/WEB-INF/spring/spring-servlet.xml”,來創(chuàng)建Servlet WebApplicationContext悠反。同時残黑,其會調(diào)用ServletContext的getAttribute方法來判斷是否存在Root WebApplicationContext。如果存在斋否,則將其設(shè)置為自己的parent梨水。這就是父子上下文(父子容器)的概念。

父子容器的作用在于茵臭,當(dāng)我們嘗試從child context(即:Servlet WebApplicationContext)中獲取一個bean時疫诽,如果找不到,則會委派給parent context (即Root WebApplicationContext)來查找旦委。

如果我們沒有通過ContextLoaderListener來創(chuàng)建Root WebApplicationContext奇徒,那么Servlet WebApplicationContext的parent就是null,也就是沒有parent context缨硝。?

2.為什么要有父子容器

筆者理解摩钙,父子容器的作用主要是劃分框架邊界。

在J2EE三層架構(gòu)中查辩,在service層我們一般使用spring框架胖笛, 而在web層則有多種選擇,如spring mvc宜肉、struts等匀钧。因此,通常對于web層我們會使用單獨的配置文件谬返。例如在上面的案例中,一開始我們使用spring-servlet.xml來配置web層日杈,使用applicationContext.xml來配置service遣铝、dao層。如果現(xiàn)在我們想把web層從spring mvc替換成struts莉擒,那么只需要將spring-servlet.xml替換成Struts的配置文件struts.xml即可酿炸,而applicationContext.xml不需要改變。

事實上涨冀,如果你的項目確定了只使用spring和spring mvc的話填硕,你甚至可以將service 、dao、web層的bean都放到spring-servlet.xml中進行配置扁眯,并不是一定要將service壮莹、dao層的配置單獨放到applicationContext.xml中,然后使用ContextLoaderListener來加載姻檀。在這種情況下命满,就沒有了Root WebApplicationContext,只有Servlet WebApplicationContext绣版。

3.Root WebApplicationContext創(chuàng)建過程源碼分析

ContextLoaderListener用于創(chuàng)建ROOT WebApplicationContext胶台,其實現(xiàn)了ServletContextListener接口的contextInitialized和contextDestroyed方法,在web應(yīng)用啟動和停止時杂抽,web容器(如tomcat)會負責(zé)回調(diào)這兩個方法诈唬。而創(chuàng)建Root WebApplicationContext就是在contextInitialized中完成的,相關(guān)源碼片段如下所示:

org.springframework.web.context.ContextLoaderListener?

public?class?ContextLoaderListener?extends?ContextLoader?implements?ServletContextListener?{

???//...

???@Override

???public?void?contextInitialized(ServletContextEvent?event)?{

??????initWebApplicationContext(event.getServletContext());

???}

???//...

}

????可以看到ContextLoaderListener繼承了ContextLoader類缩麸,真正的創(chuàng)建在操作讯榕,是在ContextLoader的initWebApplicationContext方法中完成。

org.springframework.web.context.ContextLoader#initWebApplicationContext?

public?WebApplicationContext?initWebApplicationContext(ServletContext?servletContext)?{

???//1匙睹、保證只能有一個ROOT?WebApplicationContext

???//嘗試以”org.springframework.web.context.WebApplicationContext.ROOT”為key從ServletContext中查找WebApplicationContext實例

???//如果已經(jīng)存在愚屁,則拋出異常。

???//一個典型的異常場景是在web.xml中配置了多個ContextLoaderListener痕檬,那么后初始化的ContextLoaderListener就會拋出異常

???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!");

???}


???//2霎槐、打印日志,注意日志中的提示內(nèi)容:"Initializing?Spring?root?WebApplicationContext”

???//這驗證了我們之前的說法梦谜,ContextLoaderListener創(chuàng)建的是root?WebApplicationContext

???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)?{

?????????//?3?創(chuàng)建WebApplicationContext實現(xiàn)類實例丘跌。其內(nèi)部首先會確定WebApplicationContext實例類型。

????????//?首先判斷有沒有<context-param>元素的<param-name>值為contextClass唁桩,如果有闭树,則對應(yīng)的<param-value>值,就是要創(chuàng)建的WebApplicationContext實例類型

????????//?如果沒有指定荒澡,則默認(rèn)的實現(xiàn)類為XmlWebApplicationContext报辱。這是在spring-web-xxx.jar包中的ContextLoader.properties指定的

????????//?注意這個時候,只是創(chuàng)建了WebApplicationContext對象實例单山,還沒有加載對應(yīng)的spring配置文件

?????????this.context?=?createWebApplicationContext(servletContext);

??????}

??????//4?XmlWebApplicationContext實現(xiàn)了ConfigurableWebApplicationContext接口碍现,因此會進入if代碼塊

??????if?(this.context?instanceof?ConfigurableWebApplicationContext)?{

?????????ConfigurableWebApplicationContext?cwac?=?(ConfigurableWebApplicationContext)?this.context;

?????????//?4.1?由于WebApplicationContext對象實例還沒有加載對應(yīng)配置文件,spring上下文還沒有被刷新米奸,因此isActive返回false昼接,進入if代碼塊

?????????if?(!cwac.isActive())?{

????????????//4.2?當(dāng)前ROOT?WebApplicationContext的父context為null,則嘗試通過loadParentContext方法獲取父ApplicationContext悴晰,并設(shè)置到其中

????????????//由于loadParentContext方法目前寫死返回null慢睡,因此可以忽略4.2這個步驟。

????????????if?(cwac.getParent()?==?null)?{

???????????????ApplicationContext?parent?=?loadParentContext(servletContext);

???????????????cwac.setParent(parent);

????????????}

????????????//4.3?加載配置spring文件。根據(jù)<context-param>指定的contextConfigLocation漂辐,確定配置文件的位置泪喊。

????????????configureAndRefreshWebApplicationContext(cwac,?servletContext);

?????????}

??????}

??????//?5、將創(chuàng)建的WebApplicationContext實例以”org.springframework.web.context.WebApplicationContext.ROOT”為key設(shè)置到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;

???}

}

4.Servlet WebApplicationContext創(chuàng)建過程源碼分析

DispatcherServlet負責(zé)創(chuàng)建Servlet WebApplicationContext者吁,并嘗試將ContextLoaderListener創(chuàng)建的ROOT WebApplicationContext設(shè)置為自己的parent窘俺。其類圖繼承關(guān)系如下所示:?

?其中HttpServletBean繼承了HttpServlet,因此在應(yīng)用初始化時复凳,其init方法會被調(diào)用瘤泪,如下:

org.springframework.web.servlet.HttpServletBean#init?

public?final?void?init()?throws?ServletException?{

???//...

???//?這個方法在HttpServletBean中是空實現(xiàn)

???initServletBean();

???if?(logger.isDebugEnabled())?{

??????logger.debug("Servlet?'"?+?getServletName()?+?"'?configured?successfully");

???}

}

HttpServletBean的init方法中,調(diào)用了initServletBean()方法育八,在HttpServletBean中对途,這個方法是空實現(xiàn)。FrameworkServlet覆蓋了HttpServletBean中的initServletBean方法髓棋,如下:

org.springframework.web.servlet.FrameworkServlet#initServletBean?

@Override

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

??????//?initWebApplicationContext方法中实檀,創(chuàng)建了Servlet?WebApplicationContext實例

??????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");

???}

}

上述代碼片段中,我們可以看到通過調(diào)用FrameworkServlet的另一個方法initWebApplicationContext()按声,來真正創(chuàng)建WebApplicationContext實例膳犹,其源碼如下:

org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext?

protected?WebApplicationContext?initWebApplicationContext()?{

???//1?通過工具類WebApplicationContextUtils來獲取Root?WebApplicationContext

???//?其內(nèi)部以”org.springframework.web.context.WebApplicationContext.ROOT”為key從ServletContext中查找WebApplicationContext實例作為rootContext?

???WebApplicationContext?rootContext?=

?????????WebApplicationContextUtils.getWebApplicationContext(getServletContext());

???WebApplicationContext?wac?=?null;


???//2、在我們的案例中是通過web.xml配置的DispatcherServlet签则,此時webApplicationContext為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?經(jīng)過第二步,wac依然為null渐裂,此時嘗試根據(jù)FrameServlet的contextAttribute?字段的值豺旬,從ServletContext中獲取Servlet?WebApplicationContext實例,在我們的案例中柒凉,contextAttribute值為空族阅,因此這一步過后,wac依然為null

???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?開始真正的創(chuàng)建Servlet?WebApplicationContext膝捞,并將rootContext設(shè)置為parent

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

}

5.java方式配置

最后坦刀,對于Root WebApplicationContext和Servlet WebApplicationContext的創(chuàng)建,我們也可以通過java代碼的方式進行配置绑警。spring通過以下幾個類對此提供了支持:

AbstractContextLoaderInitializer:其用于動態(tài)的往ServletContext中注冊一個ContextLoaderListener求泰,從而創(chuàng)建Root WebApplicationContext

AbstractDispatcherServletInitializer:其用于動態(tài)的往ServletContext中注冊一個DispatcherServlet,從而創(chuàng)建Servlet WebApplicationContext

對應(yīng)的類圖繼承關(guān)系如下所示:?

?AbstractAnnotationConfigDispatcherServletInitializer用于提供AbstractContextLoaderInitializer和AbstractDispatcherServletInitializer所需要的配置计盒。

AbstractAnnotationConfigDispatcherServletInitializer中有3個抽象方法需要實現(xiàn):?

public?class?MyWebAppInitializer?extends?AbstractAnnotationConfigDispatcherServletInitializer?{

????//獲得創(chuàng)建Root?WebApplicationContext所需的配置類

????@Override

????protected?Class<?>[]?getRootConfigClasses()?{

????????return?new?Class<?[]?{?RootConfig.class?};

????}

????//獲得創(chuàng)建Servlet?WebApplicationContext所需的配置類

????@Override

????protected?Class<?>[]?getServletConfigClasses()?{

????????return?new?Class<?[]?{?App1Config.class?};

????}

????//獲得DispatchServlet攔截的url

????@Override

????protected?String[]?getServletMappings()?{

????????return?new?String[]?{?"/app1/*"?};

????}

}

免費學(xué)習(xí)視頻歡迎關(guān)注云圖智聯(lián):https://e.yuntuzhilian.com/?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芽丹,隨后出現(xiàn)的幾起案子北启,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咕村,死亡現(xiàn)場離奇詭異场钉,居然都是意外死亡,警方通過查閱死者的電腦和手機懈涛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門逛万,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人批钠,你說我怎么就攤上這事宇植。” “怎么了埋心?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我景醇,道長攒驰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任茬斧,我火速辦了婚禮腰懂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘项秉。我一直安慰自己绣溜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布伙狐。 她就那樣靜靜地躺著涮毫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贷屎。 梳的紋絲不亂的頭發(fā)上罢防,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音唉侄,去河邊找鬼咒吐。 笑死,一個胖子當(dāng)著我的面吹牛属划,可吹牛的內(nèi)容都是我干的恬叹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼同眯,長吁一口氣:“原來是場噩夢啊……” “哼绽昼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起须蜗,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤硅确,失蹤者是張志新(化名)和其女友劉穎目溉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菱农,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡缭付,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了循未。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陷猫。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖的妖,靈堂內(nèi)的尸體忽然破棺而出绣檬,到底是詐尸還是另有隱情,我是刑警寧澤羔味,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布河咽,位于F島的核電站,受9級特大地震影響赋元,放射性物質(zhì)發(fā)生泄漏忘蟹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一搁凸、第九天 我趴在偏房一處隱蔽的房頂上張望媚值。 院中可真熱鬧,春花似錦护糖、人聲如沸褥芒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锰扶。三九已至,卻和暖如春寝受,著一層夾襖步出監(jiān)牢的瞬間坷牛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工很澄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留京闰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓甩苛,卻偏偏與公主長得像蹂楣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子讯蒲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355