Spring 源码阅读 之 Spring框架加载

  提及第一次阅读Spring Framework源码,大概仍是2010年吧,那个时候还不懂技巧和方法,一头扎在代码的汪洋大海里,出不来了。后面几年偶尔断断续续的也看过几回,都是不得要领,最后都是无疾而终。因此以为阅读这种大型的代码项目,很吃力,也很艰难,须要不断的坚持。 web

  最近项目不是很忙,下班早,就又把Spring的源码翻出来看看,也看了一段时间了,此次算是小有收获吧,因而打算学博客记录下来。 spring

  在这以前,并无打算在继续写博客,由于这里的这种讨厌的限制,并且也愈来愈不喜欢这里的风格。可是有以为,学过的东西,既然有价值,就记录下来吧,好记性不如烂笔头,不记得的时候能够打开看看。在这以前,个人一些学习笔记一直是使用为知笔记来记录的,如今把记录在为知笔记里的内容从新翻出来整理一下,帖子博客上吧。app

  好了,开始进入正题吧:
框架

----------------------------------------------------------------------------------------------------学习

  在使用Spring Framework的时候,各类教程都是介绍首先要在 web.xml 里面配置一个 listener,该 listener 会在web容器启动的时候依次加载,从而来加载Spring Framework。那就从这个 listener 开始阅读吧。this

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  ContextLoaderListener 实现了 ServletContextListener 接口,所以 ContextLoaderListener 监听了当前Web Application的生命周期,在 Web 容器启动时会调用其实现的 contextInitialized() 方法来加载Spring Framework框架,当容器终止时会调用 其 contextDestroyed() 方法来销毁资源。 ContextLoaderListener类的contextInitialized()方法是这样实现的:spa

public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

  这个方法很简单,可是我实在不明白框架的设计者为何要先判断 contextLoader 是否为 null,而后再经过 contextLoader 来调用 initWebApplicationContext() 方法,而为何不在 contextInitialized()方法里直接调用 initWebApplicationContext() 方法,一行代码就搞定的事,为何还这么麻烦? debug

  为何会有这样的疑问呢,由于initWebApplicationContext()方法是ContextLoader类的成员方法,而ContextLoaderListener 又继承自 ContextLoader,initWebApplicationContext()方法是用public进行修饰的,所以该方法也获得 ContextLoaderListener 继承,也就是说ContextLoaderListener也拥有此方法。那么做者为何还要在该类里增长一个ContextLoader类型的成员变量contextLoader,用 contextLoader 来调用 initWebApplicationContext()呢? 看了一下整个ContextLoaderListener类,contextLoader 都是调用其自身的方法,而这些方法也被ContextLoaderListener继承了,实在是想不通。设计

  进入 initWebApplicationContext() 方法,该方法实现了整个Spring的加载。该方法除去log和try/catch代码,真正的代码也不是不少。外表看似简单,可是里面的逻辑实现,彻底超乎个人想象,直接看代码吧: 
code

 1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
 2     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
 3         throw new IllegalStateException(
 4                 "Cannot initialize context because there is already a root application context present - " +
 5                 "check whether you have multiple ContextLoader* definitions in your web.xml!");
 6     }
 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 
15     try {
16         // Store context in local instance variable, to guarantee that
17         // it is available on ServletContext shutdown.
18         if (this.context == null) {
19             this.context = createWebApplicationContext(servletContext);
20         }
21         if (this.context instanceof ConfigurableWebApplicationContext) {
22             configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
23         }
24         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
25 
26         ClassLoader ccl = Thread.currentThread().getContextClassLoader();
27         if (ccl == ContextLoader.class.getClassLoader()) {
28             currentContext = this.context;
29         }
30         else if (ccl != null) {
31             currentContextPerThread.put(ccl, this.context);
32         }
33 
34         if (logger.isDebugEnabled()) {
35             logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
36                     WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
37         }
38         if (logger.isInfoEnabled()) {
39             long elapsedTime = System.currentTimeMillis() - startTime;
40             logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
41         }
42 
43         return this.context;
44     }
45     catch (RuntimeException ex) {
46         logger.error("Context initialization failed", ex);
47         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
48         throw ex;
49     }
50     catch (Error err) {
51         logger.error("Context initialization failed", err);
52         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
53         throw err;
54     }
55 }

    代码第二行,从 servletContext取出以 org.springframework.web.context.WebApplicationContext.ROOT 为key的对象,若是该对象不等于null,说明当前servletContext已经加载成功了Spring,则直接抛出异常。为何要作此判断呢?难道是当前Web容器启动了多个Web应用,每一个应用都使用了Spring框架框架吗?若是是这样,则每一个Web应用都对应着一个单独的ServletContext,是不会出现这种问题的,貌似也就只又在 web.xml里面配置多个 相似的加载 Spring Framework的listener才会触发此问题。好了,那 org.springframework.web.context.WebApplicationContext.ROOT 又是在什么时候放进 servletContext 的呢? 在第24行的地方,当代码执行到第24行的地方时,说明整个Spring框架已经加载并初始化完毕。   

    代码第29行,调用 createWebApplicationContext() 方法建立Spring Web应用上下文。Spring Web应用上下文是怎么建立的呢?下文在继续。。。

相关文章
相关标签/搜索