说起第一次阅读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继承了,实在是想不通。
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应用上下文是怎么创建的呢?下文在继续。。。