Spring 源码翻阅 之 Spring框架加载

Spring 源码阅读 之 Spring框架加载

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

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

  在这之前,并没有打算在继续写博客,因为这里的这种讨厌的限制,而且也越来越不喜欢这里的风格。但是有觉得,学过的东西,既然有价值,就记录下来吧,好记性不如烂笔头,不记得的时候可以打开看看。在这之前,我的一些学习笔记一直是使用为知笔记来记录的,现在把记录在为知笔记里的内容重新翻出来整理一下,帖子博客上吧。

  好了,开始进入正题吧:

----------------------------------------------------------------------------------------------------

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

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

 

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

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() 方法,一行代码就搞定的事,为什么还这么麻烦? 

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

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

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    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!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
        }
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, 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;
    }
}

 

 代码第二行,从 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应用上下文是怎么创建的呢?下文在继续。。。

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


开发者可以继承此类,然后覆写createContextLoader()生成自己的loader,这样的话,就使用用户自己的loader。这里是留了一个扩展点。