Spring MVC
Spring Boot 提供了多个包含 Spring MVC 的 starter。请注意,某些 starter 是通过依赖的方式引入 Spring MVC,而不是直接包含它。本节将解答有关 Spring MVC 和 Spring Boot 的常见问题。
编写一个 JSON REST 服务
在 Spring Boot 应用程序中,只要 Jackson2 位于 classpath 上,任何 Spring @RestController 默认都应渲染 JSON 响应,如下例所示:
- Java
- Kotlin
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("/thing")
public MyThing thing() {
return new MyThing();
}
}
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class MyController {
@RequestMapping("/thing")
fun thing(): MyThing {
return MyThing()
}
}
只要 MyThing 能被 Jackson2 序列化(对于普通的 POJO 或 Groovy 对象来说是成立的),那么 [localhost:8080/thing](http://localhost:8080/thing) 默认会提供其 JSON 表示形式。注意,在浏览器中,有时你可能会看到 XML 响应,因为浏览器倾向于发送偏好 XML 的 Accept 头。
编写一个 XML REST 服务
如果你在 classpath 中包含了 Jackson XML 扩展(jackson-dataformat-xml),就可以使用它来渲染 XML 响应。我们之前用于 JSON 的示例同样适用。要使用 Jackson XML 渲染器,请向你的项目中添加以下依赖项:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
如果 Jackson 的 XML 扩展不可用,而 JAXB 可用,则可以通过额外要求将 MyThing 注解为 @XmlRootElement 来渲染 XML,如下例所示:
- Java
- Kotlin
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class MyThing {
private String name;
// getters/setters ...
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import jakarta.xml.bind.annotation.XmlRootElement
@XmlRootElement
class MyThing {
var name: String? = null
}
你需要确保 JAXB 库已包含在你的项目中,例如通过添加:
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
要让服务器渲染 XML 而不是 JSON,你可能需要发送一个 Accept: text/xml 请求头(或者使用浏览器)。
自定义 Jackson ObjectMapper
Spring MVC(客户端和服务器端)使用 HttpMessageConverters 在 HTTP 交换中协商内容转换。如果 Jackson 位于 classpath 中,你将自动获得由 Jackson2ObjectMapperBuilder 提供的默认转换器,该构建器的实例会为你自动配置。
ObjectMapper(或用于 Jackson XML 转换器的 XmlMapper)实例(默认创建的)具有以下自定义属性:
-
MapperFeature.DEFAULT_VIEW_INCLUSION已禁用 -
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES已禁用 -
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS已禁用 -
SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS已禁用
Spring Boot 还提供了一些功能,使得自定义此行为更加容易。
你可以通过使用环境来配置 ObjectMapper 和 XmlMapper 实例。Jackson 提供了一套丰富的开关特性(on/off features),可用于配置其处理过程的各个方面。这些特性在 Jackson 中由多个枚举类描述,并映射到环境中的属性:
| 枚举 | 属性 | 值 |
|---|---|---|
| EnumFeature | spring.jackson.datatype.enum.<feature_name> | true, false |
| JsonNodeFeature | spring.jackson.datatype.json-node.<feature_name> | true, false |
| DeserializationFeature | spring.jackson.deserialization.<feature_name> | true, false |
| JsonGenerator.Feature | spring.jackson.generator.<feature_name> | true, false |
| MapperFeature | spring.jackson.mapper.<feature_name> | true, false |
| JsonParser.Feature | spring.jackson.parser.<feature_name> | true, false |
| SerializationFeature | spring.jackson.serialization.<feature_name> | true, false |
| JsonInclude.Include | spring.jackson.default-property-inclusion | always, non_null, non_absent, non_default, non_empty |
例如,要启用美化打印,可设置 spring.jackson.serialization.indent_output=true。注意,由于使用了 宽松绑定(relaxed binding),indent_output 的大小写不必与对应的枚举常量 INDENT_OUTPUT 完全一致。
此基于环境的配置会应用到自动配置的 Jackson2ObjectMapperBuilder Bean,并适用于使用该构建器创建的所有映射器,包括自动配置的 ObjectMapper Bean。
上下文的 Jackson2ObjectMapperBuilder 可以通过一个或多个 Jackson2ObjectMapperBuilderCustomizer Bean 进行自定义。这些自定义器 Bean 可以指定顺序(Boot 自带的自定义器顺序为 0),从而允许在 Boot 的自定义之前和之后应用额外的自定义配置。
任何类型为 Module 的 Bean 都会自动注册到自动配置的 Jackson2ObjectMapperBuilder 中,并应用于其创建的任何 ObjectMapper 实例。这为你在向应用程序添加新功能时提供了一种全局机制来贡献自定义模块。
如果你希望通过编程方式使用 Jackson2ObjectMapperBuilderCustomizer 注册额外的模块,请务必使用接受 consumer 参数的 modulesToInstall 方法,因为其他变体不具备叠加效果。
如果你想完全替换默认的 ObjectMapper,可以定义一个该类型的 @Bean,或者如果你更喜欢基于构建器的方式,则定义一个 Jackson2ObjectMapperBuilder @Bean。在定义 ObjectMapper bean 时,建议将其标记为 @Primary,因为要替换的自动配置所提供的 ObjectMapper 本身也是 @Primary 的。请注意,无论采用哪种方式,这样做都会禁用 ObjectMapper 的所有自动配置。
如果你提供了任何类型为 MappingJackson2HttpMessageConverter 的 @Bean,它们将替换 MVC 配置中的默认值。此外,还会提供一个类型为 HttpMessageConverters 的便捷 Bean(如果你使用默认的 MVC 配置,则该 Bean 始终可用)。它包含一些有用的方法,用于访问默认的和用户增强的消息转换器。
更多详情请参见 Customize the @ResponseBody Rendering 章节以及 WebMvcAutoConfiguration 的源代码。
自定义 @ResponseBody 渲染
Spring 使用 HttpMessageConverters 来渲染 @ResponseBody(或来自 @RestController 的响应)。你可以通过在 Spring Boot 上下文中添加适当类型的 Bean 来注册额外的转换器。如果你添加的 Bean 类型原本就会被默认包含(例如用于 JSON 转换的 MappingJackson2HttpMessageConverter),那么它将替换默认的转换器。Spring 提供了一个类型为 HttpMessageConverters 的便捷 Bean,只要使用默认的 MVC 配置,该 Bean 始终可用。它提供了一些有用的方法来访问默认的和用户增强的消息转换器(例如,当你希望手动将它们注入到自定义的 RestTemplate 中时,这会非常有用)。
与常规的 MVC 用法一样,您提供的任何 WebMvcConfigurer Bean 都可以通过重写 configureMessageConverters 方法来添加消息转换器。然而,与常规 MVC 不同的是,您只需提供所需的额外转换器即可(因为 Spring Boot 使用相同的机制来注册其默认转换器)。最后,如果您通过提供自己的 @EnableWebMvc 配置选择退出 Spring Boot 的默认 MVC 配置,则可以完全接管配置,并通过使用 WebMvcConfigurationSupport 中的 getMessageConverters 方法手动完成所有操作。
更多详情请参见 WebMvcAutoConfiguration 源代码。
处理 Multipart 文件上传
Spring Boot 采用 Servlet 5 的 Part API 来支持文件上传。默认情况下,Spring Boot 为 Spring MVC 配置了每个文件最大 1 MB、单个请求中最多 10 MB 的文件数据。你可以通过 MultipartProperties 类中暴露的属性来覆盖这些值、设置中间数据的存储位置(例如 /tmp 目录),以及指定将数据刷新到磁盘的阈值。例如,如果你想允许上传任意大小的文件,可将 spring.servlet.multipart.max-file-size 属性设置为 -1。
当您希望在 Spring MVC 控制器的处理方法中,将 multipart 编码的文件数据作为类型为 MultipartFile、并使用 @RequestParam 注解的参数接收时,multipart 支持非常有用。
更多详情请参见 MultipartAutoConfiguration 源码。
建议使用容器内置的 multipart 上传支持,而不是引入额外的依赖,例如 Apache Commons File Upload。
关闭 Spring MVC 的 DispatcherServlet
默认情况下,所有内容都从应用程序的根路径(/)提供。如果你希望映射到不同的路径,可以按如下方式配置:
- Properties
- YAML
spring.mvc.servlet.path=/mypath
spring:
mvc:
servlet:
path: "/mypath"
如果你有额外的 servlet,可以为每个 servlet 声明一个类型为 Servlet 或 ServletRegistrationBean 的 @Bean,Spring Boot 会自动将其透明地注册到容器中。此外,也可以使用 @ServletRegistration 作为基于注解的替代方案,以取代 ServletRegistrationBean。由于 servlet 是通过这种方式注册的,因此它们可以映射到 DispatcherServlet 的子上下文中,而无需调用该 DispatcherServlet。
自行配置 DispatcherServlet 的情况并不常见,但如果你确实需要这样做,则还必须提供一个类型为 DispatcherServletPath 的 @Bean,以指定你自定义的 DispatcherServlet 的路径。
关闭默认的 MVC 配置
完全控制 MVC 配置的最简单方法是提供一个带有 @EnableWebMvc 注解的自定义 @Configuration 类。这样做会将所有 MVC 配置交由你自行处理。
自定义 ViewResolvers
ViewResolver 是 Spring MVC 的核心组件,它将 @Controller 中的视图名称转换为实际的 View 实现。需要注意的是,视图解析器主要用于 UI 应用程序,而不是 REST 风格的服务(@ResponseBody 不会使用 View 进行渲染)。有多种 ViewResolver 的实现可供选择,而 Spring 本身对应该使用哪种实现并无倾向性。另一方面,Spring Boot 会根据 classpath 和应用上下文中找到的内容,自动为你配置一个或两个视图解析器。DispatcherServlet 会使用在应用上下文中找到的所有解析器,并依次尝试每个解析器,直到获得结果为止。如果你添加了自己的解析器,则需要注意其顺序以及该解析器被添加的位置。
WebMvcAutoConfiguration 会向你的上下文中添加以下 ViewResolver bean:
-
一个名为 ‘defaultViewResolver’ 的 InternalResourceViewResolver。该解析器用于定位可通过
DefaultServlet渲染的物理资源(包括静态资源和 JSP 页面,如果你使用它们的话)。它会对视图名称应用前缀和后缀,然后在 Servlet 上下文中查找具有该路径的物理资源(默认情况下前缀和后缀均为空,但可以通过spring.mvc.view.prefix和spring.mvc.view.suffix进行外部配置)。你可以通过提供一个相同类型的 Bean 来覆盖它。 -
一个名为 ‘beanNameViewResolver’ 的 BeanNameViewResolver。这是视图解析器链中的一个有用成员,会选取与正在解析的 View 同名的任何 Bean。通常不需要覆盖或替换它。
-
仅当实际存在类型为 View 的 Bean 时,才会添加一个名为 ‘viewResolver’ 的 ContentNegotiatingViewResolver。这是一个组合解析器,会委托给所有其他解析器,并尝试匹配客户端发送的 ‘Accept’ HTTP 头。有一篇关于 ContentNegotiatingViewResolver 的有用博客,你可能会有兴趣深入学习,也可以查看源代码以获取更多细节。你可以通过定义一个名为 ‘viewResolver’ 的 Bean 来关闭自动配置的 ContentNegotiatingViewResolver。
-
如果你使用 Thymeleaf,还会有一个名为 ‘thymeleafViewResolver’ 的 ThymeleafViewResolver。它通过在视图名称前后加上前缀和后缀来查找资源。前缀为
spring.thymeleaf.prefix,后缀为spring.thymeleaf.suffix。前缀和后缀的默认值分别为 ‘classpath:/templates/’ 和 ‘.html’。你可以通过提供一个同名的 Bean 来覆盖 ThymeleafViewResolver。 -
如果你使用 FreeMarker,还会有一个名为 ‘freeMarkerViewResolver’ 的 FreeMarkerViewResolver。它通过在加载器路径(外部化为
spring.freemarker.templateLoaderPath,默认值为 ‘classpath:/templates/’)中,在视图名称前后加上前缀和后缀来查找资源。前缀外部化为spring.freemarker.prefix,后缀外部化为spring.freemarker.suffix。前缀和后缀的默认值分别为空和 ‘.ftlh’。你可以通过提供一个同名的 Bean 来覆盖 FreeMarkerViewResolver。FreeMarker 变量可以通过定义一个类型为 FreeMarkerVariablesCustomizer 的 Bean 进行自定义。 -
如果你使用 Groovy 模板(实际上,只要
groovy-templates在你的 classpath 中),还会有一个名为 ‘groovyMarkupViewResolver’ 的 GroovyMarkupViewResolver。它通过在加载器路径中,在视图名称前后加上前缀和后缀(外部化为spring.groovy.template.prefix和spring.groovy.template.suffix)来查找资源。前缀和后缀的默认值分别为 ‘classpath:/templates/’ 和 ‘.tpl’。你可以通过提供一个同名的 Bean 来覆盖 GroovyMarkupViewResolver。 -
如果你使用 Mustache,还会有一个名为 ‘mustacheViewResolver’ 的 MustacheViewResolver。它通过在视图名称前后加上前缀和后缀来查找资源。前缀为
spring.mustache.prefix,后缀为spring.mustache.suffix。前缀和后缀的默认值分别为 ‘classpath:/templates/’ 和 ‘.mustache’。你可以通过提供一个同名的 Bean 来覆盖 MustacheViewResolver。
更多详情,请参见以下章节:
自定义 ‘whitelabel’ 错误页面
Spring Boot 安装了一个“whitelabel”错误页面,当你在浏览器客户端遇到服务器错误时会看到该页面(使用 JSON 和其他媒体类型的机器客户端则应该收到带有正确错误码的合理响应)。
设置 server.error.whitelabel.enabled=false 可关闭默认的错误页面。这样做会恢复为你所使用的 servlet 容器的默认行为。请注意,Spring Boot 仍然会尝试解析错误视图,因此你最好添加自己的错误页面,而不是完全禁用它。
使用你自己的错误页面来覆盖默认错误页面,取决于你所使用的模板技术。例如,如果你使用 Thymeleaf,可以添加一个 error.html 模板;如果你使用 FreeMarker,则可以添加一个 error.ftlh 模板。通常来说,你需要一个名称为 error 的 View,或者一个处理 /error 路径的 @Controller。除非你替换了部分默认配置,否则你应该能在你的 ApplicationContext 中找到一个 BeanNameViewResolver,因此,定义一个名为 error 的 @Bean 就是一种实现方式。更多选项请参见 ErrorMvcAutoConfiguration。
另请参阅 错误处理 一节,了解如何在 servlet 容器中注册处理器的详细信息。