spring上下文和springMVC上下文的关系

来源:互联网 发布:淘宝千人千面的页面 编辑:程序博客网 时间:2024/05/16 13:50

在tomcat部署一个用SSM搭起来的项目,一般会有两个上下文容器,分别是spring和SpringMVC的容器,在spring中一个容器是可以有父容器的,那么spring的容器和SpringMVC的容器关系是怎么样的。 先看tomcat中的web.xml配置文件

spring的上下文:

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://java.sun.com/xml/ns/javaee"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"         version="3.0">    <context-param>        <param-name>contextConfigLocation</param-name>        <param-value>classpath:com/fancy/resource/spring-mybatis.xml</param-value>    </context-param>    <!-- 编码过滤器 -->    <filter>        <filter-name>encodingFilter</filter-name>        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>        <init-param>            <param-name>encoding</param-name>            <param-value>UTF-8</param-value>        </init-param>    </filter>    <filter-mapping>        <filter-name>encodingFilter</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping>    <!-- spring监听器 -->    <listener>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>    <!-- 防止spring内存溢出监听器,比如quartz -->    <listener>        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>    </listener>    <!-- spring mvc servlet-->    <servlet>        <servlet-name>SpringMVC</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:com/fancy/resource/spring-mvc.xml</param-value>        </init-param>        <load-on-startup>1</load-on-startup>        <async-supported>true</async-supported>    </servlet>    <servlet-mapping>        <servlet-name>SpringMVC</servlet-name>        <!-- 此处也可以配置成 *.do 形式 -->        <url-pattern>*.do</url-pattern>    </servlet-mapping></web-app>
<!-- spring监听器 -->    <listener>                      <listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>

其中ContextLoadListener监听器在tomcat容器初始化的时候监听tomcat的servlet上下文,这个上下文给spring的上下文提供了寄生环境,在这个监听器中,spring会初始一个上下文,并将此上下文以key为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个常量,value当然是自己的上下文了。看下面代码就可以看出来

依次调用顺序为:
1.ContextLoaderListener中的

@Override    public void contextInitialized(ServletContextEvent event) {        initWebApplicationContext(event.getServletContext());    }

2.ContextLoad中的

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;        }    }

其中比较重要的代码如下:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

将spring的上下文以key为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE存储在servlet根上下中。

SpringMVC的上下文:

spring的上下文是通过监听器来初始化,SpringMVC的上下文是通过DispatchServelet来加载,因为DispatchServelet会拦截所有我们感兴趣的请求来分发,DispatchServelet初始的时候会创建自己的上下文,并从servlet根上下文中取出我们上面说的spring上下文作为自己上下文的父容器,为什么这么做呢,因为spring中容器存在父子关系,父容器不能访问子容器的资源,而子容器可以访问父容器的资源。这样springMVC的上下文就可以获取父容器的Bean来初始化自己的Bean。
分析DispatchServelet初始化SpringMVC容器的上下文之前需要知道servlet生命周期:

  • 调用 init() 方法初始化
  • 调用 service() 方法来处理客户端的请求
  • 调用 destroy() 方法释放资源,标记自身为可回收
  • 被垃圾回收器回收

其中在init方法中进行初始化工作,来看看DispatchServelet的继承关系
这里写图片描述

其中主要的父类:FrameworkServle,HttpServletBean
FrameworkServle主要就是创建上下文,HttpServletBean中

@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");        }    }

定义了init方法,这个方法中主要调用了initServletBean()方法,这个方法是个抽象方法,是由子类FrameworkServle实现,那么看看FrameworkServle中是如何重写这个方法的:

@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");        }    }

其中重要的两行代码:

this.webApplicationContext = initWebApplicationContext();            initFrameworkServlet();

初始化web上下文,这个上下文就是SpringMVC的上下文,那么继续看这个方法的具体实现:

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, etc                    if (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;    }

其中重要的部分:

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, etc                    if (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);                }            }        }

首先从servlet根上下文中获取rootContext,这个上下文就是上面所说的spring上下文,然后将springMVC 上下文中的父容器设置为rootContext,这样父子容器关系就建立起来了。

为什么要缕清这样的关系

知道了这样的关系就可以避免这样的情况:spring容器初始化时,需要依赖的Bean配置在SpringMVC中,因为spring容器是SpringMVC容器的父容器,这样是不能正常初始化spring上下文的。

原创粉丝点击