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> 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> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List initializers = new LinkedList();
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> c, ServletContext ctx) throws ServletException {
List 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的依赖:
javax.servlet
javax.servlet-api
4.0.0
provided
配置一下tomcat,运行
浏览器随便发一个请求,响应如下:
正是我的DispatcherServlet返回的内容。