跟踪Spring源码(一)

来源:互联网 发布:机械研发 知乎 编辑:程序博客网 时间:2024/05/18 00:07
一、上下文加载器

要在项目中使用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是一个基于反射的工具类方法,它根据传入的类型和参数,构造该类型的一个实例。

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

0 0
原创粉丝点击