Spring WebMvc原理-DispatcherServlet的初始化原理

从 0 开始深入学习 Spring 专栏目录总览

又到了最深奥最挑战人的原理部分了。WebMvc 的原理,小册打算围绕着 DispatcherServlet 来展开。首先咱先研究一下 DispatcherServlet 的初始化阶段,都干了哪些工作,下一章我们再研究程序运行期间,DispatcherServlet 如何处理请求并响应的。

1. 基于web.xml的DispatcherServlet初始化

前面我们在刚刚开始学习 WebMvc 的时候,我们只配置了两样东西:一个 web.xml ,一个 spring-mvc.xml 。web.xml 中注册了 DispatcherServlet ,spring-mvc.xml 负责初始化 DispatcherServlet 。那问题就来了,DispatcherServlet 在初始化的时候都干了什么,这是我们要研究的东西了。

1.1 init

我们都知道,一切 Servlet 的生命周期,都是从 init 方法开始,DispatcherServlet 也不例外。借助 IDE 往上翻,可以发现 DispatcherServlet 的 init 方法在父类 HttpServletBean 中重写了:

@Override
public final void init() throws ServletException {
    // Set bean properties from init parameters.
    // ......
    // Let subclasses do whatever initialization they like.
    initServletBean();
}

上面的初始化逻辑我们不关心了,关键的点在于下面的这个模板方法 initServletBean ,这个才是关键!

initServletBean 方法要来到 DispatcherServlet 的直接父类 FrameworkServlet 中,这里有初始化的逻辑:

@Override
protected final void initServletBean() throws ServletException {
    // log ......
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    } // catch ......

    // logger ......
}

刨去超多的日志打印不看,它的核心就两句话:一个初始化 IOC 容器,一个初始化 Servlet 本身。由于 initFrameworkServlet 方法中没有任何逻辑,DispatcherServlet 也没有重写它,所以核心还是初始化 IOC 容器。下面咱进入 initWebApplicationContext 方法。

1.2 initWebApplicationContext

初始化 DispatcherServlet 之前,先把属于它的 IOC 容器先初始化出来,以此来形成父子容器,这部分的源码比较长,小册只截取最关键的部分:(关键注释已标注在源码中)

protected WebApplicationContext initWebApplicationContext() {
    // 先获取根容器(通过ContextLoaderListener初始化的IOC容器)
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // 该分支用于处理DispatcherServlet构造时set过ApplicationContext的情况,一般不会遇到
    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()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // ......
    // 如果没有WebApplicationContext,则会创建一个全新的WebApplicationContext
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }
    // ......
    // 将该WebApplicationContext放入ServletContext中
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

纵观整段源码,在第一次初始化的时候很明显上面的乱七八糟分支都是不走的,它会走中间偏下部分的 createWebApplicationContext 方法,在此初始化 IOC 容器。我们继续跟进源码:

1.2.1 createWebApplicationContext

进来 createWebApplicationContext 方法,发现它继续往下调用重载的方法:

protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}

继续往下,发现这里面有正儿八经的逻辑了,咱先把这段源码大体浏览一下,这里面有一些关键的环节,我们需要着重去看。

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 加载IOC容器的承载实现类
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw ex ......;
    }
    // 反射实例化
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    // 获取、设置配置文件的路径
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    // 刷新IOC容器 - refresh
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

有了注释,看起来这段代码是不是非常简单了,核心就三步:实例化 IOC 容器、设置配置文件路径配置、刷新 IOC 容器

不过这里面有很多细节,我们逐一来看。

1.2.2 IOC容器的真正落地实现

首先上面的第一行,getContextClass 方法,它会获取到 WebApplicationContext 的真正落地实现,跳转进该方法,会发现它引用的是一个叫 XmlWebApplicationContext 的家伙:

public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

public Class<?> getContextClass() {
    return this.contextClass;
}

是不是感觉有点似曾相识?之前,我们在 IOC 进阶,介绍 BeanFactory 的实现类中,提到过一个 XmlBeanFactory ,咱当时在那里说,XmlBeanFactory 这个东西不要去看了,因为已经没有在用了。但这个 XmlWebApplicationContext 不一样,它跟 ClassPathXmlApplicationContext 是差不多的,只是一个在 Web 环境下使用,一个主要在普通项目环境中使用。

接下来的配置文件路径的获取,就是利用 XmlWebApplicationContext 的东西,咱继续往下来看。

1.2.3 获取配置文件的路径

String configLocation = getContextConfigLocation(); 这一句代码中,获取的就是我们之前在 web.xml 中配置的那个 contextConfigLocation 属性,配置了它,这里就能拿到配置文件的路径;如果不配置的话,它也有一套默认规则,这个默认规则我们可以在 XmlWebApplicationContext 中看到一个重写的 getDefaultConfigLocations 方法:

public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    } else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}

可见这个默认的规则,就是我们在 68 章,SpringWebMvc 的入门中提到的 /WEB-INF/{servlet-name}-servlet.xml ,这里提到的 getNamespace() 获取到的,是刚才在 FrameworkServlet 中定义的一个方法:

public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";

public String getNamespace() {
    return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}

所以,看到了这个套路了吧,把 Servlet 的 name 拿过来,后缀拼上 -servlet ,这样就形成配置文件的路径了。

至于这个配置文件在哪里用,咱下面会遇到的,莫慌莫着急。

1.2.4 刷新WebAppliCationContext

当上面的 WebAppliCationContext 都配置好之后,下面的最关键步骤,就是刷新 IOC 容器了。来到 configureAndRefreshWebApplicationContext 方法,这里面又是比较长,不过不太值得分段看了,咱总体扫一眼吧,看看里面的关键步骤:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 设置IOC容器的id,太长略过

    // 常规设置一些web的东西
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    // 此处就是上面的getNamespace方法
    wac.setNamespace(getNamespace());
    // 这里添加了一个上下文刷新的监听器
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    // 刷新IOC容器
    wac.refresh();
}

看这里面的逻辑,总体还都是挺容易理解的吧!很明显,对于一个 WebAppliCationContext 而言,它应该持有 Servlet 相关的东西,所以上面的部分很理所当然。

中间它添加了一个 ContextRefreshListener ,注意它来自 spring-webmvc 包,它就是监听我们之前在事件的章节中学习的 ContextRefreshedEvent 事件!至于这个监听器都干了什么,接下来咱马上就会说到。

最底下,它执行 IOC 容器的 refresh 方法,那它就会去加载、解析配置文件,以及初始化的逻辑等等等等。

注意一点!此时 WebAppliCationContext 中是没有 DispatcherServlet 的!所以 IOC 容器的初始化,是无法控制和操作 DispatcherServlet 的,加上咱上面说到了,initFrameworkServlet 方法里面没有任何初始化逻辑,所以如果 DispatcherServlet 还需要执行额外的初始化逻辑,那就不得不使用其他的方案来触发。

那咋触发呢?刚才不是刚刚说到一个监听器嘛,这个 ContextRefreshListener ,就是让 IOC 容器重新跟 DispatcherServlet 打交道的 “桥梁” 。

1.3 ContextRefreshListener

在进入监听器的源码之前,先想一个问题。

1.3.0 前置思考:此时 IOC 容器初始化到哪一步了?

此时正在执行的,是 ContextRefreshedEvent 事件的发布动作吧!此时第 11 步 finishBeanFactoryInitialization 方法已经执行完毕了吧,IOC 容器中的 bean 也都初始化完成了吧!( ContextRefreshedEvent 事件的发布,是在第 12 步 finishRefresh 方法中广播的)

所以,当 ContextRefreshedEvent 事件广播的时候,我们就应该知道,此时所有的 bean 都准备就绪。但是!因为我们在之前的初体验中,根本什么东西都没有注册,甚至连一个 <mvc:annotation-driven /> 标签,或者 @EnableWebMvc 注解都没有写,所以此时 IOC 容器中应该只有一个 InternalResourceViewResolver ,其余的什么 HandlerMapping 、HandlerAdapter 统统都没有!

那问题就来了,这些东西都没有注册,咋就好使了呢?

好,带着这个问题,我们可以来看 ContextRefreshListener 的源码了。

1.3.1 ContextRefreshListener的源码

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

这个 ContextRefreshListener 定义在 FrameworkServlet 的内部,而正是因为定义在内部,所以刚好能拿到 DispatcherServlet 的实例。这个监听器的触发调用的逻辑,就是 FrameworkServlet 的 onApplicationEvent 方法:

public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        onRefresh(event.getApplicationContext());
    }
}

protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}

很明显,接下来又是模板方法,跳转到 DispatcherServlet 了。

1.3.2 DispatcherServlet的onRefresh

注意这个地方到了非常非常重要的点了!!!高能预警!!!

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

可以发现,这个刷新逻辑中,把 DispatcherServlet 需要的所有组件,全部都初始化了一遍!所以各位,能猜到里面干了什么了吧!肯定是我们没有初始化哪些组件,这个 initStrategies 方法就会帮我们初始化

下面我们来挑一个比较重要的组件来看看吧,小册就挑 HandlerMapping 吧。

1.3.3 initHandlerMappings

首先我们来看 HandlerMapping 的初始化:

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

    // 默认分支:需要从容器中取出所有HandlerMapping
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        // 该分支,仅会取出名为 “handlerMapping” 的bean
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } // catch ......
    }

    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        // logger ......
    }
}

首先上面的逻辑中,它会先从 IOC 容器中,寻找那些类型为 HandlerMapping 的 bean ,借助 BeanFactoryUtils.beansOfTypeIncludingAncestors 方法,可以从子容器一路爬到最顶层父容器,来搜寻所有指定类型的 bean 。如果全部搜寻完毕后,发现没有任何 HandlerMapping ,则会调用最底下的 getDefaultStrategies 方法,使用默认的策略初始化 HandlerMapping 。

1.3.4 【默认策略】getDefaultStrategies

这个默认策略相当重要,一起来看源码是如何设计的:

private static final Properties defaultStrategies;

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 ......
        }
        return strategies;
    } else {
        return new LinkedList<>();
    }
}

可以发现!中间 for 循环中初始化了一组 Class<?> ,而对应的 classNames 是由 value 分割而来,而 value 又是根据上面的一个 Properties 类型的 defaultStrategies 变量而来!而这个 defaultStrategies 的初始化,是走的静态代码块:

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

static {
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    } // catch ......
}

注意看!**它加载了 classpath 下的一个名为 “DispatcherServlet.properties” 的文件!**而这个文件,来自于 spring-webmvc 的 jar 包:

Spring WebMvc原理-DispatcherServlet的初始化原理

打开这个 properties 文件,发现里面定义了刚才在 initStrategies 方法中初始化的所有 WebMvc 的核心组件的落地实现!

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

可见,即便我们什么也不注册,也能使用默认的策略,加载这些 SpringWebMvc 认为必需的组件。

由 properties 文件可知,默认的 HandlerMapping 会初始化以下三个实现:

  • org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
  • org.springframework.web.servlet.function.support.RouterFunctionMapping

刚好有我们之前说的最重要的 RequestMappingHandlerMapping ,所以我们不需要任何配置,就可以实现 @Controller + @RequestMapping 的注解式 WebMvc 开发。

所以,读取完配置文件后,在上面的 getDefaultStrategies 方法中,就可以拿到上面的 3 个实现类,并实例化并注册进 IOC 容器,这样就完成了默认策略的加载。

1.4 小结

使用 web.xml 配置 DispatcherServlet 的时候,即便我们不配置任何内置组件,SpringWebMvc 可以通过预先定义好的内置组件,在 IOC 容器已经初始化接近完毕的时候,借助事件监听,回调 DispatcherServlet 使其初始化默认组件的落地实现,从而达到兜底配置。

2. 基于Servlet3.0规范的DispatcherServlet初始化

基于 Servlet 3.0 规范的 SpringWebMvc 工程,咱之前说过,它的入口是通过 WebApplicationInitializer 来的。我们在初体验中编写了一个 AbstractAnnotationConfigDispatcherServletInitializer 的子类,来声明根容器和 DispatcherServlet 子容器的配置类,并且指定了 DispatcherServlet 的 servlet-mapping ,就完成了 WebMvc 的配置。

这其中,我们也是只配置了一个 InternalResourceViewResolver ,其余的肯定都跟上面一样,执行了 DispatcherServlet 的 initStrategies 方法做了兜底初始化。不过我们应该好奇的是,AbstractAnnotationConfigDispatcherServletInitializer 是怎么由 WebApplicationInitializer 来一步一步把根容器和 WebMvc 子容器都初始化好的呢?

2.1 AbstractContextLoaderInitializer

要说到这个,那就必须来看 WebAppliCationInitializer 的第一个抽象实现类了,它首先实现了 WebAppliCationInitializer 接口的 onStartUp 方法:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    registerContextLoaderListener(servletContext);
}

只不过这看上去,是为了注册之前在 web.xml 中初始化的那个 ContextLoaderListener 监听器,那里面的逻辑一定是初始化根容器咯,但是这似乎跟 DispatcherServlet 没啥关系。。。

不过要注意,IDEA 在此处给了我们提示,这个 onStartUp 方法,还有重写过的子类:

Spring WebMvc原理-DispatcherServlet的初始化原理

好,那咱就点击这个向下的 override 图标,跳转到一个叫 AbstractDispatcherServletInitializer 的子类。

2.2 AbstractDispatcherServletInitializer

这个 AbstractDispatcherServletInitializer 重写的 onStartUp 中,除了调用上面 AbstractContextLoaderInitializer 父类的 onStartUp 初始化根容器之外,还注册了 DispatcherServlet :

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);
    registerDispatcherServlet(servletContext);
}

进入到 registerDispatcherServlet 方法:(注意留意源码中标注的注释)

protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");

    // 创建WebMvc子容器
    WebApplicationContext servletAppContext = createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

    // 初始化DispatcherServlet
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

    // 利用Servlet3.0的规范,动态注册Servlet
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    // check ......

    registration.setLoadOnStartup(1);
    // 设置servlet-mapping
    registration.addMapping(getServletMappings());
    // servlet是否支持异步请求,默认true
    registration.setAsyncSupported(isAsyncSupported());

    Filter[] filters = getServletFilters();
    if (!ObjectUtils.isEmpty(filters)) {
        for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
        }
    }

    customizeRegistration(registration);
}

可以发现,这整个流程还是很容易理解的吧!首先初始化 WebMvc 的子容器,然后把 DispatcherServlet 创建出来,最后注册进 ServletContext ,完事。而初始化 WebMvc 子容器的逻辑,就定义在我们刚才说的 AbstractAnnotationConfigDispatcherServletInitializer 中了:

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    Class<?>[] configClasses = getServletConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        context.register(configClasses);
    }
    return context;
}

这里面我们要重写的方法,就是这个获取配置类的 getServletConfigClasses 方法。拿到配置类,就可以初始化 WebMvc 的子容器了,后面的逻辑也就跟上面分析的一样了。

小结

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring WebMvc原理-DispatcherServlet的初始化原理