前言

spring mvc動態註冊DispatcherServlet的流程是很簡單的(拋卻實現細節),所以我會以盡量簡單的描述來說明spring mvc在擺脫web.xml的情況下,動態的註冊DispatcherServlet流程,後面會寫一個demo來模擬註冊過程。

註冊流程

首先,聲明2個關鍵的知識點:

1. 動態註冊servlet是servlet3的新特性,即servlet 3.0以後才支持動態註冊servlet

在3.0中,ServletContext中也新增了幾個介面,標藍框的忽視,那個是4.0以後才支持

2. javax.servlet.ServletContainerInitializer

這是servlet容器初始化的一個核心介面。

public interface ServletContainerInitializer {

public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}

它的實現類及子類,當類路徑下配置正確的元數據文件,可以在容器初始化的時候自動調用實現類的onStartup方法,另外需要配合註解javax.servlet.annotation.HandlesTypes使用,不明白這幾句話的意思的話,後面的示例代碼中有使用說明。

既然servlet容器會調用ServletContainerInitializer的onStartup方法,這也是spring mvc動態註冊DispatcherServlet的入口,那就看spring mvc對這個介面的關鍵實現:

//這個註解聲明了 WebApplicationInitializer類,
//那麼servlet容器會把類路徑下所有WebApplicationInitializer的實現類及其子類添加到onStratup方法的第一個參數中
// servlet容器會調用這個類的onStartup方法
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {

List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// 將不是介面或抽象類的WebApplicationInitializer實現類及子類實例化
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;
}

AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
// 調用上面實例化的所有WebApplicationInitializer實例的onStartup方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}

}

servlet容器會自動調用這個類的onStartup方法,但是首先要告訴servlet容器這個類的存在,上文提到了一個元數據文件,它在spring-web包下,這個文件的要求是這樣的:

1. 在類路徑的WEB-INF/services目錄下

2. 名字是javax.servlet.ServletContainerInitializer

3. 內容是實現類的全路徑

如下:

看代碼里我加的注釋,就知道在servlet容器初始化的時候,會調用所有WebApplicationInitializer實例的onStartup方法,接下來看下WebApplicationInitializer的一個實現類AbstractContextLoaderInitializer的子類的部分代碼:AbstractDispatcherServletInitializer

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
// 註冊DispatcherServlet
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");

WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");

DispatcherServlet 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已經是註冊到Servlet容器了。

接下來自己模擬一下這個流程,平常如果需要動態註冊servlet也可以考慮這種實現

代碼示例

也定義個WebApplicationInitializer介面

package com.xuxd.spring;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

public interface WebApplicationInitializer {

void onStartup(ServletContext servletContext) throws ServletException;
}
定義WebApplicationInitializer介面的實現,用來註冊servlet

package com.xuxd.spring;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class DispatcherServletInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerDispatcherServlet(servletContext);
}

protected void registerDispatcherServlet(ServletContext servletContext) throws ServletException {
DispatcherServlet dispatcherServlet = servletContext.createServlet(DispatcherServlet.class);
ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
dynamic.addMapping("/");
System.out.println("register dispatcherServlet");

}
}
spring mvc的DispatcherServlet是spring mvc的核心,負責spring mvc的核心組件的管理及請求分發,這裡只是個示例:

package com.xuxd.spring;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import java.io.IOException;
import java.io.PrintWriter;

public class DispatcherServlet extends HttpServlet {

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/plain");
// 默認採用ISO-8859-1編碼
PrintWriter printWriter = res.getWriter();
printWriter.print("hello, dispatcherServlet");
}
}
這裡是入口,實現ServletContainerInitializer介面

package com.xuxd.spring;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {

List<WebApplicationInitializer> initializers = new LinkedList<>();
if (c != null) {
for (Class<?> clazz :
c) {
try {
initializers.add((WebApplicationInitializer) clazz.newInstance());
} catch (Exception e) {
throw new ServletException(e);
}
}
} else {
System.out.println("c is null");
}

for (WebApplicationInitializer initializer :
initializers) {
initializer.onStartup(ctx);
}
}
}

類路徑下配置這個文件

我這個工程本來就是一個基本的servlet/jsp工程,pom也沒什麼依賴,主要是一個servlet api的依賴:

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>

配置一下tomcat,運行

瀏覽器隨便發一個請求,響應如下:

推薦閱讀:

相关文章