网上很多关键字针对了“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.