美文网首页
Spring 在 WEB 容器中的启动过程

Spring 在 WEB 容器中的启动过程

作者: ms__960f | 来源:发表于2019-05-07 19:34 被阅读0次

    描述

    1. 对于一个WEB应用来说,它需要部署在WEB容器 中,且WEB容器 会提供一个全局的上下文环境ServletContext,也就是SpringIOC容器的宿主环境。

    2. WEB容器启动时,会加载web.xml 中提供的 contextLoaderListener监听器,会触发容器初始化事件,contextLoaderListener 会监听到该事件,其 contextInitialized 方法会被调用。在该方法中,Spring 会初始化一个启动上下文,也被称为根上下文,即 WebApplicationContext ,确切说,其实际实现类是 XmlWebApplicaitonContext。其实它就是Spring IOC 容器,其对应的Bean定义配置,由web.xmlcontext-param 标签指定。在Spring IOC容器初始化完毕后,SpringWebApplicationContext.ROOTWEBAPPLICATIONEXTATTRIBUTE 为属性key,将其存储到 ServletContextservletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

    3.contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,且Servlet 可以配置多个,以最常见的DispatcherServlet为例,这个Servlet 实际上是一个标准的前端控制器,用以 转发 匹配 处理 每个servlet 请求。

    4.DispatcherServlet上下文在初始化的时候会建立自己的 IOC容器上下文,用以持有Spring MVC 相关的Bean。在创建上下文时,会根据WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTEKey ,从ServletContext中获取之前的根上下文WebApplicationContext),来作为自己上下文的 Parent 上下文 。有了该Parent 上下文后,再初始化自己持有的上下文。通过initStrategies 方法中可以看到,DispatcherServlet初始化自己上下文大概的工作,就是初始化处理器映射 视图解析等等 ,默认实现类也是 XmlWebApplicationContext

    5.DispatcherServlet上下文在初始化完成后,Spring会将与Servlet 的名字相关的属性,作为key,将其存到ServletContext中。这样每个 Servlet 都持有自己的上下文,即拥有自己独立的 Bean 空间,同时各个Servlet共享相同的Bean,即根上下文定义的Bean

    org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register 控制台信息打印 [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] :Mapped "{[/appEdition/list.do]}" onto public org.springframework.web.servlet.ModelAndView com.hengtn.jiang.controller.AppEditionController.list(com.hengtn.jiang.entity.query.AppEditionQuery,javax.servlet.http.HttpSession,java.lang.Integer)

    Spring容器在Web容器中的创建及初始化

    SpringIOC 是一个独立的模块,并不是直接在Web容器中发挥作用的,假若要在Web环境使用IOC容器的话,需要SpringIOC 设计一个启动过程,将IOC容器导入,并在Web容器 中建立起来。

    web.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
      <filter>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
      <filter-mapping>
        <filter-name>springSessionRepositoryFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ERROR</dispatcher>
      </filter-mapping>
      <filter>
        <filter-name>characterEncodingFilter</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>
        <init-param>
          <param-name>forceEncoding</param-name>
          <param-value>true</param-value>
        </init-param>
      </filter>
      
      <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
      </context-param>
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
      <session-config>
        <session-timeout>180</session-timeout>
      </session-config>
    </web-app>
    

    contextConfigLocation 对应的 valueSpring配置文件 的绝对路径
    ContextLoaderListener 监听器主要用来对Servlet容器(这里指 Tomcat )的行为进行监听

    ContextLoaderListener 源码

    /**
     * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
     * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
     *
     * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
     * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
     * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
     * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
     *
     * @author Juergen Hoeller
     * @author Chris Beams
     * @since 17.02.2003
     * @see #setContextInitializers
     * @see org.springframework.web.WebApplicationInitializer
     */
    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    
        /**
         * Create a new {@code ContextLoaderListener} that will create a web application
         * context based on the "contextClass" and "contextConfigLocation" servlet
         * context-params. See {@link ContextLoader} superclass documentation for details on
         * default values for each.
         * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
         * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
         * required.
         * <p>The created application context will be registered into the ServletContext under
         * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
         * and the Spring application context will be closed when the {@link #contextDestroyed}
         * lifecycle method is invoked on this listener.
         * @see ContextLoader
         * @see #ContextLoaderListener(WebApplicationContext)
         * @see #contextInitialized(ServletContextEvent)
         * @see #contextDestroyed(ServletContextEvent)
         */
        public ContextLoaderListener() {
        }
    
        /**
         * Create a new {@code ContextLoaderListener} with the given application context. This
         * constructor is useful in Servlet 3.0+ environments where instance-based
         * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
         * API.
         * <p>The context may or may not yet be {@linkplain
         * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
         * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
         * (b) has <strong>not</strong> already been refreshed (the recommended approach),
         * then the following will occur:
         * <ul>
         * <li>If the given context has not already been assigned an {@linkplain
         * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
         * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
         * the application context</li>
         * <li>{@link #customizeContext} will be called</li>
         * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
         * specified through the "contextInitializerClasses" init-param will be applied.</li>
         * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
         * </ul>
         * If the context has already been refreshed or does not implement
         * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
         * assumption that the user has performed these actions (or not) per his or her
         * specific needs.
         * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
         * <p>In any case, the given application context will be registered into the
         * ServletContext under the attribute name {@link
         * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
         * application context will be closed when the {@link #contextDestroyed} lifecycle
         * method is invoked on this listener.
         * @param context the application context to manage
         * @see #contextInitialized(ServletContextEvent)
         * @see #contextDestroyed(ServletContextEvent)
         */
        public ContextLoaderListener(WebApplicationContext context) {
            super(context);
        }
    
    
        /**
         * Initialize the root web application context.
         */
        @Override
        public void contextInitialized(ServletContextEvent event) {
            initWebApplicationContext(event.getServletContext());
        }
    
    
        /**
         * Close the root web application context.
         */
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            closeWebApplicationContext(event.getServletContext());
            ContextCleanupListener.cleanupAttributes(event.getServletContext());
        }
    
    }
    

    由源码得出, ContextLoaderListener 继承自 ContextLoader,并且还实现了 ServletContextListener,而且它的构造函数中,需要传入了一个WebApplicationContext,它是继承自ApplicationContext 接口的 高级IOC容器

    ServletContextListener 源码

    public interface ServletContextListener extends EventListener {
    
        /**
         ** Notification that the web application initialization process is starting.
         * All ServletContextListeners are notified of context initialization before
         * any filter or servlet in the web application is initialized.
         * @param sce Information about the ServletContext that was initialized
         */
        public void contextInitialized(ServletContextEvent sce);
    
        /**
         ** Notification that the servlet context is about to be shut down. All
         * servlets and filters have been destroy()ed before any
         * ServletContextListeners are notified of context destruction.
         * @param sce Information about the ServletContext that was destroyed
         */
        public void contextDestroyed(ServletContextEvent sce);
    }
    

    ServletContextListenerServlet 中比较重要的一个接口,用于监听 Servlet 容器的启动 销毁 事件,所以在 ContextLoaderListener 中:

    contextInitialized(ServletContextEvent sce):参数为所要监听的ServletContextEvent,也就是Tomcat启动加载完web.xml会产生的事件,ServletContextEvent持有从web.xml 加载的初始化配置ServletContext 上下文
    contextDestroyed(ServletContextEvent sce):在Tomcat 关闭的时候执行该方法

    启动时,ServletContextListener 的执行顺序与web.xml 中的配置顺序一致,停止时执行顺序正相反

    流程梳理

    Servlet容器 启动事件发生时,将被ContextLoaderLister 监听,此时 ContextLoaderListener会调用contextInitialized(ServletContextEvent sce) 方法,该方法为ContextLoaderLister 实现ServletContextListener接口的方法,并将在web.xml 加载初始化完成后,获取的 ServletContext 传入initWebApplicationContext() 方法中,进行IoC容器的初始化

    initWebApplicationContext() 方法从 ContextLoader 继承而来,进入ContextLoader 源码中查看

        static {
            // Load default strategy implementations from properties file.
            // This is currently strictly internal and not meant to be customized
            // by application developers.
            try {
                //private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
                ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
                defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
            }
            catch (IOException ex) {
                throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
            }
        }
    

    ContextLoader 类中的静态代码块

    创建ClassPathResource 对象,同时把ContextLoader.properties 作为参数传入,易知 ContextLoader.properties 文件与 ContextLoader 类是在同一个目录下,ContextLoader.properties 文件内容如下: org.springframework.web.context.WebApplicationContext=org.springframework. web.context.support.XmlWebApplicationContex
    得到一个 Properties 对象,后面将根据类名来创建对应的 ApplicationContext 容器
    因此可知Spring默认初始化容器,是 XmlWebApplicationContext容器

    initiWebApplicationContext() 方法

    /**
         * Initialize Spring's web application context for the given servlet context,
         * using the application context provided at construction time, or creating a new one
         * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
         * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
         * @param servletContext current servlet context
         * @return the new WebApplicationContext
         * @see #ContextLoader(WebApplicationContext)
         * @see #CONTEXT_CLASS_PARAM
         * @see #CONFIG_LOCATION_PARAM
         */
        public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
            //String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
            //从servletContext中获取ApplicationContext容器,若容器存在,则抛处初始化失败异常
            //需要检查web.xml中是否定义了多个IOC容器的加载器
            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 {
                //将容器存储在本地变量,保证servletContext销毁之后,还能获取到它
                if (this.context == null) {
                    this.context = createWebApplicationContext(servletContext);
                }
               //检查创建的ApplicationContext实例,是否实现了ConfigurableWebApplicationContext接口
                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);
                    }
                }
                //String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
                //把当前容器设为根容器,并存放在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()流程梳理

    1.当调用ContextLoaderListener中的initWebApplicationContext()方法,并且将获取到的servletContext作为参数传入。
    2.initWebApplicationContext() 首先会尝试从servletContext 中获取根容器,如果容器不为空,则容器初始化失败,因为web.xml 中可能定义了多个IOC容器 的加载器。
    3.若此时容器还未初始化,则调用createWebApplicationContext() 方法创建一个容器。
    4.创建完容器之后,将会调用一个非常重要的configureAndRefreshWebApplicationContext() 方法,在执行该方法的时候,会将从ApplicationContext.xml 配置文件中获取的内容,配置到已经创建好了的XmlWebApplicationContext 容器中去,并调用refresh() 方法来完成容器的初始化。
    5.将已经完成初始化的XmlWebApplicationContext 容器注册到servletContext 中去

    其实在Web容器中,ServletContext为Spring的IoC容器提供了宿主环境,对应的建立起一个IoC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象、数据存取对象、资源、事务管理器等各种中间层对象。在这个上下文的基础上,与Web MVC相关还会有一个上下文来保持控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。因为在initWebApplicationContext方法中我们可以看到其实创建ApplicationContext容器的工作是交由createWebApplicationContext方法来实现的,下面我们来看看这个方法

    createWebApplicationContext源码

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
            //要创建的ApplicationContext的类型
            Class<?> contextClass = determineContextClass(sc);
            //如果获取到的ApplicationContext,没有实现ConfigurableWebApplicationContext接口,那么容器创建失败
            //因此要创建的ApplicationContext,必须实现ConfigurableWebApplicationContext接口
            if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
                throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                        "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
            }
            //实例化Spring容器
            return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        }
    

    功能:
    决定要创建的ApplicationContext类型
    实例化一个ApplicationContext

    那么它是如何决定要创建的ApplicationContext类型的呢?
    determineContextClass() 方法决定的

    protected Class<?> determineContextClass(ServletContext servletContext) {
            //从web.xml获取要创建的IOC容器名称
            //public static final String CONTEXT_CLASS_PARAM = "contextClass";      
            String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
            //如果获取到的类名不为空,则创建该容器的class对象    
            if (contextClassName != null) {
                try {
                    return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
                }
                catch (ClassNotFoundException ex) {
                    throw new ApplicationContextException(
                            "Failed to load custom context class [" + contextClassName + "]", ex);
                }
            }
            //否则创建默认的容器Class对象,即org.springframework.web.context.support.XmlWebApplicationContext
            //在创建ContextLoader时,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); 
            //已经准备好默认的容器类
            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);
                }
            }
        }
    

    完成IOC容器 的创建后,在initWebApplicationContext() 中将调用configureAndRefreshWebApplicationContext() 初始化该容器

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
            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 information
                String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
                if (idParam != null) {
                    wac.setId(idParam);
                }
                else {
                    // Generate default id...
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getContextPath()));
                }
            }
    
            wac.setServletContext(sc);
            String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
            if (configLocationParam != null) {
                wac.setConfigLocation(configLocationParam);
            }
    
            // 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(sc, null);
            }
    
            customizeContext(sc, wac);
            wac.refresh();
        }
    

    为创建好的 IOC容器 ,设置 Web应用 的上下文,以便二者整合
    为同一个IOC容器 ,设置配置文件的绝对路径
    调用IOC容器refresh() 函数对其进行初始化

    相关文章

      网友评论

          本文标题:Spring 在 WEB 容器中的启动过程

          本文链接:https://www.haomeiwen.com/subject/flheoqtx.html