Spring Boot中的DispatcherServlet

传统的spring-mvc应用中,需要在web.xml中配置DispatcherServlet并配置好映射的路径。Spring Boot以的最大特点就是自动配置,用户可以开箱即用。那Spring Boot内部是怎么配置的呢?本文就来分析一下这个问题。


本文关注两个问题:

  • DispatcherServlet对象是怎么被创建的?
  • DispatcherServlet是怎么注册的?

创建DispatcherServlet对象

Spring Boot提供了DispatcherServletAutoConfiguration这个自动配置类,在该类中会向bean工厂中注册DispatcherServletbean对象。

v2.7.x
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java

/**
 * The bean name for a DispatcherServlet that will be mapped to the root URL "/".
 */
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

/**
 * The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
 */
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {

    /*
     * 创建DispatcherServlet,并将dispatchServlet注入到容器中。
     */
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        // 通过直接new创建DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        // 下面是配置dispatcherServlet的各项属性
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    @Bean
    @ConditionalOnBean(MultipartResolver.class)
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        // Detect if the user has created a MultipartResolver but named it incorrectly
        // 处理容器中存在MultipartResolver实例,但是名字不叫作multipartResolver的情况,这里就只是起一个bean重命名的操作
        return resolver;
    }

}

这部分内容比较简单,后续会从bean工厂中获取该对象并进行注册。

注册DispatcherServlet

同样是在DispatcherServletAutoConfiguration这个自动配置类中,会向bean工厂中注入一个DispatcherServletRegistrationBean类型的实例,从名称可以看出来,该bean是负责servlet注册的。

v2.7.x
DispatcherServletRegistrationConfiguration
DispatcherServletRegistrationBean
ServletRegistrationBean
<
>
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration.java

/**
 * The bean name for a DispatcherServlet that will be mapped to the root URL "/".
 */
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

/**
 * The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
 */
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
	@Configuration(proxyBeanMethods = false)
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		/*
		 * 注入dispatcherServletRegistration,这是一个ServletContextInitializer。
		 * 主要负责servlet(在这里是dispatcherServlet)的注册
		 */
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
				WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			// 设置启动时加载顺序,这里setLoadOnStartup方法的实参是-1
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
		}

	}
java
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DispatcherServletRegistrationBean.java
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
    super(servlet);
    Assert.notNull(path, "Path must not be null");
    this.path = path;
    super.addUrlMappings(getServletUrlMapping());
}
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistrationBean.java
private static final String[] DEFAULT_MAPPINGS = { "/*" };

private T servlet;

private Set<String> urlMappings = new LinkedHashSet<>();

private boolean alwaysMapUrl = true;

private int loadOnStartup = -1;

private MultipartConfigElement multipartConfig;
public ServletRegistrationBean() {
}
public ServletRegistrationBean(T servlet, String... urlMappings) {
    this(servlet, true, urlMappings);
}
public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
    Assert.notNull(servlet, "Servlet must not be null");
    Assert.notNull(urlMappings, "UrlMappings must not be null");
    this.servlet = servlet;
    this.alwaysMapUrl = alwaysMapUrl;
    this.urlMappings.addAll(Arrays.asList(urlMappings));
}

在理解下面的过程之前,最好梳理一下DispatcherServletRegistrationBean类的继承结构。

那这个bean是怎么进行注册的呢?注意这个bean是ServletContextInitializer的子类型的,在创建Web服务器一文中讲到,Spring Boot在调用servlet的web服务器工厂的getWebServer方法时会传入一个ServletContextInitializer,当然这里不是直接传递的DispatcherServletRegistrationBean。Spring在这里做了一层封装,向Web服务器工厂传递的是一个叫作getSelfInitializerlambda函数,而在该lambda函数中才会调用bean工厂中其他的ServletContextInitializer

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    // 如果webServer为null,则新建一个。一般都是这种情况。
    if (webServer == null && servletContext == null) {
        StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
        // 获取WebServer工厂,这里关注TomcatServletWebServerFactory就是了
        ServletWebServerFactory factory = getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        // 创建WebServer实例,并传入ServletContainerInitializer
        this.webServer = factory.getWebServer(getSelfInitializer());
        createWebServer.end();
        // 向容器中注入两个bean
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                new WebServerGracefulShutdownLifecycle(this.webServer)); // 负责设置running标志位和停止WebServer
        getBeanFactory().registerSingleton("webServerStartStop",
                new WebServerStartStopLifecycle(this, this.webServer)); // 负责实际启动和停止WebServer
    }
    else if (servletContext != null) { // 如果servletContext对象存在
        try {
            // 则通过SCI来初始化
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}
// 返回一个SCI对象
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}
// 该方法负责初始化servletContext对象
private void selfInitialize(ServletContext servletContext) throws ServletException {
    // 设置上下文attr属性
    prepareWebApplicationContext(servletContext);
    // 向spring容器中注入ServletContextScope
    registerApplicationScope(servletContext);
    // 将servletContext对象注入到容器中
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 遍历容器中设置的其他Servlet上下文初始化器对象
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        // 执行Servlet上下文初始化器
        beans.onStartup(servletContext);
    }
}

可以看到在selfInitialize函数中会调用到bean工厂中的ServletContextInitializerbean,那DispatcherServletRegistrationBean自然也会在这里会被调用到。

本文不介绍servlet容器是怎么调用到selfInitialize这个函数的,先知道在servlet容器初始化好servlet上下文后就会调用它就行了。

回到主线,DispatcherServletRegistrationBean的间接父类RegistrationBean中实现了ServletContextInitializer接口的onStartup方法。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/RegistrationBean.java
// Servlet上下文初始化后会调用该方法
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    // 执行注册servlet的操作
    register(description, servletContext);
}
protected abstract void register(String description, ServletContext servletContext);

这里的register方法是个模板方法,留给子类实现,其子类DynamicRegistrationBean实现了该方法。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/DynamicRegistrationBean.java
@Override
protected final void register(String description, ServletContext servletContext) {
    // 执行注册servlet的操作
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        return;
    }
    configure(registration);
}
protected abstract D addRegistration(String description, ServletContext servletContext);

这里的addRegistration方法又是同样的套路, 来看子类ServletRegistrationBean的实现。

v2.7.x
java
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistrationBean.java
private static final String[] DEFAULT_MAPPINGS = { "/*" };

private T servlet;

private Set<String> urlMappings = new LinkedHashSet<>();

private boolean alwaysMapUrl = true;

private int loadOnStartup = -1;

private MultipartConfigElement multipartConfig;
	@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		String name = getServletName();
		// 将DispatchServlet添加到ServletContext中
		return servletContext.addServlet(name, this.servlet);
	}

终于,在这里看到调用了ServletContextaddServlet来注册dispatcherServlet了。注意,上面在创建DispatcherServletRegistrationBean时传递给其构造方法的servlet,会被其传递给父类的构造方法,也就是这里ServletRegistrationBean的构造方法,所以这里this.servlet就是dispatcherServlet。

总结

本文介绍了Spring Boot中DispatcherServlet实例的创建以及注册过程,知道了Spring Boot在背后为我们做了什么事情。文本只是关于DispatcherServlet的开胃小菜,后续会介绍其是怎么处理请求的。