共计 7494 个字符,预计需要花费 19 分钟才能阅读完成。
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
自定义配置化相关知识可以参考:
正文完