最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
首先咱先研究一下 DispatcherServlet
的初始化阶段,都干了哪些工作,下一章我们再研究程序运行期间,DispatcherServlet
如何处理请求并响应的。
前面我们在刚刚开始学习 WebMvc 的时候,我们只配置了两样东西:一个 web.xml
,一个 spring-mvc.xml
。web.xml
中注册了 DispatcherServlet
,spring-mvc.xml
负责初始化 DispatcherServlet
。那问题就来了,DispatcherServlet
在初始化的时候都干了什么,这是我们要研究的东西了。
我们都知道,一切 Servlet
的生命周期,都是从 init
方法开始,DispatcherServlet
也不例外。借助 IDE 往上翻,可以发现 DispatcherServlet
的 init
方法在父类 HttpServletBean
中重写了:
上面的初始化逻辑我们不关心了,关键的点在于下面的这个模板方法 initServletBean
,这个才是关键!
initServletBean
方法要来到 DispatcherServlet
的直接父类 FrameworkServlet
中,这里有初始化的逻辑:
刨去超多的日志打印不看,它的核心就两句话:一个初始化 IOC 容器,一个初始化 Servlet 本身。由于 initFrameworkServlet
方法中没有任何逻辑,DispatcherServlet
也没有重写它,所以核心还是初始化 IOC 容器。下面咱进入 initWebApplicationContext
方法。
初始化 DispatcherServlet
之前,先把属于它的 IOC 容器先初始化出来,以此来形成父子容器,这部分的源码比较长,小册只截取最关键的部分:
纵观整段源码,在第一次初始化的时候很明显上面的乱七八糟分支都是不走的,它会走中间偏下部分的 createWebApplicationContext 方法,在此初始化 IOC 容器。我们继续跟进源码:
进来 createWebApplicationContext 方法,发现它继续往下调用重载的方法:
继续往下,发现这里面有正儿八经的逻辑了,咱先把这段源码大体浏览一下,这里面有一些关键的环节,我们需要着重去看。
有了注释,看起来这段代码是不是非常简单了,核心就三步:实例化 IOC 容器、设置配置文件路径配置、刷新 IOC 容器。
不过这里面有很多细节,我们逐一来看。
首先上面的第一行,getContextClass
方法,它会获取到 WebApplicationContext
的真正落地实现,跳转进该方法,会发现它引用的是一个叫 XmlWebApplicationContext
的家伙:
是不是感觉有点似曾相识?之前,我们在 IOC 进阶,介绍 BeanFactory
的实现类中,提到过一个 XmlBeanFactory
,咱当时在那里说,XmlBeanFactory
这个东西不要去看了,因为已经没有在用了。但这个 XmlWebApplicationContext
不一样,它跟 ClassPathXmlApplicationContext
是差不多的,只是一个在 Web 环境下使用,一个主要在普通项目环境中使用。
接下来的配置文件路径的获取,就是利用 XmlWebApplicationContext
的东西,咱继续往下来看。
String configLocation = getContextConfigLocation();
这一句代码中,获取的就是我们之前在 web.xml
中配置的那个 **contextConfigLocation**
属性,配置了它,这里就能拿到配置文件的路径;如果不配置的话,它也有一套默认规则,这个默认规则我们可以在 XmlWebApplicationContext
中看到一个重写的 getDefaultConfigLocations
方法:
可见这个默认的规则,就是我们在 68 章,SpringWebMvc 的入门中提到的 /WEB-INF/{servlet-name}-servlet.xml
,这里提到的 getNamespace()
获取到的,是刚才在 FrameworkServlet
中定义的一个方法:
所以,看到了这个套路了吧,把 Servlet
的 name 拿过来,后缀拼上 -servlet
,这样就形成配置文件的路径了。
至于这个配置文件在哪里用,咱下面会遇到的,莫慌莫着急。
当上面的 WebAppliCationContext
都配置好之后,下面的最关键步骤,就是刷新 IOC 容器了。来到 configureAndRefreshWebApplicationContext
方法,这里面又是比较长,不过不太值得分段看了,咱总体扫一眼吧,看看里面的关键步骤:
看这里面的逻辑,总体还都是挺容易理解的吧!很明显,对于一个 WebAppliCationContext
而言,它应该持有 Servlet 相关的东西,所以上面的部分很理所当然。
中间它添加了一个 ContextRefreshListener
,注意它来自 spring-webmvc
包,它就是监听我们之前在事件的章节中学习的 ContextRefreshedEvent
事件!至于这个监听器都干了什么,接下来咱马上就会说到。
最底下,它执行 IOC 容器的 refresh
方法,那它就会去加载、解析配置文件,以及初始化的逻辑等等等等。
注意一点!此时 **WebAppliCationContext**
中是没有 **DispatcherServlet**
的!所以 IOC 容器的初始化,是无法控制和操作 DispatcherServlet
的,加上咱上面说到了,initFrameworkServlet
方法里面没有任何初始化逻辑,所以如果 DispatcherServlet
还需要执行额外的初始化逻辑,那就不得不使用其他的方案来触发。
那咋触发呢?刚才不是刚刚说到一个监听器嘛,这个 ContextRefreshListener
,就是让 IOC 容器重新跟 DispatcherServlet
打交道的 “桥梁” 。
在进入监听器的源码之前,先想一个问题。
此时正在执行的,是 ContextRefreshedEvent
事件的发布动作吧!此时第 11 步 **finishBeanFactoryInitialization**
方法已经执行完毕了吧,IOC 容器中的 bean 也都初始化完成了吧!( **ContextRefreshedEvent**
事件的发布,是在第 12 步 **finishRefresh**
方法中广播的)
所以,当 ContextRefreshedEvent
事件广播的时候,我们就应该知道,此时所有的 bean 都准备就绪。但是!因为我们在之前的初体验中,根本什么东西都没有注册,甚至连一个 <mvc:annotation-driven />
标签,或者 @EnableWebMvc
注解都没有写,所以此时 IOC 容器中应该只有一个 **InternalResourceViewResolver**
,其余的什么 **HandlerMapping**
、**HandlerAdapter**
统统都没有!
那问题就来了,这些东西都没有注册,咋就好使了呢?
好,带着这个问题,我们可以来看 ContextRefreshListener
的源码了。
这个 ContextRefreshListener
定义在 FrameworkServlet
的内部,而正是因为定义在内部,所以刚好能拿到 DispatcherServlet
的实例。这个监听器的触发调用的逻辑,就是 FrameworkServlet
的 onApplicationEvent
方法:
很明显,接下来又是模板方法,跳转到 DispatcherServlet
了。
注意这个地方到了非常非常重要的点了!!!高能预警!!!
可以发现,这个刷新逻辑中,把 **DispatcherServlet**
需要的所有组件,全部都初始化了一遍!所以各位,能猜到里面干了什么了吧!肯定是我们没有初始化哪些组件,这个 **initStrategies**
方法就会帮我们初始化!
下面我们来挑一个比较重要的组件来看看吧,小册就挑 HandlerMapping
吧。
首先我们来看 HandlerMapping
的初始化:
首先上面的逻辑中,它会先从 IOC 容器中,寻找那些类型为 HandlerMapping
的 bean ,借助 BeanFactoryUtils.beansOfTypeIncludingAncestors
方法,可以从子容器一路爬到最顶层父容器,来搜寻所有指定类型的 bean 。如果全部搜寻完毕后,发现没有任何 HandlerMapping
,则会调用最底下的 getDefaultStrategies
方法,使用默认的策略初始化 HandlerMapping
。
这个默认策略相当重要,一起来看源码是如何设计的:
可以发现!中间 for 循环中初始化了一组 Class<?>
,而对应的 classNames
是由 value
分割而来,而 value
又是根据上面的一个 **Properties**
类型的 **defaultStrategies**
变量而来!而这个 defaultStrategies 的初始化,是走的静态代码块:
注意看!它加载了 classpath 下的一个名为 “DispatcherServlet.properties”
的文件!而这个文件,来自于 spring-webmvc
的 jar 包:
打开这个 properties 文件,发现里面定义了刚才在 initStrategies
方法中初始化的所有 WebMvc 的核心组件的落地实现!
可见,即便我们什么也不注册,也能使用默认的策略,加载这些 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 容器,这样就完成了默认策略的加载。
使用 **web.xml**
配置 **DispatcherServlet**
的时候,即便我们不配置任何内置组件,SpringWebMvc 可以通过预先定义好的内置组件,在 IOC 容器已经初始化接近完毕的时候,借助事件监听,回调 **DispatcherServlet**
使其初始化默认组件的落地实现,从而达到兜底配置。
基于 Servlet 3.0 规范的 SpringWebMvc 工程,咱之前说过,它的入口是通过 WebApplicationInitializer
来的。我们在初体验中编写了一个 AbstractAnnotationConfigDispatcherServletInitializer
的子类,来声明根容器和 DispatcherServlet
子容器的配置类,并且指定了 DispatcherServlet
的 servlet-mapping
,就完成了 WebMvc 的配置。
这其中,我们也是只配置了一个 InternalResourceViewResolver
,其余的肯定都跟上面一样,执行了 DispatcherServlet
的 initStrategies
方法做了兜底初始化。不过我们应该好奇的是,AbstractAnnotationConfigDispatcherServletInitializer
是怎么由 WebApplicationInitializer
来一步一步把根容器和 WebMvc 子容器都初始化好的呢?
要说到这个,那就必须来看 WebAppliCationInitializer
的第一个抽象实现类了,它首先实现了 WebAppliCationInitializer
接口的 onStartUp
方法:
只不过这看上去,是为了注册之前在 web.xml
中初始化的那个 ContextLoaderListener
监听器,那里面的逻辑一定是初始化根容器咯,但是这似乎跟 DispatcherServlet
没啥关系。。。
不过要注意,IDEA 在此处给了我们提示,这个 onStartUp
方法,还有重写过的子类:
好,那咱就点击这个向下的 override 图标,跳转到一个叫 AbstractDispatcherServletInitializer
的子类。
这个 AbstractDispatcherServletInitializer
重写的 onStartUp
中,除了调用上面 AbstractContextLoaderInitializer
父类的 onStartUp
初始化根容器之外,还注册了 DispatcherServlet
:
进入到 registerDispatcherServlet
方法:
可以发现,这整个流程还是很容易理解的吧!首先初始化 WebMvc 的子容器,然后把 DispatcherServlet
创建出来,最后注册进 ServletContext
,完事。而初始化 WebMvc 子容器的逻辑,就定义在我们刚才说的 AbstractAnnotationConfigDispatcherServletInitializer
中了:
这里面我们要重写的方法,就是这个获取配置类的 getServletConfigClasses
方法。拿到配置类,就可以初始化 WebMvc 的子容器了,后面的逻辑也就跟上面分析的一样了。
好,最后我们总结一下。
使用 **web.xml**
配置 **DispatcherServlet**
的时候,即便我们不配置任何内置组件,SpringWebMvc 可以通过预先定义好的内置组件,在 IOC 容器已经初始化接近完毕的时候,借助事件监听,回调 **DispatcherServlet**
使其初始化默认组件的落地实现,从而达到兜底配置。
基于 Servlet 3.0 规范的方式,通过编写继承 **AbstractAnnotationConfigDispatcherServletInitializer**
的子类,分别提供根容器与 WebMvc 子容器的配置类,底层会帮我们初始化对应的容器,并且借助 **ServletContext**
注册 **DispatcherServlet**
。后续的处理逻辑与基于 **web.xml**
的配置方式一致。