java web中各类context的关系

我举得这篇文章解决了个人不少疑惑,理清了我之前不太清楚的Context关系,读懂这篇文章颇有助于理解源码,html

原文连接在这里:https://www.jianshu.com/p/2537e2fec546前端

我把它转载在本身博客里,惧怕之后找不到,原文以下java

 

网上博客中看到一句话,很形容的描绘了web程序和上下文的关系,这里引用一下来讲明:若是对“上下文”不太了解的,我这边说下,程序里面所谓的“上下文”就是程序的执行环境,打个比方:你有家吧?若是家都没有就别学编程了,租的也行啊!你就至关于web程序,家就至关于web程序的上下文,你能够在家里放东西,也能够取东西,你的衣食住行都依赖这个家,这个家就是你生活的上下文环境。web

该博客地址: Spring和SpringMVC配置中父子WebApplicationContext的关系spring

Spring启动过程

第一步:

 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;编程

第二步:

 其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】为属性Key,将其存储到ServletContext中,便于获取;tomcat

第三步:

 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet能够配置多个,以最多见的DispatcherServlet为例,这个servlet其实是一个标准的前端控制器,用以转发、匹配、处理每一个servlet请求。DispatcherServlet上下文在初始化的时候会创建本身的IoC上下文,用以持有spring mvc相关的bean。在创建DispatcherServlet本身的IoC上下文时,会利用【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】先从ServletContext中获取以前的根上下文(即WebApplicationContext)做为本身上下文的parent上下文(有个parent属性做为对Spring的ApplicationContext的引用)。有了这个parent上下文以后,再初始化本身持有的上下文。这个DispatcherServlet初始化本身上下文的工做在其initStrategies方法中能够看到,大概的工做就是初始化处理器映射、视图解析等。这个servlet本身持有的上下文默认实现类也是XmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是经过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每一个servlet就持有本身的上下文,即拥有本身独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。mvc

经过上面这个Spring的启动过程,咱们能够清楚的了解ServletContext、WebApplicationContext、XmlWebApplicationContext、以及DispatcherServlet上下文之间的关系,而且会将WebApplicationContext放在ServletContext中。app

 
                                                 XmlWebApplicationContext体系结构.png
 

1. ServletContext:

 首先说说ServletContext这个web应用级的上下文。web容器(好比tomcat、jboss、weblogic等)启动的时候,它会为每一个web应用程序建立一个ServletContext对象 它表明当前web应用的上下文(注意:是每一个web应用有且仅建立一个ServletContext,一个web应用,就是你一个web工程)。一个web中的全部servlet共享一个ServletContext对象,因此能够经过ServletContext对象来实现Servlet之间的通信。在一个继承自HttpServlet对象的类中,能够经过this.getServletContext来获取。async

2. WebApplicationContext:

 经过源码详细说明一下 第二步 的过程,web.xml(上图)中咱们配置了ContextLoaderListener,该listener实现了ServletContextListener的contextInitialized方法用来监听Servlet初始化事件:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/SpringApplicationContext.xml</param-value>
</context-param>
<listener>  
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>
 

 由下面的源码能够发现初始化的是WebApplicationContext的IoC容器,它是一个接口类,其默认实现是XmlWebApplicationContext。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

 

这是ContextLoaderListener中的contextInitialized()方法,这里主要是用initWebApplicationContext()方法来初始化WebApplicationContext。这里涉及到一个经常使用类WebApplicationContext:它继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定于Web的操做及属性。

 initWebApplicationContext(event.getServletContext()),进行了建立根上下文,并将该上下文以key-value的方式存储到ServletContext中。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    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);
        }
        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);
            }
        }
        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;
    }
}

 

 在initWebApplicationContext()方法中主要体现了WebApplicationContext实例的建立过程。首先,验证WebApplicationContext的存在性,经过查看ServletContext实例中是否有对应key的属性验证WebApplicationContext是否已经建立过实例。若是没有经过,createWebApplicationContext()方法来建立实例,并存放至ServletContext中。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    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);
}

 

 在createWebApplicationContext()方法中,经过BeanUtils.instanceClass()方法建立实例,而WebApplicationContext的实现类名称则经过determineContextClass()方法得到。

protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

 

 determineContextClass()方法,经过defaultStrategies.getProperty()方法得到实现类的名称,而defaultStrategies是在ContextLoader类的静态代码块中赋值的。具体的途径,则是读取ContextLoader类的同目录下的ContextLoader.properties属性文件来肯定的。

 也就是说,在初始化的过程当中,程序会首先读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个类经过反射进行实例的建立。

 

综上所述:

 LoaderListener监听器的做用就是启动Web容器时,自动装配ApplicationContext的配置信息。由于它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的contextInitialized()方法初始化WebApplicationContext实例,并放入到ServletContext中。因为在ContextLoaderListener中关联了ContextLoader这个类,因此整个加载配置过程由ContextLoader来完成。

3. DispatcherServlet

 

 
DispatcherServlet结构图.png

 详解 第三步,contextLoaderListener监听器初始化完毕后,开始初始化DispatcherServlet,下面为初始化方法的源码:

 

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
}

 

须要作的八件事情以下所述:

  • initMultipartResolver:初始化MultipartResolver,用于处理文件上传服务,若是有文件上传,那么就会将当前的HttpServletRequest包装成DefaultMultipartHttpServletRequest,而且将每一个上传的内容封装成CommonsMultipartFile对象。须要在dispatcherServlet-servlet.xml中配置文件上传解析器。
  • initLocaleResolver:用于处理应用的国际化问题,本地化解析策略。
  • initThemeResolver:用于定义一个主题。
  • initHandlerMapping:用于定义请求映射关系。
  • initHandlerAdapters:用于根据Handler的类型定义不一样的处理规则。
  • initHandlerExceptionResolvers:当Handler处理出错后,会经过此将错误日志记录在log文件中,默认实现类是SimpleMappingExceptionResolver。
  • initRequestToViewNameTranslators:将指定的ViewName按照定义的RequestToViewNameTranslators替换成想要的格式。
  • initViewResolvers:用于将View解析成页面。
  • initFlashMapManager:用于生成FlashMap管理器。

 经过查看DispatcherServlet(源码内容太多就不往上放了),DispatcherServlet继承自FrameworkServlet,而FrameworkServlet是继承自HttpServletBean的,HttpServletBean又继承了HttpServlet。这是由于DispatcherServlet自己就得是一个Servlet,且含有doGet()和doPost()方法,Web容器才能够调用它,因此它的顶级父类为含有这俩方法的HttpServlet。具体的Web请求,会通过FrameServlet的processRequest方法简单处理后,紧接着调用DispatcherServlet的doService方法,而在这个方法中封装了最终调用处理器的方法doDispatch。这也意味着,DispatcherServlet的最主要的核心功能由doService和doDispatch实现的。

DispatcherServlet类的方法大体可分为三种:

  • 初始化相关处理类的方法。
  • 响应Http请求的方法。
  • 执行处理请求逻辑的方法。
核心方法 doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }

                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}

 

DispatcherServlet处理该类请求的步骤:
  1. 在接收到请求后,经过几级Servlet类型的父类的处理,先调用doService在request中设置一些必需的参数。最终会调用DispatcherServlet的doDispatch方法。
  2. 在doDispatch方法中,首先检测request是否包含多媒体类型(如File文件上传),而后将检测后的request转换为processedRequest对象。以后检测processedRequest对象是否为原始request(若是是,即原来的request不包含多媒体信息),而后将boolean结果赋给multipartRequestParsed变量(若multipartRequestParsed为true,在最后会清除processedRequest对象中的多媒体信息)。
  3. 十分重要的一步,就是经过调用处理器映射器查找Handler。调用getHandler来获取相关的处理器对象。在getHandler方法中,利用处理器映射器HandlerMapping经过request来获取一个包含Handler处理器自己和其先后拦截器interceptor的处理器执行链HandlerExecutionChain对象。
  4. 经过HandlerExecutionChain对象获取具体的Handler处理器对象,此时使用getHandlerAdapter方法获取能够处理类型的处理器适配器HandlerAdapter对象。
  5. 调用HandlerAdapter对象的handle方法,将可能带有多媒体信息的processRequest对象,原始request对象,以及Handler处理器自己做为参数传入,handle方法会根据这些参数去执行开发者本身开发的Handler的相关
    请求处理逻辑,并返回含有反馈信息和结果视图信息的ModelAndView对象。
  6. 得到ModelAndView对象后,会进行视图渲染,将model数据填充到request域。在processDispatchResult方法中会对ModelAndView对象进行处理。而在processDispatchResult方法中包含一个render方法,其参数为ModelAndView对象以及request和response对象。在render方法中,经过resolveViewName会获取到实际须要使用的视图View对象,这个对象的具体类型是由XXX决定的。而后就会执行具体的View对象的render方法来完成数据的显示过程。这里举一个视图类型的例子,他在render方法中具体执行了如下逻辑来绑定结果数据和视图:
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception{
  //遍历model里面的数据,填充到request域
  for (Map.Entry<String, Object> entry : model.entrySet()){
      String modeName = entry.getKey();
      Object modelValue = entry.getValue();
      if(modelValue != null){
          request.setAttribute(modelName, modelValue);
          if(logger.isDebugEnabled()){
              logger.debug("Added model object '"
                  + modelName + "' of type [" + modelValue.getClass.getName()
                  +"] to request in view with name '" + getBeanName() + "'");
          } else{
             request.removeAttribute(modelName);
            if(logger.isDebugEnabled()){
                logger.debug("Remove modek object '" +modelName +
                    "' from request in view with name '" +getBeanName() + "'");
            } 
         }
      }
  }
}

 

 能够看到,在这里会把ModelAndView中model的数据遍历出来,分为key和value,而且将数据设置在request的attribute域中。以后加载页面时就可使用标签在request域中获取返回参数了。

 
                                                                   DispatcherServlet初始化时序图.png
相关文章
相关标签/搜索