上一篇关于Spring是如何启动的文章,主要是分析了从Tomcat启动到web.xml文件加载,再到通过ContextLoaderListener
监听器开始初始化WebApplicationContext
这个过程,如果不熟悉可以参考这篇-漫谈Spring的启动与初始化(一),但是上一篇还没有分析到Spring容器是如何通过web.xml里面配置的contextConfigLocation
参数和Spring容器的配置文件applicationContext.xml
来初始化Spring,本文着力于解决这个疑惑。
initWebApplicationContext方法
当ContextLoaderListener
监听到ServletContext
初始化事件的时候就会调用ContextLoader
的initWebApplicationContext
方法,这个方法完成了很多的工作,其中便有下面这段关键代码,源码如下:
createWebApplicationContext方法
下面进入该方法,源码如下:
其中determineContextClass(sc)
方法是用来寻找实现WebApplicationContext
接口的类,实现方法如下:
上面这两行代码就是决定返回的Class:如果开发人员在web.xml中配置了一个参数名为contextClass
,值为WebApplicationContext
接口实现类,那getInitParameter("contextClass")
就会返回这个配置的实现类Class;如果没有配置,也就是contextClassName==null
,那么通过defaultStrategies.getProperty(...)
则会返回Spring默认的实现类XmlWebApplicationContext
。可能有同学会好奇为什么是这个,这个类是从哪儿来的。
我们回头在ContextLoader
这个类中最下面可以看到有这么几行静态代码段,在类一加载的时候执行,如下:
在ContextLoader.class
的同一个包下面,可以找到这个配置文件,其中只有一行配置如下:
XmlWebApplicationContext
这个类就是WebApplicationContext
这个接口最终的实现类,也是Spring启动时默认使用的类。其实还有一些实现类,让我们自己去加载applicationContext.xml
,比如ClassPathXmlApplicationContext
。
这样在上述的createWebApplicationContext
方法中,我们拿到的就是XmlWebApplicationContext.class
,然后通过BeanUtils.instantiateClass(contextClass)
方法根据类名创建对应实例,并且进行强制转换得到ConfigurableWebApplicationContext
接口的实例,因为XmlWebApplicationContext
是后者的实现类,所以这样转换是没问题的(当然没问题哈哈)。
那么createWebApplicationContext
方法分析到此为止。
我们继续看initWebApplicationContext
方法中下面这段代码的最后一行:
configureAndRefreshWebApplicationContext方法
在上面代码中我们通过XmlWebApplicationContext
类创建了WebApplicationContext
的实例,本节方法则是为该实例设置一些配置信息和创建各种bean。我们重点关注下面几行代码:
在Spring的项目中,经常要在web.xml中配置contextConfigLocation的参数,我们对于下面的几行xml代码应该也很熟悉,它设置了Spring容器配置文件的路径:
既然是在web.xml中配置的这些参数,为什么是在ServletContext中去取呢?在上一篇文章中分析过,Tomcat在加载web.xml文件时,会最终将该配置文件的配置属性参数以键值对的形式存放在每个web应用对应的ServletContext中,这样我们在web应用中的任何地方都可以拿到该参数值(后面还会提到ServletContext其他的使用场景)。
当然,如果我们没有在web.xml中配置该参数的话,XmlWebApplicationContext类也是有默认值的,如下:
到这里,配置文件信息拿到了,再就是wac.refresh()
方法了,这个方法具体实现在AbstractApplicationContext类中,进入该方法:
这个方法里面就是IOC容器初始化的大致步骤了。
after configureAndRefresh
在IOC容器初始化之后,也就是configureAndRefreshWebApplicationContext方法执行结束后有一行代码如下:
可以看到,这里初始化后的context被存放到了servletContext中,具体的就是存到了一个Map变量中,key值就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个常量。这个key常量在WebApplicationContext接口中设置的,如下:
另外,我们也可以使用Spring的WebApplicationContextUtils工具类获取这个WebApplicationContext(不过这里request获取ServletContext是有限制的,要求servlet-api.jar 包的版本是在3.0以上)方式如下:
到这里Spring的启动与初始化应该就结束了,这里面要理清ServletContext和Spring容器的关系,整个Spring容器被放置在ServletContext这样一个类似于Map的结构中。ServletContext 从字面上理解也是Servlet的容器,被 Servlet 程序间接用来与外层 Web 容器通信,例如写日志,转发请求等。每一个 Web 应用程序含有一个Context ,被Web 应用内的各个程序共享。因为Context 可以用来保存资源并且共享,所以ServletContext 的常用场景是Web级应用缓存—- 把不经常更改的内容读入内存,所以服务器响应请求的时候就不需要进行慢速的磁盘I/O 了。
-EOF-