SpringMVC的自动配置参见官方文档https://docs.spring.io/spring-boot/docs/2.1.18.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-auto-configuration
Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。
自动配置在Spring的默认值之上添加了以下功能:
- 包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
。 - 支持提供静态资源,包括对WebJars的支持。
- 自动注册
Converter
,GenericConverter
和Formatter
等类。 - 支持
HttpMessageConverters
。 - 自动注册
MessageCodesResolver
。 - 静态
index.html
支持。 - 定制
Favicon
支持。 - 自动使用
ConfigurableWebBindingInitializer
bean。
如果您想保留Spring Boot MVC功能并想添加其他[MVC配置](拦截器,格式化程序,视图控制器和其他功能),则可以添加自己@Configuration
的type类WebMvcConfigurer
但不添加 @EnableWebMvc
。如果您希望提供或自定义实例RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
,ExceptionHandlerExceptionResolver
,WebMvcRegistrationsAdapter
等,则可以声明一个实例来提供此类组件。
如果您想完全控制Spring MVC,可以使用添加自己的@Configuration
注释@EnableWebMvc
。
这里扩展Spring MVC不要添加@EnableWebMvc,而完全接管就要添加,是什么原因呢?
其实从Springboot自动配置原理出发我们大致也能知道,应该就是添加了@EnableWebMvc会使WebMvcAutoConfiguration失效。
那么到底是不是呢?我们来看看源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@ EnableWebMvc注解的作用就是导入了DelegatingWebMvcConfiguration这个类
WebMvcAutoConfiguration上面有这样一个条件,就是容器没有WebMvcConfigurationSupport这个类是才会生效
那和@ EnableWebMvc又有啥关系呢?继续看DelegatingWebMvcConfiguration这个类:
wtf!?DelegatingWebMvcConfiguration是WebMvcConfigurationSupport的子类!
所以说 @EnableWebMvc注解可不能乱加,否则整个SpringMVC配置都得我们自己写。
我们以前用SpringMVC的一个核心就是视图解析器,Springboot中到底是怎么应用的呢?
打开WebMvcAutoConfiguration,找到如下方法:
@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
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
点进ContentNegotiatingViewResolver这个类中看看它是如何解析视图的
@Nullable//说明参数可以为null
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;
}
}
...
}
点进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);
}
...
}
}
可以发现,这里它就是拿到ViewResolvers这个属性(视图解析器),然后开始遍历。那么这里的视图解析器是在哪里获取到的呢???找找这个ViewResolvers属性。
@Override
protected void initServletContext(ServletContext servletContext) {
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器组合
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);
}
}
}
...
}
看到ApplicationContext是不是很熟悉?!
原来就是从容器中去获取到的视图解析器,那么我们要实现自己的视图解析器也是这个逻辑。
点开ViewResolver,原来是个接口
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
我们实现ViewResolver接口,并将自己的视图解析器加入容器中即可生效
@Bean
public ViewResolver getMyViewResolver(){
return new MyViewResolver();
}
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
如何查看是否生效呢?可以去DispatcherServlet中的doDispatch方法打断点调试,因为所有的请求都会走这个方法。
1.启动应用
2.从浏览器发起请求
3.查看控制台信息
自定义的视图解析器已然生效!
类似的,如果我们要自定义其他的一些组件,也是如此,直接将写好的组件加入容器中就可以了。