1. Servlet與SpringMVC之間的關(guān)係
Spring的MVC是基於Servlet功能實(shí)現(xiàn)的,經(jīng)過實(shí)現(xiàn)Servlet接口的DispatcherServlet來封裝其核心功能實(shí)現(xiàn)。
2. ServletContainerInitializer接口
在web容器啓動(dòng)時(shí)會(huì)作一些初始化的工做,例如註冊(cè)servlet或者filtes等,servlet規(guī)範(fàn)中經(jīng)過ServletContainerInitializer實(shí)現(xiàn)此功能。
每一個(gè)框架, 比如Spring,要使用ServletContainerInitializer就必須在對(duì)應(yīng)的jar包的META-INF/services 目錄建立一個(gè)名爲(wèi)javax.servlet.ServletContainerInitializer的文件,文件內(nèi)容指定具體的ServletContainerInitializer實(shí)現(xiàn)類。(JAVA的SPI特性)
案例演示:服務(wù)器
@HandlesTypes(value = MyHandlesType.class)//該註解聲明的類,會(huì)被注入到set中,若是沒有合適的類,set爲(wèi)null,這個(gè)動(dòng)作也是Servlet 容器做的(如tomcat)
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* @param set 感興趣類型 也就是MyHandlesType 全部子類型
* @param servletContext
* @throws ServletException
*/
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
// 1.打印全部感興趣的類型
for (Class<?> c : set) {
System.out.println(c);
}
// 2.servletContext 手動(dòng)註冊(cè)過濾器、servlet、監(jiān)聽器
ServletRegistration.Dynamic payServlet = servletContext.addServlet("payServlet", new PayServlet());
payServlet.addMapping("/pay");
}
}
以前傳統(tǒng)的Servlet開發(fā),我也需要提供web.xml到Tomcat中,在web.xml中指定Servlet,以及servlet mapping,如:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- spring mvc配置開始 -->
<servlet>
<servlet-name>let'sGo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>let'sGo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- spring mvc配置結(jié)束 -->
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
</web-app>
springmvc是如何實(shí)現(xiàn)不需要web.xml配置,靠的就是ServletContainerInitialize。
3. SpringServletContainerInitializer的作用
看下SpringServletContainerInitializer的源碼:
@HandlesTypes(WebApplicationInitializer.class) // Servlet容器會(huì)根據(jù)@HandlesTypes找到所有注解中的類的實(shí)現(xiàn),也就是所有WebApplicationInitializer.class的實(shí)現(xiàn)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//所有WebApplicationInitializer.class的實(shí)現(xiàn)都會(huì)被放在這個(gè)Set中
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(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);
// 調(diào)用所有WebApplicationInitializer實(shí)現(xiàn)類的onStartUp()函數(shù)
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
- @HandlesTypes(WebApplicationInitializer.class) // Servlet容器會(huì)根據(jù)@HandlesTypes找到所有注解中的類的實(shí)現(xiàn),也就是所有WebApplicationInitializer.class的實(shí)現(xiàn)
- 所有WebApplicationInitializer.class的實(shí)現(xiàn)都會(huì)被放在這個(gè)Set中
- 調(diào)用所有WebApplicationInitializer實(shí)現(xiàn)類的onStartUp()函數(shù),這樣Spring框架就可以在WebApplicationInitializer實(shí)現(xiàn)類里做一些初始化等操作(如創(chuàng)建DispatcherServlet,添加Filter)
SpringServletContainerInitializer通過實(shí)現(xiàn)ServletContainerInitializer將自身并入到Servlet容器的生命周期中, 并通過自身定義的WebApplicationInitializer將依賴于Spring框架的系統(tǒng)初始化需求與Servlet容器解耦. 即依賴于spring的系統(tǒng)可以通過實(shí)現(xiàn)WebApplicationInitializer來實(shí)現(xiàn)自定義的初始化邏輯. 而不需要去實(shí)現(xiàn)ServletContainerInitializer
4. WebApplicationInitializer
SpringServletContainerInitializer會(huì)調(diào)用所有實(shí)現(xiàn)了WebApplicationInitializer接口的實(shí)現(xiàn)類。Spring提供了很多實(shí)現(xiàn)類用來做初始化操作。
5. AbstractAnnotationConfigDispatcherServletInitializer
SpringMVC提供的AbstractAnnotationConfigDispatcherServletInitializer這個(gè)抽象類間接實(shí)現(xiàn)了WebApplicationInitializer接口,如下圖所示。所以我們只需要自己寫一個(gè)類,繼承AbstractAnnotationConfigDispatcherServletInitializer這個(gè)抽象類,servlet容器啟動(dòng)的時(shí)候就會(huì)調(diào)用我們寫的實(shí)現(xiàn)類的父類里的onStartUp()函數(shù)。

具體其實(shí)就是AbstractDispatcherServletInitializer.class的onStartUp()函數(shù):
//org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 創(chuàng)建Spring容器,ServletApplicationContext
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// 創(chuàng)建DispatcherServlet,并把Spring容器當(dāng)做參數(shù)穿進(jìn)去
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 將創(chuàng)建好的DispatcherServlet傳進(jìn)Servlet容器里
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("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);}
這樣的話我們就通過代碼的方式做了如下操作:
- 創(chuàng)建了Spring容器---ServletApplicationContext
- 創(chuàng)建了Servlet---DispatcherServlet,并把Spring容器放進(jìn)了Servlet中
- 把Servlet添加到Servlet 容器中。
6. 上述操作,通過web.xml也能做到,不過Servlet規(guī)范3.0以后,支持了代碼的方式, 我們看一下通過web.xml執(zhí)行上述操作的例子:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- spring mvc配置開始 -->
<servlet>
<servlet-name>let'sGo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>let'sGo</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- spring mvc配置結(jié)束 -->
<welcome-file-list>
<welcome-file>index</welcome-file>
</welcome-file-list>
</web-app>
- Servlet容器會(huì)根據(jù)Servlet規(guī)范讀取web.xml文件
- 讀到<servlet>標(biāo)簽后就創(chuàng)建<servlet>里標(biāo)記的Servlet——這里是DispatcherServlet,這里創(chuàng)建調(diào)用的是無參構(gòu)造函數(shù),跟前面通過代碼實(shí)現(xiàn)的時(shí)候創(chuàng)建DispatcherServlet用的是帶參數(shù)的構(gòu)造函數(shù)不一樣,因?yàn)檫@里沒有事先創(chuàng)建好的Spring容器(Servlet WebApplicationContext)。
- 所以在new DispatcherServlet的過程中,判斷當(dāng)前沒有現(xiàn)成的Spring容器(Servlet WebApplicationContext),就會(huì)自己創(chuàng)建一個(gè)Spring容器,代碼如下
//org.springframework.web.servlet.FrameworkServlet,它是DispatcherServlet的父類
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 看下構(gòu)造函數(shù)中是否傳進(jìn)來了 webApplicationContext,用web.xml的情況下,webApplicationContext是null
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);
}
}
}
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
// 看下Servlet容器里有沒有現(xiàn)成的 webApplicationContext
wac = findWebApplicationContext();
}
// 前面幾步都沒拿到,自己創(chuàng)建一個(gè)webApplicationContext,使用web.xml的情況走的就是這里
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
......
}
- 看下構(gòu)造函數(shù)中是否傳進(jìn)來了 webApplicationContext,用web.xml的情況下,webApplicationContext是null
- 看下Servlet容器里有沒有現(xiàn)成的 webApplicationContext
- 前面幾步都沒拿到,自己創(chuàng)建一個(gè)webApplicationContext,使用web.xml的情況走的就是這里
- 然后還是DispatcherServlet,它會(huì)把自己添加到Servlet容器中。
// org.springframework.web.servlet.FrameworkServlet
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}