zoukankan      html  css  js  c++  java
  • java web中各种context的关系

    我举得这篇文章解决了我的很多疑惑,理清了我以前不太清楚的Context关系,读懂这篇文章很有助于理解源码,

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

    我把它转载在自己博客里,害怕以后找不到,原文如下

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

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

    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中,便于获取;

    第三步:

     再次,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。

    通过上面这个Spring的启动过程,我们可以清楚的了解ServletContext、WebApplicationContext、XmlWebApplicationContext、以及DispatcherServlet上下文之间的关系,并且会将WebApplicationContext放在ServletContext中。

     
                                                     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来获取。

    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
  • 相关阅读:
    记一次Cloudera中页面监控失效问题
    spark on yarn 动态资源分配报错的解决:org.apache.hadoop.yarn.exceptions.InvalidAuxServiceException: The auxService:spark_shuffle does not exist
    sparkOnYarn报错org.apache.hadoop.fs.FSDataInputStream
    记一次newApiHadoopRdd查询数据不一致问题
    记一次sparkOnyarn错误:java.lang.UnsatisfiedLinkError
    线程八大基础核心二(启动线程)
    线程八大基础核心一(创建线程的方式)
    非常时期,做好一个普通人
    哈希算法(应用场景)
    查找算法(二分查找)
  • 原文地址:https://www.cnblogs.com/by-my-blog/p/11831053.html
Copyright © 2011-2022 走看看