SpringBoot源码剖析-自动配置SpringMVC

2022/1/15 22:03:52

本文主要是介绍SpringBoot源码剖析-自动配置SpringMVC,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在上一小节,我们介绍了 SpringBoot 是如何启动一个内置 tomcat 的。我们知道我们在 SpringBoot 项目里面是可以直接使用诸如 @RequestMapping 这类的 SpringMVC 的注解,那么同学们会不会奇 怪,这是为什么?我明明没有配置 SpringMVC 为什么就可以使用呢? 其实仅仅引入 starter 是不够的,回忆一下,在一个普通的 WEB 项目中如何去使用 SpringMVC ,我 们首先就是要在 web.xml 中配置如下配置

 

但是在 SpringBoot 中,我们没有了 web.xml 文件,我们如何去配置一个 Dispatcherservlet 呢? 其实 Servlet3.0 规范中规定,要添加一个 Servlet ,除了采用 xml 配置的方式,还有一种通过代码的 方式,伪代码如下 :
servletContext . addServlet ( name , this . servlet );
那么也就是说,如果我们能动态往 web 容器中添加一个我们构造好的 DispatcherServlet 对象, 是不是就实现自动装配 SpringMVC 了

一. 自动配置DispatcherServletDispatcherServletRegistry

 

springboot 的自动配置基于 SPI 机制,实现自动配置的核心要点就是添加一个自动配置的类, SpringBoot MVC 的自动配置自然也是相同原理。 所以,先找到 springmvc 对应的自动配置类。
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConf iguration

(一)DispatcherServletAutoConfiguration自动配置类

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
1 、首先注意到, @Configuration 表名这是一个配置类,将会被 spring 给解析。 2 、 @ConditionalOnWebApplication 意味着当时一个 web 项目,且是 Servlet 项目的时候才会被解 析。 3 、 @ConditionalOnClass 指明 DispatcherServlet 这个核心类必须存在才解析该类。 4 、 @AutoConfigureAfter 指明在 ServletWebServerFactoryAutoConfiguration 这个类之后再解 析,设定了一个顺序。 总的来说,这些注解表明了该自动配置类的会解析的前置条件需要满足。 其次, DispatcherServletAutoConfiguration 类主要包含了两个内部类,分别是 1 、 DispatcherServletConfiguration 2 、 DispatcherServletRegistrationConfiguration 顾名思义,前者是配置 DispatcherServlet ,后者是配置 DispatcherServlet 的注册类。什么是注册 类?我们知道 Servlet 实例是要被添加(注册)到如 tomcat 这样的 ServletContext 里的,这样才能 够提供请求服务。所以, DispatcherServletRegistrationConfiguration 将生成一个 Bean ,负责将 DispatcherServlet 给注册到 ServletContext 中。

(二)配置DispatcherServletConfiguration

我们先看看 DispatcherServletConfiguration 这个配置类
@Configuration(proxyBeanMethods = false)
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
	protected static class DispatcherServletConfiguration {
@Conditional 指明了一个前置条件判断,由 DefaultDispatcherServletCondition 实现。主要是判 断了是否已经存在 DispatcherServlet ,如果没有才会触发解析。 @ConditionalOnClass 指明了当 ServletRegistration 这个类存在的时候才会触发解析,生成的 DispatcherServlet 才能注册到 ServletContext 中。 最后, @EnableConfigrationProperties 将会从 application.properties 这样的配置文件中读取 spring.http 和 spring.mvc 前缀的属性生成配置对象 HttpProperties 和 WebMvcProperties 。 再看 DispatcherServletConfiguration 这个内部类的内部代码

@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
			dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.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
			return resolver;
		}
这个两个方法我们比较熟悉了,就是生成了 Bean 。 dispatcherServlet 方法将生成一个 DispatcherServlet 的 Bean 对象。比较简单,就是获取一个实 例,然后添加一些属性设置。 multipartResolver 方法主要是把你配置的 MultipartResolver 的 Bean 给重命名一下,防止你不是用 multipartResolver 这个名字作为 Bean 的名字。

(三)配置DispatcherServletRegistrationConfiguration

再看注册类的 Bean 配置
@Configuration(proxyBeanMethods = false)
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {
同样的, @Conditional 有一个前置判断, DispatcherServletRegistrationCondition 主要判断了该 注册类的 Bean 是否存在。 @ConditionOnClass 也判断了 ServletRegistration 是否存在 @EnableConfigurationProperties 生成了 WebMvcProperties 的属性对象 @Import 导入了 DispatcherServletConfiguration ,也就是我们上面的配置对象。 再看 DispatcherServletRegistrationConfiguration 的内部实现
@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);
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
		}
内部只有一个方法,生成了 DispatcherServletRegistrationBean 。核心逻辑就是实例化了一个 Bean ,设置了一些参数,如 dispatcherServlet 、 loadOnStartup 等 总结 springboot mvc 的自动配置类是 DispatcherServletAutoConfigration ,主要做了两件事: 1 )配置 DispatcherServlet 2 )配置 DispatcherServlet 的注册 Bean(DispatcherServletRegistrationBean)

二. 注册DispatcherServletServletContext

在上一小节的源码翻阅中,我们看到了 DispatcherServlet 和 DispatcherServletRegistrationBean 这两个 Bean 的自动配置。 DispatcherServlet 我们很熟悉, DispatcherServletRegistrationBean 负 责将 DispatcherServlet 注册到 ServletContext 当中

(一)DispatcherServletRegistrationBean的类图

既然该类的职责是负责注册 DispatcherServlet ,那么我们得知道什么时候触发注册操作。为此, 我们先看看 DispatcherServletRegistrationBean 这个类的类图

 (二)注册DispatcherServlet流程

1. ServletContextInitializer

我们看到,最上面是一个 ServletContextInitializer 接口。我们可以知道,实现该接口意味着是用 来初始化 ServletContext 的。我们看看该接口
public interface ServletContextInitializer { 
void onStartup(ServletContext servletContext) throws ServletException; 
}

2. RegistrationBean

看看RegistrationBean是怎么实现onStartup方法的

@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		//获取当前到底是一个filter 还是一个servlet 还是一个listener
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
			return;
		}
		register(description, servletContext);
	}
调用了内部 register 方法,跟进它

3. DynamicRegistrationBean

再看 DynamicRegistrationBean 是怎么实现 register 方法的
@Override
	protected final void register(String description, ServletContext servletContext) {
		D registration = addRegistration(description, servletContext);
		if (registration == null) {
			logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
			return;
		}
		configure(registration);
	}
跟进 addRegistration 方法
protected abstract D addRegistration(String description, ServletContext servletContext);

4.ServletRegistrationBean

再看 ServletRegistrationBean 是怎么实现 addRegistration 方法的
@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		String name = getServletName();
		//负责将DispatcherServlet注册到servletContext中
		return servletContext.addServlet(name, this.servlet);
	}
我们看到,这里直接将 DispatcherServlet 给 add 到了 servletContext 当中。

(三)SpringBoot启动流程中具体体现

getSelfInitializer().onStartup(servletContext);

这段代码其实就是去加载 SpringMVC ,那么他是如何做到的呢? getSelfInitializer() 最终会 去调用到 ServletWebServerApplicationContext 的 selfInitialize 方法,该方法代码如下

 

private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}
我们通过调试,知道 getServletContextInitializerBeans() 返回的是一个 ServletContextInitializer 集合,集合中有以下几个对象

 

然后依次去调用对象的 onStartup 方法,那么对于上图标红的对象来说,就是会调用到 DispatcherServletRegistrationBean 的 onStartup 方法,这个类并没有这个方法,所以最终 会调用到父类 RegistrationBean 的 onStartup 方法,该方法代码如下

@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		//获取当前到底是一个filter 还是一个servlet 还是一个listener
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
			return;
		}
		register(description, servletContext);
	}
这边 register(description, servletContext) ; 会调用到 DynamicRegistrationBean 的 register 方法,代码如下:
@Override
	protected final void register(String description, ServletContext servletContext) {
		D registration = addRegistration(description, servletContext);
		if (registration == null) {
			logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
			return;
		}
		configure(registration);
	}
addRegistration(description, servletContext) 又会调用到 ServletRegistrationBean 中的 addRegistration 方法,代码如下 :
@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		String name = getServletName();
		//负责将DispatcherServlet注册到servletContext中
		return servletContext.addServlet(name, this.servlet);
	}
看到了关键的 servletContext.addServlet 代码了,我们通过调试,即可知到 this.servlet 就 是 dispatcherServlet

 

总结 SpringBoot 自动装配 SpringMvc 其实就是往 ServletContext 中加入了一个 Dispatcherservlet 。 Servlet3.0 规范中有这个说明,除了可以动态加 Servlet, 还可以动态加 Listener , Filter
  • addServlet
  • addListener
  • addFilter


这篇关于SpringBoot源码剖析-自动配置SpringMVC的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程