Spring MVC

Spring Boot 有许多包含 Spring MVC 的 starter。请注意,有些 starter 包含对 Spring MVC 的依赖,而不是直接包含它。本节回答了关于 Spring MVC 和 Spring Boot 的常见问题。

编写 JSON REST 服务

Spring Boot 应用程序中的任何 Spring @RestController 只要 Jackson2 在类路径上,默认情况下都应呈现 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 对象而言为 true),那么 localhost:8080/thing 默认情况下就会提供它的 JSON 表示。请注意,在浏览器中,有时可能会看到 XML 响应,因为浏览器倾向于发送偏好 XML 的 Accept 头。

编写 XML REST 服务

如果类路径上有 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 可用,则可以使用 XML 呈现,但附加要求是 MyThing 注解为 @XmlRootElement,如下例所示

  • 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 在类路径上,你已经会获得 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 还提供了一些特性,使自定义此行为变得更容易。

你可以通过环境变量配置 ObjectMapperXmlMapper 实例。Jackson 提供了广泛的开/关特性集,可用于配置其处理的各个方面。这些特性在 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

例如,要启用美观打印(pretty print),请设置 spring.jackson.serialization.indent_output=true。请注意,得益于 relaxed binding 的使用,indent_output 的大小写不必与相应的枚举常量 INDENT_OUTPUT 的大小写匹配。

此基于环境的配置应用于自动配置的 Jackson2ObjectMapperBuilder Bean,并应用于使用此 builder 创建的任何 mapper,包括自动配置的 ObjectMapper Bean。

上下文的 Jackson2ObjectMapperBuilder 可以通过一个或多个 Jackson2ObjectMapperBuilderCustomizer Bean 进行自定义。这些 customizer Bean 可以进行排序(Boot 自己的 customizer 的顺序为 0),从而可以在 Boot 的自定义之前和之后应用额外的自定义。

任何类型为 Module 的 Bean 都会自动注册到自动配置的 Jackson2ObjectMapperBuilder 中,并应用于它创建的任何 ObjectMapper 实例。这提供了一个全局机制,可在你向应用程序添加新功能时贡献自定义模块。

如果你想完全替换默认的 ObjectMapper,可以定义一个该类型的 @Bean,或者如果你更喜欢基于 builder 的方法,可以定义一个 Jackson2ObjectMapperBuilder @Bean。在定义 ObjectMapper Bean 时,建议将其标记为 @Primary,因为它将替换的自动配置的 ObjectMapper 也是 @Primary。请注意,在任何一种情况下,这样做都会禁用 ObjectMapper 的所有自动配置。

如果你提供了任何类型为 MappingJackson2HttpMessageConverter@Beans,它们将替换 MVC 配置中的默认值。此外,还提供了一个类型为 HttpMessageConverters 的便捷 Bean(如果你使用默认的 MVC 配置,此 Bean 始终可用)。它有一些有用的方法可以访问默认和用户增强的消息转换器。

有关更多详细信息,请参阅自定义 @ResponseBody 渲染部分以及WebMvcAutoConfiguration 源代码。

自定义 @ResponseBody 渲染

Spring 使用 HttpMessageConverters 来渲染 @ResponseBody(或来自 @RestController 的响应)。你可以通过在 Spring Boot 上下文中添加适当类型的 Bean 来贡献额外的转换器。如果你添加的 Bean 是默认情况下也会包含的类型(例如用于 JSON 转换的 MappingJackson2HttpMessageConverter),它将替换默认值。还提供了一个类型为 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 时,单个文件的最大大小为 1MB,单次请求中的文件数据总量最大为 10MB。你可以使用 MultipartProperties 类中公开的属性来覆盖这些值,包括存储中间数据的 location(例如,到 /tmp 目录)以及数据刷新到磁盘的 threshold。例如,如果你想指定文件大小不受限制,请将 spring.servlet.multipart.max-file-size 属性设置为 -1

当你希望在 Spring MVC 控制器处理方法中接收 multipart 编码的文件数据作为 @RequestParam 注解的 MultipartFile 类型参数时,multipart 支持会很有帮助。

有关更多详细信息,请参阅MultipartAutoConfiguration 源代码。

建议使用容器内置的 multipart 上传支持,而不是引入像 Apache Commons File Upload 这样的额外依赖项。

关闭 Spring MVC DispatcherServlet

默认情况下,所有内容都从应用程序的根路径 (/) 提供服务。如果你想映射到不同的路径,可以按如下方式配置

  • 属性

  • YAML

spring.mvc.servlet.path=/mypath
spring:
  mvc:
    servlet:
      path: "/mypath"

如果你有额外的 servlet,可以为每个 servlet 声明一个类型为 @BeanServletServletRegistrationBean,Spring Boot 会将其透明地注册到容器中。由于 servlet 以这种方式注册,它们可以映射到 DispatcherServlet 的子上下文而无需调用 DispatcherServlet 本身。

自己配置 DispatcherServlet 是不寻常的,但如果你确实需要这样做,还必须提供一个类型为 DispatcherServletPath@Bean 来提供你自定义的 DispatcherServlet 的路径。

关闭默认 MVC 配置

完全控制 MVC 配置的最简单方法是提供带有 @Configuration 注解的自己的 @EnableWebMvc。这样做会将所有 MVC 配置权交给你。

自定义 ViewResolver

一个 ViewResolver 是 Spring MVC 的核心组件,它将 @Controller 中的视图名称转换为实际的 View 实现。请注意,视图解析器主要用于 UI 应用程序,而不是 REST 风格的服务(View 不用于渲染 @ResponseBody)。有许多 ViewResolver 实现可供选择,Spring 本身对此没有主观意见,不强制你使用哪一个。另一方面,Spring Boot 会为你安装一个或两个,具体取决于它在类路径和应用程序上下文中找到的内容。DispatcherServlet 使用它在应用程序上下文中找到的所有解析器,依次尝试每个解析器直到获得结果。如果你添加自己的解析器,则必须注意其顺序以及添加的位置。

WebMvcAutoConfiguration 将以下 ViewResolver Bean 添加到你的上下文中

  • 一个名为 ‘defaultViewResolver’ 的 InternalResourceViewResolver。它定位可以使用 DefaultServlet 渲染的物理资源(包括静态资源和 JSP 页面,如果你使用这些)。它将前缀和后缀应用于视图名称,然后在 servlet 上下文中查找具有该路径的物理资源(默认值都为空,但可以通过 spring.mvc.view.prefixspring.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 模板(实际上,如果您的 classpath 中包含 groovy-templates),您还会有一个名为 ‘groovyMarkupViewResolver’ 的 GroovyMarkupViewResolver。它通过在视图名称周围添加前缀和后缀来查找加载器路径中的资源(外部化为 spring.groovy.template.prefixspring.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 模板。通常,您需要一个名称解析为 errorView 或一个处理 /error 路径的 @Controller。除非您替换了一些默认配置,否则您应该在您的 ApplicationContext 中找到一个 BeanNameViewResolver,因此一个名为 error@Bean 将是一种实现方式。有关更多选项,请参见 ErrorMvcAutoConfiguration

另请参见关于错误处理的章节,了解如何在 servlet 容器中注册处理程序。