基于springboot1.5.19源码分析。
首先看看springboot参考手册是怎么说的:
29.1.1 Spring MVC Auto-configuration
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:
- Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans. - Support for serving static resources, including support for WebJars (covered later in this document)).
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans. - Support for
HttpMessageConverters
(covered later in this document). - Automatic registration of
MessageCodesResolver
(covered later in this document). - Static
index.html
support. - Custom
Favicon
support (covered later in this document). - Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration
class of type WebMvcConfigurer
but without @EnableWebMvc
. If you wish to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
, or ExceptionHandlerExceptionResolver
, you can declare a WebMvcRegistrationsAdapter
instance to provide such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
.
springboot为springmvc大多数的应用场景提供了自动配置。
自动配置添加了下面一些特点在spring的默认配置之上:
包含ContentNegotiatingViewResolver和BeanNameViewResolver这两个bean。
1、ContentNegotiatingViewResolver
打开WebMvcAutoConfiguration类,在类中搜索ContentNegotiatingViewResolver:
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(
beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
最上面的两个@Conditional注解表示在ViewResolver类被注册且没有注册过ContentNegotiatingViewResolver组件这个bean注册才会生效。方法第一行new了一个ContentNegotiatingViewResolver对象,我们点进去,在这个视图解析器类中,我们可以找到resolveViewName这个视图解析方法:
@Override
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;
}
}
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("No acceptable view found; returning null");
return null;
}
}
我们进入getCandidateViews这个方法:
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<View>();
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;
}
可以看到这些候选的视图解析器来自于this.viewResolvers,为了找到这个值,我们在当前类中找到了initServletContext这个方法:
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
for (int i = 0; i < viewResolvers.size(); i++) {
if (matchingBeans.contains(viewResolvers.get(i))) {
continue;
}
String name = viewResolvers.get(i).getClass().getName() + i;
getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
}
}
if (this.viewResolvers.isEmpty()) {
logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +
"'viewResolvers' property on the ContentNegotiatingViewResolver");
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
看这个方法,viewResolvers这个list数组的值来自于matchingBeans,而matchingBeans的值来自于
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
这个方法就是从spring容器中获取所有的视图解析器,也就是说我们可以写一个自定义的视图解析器只要实现ViewResovler接口,自定义的视图解析器也会被注入到spring容器中,测试一下:
自定义视图解析器:
@Configuration
public class ApplicationConfig {
public class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
/**
* 自定义视图解析器
*/
@Bean
@ConditionalOnMissingBean(MyViewResolver.class)
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
}
启动项目,在DispatcherServlet类的doDispatch方法打上断点,发送请求,看自定义的视图解析器是否生效:
2、静态资源映射
Support for serving static resources, including support for WebJars (covered later in this document)).
关于静态资源映射可以看我的另一篇文章:springboot对静态资源的映射规则
3、转换器和格式化器
Automatic registration of Converter
, GenericConverter
, and Formatter
beans.
自动注册了转换器和格式化器的组件。
@Bean @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat()); }
日期格式化器,只有在配置文件中配置了spring.mvc.date-format的值才会注册该组件。
@Override
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
private <T> Collection<T> getBeansOfType(Class<T> type) {
return this.beanFactory.getBeansOfType(type).values();
}
Converter
, GenericConverter
, and Formatter这三种组件都是从容器中获取的,我们如果想进行拓展就可以实现对应的接口就行了。
4、HttpMessageConverters
消息转换器
- Support for
HttpMessageConverters
(covered later in this document). - 支持HttpMessageConverters(本文档稍后介绍)。
HttpMessageConverter:springmvc用来转换http请求和响应,比如return对象,json形式响应
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
@Lazy HttpMessageConverters messageConverters,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConverters = messageConverters;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider
.getIfAvailable();
}
public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
this(Arrays.asList(additionalConverters));
}
public HttpMessageConverters(
Collection<HttpMessageConverter<?>> additionalConverters) {
this(true, additionalConverters);
}
从容器中获取所有的HttpMessageConverter。