最近回顾了一下去年看过的Spring相关的书籍,从《精通Spring 4.x 企业应用开发实战》这本书开始的,一上来我就又忘记了之前看过 的一个知识点,即Spring容器和web容器之间的区别和联系,又去网上找了一些文章和资料,总结如下:

1、Spring容器和SpringMVC容器都是管理Bean对象的地方,Spring容器管理Dao、Service相关的Bean,SpringMVC容器管理跟 DispatcherServlet相关的Bean,比如Controller和ViewResolver。所以我们在SpringMVC的配置文件里配置的扫描路径就是 Controller的路径,而Spring的配置文件里自然配的就是Service和Dao的路径。

  • 在实际工程中,一个项目中会包括很多配置,根据不同的业务模块来划分,我们一般思路是:Spring根容器负责所有其他 非controller的Bean的注册,而SpringMVC只负责controller相关的Bean的注册。 attention:且SpringMVC容器是在DispatcherServlet的init方法中创建的;SpringMVC拦截器也是SpringMVC容器管理的,所以在SpringMVC的拦截器里,可以直接注入bean对象。

2、Spring容器和SpringMVC容器是父子容器的关系。Spring容器是父容器,SpringMVC容器是子容器。在子容器中可以访问父容器中的对象,但是父容器中不能访问子容器中的对象。简单的可以理解为在controller 中可以访问service对象,但是在service中确不能访问controller对象。

3、web容器(比如Tomcat和jetty,当然这两者也是servlet容器,或者说包含了servlet容器;但是Apache仅仅是一个web容器,不包含servlet容器,不能处理servlet请求。 大多数servlet容器同时提供了web容器的功能,也就是说大多servlet容器可以独立运行你的web应用。),Tomcat可以看做是一个"HTTP服务器 + Servlet容器",比Spring容器的范围更大,它是管理servlet、监听器(listener)、过滤器(Filter)的, 这些并不在Spring和SpringMVC容器的管理范围以内。因此,我们无法在这些类中直接使用Spring注解的方式来注入我们需要的对象,是无效的,web容器是无法识别的。

但我们有时候又确实会有这样的需求,比如在容器启动的时候,做一些验证或者初始化操作,这时可能会在监听器里用到bean对象;又或者需要定义一个过滤器做一些拦截操作,也可能会用到bean对象。 那么在这些地方怎么获取spring的bean对象呢?可以参考下面这两个方法:它们都是ServletContextListener接口的方法,表示监听到了ServletContext初始化时事件。

(1)、public void contextInitialized(ServletContextEvent sce) {
  ApplicationContext context = (ApplicationContext) sce.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 
    //**这里是从ServletContext中拿到Spring容器,这里不仅是一个ApplicationContext,确切的说是一个WebApplicationContext
    再从Spring容器中拿到Bean**
  UserService userService = (UserService) context.getBean("userService");
}


(2)、
public void contextInitialized(ServletContextEvent sce) {
  WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); 
  UserService userService = (UserService) webApplicationContext.getBean("userService"); 
}

注意:以上代码有一个前提,那就是servlet容器在实例化ConfigListener并调用其方法之前,要确保spring容器已经初始化完毕!而spring容器的初始化也是由Listener(ContextLoaderListener)完成, 因此只需在web.xml中先配置初始化spring容器的Listener,然后在配置自己的Listener。

4、这几个容器的大体初始化流程是:web容器中有Servlet容器,Spring项目部署后存在Spring容器和SpringMVC容器。其中Spring容器控制Service和Dao层的Bean对象,SpringMVC容器控制Controller层 的Bean对象,Servlet容器控制servlet对象。项目启动时,首先servlet初始化,初始化过程中根据web.xml中的Spring配置进行加载这个配置,然后初始化Spring和SpringMVC容器,待容器加载完成,servlet 初始化完成,完成启动。这只是一个大概的启动流程,还有很多细则。HTTP请求到达web容器时,会到达Servlet容器,容器通过分发器(DispatcherServlet)将请求发到具体的Controller上。

5、Tomcat在启动的时候给每一个web应用创建一个全局的上下文环境,这个上下文就是ServletContext,它为后面创建的Spring容器提供了宿主环境。同一个web应用的所有servlet共享一个ServletContext, servlet对象可以通过ServletContext访问容器中的各种资源 Tomcat容器在 启动的过程中会触发容器初始化事件,Spring 的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法内部Spring会初始化全局的Spring根容器(根据web.xml中配置的Spring配置文件路径),这个就是Spring 的IOC容器(是一个ApplicationContext,确切的说是一个WebApplicationContext),初始化完成以后,将Spring容器存储在了ServletContext中,以便后面使用。

6、Tomcat在启动的时候还会扫描所有的servlet,一般一个web应用中可能会有多个servlet。以DispatcherServlet为例,这个servlet是一个标准的前端控制器,用于转发、匹配、处理每一个servlet请求。 servlet一般会延迟加载,当第一个请求到达时,Tomcat发现DispatcherServlet还没有实例化,就会调用DispatcherServlet的init方法,DispatcherServlet在初始化的时候,会建立自己的容器,这个 就是SpringMVC容器,用来管理持有Spring MVC相关的Bean。同时(是在SpringMVC容器初始化过程中)SpringMVC容器会从ServletContext中拿到Spring容器,并将 Spring容器设置为SpringMVC容器的父容器。也就是说,父容器这个时候已经创建好了,SpringMVC的创建是依赖于Spring容器的。

7、在Spring的具体实现上,子容器和父容器都是通过ServletContext的setAttribute()方法放置到ServletContext中的。但是ContextLoaderListener会 先于DispatcherServlet创建ApplicationContext(这个一般叫做上下文,但其实就是容器的意思),DispatcherServlet在创建ApplicationContext的 时候会先找到ContextLoaderListener创建的ApplicationContext(即父容器,此时父容器已经创建好了),再将这个创建好的ApplicationContext作为参数 传递给DispatcherServlet创建的ApplicationContext的 setParent()方法,这一步就是设置父容器。然后也会将这个Servlet创建的ApplicationCont (子容器)放入ServletContext中——即 每一个servlet都有一个自己单独的上下文。

可以看下下面的一个流程示例,包含了配置文件里的内容:

各个容器的创建过程:

1、TOMCAT启动,Servlet容器随即启动,然后读取server.xml配置文件,启动里面配置的web应用,为每个应用创建一个“全局上下文环境”(ServletContext);

2、创建Spring容器实例。调用web.xml中配置的ContextLoaderListener,初始化WebApplicationContext上下文环境(即IOC容器),加载context­param指定的配置文件信息到IOC容器中。 WebApplicationContext在ServletContext中以键值对的形式保存。

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:root-context.xml</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

contextConfigLocation指的是Spring读取配置文件的位置。ContextLoaderListener用于在启动web容器的时候,去上面的位置 读取配置文件并初始化Spring容器。这个Listener就是在标准Spring web工程中Spring开始干活的切入点,当然你也可以自己实现一个Listener去启动Spring容器 。本质上,ContextLoaderListener实现了ServletContextListener(这是servlet规范中的一个类,是ServletContext监听类,里面的两个方法分别监听到了ServletContext初始化事件和销毁事件),所以在web容器启动的时候 ,ContextLoaderListener就监听到了这个初始化事件,开始工作了。

3、创建SpringMVC容器实例。调用web.xml中配置的servlet-class,为其初始化自己的上下文信息,并加载其设置的配置信息到该上下文中。将WebApplicationContext设置为它的父容器。

<!-- springMVC配置 -->

<servlet>
  <servlet-name>appServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:servlet-context.xml</param-value>
  </init-param>
   **如果没有配置这个初始化配置文件的路径,那么会默认去哪儿找呢?应该是servlet名字加上 “”"-" 再加上servlet.xml,去这个文件里**
   **SpringMVC容器的配置文件,一般用约定的默认**

  <init-param>
    <param-name>activeReverseAjaxEnabled</param-name>
    <param-value>true</param-value>
  </init-param>

  <init-param>
    <param-name>allowScriptTagRemoting</param-name >
    <param-value>true </param-value>
  </init-param>

  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>appServlet</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

一个典型的SpringMVC容器的初始化文件内容会如下:其中包括扫描controller相关的Bean,以及一个视图解析器Bean。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.0.xsd">
       
	<!-- 扫描web包,应用Spring的注解 -->
	<context:component-scan base-package="com.smart.web"/>
	
	<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
	<bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:viewClass="org.springframework.web.servlet.view.JstlView" 
        p:prefix="/WEB-INF/jsp/"
        p:suffix=".jsp" />

</beans>

4、此后的所有servlet的初始化都按照3步中方式创建,初始化自己的上下文环境,将WebApplicationContext设置为自己的父上下文环境。当Spring在执行ApplicationContext的getBean时,如果在 自己context中找不到对应的bean,则会在父ApplicationContext中去找。