一. 知识储备
了解SpringBoot的SpringMVC自动配置原理可以从官方文档和源码下手。如下:
- 我们打开SpringBoot官网,如下:
下面就可以见到SpringBoot默认配置的SpringMVC
1.1 ContentNegotiatingViewResolver & BeanNameViewResolver
这两个的作用是自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象,视图对象决定如何渲染(即转发或重定向)
ContentNegotiatingViewResolver作用:组合所有的视图解析器
分析:
我们在idea搜索WebMvcAutoConfiguration,如下:
在WebMvcAutoConfiguration中搜索ContentNegotiatingViewResolver
,看到有如下方法:
这个方法里面返回一个ContentNegotiatingViewResolver类型的对象,我们点进去这个ContentNegotiatingViewResolver类是干了什么的?,如下:
既然是视图解析器(ViewResolver),那么它是怎么解析视图的?我们在ContentNegotiatingViewResolver中找到解析视图的这个方法resolveViewName(),如下:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
可以看到有3处return语句
,显然主要起作用的是第一处的return语句,所以我们看它return的是什么?return的是一个View,这个View通过getBestView()获取的,而getBestView()根据candidateViews, requestedMediaTypes, attrs
获取,显然requestedMediaTypes,attrs是请求参数之类的,我们仔细看candidateViews。candidateViews是通过getCandidateViews
获取的。大概意思是获取候选视图对象。我们点击getCandidateViews()看看里面是怎么实现的。如下:
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
看到上面的方法中,拿到所有的视图对象进行挨个解析
总结:所以ContentNegotiatingViewResolver的作用是组合所有的视图解析器。
疑问:这些所有的视图解析器它是从哪获得的?
答:在ContentNegotiatingViewResolver里面有一个initServletContext()方法,就是这个方法从BeanFactoryUtils中获取所有的视图解析器。如下:
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
总结:从上面的结论ContentNegotiatingViewResolver组合所有的视图解析器,ContentNegotiatingViewResolver通过initServletContext()方法扫描所有视图解析器(即ViewResolver),进而组合所有视图解析器 所以我们定制视图解析器,只需在容器中添加一个视图解析器,ContentNegotiatingViewResolver自动将其组合进来。 详情看自己定制一个简单的ViewResolver视图解析器
1.2 自动配置相关的静态资源信息
有如下:
- 静态资源文件夹,webjars
- index.html静态首页访问
- 网页图标favicon.ico
1.3 Converter GenericConverter Formatter
1.3.1 Converter 转换器
比如有如下controller方法
public String addUser(User user){
...
}
解释: 假如浏览器传来的参数有一个是文本类型的18,假如这个18对应user的age(Integer类型),则Converter会将类型自动转换,将文本类型转为Integer类型。
1.3.2 Formatter 格式化器
有时日期的格式有2020/12/12或者2020.12.12或者2020-12-12,则我们需要将日期转换为一定的格式:
日期==》Date
在WebMvcAutoConfiguration中有注册格式化日期的方法,在WebMvcAutoConfiguration中搜getDateFormat,搜到如下方法:
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
getDateFormat()方法是来自this.mvcProperties
,我们查看它的类里面有什么,按ctrl+单击 点进去,来到这里
private final WebMvcProperties mvcProperties;
再继续点进去看看这个类里面有什么,如下:
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
...
/**
* Date format to use. For instance, `dd/MM/yyyy`.
*/
private String dateFormat;
...
所以我们可以在properties文件指定日期格式化的规则spring.mvc.dateFormat=xxx
,这样就设置了日期格式。
自己添加的格式化器转换器,我们只需放在容器中即可。
1.4 HttpMessageConverters
消息转换器:SpringMVC用来转换请求和响应的,比如我要将User对象以json的格式响应
在WebMvcAutoConfiguration中搜索HttpMessageConverters,可以看到只有一个构造方法的形参中有一个是HttpMessageConverters,还看到有一个泛型为HttpMessageConverters的变量。如下:
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
}
如果一个类只有一个有参的构造方法,那么构造方法里面的参数需要从容器中获取。所以HttpMessageConverters参数需要从容器获取。我们点进去看看HttpMessageConverters类型里面有什么,如下:
public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
this(Arrays.asList(additionalConverters));
}
public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
this(true, additionalConverters);
}
可以看到有两个构造方法的形参分别是HttpMessageConverter消息转换器的可变数组或可变集合。即要从容器中获取所有的消息转换器。
自己要给容器中添加HttpMessageConverter,只需要将自己的组件注册到容器中。(如使用@Bean或@Component
)
1.5 MessageCodesResolver
我们在WebMvcAutoConfiguration中搜索MessageCodesResolver,可以看到如下方法:
@Override
public MessageCodesResolver getMessageCodesResolver() {
if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
return resolver;
}
return null;
}
可以看到if条件中有this.mvcProperties.getMessageCodesResolverFormat()
,点击getMessageCodesResolverFormat()看这个方法,如下:
public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() {
return this.messageCodesResolverFormat;
}
再点击this.messageCodesResolverFormat
中,看看是什么,如下:
private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
点击进去看看Format
里面是什么,如下:
public static enum Format implements MessageCodeFormatter {
PREFIX_ERROR_CODE {
public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
return toDelimitedString(new String[]{errorCode, objectName, field});
}
},
POSTFIX_ERROR_CODE {
public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
return toDelimitedString(new String[]{objectName, field, errorCode});
}
};
至此,我们看到这个MessageCodesResolver是用来定义错误代码的规则的,默认的有两个规则,如下:
errorCode, objectName, field //第一种
objectName, field, errorCode //第二种
总结:MessageCodesResolver是用来定义错误代码规则的
1.6 ConfiguableWebBindingInitializer
在WebMvcAutoConfiguration中搜索ConfigurableWebBindingInitializer,如下:
@Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
FormattingConversionService mvcConversionService, Validator mvcValidator) {
try {
return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
}
catch (NoSuchBeanDefinitionException ex) {
return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);
}
}
看到第一个return
是从beanFactory中获取Bean,所以 我们可以配置一个ConfigurableWebBindingInitializer来替换默认的,并添加到容器中。
第二个return
是当出现NoSuchBeanDefinitionException
,从父类中获取ConfigurableWebBindingInitializer
,我们点进去看看是什么内容,如下:
/**
* Return the {@link ConfigurableWebBindingInitializer} to use for
* initializing all {@link WebDataBinder} instances.
*/
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
FormattingConversionService mvcConversionService, Validator mvcValidator) {
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(mvcConversionService);
initializer.setValidator(mvcValidator);
MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
if (messageCodesResolver != null) {
initializer.setMessageCodesResolver(messageCodesResolver);
}
return initializer;
}
可以从注释里面看到WebDataBinder
,即ConfigurableWebBindingInitializer是用来初始化WebDataBinder(绑定器),这方法是在初始化一个ConfigurableWebBindingInitializer
。
ConfigurableWebBindingInitializer
中有一个具体的initBinder()
的方法
public void initBinder(WebDataBinder binder) {
binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
if (this.directFieldAccess) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
if (this.conversionService != null) {
binder.setConversionService(this.conversionService);
}
if (this.propertyEditorRegistrars != null) {
PropertyEditorRegistrar[] var2 = this.propertyEditorRegistrars;
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
PropertyEditorRegistrar propertyEditorRegistrar = var2[var4];
propertyEditorRegistrar.registerCustomEditors(binder);
}
}
}
总结:ConfigurableWebBindingInitializer作用是用来将请求数据转换成JavaBean(将请求参数与controller方法中的Bean形参作数据绑定,而这个绑定又需要用到前面介绍的Converters类型转换器、Formatter格式化等等)
1.7 总结
SpringBoot中不仅仅只有以上的这些SpringMVC自动配置,还有很多自动配置。可以在依赖中查看到。如下:
这个org.springframework.boot.autoconfigure.web里面有web的自动场景,通常是那些xxxAutotConfiguration
。