spring mvc(二):DispatcherServlet 初始化流程

322 阅读8分钟

注:本系列源码分析基于spring 5.2.2.RELEASE,本文的分析基于 annotation 注解方式,gitee仓库链接:gitee.com/funcy/sprin….

上一篇文章中,我们通过一个简单的demo成功启动了springmvc应用,在提供的demo中,我们知道tomcat在启动时会调用MyWebApplicationInitializer#onStartup方法,然后启动 spring 容器。那么tomcat究竟是如何启动spring的呢?

1. servlet 初始化:DispatcherServlet#init

我们再回忆下MyWebApplicationInitializer#onStartup方法:

@Override
public void onStartup(ServletContext servletContext) {
   System.out.println("webApplicationInitializer ...");
   // 创建 spring 的 applicationContext
   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
   context.register(MvcConfig.class);

   // 实例化 DispatcherServlet
   DispatcherServlet servlet = new DispatcherServlet(context);

   // 将DispatcherServlet注册到servlet容器
   ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
   registration.setLoadOnStartup(1);
   registration.addMapping("/*");
}

这段代码先准备了一个AnnotationConfigWebApplicationContext,将其作为参数传入DispatcherServlet中,然后往 servlet容器中添加DispatcherServlet。这样,servlet容器在启动时,就会启动spring容器了。这里最重要的就是DispatcherServlet,接下来我们就来分析这个servlet.

我们先来看下DispatcherServlet的继承结构:

从上图可以看到,spring提供的、跟servlet相关的类有三个:HttpServletBeanFrameworkServletDispatcherServlet。作为servlet,我们知道其初始化方法为GenericServlet#init(),也就是servlet的入口方法,我们的分析也将从这里开始。

由于DispatcherServlet 实现了HttpServletBeanFrameworkServletDispatcherServlet#init() 实际上继承自 HttpServletBean#init

@Override
public final void init() throws ServletException {

    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) {
            ...
        }
    }

    // 初始化 servlet bean,spring的相关配置就是在这里进行的
    initServletBean();
}

可以看到,这个方法加载了一些配置,然后就调用了initServletBean,并没有做一些spring实质性的内容。我们继续跟进,跳过其中不重要的方法,调用链路如下:

-HttpServletBean#init
 -FrameworkServlet#initServletBean
  -FrameworkServlet#initWebApplicationContext

一直跟到FrameworkServlet#initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() {
    // 获取类型为WebServerApplicationContext的父容器,这里得到的结果为null
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // 这个webApplicationContext,就是在MyWebApplicationInitializer#onStart方法中
        // 传入的AnnotationConfigWebApplicationContext
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                // 在这里调用 AbstractApplicationContext#refresh 启动
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // wac不为null,这里不会运行
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    // wac不为null,这里不会运行
    if (wac == null) {
        // 创建WebApplicationContext,重新运行AbstractApplicationContext#refresh
        wac = createWebApplicationContext(rootContext);
    }

    // 实际上,refreshEventReceived为true,if块的代码并不执行
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            // 刷新应用上下文,springmvc相关代码在这里运行
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // 将 WebApplicationContext视为servletContext 一个属性,加入到 servletContext 中
        // 之后就可以使用
        // WebApplicationContextUtils.getWebApplicationContext(ServletContext, String attrName)
        // 来获取
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

这个方法的相关操作代码中已有注释,实际上这个方法最 重要的代码为

protected WebApplicationContext initWebApplicationContext() {
    ...
    // 在这里调用 AbstractApplicationContext#refresh 启动
    configureAndRefreshWebApplicationContext(cwac);
    ...
    return wac;
}

相关分析如下:

FrameworkServlet#configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + 
                    '/' + getServletName());
        }
    }
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // 添加事件监听器,监听spring启动完成事件
    // 这个监听器十分重要,后面会分析
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    // 扩展点,没有什么功能,待以后扩展
    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    // 调用就是 AbstractApplicationContext.refresh
    wac.refresh();
}

这个方法实际上还是配置ConfigurableWebApplicationContext的一些属性,最后调用AbstractApplicationContext#refresh来启动 spring 容器。关于AbstractApplicationContext#refresh的分析,可以参考spring启动流程之启动前的准备工作

到这里,spring容器就真正启动了。

2. SourceFilteringListener:启动事件监听器

这里有个问题:在springmvc中,我们知道spring会识别@Controller,将RequestMapping/@PostMapping/@GetMapping等注解中的路径封装为一个 uri,等待外部访问,但是我们一路看来,似乎spring并没有做这些工作,那么这部分的工作是在哪里进行的呢?

实际上,spring这部分的工作是在启动监听器中完成的,也就是

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

这个监听器监听的是spring的启动 事件,将在spring容器启动完成后调用。

关于spring的事件相关内容,可以参考spring探秘之spring 事件机制,这里直接说结论:spring 中提供了ApplicationEventPublisher#publishEvent(Object)(事件发布器)、ApplicationEvent(事件)与 ApplicationListener (事件监听器),当spring通过ApplicationEventPublisher#publishEvent(Object)发布ApplicationEvent(事件)时,ApplicationListener (事件监听器)将会监听到。

我们来看看SourceFilteringListener

public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {

    private final Object source;

    @Nullable
    private GenericApplicationListener delegate;

    /**
     * 构造方法,传入 event 与 listener
     */
    public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
        this.source = source;
        this.delegate = (delegate instanceof GenericApplicationListener ?
                (GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
    }

    /**
     * 事件监听方法
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event.getSource() == this.source) {
            // 调用真正的事件处理
            onApplicationEventInternal(event);
        }
    }

    /**
     *  处理事件
     */
    protected void onApplicationEventInternal(ApplicationEvent event) {
        if (this.delegate == null) {
            throw new IllegalStateException(...);
        }
        // 最终还是调用传入的事件监听器的onApplicationEvent方法
        this.delegate.onApplicationEvent(event);
    }

    // 省略了一些代码
    ...

可以看到,SourceFilteringListener通过构造方法传入了ContextRefreshListener的实例,然后在SourceFilteringListener#onApplicationEvent方法中,最终调用的是ContextRefreshListener#onApplicationEvent方法。

接下来我们再来看ContextRefreshListener

FrameworkServlet.ContextRefreshListener

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

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

这个类是FrameworkServlet的内部类,代码很简单,最终调用的是FrameworkServlet#onApplicationEvent

public void onApplicationEvent(ContextRefreshedEvent event) {
    // 修改状态,这个很关键,运行了这行后,
    // FrameworkServlet#initWebApplicationContext里的onRefresh(...)就不会运行
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
         // 具体的逻辑处理
         onRefresh(event.getApplicationContext());
    }
}

接下来就是DispatcherServlet#onRefresh方法了:

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

/**
 * springmvc的终极奥秘就在这里了
 * 这个方法中,初始化了springmvc的各种组件
 */
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,这个方法虽只寥寥数行,且初始化了springmvc各依赖组件!

3. DispatcherServlet#initStrategies:初始化 springmvc 组件

spring在启动完成后,会发布启动完成事件,然后由监听器SourceFilteringListener监听到该事件后,执行监听逻辑,最终调用到DispatcherServlet#initStrategies。本节我们将来分析DispatcherServlet#initStrategies的执行过程。

其实这个方法很简单,里面有9行代码,每行代码都初始化了springmvc的一个组件,如initMultipartResolver


public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";

private void initMultipartResolver(ApplicationContext context) {
    try {
        // 从spring容器中获取multipartResolver对象
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        // 获取失败,默认为null
        this.multipartResolver = null;
        }
    }
}

multipartResolver是用来处理文件上传的bean,在spring中,我们处理文件上传时,一般会像这样引入 multipartResolver bean;

@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
    CommonsMultipartResolver resolver = new CommonsMultipartResolver();
    resolver.setDefaultEncoding("UTF-8");
    resolver.setResolveLazily(true);
    resolver.setMaxInMemorySize(40960);
    //允许上传文件最大为1G
    resolver.setMaxUploadSize(1024 * 1024 * 1024);
    return resolver;
}

如果未引入multipartResolver bean,spring默认为 null,就不能进行文件上传了。

再来看看springmvc HandlerMappings 的初始化过程:

DispatcherServlet

public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

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

private static final Properties defaultStrategies;

static {
    try {
        // 在static块中加载DispatcherServlet.properties文件
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, 
                DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException(...);
    }
}

/**
 * handlerMappings 属性
 */
@Nullable
private List<HandlerMapping> handlerMappings;

/**
 * 初始化 HandlerMappings
 * 1. 从spring 容器中获取 HandlerMapping bean,
 *     如果获取成功,则把得到的结果赋值给handlerMappings
 * 2. 如果未获得,则获取默认的 HandlerMapping bean
 */
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // 加载所有实现HandlerMapping接口的bean
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        // 这里不为空,会运行
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 排序,Spring处理请求就是根据这个排序的结果进行处理,
            // 如果当前handlerMapping不可以处理则抛给下一个
            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.
        }
    }
    if (this.handlerMappings == null) {
        // 如果未添加handlerMappings,则获取默认的 handlerMappings
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

/**
 * 获取默认的策略
 */
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    // 获取配置文件DispatcherServlet.properties中默认的 class 配置
    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 {
                // 使用反射创建bean
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
                throw new BeanInitializationException(...);
            }
            catch (LinkageError err) {
                throw new BeanInitializationException(...);
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}

初始化 HandlerMappings 时,

  1. 先从spring 容器中获取 HandlerMapping bean,如果获取成功(实际上这里也能获得),则把得到的结果赋值给DispatcherServlethandlerMappings属性;
  2. 如果未失败,表明spring容器中未存在 HandlerMapping ,则获取默认的 HandlerMapping bean.
  3. 获取默认的HandlerMapping bean时,读取DispatcherServlet.properties配置,然后使用反射实例化。

我们来看看DispatcherServlet.properties文件,该文件位于 spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties,部分内容如下:

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
...

DispatcherServlet#initStrategies中其他 initXxx() 内容类似,这里就不一一分析了。

4. 总结

本文主要分析了springmvc的启动过程,总结如下:

  1. servlet容器(这里是tomcat)启动时,通过spi机制执行 ServletContainerInitializer#onStartup方法,而springmvc对提供的SpringServletContainerInitializer对其进行了实现,于是SpringServletContainerInitializer#onStartup方法会被调用;
  2. SpringServletContainerInitializer#onStartup方法中,spring会调用WebApplicationInitializer#onStartup 方法,而MyWebApplicationInitializer对其进行了实现,于是MyWebApplicationInitializer#onStartup 会被调用;
  3. MyWebApplicationInitializer#onStartup方法中 ,我们创建了一个applicationContext对象,将其与DispatcherServlet绑定,然后将DispatcherServlet注册到servlet容器中(这里是tomcat);
  4. DispatcherServlet注册到servlet容器中(这里是tomcat)后,根据servlet生命周期,DispatcherServlet#init将会被调用;
  5. DispatcherServlet#init 中会执行spring容器的启动过程,spring容器启动后,会发布启动完成事件;
  6. spring启动完成后,ContextRefreshListener将会监听spring启动完成事件,FrameworkServlet.ContextRefreshListener#onApplicationEvent 方法会被调用,调用调用到DispatcherServlet#initStrategies
  7. spring最终在DispatcherServlet#initStrategies中初始化MultipartResolverLocaleResolver等组件,所谓的初始化,其实是获取或创建对应的bean,然后赋值给DispatcherServlet的属性。

到此,springmvc整个启动流程就完成了。不过到此我们都没有看到spring处理@RequestMapping的相关流程,那么spring是如何处理这个流程呢 ,下一篇文章将揭晓。


本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本系列的其他文章

【spring源码分析】spring源码分析系列目录