【Spring源码--IOC容器的实现】(一)Web容器的启动

  • 前言
    1.由于大家平常用Spring基本都是Web项目中,那么今天就从Web的角度来看看IOC容器是怎么启动并管理Bean的。
    2.本文及后续代码版本:Spring3.0.5。所以如发现代码(或图)不一致请注意Spring版本。
    3.还是建议大家在前几遍读源码的时候,先把路走通,再回头研究细节。
  • Web容器接口体系

    为了方便在Web环境中使用IOC容器,Spring为Web应用提供了上下文接口WebApplicationContext来满足启动过程的需要。这个WebApplicationContext接口的类层次关系如图所示:

               在接口设计中,最后是通过ApplicationContext接口与BeanFactory接口对接的,而对于具体功能的实现,很多都是封装在AbstractRefreshableWebApplicationContext中完成的。再从代码的角度看下这个继承体系:

  • Web容器的启动
    1.web.xml中的配置

    为了使用Spring,我们一般都会在web.xml中配一个监听器。如下:

       
       
    1. <context-param>
    2. <param-name>contextConfigLocation</param-name>
    3. <param-value>classpath:applicationContext.xml</param-value>
    4. </context-param>
    5. <listener>
    6. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    7. </listener>

    那么容器启动的入口也就在这里了,context-param字面意思都可以理解,就是ServletContext的参数,这里我们配置了一个xml的路径。我们重点关注listener,这个Spring的ContextLoaderListener实现了ServletContextListener接口,这个接口是在Servlet API中定义的,提供了与Servlet生命周期结合的回调,比如contextInitialized和contextDestroy。那么,作为ServletContext的监听者,如果ServletContext发生变化,监听器就会做出相应的事件。比如:在服务器(tomcat等)启动时,ServletContextListener的contextInitialized方法被调用,服务器将要关闭时,contextDestroy方法被调用。我们来看下ContextLoaderListener的代码:

    代码1.1:ContextLoaderListener源码

 
 
  1. public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
  2. private ContextLoader contextLoader;
  3. /**
  4. * 初始化容器
  5. * Initialize the root web application context.
  6. */
  7. public void contextInitialized(ServletContextEvent event) {
  8. //这个方法实现的本意是提供一个占位符方法createContextLoader()给子类机会创建客户化的环境加载,但是,后来这个证明不是非常有用的,已经鼓励不再使用了,事实上,子类可以通过重写本方法达到同样的目的
  9. this.contextLoader = createContextLoader();
  10. if (this.contextLoader == null) {
  11. this.contextLoader = this;
  12. }
  13. this.contextLoader.initWebApplicationContext(event.getServletContext());
  14. }
  15. /**
  16. * Create the ContextLoader to use. Can be overridden in subclasses.
  17. * @return the new ContextLoader
  18. * @deprecated in favor of simply subclassing ContextLoaderListener itself
  19. * (which extends ContextLoader, as of Spring 3.0)
  20. */
  21. @Deprecated
  22. protected ContextLoader createContextLoader() {
  23. return null;
  24. }
  25. /**
  26. * Return the ContextLoader used by this listener.
  27. * @return the current ContextLoader
  28. * @deprecated in favor of simply subclassing ContextLoaderListener itself
  29. * (which extends ContextLoader, as of Spring 3.0)
  30. */
  31. @Deprecated
  32. public ContextLoader getContextLoader() {
  33. return this.contextLoader;
  34. }
  35. /**
  36. * 销毁容器的方法
  37. * Close the root web application context.
  38. */
  39. public void contextDestroyed(ServletContextEvent event) {
  40. if (this.contextLoader != null) {
  41. this.contextLoader.closeWebApplicationContext(event.getServletContext());
  42. }
  43. ContextCleanupListener.cleanupAttributes(event.getServletContext());
  44. }
  45. }

我们先重点关注容器的启动,也就是contextInitialized方法,从代码中可以看到它是调用的ContextLoader的initWebApplicationContext方法。我们来看下这个方法的代码:

代码1.2:ContextLoader的initWebApplicationContext方法

 
 
  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  2. //判断ServletContext中是否已经有根上下文存在,存在抛出异常
  3. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  4. throw new IllegalStateException(
  5. "Cannot initialize context because there is already a root application context present - " +
  6. "check whether you have multiple ContextLoader* definitions in your web.xml!");
  7. }
  8. Log logger = LogFactory.getLog(ContextLoader.class);
  9. servletContext.log("Initializing Spring root WebApplicationContext");
  10. if (logger.isInfoEnabled()) {
  11. logger.info("Root WebApplicationContext: initialization started");
  12. }
  13. long startTime = System.currentTimeMillis();
  14. try {
  15. //载入双亲上下文(通常是不存在)
  16. ApplicationContext parent = loadParentContext(servletContext);
  17. // 创建WebApplicationContext
  18. this.context = createWebApplicationContext(servletContext, parent);
  19. //把创建的根context保存到Servlet环境中
  20. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  21. //取得线程的类加载器
  22. ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  23. if (ccl == ContextLoader.class.getClassLoader()) {
  24. //如果线程和本类拥有相同的类加载器,则使用静态变量保存即可,因为同一类加载器加载同一份静态变量
  25. currentContext = this.context;
  26. }
  27. else if (ccl != null) {
  28. //如果线程和本类拥有不同的类加载器,则使用线程的类加载器作为key在保存在一个映射对象里,保证析构时能拿到Webcontext进行关闭操作
  29. currentContextPerThread.put(ccl, this.context);
  30. }
  31. if (logger.isDebugEnabled()) {
  32. logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
  33. WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
  34. }
  35. if (logger.isInfoEnabled()) {
  36. long elapsedTime = System.currentTimeMillis() - startTime;
  37. logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
  38. }
  39. return this.context;
  40. }
  41. catch (RuntimeException ex) {
  42. logger.error("Context initialization failed", ex);
  43. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
  44. throw ex;
  45. }
  46. catch (Error err) {
  47. logger.error("Context initialization failed", err);
  48. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
  49. throw err;
  50. }
  51. }

这里我们重点关注createWebApplicationContext(servletContext, parent)这个方法,这里是真正生产容器的地方。至于载入双亲上下文的方法,是根据我们ServletContext的参数取的,很明显,我们在上面的web.xml中只配置了xml的路径,并没有配置双亲上下文。想研究的同学可以去看下代码,这里不再多述。那么我们来看看这个创建容器的方法。
代码1.2:ContextLoader的createWebApplicationContext方法

 
 
  1. protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
  2. //取得配置的Web应用程序环境类,如果没有配置,则使用缺省的类XmlWebApplicationContext
  3. Class<?> contextClass = determineContextClass(sc);
  4. //如果配置的Web应用程序环境类不是可配置的Web应用程序环境的子类,则抛出异常,停止初始化
  5. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  6. throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
  7. "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  8. }
  9. //实例化需要产生的IOC容器,并设置容器的各种参数,然后通过refresh启动容器初始化
  10. ConfigurableWebApplicationContext wac =
  11. (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  12. // 设置IOC容器的的ID
  13. if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
  14. // 如果 Servlet规范 <= 2.4,则使用web.xml里定义的应用程序名字定义Web应用程序名
  15. String servletContextName = sc.getServletContextName();
  16. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  17. ObjectUtils.getDisplayString(servletContextName));
  18. }
  19. else {
  20. // 如果Servlet规范是 2.5, 则使用配置的ContextPath定义Web应用程序名
  21. try {
  22. String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);
  23. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  24. ObjectUtils.getDisplayString(contextPath));
  25. }
  26. catch (Exception ex) {
  27. //如果Servlet规范是2.5,但是不能取得ContextPath,抛出异常
  28. throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
  29. }
  30. }
  31. //设置双亲上下文
  32. wac.setParent(parent);
  33. //保存Servlet环境
  34. wac.setServletContext(sc);
  35. //这里其实就是设置xml的位置
  36. wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
  37. //提供子类可互换Web应用程序环境的机会
  38. customizeContext(sc, wac);
  39. //刷新Web应用程序环境以加载Bean定义【重点】
  40. wac.refresh();
  41. return wac;
  42. }

这个refresh方法是我们后面讲IOC容器的重点,这里web容器启动也就到这里了。后面是真正的IOC怎么加载Resource、解析BeanDefinition、注册、依赖注入。


参考文献:

【Spring技术内幕】第二版

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值