Spring Framework本身没有Web功能, Spring MVC使用WebApplicationContext类扩展ApplicationContext ,使得拥有web功能。那么,Spring MVC是如何在web环境中创建IoC容器呢?web环境中的IoC容器的结构又是什么结构呢?web环境中,Spring IoC容器是怎么启动呢?

以Tomcat为例,在Web容器中使用Spirng MVC,必须进行四项的配置:

  1. 修改web.xml,添加servlet定义;
  2. 编写servletname-servlet.xml(servletname是在web.xm中配置DispactherServlet时使servlet-name的值)配置;
  3. contextConfigLocation初始化参数、配置ContextLoaderListerner;

Web.xml配置如下:

<!-- servlet定义:前端处理器,接受的HTTP请求和转发请求的类 -->
<servlet>
<servlet-name>court</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- court-servlet.xml:定义WebAppliactionContext上下文中的bean -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:court-servlet.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>court</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping> <!-- 配置contextConfigLocation初始化参数:指定Spring IoC容器需要读取的定义了非web层的Bean(DAO/Service)的XML文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/court-service.xml</param-value>
</context-param> <!-- 配置ContextLoaderListerner:Spring MVC在Web容器中的启动类,负责Spring IoC容器在Web上下文中的初始化 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在web.xml配置文件中,有两个主要的配置: ContextLoaderListener和DispatcherServlet 。同样的关于spring配置文件的相关配置也有两部分: context-param和DispatcherServlet中的init-param 。那么,这两部分的配置有什么区别呢?它们都担任什么样的职责呢?

在Spring MVC中, Spring Context是以父子的继承结构存在的 。Web环境中存在一个ROOT Context,这个Context是整个应用的根上下文,是其他context的双亲Context。同时Spring MVC也对应的持有一个独立的Context,它是ROOT Context的子上下文。

对于这样的Context结构在Spring MVC中是如何实现的呢?下面就先从ROOT Context入手, ROOT Context是在ContextLoaderListener中配置的,ContextLoaderListener读取context-param中的contextConfigLocation指定的配置文件,创建ROOT Context 。

Spring MVC启动过程大致分为两个过程:

  1. ContextLoaderListener初始化,实例化IoC容器,并将此容器实例注册到ServletContext中;
  2. DispatcherServlet初始化;

2 Web容器中Spring根上下文的加载与初始化

Web容器调用contextInitialized方法初始化ContextLoaderListener,在此方法中, ContextLoaderListener通过调用继承自ContextLoader的initWebApplicationContext方法实例化Spring Ioc容器 。

  1. 先看一下WebApplicationContext是如何扩展ApplicationContext来添加对Web环境的支持的。WebApplicationContext接口定义如下:

    public interface WebApplicationContext extends ApplicationContext {
    //根上下文在ServletContext中的名称
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    //取得web容器的ServletContext
    ServletContext getServletContext();
    }
  2. 下面看一下ContextLoaderListener中创建context的源码:ContextLoader.java
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //PS : ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE=WebApplicationContext.class.getName() + ".ROOT" 根上下文的名称
    //PS : 默认情况下,配置文件的位置和名称是: DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"
    //在整个web应用中,只能有一个根上下文
    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) {
    // 在这里执行了创建WebApplicationContext的操作
    this.context = createWebApplicationContext(servletContext);
    }
    if (this.context instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    if (!cwac.isActive()) {
    // The context has not yet been refreshed -> provide services such as
    // setting the parent context, setting the application context id, etc
    if (cwac.getParent() == null) {
    // The context instance was injected without an explicit parent ->
    // determine parent for root web application context, if any.
    ApplicationContext parent = loadParentContext(servletContext);
    cwac.setParent(parent);
    }
    configureAndRefreshWebApplicationContext(cwac, servletContext);
    }
    }
    // PS: 将根上下文放置在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. 再看一下WebApplicationContext对象是如何创建的:ContextLoader.java
    protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
    //根据web.xml中的配置决定使用何种WebApplicationContext。默认情况下使用XmlWebApplicationContext
    //web.xml中相关的配置context-param的名称“contextClass”
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    } //实例化WebApplicationContext的实现类
    ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); // Assign the best possible id value.
    if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
    // Servlet <= 2.4: resort to name specified in web.xml, if any.
    String servletContextName = sc.getServletContextName();
    if (servletContextName != null) {
    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName);
    } else {
    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX);
    }
    } else {
    // Servlet 2.5's getContextPath available!
    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath());
    } wac.setParent(parent); wac.setServletContext(sc);
    //设置spring的配置文件
    wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
    customizeContext(sc, wac);
    //spring容器初始化
    wac.refresh();
    return wac;
    }
  4. ContextLoaderListener构建Root Context时序图: 

3 Spring MVC对应的上下文加载与初始化

Spring MVC中核心的类是DispatcherServlet ,在这个类中完成Spring context的加载与创建,并且能够根据Spring Context的内容将请求分发给各个Controller类。 DispatcherServlet继承自HttpServlet ,关于Spring Context的配置文件加载和创建是在 init() 方法中进行的,主要的调用顺序是 init-->initServletBean-->initWebApplicationContext 。

  1. 先来看一下initWebApplicationContext的实现:FrameworkServlet.java

    protected WebApplicationContext initWebApplicationContext() {
    //先从web容器的ServletContext中查找WebApplicationContext
    WebApplicationContext wac = findWebApplicationContext();
    if (wac == null) {
    // No fixed context defined for this servlet - create a local one.
    //从ServletContext中取得根上下文
    WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    //创建Spring MVC的上下文,并将根上下文作为起双亲上下文
    wac = createWebApplicationContext(parent);
    } if (!this.refreshEventReceived) {
    // Apparently not a ConfigurableApplicationContext with refresh support:
    // triggering initial onRefresh manually here.
    onRefresh(wac);
    } if (this.publishContext) {
    // Publish the context as a servlet context attribute.
    // 取得context在ServletContext中的名称
    String attrName = getServletContextAttributeName();
    //将Spring MVC的Context放置到ServletContext中
    getServletContext().setAttribute(attrName, wac);
    if (this.logger.isDebugEnabled()) {
    this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
    }
    }
    return wac;
    }

    通过initWebApplicationContext方法的调用, 创建了DispatcherServlet对应的context,并将其放置到ServletContext中 ,这样就完成了在web容器中构建Spring IoC容器的过程。

  2. DispatcherServlet创建context时序图: 
  3. DispatcherServlet初始化的大体流程: 
  4. 控制器DispatcherServlet的类图及继承关系: 

4 Spring中DispacherServlet、WebApplicationContext、ServletContext的关系

要想很好理解这三个上下文的关系,需要先熟悉Spring是怎样在web容器中启动起来的。Spring的启动过程其实就是其IOC容器的启动过程,对于web程序,IOC容器启动过程即是建立上下文的过程。

Spring的启动过程:

  1. 首先,对于一个web应用,其部署在web容器中, web容器提供其一个全局的上下文环境,这个上下文就是ServletContext ,其为后面的spring IoC容器提供宿主环境;
  2. 其次, 在web.xml中会提供有contextLoaderListener 。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用, 在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。 这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3. 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。 DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean 。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。 这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean 。

在Web容器(比如Tomcat)中配置Spring时,你可能已经司空见惯于web.xml文件中的以下配置代码:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param> <listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener> <servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping></span>

以上配置 首先会在ContextLoaderListener中通过<context-param>中的applicationContext.xml创建一个ApplicationContext ,再将这个ApplicationContext塞到ServletContext里面,通过ServletContext的setAttribute方法达到此目的,在ContextLoaderListener的源代码中,我们可以看到这样的代码:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

以上由ContextLoaderListener创建的ApplicationContext是共享于整个Web应用程序的,而你可能早已经知道, DispatcherServlet会维持一个自己的ApplicationContext,默认会读取/WEB-INFO/<dispatcherServletName>-servlet.xml文件 ,而也可以重新配置:

<servlet>
<servlet-name>
customConfiguredDispacherServlet
</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>
contextConfigLocation
</param-name>
<param-value>
/WEB-INF/dispacherServletContext.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

问题是:以上两个ApplicationContext的关系是什么,它们的作用作用范围分别是什么,它们的用途分别是什么?

ContextLoaderListener中创建ApplicationContext主要用于整个Web应用程序需要共享的一些组件 ,比如DAO,数据库的ConnectionFactory等。而 由DispatcherServlet创建的ApplicationContext主要用于和该Servlet相关的一些组件 ,比如Controller、ViewResovler等。

对于作用范围而言, 在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext ,而反过来不行。

在Spring的具体实现上,这两个ApplicationContext都是通过ServletContext的setAttribute方法放到ServletContext中的。但是, ContextLoaderListener会先于DispatcherServlet创建ApplicationContext,DispatcherServlet在创建ApplicationContext时会先找到由ContextLoaderListener所创建的ApplicationContext,再将后者的ApplicationContext作为参数传给DispatcherServlet的ApplicationContext的setParent()方法 ,在Spring源代码中,你可以在FrameServlet.java中找到如下代码:

<code>wac.setParent(parent); </code>

其中, wac即为由DisptcherServlet创建的ApplicationContext,而parent则为有ContextLoaderListener创建的ApplicationContext 。此后,框架又会调用ServletContext的setAttribute()方法将wac加入到ServletContext中。

当Spring在执行ApplicationContext的getBean时, 如果在自己context中找不到对应的bean,则会在父ApplicationContext中去找 。这也解释了为什么我们可以在DispatcherServlet中获取到由ContextLoaderListener对应的ApplicationContext中的bean。

Spring mvc web 配置的更多相关文章

  1. Spring Mvc Web 配置拦截规则与访问静态资源 (三)

    拦截规则配置 1. *.do <!-- Processes application requests --> <servlet> <servlet-name>app ...

  2. 【Java Web开发学习】Spring MVC 开始配置

    Spring MVC 开始配置 转载:http://www.cnblogs.com/yangchongxing/p/8871370.htm 学习搭建最简单的Spring MVC框架. ======== ...

  3. Spring MVC Web.xml配置

    Web.xml spring&spring mvc 在web.xml中定义contextConfigLocation参数,Spring会使用这个参数去加载所有逗号分隔的xml文件,如果没有这个 ...

  4. Spring MVC 事务配置

    Spring MVC事务配置 要了解事务配置的所有方法,请看一下<Spring事务配置的5种方法> 本文介绍两种配置方法: 一.      XML,使用tx标签配置拦截器实现事务 一.   ...

  5. spring mvc+myBatis配置详解

    一.spring mvc Spring框架(框架即:编程注解+xml配置的方式)MVC是Spring框架的一大特征,Spring框架有三大特征(IOC(依赖注入),AOP(面向切面),MVC(建模M- ...

  6. 使用Maven创建一个Spring MVC Web 项目

    使用Maven创建java web 项目(Spring MVC)用到如下工具: 1.Maven 3.2 2.IntelliJ IDEA 13 3.JDK 1.7 4.Spring 4.1.1 rele ...

  7. Maven 工程下 Spring MVC 站点配置 (三) C3P0连接池与@Autowired的应用

    Maven 工程下 Spring MVC 站点配置 (一) Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作 前两篇文章主要是对站点和数据库操作配置进行了演示,如果单 ...

  8. Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作

    详细的Spring MVC框架搭配在这个连接中: Maven 工程下 Spring MVC 站点配置 (一) Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作 这篇主 ...

  9. Maven 工程下 Spring MVC 站点配置 (一)

    最近,查找一些具体资料时,虽然会有很多,但是系统的却很少,尤其是对maven 下 spring mvc 站点搭建的配置,总是说的很多但让新手一目了然的步骤却少之又少. 对此闲暇时整理了一下,做了一套较 ...

随机推荐

  1. Android -- View移动的六种方法

    layout() 如果你将滑动后的目标位置的坐标传递给layout(),这样子就会把view的位置给重新布置了一下,在视觉上就是view的一个滑动的效果. public class DragView ...

  2. 【LeetCode】95. Unique Binary Search Trees II

    Unique Binary Search Trees II Given n, generate all structurally unique BST's (binary search trees) ...

  3. Qt学习笔记 QMessageBox

    Qt的几种MessageBox 1.Infomation类型 QMessageBox::information(this,tr("hello"),tr("title&qu ...

  4. 更好的逐帧动画函数 — requestAnimationFrame 简介

    本文将会简单讲讲 requestAnimationFrame 函数的用法,与 setTimeout/setInterval 的区别和联系,以及当标签页隐藏时 requestAnimationFrame ...

  5. canvas api

    基本骨骼 <canvas id="canvas" width=1000 height=1000 style="border: 1px black dotted&qu ...

  6. HoloLens开发手记 - 应用程序模型 App model

    HoloLens使用Universal Windows Platform (UWP)提供的应用模型.UWP应用模型定义了应用如何被安全和完全地安装.更新.版本控制和移除.它管理了应用生命周期 - 应用 ...

  7. Qt环境搭建(Qt Creator)+Visual Studio

    1.http://www.cnblogs.com/ranjiewen/p/5318768.html 简述 经常有人问我编写Qt程序时使用什么IDE,其实这个真的很难回答(各有所长),只能说看个人爱好了 ...

  8. EF增删改查操作

    增 using (StudentEntities ent = new StudentEntities()) { User aNewUser = new User() { Age = , Name = ...

  9. MPLS基础

    1.1  MPLS简介 MPLS(Multiprotocol Label Switching,多协议标签交换)是一种新兴的IP骨干网技术.MPLS在无连接的IP网络上引入面向连接的标签交换概念,将第三 ...

  10. jQuery问题集锦

    [1]阻止提交表单 方法1: $(function () { $("input[type=submit]").click(function (event) { //如果不满足表单提 ...