首先咱先研究一下 DispatcherServlet
的初始化阶段,都干了哪些工作,下一章我们再研究程序运行期间,DispatcherServlet
如何处理请求并响应的。
1. 基于web.xml的DispatcherServlet初始化
前面我们在刚刚开始学习 WebMvc 的时候,我们只配置了两样东西:一个 web.xml
,一个 spring-mvc.xml
。web.xml
中注册了 DispatcherServlet
,spring-mvc.xml
负责初始化 DispatcherServlet
。那问题就来了,DispatcherServlet
在初始化的时候都干了什么,这是我们要研究的东西了。
1.1 init
我们都知道,一切 Servlet
的生命周期,都是从 init
方法开始,DispatcherServlet
也不例外。借助 IDE 往上翻,可以发现 DispatcherServlet
的 init
方法在父类 HttpServletBean
中重写了:
复制 @ Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// ......
// Let subclasses do whatever initialization they like.
initServletBean() ;
}
上面的初始化逻辑我们不关心了,关键的点在于下面的这个模板方法 initServletBean
,这个才是关键!
initServletBean
方法要来到 DispatcherServlet
的直接父类 FrameworkServlet
中,这里有初始化的逻辑:
复制 @ Override
protected final void initServletBean() throws ServletException {
// log ......
long startTime = System . currentTimeMillis ();
try {
this . webApplicationContext = initWebApplicationContext() ;
initFrameworkServlet() ;
} // catch ......
// logger ......
}
刨去超多的日志打印不看,它的核心就两句话:一个初始化 IOC 容器,一个初始化 Servlet 本身 。由于 initFrameworkServlet
方法中没有任何逻辑,DispatcherServlet
也没有重写它,所以核心还是初始化 IOC 容器。下面咱进入 initWebApplicationContext
方法。
1.2 initWebApplicationContext
初始化 DispatcherServlet
之前,先把属于它的 IOC 容器先初始化出来,以此来形成父子容器,这部分的源码比较长,小册只截取最关键的部分:
复制 protected WebApplicationContext initWebApplicationContext() {
// 先获取根容器(通过ContextLoaderListener初始化的IOC容器)
WebApplicationContext rootContext =
WebApplicationContextUtils . getWebApplicationContext ( getServletContext() );
WebApplicationContext wac = null ;
// 该分支用于处理DispatcherServlet构造时set过ApplicationContext的情况,一般不会遇到
if ( this . webApplicationContext != null ) {
// A context instance was injected at construction time -> use it
wac = this . webApplicationContext ;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if ( ! cwac . isActive ()) {
if ( cwac . getParent () == null ) {
cwac . setParent (rootContext);
}
configureAndRefreshWebApplicationContext(cwac) ;
}
}
}
// ......
// 如果没有WebApplicationContext,则会创建一个全新的WebApplicationContext
if (wac == null ) {
wac = createWebApplicationContext(rootContext) ;
}
// ......
// 将该WebApplicationContext放入ServletContext中
if ( this . publishContext ) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName() ;
getServletContext() . setAttribute (attrName , wac);
}
return wac;
}
纵观整段源码,在第一次初始化的时候很明显上面的乱七八糟分支都是不走的,它会走中间偏下部分的 createWebApplicationContext 方法,在此初始化 IOC 容器。我们继续跟进源码:
1.2.1 createWebApplicationContext
进来 createWebApplicationContext 方法,发现它继续往下调用重载的方法:
复制 protected WebApplicationContext createWebApplicationContext(@ Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent) ;
}
继续往下,发现这里面有正儿八经的逻辑了,咱先把这段源码大体浏览一下,这里面有一些关键的环节,我们需要着重去看。
复制 protected WebApplicationContext createWebApplicationContext(@ Nullable ApplicationContext parent) {
// 加载IOC容器的承载实现类
Class < ? > contextClass = getContextClass() ;
if ( ! ConfigurableWebApplicationContext . class . isAssignableFrom (contextClass)) {
throw ex ...... ;
}
// 反射实例化
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils . instantiateClass (contextClass);
wac . setEnvironment ( getEnvironment() );
wac . setParent (parent);
// 获取、设置配置文件的路径
String configLocation = getContextConfigLocation() ;
if (configLocation != null ) {
wac . setConfigLocation (configLocation);
}
// 刷新IOC容器 - refresh
configureAndRefreshWebApplicationContext(wac) ;
return wac;
}
有了注释,看起来这段代码是不是非常简单了,核心就三步:实例化 IOC 容器、设置配置文件路径配置、刷新 IOC 容器 。
不过这里面有很多细节,我们逐一来看。
1.2.2 IOC容器的真正落地实现
首先上面的第一行,getContextClass
方法,它会获取到 WebApplicationContext
的真正落地实现,跳转进该方法,会发现它引用的是一个叫 XmlWebApplicationContext
的家伙:
复制 public static final Class < ? > DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext . class ;
private Class < ? > contextClass = DEFAULT_CONTEXT_CLASS;
public Class<?> getContextClass() {
return this . contextClass ;
}
是不是感觉有点似曾相识?之前,我们在 IOC 进阶,介绍 BeanFactory
的实现类中,提到过一个 XmlBeanFactory
,咱当时在那里说,XmlBeanFactory
这个东西不要去看了,因为已经没有在用了。但这个 XmlWebApplicationContext
不一样,它跟 ClassPathXmlApplicationContext
是差不多的,只是一个在 Web 环境下使用,一个主要在普通项目环境中使用。
接下来的配置文件路径的获取,就是利用 XmlWebApplicationContext
的东西,咱继续往下来看。
1.2.3 获取配置文件的路径
String configLocation = getContextConfigLocation();
这一句代码中,获取的就是我们之前在 web.xml
中配置的那个 **contextConfigLocation**
属性,配置了它,这里就能拿到配置文件的路径;如果不配置的话,它也有一套默认规则,这个默认规则我们可以在 XmlWebApplicationContext
中看到一个重写的 getDefaultConfigLocations
方法:
复制 public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml" ;
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/" ;
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml" ;
protected String [] getDefaultConfigLocations() {
if ( getNamespace() != null ) {
return new String [] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
} else {
return new String [] {DEFAULT_CONFIG_LOCATION};
}
}
可见这个默认的规则,就是我们在 68 章,SpringWebMvc 的入门中提到的 /WEB-INF/{servlet-name}-servlet.xml
,这里提到的 getNamespace()
获取到的,是刚才在 FrameworkServlet
中定义的一个方法:
复制 public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet" ;
public String getNamespace() {
return ( this . namespace != null ? this . namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
所以,看到了这个套路了吧,把 Servlet
的 name 拿过来,后缀拼上 -servlet
,这样就形成配置文件的路径了。
至于这个配置文件在哪里用,咱下面会遇到的,莫慌莫着急。
1.2.4 刷新WebAppliCationContext
当上面的 WebAppliCationContext
都配置好之后,下面的最关键步骤,就是刷新 IOC 容器了。来到 configureAndRefreshWebApplicationContext
方法,这里面又是比较长,不过不太值得分段看了,咱总体扫一眼吧,看看里面的关键步骤:
复制 protected void configureAndRefreshWebApplicationContext( ConfigurableWebApplicationContext wac) {
// 设置IOC容器的id,太长略过
// 常规设置一些web的东西
wac . setServletContext ( getServletContext() );
wac . setServletConfig ( getServletConfig() );
// 此处就是上面的getNamespace方法
wac . setNamespace ( getNamespace() );
// 这里添加了一个上下文刷新的监听器
wac . addApplicationListener ( new SourceFilteringListener(wac , new ContextRefreshListener()) );
ConfigurableEnvironment env = wac . getEnvironment ();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env) . initPropertySources ( getServletContext() , getServletConfig() );
}
postProcessWebApplicationContext(wac) ;
applyInitializers(wac) ;
// 刷新IOC容器
wac . refresh ();
}
看这里面的逻辑,总体还都是挺容易理解的吧!很明显,对于一个 WebAppliCationContext
而言,它应该持有 Servlet 相关的东西,所以上面的部分很理所当然。
中间它添加了一个 ContextRefreshListener
,注意它来自 spring-webmvc
包,它就是监听我们之前在事件的章节中学习的 ContextRefreshedEvent
事件!至于这个监听器都干了什么,接下来咱马上就会说到。
最底下,它执行 IOC 容器的 refresh
方法,那它就会去加载、解析配置文件,以及初始化的逻辑等等等等。
注意一点!此时 **WebAppliCationContext**
中是没有 **DispatcherServlet**
的 !所以 IOC 容器的初始化,是无法控制和操作 DispatcherServlet
的,加上咱上面说到了,initFrameworkServlet
方法里面没有任何初始化逻辑,所以如果 DispatcherServlet
还需要执行额外的初始化逻辑,那就不得不使用其他的方案来触发。
那咋触发呢?刚才不是刚刚说到一个监听器嘛,这个 ContextRefreshListener
,就是让 IOC 容器重新跟 DispatcherServlet
打交道的 “桥梁” 。
1.3 ContextRefreshListener
在进入监听器的源码之前,先想一个问题。
1.3.0 前置思考:此时 IOC 容器初始化到哪一步了?
此时正在执行的,是 ContextRefreshedEvent
事件的发布动作吧!此时第 11 步 **finishBeanFactoryInitialization**
方法已经执行完毕了吧,IOC 容器中的 bean 也都初始化完成了吧!( **ContextRefreshedEvent**
事件的发布,是在第 12 步 **finishRefresh**
方法中广播的)
所以,当 ContextRefreshedEvent
事件广播的时候,我们就应该知道,此时所有的 bean 都准备就绪。但是!因为我们在之前的初体验中,根本什么东西都没有注册,甚至连一个 <mvc:annotation-driven />
标签,或者 @EnableWebMvc
注解都没有写,所以此时 IOC 容器中应该只有一个 **InternalResourceViewResolver**
,其余的什么 **HandlerMapping**
、 **HandlerAdapter**
统统都没有!
那问题就来了,这些东西都没有注册,咋就好使了呢?
好,带着这个问题,我们可以来看 ContextRefreshListener
的源码了。
1.3.1 ContextRefreshListener的源码
复制 private class ContextRefreshListener implements ApplicationListener < ContextRefreshedEvent > {
@ Override
public void onApplicationEvent ( ContextRefreshedEvent event) {
FrameworkServlet . this . onApplicationEvent (event);
}
}
这个 ContextRefreshListener
定义在 FrameworkServlet
的内部,而正是因为定义在内部,所以刚好能拿到 DispatcherServlet
的实例。这个监听器的触发调用的逻辑,就是 FrameworkServlet
的 onApplicationEvent
方法:
复制 public void onApplicationEvent( ContextRefreshedEvent event) {
this . refreshEventReceived = true ;
synchronized ( this . onRefreshMonitor ) {
onRefresh( event . getApplicationContext()) ;
}
}
protected void onRefresh( ApplicationContext context) {
// For subclasses: do nothing by default.
}
很明显,接下来又是模板方法,跳转到 DispatcherServlet
了。
1.3.2 DispatcherServlet的onRefresh
注意这个地方到了非常非常重要的点了!!!高能预警!!!
复制 @ Override
protected void onRefresh( ApplicationContext context) {
initStrategies(context) ;
}
protected void initStrategies( ApplicationContext context) {
initMultipartResolver(context) ; // 文件上传
initLocaleResolver(context) ; // 国际化
initThemeResolver(context) ; // 前端主题
initHandlerMappings(context) ; // 处理器映射器
initHandlerAdapters(context) ; // 处理器适配器
initHandlerExceptionResolvers(context) ; // 异常处理器
initRequestToViewNameTranslator(context) ;
initViewResolvers(context) ; // 视图解析器
initFlashMapManager(context) ;
}
可以发现,这个刷新逻辑中,把 **DispatcherServlet**
需要的所有组件,全部都初始化了一遍 !所以各位,能猜到里面干了什么了吧!肯定是我们没有初始化哪些组件,这个 **initStrategies**
方法就会帮我们初始化 !
下面我们来挑一个比较重要的组件来看看吧,小册就挑 HandlerMapping
吧。
1.3.3 initHandlerMappings
首先我们来看 HandlerMapping
的初始化:
复制 private void initHandlerMappings( ApplicationContext context) {
this . handlerMappings = null ;
// 默认分支:需要从容器中取出所有HandlerMapping
if ( this . detectAllHandlerMappings ) {
Map < String , HandlerMapping > matchingBeans =
BeanFactoryUtils . beansOfTypeIncludingAncestors (context , HandlerMapping . class , true , false );
if ( ! matchingBeans . isEmpty ()) {
this . handlerMappings = new ArrayList <>( matchingBeans . values ());
AnnotationAwareOrderComparator . sort ( this . handlerMappings );
}
}
else {
// 该分支,仅会取出名为 “handlerMapping” 的bean
try {
HandlerMapping hm = context . getBean (HANDLER_MAPPING_BEAN_NAME , HandlerMapping . class );
this . handlerMappings = Collections . singletonList (hm);
} // catch ......
}
if ( this . handlerMappings == null ) {
this . handlerMappings = getDefaultStrategies(context , HandlerMapping . class ) ;
// logger ......
}
}
首先上面的逻辑中,它会先从 IOC 容器中,寻找那些类型为 HandlerMapping
的 bean ,借助 BeanFactoryUtils.beansOfTypeIncludingAncestors
方法,可以从子容器一路爬到最顶层父容器,来搜寻所有指定类型的 bean 。如果全部搜寻完毕后,发现没有任何 HandlerMapping
,则会调用最底下的 getDefaultStrategies
方法,使用默认的策略初始化 HandlerMapping
。
1.3.4 【默认策略】getDefaultStrategies
这个默认策略相当重要 ,一起来看源码是如何设计的:
复制 private static final Properties defaultStrategies;
protected < T > List< T > getDefaultStrategies( ApplicationContext context , Class< T > strategyInterface) {
String key = strategyInterface . getName ();
String value = defaultStrategies . getProperty (key);
if (value != null ) {
String [] classNames = StringUtils . commaDelimitedListToStringArray (value);
List < T > strategies = new ArrayList <>( classNames . length );
for ( String className : classNames) {
try {
Class < ? > clazz = ClassUtils . forName (className , DispatcherServlet . class . getClassLoader ());
Object strategy = createDefaultStrategy(context , clazz) ;
strategies . add ((T) strategy);
} // catch ......
}
return strategies;
} else {
return new LinkedList <>();
}
}
可以发现!中间 for 循环中初始化了一组 Class<?>
,而对应的 classNames
是由 value
分割而来,而 value
又是根据上面的一个 **Properties**
类型的 **defaultStrategies**
变量 而来!而这个 defaultStrategies 的初始化,是走的静态代码块:
复制 private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties" ;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH , DispatcherServlet . class ) ;
defaultStrategies = PropertiesLoaderUtils . loadProperties (resource);
} // catch ......
}
注意看!它加载了 classpath 下的一个名为 “DispatcherServlet.properties”
的文件!而这个文件,来自于 spring-webmvc
的 jar 包:
打开这个 properties 文件,发现里面定义了刚才在 initStrategies
方法中初始化的所有 WebMvc 的核心组件的落地实现!
复制 # 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.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter= org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver= org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
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
可见,即便我们什么也不注册,也能使用默认的策略,加载这些 SpringWebMvc 认为必需的组件。
由 properties 文件可知,默认的 HandlerMapping 会初始化以下三个实现:
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
**org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping**
org.springframework.web.servlet.function.support.RouterFunctionMapping
刚好有我们之前说的最重要的 RequestMappingHandlerMapping
,所以我们不需要任何配置,就可以实现 @Controller
+ @RequestMapping
的注解式 WebMvc 开发。
所以,读取完配置文件后,在上面的 getDefaultStrategies
方法中,就可以拿到上面的 3 个实现类,并实例化并注册进 IOC 容器,这样就完成了默认策略的加载。
1.4 小结
使用 **web.xml**
配置 **DispatcherServlet**
的时候,即便我们不配置任何内置组件,SpringWebMvc 可以通过预先定义好的内置组件,在 IOC 容器已经初始化接近完毕的时候,借助事件监听,回调 **DispatcherServlet**
使其初始化默认组件的落地实现,从而达到兜底配置。
2. 基于Servlet3.0规范的DispatcherServlet初始化
基于 Servlet 3.0 规范的 SpringWebMvc 工程,咱之前说过,它的入口是通过 WebApplicationInitializer
来的。我们在初体验中编写了一个 AbstractAnnotationConfigDispatcherServletInitializer
的子类,来声明根容器和 DispatcherServlet
子容器的配置类,并且指定了 DispatcherServlet
的 servlet-mapping
,就完成了 WebMvc 的配置。
这其中,我们也是只配置了一个 InternalResourceViewResolver
,其余的肯定都跟上面一样,执行了 DispatcherServlet
的 initStrategies
方法做了兜底初始化。不过我们应该好奇的是,AbstractAnnotationConfigDispatcherServletInitializer
是怎么由 WebApplicationInitializer
来一步一步把根容器和 WebMvc 子容器都初始化好的呢?
2.1 AbstractContextLoaderInitializer
要说到这个,那就必须来看 WebAppliCationInitializer
的第一个抽象实现类了,它首先实现了 WebAppliCationInitializer
接口的 onStartUp
方法:
复制 @ Override
public void onStartup( ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext) ;
}
只不过这看上去,是为了注册之前在 web.xml
中初始化的那个 ContextLoaderListener
监听器,那里面的逻辑一定是初始化根容器 咯,但是这似乎跟 DispatcherServlet
没啥关系。。。
不过要注意,IDEA 在此处给了我们提示,这个 onStartUp
方法,还有重写过的子类:
好,那咱就点击这个向下的 override 图标,跳转到一个叫 AbstractDispatcherServletInitializer
的子类。
2.2 AbstractDispatcherServletInitializer
这个 AbstractDispatcherServletInitializer
重写的 onStartUp
中,除了调用上面 AbstractContextLoaderInitializer
父类的 onStartUp
初始化根容器之外,还注册了 DispatcherServlet
:
复制 @ Override
public void onStartup( ServletContext servletContext) throws ServletException {
super . onStartup (servletContext);
registerDispatcherServlet(servletContext) ;
}
进入到 registerDispatcherServlet
方法:
复制 protected void registerDispatcherServlet( ServletContext servletContext) {
String servletName = getServletName() ;
Assert . hasLength (servletName , "getServletName() must not return null or empty" );
// 创建WebMvc子容器
WebApplicationContext servletAppContext = createServletApplicationContext() ;
Assert . notNull (servletAppContext , "createServletApplicationContext() must not return null" );
// 初始化DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext) ;
Assert . notNull (dispatcherServlet , "createDispatcherServlet(WebApplicationContext) must not return null" );
dispatcherServlet . setContextInitializers ( getServletApplicationContextInitializers() );
// 利用Servlet3.0的规范,动态注册Servlet
ServletRegistration . Dynamic registration = servletContext . addServlet (servletName , dispatcherServlet);
// check ......
registration . setLoadOnStartup ( 1 );
// 设置servlet-mapping
registration . addMapping ( getServletMappings() );
// servlet是否支持异步请求,默认true
registration . setAsyncSupported ( isAsyncSupported() );
Filter [] filters = getServletFilters() ;
if ( ! ObjectUtils . isEmpty (filters)) {
for ( Filter filter : filters) {
registerServletFilter(servletContext , filter) ;
}
}
customizeRegistration(registration) ;
}
可以发现,这整个流程还是很容易理解的吧!首先初始化 WebMvc 的子容器,然后把 DispatcherServlet
创建出来,最后注册进 ServletContext
,完事。而初始化 WebMvc 子容器的逻辑,就定义在我们刚才说的 AbstractAnnotationConfigDispatcherServletInitializer
中了:
复制 @ Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext() ;
Class < ? >[] configClasses = getServletConfigClasses() ;
if ( ! ObjectUtils . isEmpty (configClasses)) {
context . register (configClasses);
}
return context;
}
这里面我们要重写的方法,就是这个获取配置类的 getServletConfigClasses
方法。拿到配置类,就可以初始化 WebMvc 的子容器了,后面的逻辑也就跟上面分析的一样了。
小结
好,最后我们总结一下。
使用 **web.xml**
配置 **DispatcherServlet**
的时候,即便我们不配置任何内置组件,SpringWebMvc 可以通过预先定义好的内置组件,在 IOC 容器已经初始化接近完毕的时候,借助事件监听,回调 **DispatcherServlet**
使其初始化默认组件的落地实现,从而达到兜底配置。
基于 Servlet 3.0 规范的方式,通过编写继承 **AbstractAnnotationConfigDispatcherServletInitializer**
的子类,分别提供根容器与 WebMvc 子容器的配置类,底层会帮我们初始化对应的容器,并且借助 **ServletContext**
注册 **DispatcherServlet**
。后续的处理逻辑与基于 **web.xml**
的配置方式一致。