12. MVC自动配置原理
Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
12.1 功能
自动配置在Spring默认设置的基础上添加了以下功能:
- 视图解析器
- 支持静态资源文件夹的路径,以及webjars
- Converter:自动转换器,就是前端提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
- Formatter:格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象
- SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串
- 定义错误代码生成规则的
- 首页映射
- 图标自定义
- 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中
12.1.1 视图解析器分析
ContentNegotiatingViewResolver 内容协商视图解析器
自动配置了ViewResolver
-
进入
WebMvcAutoConfiguration.java
查找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使用其他视图解析器来定位视图,因此它具有较高的优先级 resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; }
-
ContentNegotiatingViewResolver.java
解析视图@Override @Nullable // 注解说明:@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 = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest()); if (requestedMediaTypes != null) { // 获取候选的视图对象 List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes); // 选择一个最适合的视图对象,然后把这个对象返回 View bestView = this.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"); // this.viewResolvers:遍历工具类(就是Bean)中所有的视图解析器 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; } protected void initServletContext(ServletContext servletContext) { // 从beanFactory工具中获取容器中所有视图解析器 // ViewRescolver.class 会将所有的视图解析器组合起来 Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values(); ViewResolver viewResolver; if (this.viewResolvers == null) { this.viewResolvers = new ArrayList(matchingBeans.size()); } // ............... }
结论:ContentNegotiatingViewResolver 这个视图解析器会将所有的视图解析器组合起来
12.1.2 自定义视图解析器
-
创建
MyMvcConfig.java
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean //放到bean中 public ViewResolver myViewResolver(){ return new MyViewResolver(); } //写一个静态内部类,视图解析器需要实现ViewResolver接口 private static class MyViewResolver implements ViewResolver{ @Override public View resolveViewName(String s, Locale locale) throws Exception { return null; } } }
-
使用调试查看解析器是否起作用
给
DispatcherServlet
中的doDispatch()
方法加个断点 -
启动项目,然后随便访问一个页面,看一下Debug信息,找到this
找到视图解析器,可以看到里面有自定义的视图解析器
所以,如果想要使用自己定制化的东西,只需要给容器中添加这个组件就可以了
12.1.3 格式化转换器
此处源码分析以Spring Boot2.3.3版本为准
-
进入
WebMvcAutoConfiguration.java
查找 formatters@Bean @Override public FormattingConversionService mvcConversionService() { Format format = this.mvcProperties.getFormat(); WebConversionService conversionService = new WebConversionService(new DateTimeFormatters() .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService; }
-
进入 mvcProperties ,找到 DateFormat
@Deprecated @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date") public String getDateFormat() { return this.format.getDate(); } @Deprecated public void setDateFormat(String dateFormat) { this.format.setDate(dateFormat); }
-
format 内部集成了时间、日期、年月日时分秒,在我们的配置文件中可以修改它们
# 示例 spring.mvc.format.date=dd/MM/yyyy
12.2 修改Spring Boot的默认配置
此处源码分析以Spring Boot2.3.3版本为准
/**
* 自定义类扩展MVC功能
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发生/neo,会跳转到test页面
registry.addViewController("/neo").setViewName("test");
}
}
-
WebMvcAutoConfiguration
是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
-
类上面有个注解,在做其他自动配置时会自动导入
@Import(EnableWebMvcConfiguration.class)
-
EnableWebMvcConfiguration
继承了一个父类:DelegatingWebMvcConfiguration.java
,其中有这样一个方法public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); //从容器中获取所有的webmvcConfigurer @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } // 这就是示例中设置过的viewController @Override protected void addViewControllers(ViewControllerRegistry registry) { this.configurers.addViewControllers(registry); } }
-
再进入内部会跳转到
WebMvcConfigurerComposite
这个类,它实现了这个方法class WebMvcConfigurerComposite implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addViewControllers(registry); } } }
结论:所有的WebMvcConfiguration都会起作用,除了Spring自己的配置类,自定义的配置类也会被调用
12.3 全面接管SpringMVC
只需要在配置类中加一个@EnableWebMvc
注解,就可以使所有的SpringMVC自动配置全部失效
12.3.1 访问index.html
不加注解之前:
加上注解之后:
12.3.1 分析
-
进入注解内部
@Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
-
进入
DelegatingWebMvcConfiguration
,它继承了WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { // ... }
-
再进入
WebMvcAutoConfiguration
,类上面有一个注解// 这个注解会判断容器中有没有这个组件,没有的话,自动配置类才会生效 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
结论:因为@EnableMvc导入了WebMvcConfigurationSupport
所以Spring的自动配置就失效了