说起第一次阅读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代码,真正的代码也不是很多。外表看似简单,但是里面的逻辑实现,完全超乎我的想象,直接看代码吧:
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应用上下文是怎么创建的呢?下文在继续。。。