官方文档相关说明
在进行项目编写前,我们还需要知道SpringBoot对SpringMVC还做了哪些配置,包括如何扩展,如何定制
我们先去看看SpringBoot的官方文档,点击此处查看
If you want to build servlet-based web applications, you can take advantage of Spring Boot’s auto-configuration for Spring MVC or Jersey.
如果您想构建基于 servlet 的 Web 应用程序,您可以利用 Spring Boot 的 Spring MVC 或 Jersey 自动配置。
The Spring Web MVC framework (often referred to as “Spring MVC”) is a rich “model view controller” web framework. Spring MVC lets you create special @Controller
or @RestController
beans to handle incoming HTTP requests. Methods in your controller are mapped to HTTP by using @RequestMapping
annotations.
Spring Web MVC 框架(通常称为“Spring MVC”)是一个丰富的“模型视图控制器”Web 框架。Spring MVC 允许您创建特殊的@Controller
或@RestController
bean 来处理传入的 HTTP 请求。控制器中的方法通过使用@RequestMapping
注解映射到 HTTP。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
Spring Boot 为 Spring MVC 提供了自动配置,它可以很好地与大多数应用程序一起工作。
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. - Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).
自动配置在Spring默认设置的基础上添加了以下功能:
- 包括
ContentNegotiatingViewResolver
和BeanNameViewResolver
这两个视图解析器。 - 支持提供静态资源文件夹的路径,包括对 WebJars 的支持(本文档后面会介绍)。
- 自动注册
Converter
、(这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为 int类型)GenericConverter
和Formatter
(格式化器,比如页面给我们了一个2022-1-1,它会给我们自动格式化为Date对象)bean。 - 支持
HttpMessageConverters
(SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串)(本文档稍后介绍)。 - 自动注册
MessageCodesResolver
(用来定义错误代码生成规则的)(本文档稍后介绍)。 - 静态
index.html
支持。 -
ConfigurableWebBindingInitializer
bean( 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中)的自动使用(本文档稍后会介绍)。
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration
class of type WebMvcConfigurer
but without @EnableWebMvc
.
如果您想保留 Spring Boot MVC 功能并进行更多MVC 自定义(拦截器、格式化程序、视图控制器和其他功能),您可以添加自己的@Configuration
的类,类型为WebMvcConfigurer
但不添加 @EnableWebMvc
。
If you want to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
, or ExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations
and use it to provide custom instances of those components.
如果您想提供RequestMappingHandlerMapping``RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
的自定义实例,并且仍然保留 Spring Boot MVC 自定义,则可以声明一个类型为WebMvcRegistrations
的 bean并使用它来提供这些组件的自定义实例。
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
, or alternatively add your own @Configuration
-annotated DelegatingWebMvcConfiguration
as described in the Javadoc of @EnableWebMvc
.
如果你想完全控制 Spring MVC,你可以添加你自己的@Configuration
并用@EnableWebMvc
进行 注释,或者添加你自己的@Configuration
带注释的 DelegatingWebMvcConfiguration
,如@EnableWebMvc
。
ContentNegotiatingViewResolver
现在我们就来看看这个由 SpringBoot 自动配置了 SpringMVC 的ContentNegotiatingViewResolver 内容协商视图解析器,这就是我们之前学习的SpringMVC的视图解析器,它会根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们再来看看源码,找到 WebMvcAutoConfiguration 中的 ContentNegotiatingViewResolver,发现这个 ContentNegotiatingViewResolver 是 WebMvcAutoConfigurationAdapter 类的 viewResolver方法返回的结果
@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使用所有其他视图解析器来定位
// 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;
}
我们点进 ContentNegotiatingViewResolver 这个类看看,找到对应的解析视图的代码
//参数可为null
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//获取RequestContextHolder的请求属性
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
//声明断言
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//获取ContentNegotiatingViewResolver类的媒体类型
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;
}
}
......
}
我们点进 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");
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
Iterator var8 = requestedMediaTypes.iterator();
while(var8.hasNext()) {
MediaType requestedMediaType = (MediaType)var8.next();
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
Iterator var11 = extensions.iterator();
while(var11.hasNext()) {
String extension = (String)var11.next();
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;
}
可以看到它是把所有的视图解析器拿来,进行while循环,挨个解析
所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的
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());
Iterator var3 = matchingBeans.iterator();
while(var3.hasNext()) {
viewResolver = (ViewResolver)var3.next();
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
} else {
for(int i = 0; i < this.viewResolvers.size(); ++i) {
viewResolver = (ViewResolver)this.viewResolvers.get(i);
if (!matchingBeans.contains(viewResolver)) {
String name = viewResolver.getClass().getName() + i;
this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
}
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
我们可以自己给容器中添加一个视图解析器,这个类就会帮我们自动的将它组合进来
现在在我们的主程序中写一个视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
怎么看我们自己写的视图解析器有没有起作用呢?我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中
然后Debug启动我们的项目,随便访问一个页面,看一下Debug信息
找到this
找到视图解析器,我们自己定义的就在这里了
如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了,剩下的事情SpringBoot就会帮我们做了
修改SpringBoot的默认配置
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解。我们新建一个包叫config,写一个MyMvcConfig类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/test").setViewName("test");
}
}
测试一下发现确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
我们可以去分析一下原理
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
这个父类中有这样一段代码:
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当做参考
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
发现它调用了一个
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我 们配置的
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用
全面接管SpringMVC
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置
只需在我们的配置类中要加一个@EnableWebMvc
如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,当然,我们开发中不推荐使用全面接管SpringMVC
为什么加了一个注解,自动配置就失效了!我们看下源码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
我们发现它导入了一个 DelegatingWebMvcConfiguration.class ,点进去发现这个类继承了一个父类 WebMvcConfigurationSupport
再回到WebMvcAutoConfiguration这个类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
......
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了,而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!