SpringMVC源码 1 ContextLoaderListener和Servlet容器web.xml配置

SpringMVC源码 1 ContextLoaderListener和Servlet容器web.xml配置


1.先说下ServletContext
javaee标准规定了,servlet容器需要在应用项目启动时,给应用项目初始化一个ServletContext作为公共环境容器存放公共信息。ServletContext中的信息都是由容器提供的。

通过contextListener获取web.xml中的参数。<context-param>以及<listener>
1.servlet容器启动的时候,找到应用配置文件(web.xml)中的<context-param>作为键值对放到ServletContext中。
2.servlet容器读取<listener>节点,构建对应的listener,容器调用了contextInitialized(ServletContextEvent event)方法,执行其中的操作。(所有Listener必须继承自ServletContextListener

<context-param>只是将上下文的键值对传入servletContext中,本身并没有什么实际意义,需要Listener对参数进行解析后才具有一定意义。其中param-name是规定的,有Listener中进行定义。例如定义了m-name,在配置文件中<param-name>也需要是m-name。
<listener>  初始化上下文的监听器。    <listener>是否可以设置多个???答案是显然的,当然可以。

web.xml
<context-param>
     <param-name>contextConfigLocation</param-name> //contextConfigLocation是在Spring的ContextLoader中定义的参数,确定Spring配置文件位置
     <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<context-param>
     <param-name>m-name</param-name>  //m-name是在自己写的ServletContextListener实现类中定义的参数
     <param-value>mname</param-value>
</context-param>
<context-param>
     <param-name>contextId</param-name> //contextId是在Spring的ContextLoader中定义的参数
     <param-value>MMM-WebApplicationContext</param-value>
</context-param>
//如果想要在web程序初始化的时候使用ServletContextListener,就必须在web.xml中加入<listener>节点
<listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
     <listener-class>com.XXX.MYContextListener</listener-class>
</listener>

public class MYContextListener implements ServletContextListener{
     public static final String CONTEXT_NAME_PARAM = "m-name";
     public static final String CONTEXT_CLASS_PARAM = "m-class";
     @Override
     public void contextInitialized(ServletContextEvent sce) {
           ServletContext context = sce.getServletContext();
           String mname = (String) context.getInitParameter(CONTEXT_NAME_PARAM);
           String mclass = context.getInitParameter(CONTEXT_CLASS_PARAM);
           System.out.println(mname+"   "+mclass);
     }

     @Override
     public void contextDestroyed(ServletContextEvent sce) {
           // TODO Auto-generated method stub

     }
}

public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
     ........

     @Override
     public void contextInitialized(ServletContextEvent event) {
           initWebApplicationContext(event.getServletContext());
     }

     /**Close the root web application context.*/
     @Override
     public void contextDestroyed(ServletContextEvent event) {
          closeWebApplicationContext(event.getServletContext());
          ContextCleanupListener.cleanupAttributes(event.getServletContext());
     }
}

其中spring需要的contextConfigLocation也是在spring中的ContextLoader.class中定义的
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String CONTEXT_CLASS_PARAM = "contextClass";等等等等  具体看后面的源码解释。

2.执行流程
web.xml在<context-param>标签中声明应用范围内的初始化参数
1.启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener>和 <context-param>
2.紧接着,容器创建一个ServletContext(上下文)。在该应用内全局共享。
3.容器将<context-param>转化为键值对,并交给ServletContext。有接下来的ServletContextListener去调用
4.容器创建<listener>中的类实例,即创建监听.该监听器必须实现自ServletContextListener接口。可以定义多个listener,执行顺序按照web.xml中定义的顺序来执行。
5.在Listener中会有contextInitialized(ServletContextEvent event)初始化方法
  在这个方法中获得ServletContext = ServletContextEvent.getServletContext();
  String value = ServletContext.getInitParameter("<context-param name>");
6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.
换句话说,这个时候,你对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行.只有在这个时候<context-param>中的参数才是有意义的(也只对相应的listener而言)。对于ServletContext来说只是一组String键值对

所有的servlet容器和listener监听类,都是同意按照java Servlet API来进行开发的。Package javax.servlet

3.ContextLoaderListener 和ContextLoader

项目中使用Spring 时,applicationContext.xml配置文件中并没有BeanFactory,要想在业务层中的class 文件中直接引用Spring容器管理的bean可通过以下方式
1、在web.xml配置监听器ContextLoaderListener。ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在 web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。
在ContextLoaderListener继承了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。


ContextLoaderListener作为springMVC,在Servlet容器启动时的上下文件监听器。在servlet容器启动的时候,会调用在<listener>中定义的ContextLoaderListener,调用contextInitialzed()方法,初始化上下文。
ContextLoaderListener实现ServletContextListener接口,用于在Servlet容器启动时开启监听,获取ServletContext对象。
ContextLoaderListener实现了ContextLoader,也是ContextLoader的唯一子类。主要的上下文加载工作由ContextLoader完成。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
     public ContextLoaderListener() {
     }
     public ContextLoaderListener(WebApplicationContext context) {
           super(context);
     }
     /** Initialize the root web application context.*/
     @Override
     public void contextInitialized(ServletContextEvent event) {
           initWebApplicationContext(event.getServletContext());
     }
     /** Close the root web application context. */
     @Override
     public void contextDestroyed(ServletContextEvent event) {
          closeWebApplicationContext(event.getServletContext());
          ContextCleanupListener.cleanupAttributes(event.getServletContext());
     }
}


public class ContextLoader {
//定义web.xml <context-param>中可以配置的<context-name>Name of servlet context parameter
public static final String CONTEXT_ID_PARAM = "contextId";            //上下文的id
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";  //为root context确定配置的位置。(配置文件的位置)
public static final String CONTEXT_CLASS_PARAM = "contextClass";  //确定使用WebApplicationContext的那个子类。默认使用XmlWebApplicationContext。
//也可以配置为AnnotationConfigWebApplicationContext、GenericWebApplicationContext等
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
     
     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
          //如果已经存在了根共享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) {
                     this.context = createWebApplicationContext(servletContext);  //开始创建WebApplicationcontext。
1.确定需要构造的WebApplicationContext的类型 class<?> contextClass。 从ServletContext中获取contextClass的定义。需要在web.xml中配置<context-param>对应的<param-name>和<param-value>配置需要全类名
如果在web.xml没有配置,则默认使用的是org.springframework.web.context.support.XmlWebApplicationContext。
2.判断是否是contextClass是不是ConfigurableWebApplicationContext(可配置的web应用上下文)的子类。不是的话抛出异常。   ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)
3.通过contexClass构造WebApplicationContext 的实例
                }
                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);
事实上,根共享环境的加载时同样可以加载一个父环境。尽管这种情况是不常见的,但是Spring Web MVC提供了这样的扩展性。在Servlet初始化参数中可以配置一个Bean工厂路径(locatorFactorySelector),这个Bean工厂路径会被Bean工厂定位器所加载,Bean工厂定位器会在这个Bean工厂中查找以另外一个Servlet参数(parentContextKey)为名字的Bean工厂对象,最后得到的Bean工厂对象则是根共享环境的父环境。如果在初始化参数中没有配置Bean工厂路径,则用缺省的Bean工厂路径classpath*:beanRefFactory.xml。
没有在web.xml配置上述两个参数时,返回的ptarent是null。即根web应用上下文没有父级。
                                cwac.setParent(parent);
                           }
                          configureAndRefreshWebApplicationContext(cwac, servletContext);  //配置和刷新web应用上下文,这才是启动sprigIoc的关键
                     }
                }
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
将WebApplicationcontext放到ServletContext中,将这个环境以ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为关键字保存在Servlet环境中,这个根共享环境在Servlet加载专用子环境中被引用作为父环境。
                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;
           }
     }
     
}
  
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//根据ServletContext中“contextClass”的参数去创建对应的WebApplication,如果没有这个参数,则通过去加载ContextLoader.properties文件中的默认参数。(XMLWebApplicationContext)
     Class<?> contextClass = determineContextClass(sc);  
     if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
          throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
               "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
     }
     return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

- protected ApplicationContext loadParentContext(ServletContext servletContext) {
-     ApplicationContext parentContext = null;
-
-     //取得Web.xml初始化参数配置中对LOCATOR_FACTORY_SELECTOR_PARAM的配置串,这是Bean工厂定位器使用的Bean工厂的路径,如果这个值没有配置,则使用缺省的classpath*:beanRefFactory.xml
-     String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
-     //取得Web.xml初始化参数配置中对LOCATOR_FACTORY_KEY_PARAM的配置串,这是用来取得Bean工厂的关键字
-     String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
-
-     if (parentContextKey != null) {
-         //locatorFactorySelector如果为空,则使用缺省值classpath*:beanRefFactory.xml初始化Bean工厂定位器
-         BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
-         Log logger = LogFactory.getLog(ContextLoader.class);
-         if (logger.isDebugEnabled()) {
-             logger.debug("Getting parent context definition: using parent context key of '" +
-                     parentContextKey + "' with BeanFactoryLocator");
-         }
-         //Bean工厂定位器从配置的Bean工厂中找到制定关键字(参数LOCATOR_FACTORY_KEY_PARAM的值) 的工厂
-         this.parentContextRef = locator.useBeanFactory(parentContextKey);
-
-         //进而取得一个应用程序环境,这个应用程序环境作为根共享应用程序环境的父环境
-         parentContext = (ApplicationContext) this.parentContextRef.getFactory();
-     }
-     return parentContext;
- }

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
     if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
           String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
           if (idParam != null) {
                wac.setId(idParam);
           }else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +   //默认id
                          ObjectUtils.getDisplayString(sc.getContextPath()));
           }
     }
     wac.setServletContext(sc);  //对WebApplication设置Servlet容器的ServletContext。
     String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
     if (configLocationParam != null) {
           wac.setConfigLocation(configLocationParam);  //配置spring的配置文件路径   XmlWebApplicationContext默认为  “/WEB-INF/applicationContext.xml”
     }
     // The wac environment's #initPropertySources will be called in any case when the context
     // is refreshed; do it eagerly here to ensure servlet property sources are in place for
     // use in any post-processing or initialization that occurs below prior to #refresh
     ConfigurableEnvironment env = wac.getEnvironment();
     if (env instanceof ConfigurableWebEnvironment) {
           ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
     }
     customizeContext(sc, wac);
     wac.refresh();   刷新上线文,具体上下文初始化操作,可以看AbstractApplicationContext.refresh()。
}

SpringMVC源码 1 ContextLoaderListener和Servlet容器web.xml配置

4.Spring上下文在web.xml中的配置
Spring为我们提供了实现ServletContextListener接口的上下文初始化监听器:org.springframework.web.context.ContextLoaderListener,也是唯一的一个实现。SpringMVC使用其进行初始化。
SpringMVC源码 1 ContextLoaderListener和Servlet容器web.xml配置
spring为我们提供的IOC容器,需要我们指定容器的配置文件,然后由该监听器初始化并创建该容器。
要求你指定配置文件的地址及文件名称,一定要使用:contextConfigLocation作为参数名称。因为这是在ContextLoader中定义好的。

<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-INF/下的applicationContext.xml文件。但是通过context-param指定配置文件路径后,便会去你指定的路径下读取对应的配置文件,并进行初始化。

public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

<param-value>/WEB-INF/applicationContext.xml</param-value>
<param-value>/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml,/WEB-INF/jason-servlet.xml</param-value>
<param-value>classpath:applicationContext.xml </param-value>

5.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <!--1、在web.xml配置监听器ContextLoaderListener。-->
    <!--ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在 web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。在ContextLoaderListener继承了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--2、部署applicationContext的xml文件-->
    <!--如果在web.xml中不写任何参数配置信息,默认的路径是"/WEB-INF/applicationContext.xml, 在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml。
    如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数:
    在<param-value> </param-value>里指定相应的xml文件名,如果有多个xml文件,可以写在一起并以“,”号分隔。 也可以这样applicationContext-*.xml采用通配符,比如这那个目录下有applicationContext-ibatis-base.xml,applicationContext-action.xml,applicationContext-ibatis-dao.xml等文件,都会一同被载入。
    在ContextLoaderListener中继承了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/applicationContext.xml</param-value>
    </context-param>


    <!--使用Spring MVC,配置DispatcherServlet是第一步。DispatcherServlet是一个Servlet,,所以可以配置多个DispatcherServlet-->
    <!--DispatcherServlet是前置控制器,配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据某某规则分发到目标Controller(我们写的Action)来处理。-->
    <servlet>
        <!--在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。-->
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指明了配置文件的文件名,不使用默认配置文件名,而使用dispatcher-servlet.xml配置文件。-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!--其中<param-value>**.xml</param-value> 这里可以使用多种写法-->
            <!--1、不写,使用默认值:/WEB-INF/<servlet-name>-servlet.xml-->
            <!--2、<param-value>/WEB-INF/classes/dispatcher-servlet.xml</param-value>-->
            <!--3、<param-value>classpath*:dispatcher-servlet.xml</param-value>-->
            <!--4、多个值用逗号分隔-->
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>
        </init-param>
        <!--是启动顺序,让这个Servlet随Servletp容器一起启动。-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <!--这个Servlet的名字是dispatcher,可以有多个DispatcherServlet,是通过名字来区分的。每一个DispatcherServlet有自己的WebApplicationContext上下文对象。同时保存的ServletContext中和Request对象中.-->
        <!--ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些,ApplicationContext则是“应用的容器”了:P,Spring把Bean放在这个容器中,在需要的时候,用getBean方法取出-->
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern> <!--会拦截URL中带“/”的请求。-->
    </servlet-mapping>
</web-app>

contextConfigLocation:Spring的核心就是配置文件,可以说配置文件是Spring中必不可少的。这个参数就是制定了SpringMVC需要使用的配置文件,这个参数将Web和Spring的配置文件相结合的一个关键参数。主要就是通过ContextLoaderListener在Servlet容器初始化过程中,获取contextConfigLocation参数,然后ContextLoader根据参数指定的路径加载配置文件,完成Spring容器的加载。

DispatchServlet:包含了SpringMVC的请求逻辑,SpringMVC使用此类拦截web请求并转发到相应的Controller中进行相应的逻辑处理。

注意:<context-param>中的contextConfigLocation初始化的Spring Ioc容器,针对整个Spring应用。但是在<init-param>中的contextConfigLocation是属于DispatchServlet的,也就是说的SpringMVC Ioc容器,主要包含的是@Controller。需要注意Spring Ioc和SpringMVC Ioc容器的关系,防止bean的重复加载。

5.Spring上下文初始化了什么??

ServletContext由Servlet容器进行初始化,spring的ContextLoaderListener初始化了WebApplicationContext。
1.servlet容器启动,为web应用创建一个全局上下文环境,ServletContext。每个web.xml对应一个全局上下文环境。
2.servlet容器调用web.xml中配置的ContextLoaderListener,加载context-param指定的配置文件的信息到IOC容器中。 初始化属于SpringMVC 的WebApplicationContext上下文环境(即IOC容器)。
     从上文的源码中可以看到WebApplicationContext在ServletContext中以键值对的形式保存。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 将WebApplicationcontext放到ServletContext中
3.servlet容器开始初始化web.xml中配置的<servlet>。为其初始化属于自己的ServletContext(这个每个servlet都是独立的,上面的ServletContext是整个webapp中全局上下文)。并加载其设置的配置信息到该上下文中。将WebApplicationContext设置为Servlet的父容器。
4. 此后的所有servlet的初始化都按照3步中方式创建,初始化自己的上下文环境,将WebApplicationContext设置为自己的父上下文环境。
<servlet>
     <servlet-name>springmvc</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     <init-param>
           <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
     <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
     <servlet-name>springmvc</servlet-name>
     <url-pattern>/</url-pattern>
</servlet-mapping>    

SpringMVC源码 1 ContextLoaderListener和Servlet容器web.xml配置


keyA = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE



spring配置时:<context:exclude-filter>的使用原因,为什么在applicationContext.xml中排除controller,而在spring-mvc.xml中incloud这个controller
既然知道了spring的启动流程,那么web容器初始化webApplicationContext时作为公共的上下文环境,只需要将service、dao等的配置信息在这里加载,而servlet自己的上下文环境信息不需要加载。故,在applicationContext.xml中将@Controller注释的组件排除在外,而在dispatcherServlet加载的配置文件中将@Controller注释的组件加载进来,方便dispatcherServlet进行控制和查找。故,配置如下:
 
applicationContext.mxl中:
 <context:component-scan base-package="com.linkage.edumanage">
      <context:exclude-filter expression="org.springframework.stereotype.Controller"    type="annotation" /> 
 </context:component-scan>
spring-mvc.xml中:
  <context:component-scan base-package="com.brolanda.cloud"   use-default-filters="false"> 
      <context:include-filter expression="org.springframework.stereotype.Controller"    type="annotation" /> 
 </context:component-scan>
若SpringIoC容器和SpringMVCIoc容器扫描的包有重合的部分,会导致重合的Bean被创建两次。
SpringIOC容器可以理解为是WebApplicationCont。SpringMVCIoc容器则是,DispatchServlet对应的ServletContext。