首先我们来看一段web.xml中的配置:

<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:/applicationContext.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener></web-app>

有经验的都知道上面一段配置是关于web集成Spring的,每个初次学习Spring的,心中都会疑问为何如此配置,下面跟着我来理解为何如此配置。

首先理解一下什么是listener?

Listener 的作用非常类似于load-on-startup Servlet。用于在Web 应用启动时,启动某些后台程序,这些后台程序负责为系统运行提供支持。

Listener 与load-on-startup Servlet 的区别在于: Listener 的启动时机比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 规范之后才出现的。
使用Listener 只需要两个步骤:
(1)创建Listener 实现类。
(2)在web.xml 文件中配置Listener。
创建Listener 类必须实现ServletContextListener 接口,该接口包含两个方法。
contextInitialized(ServletContextEvent sce): 启动Web 应用时,系统调用该Filter的方法。

contextDestroyed(ServletContextEvent sce): 关闭Web 应用时候,系统调用Filter的方法。

先大致介绍一下ContextLoaderListener启动运行过程:

简单介绍一下上图的运行流程:

①启动项目时触发contextInitialized方法,该方法就做一件事:通过父类contextLoader的initWebApplicationContext方法创建Spring上下文对象。

②initWebApplicationContext方法做了三件事:创建WebApplicationContext;加载对应的Spring文件创建里面的Bean实例;将WebApplicationContext放入ServletContext(就是Java Web的全局变量)中。

③createWebApplicationContext创建上下文对象,支持用户自定义的上下文对象,但必须继承自ConfigurableWebApplicationContext,而Spring MVC默认使用ConfigurableWebApplicationContext作为ApplicationContext(它仅仅是一个接口)的实现。

④configureAndRefreshWebApplicationContext方法用于封装ApplicationContext数据并且初始化所有相关Bean对象。它会从web.xml中读取名为contextConfigLocation的配置,这就是spring xml数据源设置,然后放到ApplicationContext中,最后调用传说中的refresh方法执行所有Java对象的创建。

⑤完成ApplicationContext创建之后就是将其放入ServletContext中,注意它存储的key值常量。

下面来看一下ContextLoaderListener的源码:

publicclassContextLoaderListenerextendsContextLoaderimplementsServletContextListener{publicContextLoaderListener(){}publicContextLoaderListener(WebApplicationContextcontext){super(context);}publicvoidcontextInitialized(ServletContextEventevent){//这个方法已经废弃,返回值为nullcontextLoader=createContextLoader();if(contextLoader==null)//因为ContextLoaderListerner继承了ContextLoader,所以可把自身赋值给contextLoader=this;//接着就实例化webApplicationContext,由父类ContextLoader实现contextLoader.initWebApplicationContext(event.getServletContext());}protectedContextLoadercreateContextLoader(){returnnull;}publicContextLoadergetContextLoader(){returncontextLoader;}publicvoidcontextDestroyed(ServletContextEventevent){if(contextLoader!=null)contextLoader.closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}privateContextLoadercontextLoader;}

因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成,ContextLoader是一个工具类,用来初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我们继续追踪initWebApplicationContext这个方法,我们发现,原来ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默认实现类)放在了ServletContext中,ServletContext也是一个“容器”,也是一个类似Map的结构,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们如果要使用WebApplicationContext则需要从ServletContext取出,Spring提供了一个WebApplicationContextUtils类,可以方便的取出WebApplicationContext,只要把ServletContext传入就可以了。

在创建ContextLoader,会执行它的一段静态代码块。

static{try{//这里创建一个ClassPathResource对象,载入ContextLoader.properties,用于创建对应的ApplicationContext容器//这个文件跟ContextLoader类在同一个目录下,文件内容如://org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext//如此说来,spring默认初始化的是XmlWebApplicationContextClassPathResourceresource=newClassPathResource("ContextLoader.properties",org/springframework/web/context/ContextLoader);defaultStrategies=PropertiesLoaderUtils.loadProperties(resource);}catch(IOExceptionex){thrownewIllegalStateException((newStringBuilder("Couldnotload'ContextLoader.properties':")).append(ex.getMessage()).toString());}}

下面我们主要看看ContextLoader这个类的initWebApplicationContext方法。

//以下这段代码主要判断是否重复实例化的问题,因为实例化webApplicationContext后,会把它放到servletContext的一个属性里,所以我们可以从servletContext的属性取出webApplicationContext,如果不为空,则已经实例化,接着就会抛出异常.if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)!=null)thrownewIllegalStateException("Cannotinitializecontextbecausethereisalreadyarootapplicationcontextpresent-checkwhetheryouhavemultipleContextLoader*definitionsinyourweb.xml!");

这个地点是为什么Spring需要引入common-logging这个jar,不然会报错。

logger=LogFactory.getLog(org/springframework/web/context/ContextLoader);

调用LogFactory.getLog()时发生的事情:

(1).common-logging首先在CLASSPATH中查找commons-logging.properties文件。这个属性文件至少定义org.apache.commons.logging.Log属性,它的值应该是上述任意Log接口实现的完整限定名称。如果找到 org.apache.commons.logging.Log属相,则使用该属相对应的日志组件。结束发现过程。

(2).如果上面的步骤失败(文件不存在或属相不存在),common-logging接着检查系统属性org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系统属性,则使用该系统属性对应的日志组件。结束发现过程。
(3).如果找不到org.apache.commons.logging.Log系统属性,common-logging接着在CLASSPATH中寻找log4j的类。如果找到了就假定应用要使用的是log4j。不过这时log4j本身的属性仍要通过log4j.properties文件正确配置。结束发现过程。
(4).如果上述查找均不能找到适当的Logging API,但应用程序正运行在JRE 1.4或更高版本上,则默认使用JRE 1.4的日志记录功能。结束发现过程。
(5).最后,如果上述操作都失败(JRE 版本也低于1.4),则应用将使用内建的SimpleLog。SimpleLog把所有日志信息直接输出到System.err。结束发现过程。

为了简化配置 commons-logging ,一般不使用 commons-logging 的配置文件,也不设置与 commons-logging 相关的系统环境变量,而只需将 Log4j 的 Jar 包放置到 classpash 中就可以了。这样就很简单地完成了 commons-logging 与 Log4j 的融合。

接着我们可看到:

//接着就会创建webApplicationContextif(context==null)context=createWebApplicationContext(servletContext);

跟进createWebApplicationContext这个方法:

protectedWebApplicationContextcreateWebApplicationContext(ServletContextsc){//获取相应的class,其实就是ConfigurableWebApplicationContext.classClasscontextClass=determineContextClass(sc);//判断是否为ConfigurableWebApplicationContext.class或从它继承if(!org/springframework/web/context/ConfigurableWebApplicationContext.isAssignableFrom(contextClass)){//若非ConfigurableWebApplicationContext.class或非其子类则抛出异常thrownewApplicationContextException((newStringBuilder("Customcontextclass[")).append(contextClass.getName()).append("]isnotoftype[").append(org/springframework/web/context/ConfigurableWebApplicationContext.getName()).append("]").toString());}else{//创建一个contextClass的实例对象返回,然后就是把context强制转换为configrableWebApplicationContext,实例化spring容器ConfigurableWebApplicationContextwac=(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);returnwac;}}

该方法首先判断从web.xml文件的初始化参数CONTEXT_CLASS_PARAM(的定义为public static final String CONTEXT_CLASS_PARAM = "contextClass";)获取的的类名是否存在,如果存在,则容器的Class;否则返回默认的Class。如何获取默认的容器Class,注意看创建contextLoader时的代码注释就知道了, 由此看来,spring不仅有默认的applicationContext的容器类,还允许我们自定义applicationContext容器类,不过Spring不建义我们自定义applicationContext容器类。

protectedClassdetermineContextClass(ServletContextservletContext){StringcontextClassName;//从servletContext读取contextClassNamecontextClassName=servletContext.getInitParameter("contextClass");if(contextClassName==null)//否则创建默认的容器的Class对象,即:org.springframework.web.context.support.XmlWebApplicationContextcontextClassName=defaultStrategies.getProperty(WebApplicationContext.class.getName());//如果从servletContext读取到的contextClassName不为空,就返回对应的class类returnClassUtils.forName(contextClassName,ClassUtils.getDefaultClassLoader());ClassNotFoundExceptionex;ex;//如果找不到该类名的类就抛出异常thrownewApplicationContextException((newStringBuilder("Failedtoloadcustomcontextclass[")).append(contextClassName).append("]").toString(),ex);contextClassName=defaultStrategies.getProperty(org/springframework/web/context/WebApplicationContext.getName());returnClassUtils.forName(contextClassName,org/springframework/web/context/ContextLoader.getClassLoader());ex;thrownewApplicationContextException((newStringBuilder("Failedtoloaddefaultcontextclass[")).append(contextClassName).append("]").toString(),ex);}

接着看核心方法configureAndRefreshWebApplicationContext

protectedvoidconfigureAndRefreshWebApplicationContext(ConfigurableWebApplicationContextwac,ServletContextsc){if(ObjectUtils.identityToString(wac).equals(wac.getId())){StringidParam=sc.getInitParameter("contextId");if(idParam!=null)wac.setId(idParam);elseif(sc.getMajorVersion()==2&&sc.getMinorVersion()<5)wac.setId((newStringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getServletContextName())).toString());elsewac.setId((newStringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getContextPath())).toString());}ApplicationContextparent=loadParentContext(sc);wac.setParent(parent);//把servletContext放到webApplicationContext中,以后可以直接取出来用wac.setServletContext(sc);StringinitParameter=sc.getInitParameter("contextConfigLocation");if(initParameter!=null)wac.setConfigLocation(initParameter);//用户自己的一些设置customizeContext(sc,wac);//进行bean加载wac.refresh();}

我们进入webApplicationContext的refresh方法看一下

publicvoidrefresh()throwsBeansException,IllegalStateException{synchronized(startupShutdownMonitor){prepareRefresh();ConfigurableListableBeanFactorybeanFactory=obtainFreshBeanFactory();prepareBeanFactory(beanFactory);try{postProcessBeanFactory(beanFactory);invokeBeanFactoryPostProcessors(beanFactory);registerBeanPostProcessors(beanFactory);initMessageSource();initApplicationEventMulticaster();onRefresh();registerListeners();finishBeanFactoryInitialization(beanFactory);finishRefresh();}catch(BeansExceptionex){destroyBeans();cancelRefresh(ex);throwex;}}}

这是一个同步的方法,这里最核心的方法就是obtainFreshBeanFactory方法。其他的方法都是对webApplicationContext和beanFactory做一些前后的装饰和准备。

protectedConfigurableListableBeanFactoryobtainFreshBeanFactory(){refreshBeanFactory();ConfigurableListableBeanFactorybeanFactory=getBeanFactory();if(logger.isDebugEnabled())logger.debug((newStringBuilder("Beanfactoryfor")).append(getDisplayName()).append(":").append(beanFactory).toString());returnbeanFactory;}

这里的核心方法就是refreshBeanFactory();它负责生成BeanFactory并加载bean

protectedfinalvoidrefreshBeanFactory()throwsBeansException{//判断是否已经存在beanFactory如果有则销毁。if(hasBeanFactory()){destroyBeans();closeBeanFactory();}try{DefaultListableBeanFactorybeanFactory=createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);//用户自己做一些设置loadBeanDefinitions(beanFactory);//这个方法很关键,负责加载所有的beansynchronized(beanFactoryMonitor){this.beanFactory=beanFactory;}}catch(IOExceptionex){thrownewApplicationContextException((newStringBuilder("I/Oerrorparsingbeandefinitionsourcefor")).append(getDisplayName()).toString(),ex);}}

然后我们进入loadBeanDefinitions(beanFactory);看一下

protectedvoidloadBeanDefinitions(DefaultListableBeanFactorybeanFactory)throwsBeansException,IOException{XmlBeanDefinitionReaderbeanDefinitionReader=newXmlBeanDefinitionReader(beanFactory);beanDefinitionReader.setEnvironment(getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(newResourceEntityResolver(this));initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader);}

这里主要工作就是new一个XmlBeanDefinitionReader,给它设置environment,resourceLoader和entityResolver,注意一下由于webApplicationContext实现了ResouceLoader接口,所以它本身就是一个ResourceLoader.我们可以看到它并不自己去实现lobeanDefinitions方法,而是委托给XmlBeanDefinitionReader去实现。

protectedvoidloadBeanDefinitions(XmlBeanDefinitionReaderreader)throwsIOException{StringconfigLocations[]=getConfigLocations();if(configLocations!=null){Stringas[];intj=(as=configLocations).length;for(inti=0;i<j;i++){StringconfigLocation=as[i];reader.loadBeanDefinitions(configLocation);}}}

这里就对configLocations进行bean的加载,调用重载的方法

publicintloadBeanDefinitions(Stringlocation)throwsBeanDefinitionStoreException{returnloadBeanDefinitions(location,null);}