做项目时碰到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


posted on 2017-10-11 15:24  傲视苍穹  阅读(476)  评论(0编辑  收藏  举报