1.Spring中的几个概念
在阅读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
的引用.
2.Spring容器的初始化
在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
.
接下来我们具体分析Spring容器初始化:
- 关于
ApplicationContext
的配置,首先,在ServletContext
中配置context-param
参数.通过监听器会生成所谓的Root Application Context
,而每个DispatcherServlet
中指定的init-param
参数会生成Servlet Application Context
.而且它的parent
就是ServletContext
中生成的Root Application Context
.因此在ServletContext
中定义的所有配置都会继承到DispatcherServlet
中,这在之后代码中会有直观的提现.
1. Root Application Context
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
配置文件来获取当前容器全路径类名称
2. Servlet Application Context
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
中.
3. 总结
以上就是关于在
Spring
中容器的大致分析,我们会在项目启动时将不同的组件实例注入到Spring
容器中,从而实现Spring IOC
机制.
- 若想要了解如何通过
Spring
注解方式自定义DispatcherServlet
相关内容可以参考:Spring中关于的WebApplicationInitializer及其实现的分析- 若想要了解关于
SpringMVC
自定义配置化相关知识可以参考: