跟踪Spring源码(一)

自闭症网瘾萝莉.ら 提交于 2021-01-23 23:34:17
一、上下文加载器

要在项目中使用Spring框架,需要在web.xml做如下配置:

<!--contextConfigLocation在 ContextLoaderListener类中的默认值是 /WEB-INF/applicationContext.xml-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
    <!-- <param-value>classpath:applicationContext*.xml</param-value> -->
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

以下为类ContextLoaderListener的声明:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
    // 上下文加载器
    private ContextLoader contextLoader;
          
    // 无参构造放
    public ContextLoaderListener() {
    }
      
    // 带参构造函数
    public ContextLoaderListener(WebApplicationContext context){
        super(context);
    }
  
    // ......其它代码
}

ServletContextListener是Servlet API中的一个接口,它能够坚挺ServletContext对象的生命周期,也就是监听整个Web应用的生命周期。在Servlet容器启动时,会触发ServletContextEvent事件,这个事件就由ServletContextListener来处理。在ServletContextListener接口定义了两个处理ServletContextEvent事件的方法,分别是Web应用初始化时的contextInitialized方法和Web应用销毁时contextDestroyed方法。

/**
 * Initialize the root web application context.
 */
public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

上面的代码是Spring的ContextLoaderListener中覆写的初始化监听方法,所有支撑Spring工作的初始化工作,都在这个方法中完成。在这个方法中,第一行通过createContextLoader()创建一个上下文的加载器,不过在Spring中,这个方法的实现仅仅返回了一个null,同时注释中提示,它可以被子类覆写。另外一方面,这个方法已经过时。所以,整个初始化工作的关键,就是最后一行代码:

this.contextLoader.initWebApplicationContext(event.getServletContext());

由于createContextLoader()方法的不作为,实际上this.contextLoader就是对象本身的this。也就是说,如果没有覆写createContextLoader方法,那么默认的上下文加载器就会是ContextLoaderListener自身。

二、Web应用上下文

至于initWebApplicationContext方法,ContextLoaderListener本身并没有实现,而是它继承自ContextLoader的一个方法,源码如下:

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【将context保存在一个实例变量中,以保证】
            // it is available on ServletContext shutdown.                【在ServletContext关闭时可用。】
            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;
        }
    }

这个方法的作用,主要是初始化Web应用的环境上下文context,ContextLoader持有这样一个成员变量,类型为WebApplicationContext。在这个方法中,首先判断是否已经存在了一个上下文加载器,如果已经存在而又去初始化加载器,就会抛出一个IllegalStateException异常,提示无法完成上下文的初始化,要求检查是否在web.xml中配置了多个上下文加载器。如果没有抛出这个异常,接下来就要做一些准备工作,比如打印日志,记录时间戳,但这些都不是这个方法的重点。

if (this.context == null) {
    this.context = createWebApplicationContext(servletContext); // ①稍后回来
}

上面的代码是创建Web应用上下文的关键点,我们先在这里打上一个标记。方法createWebApplicationContext的源码如下:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    return wac;
}

第一行代码用于决定使用何种方式创建环境上下文context,比如注解方式创建上下文或者XML配置方式创建上下文,这两种方式分别对应了AnnotationConfigWebApplicationContext和XmlWebApplicationContext类,它们都实现了ConfigurableWebApplicationContext接口。显然,我们需要关注determineContextClass(sc)究竟返回了一个什么样的创建方式对应的类。

protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

观察源码发现,上下文类的获取方式有两种,一是从ServletContext中获取初始化参数CONTEXT_CLASS_PARAM(取值为contextClass),这个可以通过在web.xml中配置context节点注入参数(存疑);另一种是从defaultStrategies获取默认的上下文类。我们先来关注下,默认的应用上下文是什么。defaultStrategies是Properties类的一个实例,Properties类则继承了HashMap。所以,Properties.getProperty相当于HashMap.get。同时defaultStrategies也是ContextLoader的一个静态成员变量。既然我们要从defaultStrategies中get一些属性,就需要确认defaultStrategies的初始化位置。

在ContextLoader声明defaultStrategies变量之后,紧接着就是对该变量的初始化代码,如下:

/**
 * Name of the class path resource (relative to the ContextLoader class)
 * that defines ContextLoader's default strategy names.
 */
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
 
private static final Properties defaultStrategies;
  
static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    }
}

从上面可以看出,defaultStrategies默认是使用了一个属性文件进行填充,至于ClassPathResource类和PropertiesLoaderUtils以及PropertiesLoaderUtils的loadProperties方法,这里就不继续跟下去了,否则又是一个没完没了的封装。我们直接来看看,是哪个属性文件保存着默认的上下文类。这里有一个DEFAULT_STRATEGIES_PATH变量,取值是ContextLoader.properties,它的位置其实已经通过注释给出了:class path资源文件的名称(相对于ContextLoader类),它定义了上下文加载器的默认策略。我们可以很轻松的在spring-web.jar中org.springframework.web.context包下找到这个ContextLoader.properties文件,打开可以看到它的内容如下:

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

所以,绕了这么大个圈子,spring默认就是使用XML配置的方式创建并初始化上下文环境。

再回到createWebApplicationContext方法中,由之前一系列跟踪我们知道,contextClass默认是XmlWebApplicationContext类,至于创建这个类的实例的工作,也就是创建真正的应用环境上下文的工作,就由下面的代码完成:

ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

​BeanUtils.instantiateClass是一个基于反射的工具类方法,它根据传入的类型和参数,构造该类型的一个实例。

至此,环境上下文就创建完成了,我们也可以回到之前标记的位置①处,继续跟踪其后的代码。

版权声明:本文为博主原创文章,未经博主允许不得转载。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!