在阅读Spring源码或相关文献时,会经常遇到这几个名词: WebApplicationContext
--- ApplicationContext
--- ServletContext
--- ServletConfig
.这些名词很相近但适用范围有所不同,容易造成 spring
内部实现的理解混淆,所以首先大致解释这几个名词.
ServletContext
:这个是来自Servlet规范里的概念,它是 Servlet
用来与容器间进行交互的接口的组合.也就是,这个接口定义了一系列的方法, Servlet
通过这些方法可以很方便地与所在的容器进行一些交互.从它的定义中也可以看出在一个应用中(一个 JVM
)只有一个 ServletContext
.也就是说,容器中所有的 Servlet
都共享一个 ServletContext
. ServletConfig
:它与 ServletContext
的区别在于, ServletConfig
是针对 servlet
而言的,每个 servlet
都有它独特的 ServletConfig
信息,相互之间不共享. ApplicationContext
:这个类是 Spring
容器功能的核心接口,它是 Spring
实现 IOC
功能最重要的接口.从它的名字可以看出,它维护了整个程序运行期所需要的上下文信息,注意这里的应用程序并不一定是 web
程序.在 Spring
中允许存在多个 ApplicationContext
,这些 ApplicationContext
相互之间形成父子,继承与被继承的关系,这也是通常我们所说的:在 Spring
中存在两个 context
,一个 Root Application Context
,一个是 Servlet Application Context
,这一点在后续会详细阐述. WebApplicaitonContext
:这个接口只是 ApplicationContext
接口的一个子接口,只不过它的应用形式是 web
,它在 ApplicaitonContext
的基础上,添加了对 ServletContext
的引用. 在 SpringMVC
配置文件中,我们通常会配置一个前端控制器 DispatcherServlet
和监听器 ContextLoaderListener
来进行 Spring
应用上下文的初始化
ServletContext
是容器中所有 Servlet
共享的配置,它是应用于全局的.根据 Servlet
规范的规定,根据以上监听器的配置,其中 context-param
指定了配置文件的未知.在容器启动后初始化 ServletContext
时,监听器会自动加载配置文件,来初始化 Spring
的根容器 Root Application Context
. ServletConfig
是针对每个 Servlet
进步配置的,因此它的配置是在 servlet
的配置中,根据以上 DispatcherServlet
的配置,配置中 init-param
同样指定了在 Servlet
初始化调用 #init
方法时加载配置信息的 xml
文件,并初始化 Spring
应用容器 Servlet Application Context
. ApplicationContext
的配置,首先,在 ServletContext
中配置 context-param
参数.通过监听器会生成所谓的 Root Application Context
,而每个 DispatcherServlet
中指定的 init-param
参数会生成 Servlet Application Context
.而且它的 parent
就是 ServletContext
中生成的 Root Application Context
.因此在 ServletContext
中定义的所有配置都会继承到 DispatcherServlet
中,这在之后代码中会有直观的提现. public class ContextLoaderListener extends ContextLoader implements ServletContextListener { //................... public void contextInitialized(ServletContextEvent event) { this.initWebApplicationContext(event.getServletContext()); } } -------------------------------------------------------------------------------------------------------------------------------------------------------- public class ContextLoader { //................... public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 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!"); } else { //................... try { if(this.context == null) { this.context = this.createWebApplicationContext(servletContext); } //................... servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); return this.context; } } } protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = this.determineContextClass(sc); if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } else { return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); } } protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter("contextClass"); if(contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } } } static { try { ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException var1) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage()); } currentContextPerThread = new ConcurrentHashMap(1); } }
为了方便阅读,这里把一些不是核心的代码过滤
ContextLoaderListener
对象来监听 ServletContext
初始化,在初始化方法中会调用父类 ContextLoader#initWebApplicationContext
方法来进行 initWebApplication
中首先判断是否存在 Root Application Context
,如果存在则抛出异常.之后通过 #createWebApplicationContext
方法来创建容器对象,并会将容器放入 ServletContext
中.所以对于 ApplicationContext
和 ServletContext
的区别就是 ApplicationContext
其实就是 ServletContext
中的一个属性值而已.这个属性中存有程序运行的所有上下文信息,由于这个 ApplicationContext
是全局的应用上下文,所以在 Spring
中称它为 "Root Application Context"
. #createWebApplicationContext
方法中可以看到它是通过实例化容器的class类来创建容器的,而在 #determineContextClass
方法中首先通过初始化参数来获取全路径类名,若不存在则通过配置类 #defaultStrategies
来获取容器名称. ContextLoader.properties
配置文件来获取当前容器全路径类名称 public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { public final void init() throws ServletException { //遍历获取servletConfig的所有参数 PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties); //初始化servlet applicationContext this.initServletBean(); } } -------------------------------------------------------------------------------------------------------------------------------------------------------- public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { protected final void initServletBean() throws ServletException { //................... try { this.webApplicationContext = this.initWebApplicationContext(); } } protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); WebApplicationContext wac = null; if(wac == null) { wac = this.createWebApplicationContext(rootContext); } if(this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); if(this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; } protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { return this.createWebApplicationContext((ApplicationContext)parent); } protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { Class<?> contextClass = this.getContextClass(); if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); wac.setParent(parent); wac.setConfigLocation(this.getContextConfigLocation()); this.configureAndRefreshWebApplicationContext(wac); return wac; } } }
接下来我们再看 DispatcherServlet
Servlet
,根据规范它的配置信息应该是在 #init
方法中完成,我们首先进入到 DispatcherServlet#init
,其方法是继承自父类 HttpServletBean
中.在父类的 #init
方法中,首先通过遍历获取 ServletConfig
的所有参数,然后进行 Servlet Application Context
的初始化 #initServletBean
位于父类 FrameworkServlet
中,在 #initServletBean
方法中调用 #initWebApplicationContex
t方法 initWebApplicationContext
方法中首先通过 ServletContext
获取 Root Application Context
,然后开始初始化 Servlet Application Context
,在创建容器的过程会传入 Root Application Context
作为它的 Parent
,也就是在这里两者建立父子关系,形成之前所说的继承关系,最后同样将新创建的容器放入 ContextServlet
中. 以上就是关于在 Spring
中容器的大致分析,我们会在项目启动时将不同的组件实例注入到 Spring
容器中,从而实现 Spring IOC
机制.
Spring
注解方式自定义 DispatcherServlet
相关内容可以参考: Spring中关于的WebApplicationInitializer及其实现的分析 SpringMVC
自定义配置化相关知识可以参考: