SpringBoot对默认通过WebMvcAutoConfiguration这个类对SpringMVC进行配置。根据官方文档,它实现了以下功能:
1.包含ContentNegotiatingViewResolver和BeanNameViewResolver的视图解析器
通过@Bean注解自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染)
ContentNegotiatingViewResolver是如何解析视图的,下面是在这个类中解析视图的方法。
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;
}
}
...
}
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) {
//根据视图名称,即return的返回值和locale国家化返回视图,如果解析不了,返回null
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如何获得解析器,在initServletContext方法中可以发现
@Override
protected void initServletContext(ServletContext servletContext) {
//在这里BeanFactoryUtils通过类型判断,在容器中获得ViewResolver类型的解析器
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
...
}
这样,就很清晰了,ContentNegotiatingViewResolver通过initServletContext方法组合容器中所有ViewResolver的实现类,再通过resolveViewName解析获得视图。所以,如果想要自定义解析器,只需要实现ViewResolver接口,并通过@Bean放入容器即可,这样SpringBoot就自动会将其组合进来。
2.支持提供静态资源,包括对WebJars的支持
在WebMvcAutoConfiguration中有这样一个方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
...
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
//第一种静态资源:如果资源注册器中不包含/webjars/**映射,则添加
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
//第二种静态资源:获取静态资源文件映射
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//如果不包含,则添加,具体看下面分析
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
- 所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源。webjars:以jar包的方式引入静态资源。
- "/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射,this.resourceProperties.getStaticLocations()
如果想自定义静态资源位置,也可以在配置文件配置
- 欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext),
applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}
private Optional<Resource> getWelcomePage() {
//获取静态资源文件地址,即"classpath:/META-INF/resources/",
//"classpath:/resources/", "classpath:/static/", "classpath:/public/"
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
//在这些文件地址下寻找
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
//在资源文件地址下寻找index.html
return this.resourceLoader.getResource(location + "index.html");
}
- 所有的 **/favicon.ico 都是在静态资源文件下找;
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {
private final ResourceProperties resourceProperties;
private ResourceLoader resourceLoader;
...
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
//设置图标的资源映射
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
//设置路径地址
requestHandler.setLocations(resolveFaviconLocations());
return requestHandler;
}
private List<Resource> resolveFaviconLocations() {
//获取路径地址集合,即"classpath:/META-INF/resources/",
//"classpath:/resources/", "classpath:/static/", "classpath:/public/"
String[] staticLocations = getResourceLocations(this.resourceProperties.getStaticLocations());
List<Resource> locations = new ArrayList<>(staticLocations.length + 1)
Arrays.stream(staticLocations).map(this.resourceLoader::getResource).forEach(locations::add);
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
}
3.自动注册Converter , GenericConverter , Formatter beans.
- Converter:转换器; public String hello(User user):类型转换使用Converter
- Formatter :格式化器; 2017.12.17 => Date;
@Bean
@Override
public FormattingConversionService mvcConversionService() {
//创建一个转换服务类,根据配置的转换格式
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
//将转换服务器添加到注册器种
addFormatters(conversionService);
return conversionService;
}
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
/**
* Date format to use. For instance, `dd/MM/yyyy`.
*/
private String dateFormat;
...
}
可以在配置文件配置转换格式。
@Override
public void addFormatters(FormatterRegistry registry) {
//根据类型获取Converter类型组件集合,遍历添加
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
//根据类型获取GenericConverter类型组件集合,遍历添加
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
//根据类型获取Formatter类型组件集合,遍历添加
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
所以,自己添加的格式化器转换器,我们只需要放在容器中即可。
4.Support for HttpMessageConverters (see below).
HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User ---> Json;
这个messageConvertersProvider因为在构造函数中,所以会去IOC容器中获取,自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中 (@Bean,@Component)
5.Automatic registration of MessageCodesResolver (see below).
public MessageCodesResolver getMessageCodesResolver() {
//从mvcProperties中获取MessageCodesResolverFormat
if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
resolver.setMessageCodeFormatter(this.mvcProperties
.getMessageCodesResolverFormat());
return resolver;
}
return null;
}
所以可以看到,它是在定义错误代码生成规则。
6.Automatic use of a ConfigurableWebBindingInitializer bean (see below).
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
try {
return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
}
catch (NoSuchBeanDefinitionException ex) {
return super.getConfigurableWebBindingInitializer();
}
}
可以看到,它是从容器中获取ConfigurableWebBindingInitializer类型组件的,如果没有这样的组件,就调用父类方法初始化一个
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(mvcConversionService());
initializer.setValidator(mvcValidator());
MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
if (messageCodesResolver != null) {
initializer.setMessageCodesResolver(messageCodesResolver);
}
return initializer;
}
ConfigurableWebBindingInitializer的作用是将请求数据绑定到相关JavaBean;在其类中有一个initBinder方法就是来初始化绑定的。