SpringBoot源码剖析-自动配置SpringMVC

本文详细解析了SpringBoot如何自动配置SpringMVC的过程,包括自动配置DispatcherServlet及其注册Bean,并介绍了DispatcherServlet如何被注册到ServletContext中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一小节,我们介绍了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类主要包含了两个内部类,分别是
1DispatcherServletConfiguration
2DispatcherServletRegistrationConfiguration
顾名思义,前者是配置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.httpspring.mvc前缀的属性生成配置对象HttpPropertiesWebMvcProperties
再看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方法将生成一个DispatcherServletBean对象。比较简单,就是获取一个实
例,然后添加一些属性设置。
multipartResolver方法主要是把你配置的MultipartResolverBean给重命名一下,防止你不是用
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,设置了一些参数,如dispatcherServletloadOnStartup
总结
springboot mvc的自动配置类是DispatcherServletAutoConfigration,主要做了两件事:
1)配置DispatcherServlet
2)配置DispatcherServlet的注册Bean(DispatcherServletRegistrationBean)

二. 注册DispatcherServletServletContext

在上一小节的源码翻阅中,我们看到了DispatcherServletDispatcherServletRegistrationBean
这两个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);
	}
我们看到,这里直接将DispatcherServletadd到了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,还可以动态加ListenerFilter
  • addServlet
  • addListener
  • addFilter
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值