跟踪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是一个基于反射的工具类方法,它根据传入的类型和参数,构造该类型的一个实例。
至此,环境上下文就创建完成了,我们也可以回到之前标记的位置①处,继续跟踪其后的代码。
- 跟踪Spring源码(一)
- spring-@Profile源码跟踪
- spring启动过程之源码跟踪(上)--spring Debug
- spring启动过程之源码跟踪(中)--spring Debug
- spring启动过程之源码跟踪(下)--spring Debug
- spring启动过程之源码跟踪(下)--spring Debug
- spring启动过程之源码跟踪(上)--spring Debug
- Nginx 源码学习(一) nginx的跟踪与调试
- spring 源码分析(一)
- spring源码解析(一)
- Spring源码解析(一)
- spring源码学习(一)
- Spring源码分析(一)
- Spring源码学习(一)
- Spring源码-IOC(一)
- Spring源码学习(一源码下载)
- spring启动过程之源码跟踪(续beanfactory)--spring Debug
- spring启动过程之源码跟踪(小结bean的生命周期)--spring Debug
- Path Sum
- (未完成)ffmpeg tutorial 4 (播放视频) 解读
- JAVA面试
- Quartz CronTrigger最完整配置说明(简要版)
- 《算法帝国》
- 跟踪Spring源码(一)
- APPLE Swift编程语言入门教程
- jsp中文件下载的实现
- C语言标准库函数与功能讲解
- C#服务端与客户端
- Android中使用颜色值作为drawable
- 心跳包机制
- 类 Unix 系统下常用工具小结
- MyEclipse 10 中增加svn插件