js css html

DispatcherServlet 初始化过程

2023-04-11  本文已影响0人  程序员札记

在说 DispatcherServlet 之前,我们先要知道一个 Java J2EE Servlet 的接口的 init(ServletConfig config) 方法。Servlet 容器调用 Servlet # init(ServletConfig config) 方法来指示正在初始化某一个具体的 Servlet 实例对象。

image.png

Servlet 容器读取到 webapp/WEB-INF/web.xml 文件中的 <servlet /> 标签时,会根据其中的 <load-on-startup > 的值做不同的处理。如下图所示:

image.png

关于容器加载某个 servlet 时机的选择:

A. 如果没有指定 <load-on-startup /> 容器在该 servlet 被选择时才加载。

B. 如果指定值 < 0, 情况同 A

C. 如果指定值 >= 0, 容器在 Web 应用启动时就加载该 servlet

容器在启动时,初始化多个 servlet 的优先级顺序:

1.首先加载 Servlet 容器自带的 servlet

2.然后优先加载 <load-on-startup> 为较小自然数的 servlet

3.相同 <load-on-startup> 值,优先加载 <web-app> 标签中更靠前的 servlet

FrameworkServlet 初始化过程

image.png

第一,从继承关系上来看,GenericServlet 是 FrameworkServlet 的超类,FrameworkServlet 是 DispatcherServlet 超类。

image.png
  1. Servlet 容器主动调用 Servlet 实现类 GenericServlet 的 init 方法:

public abstract class GenericServlet implements Servlet {
      /**
       * Called by the servlet container to indicate to a servlet that the
       * servlet is being placed into service. 
       */
      public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
      }
}

2. 调用 HttpServletBean 的 init 方法


public abstract class HttpServletBean extends HttpServlet {
        /**
         * 覆写了父类 GenericServlet#init() 方法
         */
    @Override
    public final void init() throws ServletException {
        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                                /**
                                 * 如果你有设置 init-param 'contextConfigLocation',那么就会调用 DispatcherServlet#setContextConfigLocation 方法
                                 * 如果你有设置 init-param 'contextClass',就会调用 DispatcherServlet#setContextClass 方法
                                 * 如果你有设置 init-param 'contextInitializerClasses',就会调用 DispatcherServlet#setContextInitializerClasses 方法
                                 */
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
        // Let subclasses do whatever initialization they like.
        initServletBean();
    }
}

3.FrameworkServlet # initWebApplicationContext

了解过 ContextLoaderListener 的朋友,应该熟悉 <context-param /> + <listener /> 的这套常规“组合拳”。

<web-app ....(此处省略命名空间)>
    <!--配置多个上下文会导致多次执行-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-mvc.xml</param-value>
    </context-param>

    <!-- ================================== listener ================================== -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

他们将为我们的 Web 应用程序创建一个“根”应用上下文。

设置 Web 应用“根”上下文的地方: ContextLoader # initWebApplicationContext
在 ContextLoader # initWebApplicationContext(ServletContext servletContext) 方法中,
把创建好的 WebApplicationContext 实例,通过调用 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context) 设置到 Servlet 容器上下文中。
键值为 org.springframework.web.context.WebApplicationContext.ROOT

获取 Web 应用“根”上下文的地方: FrameworkServlet # initWebApplicationContext
在 FrameworkServlet # initWebApplicationContext() 方法中, 调用 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()) 获取 Web 应用“根”上下文

4.为当前 DispatcherServlet 创建一个应用上下文

在 FrameworkServlet # initWebApplicationContext 方法中,

假如当前的 DispatcherServlet 还没有一个 webApplicationContext 成员变量,

调用 createWebApplicationContext 给自己创建一个 WebApplicationContext 实例。

5.实例化应用上下文对象

// 获取需要实例化的应用上下文的类对象
Class<?> contextClass = getContextClass();

<init-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
</init-param>

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>

6.配置和初始化应用上下文对象

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value></param-value>
</init-param>

因为 FrameworkServlet # createWebApplicationContext 对 contextConfigLocation 的判断是非 null

String configLocation = getContextConfigLocation();
// 值为空字符"",但不是 null,所以可以设置进去
if (configLocation != null) {
      wac.setConfigLocation(configLocation);
}

AbstractRefreshableConfigApplicationContext # setConfigLocation


// 传入空字符串"",得到的是长度为0的String数组
public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for(int i = 0; i < locations.length; ++i) {
            this.configLocations[i] = this.resolvePath(locations[i]).trim();
        }
    } else {
        this.configLocations = null;
    }
}

XmlWebApplicationContext # loadBeanDefinitions


protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
                // 配置数组长度为0时,不会进入函数体内部,就不会执行 reader.loadBeanDefinitions
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

7.刷新上下文

AbstractApplicationContext # refresh


public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();
        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);
        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);
            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            // Initialize message source for this context.
            initMessageSource();
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();
            // Initialize other special beans in specific context subclasses.
            onRefresh();
            // Check for listener beans and register them.
            registerListeners();
            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);
            // Last step: publish corresponding event.
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }
        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

8.完成刷新发布事件

AbstractApplicationContext # finishRefresh


protected void finishRefresh() {
    // Clear context-level resource caches (such as ASM metadata from scanni
    clearResourceCaches();
    // Initialize lifecycle processor for this context.
    initLifecycleProcessor();
    // Propagate refresh to lifecycle processor first.
    getLifecycleProcessor().onRefresh();
        // 这个事件会被 FrameworkServlet 接收和处理
    // Publish the final event.
    publishEvent(new ContextRefreshedEvent(this));
    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}

注册事件的位置:FrameworkServlet # configureAndRefreshWebApplicationContext


wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

接收和处理事件的位置:FrameworkServlet.ContextRefreshListener


private class ContextRefreshListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

到此为止,上半篇就算结束了,通过最后一步的事件接收,我们将进入 DispatcherServlet # initStrategies

DispatcherServlet 初始化过程

DispatcherServlet # initStrategies

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

首先了解一下默认配置 defaultStrategies:

public class DispatcherServlet extends FrameworkServlet {
      private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

      private static final Properties defaultStrategies;

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

DispatcherServlet.properties

我简单说一下常用的几个初始化方法:

结语

在整理本文的过程中,又带出了 ApplicationListener 是如何工作的?以及 BeanWrapper 是如何工作的?DispatcherServlet 初始化的这些 HandlerMapping,HandlerAdapter,HandlerExceptionResolves,ViewResolvers 是怎么串起来?待以后再继续分析。

上一篇下一篇

猜你喜欢

热点阅读