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