关于spring:深入分析Spring-与-Spring-MVC容器

2次阅读

共计 11990 个字符,预计需要花费 30 分钟才能阅读完成。

1 Spring MVC WEB 配置

Spring Framework 自身没有 Web 性能,Spring MVC 应用 WebApplicationContext 类扩大 ApplicationContext,使得领有 web 性能。那么,Spring MVC 是如何在 web 环境中创立 IoC 容器呢?web 环境中的 IoC 容器的构造又是什么构造呢?web 环境中,Spring IoC 容器是怎么启动呢?

以 Tomcat 为例,在 Web 容器中应用 Spirng MVC,必须进行四项的配置:

  1. 批改 web.xml,增加 servlet 定义;
  2. 编写 servletname-servlet.xml(servletname 是在 web.xm 中配置 DispactherServlet 时使 servlet-name 的值)配置;
  3. contextConfigLocation 初始化参数、配置 ContextLoaderListerner;

Web.xml 配置如下

 <!-- servlet 定义:前端处理器,承受的 HTTP 申请和转发申请的类 -->
    <servlet>
        <servlet-name>court</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- court-servlet.xml:定义 WebAppliactionContext 上下文中的 bean -->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:court-servlet.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
             
    <servlet-mapping>
        <servlet-name>court</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 
    <!-- 配置 contextConfigLocation 初始化参数:指定 Spring IoC 容器须要读取的定义了非 web 层的 Bean(DAO/Service)的 XML 文件门路 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/court-service.xml</param-value>
    </context-param>
 
    <!-- 配置 ContextLoaderListerner:Spring MVC 在 Web 容器中的启动类,负责 Spring IoC 容器在 Web 上下文中的初始化 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
复制代码

如果感觉看完文章有所播种的话,能够关注我一下哦
知乎:秃顶之路
b 站:linux 亦有归途
每天都会更新咱们的公开课录播以及编程干货和大厂面经
或者间接点击链接 c /c++ linux 服务器开发高级架构师
来课堂上跟咱们讲师面对面交换
须要大厂面经跟学习纲要的小伙伴能够加群 973961276 获取

在 web.xml 配置文件中,有两个次要的配置:ContextLoaderListener 和 DispatcherServlet。同样的对于 spring 配置文件的相干配置也有两局部:context-param 和 DispatcherServlet 中的 init-param。那么,这两局部的配置有什么区别呢?它们都负责什么样的职责呢?

在 Spring MVC 中,Spring Context 是以父子的继承构造存在的。Web 环境中存在一个 ROOT Context,这个 Context 是整个利用的根上下文,是其余 context 的双亲 Context。同时 Spring MVC 也对应的持有一个独立的 Context,它是 ROOT Context 的子上下文。

对于这样的 Context 构造在 Spring MVC 中是如何实现的呢?上面就先从 ROOT Context 动手,ROOT Context 是在 ContextLoaderListener 中配置的,ContextLoaderListener 读取 context-param 中的 contextConfigLocation 指定的配置文件,创立 ROOT Context

Spring MVC 启动过程大抵分为两个过程:

  1. ContextLoaderListener 初始化,实例化 IoC 容器,并将此容器实例注册到 ServletContext 中;
  2. DispatcherServlet 初始化;

2 Web 容器中 Spring 根上下文的加载与初始化

Web 容器调用 contextInitialized 办法初始化 ContextLoaderListener,在此办法中,ContextLoaderListener 通过调用继承自 ContextLoader 的 initWebApplicationContext 办法实例化 Spring Ioc 容器。

  1. 先看一下 WebApplicationContext 是如何扩大 ApplicationContext 来增加对 Web 环境的反对的。WebApplicationContext 接口定义如下:
 public interface WebApplicationContext extends ApplicationContext {
        // 根上下文在 ServletContext 中的名称
        String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
        // 获得 web 容器的 ServletContext
        ServletContext getServletContext();}
复制代码
  1. 上面看一下 ContextLoaderListener 中创立 context 的源码:ContextLoader.java
 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {//PS : ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE=WebApplicationContext.class.getName() + ".ROOT" 根上下文的名称
        //PS : 默认状况下,配置文件的地位和名称是:DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml" 
        // 在整个 web 利用中,只能有一个根上下文
        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) {
                // 在这里执行了创立 WebApplicationContext 的操作
                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);
                }
            }
            // PS: 将根上下文搁置在 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;
        }
    }
复制代码
  1. 再看一下 WebApplicationContext 对象是如何创立的:ContextLoader.java
 protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
        // 依据 web.xml 中的配置决定应用何种 WebApplicationContext。默认状况下应用 XmlWebApplicationContext
        //web.xml 中相干的配置 context-param 的名称“contextClass”Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }

        // 实例化 WebApplicationContext 的实现类
        ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        // Assign the best possible id value.
        if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
        // Servlet <= 2.4: resort to name specified in web.xml, if any.
            String servletContextName = sc.getServletContextName();
            if (servletContextName != null) {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName);
            } else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX);
            }
        } else {
            // Servlet 2.5's getContextPath available!
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath());
        }

        wac.setParent(parent);

        wac.setServletContext(sc);
        // 设置 spring 的配置文件
        wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
        customizeContext(sc, wac);
        //spring 容器初始化
        wac.refresh();
        return wac;
    }
复制代码
  1. ContextLoaderListener 构建 Root Context 时序图:

3 Spring MVC 对应的上下文加载与初始化

Spring MVC 中外围的类是 DispatcherServlet,在这个类中实现 Spring context 的加载与创立,并且可能依据 Spring Context 的内容将申请分发给各个 Controller 类。DispatcherServlet 继承自 HttpServlet,对于 Spring Context 的配置文件加载和创立是在 init()办法中进行的,次要的调用程序是 init-->initServletBean-->initWebApplicationContext

  1. 先来看一下 initWebApplicationContext 的实现:FrameworkServlet.java
 protected WebApplicationContext initWebApplicationContext() {
        // 先从 web 容器的 ServletContext 中查找 WebApplicationContext
        WebApplicationContext wac = findWebApplicationContext();
        if (wac == null) {
            // No fixed context defined for this servlet - create a local one.
            // 从 ServletContext 中获得根上下文
            WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            // 创立 Spring MVC 的上下文,并将根上下文作为起双亲上下文
            wac = createWebApplicationContext(parent);
        }

        if (!this.refreshEventReceived) {
            // Apparently not a ConfigurableApplicationContext with refresh support:
            // triggering initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            // 获得 context 在 ServletContext 中的名称
            String attrName = getServletContextAttributeName();
            // 将 Spring MVC 的 Context 搁置到 ServletContext 中
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet'" + getServletName() + "'as ServletContext attribute with name [" + attrName + "]");
            }
        }
            return wac;
    }
复制代码

通过 initWebApplicationContext 办法的调用,创立了 DispatcherServlet 对应的 context,并将其搁置到 ServletContext 中,这样就实现了在 web 容器中构建 Spring IoC 容器的过程。

  1. DispatcherServlet 创立 context 时序图:
  1. DispatcherServlet 初始化的大体流程:
  1. 控制器 DispatcherServlet 的类图及继承关系:

4 Spring 中 DispacherServlet、WebApplicationContext、ServletContext 的关系

要想很好了解这三个上下文的关系,须要先相熟 Spring 是怎么在 web 容器中启动起来的。Spring 的启动过程其实就是其 IOC 容器的启动过程,对于 web 程序,IOC 容器启动过程即是建设上下文的过程。

Spring 的启动过程:

  1. 首先,对于一个 web 利用,其部署在 web 容器中,web 容器提供其一个全局的上下文环境,这个上下文就是 ServletContext,其为前面的 spring IoC 容器提供宿主环境;
  2. 其次,在 web.xml 中会提供有 contextLoaderListener。在 web 容器启动时,会触发容器初始化事件,此时 contextLoaderListener 会监听到这个事件,其 contextInitialized 办法会被调用,在这个办法中,spring 会初始化一个启动上下文,这个上下文被称为根上下文,即 WebApplicationContext,这是一个接口类,确切的说,其理论的实现类是 XmlWebApplicationContext。 这个就是 spring 的 IoC 容器,其对应的 Bean 定义的配置由 web.xml 中的 context-param 标签指定。在这个 IoC 容器初始化结束后,spring 以 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 为属性 Key,将其存储到 ServletContext 中,便于获取;
  3. 再次,contextLoaderListener 监听器初始化结束后,开始初始化 web.xml 中配置的 Servlet,这个 servlet 能够配置多个,以最常见的 DispatcherServlet 为例,这个 servlet 实际上是一个规范的前端控制器,用以转发、匹配、解决每个 servlet 申请。DispatcherServlet 上下文在初始化的时候会建设本人的 IoC 上下文,用以持有 spring mvc 相干的 bean。在建设 DispatcherServlet 本人的 IoC 上下文时,会利用 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 先从 ServletContext 中获取之前的根上下文 (即 WebApplicationContext) 作为本人上下文的 parent 上下文。有了这个 parent 上下文之后,再初始化本人持有的上下文。这个 DispatcherServlet 初始化本人上下文的工作在其 initStrategies 办法中能够看到,大略的工作就是初始化处理器映射、视图解析等。这个 servlet 本人持有的上下文默认实现类也是 XmlWebApplicationContext。初始化结束后,spring 以与 servlet 的名字相干 (此处不是简略的以 servlet 名为 Key,而是通过一些转换,具体可自行查看源码) 的属性为属性 Key,也将其存到 ServletContext 中,以便后续应用。这样每个 servlet 就持有本人的上下文,即领有本人独立的 bean 空间,同时各个 servlet 共享雷同的 bean,即根上下文 (第 2 步中初始化的上下文) 定义的那些 bean。

在 Web 容器(比方 Tomcat)中配置 Spring 时,你可能曾经司空见惯于 web.xml 文件中的以下配置代码:

 <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
                                                                                                                                             
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
                                                                                                                                             
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
                                                                                                                                         
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping></span>
复制代码

以上配置 首先会在 ContextLoaderListener 中通过 <context-param> 中的 applicationContext.xml 创立一个 ApplicationContext,再将这个 ApplicationContext 塞到 ServletContext 外面,通过 ServletContext 的 setAttribute 办法达到此目标,在 ContextLoaderListener 的源代码中,咱们能够看到这样的代码:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
复制代码

以上由 ContextLoaderListener 创立的 ApplicationContext 是共享于整个 Web 应用程序的,而你可能早曾经晓得,DispatcherServlet 会维持一个本人的 ApplicationContext,默认会读取 /WEB-INFO/<dispatcherServletName>-servlet.xml 文件,而也能够重新配置:

 <servlet>  
        <servlet-name>  
           customConfiguredDispacherServlet  
        </servlet-name>  
        <servlet-class>  
            org.springframework.web.servlet.DispatcherServlet  
        </servlet-class>  
        <init-param>  
            <param-name>  
                contextConfigLocation  
            </param-name>  
            <param-value>  
                /WEB-INF/dispacherServletContext.xml  
            </param-value>  
        </init-param>  
        <load-on-startup>1</load-on-startup>  
    </servlet>
复制代码

问题是:以上两个 ApplicationContext 的关系是什么,它们的作用作用范畴别离是什么,它们的用处别离是什么?

ContextLoaderListener 中创立 ApplicationContext 次要用于整个 Web 应用程序须要共享的一些组件 ,比方 DAO,数据库的 ConnectionFactory 等。而 由 DispatcherServlet 创立的 ApplicationContext 次要用于和该 Servlet 相干的一些组件,比方 Controller、ViewResovler 等。

对于作用范畴而言,在 DispatcherServlet 中能够援用由 ContextLoaderListener 所创立的 ApplicationContext,而反过来不行。

在 Spring 的具体实现上,这两个 ApplicationContext 都是通过 ServletContext 的 setAttribute 办法放到 ServletContext 中的。然而,ContextLoaderListener 会先于 DispatcherServlet 创立 ApplicationContext,DispatcherServlet 在创立 ApplicationContext 时会先找到由 ContextLoaderListener 所创立的 ApplicationContext,再将后者的 ApplicationContext 作为参数传给 DispatcherServlet 的 ApplicationContext 的 setParent()办法,在 Spring 源代码中,你能够在 FrameServlet.java 中找到如下代码:

wac.setParent(parent);
复制代码

其中,wac 即为由 DisptcherServlet 创立的 ApplicationContext,而 parent 则为有 ContextLoaderListener 创立的 ApplicationContext。尔后,框架又会调用 ServletContext 的 setAttribute()办法将 wac 退出到 ServletContext 中。

当 Spring 在执行 ApplicationContext 的 getBean 时,如果在本人 context 中找不到对应的 bean,则会在父 ApplicationContext 中去找。这也解释了为什么咱们能够在 DispatcherServlet 中获取到由 ContextLoaderListener 对应的 ApplicationContext 中的 bean。

正文完
 0