Spring Boot WebMvc自动配置原理

Spring Boot WebMvc自动配置原理

每次写文章之前,我都习惯扯一下最近,我为什么写这篇文章。这一切都源于我对 WebMvcConfigurer 好奇,为什么 Spring boot 配置 WebMvc 只需要实现 WebMvcConfigurer,然后重写对应的方法就可以?我带着这个问题,看了 Spring Mvc 的源码,看了一次请求的大致过程。但是我看完还是没有答案。因为当前启动容器后,DispatcherServlet 的各种组件都已经初始化好了。那么是在何时初始化呢,于是,就有了这篇文章。

下面,我将带大家,从我当初一脸懵逼状态到恍然大悟的学习过程。

首先还是在 WebMvcAutoConfiguration 开始

// 声明为配置类,并且 Bean 的方法不进行代理
@Configuration(proxyBeanMethods = false)
// 判断当前环境是一个 SERVLET 环境,也就是说是 Web 环境下这个配置类才生效
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个后面会说,暂且忽略
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
// WebMVC 配置的核心注解,主要是对 DispatcherServlet 进行 Java 配置,以及对任务执行和校验器进行 Java 配置。
// 当这个配置类生效之后,就会接着进行 DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration,
// ValidationAutoConfiguration 的配置,我这次重点看 DispatcherServletAutoConfiguration
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

也就是说,先配置 DispatcherServletAutoConfiguration,再来配置 WebMvcAutoConfiguration

DispatcherServletAutoConfiguration

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 如果环境中已经有了用户自己配置的 DispatcherServlet,就不再自动配置
@ConditionalOnClass(DispatcherServlet.class)
// 这个暂且不管。不影响我们理解
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
直接 new 一个 DispatcherServlet 交给 IOC 容器

在这个 DispatcherServlet 自动配置类中,最核心的就是对 DispatcherServlet 的配置,可以看到对 DispatcherServlet 配置的核心代码在静态内部类 DispatcherServletConfiguration 的 dispatcherServlet 方法上,通过 @Bean 返回给 Spring 容器。

	@Configuration(proxyBeanMethods = false)
	@Conditional(DefaultDispatcherServletCondition.class)
	// 这个很重要,主要用于注册 servlet 到 tomcat 容器中
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration {

		// 返回一个 DispatcherServlet 交由 Spring 容器管理
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
			// 直接new,并赋值一些基本属性(未包括组件)
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			...
			...
			return dispatcherServlet;
		}

这就完了吗,并没有。我们都知道此时的 servlet 还没注册到 Tomcat。也许你们跟我刚开始看到这里会有同样的疑问。注册 servlet 是什么鬼?你还记得 servet 的生命周期吗,其中有一个 init()。DispatcherServlet 的各种组件就是在该方法中赋值。我理解这个初始化方法就是 tomcat 这边管理调用的。

你可能会说,那我以前写 web.xml 也没有注册啥 servlet,直接在 web.xml 配置一个 servlet,启动的时候也会调用 init()。是的,那是 Tomcat 都给你做好了。但是,现在使用 Spring Boot 的话,没有了 web.xml,所以在自动配置 DispatcherServlet 时,这时候就需要Springboot 来完成这个事情。

DispatcherServlet 如何被注册到 Tomcat

在上面的方法执行结束将一个 DispatcherServlet 交给 Spring 容器后,接下来会调用静态内部类DispatcherServletRegistrationConfiguration 的 dispatcherServletRegistration() ,对 DispatcherServlet 的参数进行配置:

	@Configuration(proxyBeanMethods = false)
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		// 参数是上面已经注册到 Spring 容器的 dispatcherServlet
		// 通过返回 ServletRegistrationBean 对象 将 DispatcherServlet 注入到 Tomcat 容器中
		// 其中就设置了映射、loadOnStartup 等之前我们在配置文件中设置的参数

		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);
			
			// 当 load-on-startup 为负数或者没有配置时,在第一次 request 请求时加载;
			// 当 load-on-startup 不为负数时,代表 DispatcherServlet 在 ContextLoaderListener 完成根上下文的初始化之后即加载。
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
		}

	}

DispatcherServletRegistrationBean 这个类很重要,你点进去看的话,会发现,这个类最终实现了 ServletContextInitializer 接口·

看到这里如果前面对 Servlet 的 SPI 有所了解很快就能反应过来,这里是有一个 onStartup() 方法通过 SPI 机制让 Tomcat 启动时能自动调用到 onStartup 里面来,通过调用链最终进行了 addServlet() 操作将 DispatchServlet 添加到 Tomcat 容器中并生效。

	@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
			return;
		}
		register(description, servletContext);
	}
DispatcherServlet 中组件如何被初始化

前面提到返回 ServletRegistrationBean 对象,其中有一个很重要的参数 load-on-startup,默认为 -1,如果为负数,DispatcherServlet在第一次请求时初始化(调用servlet的init方法)。我们可以通过在 application.properties 文件中添加spring.mvc.servlet.loadOnStartup = 1,修改其默认行为,IOC 容器初始化完成就调用。你可以打断点试试…

在前面,我们知道了通过 new 一个 DispatcherServlet 之后直接用 @Bean 返回给 Spring 容器了。在学习 SpringMVC 时我们知道DispatcherServlet 需要传入一个 ApplicationContext 上下文,如果没有传递则会在调用 servlet 的 init() 中会去默认配置文件解析出一个 ApplicationContext 上下文。但是这里这个 DispatcherServlet 却没有关联任何 Spring 上下文的地方使 DispatcherServlet 起作用,那这个关联操作到底是在哪里进行的呢?答案是 DispatcherServlet 的父类 FrameworkServlet。

接下来我们暂且叫 IOC 容器为业务容器,servlet 容器为 web 容器

FrameworkServlet 实现了 ApplicationContextAware 接口,所以当业务容器初始化结束之后,实现了 ApplicationContextAware 接口的实现类就会被调用并且执行 setApplicationContext() 方法。

Public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware

看过 DispatcherServlet 源码的同学应该知道,调用 servlet 的 init() 意味着初始化 Web 容器,可以理解为 dispatcherServlet 对象赋值各种属性(因为此时的 dispatcherServlet 对象很多组件处理器映射器以及处理器适配器等属性都还是空的,还不能够处理 http 请求)

// 最终会调用 dispatcherServlet 父类 FrameworkServlet 的 initWebApplicationContext 方法
protected WebApplicationContext initWebApplicationContext() {
		// 上下文中获取业务容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		// 声明一个wac,为 dispatcherServlet 对象赋值各种属性的上下文
		WebApplicationContext wac = null;
		// ApplicationContextAware 接口 注入的 webApplicationContext
		if (this.webApplicationContext != null) {
			// 直接赋值
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						// 把 rootContext 作为 Web 容器的父容器
						cwac.setParent(rootContext);
					}
					
					// 刷新容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
		
			// 触发真正的 dispatcherServlet 初始化
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

实际上 onRefresh(wac) 方法最终回调用到该方法。。

	// ApplicationContext 中根据类型获取 bean 集合赋值
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		// 处理器映射
		initHandlerMappings(context);
		// 处理器适配器
		initHandlerAdapters(context);
		// 处理器异常解析器
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		// 视图解析器
		initViewResolvers(context);
		initFlashMapManager(context);
	}

到这里,DispatcherServlet 已经初始化完成,可以处理前端的各种请求了。

WebMvcAutoConfiguration

最开始已经简单介绍了 WebMvcAutoConfiguration 生效的各种条件,其实主要配置各种组件的是它的一个内部类WebMvcAutoConfigurationAdapter。

@Configuration(proxyBeanMethods = false)
// 这个类非常重要,EnableWebMvcConfiguration 开启 WebMvc 的配置
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
		org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

这里有两个重点。

  1. @Import 注解注入的 EnableWebMvcConfiguration.class,不了解 @Import 的先学习一波?
  2. WebMvcAutoConfigurationAdapter 实现了 WebMvcConfigurer 接口。WebMvcConfigurer 是一个扩展 SpringMVC 配置的接口,下面会说到。
WebMvcConfigurationSupport 默认组件初始化,交由 IOC 容器

首先说第一点,EnableWebMvcConfiguration.class。EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
// DelegatingWebMvcConfigurationyou 又继承了 WebMvcConfigurationSupport
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

这里我得解释一下,突然就多了两个类多多少少会有点懵。我理解 EnableWebMvcConfiguration 类本身就是开启 WebMvc 配置,就是一个开关。但它基本啥都没干。全都委托给父类 DelegatingWebMvcConfiguration。

DelegatingWebMvcConfiguration 又是什么呢,顾名思义是委托 WebMvc 配置,也就是说 EnableWebMvcConfiguration 委托DelegatingWebMvcConfiguration 完成大部分的配置。DelegatingWebMvcConfiguration 这个类会获取所有的 WebMvcConfigurer 实现来使每一个 WebMvcConfigurer 都生效。

WebMvcConfigurationSupport 我理解它是一个 WebMvc 配置的支持类。那他干了什么呢。该类主要配置了一些 WebMvc 组件,比如处理器映射器,处理器适配器等。

public class WebMvcConfigurationSupport{

	...
	...
	
	// 返回一个RequestMappingHandlerMapping,用于处理请求映射handler
	@Bean
	@SuppressWarnings("deprecation")
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		mapping.setOrder(0);
		// 赋值拦截器
		mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
		mapping.setContentNegotiationManager(contentNegotiationManager);
		// 跨域配置
		mapping.setCorsConfigurations(getCorsConfigurations());
		...
		...
		return mapping;
	}
	
	// 返回一个 RequestMappingHandlerAdapter(处理器适配器)
	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcValidator") Validator validator) {

		RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
		adapter.setContentNegotiationManager(contentNegotiationManager);
		// 赋值消息转换器 
		// getMessageConverters()这些方法很重要,下面会说到
		adapter.setMessageConverters(getMessageConverters());
		adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
		// 参数解析器
		adapter.setCustomArgumentResolvers(getArgumentResolvers());
		// 返回值处理器
		adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
		...
		...
		return adapter;
	}
	
	...
	...
	
}

这些基本组件注册到容器后,最后会在初始化 DisPatcherServlet 调用 init() 时,从容器中根据类型获取处理器列表,适配器列表等等赋值到 DisPatcherServlet 对应的属性中。

不知道你们发现没有,上面注册 RequestMappingHandlerMapping 的 bean 时,是需要为该对象赋值 Interceptor,注册RequestMappingHandlerAdapter 的 bean 需要 MessageConverter,ArgumentResolver,ReturnValueHandler 等等。

依我的理解,赋值 Interceptor,MessageConverter,ArgumentResolver,ReturnValueHandler 时理应包括 WebMvc 默认的和我们自定义配置的。是的,Spring Boot在自动配置时确实也是这么做的,那它是怎么实现的呢?

为什么 Spring boot 配置 WebMvc 只需要实现 WebMvcConfigurer,然后重写对应的方法就可以?

还记得我上面提到的第二点吗,WebMvcAutoConfigurationAdapter 实现了 WebMvcConfigurer 接口。

WebMvcAutoConfigurationAdapter 这个类本身配置了 WebMvc 的一些默认配置,比如消息转换器,类型转换器

// 省略注解...
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer{

	...
	...
	
	// 配置默认消息转换器
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.messageConvertersProvider
				.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
	}
	
    // 配置默认 Formatter
	@Override
	public void addFormatters(FormatterRegistry registry) {
		ApplicationConversionService.addBeans(registry, this.beanFactory);
	}
		
	@Override
	public MessageCodesResolver getMessageCodesResolver() {
		if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
			DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
			resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
			return resolver;
		}
		return null;
	}

	...
	...
	
}

在上面 WebMvcConfigurationSupport 类中注册 RequestMappingHandlerMapping,RequestMappingHandlerAdapter 的 bean 时,我有说到 getMessageConverters(),getArgumentResolvers(),getInterceptors(conversionService, resourceUrlProvider) 等方法很重要。比如 getMessageConverters() 方法,实际上可以理解是获取到了所有的消息转换器。

public class WebMvcConfigurationSupport{

	...
	...
	
	@Nullable
	private List<HandlerMethodArgumentResolver> argumentResolvers;

	@Nullable
	private List<HandlerMethodReturnValueHandler> returnValueHandlers;

	@Nullable
	private List<HttpMessageConverter<?>> messageConverters;

	@Nullable
	private Map<String, CorsConfiguration> corsConfigurations;
	
	// 获取消息所有的消息转换器
	protected final List<HttpMessageConverter<?>> getMessageConverters() {
		// 初始化时为空
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<>();
			// 配置默认的和自定义的 MessageConverter
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}
	
	// 该方法是一个空方法,实现委派给子类 DelegatingWebMvcConfiguration 实现
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
	}
	
	// 获取参数解析器
	protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
		if (this.argumentResolvers == null) {
			this.argumentResolvers = new ArrayList<>();
			addArgumentResolvers(this.argumentResolvers);
		}
		return this.argumentResolvers;
	}
	
	protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
	}
	
	// 获取拦截器
	protected final Object[] getInterceptors(
			FormattingConversionService mvcConversionService,
			ResourceUrlProvider mvcResourceUrlProvider) {

		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
			// 添加默认的和用户自定义的拦截器
			addInterceptors(registry);
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}
	
	// 子类覆盖该方法去组装所有默认的和用户自定义的 Interceptor
	protected void addInterceptors(InterceptorRegistry registry) {
	}
	
	// FormattingConversionService 是一个类型格式化转换器服务,封装了默认的转换器,包括Formatter和Converter
	// 该类在注册很多组件都会用到,直接在IOC容器中获取
	@Bean
	public FormattingConversionService mvcConversionService() {
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		addFormatters(conversionService);
		return conversionService;
	}
	
	// 子类覆盖该方法去组装所有默认的和用户自定义的 Converter和Formatter
	protected void addFormatters(FormatterRegistry registry) {
	}
	
	...
	...
}
	

可以看到,大部分都是委托子类 DelegatingWebMvcConfiguration 去完成。

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	// 这个变量类型,看名字大概可以猜到它是一个 WebMvcConfigurer 实现的组合
	// 它组合了所有的 WebMvcConfigurer,可以理解他就是一个 List<WebMvcConfigurer>
	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	// configurers初始化,WebMvcConfigurer 类型注入所有的 WebMvcConfigurer
	// 包括我们自定扩展的 WebMvcConfigurer 配置类
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
	
	// 组合WebMvcConfigurer配置的格式化器
	@Override
	protected void addFormatters(FormatterRegistry registry) {
		this.configurers.addFormatters(registry);
	}

	// 组合WebMvcConfigurer配置的拦截器
	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		this.configurers.addInterceptors(registry);
	}

	// 跨域处理
	@Override
	protected void addCorsMappings(CorsRegistry registry) {
		this.configurers.addCorsMappings(registry);
	}

	// 视图解析器
	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
		this.configurers.configureViewResolvers(registry);
	}

	// 参数解析
	@Override
	protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		this.configurers.addArgumentResolvers(argumentResolvers);
	}

	// 返回值处理器
	@Override
	protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		this.configurers.addReturnValueHandlers(returnValueHandlers);
	}

	// 转换器
	@Override
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.configureMessageConverters(converters);
	}

	...
	...

}

基本上,都是千篇一律的 this.configurers.addXXX(registry) 或者 this.configurers.configureXXX(registry) 等等。我们在看一下 WebMvcConfigurerComposite,真相就是遍历每一个 WebMvcConfigurer(包括 WebMvcAutoConfigurationAdapter 类的默认配置和用户自定义的),确保了所有 WebMvcConfigurer 都起作用!

class WebMvcConfigurerComposite implements WebMvcConfigurer {

	// 本质就是一个WebMvcConfigurer数组
	private final List<WebMvcConfigurer> delegates = new ArrayList<>();
	// 初始化 delegates
	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.delegates.addAll(configurers);
		}
	}

	@Override
	public void addFormatters(FormatterRegistry registry) {
		// 遍历每一个 WebMvcConfigurer,确保默认的和用户自定义的 都生效
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addFormatters(registry);
		}
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addInterceptors(registry);
		}
	}

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addArgumentResolvers(argumentResolvers);
		}
	}

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.configureMessageConverters(converters);
		}
	}
}

EnableWebMvc

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 很关键
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

DelegatingWebMvcConfiguration 是继承了 WebMvcConfigurationSupport。在最开始,WebMvcAutoConfiguration 自动配置类有一个很关键的条件是 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),也就是说,在容器中没有 WebMvcConfigurationSupport 的 bean 时,配置类才生效,这样就相当于我们全面接管了 SpringMVC。

我们平时扩展 WebMvc 配置,一般都不会在自定义配置类上标注这个注解。

同理,通过继承 WebMvcConfigurationSupport 来扩展 WebMvc 配置也会使 WebMvcAutoConfiguration 失效。

最后,断断续续,这篇文章写了我两天。写技术文章真的非常的耗时,人间值得。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值