layout: post title: "在使用 SpringMVC 时,Spring 容器是如何与 Servlet 容器进行交互的?" categories: Spring tags: context servlet springmvc 容器
可否还记得,当年还没有 Springboot 的时候,在 Tomcat 的 web.xml 中进行面向 xml 编程的青葱岁月?其中有那么几段配置总是令我记忆犹新:
首先是 Spring 容器配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-config.xml</param-value>
</context-param>
其次是 Servlet 容器监听器配置:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在 Tomcat 启动时,根据这两段配置,究竟做了什么动作,使得 Tomcat 与 Spring 完美地结合在一起了呢?
首先我们来看下 ContextLoaderListener 监听器的源码:
我们发现它继承了 ContextLoader,并且实现了 ServletContextListener 接口,下面说下这两个东西的作用:
因此,ContextLoaderListener 最主要的作用就是在 Tomcat 启动时,根据配置加载 Spring 容器。
以上就是 ContextLoaderListener 实现 contextInitialized 方法的逻辑,也是加载并初始化 Spring 容器的开始。
org.springframework.web.context.ContextLoader#initWebApplicationContext
以上代码逻辑主要做了以下几个操作:
还发现 Spring 容器保存到 Servlet 容器中的 key 为 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们顺藤摸瓜找到获取 Spring 容器的方法:
org.springframework.web.context.support.WebApplicationContextUtils#getWebApplicationContext
关于这个方法在哪里调用后面有说到。
org.springframework.web.context.ContextLoader#configureAndRefreshWebApplicationContext
以上是 Spring 容器初始化逻辑,其中,CONFIG_LOCATION_PARAM 即是我们在 xml 中配置的 contextConfigLocation 参数:
同时还会将 Servlet 容器保存到 Spring 容器中,最后调用 refresh 方法进行初始化。
在将 Spring 容器初始化最后以一个元素的形式保存到 Servlet 容器之后,那么 SpringMVC 在初始化时,是如何拿到 Spring 容器的呢?
我们继续看 SpringMVC 初始化是怎么操作的。
SpringMVC 本质上来讲,就是一个大号的 Servlet,其各种机制都是围绕着一个名叫 DispatcherServlet 的 Servlet 展开的,因此它必然实现了 Servlet 接口,那么在 Tomcat 启动时,它必然会通过 Servlet#init 方法进行初始化动作,我在其调用链路上发现以下方法:
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
DispatcherServlet 的父类同样有一个方法,该方法是加载 SpringMVC 容器,即源码中的 webApplicationContext:
我们发现,rootContext 就是 ContextLoaderListener 加载的 Spring 容器,在这里,它会以父容器的身份保存到 SpringMVC 容器中。
当然,如果是用 Springboot 环境,那么默认只会存在一个上下文环境,原因如下:
1、在 Springboot 应用程序启动时,在 SpringBootServletInitializer#onStartup 方法中,会创建一个 rootAppContext 容器,如下:
同时将上文所说的 ContextLoaderListener 监听器添加到 Servlet 容器中,同样达到了 xml 配置的效果,而调用 createRootApplicationContext 方法创建 rootAppContext 容器时,会将 contextClass 设置为 AnnotationConfigServletWebServerApplicationContext.class。
2、DispatcherServlet 此时作为一个 Bean,实现了 ApplicationContextAware 接口,会自动将上下文环境保存到 webApplicationContext 字段中;
DispatcherServlet 初始化时,经过 debug 可以看到,rootContext 和 webApplicationContext 是同一个实例对象:
原因是通过 ContextLoaderListener 加载的上下文环境,通过 ApplicationContextAware 接口自动 set 进来保存到 DispatcherServlet 的 webApplicationContext 变量中了。
在 FrameworkServlet#initWebApplicationContext 方法最后,最终会将 webApplicationContext 注入以一个元素的形式保存到 Servlet 容器中:
最终,SpringMVC 初始化会调用该方法:
org.springframework.web.servlet.DispatcherServlet#onRefresh
DispatcherServlet 初始化时,从 Spring 容器中获取相关 Bean,初始化各种不同的组件,比如初始化 HandlerMapping:
本质上来讲,Servlet 容器与 Spring 容器并不互通,但因为有 Servlet 容器的监听器 ServletContextListener,在它们之间构筑了桥梁。