SpringMVC DispatcherServlet源码(1) 注册DispatcherServlet流程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

本文通过阅读源码分析Spring MVC和Spring Boot注册DispatcherServlet的流程。

概述

DispatcherServlet是Spring MVC的核心组件他会被注册到Servlet Web容器(例如tomcat)中接收/*请求然后做请求分发调用Controller方法处理请求接收响应返回给客户端。

在早期版本的Spring MVC中使用web.xml文件配置DispatcherServlet

<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

这是Servlet规范的要求所有的Java Web服务都需要这样配置进行Servlet注册。

这种方式我们不做分析我们要重点分析的是

  • 注解驱动的Spring MVC注册DispatcherServlet的方式
  • Spring Boot注册DispatcherServlet的方式

Spring MVC注册DispatcherServlet

注解驱动的Spring MVC

注解驱动的Spring MVC要求开发者编写一个WebApplicationInitializer实现类

/*
 * Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext 
 * programmatically -- as opposed to (or possibly in conjunction with) the traditional 
 * web.xml-based approach.
 * Implementations of this SPI will be detected automatically by SpringServletContainerInitializer,
 * which itself is bootstrapped automatically by any Servlet 3.0 container.
 */
public interface WebApplicationInitializer {
	void onStartup(ServletContext servletContext) throws ServletException;
}

实现类这样写

// 不需要直接实现WebApplicationInitializer接口
// Spring提供了抽象类开发者继承抽象类实现抽象方法即可
public class SpringMvcInitializer extends
    AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[]{AppConfig.class, MybatisConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return null;
  }

  @Override
  protected String[] getServletMappings() {
    return new String[]{"/"};
  }
}

这样把工程部署到tomcat之类的Servlet容器之后即可启动Spring MVC并注册DispatcherServlet。

那么这是如何实现的这得从Servlet 3.0新特性说起。

Servlet 3.0和ServletContainerInitializer接口

在Servlet 3.0中提供了一个ServletContainerInitializer接口

// Since: Servlet 3.0
public interface ServletContainerInitializer {

    /**
     * Receives notification during startup of a web application of the classes within the 
     * web application that matched the criteria defined via the 
     * javax.servlet.annotation.HandlesTypes annotation.
     * Params:
     * c – The (possibly null) set of classes that met the specified criteria
     * ctx – The ServletContext of the web application in which the classes were discovered
     */
    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

这个接口的用途

ServletContainerInitializers (SCIs) are registered via an entry in the file META-INF/services/javax.servlet.ServletContainerInitializer that must be included in the JAR file that contains the SCI implementation.

SCIs register an interest in annotations (class, method or field) and/or types via the javax.servlet.annotation.HandlesTypes annotation which is added to the class.

----------

ServletContainerInitializers通过SPI方式向Servlet容器注入实现。

在Servlet容器启动时会通过SPI获取到ServletContainerInitializer的实现类实例然后调用onStartup方法。

ServletContainerInitializer的实现类通过HandlesTypes注册自己需要的类在调用onStartup方法时会通过Set<Class<?>> c参数传递给实现类。

Tomcat对ServletContainerInitializer的支持

在tomcat中使用SPI加载所有的ServletContainerInitializer实现类对象并将其注册到Context中等待调用加载SPI的代码在org.apache.catalina.startup.ContextConfig的processServletContainerInitializers方法中

protected void processServletContainerInitializers() {

    List<ServletContainerInitializer> detectedScis;
    try {
        // 使用SPI加载ServletContainerInitializer实现
        WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
        detectedScis = loader.load(ServletContainerInitializer.class);
    } catch (IOException e) {
        ok = false;
        return;
    }

    for (ServletContainerInitializer sci : detectedScis) {
        initializerClassMap.put(sci, new HashSet<Class<?>>());

        // 解析HandlesTypes注解
        HandlesTypes ht;
        try {
            ht = sci.getClass().getAnnotation(HandlesTypes.class);
        } catch (Exception e) {
            continue;
        }
        if (ht == null) {
            continue;
        }
        Class<?>[] types = ht.value();
        if (types == null) {
            continue;
        }

        for (Class<?> type : types) {
            if (type.isAnnotation()) {
                handlesTypesAnnotations = true;
            } else {
                handlesTypesNonAnnotations = true;
            }
            Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
            if (scis == null) {
                scis = new HashSet<>();
                typeInitializerMap.put(type, scis);
            }
            scis.add(sci);
        }
    }
}

然后在StandardContext的startInternal获取到所有的ServletContainerInitializer并调用onStartup方法

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) {
    try {
        entry.getKey().onStartup(entry.getValue(), getServletContext());
    } catch (ServletException e) {
        ok = false;
        break;
    }
}

Spring MVC和SpringServletContainerInitializer类

Spring MVC中SpringServletContainerInitializer类实现了ServletContainerInitializer接口接口

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
                        // 创建WebApplicationInitializer实现类对象
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					} catch (Throwable ex) {
						throw new ServletException("...");
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			return;
		}

        // 排序调用WebApplicationInitializer
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

Spring MVC使用spring-web-5.2.12.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer文件做SPI配置

org.springframework.web.SpringServletContainerInitializer

AbstractAnnotationConfigDispatcherServletInitializer类

一个抽象类

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
		extends AbstractDispatcherServletInitializer {
    // ...
}

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

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

	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();

		WebApplicationContext servletAppContext = createServletApplicationContext();

        // 创建DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		ServletRegistration.Dynamic registration = servletContext
            .addServlet(servletName, dispatcherServlet);

		registration.setLoadOnStartup(1);
        // 添加url mapping
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

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

		customizeRegistration(registration);
	}
}

小结

  1. Spring MVC使用SPI注册SpringServletContainerInitializer类
  2. Tomcat在启动时使用SPI加载ServletContainerInitializer的所有实现包括SpringServletContainerInitializer类并且解析@HandlesTypes注解解析该注解声明的所有Class集
  3. Tomcat启动StandardContext时调用所有的ServletContainerInitializer的onStartup方法并传递Class集参数
  4. SpringServletContainerInitializer类onStartup方法实例化所有的WebApplicationInitializer实现类调用onStartup方法进行应用初始化在这个过程中注册DispatcherServlet

Spring Boot注册DispatcherServlet

DispatcherServletAutoConfiguration自动装配类

Auto-configuration for the Spring DispatcherServlet.

Should work for a standalone application where an embedded web server is already present and also for a deployable application using SpringBootServletInitializer.

这个类会自动装配DispatcherServlet对象

@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
	DispatcherServlet dispatcherServlet = new DispatcherServlet();
	dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
	dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
	dispatcherServlet
        .setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
	dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
	dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
	return 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,
        	// spring.mvc.servlet.path参数
			webMvcProperties.getServlet().getPath());

	registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    // spring.mvc.servlet.loadOnStartup参数
	registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
	multipartConfig.ifAvailable(registration::setMultipartConfig);
	return registration;
}

DispatcherServletRegistrationBean实现了ServletContextInitializer接口这个接口会在web服务器启动阶段随着TomcatStarter一起启动这个后续会介绍。

请记住DispatcherServletRegistrationBean实现类后续还会用到。

onRefresh

Spring Boot使用ServletWebServerApplicationContext作为ApplicationContext的实现在onRefresh阶段创建Servlet web服务器。

ServletWebServerApplicationContext类

protected void onRefresh() {
	super.onRefresh();
	try {
        // 创建Servlet web服务器
		createWebServer();
	} catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
        // 创建WebServer
        // 在默认的配置下这里获取到的是TomcatServletWebServerFactory对象
		ServletWebServerFactory factory = getWebServerFactory();
		this.webServer = factory.getWebServer(getSelfInitializer());
		// other code lines
	} else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		} catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
	return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 从容器获取所有的ServletContextInitializer对象调用onStartup方法做初始化
    // 自然也会调用DispatcherServletRegistrationBean的onStartup方法
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

TomcatServletWebServerFactory创建WebServer

public WebServer getWebServer(ServletContextInitializer... initializers) {
	if (this.disableMBeanRegistry) {
		Registry.disableRegistry();
	}
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protocol);
	connector.setThrowOnFailure(true);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
    // 创建Context
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
	// 前面代码不记录了只记录重点内容
	ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
	host.addChild(context);
    // 配置Context
	configureContext(context, initializersToUse);
	postProcessContext(context);
}

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    // TomcatStarter是ServletContainerInitializer接口的实现类
    // 在前面的章节介绍过ServletContainerInitializer的onStart方法会在StandardContext的启动阶段调用
	TomcatStarter starter = new TomcatStarter(initializers);
	if (context instanceof TomcatEmbeddedContext) {
		TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
		embeddedContext.setStarter(starter);
		embeddedContext.setFailCtxIfServletStartFails(true);
	}
    // 把starter添加到TomcatEmbeddedContext等待启动时调用
	context.addServletContainerInitializer(starter, NO_CLASSES);

    // 其余代码
}

TomcatStarter类

class TomcatStarter implements ServletContainerInitializer {

	private final ServletContextInitializer[] initializers;

	TomcatStarter(ServletContextInitializer[] initializers) {
		this.initializers = initializers;
	}

	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
		try {
            // 调用ServletContextInitializer
			for (ServletContextInitializer initializer : this.initializers) {
				initializer.onStartup(servletContext);
			}
		} catch (Exception ex) {
			this.startUpException = ex;
		}
	}

    // ...
}

DispatcherServletRegistrationBean实现类

实现了ServletContextInitializer接口但是onStart是在其抽象父类中实现的。

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {

	private int order = Ordered.LOWEST_PRECEDENCE;

	private boolean enabled = true;

	@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		String description = getDescription();
		if (!isEnabled()) {
			return;
		}
        // 抽象方法子类实现
		register(description, servletContext);
	}
}

public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {

	@Override
	protected final void register(String description, ServletContext servletContext) {
        // 抽象方法子类实现
        // 将Servlet添加到web服务器的Context中
		D registration = addRegistration(description, servletContext);
		if (registration == null) {
			return;
		}
        // 子类实现
		configure(registration);
	}
}

public class ServletRegistrationBean<T extends Servlet> extends 
    	DynamicRegistrationBean<ServletRegistration.Dynamic> {

	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();
        // 把servlet添加到Context
        // 这个servlet就是DispatcherServlet对象
		return servletContext.addServlet(name, this.servlet);
	}

	@Override
	protected void configure(ServletRegistration.Dynamic registration) {
		super.configure(registration);
		String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
		if (urlMapping.length == 0 && this.alwaysMapUrl) {
			urlMapping = DEFAULT_MAPPINGS;
		}
		if (!ObjectUtils.isEmpty(urlMapping)) {
            // 添加url mapping
			registration.addMapping(urlMapping);
		}
		registration.setLoadOnStartup(this.loadOnStartup);
		if (this.multipartConfig != null) {
			registration.setMultipartConfig(this.multipartConfig);
		}
	}

小结

  1. DispatcherServletAutoConfiguration自动装配DispatcherServlet和DispatcherServletRegistrationBean组件DispatcherServletRegistrationBean实现了ServletContextInitializer接口onStart方法会在web服务器启动时被调用用于初始化Context
  2. ServletWebServerApplicationContext在onRefresh阶段创建web服务器
  3. 创建web服务器过程中会创建TomcatStarter并添加到TomcatEmbeddedContext中他实现了ServletContainerInitializer接口onStart方法会在StandardContext的启动阶段调用
  4. web服务器启动调用TomcatStarter的onStart方法。过程中获取到Spring容器中的所有ServletContextInitializer实现包括DispatcherServletRegistrationBean对象
  5. 调用DispatcherServletRegistrationBean对象的onStart方法向web服务器注册DispatcherServlet并配置urlMapping等

通过源码分析我们深入了解了Spring MVC和Spring Boot注册DispatcherServlet的流程这只是开始后续我们将分析DispatcherServlet初始化HandlerMapping和分发请求的原理。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring