前言
年初面试时接触到一道面试题,在聊到SpringMVC时提到了SpringMVC的开发者为何要设计父子容器呢,又或者说是父子容器的设计有什么更实际的作用呢?
首先要理解对于一个web应用,当其部署在web容器上时,容器会为其提供一个全局上下文环境ServletContext,这个上下文环境将为后续的Spring提供宿主环境。
SpringMVC工作流程
DispatcherServlet上下文继承关系
SpringMVC设计的父子容器
父子容器配置文件
--在web.xml中配置,两个重要的xml:applicationContext.xml和SpringMVC-conf.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applictionContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springMVC-conf.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
父子容器的设计目的
根据SpringMVC的官方解释,父(根)容器主要包括一些基础脚手架的bean,比如Pool、DataSource、Dao、Service。目的是在不同的Servlet实例之间共享。这些不同的bean可以在子容器中重写。
而子容器主要包括一些Controller、View等一些web相关的bean。
DispatcherServlet源码分析
既然SpringMVC中同时包含Spring容器和SpringMVC容器,那么这两个容器都是在什么时候初始化呢?
根容器初始化
首先,根容器是通过ServletContext监听器进行创建,默认的监听器为ContextLoaderListener,当web应用启动时,会调用监听器的contextInitialized方法。
那么根容器的初始化就从ContextLoaderListener类说起吧,,Spring官方对该类的描述是启动监听器去启动和关闭Spring的root WebApplicationContext(翻译的实在有点蹩脚)。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
//===初始化root WebApplicationContext===
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
//ContextLoader.java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//初始化Spring容器时如果发现servlet 容器中已存在根Spring容根器则抛出异常,证明rootWebApplicationContext只能有一个。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
try {
//创建webApplicationContext实例
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext