SpringBoot3 基础教程之三 Web 开发

大纲

前言

本章节所需的案例代码,可以直接从 GitHub 下载对应章节 spring-boot3-04

自动配置

自动配置的原理

  • Web 场景的自动配置原理
    • 1、导入 spring-boot-starter-web,会导入 spring-boot-starter,也就会导入 spring-boot-autoconfigure 包。
    • 2、spring-boot-autoconfigure 包里面有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定了应用启动时所有要加载的自动配置类。
    • 3、@EnableAutoConfiguration 会自动地将上面文件里写的所有自动配置类都导入进来,同时 xxxAutoConfiguration 是有声明条件注解的,目的是按需加载。
    • 4、xxxAutoConfiguration 往容器中导入一堆组件,这些组件都是从 xxxProperties 中获取属性值。
    • 5、xxxProperties 又是和配置文件进行了绑定。
  • Web 场景的所有自动配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
  • Web 场景下配置文件的前缀配置项说明
    • 1、服务器的配置 server
    • 2、Web 场景通用配置 spring.web
    • 3、Spring MVC 的所有配置 spring.mvc
    • 4、文件上传配置 spring.servlet.multipart

自动配置的默认效果

SpringBoot Web 场景自动配置的效果如下:

  • 支持静态 index.html
  • 支持默认的静态资源处理机制,静态资源放在 static 文件夹下即可直接访问
  • 包含了 ContentNegotiatingViewResolverBeanNameViewResolver 组件,用于视图解析
  • 自动注册了 ConverterGenericConverterFormatter 组件,适配了常见的数据类型转换和格式化需求
  • 支持 HttpMessageConverters,可以方便返回 JSON 等数据类型
  • 注册 MessageCodesResolver,方便国际化及错误消息处理
  • 自动使用 ConfigurableWebBindingInitializer 实现消息处理、数据绑定、类型转化、数据校验等功能

重点知识

  • 如果想保持 SpringBoot MVC 的默认配置,并且自定义更多的 MVC 配置(如 interceptors,formatters,view controllers 等),可以使用 @Configuration 标注一个配置类,让配置类实现 WebMvcConfigurer 接口,并不要使用 @EnableWebMvc 注解。
  • 如果想保持 SpringBoot MVC 的默认配置,但要自定义核心组件的实例(如 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver),往容器中放一个 WebMvcRegistrations 组件,并不要使用 @EnableWebMvc 注解。
  • 如果想全面接管 SpringBoot MVC 的配置,可以使用 @Configuration 标注一个配置类,让配置类实现 WebMvcConfigurer 接口,并加上 @EnableWebMvc 注解即可。

静态资源

默认静态资源规则

静态资源映射

静态资源映射规则都在 WebMvcAutoConfiguration 自动配置类中进行了定义。

  • 访问 /webjars/** 路径就会去 classpath:/META-INF/resources/webjars/ 下找资源,一般用于访问通过 Maven 引入的第三方前端组件(如 Vue、Bootstrap)
  • 访问 /** 路径就会去静态资源默认的四个位置找资源,包括 classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/

静态资源缓存

所有静态资源都定义了缓存规则,浏览器访问过一次,就会缓存一段时间,但此功能的参数无默认值。

  • cachePeriod:缓存周期,多久不用找服务器要新的,默认值 0s,以秒为单位
  • cacheControl: HTTP 缓存控制,参照 Mozilla 文档
  • useLastModified:是否使用 Last-Modified 头,默认值 false,配合 HTTP Cache 规则使用
  • 所有缓存参数的配置,都可以通过配置文件里的 spring.web 前缀配置项指定(如下)
1
2
3
4
5
6
7
8
# 缓存周期(秒)
spring.web.resources.cache.period=3600

# HTTP 缓存控制(秒),浏览器第一次请求服务器时,服务器会告诉浏览器此资源缓存7200秒,即7200秒以内访问此资源的所有请求都不会发送给服务器
spring.web.resources.cache.cachecontrol.max-age=7200

# 是否使用 Last-Modified 头,对比服务器和浏览器的资源是否有变化,如果资源没有变化,服务器会返回304码,此时浏览器会使用本地缓存中的资源
spring.web.resources.cache.use-last-modified=true

Favicon 图标

  • 在默认的四个静态资源路径下查找 favicon.ico

欢迎页面

欢迎页面的映射规则在 WebMvcAutoConfiguration 中进行了定义:

  • 首先会在默认的四个静态资源路径下查找 index.html
  • 如果静态资源路径下找不到,就会在 templates 目录下找 index 模板页面

自定义静态资源规则

配置方式

  • spring.mvc:配置静态资源访问路径的前缀
  • spring.web:配合静态资源目录、静态资源策略 (开启映射、处理链、缓存规则)、国际化的区域信息
1
2
3
4
5
6
7
8
# 自定义静态资源访问路径的前缀
spring.mvc.static-path-pattern=/static/**

# 自定义webjars访问路径的前缀
spring.mvc.webjars-path-pattern=/webjars/**

# 自定义静态资源文件夹的位置
spring.web.resources.static-locations=classpath:/static/,classpath:/public/,classpath:/asset/

提示

  • 当不使用 spring.mvc.static-path-pattern 自定义静态资源访问路径的前缀时,静态资源默认的访问路径示例是 http://127.0.0.1:8080/backgrond.png
  • 当使用 spring.mvc.static-path-pattern=/static/** 自定义静态资源访问路径的前缀时,静态资源的访问路径示例是 http://127.0.0.1:8080/static/backgrond.png

代码方式

提示

  • 如果希望完全禁用 SpringBoot MVC 的自动配置,可以在配置类上添加 @EnableWebMvc 注解,此时相当于采用全手动的方式配置 MVC。
  • 第一种写法:实现 WebMvcConfigurer 接口,同样可以自定义静态资源规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.TimeUnit;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 保留默认的配置规则,并添加新的配置规则
registry
// 自定义静态资源访问路径的前缀
.addResourceHandler("/static/**")
// 自定义静态资源文件夹的位置
.addResourceLocations("classpath:/static/", "classpath:/public/", "classpath:/asset/")
// 自定义HTTP缓存控制
.setCacheControl(CacheControl.maxAge(7200, TimeUnit.SECONDS));
}

}
  • 第二种写法:使用 @Bean 注解,定义 WebMvcConfigurer 组件,同样可以自定义静态资源规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.TimeUnit;

@Configuration
public class WebConfiguration {

@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 保留默认的配置规则,并添加新的配置规则
registry
// 自定义静态资源访问路径的前缀
.addResourceHandler("/static/**")
// 自定义静态资源文件夹的位置
.addResourceLocations("classpath:/static/", "classpath:/public/", "classpath:/asset/")
// 自定义HTTP缓存控制
.setCacheControl(CacheControl.maxAge(7200, TimeUnit.SECONDS));
}
};
}

}

错误处理

默认机制

SpringBoot 错误处理的自动配置都在 ErrorMvcAutoConfiguration 中,两大核心机制:

  • 1、SpringBoot 会自适应处理错误,响应页面或 JSON 数据给客户端
  • 2、SpringMVC 的错误处理机制依然保留,SpringMVC 处理不了的,才会交给 SpringBoot 进行处理

  • 发生错误以后,请求转发给 /error 路径,SpringBoot 在底层写好一个 BasicErrorController 组件,专门处理这个请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 返回HTML
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

/**
* 返回 ResponseEntity,JSON
*/
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
  • 错误页面是这么解析到的
1
2
3
4
// 解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
// 如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  • 容器中专门有一个错误视图解析器
1
2
3
4
5
6
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
  • SpringBoot 解析自定义错误页的默认规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
  • 容器中有一个默认的名为 error 的 View,提供了默认错误页面的功能
1
2
3
4
5
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
  • 封装了 JSON 格式的错误信息
1
2
3
4
5
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
  • 错误页面的解析规则
    • 1、解析一个错误页
      • a. 如果发生了 500、404、503、403 这些错误
        • ⅰ. 如果有模板引擎,模板文件默认在 classpath:/templates/error/精确码.html
        • ⅱ. 如果没有模板引擎,在静态资源文件夹下找 精确码.html
      • b. 如果匹配不到 精确码.html 这些精确的错误页,就去找 5xx.html4xx.html 模糊匹配
        • ⅰ. 如果有模板引擎,模板文件默认在 classpath:/templates/error/5xx.html
        • ⅱ. 如果没有模板引擎,在静态资源文件夹下找 5xx.html
    • 2、如果模板引擎路径 templates 下有 error.html 页面,就直接渲染并返回给客户端

自定义错误响应

  • 自定义 JSON 响应,使用 @ControllerAdvice + @ExceptionHandler 进行统一异常处理
1
2
3
4
5
6
7
8
9
10
@ControllerAdvice
public class GlobalExceptionHandler {

@ResponseBody
@ExceptionHandler(Exception.class)
public R handleException(Exception exception) {
return R.error(exception.getMessage());
}

}
  • 自定义页面响应,根据 SpringBoot 的错误页面解析规则,自定义错误页面。

错误处理的最佳实践

  • 前后端分离开发模式
    • 后台发生的所有错误,通过 @ControllerAdvice + @ExceptionHandler 进行统一异常处理
  • 前后端不分离模式(服务端模板页面渲染)
    • 一些不可预知的错误,如使用 HTTP 码表示的服务器或客户端错误
      • classpath:/templates/error/ 下面,存放常用精确的错误码页面,如 500.html404.html
      • classpath:/templates/error/ 下面,存放通用模糊匹配的错误码页面,如 5xx.html4xx.html
    • 发生业务错误
      • 核心业务的每一种错误,都应该通过代码控制,跳转到定制的错误页
      • 通用业务,可以通过 classpath:/templates/error.html 错误页面,显示通用的错误信息
  • 在模板页面或者服务端返回的 JSON 数据中,可用的 Model 数据如下:

嵌入式容器

Servlet 容器指的是管理、运行 Servlet 组件(ServletFilterListener)的环境,一般指 Web 服务器。

自动配置原理

  • SpringBoot 默认使用嵌入的 Tomcat 作为 Servlet 容器
  • 嵌入式容器的自动配置类是 ServletWebServerFactoryAutoConfigurationEmbeddedWebServerFactoryCustomizerAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景
  • 绑定了 ServerProperties 配置类,所有和服务器相关的配置都使用 server 作为开始前缀
  • ServletWebServerFactoryAutoConfiguration 默认导入了嵌入式的三大服务器,包括 TomcatJettyUndertow
    • 导入 TomcatJettyUndertow 时都有条件注解,系统中有对应的类才会生效(也就是导了包)
    • 在默认情况下,Tomcat 的配置会生效,SpringBoot 往容器中放了 TomcatServletWebServerFactory 组件
    • 往容器中放一个 Web 服务器工厂 ServletWebServerFactory 后,可以创建 Web 服务器
    • Web 服务器工厂都有一个功能,可以调用 getWebServer() 获取 Web 服务器
    • TomcatServletWebServerFactory 创建了 Tomcat Web 服务器
  • ServletWebServerApplicationContext IOC 容器在启动的时候,会调用 ServletWebServerFactory 创建 Web 服务器
  • Spring 容器刷新(启动)的时候,会预留一个时机,调用 onRefresh() 刷新子容器
  • refresh() 容器刷新,十二大步的刷新子容器会调用 onRefresh()
1
2
3
4
5
6
7
8
9
10
11
12
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

}
1
2
3
4
5
6
7
8
9
10
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}

总结

  • Web 场景的 Spring 容器启动,在调用 onRefresh() 的时候,会调用创建 Web 服务器的方法。
  • Web 服务器的创建是通过 WebServerFactory 实现的,容器中又会根据条件注解,启动相关的服务器配置,默认 EmbeddedTomcat 会往容器中放一个 TomcatServletWebServerFactory 组件,导致项目启动后,自动创建出 Tomcat 服务器。

自定义嵌入式容器

  • 嵌入式三大容器有 TomcatJettyUndertow,SpringBoot 默认使用 Tomcat 作为容器。若希望切换到其他容器,只需要更改 Maven 的配置即可,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<properties>
<!-- 定义Servlet版本 -->
<servlet-api.version>3.1.0</servlet-api.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除Tomcat依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- 使用Undertow容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
  • 最佳实践
    • 修改以 server 为开始前缀的相关配置,这样就可以修改服务器参数
    • 通过往容器中放一个 ServletWebServerFactory 组件,来禁用掉 SpringBoot 默认引入的 Web 服务器工厂,这样就可以实现自定义任意的嵌入服务器。

底层源码浅析

Web MVC 自动配置原理

自动配置生效的条件

1
2
3
4
5
6
7
8
9
10
11
12
/**
* WebMvcAutoConfiguration 的底层源码
*/
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) // 在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) // 如果是 Web 应用就生效,类型: SERVLET、REACTIVE
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 容器中没有这个 Bean 才生效,默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) // 优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {

}

自动配置实现的效果

效果一:往容器中放了两个 Filter

  • HiddenHttpMethodFilter:页面表单提交 REST 请求(支持 GET、POST、PUT、DELETE 方法)
  • FormContentFilter: 表单内容 Filter,GET(数据放 URL 后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略

效果二:往容器中放了 WebMvcConfigurer 组件,给 Spring MVC 添加各种定制功能,所有功能最终都会和配置文件的内容进行绑定

  • WebMvcProperties: 绑定了以 spring.mvc 为开始前缀的配置项
  • WebProperties: 绑定了以 spring.web 为开始前缀的配置项

WebMvcConfigurer 接口

1
2
3
4
5
6
7
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class) // 额外导入了其他配置
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{

}

静态资源映射规则的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}

});
}
}

}

规则一:访问 /webjars/** 路径就会去 classpath:/META-INF/resources/webjars/ 下找资源,一般用于访问通过 Maven 引入的第三方前端组件(如 Vue、Bootstrap)
规则二:访问 /** 路径就会去静态资源默认的四个位置找资源,包括 classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/
规则三:静态资源默认都有缓存规则的设置

  • 所有缓存参数的配置,都可以通过配置文件里的 spring.web 前缀配置项指定
  • cachePeriod:缓存周期,多久不用找服务器要新的,默认值 0s,以秒为单位
  • cacheControl: HTTP 缓存控制,参照 Mozilla 文档
  • useLastModified:是否使用 Last-Modified 头,默认值 false,配合 HTTP Cache 规则使用
1
2
3
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());

提示

当浏览器访问了一个静态资源文件 index.js,如果在服务器中这个资源文件没有发生变化,那么在用户下次访问的时候,就可以直接让浏览器用本地缓存中的资源文件,而不用给服务器发送请求。

EnableWebMvcConfiguration 的源码

EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration,而 DelegatingWebMvcConfiguration 则继承了 WebMvcConfigurationSupport

1
2
3
4
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

}
1
2
3
4
5
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

}

SpringBoot 默认会往容器中放入 WebMvcConfigurationSupport 组件,如果开发者注册了 WebMvcConfigurationSupport 组件,那么 SpringBoot 的 WebMvcAutoConfiguration 组件就会失效。这相当于禁用 SpringBoot 的自动配置,采用全手动的方式配置 MVC。

1
2
3
4
5
6
7
8
9
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 容器中没有这个 Bean 才生效,默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {

}
  • 欢迎页面的查找规则
    • HandlerMapping:根据请求路径找到能处理请求的 Handler
    • WelcomePageHandlerMapping
      • 访问 /** 路径下的所有请求,都会去默认的四个静态资源路径下查找页面,这也适用于欢迎页面
      • 只要在任意一个静态资源路径下有一个 index.html 页面,项目启动后就可以正常访问它

为什么定义 WebMvcConfigurer 就能配置底层行为

  • WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration 配置类
  • EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration,且两者都会生效
  • DelegatingWebMvcConfiguration 利用 DI (自动注入) 将容器中所有的 WebMvcConfigurer 都注入进来
  • 别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它自身则调用所有 WebMvcConfigurer 的底层配置方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* DelegatingWebMvcConfiguration 的底层源码
*/
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}

@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}

@Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
this.configurers.configureContentNegotiation(configurer);
}

...

}

全面接管 Spring MVC

SpringBoot 默认配置好了 Spring MVC 的所有常用特性,如果需要全面接管 Spring MVC 的所有配置并禁用默认配置,仅需要编写一个 WebMvcConfigurer 配置类,并标注 @EnableWebMvc 即可。

WebMvcAutoConfiguration 自动配置了哪些规则

Spring MVC 自动配置场景给我们配置了如下所有默认行为:

  • WebMvcAutoConfiguration Web 场景的自动配置类

    • 支持 RESTful 的 Filter:HiddenHttpMethodFilter
    • 支持非 POST 请求体携带数据:FormContentFilter
    • 导入 EnableWebMvcConfiguration 配置类后:
      • RequestMappingHandlerAdapter
      • WelcomePageHandlerMapping: 欢迎页功能支持(模板引擎目录、静态资源目录放 index.html 页面),项目访问 / 就默认展示这个页面
      • RequestMappingHandlerMapping:找每个请求由谁处理的映射关系
      • ExceptionHandlerExceptionResolver:默认的异常解析器
      • LocaleResolver:国际化解析器
      • ThemeResolver:主题解析器
      • FlashMapManager:临时数据共享
      • FormattingConversionService: 数据格式化 、类型转化
      • Validator:JSR303 提供的数据校验功能
      • WebBindingInitializer:请求参数的封装与绑定
      • ContentNegotiationManager:内容协商管理器
  • WebMvcAutoConfigurationAdapter 配置生效,它是一个 WebMvcConfigurer,定义了 MVC 底层组件

    • 定义好 WebMvcConfigurer:底层组件的默认功能
    • 视图解析器:InternalResourceViewResolver
    • 视图解析器:BeanNameViewResolver,视图名(Controller 方法的返回值字符串)就是组件名
    • 内容协商解析器:ContentNegotiatingViewResolver
    • 请求上下文过滤器:RequestContextFilter,在任意位置直接获取当前请求
    • 静态资源链规则
    • 错误详情:ProblemDetailsExceptionHandler,Spring MVC 内部场景的异常都会被它捕获

@EnableWebMvc 禁用默认行为

  • @EnableWebMvc 往容器中导入了 DelegatingWebMvcConfiguration 组件,它继承自 WebMvcConfigurationSupport
  • WebMvcAutoConfiguration 有一个核心的条件注解,@ConditionalOnMissingBean (WebMvcConfigurationSupport.class),当容器中没有 WebMvcConfigurationSupport 组件时,WebMvcAutoConfiguration 才生效
  • @EnableWebMvc 导入了 WebMvcConfigurationSupport,从而会导致 WebMvcAutoConfiguration 失效,即会导致 SpringBoot 禁用 MVC 自动配置的所有功能

WebMvcConfigurer 的功能

WebMvcConfigurer 定义并扩展了 Spring MVC 的底层功能,其中的功能列表如下:

提供方法核心参数功能默认
addFormattersFormatterRegistry 格式化器:支持属性上 @NumberFormat@DatetimeFormat 的数据类型转换 GenericConversionService
getValidator数据校验:校验 Controller 上使用 @Valid 标注的参数合法性。需要导入 spring-boot-starter-validator
addInterceptorsInterceptorRegistry 拦截器:拦截收到的所有请求
configureContentNegotiationContentNegotiationConfigurer 内容协商:支持多种数据格式返回。需要配合支持这种类型的 HttpMessageConverter支持 JSON
configureMessageConvertersList<HttpMessageConverter<?>> 消息转换器:标注 @ResponseBody 的返回值会利用 MessageConverter 直接写出去支持 8 种数据类型,包括 bytestringmultipartresourcejson
addViewControllersViewControllerRegistry 视图映射:直接将请求路径与物理视图映射。用于无 Java 业务逻辑的直接视图页渲染
configureViewResolversViewResolverRegistry 视图解析器:逻辑视图转为物理视图 ViewResolverComposite
addResourceHandlersResourceHandlerRegistry 静态资源处理:静态资源路径映射、缓存控制 ResourceHandlerRegistry
configureDefaultServletHandlingDefaultServletHandlerConfigurer 默认 Servlet:可以覆盖 Tomcat 的 DefaultServlet。让 DispatcherServlet 拦截 /
configurePathMatchPathMatchConfigurer 路径匹配:自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 /api
configureAsyncSupportAsyncSupportConfigurer 异步支持 TaskExecutionAutoConfiguration
addCorsMappingsCorsRegistry 跨域支持
addArgumentResolversList 参数解析器 MVC 默认提供
addReturnValueHandlersList 返回值解析器 MVC 默认提供
configureHandlerExceptionResolversList 异常处理器 3 个核心类:ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver
getMessageCodesResolver消息码解析器:国际化使用

最佳实践

SpringBoot 已经默认配置好了 Web 开发场景常用的功能,一般情况下直接使用即可。

三种配置方式

在 SpringBoot Web 开发场景下,有以下三种方式可以配置 SpringBoot MVC。

方式用法重点效果
全自动直接编写控制器的业务逻辑全部使用 SpringBoot 自动配置的默认效果
手自一体@Configuration + 配置 WebMvcConfigurer 或者配置 WebMvcRegistrations不要标注 @EnableWebMvc 注解保留 SpringBoot 自动配置效果,手动配置部分功能,自定义 MVC 底层组件
全手动@Configuration + 配置 WebMvcConfigurer标注 @EnableWebMvc 注解禁用 SpringBoot 自动配置效果,全手动配置

两种开发模式

  • 前后端分离模式: @RestController 直接响应 JSON 数据
  • 前后端不分离模式:@Controller + Thymeleaf 模板引擎