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文件中的以下配置代码:
[plain] view plain copy
  1. <span style="font-family:SimSun;font-size:14px;"><context-param>  
  2.         <param-name>contextConfigLocation</param-name>  
  3.         <param-value>/WEB-INF/applicationContext.xml</param-value>  
  4.     </context-param>  
  5.                                                                                                                                                
  6.     <listener>  
  7.         <listener-class>  
  8.             org.springframework.web.context.ContextLoaderListener  
  9.         </listener-class>  
  10.     </listener>  
  11.                                                                                                                                                
  12.     <servlet>  
  13.         <servlet-name>mvc-dispatcher</servlet-name>  
  14.         <servlet-class>  
  15.             org.springframework.web.servlet.DispatcherServlet  
  16.         </servlet-class>  
  17.         <load-on-startup>1</load-on-startup>  
  18.     </servlet>  
  19.                                                                                                                                            
  20.     <servlet-mapping>  
  21.         <servlet-name>mvc-dispatcher</servlet-name>  
  22.         <url-pattern>/</url-pattern>  
  23.     </servlet-mapping></span>  

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

[java] view plain copy
  1. <span style="font-family:SimSun;font-size:14px;">servletContext.setAttribute(  
  2.               WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,  
  3.  this.context);</span>  

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

[plain] view plain copy
  1. <span style="font-family:SimSun;font-size:14px;"><servlet>  
  2.     <servlet-name>  
  3.        customConfiguredDispacherServlet  
  4.    </servlet-name>  
  5.    <servlet-class>  
  6.        org.springframework.web.servlet.DispatcherServlet  
  7.    </servlet-class>  
  8.    <init-param>  
  9.        <param-name>  
  10.            contextConfigLocation  
  11.        </param-name>  
  12.        <param-value>  
  13.            /WEB-INF/dispacherServletContext.xml  
  14.        </param-value>  
  15.    </init-param>  
  16.    <load-on-startup>1</load-on-startup>  
  17. </servlet></span>  

    问题是:以上两个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中找到如下代码:

    wac.setParent(parent);

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

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

Spring API中的解释:

public interface WebApplicationContext
extends ApplicationContext

Interface to provide configuration for a web application. This is read-only while the application is running, but may be reloaded if the implementation supports this.

This interface adds a getServletContext() method to the generic ApplicationContext interface, and defines a well-known application attribute name that the root context must be bound to in the bootstrap process.

Like generic application contexts, web application contexts are hierarchical. There is a single root context per application, while each servlet in the application (including a dispatcher servlet in the MVC framework) has its own child context.

In addition to standard application context lifecycle capabilities, WebApplicationContext implementations need to detect ServletContextAware beans and invoke the setServletContext method accordingly.


翻译:

公共接口WebApplicationContext
扩展ApplicationContext
提供Web应用程序配置的接口。 这在应用程序运行时是只读的,但是如果实现支持这个,可以重新加载。
该接口将一个getServletContext()方法添加到通用ApplicationContext接口,并定义一个众所周知的应用程序属性名称,该名称在引导进程中必须绑定根上下文。
像通用应用程序上下文一样,Web应用程序上下文是分层的。 每个应用程序都有一个根上下文,而应用程序中的每个servlet(包括MVC框架中的调度器servlet)都有自己的子上下文。
除了标准的应用程序上下文生命周期功能,WebApplicationContext实现还需要检测ServletContextAware bean,并相应地调用setServletContext方法。


工作机制:

Spring中DispacherServlet与WebApplicationContext、ServletContext的关系和工作机制

1.DispacherServlet是前端控制器(Struts是Filter),负责接收前端请求,并根据请求找到具体的Handler(目前的Handler是方法级别的);SpringMVC中DispacherServlet初始化放在web.xml中,<load-on-start>1</load-on-start>,意思是Servlet容器启动时自动加载该Servlet。


2.HandlerMapping:负责将URL和controller匹配到一起,简单来说就是根据URL找到具体的类(根据注解@Controller和@RequestMapping找到);


3.HandlerAdapter:根据URL找到具体类的具体方法;


  jdk 具体执行过程如下:


  1.DispatcherServlet本质上还是一个Servlet,故需要在web容器里初始化


[java] view plain copy
  1. /** 
  2.      * Initialize the strategy objects that this servlet uses. 
  3.      * <p>May be overridden in subclasses in order to initialize further strategy objects. 
  4.      */  
  5.     protected void initStrategies(ApplicationContext context) {  
  6.         initMultipartResolver(context);  
  7.         initLocaleResolver(context);  
  8.         initThemeResolver(context);   
  9.         initHandlerMappings(context); // 从ApplicationContext容器中初始生成HandlerMappings  
  10.         initHandlerAdapters(context); // 初始化HandlerAdapter  
  11.         initHandlerExceptionResolvers(context);  
  12.         initRequestToViewNameTranslator(context);  
  13.         initViewResolvers(context);  
  14.         initFlashMapManager(context);  
  15.     }  
2.当有一个请求到达时,调用DispatcherServlet的doDispatch()方法,该方法找到真正的handler处理该请求;主要代码如下:

[java] view plain copy
  1. /** 
  2.      * Process the actual dispatching to the handler. 
  3.      * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. 
  4.      * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters 
  5.      * to find the first that supports the handler class. 
  6.      * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers 
  7.      * themselves to decide which methods are acceptable. 
  8.      * @param request current HTTP request 
  9.      * @param response current HTTP response 
  10.      * @throws Exception in case of any kind of processing failure 
  11.    * 
  12.     
  13.     通过查找HandlerMapping找到真正的Handler;先找到handler class,根据该class找到HandlerAdapter;    
  14.     通过查询servlet已注册的HandlerAdapters,找到真正处理该请求的method 
  15.  
  16.      */  
  17.     protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
  18.         HttpServletRequest processedRequest = request;  
  19.         HandlerExecutionChain mappedHandler = null;  
  20.         boolean multipartRequestParsed = false;  
  21.   
  22.             ModelAndView mv = null;  
  23.            // Determine handler for the current request.返回HandlerExecutionChain;  
  24.            mappedHandler = getHandler(processedRequest, false);  
  25.       // Determine handler adapter for the current request.根据HandlerExecutionChain找到HandlerAdapter  
  26.       HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  27.       
  28.       // Actually invoke the handler.使用给定的handler处理请求;  
  29.       mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  30.   
  31.     }  

[java] view plain copy
  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {  
  2.         for (HandlerMapping hm : this.handlerMappings) {  
  3.             if (logger.isTraceEnabled()) {  
  4.                 logger.trace(  
  5.                         "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");  
  6.             }  
  7.   
  8.     //查询所有已注册的HandlerMapping beans找到处理该请求的Handler;  
  9.             HandlerExecutionChain handler = hm.getHandler(request);  
  10.             if (handler != null) {  
  11.                 return handler;  
  12.             }  
  13.         }  
  14.         return null;  
  15.     }  

其中HandlerExecutionChain是有一个HandlerAdapter和若干个HandlerInteceptor组成,请求先经HandlerInteceptor处理后在传给Adapter。一般我们使用HandlerInteceptor做一些
登录验证,安全验证等


3.HandlerInterceptor和Servlet里的Filter作用很像,下面讲一下关于Filter的东西
  3.1  Filter作用?


    用于HttpServletRequest预处理和HttpServletResponse后处理;执行流程是 request---->listener----->filter------>struts拦截器(?)----->Servlet,这是链式处理过程,请求最后总会到达Servlet的;


  3.2 一般用来干啥?


    用户登录验证,用户发来请求,拦截请求后,验证用户是否登录过,如果没有登录调到登录页面,日志功能,编码格式。


  3.3Filter有三个过程


    Filter一般在服务器端工作,故web容器调用它的init(),在web容器初始化的时候创建Filter的实例对象;


    chain.doFilter(),把要做的事情都放在doFilter()方法里面完成;


  3.4 filter使用配置文件注册,在web应用程序一启动,web服务器就会实例化filter对象;


    filter实例化


    web.xml配置:


[java] view plain copy
  1. <!-- 编码过滤器 -->    
  2.     <filter>    
  3.         <filter-name>setCharacterEncoding</filter-name>    
  4.         <filter-class>com.company.strutstudy.web.servletstudy.filter.EncodingFilter</filter-class>  //该过滤器(EncodingFilter)具体位置。  
  5.         <init-param>    
  6.             <param-name>encoding</param-name>   // 设定encoding编码为utf-8.  
  7.             <param-value>utf-8</param-value>    
  8.         </init-param>    
  9.     </filter>    
  10.     <filter-mapping>    
  11.         <filter-name>setCharacterEncoding</filter-name>    
  12.         <url-pattern>/*</url-pattern>    //处理所有页面的编码格式  
  13.     </filter-mapping>    
  14.      
  15. <!-- 请求url日志记录过滤器 -->    
  16.     <filter>    
  17.         <filter-name>logfilter</filter-name>    
  18.         <filter-class>com.company.strutstudy.web.servletstudy.filter.LogFilter</filter-class>    
  19.     </filter>    
  20.     <filter-mapping>    
  21.         <filter-name>logfilter</filter-name>    
  22.         <url-pattern>/*</url-pattern>    
  23.     </filter-mapping>    
编码拦截器:

[java] view plain copy
  1. public class EncodingFilter implements Filter {    
  2.     private String encoding;    
  3.     private Map<String, String> params = new HashMap<String, String>();    
  4.     // 项目结束时就已经进行销毁    
  5.     public void destroy() {    
  6.         System.out.println("end do the encoding filter!");    
  7.         params=null;    
  8.         encoding=null;    
  9.     }    
  10.     public void doFilter(ServletRequest req, ServletResponse resp,    
  11.             FilterChain chain) throws IOException, ServletException {    
  12.         //UtilTimerStack.push("EncodingFilter_doFilter:");    
  13.         System.out.println("before encoding " + encoding + " filter!");    
  14.         req.setCharacterEncoding(encoding);    
  15.         // resp.setCharacterEncoding(encoding);    
  16.         // resp.setContentType("text/html;charset="+encoding);    
  17.         chain.doFilter(req, resp);          
  18.         System.out.println("after encoding " + encoding + " filter!");    
  19.         System.err.println("----------------------------------------");    
  20.         //UtilTimerStack.pop("EncodingFilter_doFilter:");    
  21.     }    
  22.      
  23.     // 项目启动时就已经进行读取    
  24.     public void init(FilterConfig config) throws ServletException {    
  25.         System.out.println("begin do the encoding filter!");    
  26.         encoding = config.getInitParameter("encoding");    
  27.         for (Enumeration e = config.getInitParameterNames(); e    
  28.                 .hasMoreElements();) {    
  29.             String name = (String) e.nextElement();    
  30.             String value = config.getInitParameter(name);    
  31.             params.put(name, value);    
  32.         }    
  33.     }    
  34.  }