本文通过阅读源码,分析Spring MVC和Spring Boot注册DispatcherServlet的流程。
概述
DispatcherServlet是Spring MVC的核心组件,他会被注册到Servlet Web容器(例如tomcat)中,接收/*请求,然后做请求分发,调用Controller方法处理请求,接收响应返回给客户端。
在早期版本的Spring MVC中,使用web.xml文件配置DispatcherServlet:
<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>
这是Servlet规范的要求,所有的Java Web服务都需要这样配置,进行Servlet注册。
这种方式我们不做分析,我们要重点分析的是:
- 注解驱动的Spring MVC注册DispatcherServlet的方式
- Spring Boot注册DispatcherServlet的方式
Spring MVC注册DispatcherServlet
注解驱动的Spring MVC
注解驱动的Spring MVC要求开发者编写一个WebApplicationInitializer实现类:
/*
* Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext
* programmatically -- as opposed to (or possibly in conjunction with) the traditional
* web.xml-based approach.
* Implementations of this SPI will be detected automatically by SpringServletContainerInitializer,
* which itself is bootstrapped automatically by any Servlet 3.0 container.
*/
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
实现类这样写:
// 不需要直接实现WebApplicationInitializer接口
// Spring提供了抽象类,开发者继承抽象类实现抽象方法即可
public class SpringMvcInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{AppConfig.class, MybatisConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
这样把工程部署到tomcat之类的Servlet容器之后,即可启动Spring MVC并注册DispatcherServlet。
那么这是如何实现的,这得从Servlet 3.0新特性说起。
Servlet 3.0和ServletContainerInitializer接口
在Servlet 3.0中,提供了一个ServletContainerInitializer接口:
// Since: Servlet 3.0
public interface ServletContainerInitializer {
/**
* Receives notification during startup of a web application of the classes within the
* web application that matched the criteria defined via the
* javax.servlet.annotation.HandlesTypes annotation.
* Params:
* c – The (possibly null) set of classes that met the specified criteria
* ctx – The ServletContext of the web application in which the classes were discovered
*/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
这个接口的用途:
ServletContainerInitializers (SCIs) are registered via an entry in the file META-INF/services/javax.servlet.ServletContainerInitializer that must be included in the JAR file that contains the SCI implementation.
SCIs register an interest in annotations (class, method or field) and/or types via the javax.servlet.annotation.HandlesTypes annotation which is added to the class.
----------
ServletContainerInitializers通过SPI方式向Servlet容器注入实现。
在Servlet容器启动时,会通过SPI获取到ServletContainerInitializer的实现类实例,然后调用onStartup方法。
ServletContainerInitializer的实现类通过HandlesTypes注册自己需要的类,在调用onStartup方法时会通过Set<Class<?>> c参数传递给实现类。
Tomcat对ServletContainerInitializer的支持
在tomcat中,使用SPI加载所有的ServletContainerInitializer实现类对象,并将其注册到Context中,等待调用,加载SPI的代码在org.apache.catalina.startup.ContextConfig的processServletContainerInitializers方法中:
protected void processServletContainerInitializers() {
List<ServletContainerInitializer> detectedScis;
try {
// 使用SPI加载ServletContainerInitializer实现
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
ok = false;
return;
}
for (ServletContainerInitializer sci : detectedScis) {
initializerClassMap.put(sci, new HashSet<Class<?>>());
// 解析HandlesTypes注解
HandlesTypes ht;
try {
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
continue;
}
if (ht == null) {
continue;
}
Class<?>[] types = ht.value();
if (types == null) {
continue;
}
for (Class<?> type : types) {
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
if (scis == null) {
scis = new HashSet<>();
typeInitializerMap.put(type, scis);
}
scis.add(sci);
}
}
}
然后在StandardContext的startInternal获取到所有的ServletContainerInitializer并调用onStartup方法:
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(), getServletContext());
} catch (ServletException e) {
ok = false;
break;
}
}
Spring MVC和SpringServletContainerInitializer类
Spring MVC中SpringServletContainerInitializer类实现了ServletContainerInitializer接口接口:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
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 {
// 创建WebApplicationInitializer实现类对象
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
} catch (Throwable ex) {
throw new ServletException("...");
}
}
}
}
if (initializers.isEmpty()) {
return;
}
// 排序调用WebApplicationInitializer
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
Spring MVC使用spring-web-5.2.12.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer文件做SPI配置:
org.springframework.web.SpringServletContainerInitializer
AbstractAnnotationConfigDispatcherServletInitializer类
一个抽象类:
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
// ...
}
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
WebApplicationContext servletAppContext = createServletApplicationContext();
// 创建DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext
.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
// 添加url mapping
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
}
小结
- Spring MVC使用SPI注册SpringServletContainerInitializer类
- Tomcat在启动时使用SPI加载ServletContainerInitializer的所有实现,包括SpringServletContainerInitializer类,并且解析@HandlesTypes注解,解析该注解声明的所有Class集
- Tomcat启动StandardContext时,调用所有的ServletContainerInitializer的onStartup方法,并传递Class集参数
- SpringServletContainerInitializer类onStartup方法,实例化所有的WebApplicationInitializer实现类,调用onStartup方法,进行应用初始化,在这个过程中注册DispatcherServlet
Spring Boot注册DispatcherServlet
DispatcherServletAutoConfiguration自动装配类
Auto-configuration for the Spring DispatcherServlet.
Should work for a standalone application where an embedded web server is already present and also for a deployable application using SpringBootServletInitializer.
这个类会自动装配DispatcherServlet对象:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet
.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
dispatcherServlet,
// spring.mvc.servlet.path参数
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// spring.mvc.servlet.loadOnStartup参数
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
DispatcherServletRegistrationBean实现了ServletContextInitializer接口,这个接口会在web服务器启动阶段随着TomcatStarter一起启动,这个后续会介绍。
请记住DispatcherServletRegistrationBean实现类,后续还会用到。
onRefresh
Spring Boot使用ServletWebServerApplicationContext作为ApplicationContext的实现,在onRefresh阶段创建Servlet web服务器。
ServletWebServerApplicationContext类
protected void onRefresh() {
super.onRefresh();
try {
// 创建Servlet web服务器
createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 创建WebServer
// 在默认的配置下,这里获取到的是TomcatServletWebServerFactory对象
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
// other code lines
} else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
} catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 从容器获取所有的ServletContextInitializer对象,调用onStartup方法做初始化
// 自然也会调用DispatcherServletRegistrationBean的onStartup方法
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
TomcatServletWebServerFactory创建WebServer
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 创建Context
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
// 前面代码不记录了,只记录重点内容
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
// 配置Context
configureContext(context, initializersToUse);
postProcessContext(context);
}
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// TomcatStarter是ServletContainerInitializer接口的实现类
// 在前面的章节,介绍过ServletContainerInitializer的onStart方法会在StandardContext的启动阶段调用
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
// 把starter添加到TomcatEmbeddedContext等待启动时调用
context.addServletContainerInitializer(starter, NO_CLASSES);
// 其余代码
}
TomcatStarter类:
class TomcatStarter implements ServletContainerInitializer {
private final ServletContextInitializer[] initializers;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
// 调用ServletContextInitializer
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
} catch (Exception ex) {
this.startUpException = ex;
}
}
// ...
}
DispatcherServletRegistrationBean实现类
实现了ServletContextInitializer接口,但是onStart是在其抽象父类中实现的。
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE;
private boolean enabled = true;
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
return;
}
// 抽象方法子类实现
register(description, servletContext);
}
}
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
@Override
protected final void register(String description, ServletContext servletContext) {
// 抽象方法子类实现
// 将Servlet添加到web服务器的Context中
D registration = addRegistration(description, servletContext);
if (registration == null) {
return;
}
// 子类实现
configure(registration);
}
}
public class ServletRegistrationBean<T extends Servlet> extends
DynamicRegistrationBean<ServletRegistration.Dynamic> {
private static final String[] DEFAULT_MAPPINGS = { "/*" };
private T servlet;
private Set<String> urlMappings = new LinkedHashSet<>();
private boolean alwaysMapUrl = true;
private int loadOnStartup = -1;
private MultipartConfigElement multipartConfig;
@Override
protected ServletRegistration.Dynamic addRegistration(
String description,
ServletContext servletContext) {
String name = getServletName();
// 把servlet添加到Context
// 这个servlet就是DispatcherServlet对象
return servletContext.addServlet(name, this.servlet);
}
@Override
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
}
if (!ObjectUtils.isEmpty(urlMapping)) {
// 添加url mapping
registration.addMapping(urlMapping);
}
registration.setLoadOnStartup(this.loadOnStartup);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
小结
- DispatcherServletAutoConfiguration自动装配DispatcherServlet和DispatcherServletRegistrationBean组件,DispatcherServletRegistrationBean实现了ServletContextInitializer接口,onStart方法会在web服务器启动时被调用用于初始化Context
- ServletWebServerApplicationContext在onRefresh阶段创建web服务器
- 创建web服务器过程中会创建TomcatStarter并添加到TomcatEmbeddedContext中,他实现了ServletContainerInitializer接口,onStart方法会在StandardContext的启动阶段调用
- web服务器启动,调用TomcatStarter的onStart方法。过程中获取到Spring容器中的所有ServletContextInitializer实现,包括DispatcherServletRegistrationBean对象
- 调用DispatcherServletRegistrationBean对象的onStart方法,向web服务器注册DispatcherServlet并配置urlMapping等
通过源码分析,我们深入了解了Spring MVC和Spring Boot注册DispatcherServlet的流程,这只是开始,后续我们将分析DispatcherServlet初始化HandlerMapping和分发请求的原理。