本文從API角度入手,帶你了解SpringMVC啟動(dòng)的原理。
ServletContainerInitializer
在web容器啟動(dòng)時(shí)為提供給第三方組件機(jī)會(huì)做一些初始化的工作,例如注冊(cè)servlet
或者filtes
等,servlet規(guī)范中通過(guò)ServletContainerInitializer
實(shí)現(xiàn)此功能。
每個(gè)框架要使用ServletContainerInitializer
就必須在對(duì)應(yīng)的jar包的META-INF/services
目錄創(chuàng)建一個(gè)名為javax.servlet.ServletContainerInitializer
的文件,文件內(nèi)容指定具體的ServletContainerInitializer
實(shí)現(xiàn)類,
那么,當(dāng)web容器啟動(dòng)時(shí)就會(huì)運(yùn)行這個(gè)初始化器做一些組件內(nèi)的初始化工作。
一般伴隨著ServletContainerInitializer
一起使用的還有HandlesTypes
注解,通過(guò)HandlesTypes
可以將感興趣的一些類注入到ServletContainerInitializerde
的onStartup
方法作為參數(shù)傳入。
Tomcat容器的ServletContainerInitializer
機(jī)制的實(shí)現(xiàn),主要交由Context容器和ContextConfig監(jiān)聽器共同實(shí)現(xiàn),
ContextConfig監(jiān)聽器負(fù)責(zé)在容器啟動(dòng)時(shí)讀取每個(gè)web應(yīng)用的WEB-INF/lib
目錄下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer
,以及web根目錄
下的META-INF/services/javax.servlet.ServletContainerInitializer
,
通過(guò)反射完成這些ServletContainerInitializer
的實(shí)例化,然后再設(shè)置到Context容器中,最后Context容器啟動(dòng)時(shí)就會(huì)分別調(diào)用每個(gè)ServletContainerInitializer
的onStartup
方法,并將感興趣的類作為參數(shù)傳入。
基本的實(shí)現(xiàn)機(jī)制如圖,首先通過(guò)ContextConfig監(jiān)聽器遍歷每個(gè)jar包或web根目錄的META-INF/services/javax.servlet.ServletContainerInitializer
文件,根據(jù)讀到的類路徑實(shí)例化每個(gè)ServletContainerInitializer
;然后再分別將實(shí)例化好的ServletContainerInitializer
設(shè)置進(jìn)Context容器中;最后Context容器啟動(dòng)時(shí)分別調(diào)用所有ServletContainerInitializer
對(duì)象的onStartup
方法。
假如讀出來(lái)的內(nèi)容為com.seaboat.mytomcat.CustomServletContainerInitializer
,則通過(guò)反射實(shí)例化一個(gè)CustomServletContainerInitializer
對(duì)象,這里涉及到一個(gè)@HandlesTypes
注解的處理,被它標(biāo)明的類需要作為參數(shù)值傳入到onStartup
方法。
如下例子:
@HandlesTypes({ HttpServlet.class,Filter.class })
public class CustomServletContainerInitializer implements
ServletContainerInitializer {
public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
throws ServletException {
for(Class c : classes)
System.out.println(c.getName());
}
}
其中@HandlesTypes
標(biāo)明的HttpServlet
和Filter
兩個(gè)class被注入到了onStartup
方法。
所以這個(gè)注解也是需要在ContextConfig監(jiān)聽器中處理。
前面已經(jīng)介紹了注解的實(shí)現(xiàn)原理,由于有了編譯器的協(xié)助,我們可以方便地通過(guò)ServletContainerInitializer
的class對(duì)象中獲取到HandlesTypes
對(duì)象,進(jìn)而再獲取到注解聲明的類數(shù)組,如
HandlesTypes ht =servletContainerInitializer.getClass().getAnnotation(HandlesTypes.class);
Class<?>[] types = ht.value();
即可獲取到HttpServlet
和Filter
的class對(duì)象數(shù)組,后面Context容器調(diào)用CustomServletContainerInitializer
對(duì)象的onStartup
方法時(shí)作為參數(shù)傳入。
至此,即完成了servlet規(guī)范的ServletContainerInitializer
初始化器機(jī)制。
SpringServletContainerInitializer
上面提到了META-INF/services/javax.servlet.ServletContainerInitializer
,在Springspring-web-4.3.0.RELEASE.jar
Jar包中可以找到該文件,內(nèi)容如下:
org.springframework.web.SpringServletContainerInitializer
下面,我們就來(lái)詳細(xì)講解下SpringServletContainerInitializer
。
首先看下API中的描述:
Servlet 3.0 ServletContainerInitializer
被設(shè)計(jì)為使用Spring的WebApplicationInitializer
SPI來(lái)支持Servlet容器的基于代碼的配置,而不是傳統(tǒng)的基于web.xml的配置(也可能兩者結(jié)合使用)。
一、運(yùn)作機(jī)制
假設(shè)類路徑中存在spring-web
模塊的JAR包,SpringServletContainerInitializer
將被加載并實(shí)例化,并且在容器啟動(dòng)期間由Servlet 3.0容器調(diào)用onStartup
方法。
這是通過(guò)JAR Services API ServiceLoader.load(Class)
方法(檢測(cè)Spring-Web模塊的META-INF/services/javax.servlet.ServletContainerInitializer
配置文件)實(shí)現(xiàn)的。
二、與web.xml結(jié)合使用
Web應(yīng)用程序可以選擇通過(guò)web.xml
中的metadata-complete
屬性(它控制掃描Servlet注解的行為)
或通過(guò)web.xml
中的<absolute-ordering>
元素(它控制哪些web fragments
(i.e. jars)被允許執(zhí)行掃描ServletContainerInitializer
)
來(lái)限制Servlet容器在啟動(dòng)時(shí)掃描的類路徑。
當(dāng)使用這個(gè)特性時(shí),可以通過(guò)添加"spring_web"到web.xml
里的web fragments
列表來(lái)啟用SpringServletContainerInitializer
,
如下所示:
<absolute-ordering>
<name>some_web_fragment</name>
<name>spring_web</name>
</absolute-ordering>
servlet3.X中的metadata-complete屬性
在Servlet3.X的web.xml中可以設(shè)置metadata-complete屬性,例如:<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> </web-app>
如果設(shè)置
metadata-complete="true"
,會(huì)在啟動(dòng)時(shí)不掃描注解(annotation)。如果不掃描注解的話,用注解進(jìn)行的配置就無(wú)法生效,例如:@WebServlet
三、與Spring的WebApplicationInitializer
的關(guān)系
Spring的WebApplicationInitializer SPI僅由一個(gè)方法組成:WebApplicationInitializer.onStartup(ServletContext)
。聲明與ServletContainerInitializer.onStartup(Set, ServletContext)
非常相似:簡(jiǎn)單地說(shuō),SpringServletContainerInitializer
負(fù)責(zé)將ServletContext
實(shí)例化并委托給用戶定義的WebApplicationInitializer
實(shí)現(xiàn)。然后每個(gè)WebApplicationInitializer
負(fù)責(zé)完成初始化ServletContext
的實(shí)際工作。下面的onStartup
文檔中詳細(xì)介紹了委托的具體過(guò)程。
四、注意事項(xiàng)
一般來(lái)說(shuō),這個(gè)類應(yīng)該被視為WebApplicationInitializer
SPI的支持。利用這個(gè)容器初始化器也是完全可選的:雖然這個(gè)初始化器在所有的Servlet 3.0+運(yùn)行環(huán)境下被加載和調(diào)用,但用戶可以選擇是否提供WebApplicationInitializer
實(shí)現(xiàn)。如果未檢測(cè)到WebApplicationInitializer
類型,則此SpringServletContainerInitializer
將不起作用。
請(qǐng)注意,除了這些類型是在spring-web
模塊JAR中提供的,使用這個(gè)SpringServletContainerInitializer
和WebApplicationInitializer
與Spring MVC沒(méi)有任何“捆綁”。相反,它們可以被認(rèn)為是通用的,以便于簡(jiǎn)化ServletContext
基于代碼的配置。換句話說(shuō),任何servlet
, listener
, 或者filter
都可以在WebApplicationInitializer
中注冊(cè),而不僅僅是Spring MVC特定的組件。
SpringServletContainerInitializer
既不是為擴(kuò)展而設(shè)計(jì)的。它應(yīng)該被認(rèn)為是一個(gè)內(nèi)部類型,WebApplicationInitializer
是面向用戶的SPI。
好啦,現(xiàn)在對(duì)SpringServletContainerInitializer
有了一個(gè)比較透徹的了解,下面我們來(lái)看一下唯一的onStartup
方法。
將ServletContext
委托給類路徑中的WebApplicationInitializer
實(shí)現(xiàn)。
因?yàn)檫@個(gè)類聲明了@HandlesTypes(WebApplicationInitializer.class)
,所以
Servlet 3.0+容器會(huì)自動(dòng)掃描類路徑下Spring的WebApplicationInitializer
接口的實(shí)現(xiàn),并將所有這些類型的集合提供給這個(gè)方法的webAppInitializerClasses
參數(shù)。
如果在類路徑下找不到WebApplicationInitializer
實(shí)現(xiàn),則此方法不會(huì)有任何操作。將發(fā)出INFO級(jí)別的日志消息,通知用戶ServletContainerInitializer
確實(shí)已被調(diào)用,但沒(méi)有找到WebApplicationInitializer
實(shí)現(xiàn)。
假設(shè)檢測(cè)到一個(gè)或多個(gè)WebApplicationInitializer
類型,它們將被實(shí)例化(如果存在@Order
注釋或?qū)崿F(xiàn)Ordered
接口,則對(duì)其進(jìn)行排序)。然后,將調(diào)用每個(gè)實(shí)例WebApplicationInitializer.onStartup(ServletContext)
方法,并委派ServletContext
,以便每個(gè)實(shí)例都可以注冊(cè)和配置Servlet,例如Spring的DispatcherServlet
,listeners(如Spring的ContextLoaderListener
),或者其他Servlet API組件(如filters)。
下面是SpringServletContainerInitializer
的源碼:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
// WebApplicationInitializer實(shí)現(xiàn)如果存在`@Order`注釋或?qū)崿F(xiàn)`Ordered`接口,則對(duì)其進(jìn)行排序,故這里使用LinkedList
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
// 有時(shí)候,Servlet容器提供給我們的可能是無(wú)效的webAppInitializerClass
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
WebApplicationInitializer
下面我們來(lái)看下WebApplicationInitializer
API文檔的相關(guān)介紹。
在Servlet 3.0+環(huán)境中實(shí)現(xiàn)該接口,以便以編程方式配置ServletContext
,而不是以傳統(tǒng)的基于web.xml的方法。WebApplicationInitializer
SPI的實(shí)現(xiàn)將被SpringServletContainerInitializer
(它本身是由Servlet 3.0容器自動(dòng)引導(dǎo)的)自動(dòng)檢測(cè)到。
Example
基于XML的方式
大多數(shù)Spring用戶構(gòu)建Web應(yīng)用程序時(shí)需要注冊(cè)Spring的DispatcherServlet
。作為參考,通常在WEB-INF/web.xml
中按如下方式:
<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/dispatcher-config.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>
基于代碼的方式
DispatcherServlet
注冊(cè)邏輯與上述等效
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(appContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
作為上述的替代方法,您還可以繼承自org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
。
正如您所看到的,使用Servlet 3.0的ServletContext.addServlet
方法,我們注冊(cè)了一個(gè)DispatcherServlet
的實(shí)例。
這種風(fēng)格簡(jiǎn)單明了。不用關(guān)心處理init-params
等,只是普通的JavaBean風(fēng)格的屬性和構(gòu)造函數(shù)參數(shù)。在將其注入到DispatcherServlet
之前,您可以根據(jù)需要自由創(chuàng)建和使用Spring應(yīng)用程序上下文。
大多數(shù)Spring Web組件已經(jīng)更新,以支持這種注冊(cè)方式。你會(huì)發(fā)現(xiàn)DispatcherServlet
,FrameworkServlet
,ContextLoaderListener
和DelegatingFilterProxy
現(xiàn)在都支持構(gòu)造函數(shù)參數(shù)。Servlet 3.0 ServletContext API允許以編程方式設(shè)置init-params
,context-params
等。
完全基于代碼的配置方法
在上面的例子中,WEB-INF/web.xml
以WebApplicationInitializer
形式的代碼替換,但dispatcher-config.xml
配置仍然是基于XML的。WebApplicationInitializer
非常適合與Spring的基于代碼的@Configuration
類一起使用。以下示例演示了使用Spring的AnnotationConfigWebApplicationContext
代替XmlWebApplicationContext
進(jìn)行重構(gòu),以及使用用戶定義的@Configuration
類AppConfig
和DispatcherConfig
,而不是Spring XML文件。這個(gè)例子也超出了上面的例子來(lái)演示根應(yīng)用上下文的典型配置和ContextLoaderListener
的注冊(cè):
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
作為上述的替代方法,您還可以繼承自org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
。注意,WebApplicationInitializer
的實(shí)現(xiàn)類會(huì)被自動(dòng)檢測(cè)到。
Ordering WebApplicationInitializer execution
WebApplicationInitializer
實(shí)現(xiàn)類可以有選擇地在類上使用Spring的@Order
注解,也可以實(shí)現(xiàn)Spring的Ordered
接口。如果是這樣,初始化程序?qū)⒃谡{(diào)用之前排序。這為用戶提供了確保Servlet容器初始化順序的機(jī)制。使用此功能的情況很少,因?yàn)榈湫偷膽?yīng)用程序可能會(huì)將所有容器初始化集中在一個(gè)WebApplicationInitializer
中。
注意事項(xiàng)
web.xml版本
WEB-INF/web.xml
和WebApplicationInitializer
的使用不是互斥的; 例如,web.xml
可以注冊(cè)一個(gè)servlet
,而WebApplicationInitializer
可以注冊(cè)另一個(gè)。 Initializer甚至可以通過(guò)諸如ServletContext.getServletRegistration(String)
之類的方法來(lái)修改在web.xml
中執(zhí)行的注冊(cè)。但是,如果應(yīng)用程序中存在WEB-INF/web.xml
,則其版本屬性必須設(shè)置為"3.0"或更高,否則Servlet容器將忽略ServletContainerInitializer
的引導(dǎo)。
下面我們來(lái)看一組WebApplicationInitializer
的實(shí)現(xiàn)類:
繼承關(guān)系如下:
AbstractAnnotationConfigDispatcherServletInitializer
|
| —— AbstractDispatcherServletInitializer
|
| —— AbstractContextLoaderInitializer
|
| —— WebApplicationInitializer
AbstractAnnotationConfigDispatcherServletInitializer
org.springframework.web.WebApplicationInitializer
實(shí)現(xiàn)類的基類,用于注冊(cè)配置了@Configuration/@Component
注解標(biāo)記的配置類的DispatcherServlet
。
具體的實(shí)現(xiàn)類需要實(shí)現(xiàn)getRootConfigClasses()
和getServletConfigClasses()
以及getServletMappings()
方法。更多的方法由AbstractDispatcherServletInitializer
提供。這是使用基于Java配置應(yīng)用程序的首選方法。
下面是AbstractAnnotationConfigDispatcherServletInitializer
的源碼:
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
/**
* 創(chuàng)建要提供給`ContextLoaderListener`的**根應(yīng)用程序上下文**。
* <p>
* 返回的上下文委托給`ContextLoaderListener.ContextLoaderListener(WebApplicationContext)`,
* 并將作為`DispatcherServlet`應(yīng)用程序上下文的父上下文來(lái)建立。
* <p>
* 因此,它通常包含中間層服務(wù),數(shù)據(jù)源等。
* <p>
* 該方法創(chuàng)建一個(gè)`AnnotationConfigWebApplicationContext`,并為其提供由`getRootConfigClasses()`返回的配置類。如果`getRootConfigClasses()`返回Null,則不會(huì)創(chuàng)建根應(yīng)用上下文。
*/
@Override
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
/**
* 創(chuàng)建一個(gè)**Servlet應(yīng)用上下文**以提供給`DispatcherServlet`。
* <p>
* 返回的上下文被委托給Spring的`DispatcherServlet.DispatcherServlet(WebApplicationContext)`方法。
* <p>
* 因此,它通常包含控制器,視圖解析器,locale解析器和其他Web相關(guān)的bean。
* <p>
* 該實(shí)現(xiàn)創(chuàng)建一個(gè)`AnnotationConfigWebApplicationContext`,為其提供由`getServletConfigClasses()`返回的配置類。
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
/**
* 指定要提供給根應(yīng)用上下文的`@Configuration`或`@Component`注解標(biāo)記的配置類。
*/
protected abstract Class<?>[] getRootConfigClasses();
/**
* 指定要提供給Dispatcher Servlet應(yīng)用上下文的`@Configuration`或`@Component`注解標(biāo)記的配置類。
*/
protected abstract Class<?>[] getServletConfigClasses();
}
AbstractDispatcherServletInitializer
org.springframework.web.WebApplicationInitializer
實(shí)現(xiàn)的基類,在ServletContext
中注冊(cè)DispatcherServlet
。
具體的實(shí)現(xiàn)類需要實(shí)現(xiàn)createServletApplicationContext()
和getServletMappings()
方法,兩者都由registerDispatcherServlet(ServletContext)
調(diào)用。
進(jìn)一步的自定義可以通過(guò)重寫customizeRegistration(ServletRegistration.Dynamic)
方法來(lái)實(shí)現(xiàn)。
由于此類繼承了AbstractContextLoaderInitializer
抽象類,具體實(shí)現(xiàn)類也需要實(shí)現(xiàn)createRootApplicationContext()
來(lái)設(shè)置父級(jí)根應(yīng)用上下文。如果不需要根應(yīng)用上下文,createRootApplicationContext()
返回null即可。
下面看下源碼:
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
/**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
/**
* 針對(duì)給定的Servlet上下文注冊(cè)一個(gè)`DispatcherServlet`。
* <p>
* 該方法將創(chuàng)建一個(gè)名稱為由`getServletName()`指定的`DispatcherServlet`,
* 并使用從`createServletApplicationContext()`返回的Servlet應(yīng)用上下文對(duì)其進(jìn)行初始化,
* 并將其映射到從`getServletMappings()`返回的`pattern`。
* <p>
* 進(jìn)一步的自定義可以通過(guò)重寫`customizeRegistration(ServletRegistration.Dynamic)`或`createDispatcherServlet(WebApplicationContext)`來(lái)實(shí)現(xiàn)。
*
* @param servletContext 注冊(cè)servlet的上下文
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
// DispatcherServlet被注冊(cè)的名稱
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
// 創(chuàng)建Servlet應(yīng)用上下文,參見(jiàn)AbstractAnnotationConfigDispatcherServletInitializer的實(shí)現(xiàn)
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
// 將Servlet應(yīng)用上下文以委托給DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
/**
* 返回`DispatcherServlet`被注冊(cè)的名稱。 默認(rèn)值為DEFAULT_SERVLET_NAME。
*
* @see #registerDispatcherServlet(ServletContext)
*/
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}
/**
* 創(chuàng)建一個(gè)Servlet應(yīng)用上下文以提供給`DispatcherServlet`。
* 返回的上下文被委托給Spring的`DispatcherServlet.DispatcherServlet(WebApplicationContext)`。
* <p>
* 因此,它通常包含控制器,視圖解析器,locale解析器和其他Web相關(guān)的bean。
*
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract WebApplicationContext createServletApplicationContext();
/**
* 使用指定的`WebApplicationContext`Servlet應(yīng)用上下文創(chuàng)建`DispatcherServlet`(或由`FrameworkServlet`派生的其他類型的`dispatcher`)。
* <p>
* 注意:從4.2.3開始允許返回任意`FrameworkServlet`的子類。
* 以前,它必須要返回一個(gè)`DispatcherServlet`或其子類。
*/
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
/**
* Specify application context initializers to be applied to the servlet-specific
* application context that the {@code DispatcherServlet} is being created with.
*
* @see #createServletApplicationContext()
* @see DispatcherServlet#setContextInitializers
* @see #getRootApplicationContextInitializers()
* @since 4.2
*/
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
/**
* 指定`DispatcherServlet`的servlet映射 - 例如"/", "/app"等
*
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract String[] getServletMappings();
/**
* 指定添加到`ServletContext`,并映射到`DispatcherServlet`的過(guò)濾器。
*
* @return an array of filters or {@code null}
* @see #registerServletFilter(ServletContext, Filter)
*/
protected Filter[] getServletFilters() {
return null;
}
/**
* 將給定的過(guò)濾器添加到`ServletContext`并將其映射到`DispatcherServlet`,如下所示:
* <ul>
* <li>1. 根據(jù)具體的類型選擇默認(rèn)的過(guò)濾器名稱;
* <li>2. 異步支持是根據(jù)`asyncSupported`的返回值設(shè)置的;
* <li>3. 使用`dispatcher`類型為REQUEST,F(xiàn)ORWARD,INCLUDE和ASYNC(取決于asyncSupported的返回值)創(chuàng)建過(guò)濾器映射
* </ul>
* <p>
* 如果上面的默認(rèn)值不合適,重寫這個(gè)方法并直接用`ServletContext`注冊(cè)過(guò)濾器。
*
* @param servletContext the servlet context to register filters with
* @param filter the filter to be registered
* @return the filter registration
*/
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
// 獲取要注冊(cè)的filter的名稱
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {// 注冊(cè)失敗,名稱追加序號(hào)并重試
int counter = -1;
while (counter == -1 || registration == null) {
counter++;
registration = servletContext.addFilter(filterName + "#" + counter, filter);
Assert.isTrue(counter < 100,
"Failed to register filter '" + filter + "'." +
"Could the same Filter instance have been registered already?");
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
/**
* `DispatcherServlet`和通過(guò)`getServletFilters()`添加的所有過(guò)濾器的異步支持標(biāo)記位
*/
protected boolean isAsyncSupported() {
return true;
}
/**
* Optionally perform further registration customization once
* {@link #registerDispatcherServlet(ServletContext)} has completed.
*
* @param registration the {@code DispatcherServlet} registration to be customized
* @see #registerDispatcherServlet(ServletContext)
*/
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}
?? 補(bǔ)充:registration.addMappingForServletNames
為當(dāng)前FilterRegistration
所代表的Filter
添加一個(gè)具有指定Servlet
名稱和dispatcher
類型的過(guò)濾器映射。過(guò)濾器映射按照添加的順序進(jìn)行匹配。根據(jù)isMatchAfter
參數(shù)的值,來(lái)確定給定的過(guò)濾器映射在ServletContext
(從中獲取當(dāng)前FilterRegistration
)的任何聲明的過(guò)濾器映射之前或之后匹配。如果這個(gè)方法被多次調(diào)用,每個(gè)連續(xù)的調(diào)用都會(huì)作用于前者。
Parameters:
dispatcherTypes - 過(guò)濾器映射的
dispatcher
類型,如果要使用默認(rèn)的DispatcherType.REQUEST
,則為nullisMatchAfter - 如果給定的過(guò)濾器映射在任何聲明的過(guò)濾器映射之后匹配,則為true;如果在從中獲取此
FilterRegistration
的ServletContext
的任何聲明的過(guò)濾器映射之前匹配,則為falseservletNames - 過(guò)濾器映射的Servlet名稱
AbstractContextLoaderInitializer
WebApplicationInitializer
實(shí)現(xiàn)類的基類,在ServletContext
中注冊(cè)ContextLoaderListener
。需要由子類實(shí)現(xiàn)的唯一方法是createRootApplicationContext()
,它在registerContextLoaderListener(ServletContext)
中被調(diào)用。
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
/**
* 針對(duì)給定的`ServletContext`注冊(cè)一個(gè)`ContextLoaderListener`。
* `ContextLoaderListener`使用從`createRootApplicationContext()`方法返回的根應(yīng)用上下文進(jìn)行初始化。
*
* @param servletContext the servlet context to register the listener against
*/
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
/**
* Create the "<strong>root</strong>" application context to be provided to the
* {@code ContextLoaderListener}.
* <p>The returned context is delegated to
* {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will
* be established as the parent context for any {@code DispatcherServlet} application
* contexts. As such, it typically contains middle-tier services, data sources, etc.
* @return the root application context, or {@code null} if a root context is not
* desired
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
*/
protected abstract WebApplicationContext createRootApplicationContext();
/**
* Specify application context initializers to be applied to the root application
* context that the {@code ContextLoaderListener} is being created with.
* @since 4.2
* @see #createRootApplicationContext()
* @see ContextLoaderListener#setContextInitializers
*/
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
上面說(shuō)了這么多,簡(jiǎn)單總結(jié)下。
如圖所示:
ContextLoaderListener
上面提到了ContextLoaderListener
,下面來(lái)說(shuō)下這個(gè)類的作用。
ServletContextListener
首先看下javax.servlet.ServletContextListener
ServletContextListener為一個(gè)接口,聲明如下:
public interface ServletContextListener extends EventListener {
ServletContextListener
接口用于接收有關(guān)javax.servlet.ServletContext
生命周期改變的通知事件。
為了接收這些通知事件,其實(shí)現(xiàn)類有如下三種方式聲明:
- Web應(yīng)用程序的部署描述符(deployment descriptor of the web application,web.xml)中聲明
<listener>
- 使用
javax.servlet.annotation.WebListener
進(jìn)行注釋 - 通過(guò)
javax.servlet.ServletContext
上定義的javax.servlet.ServletContext.addListener
方法注冊(cè)
這個(gè)接口實(shí)現(xiàn)類的contextInitialized
方法按照它們的聲明順序被調(diào)用, 而contextDestroyed
方法則以相反的順序被調(diào)用。
import javax.servlet.ServletContextEvent;
import java.util.EventListener;
/**
* 用于接收有關(guān){@link javax.servlet.ServletContext}生命周期改變的通知事件的接口。
* <p>
* 為了接收這些通知事件,實(shí)現(xiàn)類必須
* 在Web應(yīng)用程序的部署描述符(deployment descriptor of the web application)中聲明、
* 使用{@link javax.servlet.annotation.WebListener}進(jìn)行注釋,
* 或者通過(guò){@link javax.servlet.ServletContext}上定義的{@link javax.servlet.ServletContext#addListener}方法注冊(cè)。
* <p>
* 這個(gè)接口實(shí)現(xiàn)類的{@link #contextInitialized}方法按照它們的聲明順序被調(diào)用,
* 而{@link #contextDestroyed}方法則以相反的順序被調(diào)用。
*
* @see ServletContextEvent
* @since Servlet 2.3
*/
public interface ServletContextListener extends EventListener {
/**
* 接收Web應(yīng)用程序初始化過(guò)程正在啟動(dòng)的通知。
* <p>
* 在Web應(yīng)用程序中的任何一個(gè)filter或servlet被初始化之前,所有ServletContextListeners都會(huì)收到上下文初始化的通知。
*
* @param sce 包含正在初始化的ServletContext的ServletContextEvent
*/
public void contextInitialized(ServletContextEvent sce);
/**
* 接收ServletContext即將關(guān)閉的通知。
* <p>
* 在任何ServletContextListeners收到上下文銷毀通知之前,所有servlet和filter都將被銷毀。
*
* @param sce 包含正在被銷毀的ServletContext的ServletContextEvent
*/
public void contextDestroyed(ServletContextEvent sce);
}
ContextLoaderListener
然后再看下ContextLoaderListener
聲明如下:
public class ContextLoaderListener
extends ContextLoader implements ServletContextListener {
引導(dǎo)listener啟動(dòng)和關(guān)閉Spring的根WebApplicationContext。它只是
簡(jiǎn)單地委托給ContextLoader
(繼承)以及ContextCleanupListener
(在contextDestroyed
方法中調(diào)用)。
該listener應(yīng)該在web.xml
中的org.springframework.web.util.Log4jConfigListener
(該類已標(biāo)記過(guò)時(shí))之后進(jìn)行注冊(cè)。
從Spring 3.1開始,ContextLoaderListener
支持通過(guò)ContextLoaderListener(WebApplicationContext)
構(gòu)造函數(shù)注入根Web應(yīng)用程序上下文, 從而允許在Servlet 3.0+環(huán)境中進(jìn)行編程式配置。
ContextLoaderListener()
創(chuàng)建一個(gè)新的ContextLoaderListener
, 它將基于"contextClass"和"contextConfigLocation" Servlet <context-params>
參數(shù)創(chuàng)建一個(gè)Web應(yīng)用程序上下文。
當(dāng)在web.xml
中聲明ContextLoaderListener
為<listener>
時(shí),通常使用這個(gè)構(gòu)造函數(shù)。
創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext
屬性WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
下, 并且當(dāng)在此listener上調(diào)用contextDestroyed
方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。
ContextLoaderListener(WebApplicationContext context)
用給定的應(yīng)用程序上下文創(chuàng)建一個(gè)新的ContextLoaderListener
。 這個(gè)構(gòu)造函數(shù)用于Servlet 3.0+,通過(guò)javax.servlet.ServletContext.addListener
API可以實(shí)現(xiàn)基于實(shí)例的listeners注冊(cè)。
上下文可能或尚未刷新。 如果
- (a)是ConfigurableWebApplicationContext的實(shí)現(xiàn).
- (b)尚未刷新(推薦的方法)
滿足上面兩個(gè)條件,則會(huì)發(fā)生以下情況:
- 如果給定的上下文還沒(méi)有被分配一個(gè)id,則將被分配一個(gè)
-
ServletContext
和ServletConfig
對(duì)象將被委托給應(yīng)用程序上下文 -
customizeContext
將被調(diào)用 - 任何通過(guò)"contextInitializerClasses"
init-param
指定的ApplicationContextInitializers
將被應(yīng)用。 -
refresh()
將被調(diào)用
如果上下文已經(jīng)被刷新或者沒(méi)有實(shí)現(xiàn)ConfigurableWebApplicationContext
,上述任何一種情況都不會(huì)發(fā)生。
創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext
屬性WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
下, 并且當(dāng)在此listener上調(diào)用contextDestroyed
方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ContextCleanupListener;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* 引導(dǎo)listener啟動(dòng)和關(guān)閉Spring的根{@link WebApplicationContext}。
* 簡(jiǎn)單地委托給{@link ContextLoader}以及{@link ContextCleanupListener}。
* <p>
* 該listener應(yīng)該在{@code web.xml}中的{@link org.springframework.web.util.Log4jConfigListener}之后進(jìn)行注冊(cè),如果使用它的話。
* <p>
* 從Spring 3.1開始,{@code ContextLoaderListener}支持通過(guò){@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)}構(gòu)造函數(shù)注入根Web應(yīng)用程序上下文,
* 從而允許在Servlet 3.0+環(huán)境中進(jìn)行編程式配置。
*
* @author Juergen Hoeller
* @author Chris Beams
* @see #setContextInitializers
* @see org.springframework.web.WebApplicationInitializer
* @see org.springframework.web.util.Log4jConfigListener
* @since 17.02.2003
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
/**
* 創(chuàng)建一個(gè)新的{@code ContextLoaderListener},
* 它將基于"contextClass"和"contextConfigLocation" Servlet {@code <context-params>}參數(shù)創(chuàng)建一個(gè)Web應(yīng)用程序上下文。
* 請(qǐng)參閱{@link ContextLoader}父類文檔以獲取詳細(xì)信息。
* <p>
* 當(dāng)在{@code web.xml}中聲明{@code ContextLoaderListener}為{@code <listener>}時(shí),通常使用這個(gè)構(gòu)造函數(shù)。
* <p>
* 創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext屬性{@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}下,
* 并且當(dāng)在此listener上調(diào)用{@link #contextDestroyed}方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。
*
* @see ContextLoader
* @see #ContextLoaderListener(WebApplicationContext)
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener() {
}
/**
* 用<strong>給定的應(yīng)用程序上下文</strong>創(chuàng)建一個(gè)新的{@code ContextLoaderListener}。
* 這個(gè)構(gòu)造函數(shù)用于Servlet 3.0+,通過(guò){@link javax.servlet.ServletContext#addListener} API可以實(shí)現(xiàn)基于實(shí)例的listeners注冊(cè)。
* <p>
* 上下文可能或尚未{@linkplain org.springframework.context.ConfigurableApplicationContext#refresh() 刷新}。
* 如果(a)是{@link ConfigurableWebApplicationContext}的實(shí)現(xiàn),并且(b)<strong>尚未刷新</strong>(推薦的方法),則會(huì)發(fā)生以下情況:
* <ul>
* <li>如果給定的上下文還沒(méi)有被分配一個(gè){@linkplain org.springframework.context.ConfigurableApplicationContext#setId id},則將被分配一個(gè)
* <li>{@code ServletContext}和{@code ServletConfig}對(duì)象將被委托給應(yīng)用程序上下文
* <li>{@link #customizeContext}將被調(diào)用
* <li>任何通過(guò)"contextInitializerClasses" init-param指定的{@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s將被應(yīng)用。
* <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()}將被調(diào)用
* </ul>
* <p>
* 如果上下文已經(jīng)被刷新或者沒(méi)有實(shí)現(xiàn){@code ConfigurableWebApplicationContext},上述任何一種情況都不會(huì)發(fā)生。
* <p>
* 創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext屬性{@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}下,
* 并且當(dāng)在此listener上調(diào)用{@link #contextDestroyed}方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。
*
* @param context the application context to manage
* @see #contextInitialized(ServletContextEvent)
* @see #contextDestroyed(ServletContextEvent)
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ContextLoader
聲明如下:
public class ContextLoader {
為給定的servlet上下文初始化Spring的Web應(yīng)用程序上下文,
使用構(gòu)造時(shí)提供的應(yīng)用程序上下文, 或者根據(jù)"contextClass" 和"contextConfigLocation" <context-param>
創(chuàng)建一個(gè)新的。
方式1 通過(guò)web.xml配置
在web.xml
<context-param>
中查找"contextClass"參數(shù)以指定上下文類型, 如果未找到則使用org.springframework.web.context.support.XmlWebApplicationContext
。
默認(rèn),指定的任何上下文類都需要實(shí)現(xiàn)ConfigurableWebApplicationContext
接口。
處理web.xml
<context-param>
中的"contextConfigLocation"參數(shù), 并將其值傳遞給上下文實(shí)例,其可能為多個(gè)配置文件路徑(用逗號(hào)和空格分隔)。例如,"WEB-INF/applicationContext1.xml, WEB-INF/applicationContext2.xml"。 還支持Ant樣式的路徑模式,例如,"WEB-INF/Context.xml,WEB-INF/spring.xml" 或者 "WEB-INF/*/Context.xml"。 如果沒(méi)有明確指定,上下文實(shí)現(xiàn)應(yīng)該使用默認(rèn)位置(對(duì)于XmlWebApplicationContext,"/WEB-INF/applicationContext.xml")。
注意:在多個(gè)配置文件的情況下,后面的bean定義將覆蓋之前加載的文件中的定義,至少在使用Spring的默認(rèn)ApplicationContext實(shí)現(xiàn)時(shí)是這樣。 這可以用來(lái)通過(guò)一個(gè)額外的XML文件重寫某些bean定義。
方式2 通過(guò)Java Config配置
從Spring 3.1開始,ContextLoader
支持通過(guò)ContextLoader(WebApplicationContext)
構(gòu)造函數(shù)注入根Web應(yīng)用程序上下文, 從而允許在Servlet 3.0+環(huán)境中進(jìn)行編程式配置。 有關(guān)使用示例,請(qǐng)參閱org.springframework.web.WebApplicationInitializer
。
總結(jié)
介紹了這么多類,這里做一個(gè)簡(jiǎn)短的總結(jié)。
Servlet3.0+規(guī)范后,允許Servlet,F(xiàn)ilter,Listener不必聲明在web.xml中,而是以Java Config的方式編碼存在,實(shí)現(xiàn)容器的零配置。
ServletContainerInitializer
啟動(dòng)容器時(shí)負(fù)責(zé)加載相關(guān)配置。
package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
Servlet容器啟動(dòng)時(shí)會(huì)自動(dòng)掃描當(dāng)前服務(wù)中ServletContainerInitializer
的實(shí)現(xiàn)類。并調(diào)用其onStartup
方法,其參數(shù)Set<Class<?>> c
可通過(guò)在實(shí)現(xiàn)類上聲明注解javax.servlet.annotation.HandlesTypes(xxx.class)
注解自動(dòng)注入。
@HandlesTypes
會(huì)自動(dòng)掃描項(xiàng)目中所有的xxx.class
的實(shí)現(xiàn)類,并將其全部注入Set。
Spring為其提供了一個(gè)實(shí)現(xiàn)類:SpringServletContainerInitializer
類。通過(guò)查看源碼可以看出,WebApplicationInitializer
才是我們需要關(guān)心的接口。
我們只需要將相應(yīng)的Servlet
,Filter
,Listener
等硬編碼到該接口的實(shí)現(xiàn)類中即可。
Spring為我們提供了一些WebApplicationInitializer的抽象類,我們只需要繼承并按需修改即可。常見(jiàn)的實(shí)現(xiàn)類有:
AbstractAnnotationConfigDispatcherServletInitializer
AbstractDispatcherServletInitializer
AbstractContextLoaderInitializer
AbstractHttpSessionApplicationInitializer
對(duì)于一個(gè)web應(yīng)用,其部署在web容器中,web容器提供其一個(gè)全局的上下文環(huán)境,這個(gè)上下文就是ServletContext
,其為后面的Spring IoC容器提供宿主環(huán)境;
其次,在web.xml
中會(huì)提供有ContextLoaderListener
。
在web容器啟動(dòng)時(shí),會(huì)觸發(fā)容器初始化事件,此時(shí)ContextLoaderListener
會(huì)監(jiān)聽到這個(gè)事件,其contextInitialized
方法會(huì)被調(diào)用。
package org.springframework.web.context;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
在contextInitialized
這個(gè)方法中,spring會(huì)初始化一個(gè)啟動(dòng)上下文,這個(gè)上下文被稱為根上下文,即WebApplicationContext
,這是一個(gè)接口類。
確切的說(shuō),其實(shí)現(xiàn)類是XmlWebApplicationContext
(基于web.xml配置)或者上面提及的AnnotationConfigWebApplicationContext
(基于JavaConfig配置)。
這個(gè)就是Spring IoC容器,其對(duì)應(yīng)的自定義的配置由web.xml
中的<context-param>
標(biāo)簽指定。
在這個(gè)IoC容器初始化完畢后,TODO Spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
為屬性Key,將其存儲(chǔ)到ServletContext中,便于獲取。
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
}
public class ContextLoader {
...
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
// 以`WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`為屬性Key,將根上下文存儲(chǔ)到ServletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
...
}
...
}
再次,ContextLoaderListener
監(jiān)聽器初始化完畢后,開始初始化web.xml
中配置的Servlet,這個(gè)servlet可以配置多個(gè),以最常見(jiàn)的DispatcherServlet
為例,
這個(gè)servlet實(shí)際上是一個(gè)標(biāo)準(zhǔn)的前端控制器,用以轉(zhuǎn)發(fā)、匹配、處理每個(gè)servlet請(qǐng)求。
DispatcherServlet
上下文在初始化的時(shí)候會(huì)建立自己的IoC上下文,用以持有spring mvc相關(guān)的bean。
在建立DispatcherServlet
自己的IoC上下文時(shí),TODO 會(huì)利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
先從ServletContext
中獲取之前的根上下文(即WebApplicationContext
)作為自己上下文的parent上下文。
有了這個(gè)parent上下文之后,再初始化自己持有的上下文。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
protected WebApplicationContext initWebApplicationContext() {
...
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);
}
...
}
}
這個(gè)DispatcherServlet
初始化自己上下文的工作在其initStrategies
方法中可以看到,大概的工作就是初始化處理器映射、視圖解析等。
public class DispatcherServlet extends FrameworkServlet {
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
}
這個(gè)servlet自己持有的上下文默認(rèn)實(shí)現(xiàn)類也是XmlWebApplicationContext
,當(dāng)然也可以基于JavaConfig方式配置AnnotationConfigWebApplicationContext
。
初始化完畢后,TODO spring以與servlet的名字相關(guān)(此處不是簡(jiǎn)單的以servlet名為Key,而是通過(guò)一些轉(zhuǎn)換)的屬性為屬性Key,也將其存到ServletContext中,以便后續(xù)使用。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
protected WebApplicationContext initWebApplicationContext() {
...
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
...
}
/**
* Return the ServletContext attribute name for this servlet's WebApplicationContext.
* <p>The default implementation returns
* {@code SERVLET_CONTEXT_PREFIX + servlet name}.
* @see #SERVLET_CONTEXT_PREFIX
* @see #getServletName
*/
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
}
這樣每個(gè)servlet就持有自己的上下文,即擁有自己獨(dú)立的bean空間,同時(shí)各個(gè)servlet共享相同的bean,即根上下文定義的那些bean。