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 PATCHDELETE,主要用与 form 表单的提交 PUTPATCHDELETE 类型的请求。

功能参考《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,功能是,为请求方法为 PUTPATCHDELETE 的请求解析表单数据并将其作为 Servlet 请求参数。不管是 form 表单还是客户端提交 PUTPATCHDELETE,此过滤器都可以发挥作用。

简单实践一下:

控制器方法:

@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 配置的前面。

WebMvcAutoConfigurationAdapterEnableWebMvcConfiguration 的关系,参考《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 {}

WebMvcAutoConfigurationAdapterWebProperties(对应 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 不会自动注入,也就是我们说的 全手动 模式。

WebMvcAutoConfigurationAdapterEnableWebMvcConfiguration 的关系,参考《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;
}

我们来简单看看 OptionalPathExtensionContentNegotiationStrategyOptionalPathExtensionContentNegotiationStrategyWebMvcAutoConfiguration 的内部类

其实这个内容协商策略也很简单,就是将 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.enabledspring.web.resources.chain.strategy.fixed.enabled

用于配置静态资源解析链

《SpringMVC 中的静态资源处理.md

只注入了一个 bean:ResourceChainResourceHandlerRegistrationCustomizer

ResourceChainResourceHandlerRegistrationCustomizer

以后有时间再研究