DispatcherServlet的初始化过程

2019-03-23  本文已影响0人  buzzerrookie

本文探讨Spring MVC中DispatcherServlet是如何初始化的,DispatcherServlet初始化指的是init()生命周期方法被执行,而不是DispatcherServlet被实例化的过程。

DispatcherServlet类

DispatcherServlet的类层次如下图所示

DispatcherServlet.png
不管DispatcherServlet被如何包装,它本质上是一个servlet,servlet的生命周期是init -> service -> destroy,因此本文从init()方法入手分析DispatcherServlet的初始化过程。(如果你对servlet不熟悉,我建议你看一下这篇入门指南:Java Servlet完全教程

init()方法

DispatcherServlet的init()方法在父类HttpServletBean中定义,其代码如下所示:

@Override
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }
    // 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);
            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();
    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}
ServletConfigPropertyValues.png

initServletBean()方法

DispatcherServlet的initServletBean()方法在父类FrameworkServlet中定义,它调用initWebApplicationContext方法初始化DispatcherServlet自己的应用上下文:

@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();
    }
    // 省略一些代码
}

protected void initFrameworkServlet() throws ServletException {
}

初始化servlet应用上下文

initWebApplicationContext方法负责初始化DispatcherServlet自己的应用上下文,其代码如下所示:

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

该方法的概要流程如下:

  1. 获得ContextLoaderListener创建的根应用上下文;
  2. 为DispatcherServlet创建自己的应用上下文;
  3. 刷新DispatcherServlet自己的应用上下文。

获得根应用上下文

利用WebApplicationContextUtils类的getWebApplicationContext静态方法取得根应用上下文,相关代码如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

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

前面文章指出根应用上下文已经通过ContextLoaderListener被容器初始化,其类型默认是XmlWebApplicationContext类,启动过程中会将WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE(即org.springframework.web.context.WebApplicationContext.ROOT)和根应用上下文通过ServletContext.setAttribute设置到应用的ServletContext。


rootContext.png

创建DispatcherServlet的应用上下文

  1. 若this.webApplicationContext不为null,则说明DispatcherServlet在实例化期间已经被注入了应用上下文。这种情况发生在Spring Boot应用启动时,由于父类FrameworkServlet实现了ApplicationContextAware接口,所以setApplicationContext回调函数被调用时将字段webApplicationContext设置为根应用上下文,注意这并不是在init()初始化方法中完成的,而是在实例化DispatcherServlet的过程中完成的。
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
    if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
        this.webApplicationContext = (WebApplicationContext) applicationContext;
        this.webApplicationContextInjected = true;
    }
}
  1. 若this.webApplicationContext为null,则说明DispatcherServlet在实例化期间没有被注入应用上下文。首先通过findWebApplicationContext方法尝试寻找先前创建的应用上下文。
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;
}

public String getContextAttribute() {
    return this.contextAttribute;
}

该方法从ServletContext的属性中找到该servlet初始化属性(<init-param>元素)contextAttribute的值对应的应用上下文,若没有找到则报错。

  1. 若第2步没有找到之前初始化的应用上下文那么就需要通过createWebApplicationContext方法为DispatcherServlet创建一个以根应用上下文为父的应用上下文。这个应用上下文的类型是由父类FrameworkServlet的contextClass字段指定的,可以在web.xml中配置,默认是XmlWebApplicationContext类型,可以参见DispatcherServlet配置文档
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;
}

具体的创建过程如下:

  1. 最后的疑问是部署描述符web.xml中的contextClass等参数是如何被绑定到FrameworkServlet类或DispatcherServlet类的对应字段的呢?这是由上文提到的init()方法中ServletConfigPropertyValues和BeanWrapper完成的。


    contextClass.png

刷新DispatcherServlet的应用上下文

onRefresh方法刷新DispatcherServlet自己的应用上下文,DispatcherServlet类重写了父类FrameworkServlet的onRefresh方法,该方法调用initStrategies()方法实例化MultipartResolver、LocaleResolver、HandlerMapping、HandlerAdapter和ViewResolver等组件。

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

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

以实例化HandlerMapping的initHandlerMappings方法为例,其代码如下:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

用getDefaultStrategies方法获取默认策略:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
                throw new BeanInitializationException(
                        "Could not find DispatcherServlet's default strategy class [" + className +
                        "] for interface [" + key + "]", ex);
            }
            catch (LinkageError err) {
                throw new BeanInitializationException(
                        "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                        className + "] for interface [" + key + "]", err);
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}

总结

至此,本文完成了对DispatcherServlet的init方法的分析,它已准备好提供服务了,对请求处理的分析请看后续文章。

参考文献

SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

上一篇 下一篇

猜你喜欢

热点阅读