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 容器 ...
随机推荐
- Windows 10 提权漏洞复现及武器化利用
项目地址:https://github.com/SandboxEscaper/randomrepo 相关工具的下载地址: Process Explorer:https://docs.microsoft ...
- Error: listen EACCES 0.0.0.0:8080 错误解决记录
live-server -- 热加载利器 实现本地服务器,可及时刷新. 1.通过npm install -g live-server进行安装 2.npm init 初始化项目3.在所需要的文件夹内运行 ...
- ffmpeg最简单的解码保存YUV数据 <转>
video的raw data一般都是YUV420p的格式,简单的记录下这个格式的细节,如有不对希望大家能指出. YUV图像通常有两种格式,一种是packet 还有一种是planar 从字面上 ...
- 找不到 org/springframework/dao/support/PersistenceExceptionTranslator
如果用的spring2 则原因是缺少spring-dao.jar 如果用的是spring3(我就栽这儿了) 则原因是缺少org.springframework.transaction-3.0.4.R ...
- Zookeeper的基本概念和重要特性
目录 1. 什么是Zookeeper 2. Zookeeper集群角色 3. Zookeeper的数据模型 3.1 Znode的类型 3.2 Znode的结构 4. Zookeeper的事件监听机制 ...
- cocos2dx 屏幕分辨率问题
做手机上的软件首先要考虑的就是屏幕分辨率怎么解决.coco2dx已经有了很好的解决方法. 用cocos2dx的python脚本创建工程时默认生成一个Helloworld的demo.我们就以这个demo ...
- MYSQL中GROUP BY的细节及SELECT语句顺序
一.GROUP BY语句的细节 我们知道,在sql中,GROUP BY语句主要用来给数据分组,以便能对每个组进行聚集计算,但是GROUP BY也有一些限制需要知道: 1. GROUP BY字句可以包含 ...
- 设计模式--单例模式(Singleton)详解
单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频繁,对于一些大型的对象,这是一笔 ...
- libpcap编程实例
#include <stdio.h> #include <stdlib.h> #include <pcap.h> #include <errno.h> ...
- DWR 3.0 入门示例教程
DWR(Direct Web Remoting) DWR is a Java library that enables Java on the server and JavaScript in a b ...