zoukankan      html  css  js  c++  java
  • spring mvc DispatcherServlet详解之前传---FrameworkServlet

    做项目时碰到Controller不能使用aop进行拦截,从网上搜索得知:使用spring mvc 启动了两个context:applicationContext 和WebapplicationContext。

    首先我们来了解applicationContext 和WebapplicationContext区别和联系吧

    1. ApplicationContext和WebApplicationContext是继承关系

    /**
    	 * 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.
    	 *
    	 * <p>This interface adds a {@code 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.
    	 *
    	 * <p>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.
    	 *
    	 * <p>In addition to standard application context lifecycle capabilities,
    	 * WebApplicationContext implementations need to detect {@link ServletContextAware}
    	 * beans and invoke the {@code setServletContext} method accordingly.*/
    	public interface WebApplicationContext extends ApplicationContext {}

    2. ContextLoaderListener 创建基于web的应用根applicationContext, 并将applicationContext放入到ServletContext, applicationContext加载或者卸载spring管理的beans。在structs和spring mvc的控制层都是这样使用的。


    3. DispatcherServlet创建自己的WebApplicationContext并管理这个WebApplicationContext里面的 handlers/controllers/view-resolvers. 

    4. 当ContextLoaderListener和DispatcherServlet一起使用时, ContextLoaderListener 先创建一个根applicationContext,然后DispatcherSerlvet创建一个子applicationContext并且绑定到根applicationContext。

    首先来看看web.xml的定义:

    一般的web应用,通过ContextLoaderListener监听,ContextLoaderListener中加载的context成功后,spring 将 applicationContext存放在ServletContext的key值为"org.springframework.web.context.WebApplicationContext.ROOT"的attribute中。

    复制代码
    <listener>  
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  
    <context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>classpath*:conf/applicationContext*.xml</param-value>  
    </context-param>  
    复制代码

    DispatcherServlet加载的context成功后,会将applicationContext存放在org.springframework.web.servlet.FrameworkServlet.CONTEXT. + (servletName)的attribute中。 

    <servlet>
    	<servlet-name>mvcServlet</servlet-name>
    	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	<init-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:conf/spring-dispatcher-servlet.xml</param-value>
    	</init-param>
    	<load-on-startup>1</load-on-startup>
    </servlet>
    

      

    当然,如果没有指定*servlet.xml 配置,则默认使用DispatcherServlet的默认配置DispatcherServlet.properties

    复制代码
    # Default implementation classes for DispatcherServlet's strategy interfaces.
    # Used as fallback when no matching beans are found in the DispatcherServlet context.
    # Not meant to be customized by application developers.
    
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
        org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
        org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,
        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,
        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
    
    org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
    
    org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
    复制代码

    简单的来说:spring bean的管理在applicationContext中,ContextLoaderListener的作用:

    1. 将applicationContext的生命周期和servletContext的生命周期联系到一起。

    2. 自动管理applicationContext的创建,ContextLoaderListener 给我们提供了一个便利,不用显式的去创建applicationContext。

    DispatcherServlet 相关bean的管理在WebApplicationContext,ServletContextListener创建WebApplicationContext,WebApplicationContext可以访问ServletContext/ServletContextAware这些bean,还可以访问getServletContext方法。

    正式的官方文档:

    在一个web应用中可以使用多个DispatcherServlet,每个servlet通过自己的命名空间来获取自己的webapplicationContext,然后加载此applicationContext里面的hangdlerMapping,hangdlerAdapter等等。ContextLoaderListener加载根application,所有子applicationContext共享根applicationContext。

    从版本3.1 后,spring 支持将DispatcherServlet注入到根applicationContext,而不用创建自己的webapplicationContext,这主要为支持servlet 3.0 以上版本环境的要求,因为servlet 3.0 以上版本支持使用编程的方式来注册servlet实例。

    spring支持使用多层层次applicationContext,通常我们使用两层结构就够了。

    接下来,通过深入源代码层来探究WebApplicationContext是如何创建的?

    1. DispatcherServlet的初始化过程使用到applicationContext。

        我们知道DispatcherServlet直接继承自FrameworkServlet,而FrameworkServlet又继承了HttpServletBean和 ApplicationContextAware。

    2. FrameworkServlet实现了ApplicationContextAware接口的setApplicationContext()方法,可知DispatcherServlet的applicationContext来自FrameworkServlet。

    复制代码
        /**
         * Called by Spring via {@link ApplicationContextAware} to inject the current
         * application context. This method allows FrameworkServlets to be registered as
         * Spring beans inside an existing {@link WebApplicationContext} rather than
         * {@link #findWebApplicationContext() finding} a
         * {@link org.springframework.web.context.ContextLoaderListener bootstrapped} context.
         * <p>Primarily added to support use in embedded servlet containers.
         * @since 4.0
         */
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
                this.webApplicationContext = (WebApplicationContext) applicationContext;
                this.webApplicationContextInjected = true;
            }
        }
    复制代码

    3. FrameworkServlet的setApplicationContext()方法中WebApplicationContext是如何实例化的呢?

       FrameworkServlet继承自HttpServletBean,HttpServletBean的初始化方法:

    复制代码
    /**
         * Map config parameters onto bean properties of this servlet, and
         * invoke subclass initialization.
         * @throws ServletException if bean properties are invalid (or required
         * properties are missing), or if subclass initialization fails.
         */
        @Override
        public final void init() throws ServletException {
            if (logger.isDebugEnabled()) {
                logger.debug("Initializing servlet '" + getServletName() + "'");
            }
    
            // Set bean properties from init parameters.try {
                PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                throw ex;
            }
    
            // Let subclasses do whatever initialization they like.
            initServletBean();
    
            if (logger.isDebugEnabled()) {
                logger.debug("Servlet '" + getServletName() + "' configured successfully");
            }
        }
    复制代码

    FrameworkServlet 实现了initServletBean(); 

    复制代码
        /**
         * Overridden method of {@link HttpServletBean}, invoked after any bean properties
         * have been set. Creates this servlet's WebApplicationContext.
         */
        @Override
        protected final void initServletBean() throws ServletException {
            getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
            if (this.logger.isInfoEnabled()) {
                this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
            }
            long startTime = System.currentTimeMillis();
    
            try {
                this.webApplicationContext = initWebApplicationContext();
                initFrameworkServlet();
            }
            catch (ServletException ex) {
                this.logger.error("Context initialization failed", ex);
                throw ex;
            }
            catch (RuntimeException ex) {
                this.logger.error("Context initialization failed", ex);
                throw ex;
            }
    
            if (this.logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                        elapsedTime + " ms");
            }
        }
    复制代码

    最终追踪到FrameworkServlet 的initWebApplicationContext()方法

    复制代码
    /**
         * Initialize and publish the WebApplicationContext for this servlet.
         * <p>Delegates to {@link #createWebApplicationContext} for actual creation
         * of the context. Can be overridden in subclasses.
         * @return the WebApplicationContext instance
         * @see #FrameworkServlet(WebApplicationContext)
         * @see #setContextClass
         * @see #setContextConfigLocation
         */protected WebApplicationContext initWebApplicationContext() {
            WebApplicationContext rootContext =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            WebApplicationContext wac = null;
    
            if (this.webApplicationContext != null) {
                // A context instance was injected at construction time -> use it
                wac = this.webApplicationContext;
                if (wac instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                    if (!cwac.isActive()) {
                        // The context has not yet been refreshed -> provide services such as
                        // setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {
                            // The context instance was injected without an explicit parent -> set
                            // the root application context (if any; may be null) as the parent                        cwac.setParent(rootContext);
                        }
                        configureAndRefreshWebApplicationContext(cwac);
                    }
                }
            }
            if (wac == null) {
                // No context instance was injected at construction time -> see if one
                // has been registered in the servlet context. If one exists, it is assumed
                // that the parent context (if any) has already been set and that the
                // user has performed any initialization such as setting the context id
                wac = findWebApplicationContext();
            }
            if (wac == null) {
                // No context instance is defined for this servlet -> create a local one
                wac = createWebApplicationContext(rootContext);
            }
    
            if (!this.refreshEventReceived) {
                // Either the context is not a ConfigurableApplicationContext with refresh
                // support or the context injected at construction time had already been
                // refreshed -> trigger initial onRefresh manually here.            onRefresh(wac);
            }
    
            if (this.publishContext) {
                // Publish the context as a servlet context attribute.
                String attrName = getServletContextAttributeName();
                getServletContext().setAttribute(attrName, wac);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                            "' as ServletContext attribute with name [" + attrName + "]");
                }
            }
    
            return wac;
        }
    复制代码

    我们来分析整个流程吧:

    1. 获取根applicationContext。

    复制代码
            WebApplicationContext rootContext =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    
        /**
         * Find the root {@link WebApplicationContext} for this web app, typically
         * loaded via {@link org.springframework.web.context.ContextLoaderListener}.
         * <p>Will rethrow an exception that happened on root context startup,
         * to differentiate between a failed context startup and no context at all.
         * @param sc ServletContext to find the web application context for
         * @return the root WebApplicationContext for this web app, or {@code null} if none
         * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
         */public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
            return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        }
        /**
         * Find a custom {@link WebApplicationContext} for this web app.
         * @param sc ServletContext to find the web application context for
         * @param attrName the name of the ServletContext attribute to look for
         * @return the desired WebApplicationContext for this web app, or {@code null} if none
         */public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
            Assert.notNull(sc, "ServletContext must not be null");
            Object attr = sc.getAttribute(attrName);
            if (attr == null) {
                return null;
            }
            if (attr instanceof RuntimeException) {
                throw (RuntimeException) attr;
            }
            if (attr instanceof Error) {
                throw (Error) attr;
            }
            if (attr instanceof Exception) {
                throw new IllegalStateException((Exception) attr);
            }
            if (!(attr instanceof WebApplicationContext)) {
                throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
            }
            return (WebApplicationContext) attr;
        }
    复制代码

    2. 判断webapplicationContext是否存在?存在的话就重利用该webapplicationContext

    复制代码
        protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
            if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
                // The application context id is still set to its original default value
                // -> assign a more useful id based on available informationif (this.contextId != null) {
                    wac.setId(this.contextId);
                }
                else {
                    // Generate default id...
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
                }
            }
    
            wac.setServletContext(getServletContext());
            wac.setServletConfig(getServletConfig());
            wac.setNamespace(getNamespace());
            wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    
            // 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(getServletContext(), getServletConfig());
            }
    
            postProcessWebApplicationContext(wac);
            applyInitializers(wac);
            wac.refresh();
        }
    复制代码

    不存在的话,根据配置的属性名去查询:

    复制代码
        /**
         * Retrieve a {@code WebApplicationContext} from the {@code ServletContext}
         * attribute with the {@link #setContextAttribute configured name}. The
         * {@code WebApplicationContext} must have already been loaded and stored in the
         * {@code ServletContext} before this servlet gets initialized (or invoked).
         * <p>Subclasses may override this method to provide a different
         * {@code WebApplicationContext} retrieval strategy.
         * @return the WebApplicationContext for this servlet, or {@code null} if not found
         * @see #getContextAttribute()
         */protected WebApplicationContext findWebApplicationContext() {
            String attrName = getContextAttribute();
            if (attrName == null) {
                return null;
            }
            WebApplicationContext wac =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
            if (wac == null) {
                throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
            }
            return wac;
        }
    复制代码

    3. 如果还查询不到WebapplicationContext,那么就创建一个新的WebapplicationContext,并绑定到root WebapplicationContext上:

    复制代码
    /**
         * Instantiate the WebApplicationContext for this servlet, either a default
         * {@link org.springframework.web.context.support.XmlWebApplicationContext}
         * or a {@link #setContextClass custom context class}, if set.
         * <p>This implementation expects custom contexts to implement the
         * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
         * interface. Can be overridden in subclasses.
         * <p>Do not forget to register this servlet instance as application listener on the
         * created context (for triggering its {@link #onRefresh callback}, and to call
         * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
         * before returning the context instance.
         * @param parent the parent ApplicationContext to use, or {@code null} if none
         * @return the WebApplicationContext for this servlet
         * @see org.springframework.web.context.support.XmlWebApplicationContext
         */protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
            Class<?> contextClass = getContextClass();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Servlet with name '" + getServletName() +
                        "' will try to create custom WebApplicationContext context of class '" +
                        contextClass.getName() + "'" + ", using parent context [" + parent + "]");
            }
            if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
                throw new ApplicationContextException(
                        "Fatal initialization error in servlet with name '" + getServletName() +
                        "': custom WebApplicationContext class [" + contextClass.getName() +
                        "] is not of type ConfigurableWebApplicationContext");
            }
            ConfigurableWebApplicationContext wac =
                    (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
            wac.setEnvironment(getEnvironment());
            wac.setParent(parent);
            wac.setConfigLocation(getContextConfigLocation());
    
            configureAndRefreshWebApplicationContext(wac);
    
            return wac;
        }
    复制代码

    4. 将子applicationContext发布到servlet context上。

    复制代码
            if (this.publishContext) {
                // Publish the context as a servlet context attribute.
                String attrName = getServletContextAttributeName();
                getServletContext().setAttribute(attrName, wac);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                            "' as ServletContext attribute with name [" + attrName + "]");
                }
            }

    /**
    * Return the ServletContext attribute name for this servlet's WebApplicationContext.
    * <p>The default implementation returns
    * {@code SERVLET_CONTEXT_PREFIX + servlet name}.
    * @see #SERVLET_CONTEXT_PREFIX
    * @see #getServletName
    */
    public String getServletContextAttributeName() {
    return SERVLET_CONTEXT_PREFIX + getServletName();
    }

    /**
    * Prefix for the ServletContext attribute for the WebApplicationContext.
    * The completion is the servlet name.
    */
    public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

     
    复制代码

    最后,ContextLoaderListener启动时如何产生applicationContext呢?

    见参考我的这篇文章:http://www.cnblogs.com/davidwang456/archive/2013/03/12/2956125.html

    小结:

    我们就以FrameworkServlet的官方说明来结束吧。没有比它更合适的!希望你喜欢。

    复制代码

    公共抽象类FrameworkServlet继承HttpServletBean;Spring的web框架实现ApplicationContextAwareBase servlet。在一个以java为基础的整体解决方案中提供与Spring应用程序上下文的集成。
    这个类提供以下功能:
    管理每个servlet的WebApplicationContext实例。servlet的配置由servlet的名称空间中的bean决定。
    在请求处理上发布事件,不管请求是否成功处理。子类必须实现doService(javax.servlet.http。HttpServletRequest,javax.servlet。

    复制代码

    参考资料:

    http://starscream.iteye.com/blog/1107036

    http://www.softwarevol.com/en/tutorial/Spring-ContextLoaderListener-And-DispatcherServlet-Concepts


  • 相关阅读:
    删除了原有的offset之后再次启动会报错park Streaming from Kafka has error numRecords must not ...
    sparkStreaming消费kafka-1.0.1方式:direct方式(存储offset到Hbase)
    sparkStreaming消费kafka-1.0.1方式:direct方式(存储offset到zookeeper)
    进程的管理(五)-进程的实现
    进程管理(四)-进程的状态以及转换
    进程管理(三)-进程的层次
    numpy库的认识以及数组的创建
    进程管理(二)-进程的终止
    进程管理(一)-进程的概念以及进程的创建
    python爬取b站排行榜
  • 原文地址:https://www.cnblogs.com/aoshicangqiong/p/7650791.html
Copyright © 2011-2022 走看看