spring源码研究之IoC容器在web容器中初始化过程
转载自 http://ljbal.iteye.com/blog/497314
前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spring的IoC容器在web容器如何启动和起作用的并不清楚。所以就抽时间看一下spring的源代码,借此了解它的原理。
我们知道,对于使用Spring的web应用,无须手动创建Spring容器,而是通过配置文件,声明式的创建Spring容器。因此在Web应用中创建Spring容器有如下两种方式:
1. 直接在web.xml文件中配置创建Spring容器。
2. 利用第三方MVC框架的扩展点,创建Spring容器。
其实第一种方式是更加常见。为了让Spring容器随Web应用的启动而启动,有如下两种方式:
1. 利用ServletContextListener实现。
2. 利用load-on-startup Servlet实现。
Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类可以作为Listener 使用,它会在创建时自动查找WEB-INF下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,则只需在web.xml文件中增加以下配置片段就可以了。
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
如果有多个配置文件需要载入,则考虑使用<context-param...>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contentConfigLocation的初始化参数。因此,配置<context-param...>时就指定参数名为contextConfigLocation。
带多个配置文件的web.xml文件如下:
- <context-param>
- <param-name>contextLoaderListener</param-name>
- <param-value>
- WEB-INF/*.xml, classpath:spring/*.xml
- </param-value>
- </context-param>
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
多个配置文件之间用“,”隔开。
下面我们来看它的具体实现过程是怎样的,首先我们从ContextLoaderListener入手,它的代码如下:
- public class ContextLoaderListener implements ServletContextListener
- {
- private ContextLoader contextLoader;
- /**
- * 这个方法就是用来初始化web application context的
- */
- public void contextInitialized(ServletContextEvent event)
- {
- this.contextLoader = createContextLoader();
- this.contextLoader.initWebApplicationContext(event.getServletContext());
- }
- /**
- * 创建一个contextLoader.
- * @return the new ContextLoader
- */
- protected ContextLoader createContextLoader()
- {
- return new ContextLoader();
- }
- ................
- }
我们看到初始化web application context的时候,首先通过new ContextLoader()创建一个contextLoader,
new ContextLoader()具体做了什么事呢?ContextLoader的代码片段:
- static {
- try {
- // 这里创建一个ClassPathResource对象,载入ContextLoader.properties,用于创建对应的ApplicationContext容器
- // 这个文件跟ContextLoader类在同一个目录下,文件内容如:
- // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
- // 如此说来,spring默认初始化的是XmlWebApplicationContext
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
- // 得到一个Properties对象,后面根据类名来创建ApplicationContext容器
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
- }
- }
代码注释里面已经说得很清楚了,很容易理解吧?嘿嘿......
再下来我们再看一下initWebApplicationContext方法的实现过程:
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
- throws IllegalStateException, BeansException {
- // 从servletContext中获取ApplicationContext容器;如果已经存在,则提示初始化容器失败,检查web.xml文件中是否定义有多个容器加载器
- // ServletContext接口的简述:public interface ServletContext
- // 定义了一系列方法用于与相应的servlet容器通信,比如:获得文件的MIME类型,分派请求,或者是向日志文件写日志等。
- // 每一个web-app只能有一个ServletContext,web-app可以是一个放置有web application 文件的文件夹,也可以是一个.war的文件。
- // ServletContext对象包含在ServletConfig对象之中,ServletConfig对象在servlet初始化时提供servlet对象。
- 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!");
- }
- servletContext.log("Initializing Spring root WebApplicationContext");
- if (logger.isInfoEnabled()) {
- logger.info("Root WebApplicationContext: initialization started");
- }
- long startTime = System.currentTimeMillis();
- try {
- // Determine parent for root web application context, if any.
- // 获取父容器
- ApplicationContext parent = loadParentContext(servletContext);
- // Store context in local instance variable, to guarantee that
- // it is available on ServletContext shutdown.
- // 创建ApplicationContext容器
- this.context = createWebApplicationContext(servletContext, parent);
- // 把容器放入到servletContext中
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
- if (logger.isDebugEnabled()) {
- logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
- WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
- }
- if (logger.isInfoEnabled()) {
- long elapsedTime = System.currentTimeMillis() - startTime;
- logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
- }
- return this.context;
- }
- catch (RuntimeException ex) {
- logger.error("Context initialization failed", ex);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
- throw ex;
- }
- catch (Error err) {
- logger.error("Context initialization failed", err);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
- throw err;
- }
- }
从上面的代码可以看出,我们创建好的applicationContext容器会放在servletContext中。servletContext是什么 呢?
在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。
从initWebApplicationContext中可以看到真正创建applicationContext容器是由createWebApplicationContext方法来实现的,它的代码如下:
- protected WebApplicationContext createWebApplicationContext(
- ServletContext servletContext, ApplicationContext parent) throws BeansException
- {
- // 首先决定要创建的applicationContext容器的类
- Class contextClass = determineContextClass(servletContext);
- // 如果获取到的类不是ConfigurableWebApplicationContext类型的,则创建容器失败,所以这里创建的容器必须是ConfigurableWebApplicationContext类型的
- if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
- {
- throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
- "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
- }
- // 实例化spring容器
- ConfigurableWebApplicationContext wac =
- (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
- wac.setParent(parent);
- wac.setServletContext(servletContext);
- // 获取contextConfigLocation初始化参数,该参数记录的是需要载入的多个配置文件(即定义bean的配置文件)
- String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
- if (configLocation != null)
- {
- wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
- ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
- wac.refresh();
- return wac;
- }
createWebApplicationContext方法实现步骤为:
1. 首先决定要创建的applicationContext容器的类
2. 实例化applicationContext容器
但它是如何决定要创建的容器类呢?我们看一下determineContextClass方法:
- protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException
- {
- // 从web.xml中获取需要初始化的容器的类名
- String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
- // 如果获取到的类名不为空,则创建该容器的Class对象
- if (contextClassName != null)
- {
- try {
- return ClassUtils.forName(contextClassName);
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load custom context class [" + contextClassName + "]", ex);
- }
- }
- // 否则创建默认的容器的Class对象,即:org.springframework.web.context.support.XmlWebApplicationContext
- // 在创建ContextLoader时,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);这句代码已经准备好默认的容器类
- else
- {
- contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
- try
- {
- return ClassUtils.forName(contextClassName);
- }
- catch (ClassNotFoundException ex)
- {
- throw new ApplicationContextException(
- "Failed to load default context class [" + contextClassName + "]", ex);
- }
- }
- }
该方法首先判断从web.xml文件的初始化参数CONTEXT_CLASS_PARAM(的定义为public static final String CONTEXT_CLASS_PARAM = "contextClass";)获取的的类名是否存在,如果存在,则容器的Class;否则返回默认的
Class。如何获取默认的容器Class,注意看创建contextLoader时的代码注释就知道了。
由此看来,spring不仅有默认的applicationContext的容器类,还允许我们自定义applicationContext容器类,不过Spring不建义我们自定义applicationContext容器类。
好了,这就是spring的IoC容器在web容器如何启动和起作用的全部过程。细心的朋友可以看出创建applicationContext容器的同时会初始化配置文件中定义的bean类,createWebApplicationContext方法中的wac.refresh();这段代码就是用来初始化配置文件中定义的bean类的。它具体的实现过程现在还没完全搞清楚,等搞清楚了再跟大家分享!
spring源码研究之IoC容器在web容器中初始化过程的更多相关文章
- 【spring源码分析】IOC容器初始化(总结)
前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...
- 【spring源码分析】IOC容器初始化(二)
前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...
- 【spring源码分析】IOC容器初始化(三)
前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...
- 【spring源码分析】IOC容器初始化(四)
前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...
- 【spring源码分析】IOC容器初始化(七)
前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...
- 【spring源码分析】IOC容器初始化(十)
前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...
- 【spring源码分析】IOC容器初始化——查漏补缺(一)
前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...
- 转 Spring源码剖析——核心IOC容器原理
Spring源码剖析——核心IOC容器原理 2016年08月05日 15:06:16 阅读数:8312 标签: spring源码ioc编程bean 更多 个人分类: Java https://blog ...
- Spring源码分析专题 —— IOC容器启动过程(上篇)
声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...
随机推荐
- sed、grep、awk -- 三剑客笔记记录
sed常用操作笔记 1.删除文件最后一行: sed -i '$d' filename 2.递归替换内容:sed -i 's/内容A/内容B/g' filename sed -i "s/S ...
- linux编辑器使用记录
超强大vim配置文件: wget http://files.cnblogs.com/ma6174/vimrc.zip unzip -f vimrc.zip -d ~/ 一.vim编辑器 进入 ...
- IDA Pro 权威指南学习笔记(九) - 数据搜索
Search -> Next Code 命令将光标移动到下一个包含指令的位置 Jump -> Jump to Function 命令可以打开所有函数,可以迅速选择一个函数并导航到该函数所在 ...
- "网页安全政策"(Content Security Policy,缩写 CSP)的来历
作者:阿里聚安全链接:https://www.zhihu.com/question/21979782/answer/122682029来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载 ...
- 自动删除 Elasticsearch 索引
#!/bin/bash # author: Wang XiaoQiang# crontab -e# 0 0 * * * /root/script/del_esindex.sh # auto delet ...
- Eclipse debug 的 drop to frame 的技巧
前些天和同事交流调试技巧时,知道了 Eclipse debug 时有个 drop to frame 的技巧.这是我以前不知道的,自己又查了一下这个功能的含义.官方的解释是: Select the Dr ...
- C# 文本文件的读写
// *********************************************************************** // Assembly : XXX // Auth ...
- My97DatePicker日期控件的使用
本文演示如何在MyEclipse项目中使用My97DatePicker日期控件 1.下载My97DatePicker日期控件, My97DatePicker日期控件下载地址 2.在MyEclipse项 ...
- MySQL内置功能之事务、函数和流程控制
主要内容: 一.事务 二.函数 三.流程控制 1️⃣ 事务 一.何谓事务? 事务用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性. # ...
- MySQL数据库篇之多表查询
主要内容: 一.多表连接查询 二.复合条件连接查询 三.子查询 1️⃣ 多表连接查询 一.准备表 #建表 create table department( id int, name varchar( ...