简单的说,在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。在web容器中启动Spring应用程序就是一个建立这个上下文体系的过程。Spring为web应用提供了上下文的扩展接口
WebApplicationContext:
- public interface WebApplicationContext extends ApplicationContext {
- //这里定义的常量用于在ServletContext中存取根上下文
- String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
- ......
- //对WebApplicationContext来说,需要得到Web容器的ServletContext
- ServletContext getServletContext();
- }
而一般的启动过程,Spring会使用一个默认的实现,XmlWebApplicationContext - 这个上下文实现作为在web容器中的根上下文容器被建立起来,具体的建立过程在下面我们会详细分析。
- public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
- /** 这是和web部署相关的位置信息,用来作为默认的根上下文bean定义信息的存放位置*/
- public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
- public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
- public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
- //我们又看到了熟悉的loadBeanDefinition,就像我们前面对IOC容器的分析中一样,这个加载工程在容器的refresh()的时候启动。
- protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
- //对于XmlWebApplicationContext,当然使用的是XmlBeanDefinitionReader来对bean定义信息来进行解析
- XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
- beanDefinitionReader.setResourceLoader(this);
- beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
- initBeanDefinitionReader(beanDefinitionReader);
- loadBeanDefinitions(beanDefinitionReader);
- }
- protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
- }
- //使用XmlBeanDefinitionReader来读入bean定义信息
- protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
- String[] configLocations = getConfigLocations();
- if (configLocations != null) {
- for (int i = 0; i < configLocations.length; i++) {
- reader.loadBeanDefinitions(configLocations[i]);
- }
- }
- }
- //这里取得bean定义信息位置,默认的地方是/WEB-INF/applicationContext.xml
- protected String[] getDefaultConfigLocations() {
- if (getNamespace() != null) {
- return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
- }
- else {
- return new String[] {DEFAULT_CONFIG_LOCATION};
- }
- }
- }
对于一个Spring激活的web应用程序,可以通过使用Spring代码声明式的指定在web应用程序启动时载入应用程序上下文(WebApplicationContext),Spring的ContextLoader是提供这样性能的类,我们可以使用 ContextLoaderServlet或者ContextLoaderListener的启动时载入的Servlet来实例化Spring IOC容器 - 为什么会有两个不同的类来装载它呢,这是因为它们的使用需要区别不同的Servlet容器支持的Serlvet版本。但不管是 ContextLoaderSevlet还是 ContextLoaderListener都使用ContextLoader来完成实际的WebApplicationContext的初始化工作。这个ContextLoder就像是Spring Web应用程序在Web容器中的加载器booter。当然这些Servlet的具体使用我们都要借助web容器中的部署描述符来进行相关的定义。
下面我们使用ContextLoaderListener作为载入器作一个详细的分析,这个Servlet的监听器是根上下文被载入的地方,也是整个 Spring web应用加载上下文的第一个地方;从加载过程我们可以看到,首先从Servlet事件中得到ServletContext,然后可以读到配置好的在web.xml的中的各个属性值,然后ContextLoder实例化WebApplicationContext并完成其载入和初始化作为根上下文。当这个根上下文被载入后,它被绑定到web应用程序的ServletContext上。任何需要访问该ApplicationContext的应用程序代码都可以从WebApplicationContextUtils类的静态方法来得到:
- WebApplicationContext getWebApplicationContext(ServletContext sc)
以Tomcat作为Servlet容器为例,下面是具体的步骤:
1.Tomcat 启动时需要从web.xml中读取启动参数,在web.xml中我们需要对ContextLoaderListener进行配置,对于在web应用启动入口是在ContextLoaderListener中的初始化部分;从Spring MVC上看,实际上在web容器中维护了一系列的IOC容器,其中在ContextLoader中载入的IOC容器作为根上下文而存在于 ServletContext中。
- //这里对根上下文进行初始化。
- public void contextInitialized(ServletContextEvent event) {
- //这里创建需要的ContextLoader
- this.contextLoader = createContextLoader();
- //这里使用ContextLoader对根上下文进行载入和初始化
- this.contextLoader.initWebApplicationContext(event.getServletContext());
- }
通过ContextLoader建立起根上下文的过程,我们可以在ContextLoader中看到:
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
- throws IllegalStateException, BeansException {
- //这里先看看是不是已经在ServletContext中存在上下文,如果有说明前面已经被载入过,或者是配置文件有错误。
- if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
- //直接抛出异常
- .........
- }
- ...............
- try {
- // 这里载入根上下文的父上下文
- ApplicationContext parent = loadParentContext(servletContext);
- //这里创建根上下文作为整个应用的上下文同时把它存到ServletContext中去,注意这里使用的ServletContext的属性值是
- //ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后的应用都是根据这个属性值来取得根上下文的 - 往往作为自己上下文的父上下文
- this.context = createWebApplicationContext(servletContext, parent);
- servletContext.setAttribute(
- WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
- ..........
- return this.context;
- }
- ............
- }
建立根上下文的父上下文使用的是下面的代码,取决于在web.xml中定义的参数:locatorFactorySelector,这是一个可选参数:
- protected ApplicationContext loadParentContext(ServletContext servletContext)
- throws BeansException {
- ApplicationContext parentContext = null;
- String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
- String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
- if (locatorFactorySelector != null) {
- BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
- ........
- //得到根上下文的父上下文的引用
- this.parentContextRef = locator.useBeanFactory(parentContextKey);
- //这里建立得到根上下文的父上下文
- parentContext = (ApplicationContext) this.parentContextRef.getFactory();
- }
- return parentContext;
- }
得到根上下文的父上下文以后,就是根上下文的创建过程:
- protected WebApplicationContext createWebApplicationContext(
- ServletContext servletContext, ApplicationContext parent) throws BeansException {
- //这里需要确定我们载入的根WebApplication的类型,由在web.xml中配置的contextClass中配置的参数可以决定我们需要载入什么样的ApplicationContext,
- //如果没有使用默认的。
- Class contextClass = determineContextClass(servletContext);
- .........
- //这里就是上下文的创建过程
- ConfigurableWebApplicationContext wac =
- (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
- //这里保持对父上下文和ServletContext的引用到根上下文中
- wac.setParent(parent);
- wac.setServletContext(servletContext);
- //这里从web.xml中取得相关的初始化参数
- String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
- if (configLocation != null) {
- wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
- ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
- //这里对WebApplicationContext进行初始化,我们又看到了熟悉的refresh调用。
- wac.refresh();
- return wac;
- }
初始化根ApplicationContext后将其存储到SevletContext中去以后,这样就建立了一个全局的关于整个应用的上下文。这个根上下文会被以后的DispatcherServlet初始化自己的时候作为自己ApplicationContext的父上下文。这个在对 DispatcherServlet做分析的时候我们可以看看到。
3.完成对ContextLoaderListener的初始化以后, Tomcat开始初始化DispatchServlet,- 还记得我们在web.xml中队载入次序进行了定义。DispatcherServlet会建立自己的ApplicationContext,同时建立这个自己的上下文的时候会从ServletContext中得到根上下文作为父上下文,然后再对自己的上下文进行初始化,并最后存到 ServletContext中去供以后检索和使用。
可以从DispatchServlet的父类FrameworkServlet的代码中看到大致的初始化过程,整个ApplicationContext的创建过程和ContextLoder创建的过程相类似:
- protected final void initServletBean() throws ServletException, BeansException {
- .........
- try {
- //这里是对上下文的初始化过程。
- this.webApplicationContext = initWebApplicationContext();
- //在完成对上下文的初始化过程结束后,根据bean配置信息建立MVC框架的各个主要元素
- initFrameworkServlet();
- }
- ........
- }
对initWebApplicationContext()调用的代码如下: