转载自 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文件中增加以下配置片段就可以了。

  1. <listener>
  2. <listener-class>
  3. org.springframework.web.context.ContextLoaderListener
  4. </listener-class>
  5. </listener>

如果有多个配置文件需要载入,则考虑使用<context-param...>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contentConfigLocation的初始化参数。因此,配置<context-param...>时就指定参数名为contextConfigLocation。

带多个配置文件的web.xml文件如下:

  1. <context-param>
  2. <param-name>contextLoaderListener</param-name>
  3. <param-value>
  4. WEB-INF/*.xml, classpath:spring/*.xml
  5. </param-value>
  6. </context-param>
  1. <listener>
  2. <listener-class>
  3. org.springframework.web.context.ContextLoaderListener
  4. </listener-class>
  5. </listener>

多个配置文件之间用“,”隔开。

下面我们来看它的具体实现过程是怎样的,首先我们从ContextLoaderListener入手,它的代码如下:

  1. public class ContextLoaderListener implements ServletContextListener
  2. {
  3. private ContextLoader contextLoader;
  4. /**
  5. * 这个方法就是用来初始化web application context的
  6. */
  7. public void contextInitialized(ServletContextEvent event)
  8. {
  9. this.contextLoader = createContextLoader();
  10. this.contextLoader.initWebApplicationContext(event.getServletContext());
  11. }
  12. /**
  13. * 创建一个contextLoader.
  14. * @return the new ContextLoader
  15. */
  16. protected ContextLoader createContextLoader()
  17. {
  18. return new ContextLoader();
  19. }
  20. ................
  21. }

我们看到初始化web application context的时候,首先通过new ContextLoader()创建一个contextLoader,

new ContextLoader()具体做了什么事呢?ContextLoader的代码片段:

  1. static {
  2. try {
  3. // 这里创建一个ClassPathResource对象,载入ContextLoader.properties,用于创建对应的ApplicationContext容器
  4. // 这个文件跟ContextLoader类在同一个目录下,文件内容如:
  5. // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
  6. // 如此说来,spring默认初始化的是XmlWebApplicationContext
  7. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
  8. // 得到一个Properties对象,后面根据类名来创建ApplicationContext容器
  9. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  10. }
  11. catch (IOException ex) {
  12. throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
  13. }
  14. }

代码注释里面已经说得很清楚了,很容易理解吧?嘿嘿......

再下来我们再看一下initWebApplicationContext方法的实现过程:

  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
  2. throws IllegalStateException, BeansException {
  3. // 从servletContext中获取ApplicationContext容器;如果已经存在,则提示初始化容器失败,检查web.xml文件中是否定义有多个容器加载器
  4. // ServletContext接口的简述:public interface ServletContext
  5. // 定义了一系列方法用于与相应的servlet容器通信,比如:获得文件的MIME类型,分派请求,或者是向日志文件写日志等。
  6. // 每一个web-app只能有一个ServletContext,web-app可以是一个放置有web application 文件的文件夹,也可以是一个.war的文件。
  7. // ServletContext对象包含在ServletConfig对象之中,ServletConfig对象在servlet初始化时提供servlet对象。
  8. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  9. throw new IllegalStateException(
  10. "Cannot initialize context because there is already a root application context present - " +
  11. "check whether you have multiple ContextLoader* definitions in your web.xml!");
  12. }
  13. servletContext.log("Initializing Spring root WebApplicationContext");
  14. if (logger.isInfoEnabled()) {
  15. logger.info("Root WebApplicationContext: initialization started");
  16. }
  17. long startTime = System.currentTimeMillis();
  18. try {
  19. // Determine parent for root web application context, if any.
  20. // 获取父容器
  21. ApplicationContext parent = loadParentContext(servletContext);
  22. // Store context in local instance variable, to guarantee that
  23. // it is available on ServletContext shutdown.
  24. // 创建ApplicationContext容器
  25. this.context = createWebApplicationContext(servletContext, parent);
  26. // 把容器放入到servletContext中
  27. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  28. if (logger.isDebugEnabled()) {
  29. logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
  30. WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
  31. }
  32. if (logger.isInfoEnabled()) {
  33. long elapsedTime = System.currentTimeMillis() - startTime;
  34. logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
  35. }
  36. return this.context;
  37. }
  38. catch (RuntimeException ex) {
  39. logger.error("Context initialization failed", ex);
  40. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
  41. throw ex;
  42. }
  43. catch (Error err) {
  44. logger.error("Context initialization failed", err);
  45. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
  46. throw err;
  47. }
  48. }

从上面的代码可以看出,我们创建好的applicationContext容器会放在servletContext中。servletContext是什么  呢?

在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。

从initWebApplicationContext中可以看到真正创建applicationContext容器是由createWebApplicationContext方法来实现的,它的代码如下:

  1. protected WebApplicationContext createWebApplicationContext(
  2. ServletContext servletContext, ApplicationContext parent) throws BeansException
  3. {
  4. // 首先决定要创建的applicationContext容器的类
  5. Class contextClass = determineContextClass(servletContext);
  6. // 如果获取到的类不是ConfigurableWebApplicationContext类型的,则创建容器失败,所以这里创建的容器必须是ConfigurableWebApplicationContext类型的
  7. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
  8. {
  9. throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
  10. "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  11. }
  12. // 实例化spring容器
  13. ConfigurableWebApplicationContext wac =
  14. (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  15. wac.setParent(parent);
  16. wac.setServletContext(servletContext);
  17. // 获取contextConfigLocation初始化参数,该参数记录的是需要载入的多个配置文件(即定义bean的配置文件)
  18. String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
  19. if (configLocation != null)
  20. {
  21. wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
  22. ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
  23. }
  24. wac.refresh();
  25. return wac;
  26. }

createWebApplicationContext方法实现步骤为:

1. 首先决定要创建的applicationContext容器的类
    2. 实例化applicationContext容器

但它是如何决定要创建的容器类呢?我们看一下determineContextClass方法:

  1. protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException
  2. {
  3. // 从web.xml中获取需要初始化的容器的类名
  4. String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
  5. // 如果获取到的类名不为空,则创建该容器的Class对象
  6. if (contextClassName != null)
  7. {
  8. try {
  9. return ClassUtils.forName(contextClassName);
  10. }
  11. catch (ClassNotFoundException ex) {
  12. throw new ApplicationContextException(
  13. "Failed to load custom context class [" + contextClassName + "]", ex);
  14. }
  15. }
  16. // 否则创建默认的容器的Class对象,即:org.springframework.web.context.support.XmlWebApplicationContext
  17. // 在创建ContextLoader时,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);这句代码已经准备好默认的容器类
  18. else
  19. {
  20. contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
  21. try
  22. {
  23. return ClassUtils.forName(contextClassName);
  24. }
  25. catch (ClassNotFoundException ex)
  26. {
  27. throw new ApplicationContextException(
  28. "Failed to load default context class [" + contextClassName + "]", ex);
  29. }
  30. }
  31. }

该方法首先判断从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容器中初始化过程的更多相关文章

  1. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  2. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  3. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

  4. 【spring源码分析】IOC容器初始化(四)

    前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...

  5. 【spring源码分析】IOC容器初始化(七)

    前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...

  6. 【spring源码分析】IOC容器初始化(十)

    前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...

  7. 【spring源码分析】IOC容器初始化——查漏补缺(一)

    前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...

  8. 转 Spring源码剖析——核心IOC容器原理

    Spring源码剖析——核心IOC容器原理 2016年08月05日 15:06:16 阅读数:8312 标签: spring源码ioc编程bean 更多 个人分类: Java https://blog ...

  9. Spring源码分析专题 —— IOC容器启动过程(上篇)

    声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...

随机推荐

  1. 前端学习---JavaScript

    JavaScript基本知识 JavaScript是一门独立的语言,像我们学习php,python等需要安装apache,python3.6,那我们学习JavaScript只需要我们电脑有一个浏览器即 ...

  2. spring-boot-actuator健康监控

    #健康监控 management.security.enabled=false health.mail.enabled =false http://localhost:54001/autoconfig ...

  3. ffmpeg源码分析四:transcode_step函数 (转4)

    原帖地址:http://blog.csdn.net/austinblog/article/details/25099979 该函数的主要功能是一步完整的转换工作,下面看看源代码: static int ...

  4. java 蓝桥杯算法提高 _3K好数

    nums[i][j] 存的是i位数的时候,首位数字是j的K好数的数目,i从1位开始的结果,去算2位时的结果,去算3位时的结果...最后得到l位的结果.K进制只是一个范围. import java.ut ...

  5. python's is&==区别

    [python's is&==区别] 通常我们写: if foo is None: pass 这个写法与以下的写法有何区别呢? if foo == None: pass is当比较的是相同的对 ...

  6. Python中装饰器(转)

    本文由 伯乐在线 - 7even 翻译,艾凌风 校稿.未经许可,禁止转载!英文出处:Simeon Franklin.欢迎加入翻译组. 好吧,我标题党了.作为 Python 教师,我发现理解装饰器是学生 ...

  7. Opencv 图像矩

    #include <iostream>#include <opencv2/opencv.hpp> using namespace std;using namespace cv; ...

  8. 面向对象的JavaScript-007-Function.prototype.bind() 的4种作用

    1. // Function.prototype.bind() 的作用 // 1.Creating a bound function this.x = 9; var module = { x: 81, ...

  9. code3027 线段覆盖2

    dp 数据:d[i].a d[i].b d[i].v 分别表示第i条线段的起始点,结束点,价值 先按d[i].b排好序 dp[i]表示前i条线段的最大价值 方程: dp[i]=max{ dp[i-1] ...

  10. js改变触发

    onchange="doEmpty($(this))"