概述
缺省配置Springboot Web应用中的SpringMVC DispatcherServlet,从整个应用开始启动到最终处于服务请求的状态,粗略地讲,可以分为两步 :
- 缺省配置
Springboot Web应用启动过程中SpringMVC DispatcherServlet的准备
这一步相当于把
DispatcherServlet安装部署到一个可以随时服务请求的web应用中去;
关于这一步的详细情况,可以参考文章
缺省配置Springboot Web应用启动中准备SpringMVC DispatcherServlet
- 缺省配置
Springboot Web应用运行中SpringMVC DispatcherServlet的初始化
这一步指的是
Servlet.init()方法的调用,本文主要分析这一过程。
源代码分析
HttpServletBean的init方法
DispatcherServlet和父类的继承关系如下 :
DispatcherServlet -> FrameworkServlet -> HttpServletBean --> HttpServlet
这个继承关系里面,DispatcherServlet,FrameworkServlet,HttpServletBean都是Spring的类,位于以下包 :
org.springframework.web.servlet
而 HttpServlet 是 Java Servlet API,表示一个HTTP Servlet,是一个抽象类,位于包 :
javax.servlet.http
这里面Servlet的初始化方法 init() 实现在 HttpServletBean 中 :
// 类 org.springframework.web.servlet.HttpServletBean 的方法
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
// 从初始化参数 init paramters 中提取参数值设置bean属性
// 完全缺省配置的Springboot Web应用,这里最终的pvs为空:pvs.isEmpty()==true
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// initServletBean()是HttpServletBean定义的一个空方法,目的是让子类填充相应的初始化实现
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
FrameworkServlet 的initServletBean方法
从上面的分析可以看出,HttpServletBean.init()在做了一些简单的参数设置时候,具体的初始化工作交给了子类对方法initServletBean()的具体实现。而作为子类,FrameworkServlet 实现了方法initServletBean(),源代码如下 :
// 类 org.springframework.web.servlet.FrameworkServlet 的方法
/**
* 对父类HttpServletBean所定义方法的覆盖实现, 在所有bean属性设置后被调用,
* 用于创建servlet的WebApplicationContext实例
*/
@Override
protected final void initServletBean() throws ServletException {
// 日志输出,控制台上看到的效果如下 :
// Initializing Spring FrameworkServlet 'dispatcherServlet'
// FrameworkServlet 'dispatcherServlet': initialization started
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 创建针对该Servlet(也就是DispatcherServlet)的Web ApplicationContext
this.webApplicationContext = initWebApplicationContext();
// initFrameworkServlet是FrameworkServlet提供的空方法,
// 用于在所有属性已设置,并且Servlet WebApplicationContext加载后
// 提供给子类一个机会执行一些它们需要的初始化工作
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
/**
* Initialize and publish the WebApplicationContext for this servlet.
* Delegates to createWebApplicationContext for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
// 获取当前Servlet实例所在的Web应用的WebApplicationContext,称之为根上下文rootContext,
// 该根上下文在创建成功后记录为servletContext属性
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
// 这里通过工具类WebApplicationContextUtils把它取出来使用。
//
// 在完全采用缺省配置的Springboot Web应用中,它是一个如下类的实例
// org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 现在准备创建当前Servlet的WebApplicationContext了,将要把它记录为wac
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 如果当前Servlet实例的webApplicationContext属性已经设置,则使用它
// 对于完全使用缺省配置的Springboot Web应用,当前Servlet实例是DispatcherServlet,
// 因为他实现了接口ApplicationContextAware,所以在它初始化时其属性
// webApplicationContext已经被设置,这里将使用该属性
// A context instance was injected at construction time -> use it
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);
}
}
}
// 对于完全使用缺省配置的Springboot Web应用,从上面的分析可以得出结论,
// Web app 的WebApplicationContext rootContext,和当前Servlet,也就是DispatcherServlet servlet
// 的WebApplicationContext wac,最终指向了同一个对象
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
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
// 对于完全使用缺省配置的Springboot Web应用,这个标志初始化为false,并且没有被修改为true,
// 所以对于DispatcherServlet,下面if与句中的逻辑会被执行
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
// onRefresh是FrameworkServlet的空方法,供子类提供实现,而DispatcherServlet
// 提供了相应的实现
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 为当前Servlet构造属性名称,类似于 :
// org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet
String attrName = getServletContextAttributeName();
// 将当前 Servlet 的 WebApplicationContext wac登记为servletContext的属性
getServletContext().setAttribute(attrName, wac);
// DispatcherServlet的初始化执行到这里,所属servletContext的属性中有两个属性:
// 1.org.springframework.web.context.WebApplicationContext.ROOT
// 2.org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet
// 并且它们指向了同一个WebApplicationContext,也就是 SpringApplication.run()方法中
// 调用createApplicationContext()创建的那个
// AnnotationConfigEmbeddedWebApplicationContext实例
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
从上面的代码分析看出,对于一个完全采用缺省配置的Springboot web应用,里面只有唯一的一个DispatcherServlet实例,在这种情况下,该DispatcherServlet实例的Web ApplicationContext和整个应用的Root ApplicationContext是同一个。这是Spring MVC官方文档中介绍的一种用法,如下图所示:

以上是Spring MVC官方文档配图"Figure 22.3. Single root context in Spring Web MVC"。
DispatcherServlet的onRefresh方法
// 类 org.springframework.web.servlet.DispatcherServlet 的方法
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* 初始化当前Servlet使用的各种策略对象
* May be overridden in subclasses in order to initialize further strategy objects.
* 子类可以覆盖该方法用于初始化更多的策略对象
*
*/
protected void initStrategies(ApplicationContext context) {
// MultipartResolver 文件上传相关
// 从bean容器中获取名字为MULTIPART_RESOLVER_BEAN_NAME(multipartResolver)的bean
// 记录到属性multipartResolver,没有相应bean的话设置为NULL
initMultipartResolver(context);
// LocaleResolver, 本地化语言相关
// 从bean容器中获取名字为LOCALE_RESOLVER_BEAN_NAME(localeResolver)的bean
// 记录到属性localeResolver,没有相应bean的话使用缺省策略
initLocaleResolver(context);
// ThemeResolver 相关
// 从bean容器中获取名字为THEME_RESOLVER_BEAN_NAME(themeResolver)的bean
// 记录到属性themeResolver,没有相应bean的话使用缺省策略
initThemeResolver(context);
// HandlerMapping (list) 相关
// 从bean容器中获取名字为HANDLER_MAPPING_BEAN_NAME(handlerMapping)的bean,
// 或者获取所有类型是HandlerMapping的bean(DispatcherServlet的缺省做法),
// 记录到属性handlerMappings,没有相应bean的话使用缺省策略
initHandlerMappings(context);
// HandlerAdapter (list) 相关
// 从bean容器中获取名字为HANDLER_ADAPTER_BEAN_NAME(handlerAdapter)的bean,
// 或者获取所有类型是HandlerAdapter的bean(DispatcherServlet的缺省做法),
// 记录到属性handlerAdapters,没有相应bean的话使用缺省策略
initHandlerAdapters(context);
// HandlerExceptionResolver (list) 异常处理Handler相关
// 从bean容器中获取名字为HANDLER_EXCEPTION_RESOLVER_BEAN_NAME(handlerExceptionResolver)的bean,
// 或者获取所有类型是HandlerExceptionResolver的bean(DispatcherServlet的缺省做法),
// 记录到属性handlerExceptionResolvers,没有相应bean的话使用缺省策略
initHandlerExceptionResolvers(context);
// RequestToViewNameTranslator 相关
// 从bean容器中获取名字为REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME(viewNameTranslator)的bean
// 记录到属性viewNameTranslator,没有相应bean的话使用缺省策略
initRequestToViewNameTranslator(context);
// ViewResolver (list) 相关
// 从bean容器中获取名字为VIEW_RESOLVER_BEAN_NAME(viewResolver)的bean,
// 或者获取所有类型是ViewResolver的bean(DispatcherServlet的缺省做法),
// 记录到属性viewResolvers,没有相应bean的话使用缺省策略
initViewResolvers(context);
// FlashMapManager 相关
// 从bean容器中获取名字为FLASH_MAP_MANAGER_BEAN_NAME(flashMapManager)的bean
// 记录到属性flashMapManager,没有相应bean的话使用缺省策略
initFlashMapManager(context);
}
上面初始化过程中提到的9大策略组件,如果从bean容器中找不到相应的bean,会使用缺省值,而这些缺省值,定义在spring-webmvc jar包的文件org.springframework.web.servlet.DispatcherServlet.properties中,如下所示 :
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
关于上面9大组件的初始化过程,可以参考Spring MVC DispatcherServlet 策略初始化有关文章合集
#参考资料
Spring MVC是如何工作的?
缺省配置Springboot Web应用启动中准备 DispatcherServlet
缺省配置Springboot Web应用启动过程中Bean定义的登记
Spring MVC DispatcherServlet 策略初始化有关文章合集
这篇博客详细分析了Springboot Web应用中DispatcherServlet的初始化过程,包括启动准备和运行时的初始化。文章深入源代码,探讨了DispatcherServlet如何进行配置、其初始化方法的实现,以及缺省策略组件的设定。同时,提到了Spring MVC的官方文档,并引用了相关参考资料以供深入学习。
21万+

被折叠的 条评论
为什么被折叠?



