网上很多关键字针对了“spring源码阅读”,但仔细的查看了很多,发现大部分都是一样的,而且讲得很粗,可以借鉴这些思路.但粗粗的去读一下这些文章,对spring真正的实现,还是比较迷茫的,要想真正了解spring,还是得仔细的研读它的每一行代码,对照着别人的思路,静下心来,反复看,才能一点一点的去理解它。有些关键字在百度上讨论得很少的关键字,比如:“EntityResolver”,“PropertySource”,"propertyResolver"之类的,很多人的博文略提到了一点,但也是匆匆而过,这些,都需要自己去看document,自己去看代码,自己去想。
首先,从哪里开始读,我们的web工程要用到spring,必须在web的配置中引入spring的框架,那么,首先就需要去web.xml里找。contextLoaderListener正是我们要找的答案,同样的道理,log4j等等这些框架,也是在这里进行配置,我们通过listener的方式去引入它
<context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:spring/applicationcontext.xml, classpath*:spring/spring-*.xml, classpath*:spring/consumer/spring-*.xml, classpath*:spring/provider/spring-*.xml </param-value> </context-param> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>60000</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>这里可以看到,我们用到了spring的web包下的contextLoaderListener来作为整个spring容器加载的入口,这里,要稍微提一下,读spring的任何源码的时候,最好刻意的,让自己去看一下他的包路径,对理解它整个框架的划分,慢慢的会有一些好处。 下面,我们来看看contextLoaderListener.
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {从ServletContextListener继承而来,ServletContextListener是servlet包下的接口
/** * Implementations of this interface receive notifications about * changes to the servlet context of the web application they are * part of. * To receive notification events, the implementation class * must be configured in the deployment descriptor for the web * application. * @see ServletContextEvent * @since v 2.3 */ public interface ServletContextListener extends EventListener { /** ** Notification that the web application initialization ** process is starting. ** All ServletContextListeners are notified of context ** initialization before any filter or servlet in the web ** application is initialized. */ public void contextInitialized ( ServletContextEvent sce ); /** ** Notification that the servlet context is about to be shut down. ** All servlets and filters have been destroy()ed before any ** ServletContextListeners are notified of context ** destruction. */ public void contextDestroyed ( ServletContextEvent sce );可以看到,他有一个contextInitialized方法,这个方法在启动初始化时回调,因此,在contextLoaderListener中,找到这个方法的实现,就是我们需要的地方
public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); }好了,前4行代码忽略,createContextLoader()在spring2.5以后,变成了空实现,里面啥也没干,直接进入 contextLoader.initWebApplicationContext. 在进入之前,我们对它的名字思考一下,顾名思义, contextLoaderListner中有一个contextLoader对象,我们用它去初始化WebApplicationContext.用一个ServletContext作为参数,这里要先强调一下,读spring的代码,一定要注意它的一些抽象概念,比如context,ServletContext,Reader,Bean,这种名词,当你全部理解了这些名词的时候,你也就真的懂了 这里我们用到了这两个: 1.contextLoader: 读取器,用来读context的读取器,context是spring中一个非常重要的概念,我们可以简单的 理解为容器,如ApplicationContext. 2.ServletContext: servlet上下文,我们在很多时候写代码都需要一些贯穿始终的参数,在servlet环境中,web容器就是利用servletContext来作为一个参数进行传递,后续我们还会读到spring中的一些比如holder. propertySources等概念,也是这个用途 3.WebApplicationContext: web环境的spring容器,是容器的一种 接下来,进入initWebApplicationContext方法
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 // it is available on ServletContext shutdown. 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; } }我们看前3行,可以根据字面意思猜到,容器不能重复定义或重复加载,而且,容器创建好之后会放在ServletContext这个上下文中,并且key是一个常量,等会看到最后我们来验证这个猜测。 紧接着一堆日志,开始时间,这些我们都不关注 真正的加载代码,从try开始
this.context = createWebApplicationContext(servletContext);这行字面意思是,创建一个web环境的容器,接着
if (this.context instanceof ConfigurableWebApplicationContext) { configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext); }表示创建的web环境的容器,必须是configurable的,可配置的,假如是可配置的,就执行配置+刷新操作。 至于为什么可配置,怎么配置,什么叫配置,我们待会应该能看懂。 再往下
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);验证了我们之前的猜测,容器创建好之后放到servlet的上下文中 再后面的几行代码,我们就不用去关心了,至此,spring容器加载的最重要3步就是: 1.创建一个web applicationcontext, 2.配置并且刷新bean 3.把刷新好的context放到servlet上下文中去。 有了这三步,我们再去逐一的看看,他们分别是怎么实现的。后续还会遇到很多名词,在spring中,这些名词都有很深刻的含义,了解了这些名词才能深刻的了解spring,在(二)中我们去学习创建webApplicationContext.