DispatcherServlet
注册到Spring
容器在一个Springboot Web
应用中,Spring MVC
的DispatcherServlet
是通过Springboot autoconfigure
机制注册进来的。
在jar
包spring-boot-autoconfigure-xxx.jar
的org.springframework.boot.autoconfigure.web
下面存在autoconfigure
类 DispatcherServletAutoConfiguration
,通过该类 :
DispatcherServlet
被作为一个单例bean
被定义和注册到容器;ServletRegistrationBean bean
用来添加该DispatcherServlet bean
到ServletContext
;其具体实现逻辑如下所示 :
/*
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/*
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME =
"dispatcherServletRegistration";
@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
private final WebMvcProperties webMvcProperties;
public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
this.webMvcProperties = webMvcProperties;
}
// 定义一个Bean到Spring IoC容器,名称为dispatcherServlet,对应为DispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
private final ServerProperties serverProperties;
private final WebMvcProperties webMvcProperties;
private final MultipartConfigElement multipartConfig;
public DispatcherServletRegistrationConfiguration(
ServerProperties serverProperties, WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
this.serverProperties = serverProperties;
this.webMvcProperties = webMvcProperties;
this.multipartConfig = multipartConfigProvider.getIfAvailable();
}
// 定义另外一个bean ServletRegistrationBean,名字为dispatcherServletRegistration,
// 这个ServletRegistrationBean有点特殊,它不是个普通意义上的Bean,它实现了Spring SCI
// 接口,而对于此类实现了Spring SCI接口的bean定义,在内置的Tomcat servlet容器启动阶段,
// 严格地讲,是其中相当于web app的StarndartContext的启动阶段,会被逐个实例化并调用其
// onStartup()方法
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class,
name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
}
WebApplicationContext
启动过程指定Tomcat Servlet
容器执行一些Spring SCI
// WebApplicationContext启动过程中创建嵌入式Servlet的方法,
// 具体实现类是 EmbeddedWebApplicationContext
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
// 这里getSelfInitializer()返回一个实现了SCI接口的实例,目的是
// 让嵌入式Tomcat Servlet容器在相应的SCI执行阶段执行其逻辑。
// getSelfInitializer()的具体实现见下面的分析。
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
/**
* Returns the ServletContextInitializer that will be used to complete the
* setup of this WebApplicationContext.
* 返回一个SCI实例,让内置Servlet容器在ServletContext就绪时执行相应的SCI逻辑。
* 这个SCI实例的逻辑就是当前WebApplicationContext的selfInitialize()方法。
* @return the self initializer
* @see #prepareEmbeddedWebApplicationContext(ServletContext)
*/
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
}
从上面的分析可以看出,在一个Springboot Web
应用中,DispatcherServlet
是作为一个bean
首先被注册到Spring
容器中的,并且Springboot
还注册了一个向Servlet
容器中的Web
应用注册DispatcherServlet
的注册器ServletRegistrationBean
。
作为一个Servlet
,被定义时还有一个属性load-on-startup
可以被设置用来指示该Servlet
是否需要在容器初始化过程中被初始化。从上面的分析可以看出,缺省Springboot Web
应用并没有设置load-on-startup
属性,也就是说,该Servlet
不会在容器启动过程中被初始化。
DispatcherServlet
注册到Web
应用//调用链 :
Tomcat StandardContxt.start()
=>startInternal()
=> TomcatStarter.onStartup()
=> EmbeddedWebApplicationContext.selfInitialize()
Tomcat StandardContext
启动时调用SCI
的onStartup
方法这里TomcatStarter
是一个Spring
对Servlet API
标准SCI
接口的一个实现,Tomcat Servlet
容器根据Servlet
规范的要求,会在容器启动过程中被调用其onStartup()
方法。
// Call ServletContainerInitializers
// 在采用缺省配置的Springboot web应用中,下面的initializers其实就包含一个TomcatStarter对象
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
TomcatStarter
的onStartup
方法执行指定的Spring SCI
实例的onStartup
方法 @Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: "
+ ex.getClass().getName() + ". Message: " + ex.getMessage());
}
}
}
EmbeddedWebApplicationContext
的方法selfInitialize
// 根据上面的分析,该方法会在内置Tomcat Servlet容器的ServletContext就绪后的相应时机被回调。
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 主要是将当前Spring EmbeddedWebApplicationContext实例和ServletContext互相关联:
// 1.将当前EmbeddedWebApplicationContext实例登记为ServletContext对象的属性
// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
// 2.将ServletContext对象设置到当前EmbeddedWebApplicationContext实例的成员变量
// servletContext。
prepareEmbeddedWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
// 从Bean容器中获取所有是SCI的bean,并调用其onStartup()方法
// 而通过上面的分析可知,用于注册DispatcherServlet bean的ServletRegistrationBean,
// 正是这样一个SCI,所以这里会尝试从容器中获取这个bean并执行其onStartup()方法,此过程
// 也首先触发了DispatchServlet bean的创建。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
/**
* Returns ServletContextInitializers that should be used with the embedded
* Servlet context. By default this method will first attempt to find
* ServletContextInitializer, Servlet, Filter and certain EventListener beans.
* return the servlet initializer beans
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
DispatcherServlet
被注册到ServletContext
// ServletRegistrationBean类的方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.servlet, "Servlet must not be null");
String name = getServletName();
if (!isEnabled()) {
logger.info("Servlet " + name + " was not registered (disabled)");
return;
}
logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
// 将一个Servlet添加到Tomcat的ServletContext对象,在本文中,我们指的这个Servlet实例
// 就是Spring MVC核心的前端控制器DispatcherServlet
Dynamic added = servletContext.addServlet(name, this.servlet);
if (added == null) {
logger.info("Servlet " + name + " was not registered "
+ "(possibly already registered?)");
return;
}
configure(added);
}
到此为止,整个Springboot Web
应用启动过程中对DispatcherServlet bean
的准备工作就完全结束了。
然而,DispatcherServlet
是一个Servlet
,按说它生命周期中的初始化方法init()
应该被调用却没有在上述应用启动过程中发生,这又是为什么呢 ? 其原因其实也很简单。如上分析所说,因为DispatcherServlet
的属性load-on-startup
没有被设置,所以该DispatcherServlet
的初始化,也就是init()
方法的调用,不是在容器启动过程中,而是在容器接收到第一个来自客户端针对该Servlet
的请求时发生。
缺省配置Springboot Web应用中tomcat的启动过程
缺省配置Springboot Web应用启动过程中Bean定义的登记