Web 相关自动配置类源码解析 -WebMvcAutoConfiguration
Web 相关自动配置类源码解析 -WebMvcAutoConfiguration
源码版本:spring-boot-autoconfigure-2.7.5.jar
作用
WebMvcAutoConfiguration
基本上就对应着对 SpringMVC 的自动配置,也就是用自动配置代替以下代码,
@Configuration
//扫描组件
@ComponentScan("xyz.xiashuo.springmvcannotationconfig")
//开启MVC注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// ......
}
参考《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》
生效条件
// @AutoConfiguration 是 SpringBoot 2.7.0 才开始引入的注解 继承了 @AutoConfigureBefore 和 @AutoConfigureAfter ,主要用来配置 自动配置类的加载顺序
// 自动配置类的加载顺序在这些类后面
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
// Web类型必须是 SERVLET
@ConditionalOnWebApplication(type = Type.SERVLET)
// 类路径下必须存在这些类,这些类都在 org.springframework.web.servlet 下面
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 注意,如果用户没有自己注册 WebMvcConfigurationSupport 类型的bean,也就是没有使用 @EnableWebMvc 注解,此自动配置才生效,否则,不生效
// 参考 《SpringMVC-第十篇:基于注解配置SpringMVC.md》 的 @EnableWebMvc注解 小节
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {}
尤其注意一点:
如果用户没有自己注册 WebMvcConfigurationSupport
类型的 bean,也就是没有使用 @EnableWebMvc
注解,此自动配置才生效,否则,不生效,即对应着我们不需要 SpringBoot 的自动配置,全面接管 SpringMVC 的配置。
@EnableWebMvc
注解引入的WebMvcConfigurationSupport
类型的 bean 为DelegatingWebMvcConfiguration
参考《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》的
@EnableWebMvc注解
小节
自动配置了哪些 Bean
HiddenHttpMethodFilter - 手动注入
实际类型为 org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter
,功能是通过 Post 类型的请求结合一个自定义的请求参数,将请求方法转化为 PUT
、PATCH
、DELETE
,主要用与 form 表单的提交 PUT
、PATCH
、DELETE
类型的请求。
功能参考《SpringMVC- 第六篇:RESTful.md》的
如何put和delete请求呢?
小节
简单测试一下
注意,此 bean 不默认注入,需要手动配置注入
spring:
mvc:
hiddenmethod:
filter:
enabled: true
控制器方法
@RestController
@RequestMapping("/test")
public class WebMVCConfigController {
@PutMapping("/testPut")
public String TestFormContentFilter(@RequestParam("name") String name) {
return name;
}
}
发起请求

响应
FormContentFilter - 默认注入
实际类型为 org.springframework.boot.web.servlet.filter.OrderedFormContentFilter
,功能是,为请求方法为 PUT
、PATCH
和 DELETE
的请求解析表单数据并将其作为 Servlet 请求参数。不管是 form 表单还是客户端提交 PUT
、PATCH
、DELETE
,此过滤器都可以发挥作用。
简单实践一下:
控制器方法:
@RestController
@RequestMapping("/test")
public class WebMVCConfigController {
@PutMapping("/testPut")
public String TestFormContentFilter(@RequestParam("name") String name) {
return name;
}
}
发起的请求

因为 FormContentFilter
默认注入
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
// 此属性不配置也默认为true
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
因此我们手动禁用这个配置
spring:
mvc:
formcontent:
filter:
enabled: false
此时发起请求,会报错,因为无法正常解析请求参数(在 url 后缀中解析或者在格式为 application/x-www-form-urlencoded
的请求体中解析)
修改参数为 true,或者删掉这段配置,默认就是注入的。
spring:
mvc:
formcontent:
filter:
enabled: true
即可正常解析
WebMvcAutoConfigurationAdapter - 默认注入 - 弃用
WebMvcAutoConfigurationAdapter
是一个 Web 配置类,是多个 Web 配置中的其中一个,当用户需要进行半自动的 SpringMVC 配置的时候(不使用 @EnableWebMvc
注解,同时 SpringBoot 的默认配置跟用户的配置并存),用户配置的(实现 WebMvcConfigurer
接口)Web 配置类会跟这个 WebMvcAutoConfigurationAdapter
这个 Web 配置类共存,同时生效,因此这种半自动的配置方式也可以叫混合模式。而且需要注意,WebMvcAutoConfigurationAdapter
的位于众多 Web 配置中的第一个,排在所有用户自定义的 Web 配置的前面。
WebMvcAutoConfigurationAdapter
与EnableWebMvcConfiguration
的关系,参考《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》中的DelegatingWebMvcConfiguration
小节
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
// 引入Web核心配置类 EnableWebMvcConfiguration 具体细节请看 WebMvcAutoConfiguration的EnableWebMvcConfiguration类
// EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration,而 DelegatingWebMvcConfiguration 继承了 WebMvcConfigurationSupport
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
// 注意,此Web配置的order为0,也就是说,排在所有用户自定义的Web配置的前面
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
WebMvcAutoConfigurationAdapter
跟 WebProperties
(对应 spring.web
开头的配置)和 WebMvcProperties
(对应 spring.mvc
开头的配置)这两个属性类进行了绑定。同时注意 WebMvcAutoConfigurationAdapter
只有一个有参构造器,一个 bean 如果只有一个有参构造,那么所有的参数都会从容器中自动注入,不需要在构造器方法上使用 @Autowired
注解
这个在《Spring5-IOC 容器.docx》的
@Autowired 通过属性类型进行自动装配
小节中提到过
WebMvcAutoConfigurationAdapter
具体的配置内容具体做了什么,参考《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》的 SpringMVC常用配置项
小节,
我们可以注意到,WebMvcAutoConfigurationAdapter
对 web 的配置主要分两种方式,一种是重写方法 @Override
,一种是注册 bean,而注册 bean 的时候,大部分都配合了 @ConditionalOnMissingBean
注解,即,如果用户手动配置了此 bean,那么 SpringBoot 自动配置类就不帮用户配置此 bean。
内容有点多,接下来我们先看点常用的
configureMessageConverters
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 直接委托给 ObjectProvider.ifAvailable
this.messageConvertersProvider.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
this.messageConvertersProvider.ifAvailable
最终的效果是:获取容器中所有的 HttpMessageConverter
类型的 bean 和默认的 HttpMessageConverter
,注意,这个默认的 HttpMessageConverter
其实还是来自于 WebMvcConfigurationSupport#addDefaultHttpMessageConverters
方法,然后将这个 HttpMessageConverter
列表添加到 converters 中。
具体分析,messageConvertersProvider
是什么?这是在 WebMvcAutoConfigurationAdapter
的构造函数中,初始化的字段
// 所有这些参数,都会从IOC容器中查找对应类型的bean,然后注入到构造函数的参数中
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegis trationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
调试一下
我们注意到 messageConvertersProvider
的类型是 ObjectProvider<HttpMessageConverters>
,ObjectProvider
有多个实现类,从上图中我们可以发现,实现类为 DefaultListableBeanFactory$DependencyObjectProvider
,而且我们可以发现,DependencyObjectProvider
中保存的都是使用 DependencyObjectProvider
这个 bean 的位置的信息,即待注入的位置的信息,比如依赖 DependencyObjectProvider
的 bean 的名称,以及等待注入 DependencyObjectProvider
的位置。
ObjectProvider
接口是ObjectFactory
的一个变体,专门为注入点设计,专门用于各种注入,比如函数参数注入和属性注入,此参数允许通过编程进行可选性的处理和具有多个待注入 bean 时的处理。属于比@Autowired 高级一些的注入工具。
回到 configureMessageConverters
方法,我们开始看 DependencyObjectProvider#ifAvailable
,
public void ifAvailable(Consumer<Object> dependencyConsumer) throws BeansException {
// 获取容器中所有的 HttpMessageConverter 类型的 bean 和默认的HttpMessageConverter ,然后将其添加到 最终的 HttpMessageConverter 列表中
// dependency 的类型实际上是 HttpMessageConverters , HttpMessageConverters 是 HttpMessageConverter 的集合
Object dependency = this.getIfAvailable();
if (dependency != null) {
try {
dependencyConsumer.accept(dependency);
} catch (ScopeNotActiveException var4) {
}
}
}
DependencyObjectProvider#ifAvailable
调用了 DependencyObjectProvider#getIfAvailable
@Override
@Nullable
public Object getIfAvailable() throws BeansException {
try {
if (this.optional) {
return createOptionalDependency(this.descriptor, this.beanName);
}
else {
// 匿名内部类的写法
DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
@Override
public boolean isRequired() {
return false;
}
};
// 最终调用 doResolveDependency
return doResolveDependency(descriptorToUse, this.beanName, null, null);
}
}
catch (ScopeNotActiveException ex) {
// Ignore resolved bean in non-active scope
return null;
}
}
DependencyObjectProvider#getIfAvailable
将解析依赖的工作委托给 DefaultListableBeanFactory#doResolveDependency
,注意,这个方法是在 DependencyObjectProvider
的宿主类 DefaultListableBeanFactory
中。
在 DefaultListableBeanFactory#doResolveDependency
方法中,通过遍历 IOC 容器,发现在 HttpMessageConvertersAutoConfiguration
自动配置类中有一个名字叫 messageConverters
(其实就是 @Bean
注解修饰的方法名)的类型为 HttpMessageConverters
的 bean,将这个 bean 其初始化,然后返回
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
// 获取依赖描述中记录的依赖的类型,type为 class org.springframework.boot.autoconfigure.http.HttpMessageConverters
Class<?> type = descriptor.getDependencyType();
// value 为null
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
// multipleBeans 为 null
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
// 终于查找到符合要求的bean
// matchingBeans只有一个key,就是 messageConverters(bean的名称),value为 org.springframework.boot.autoconfigure.http.HttpMessageConverters (bean的类型)
// 这个bean在 HttpMessageConvertersAutoConfiguration 配置类中被注册
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
// 因为只有一个,那就直接使用,将key和value直接赋值给 autowiredBeanName 和 instanceCandidate
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
// 开始 实例化
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
// 开始实例化 bean名称(autowiredBeanName)为 messageConverters 类型(type) org.springframework.boot.autoconfigure.http.HttpMessageConverters 的 bean
// 最终调用 HttpMessageConvertersAutoConfiguration 中的 messageConverters 方法
// 具体的细节,请看 《SpringBoot-HttpMessageConverters自动配置类源码解析-HttpMessageConvertersAutoConfiguration.md》
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
// HttpMessageConvertersAutoConfiguration 中的 messageConverters 方法返回的bean被作为结果直接返回。
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
return result;
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
关于名为
messageConverters
类型HttpMessageConverters
这个 bean 的的分析,请看《SpringBoot-HttpMessageConverters 自动配置类源码解析 -HttpMessageConvertersAutoConfiguration.md》
最终在 DependencyObjectProvider#ifAvailable
方法中进行使用
public void ifAvailable(Consumer<Object> dependencyConsumer) throws BeansException {
// dependency 的 实际上就是 HttpMessageConvertersAutoConfiguration 中 messageConverters 方法 返回的 HttpMessageConverters 类型的bean
Object dependency = this.getIfAvailable();
// 如果 dependency 存在,则执行 dependencyConsumer的accept方法
// 当然,假如不存在,那么也不会报错,这就是 ObjectProvider#ifAvailable 方法的意义,有的话就执行啥操作,没有的话,就啥也不干,也不报错,非常方便
if (dependency != null) {
try {
dependencyConsumer.accept(dependency);
} catch (ScopeNotActiveException var4) {
}
}
}
这里我们也稍微了解了一下
ObjectProvider#ifAvailable
方法的意义,有的话就执行啥操作,没有的话,就啥也不干,也不报错,非常方便,我们在日常中也可以使用ObjectProvider
,关于ObjectProvider
的详细解析,请看《SpringBoot-IOC-ObjectProvider 详解.md》
最终,经过 WebMvcAutoConfigurationAdapter#configureMessageConverters
这个 Web 配置之后,WebMvcConfigurationSupport#messageConverters
将不为空,则不会再调用 WebMvcConfigurationSupport#addDefaultHttpMessageConverters
添加默认的 MessageConverter(实际上也不需要,在构造 HttpMessageConverters
的时候已经调用了一遍了。)
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
// 经过了 WebMvcAutoConfigurationAdapter 的 configureMessageConverters 之后,
// messageConverters 不再为空,不再调用 addDefaultHttpMessageConverters
// 不过没关系,在 WebMvcAutoConfigurationAdapter 的 configureMessageConverters 中,构造 HttpMessageConverters 时候,已经调用过一遍了
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
configureAsyncSupport
有时间再研究,TODO
configurePathMatch
有时间再研究,TODO
configureContentNegotiation
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
// 对应配置 spring.mvc.contentnegotiation.favor-path-extension 默认为 false
// 配置是否支持基于URL拓展名的内容协商
configurer.favorPathExtension(contentnegotiation.isFavorPathExtension());
// 对应配置 spring.mvc.contentnegotiation.favor-parameter 默认为 false
// 配置是否支持基于请求参数的内容协商
configurer.favorParameter(contentnegotiation.isFavorParameter());
if (contentnegotiation.getParameterName() != null) {
// 对应配置 spring.mvc.contentnegotiation.parameter-name 默认为 null
// 设置基于请求参数的内容协商生效时,请求参数的名称
configurer.parameterName(contentnegotiation.getParameterName());
}
// 对应配置 spring.mvc.contentnegotiation.media-types 默认为 null
// 设置内容协商的 MediaType 列表
Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
// 注意,这里不会覆盖默认 WebMvcConfigurationSupport#getDefaultMediaTypes 中默认添加的 文件拓展名跟 MediaType 的映射关系
mediaTypes.forEachmediaType;
}
ContentNegotiationConfigurer
中的所有配置项还有具体的源码分析,请看《SpringMVC-ContentNegotiation 内容协商.md》的 通过Java配置
小节
getMessageCodesResolver
有时间再研究,TODO
addFormatters
有时间再研究,TODO
addResourceHandlers - 资源处理的默认规则配置
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// resourceProperties 来自于 WebProperties 的 resources 配置,即 spring.web.resources 配置
// isAddMappings 对应配置 spring.web.resources.add-mappings, 默认为true,即默认是会主动添加资源处理的默认规则
// 当然你也可以把这个配置设置为false,不开启资源处理的默认规则
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
// 关于webjar的资源配置
// 排在默认的默认的静态资源位置的配置前面
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
// 默认的静态资源位置的配置
// this.mvcProperties.getStaticPathPattern() 中,对应的配置是 spring.mvc.static-path-pattern ,默认值是 "/**"
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
// this.resourceProperties.getStaticLocations() 中,如果用户没有设置 spring.web.resources.static-locations 则默认值是 { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }
// 因为资源的添加顺序就是查找顺序,所以,默认的资源查找路径的查找顺序,是
// "classpath:/META-INF/resources/"
// "classpath:/resources/"
// "classpath:/static/"
// "classpath:/public/"
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
// 已经添加了对URL路径的映射,就不再重复添加,以先添加的为主
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
customizer.accept(registration);
// resourceProperties 来自于 WebProperties 的 resources 配置,即 spring.web.resources 配置
// getCache() 对应配置 spring.web.resources.cache ,用于添加资源的缓存策略,
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
// 实际上是委托给 resourceHandlerRegistrationCustomizer ,也就是 ResourceHandlerRegistrationCustomizer , 也就是 WebMvcAutoConfiguration 后面注册的 ResourceChainCustomizerConfiguration类型的bean
// 主要功能为静态资源添加处理链配置,对应配置为 spring.web.resources.chain
customizeResourceHandlerRegistration(registration);
}
private Integer getSeconds(Duration cachePeriod) {
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
}
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
if (this.resourceHandlerRegistrationCustomizer != null) {
this.resourceHandlerRegistrationCustomizer.customize(registration);
}
}
注意缓存配置(spring.web.resources.cache
)和资源处理链的配置(spring.web.resources.chain
)。
参考
《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》中的
addResourceHandlers
小节《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》
InternalResourceViewResolver Bean
有时间再研究,TODO
BeanNameViewResolver Bean
有时间再研究,TODO
ContentNegotiatingViewResolver Bean
有时间再研究,TODO
RequestContextFilter Bean
有时间再研究,TODO
EnableWebMvcConfiguration - 默认注入
Web 应用的核心主配置类,当用户没有使用 @EnableWebMvc
注解,也就是没有自己注册 WebMvcConfigurationSupport
类型的 bean(@EnableWebMvc
注解引入的 WebMvcConfigurationSupport
类型的 bean 为 DelegatingWebMvcConfiguration
)的时候,SpringBoot 注册的 WebMvcConfigurationSupport
类型的 bean 也就是 EnableWebMvcConfiguration
才会生效,否则,EnableWebMvcConfiguration
不会自动注入,也就是我们说的 全手动
模式。
WebMvcAutoConfigurationAdapter
与EnableWebMvcConfiguration
的关系,参考《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》中的DelegatingWebMvcConfiguration
小节
重写 DelegatingWebMvcConfiguration 的 requestMappingHandlerAdapter
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 createRequestMappingHandlerAdapter
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 localeResolver
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 themeResolver
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 flashMapManager
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 mvcConversionService
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 mvcValidator
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 createRequestMappingHandlerMapping
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 getConfigurableWebBindingInitializer
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 createExceptionHandlerExceptionResolver
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 extendHandlerExceptionResolvers
有时间再研究,TODO
重写 DelegatingWebMvcConfiguration 的 mvcContentNegotiationManager
@Bean
@Override
@SuppressWarnings("deprecation")
public ContentNegotiationManager mvcContentNegotiationManager() {
// 先调用父类,也就是 WebMvcConfigurationSupport 中的 mvcContentNegotiationManager,
// 调用 `WebMvcConfigurationSupport#mvcContentNegotiationManager` 的时候会调用所有实现了 WebMvcConfigurer 的Web配置类的 configureContentNegotiation 方法
// 这其中,就包括 WebMvcAutoConfigurationAdapter
// `WebMvcConfigurationSupport#mvcContentNegotiationManager`的分析请参考《SpringMVC-第十篇:基于注解配置SpringMVC.md》中的`configureContentNegotiation`小节
ContentNegotiationManager manager = super.mvcContentNegotiationManager();
// 但是需要进行额外的操作,这个额外的操作就是替换掉 内容协商策略种的 PathExtensionContentNegotiationStrategy ,将其替换成 OptionalPathExtensionContentNegotiationStrategy
List<ContentNegotiationStrategy> strategies = manager.getStrategies();
ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator();
while (iterator.hasNext()) {
ContentNegotiationStrategy strategy = iterator.next();
if (strategy instanceof org.springframework.web.accept.PathExtensionContentNegotiationStrategy) {
// 将 PathExtensionContentNegotiationStrategy 替换成 OptionalPathExtensionContentNegotiationStrategy
// 同时将当前 PathExtensionContentNegotiationStrategy 实例作为构造器参数构造 OptionalPathExtensionContentNegotiationStrategy
iterator.set(new OptionalPathExtensionContentNegotiationStrategy(strategy));
}
}
return manager;
}
我们来简单看看 OptionalPathExtensionContentNegotiationStrategy
,OptionalPathExtensionContentNegotiationStrategy
是 WebMvcAutoConfiguration
的内部类
其实这个内容协商策略也很简单,就是将 PathExtensionContentNegotiationStrategy
包装了一层。然后中间加了个判断,根据配置判断是否跳过,如果配置不跳过,则委托给构造时传入的 PathExtensionContentNegotiationStrategy
实例。
static class OptionalPathExtensionContentNegotiationStrategy implements ContentNegotiationStrategy {
@SuppressWarnings("deprecation")
private static final String SKIP_ATTRIBUTE = org.springframework.web.accept.PathExtensionContentNegotiationStrategy.class.getName() + ".SKIP";
private final ContentNegotiationStrategy delegate;
OptionalPathExtensionContentNegotiationStrategy(ContentNegotiationStrategy delegate) {
// 构造的时候传入委托对象,一般就是 PathExtensionContentNegotiationStrategy 实例
this.delegate = delegate;
}
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
// 获取配置
Object skip = webRequest.getAttribute(SKIP_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (skip != null && Boolean.parseBoolean(skip.toString())) {
// 如果配置为true,则跳过
return MEDIA_TYPE_ALL_LIST;
}
// 如果配置不为true,则委托给 delegate,一般就是 PathExtensionContentNegotiationStrategy 实例
return this.delegate.resolveMediaTypes(webRequest);
}
}
welcomePageHandlerMapping - 添加 Bean - 欢迎页配置
// 不仔细研究了
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
// WelcomePageHandlerMapping 继承 AbstractHandlerMapping
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern());
// 设置拦截器
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
// 设置跨域规则
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
其中我们看 WelcomePageHandlerMapping
的构造方法
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
// 如果 spring.mvc.static-path-pattern 配置(或者默认配置)的是 /** ,才配置欢迎页,将欢迎页转发到index.html页面,否则不转发到index.html页面
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
setRootViewName("forward:index.html");
}
// 如果 spring.mvc.static-path-pattern 配置不是 /** ,就将欢迎页转发到index请求,也就是由控制器方法处理
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
我们可以来试一下自定义 spring.mvc.static-path-pattern
后如何先是自定义首页。
简单实践如下:TODO
ResourceChainCustomizerConfiguration - 手动注入
是否注入,看 @ConditionalOnEnabledResourceChain
注解依赖的配置,对应配置为 spring.web.resources.chain
下的配置,spring.web.resources.chain.strategy.content.enabled
、spring.web.resources.chain.strategy.fixed.enabled
用于配置静态资源解析链
《SpringMVC 中的静态资源处理.md》
只注入了一个 bean:ResourceChainResourceHandlerRegistrationCustomizer
ResourceChainResourceHandlerRegistrationCustomizer
以后有时间再研究