77. Spring MVC

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

77.1 写一个 JSON REST 服务

只要 Jackson2 在类路径上,Spring Boot 应用程序中的任何 Spring @RestController 都应该默认呈现 JSON 响应,如下面的示例所示:

@RestController
public class MyController {

	@RequestMapping("/thing")
	public MyThing thing() {
			return new MyThing();
	}

}

只要 MyThing 可以由 Jackson2 序列化(对于普通的 POJO 或 Groovy 对象为真),那么 localhost:8080/thing 默认为它的 JSON 表示服务。注意,在浏览器中,有时可能会看到 XML 响应,因为浏览器倾向于发送喜欢 XML 的接受头。

77.2 写一个 XML REST 服务

如果在类路径上有 Jackson XML 扩展(jackson-dataformat-xml),则可以使用它来呈现 XML 响应。前面使用的 JSON 示例将起作用。若要使用 Jackson XML 渲染器,请将以下依赖项添加到项目中:

<dependency>
	<groupId>com.fasterxml.jackson.dataformat</groupId>
	<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

你还可能希望对 Woodstox 添加依赖项。它比 JDK 提供的默认 StAX 实现更快,还添加了漂亮的打印支持和改进的名称空间处理。下面的列表显示了如何包含 Woodstox 上的依赖项:

<dependency>
	<groupId>org.codehaus.woodstox</groupId>
	<artifactId>woodstox-core-asl</artifactId>
</dependency>

如果 Jackson 的 XML 扩展不可用,则使用 JAXB(在 JDK 中默认提供),另外还需要将 MyThing 注解为 @XmlRootElement,如下面的示例所示:

@XmlRootElement
public class MyThing {
	private String name;
	// .. getters and setters
}

要使服务器呈现 XML 而不是 JSON,你可能需要发送 Accept: text/xml 报头(或使用浏览器)。

77.3 自定义 Jackson 对象映射

Spring MVC(客户端和服务器端)使用 HttpMessageConverters 来协商 HTTP 交换机中的内容转换。如果 Jackson 在类路径上,那么你已经获得了 Jackson2ObjectMapperBuilder 提供的默认转换器,该转换器的实例是为你自动配置的。

ObjectMapper(或 Jackson XML 转换器的 XmlMapper)实例(默认创建)具有以下自定义属性:

  • MapperFeature.DEFAULT_VIEW_INCLUSION 被禁用
  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 被禁用
  • SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 被禁用

Spring Boot 还具有一些特性,可以更容易定制这种行为。

可以使用环境配置 ObjectMapper 和 XmlMapper 实例。 Jackson 提供了一系列简单的 on/off 特性,可以用来配置其处理的各个方面。这些特性在映射到环境中的属性的六个枚举(Jackson 中)中描述: You can configure the ObjectMapper and XmlMapper instances by using the environment. Jackson provides an extensive suite of simple on/off features that can be used to configure various aspects of its processing. These features are described in six enums (in Jackson) that map onto properties in the environment:

Enum 属性 值

com.fasterxml.jackson.databind.DeserializationFeature

spring.jackson.deserialization.<feature_name>

true, false

com.fasterxml.jackson.core.JsonGenerator.Feature

spring.jackson.generator.<feature_name>

true, false

com.fasterxml.jackson.databind.MapperFeature

spring.jackson.mapper.<feature_name>

true, false

com.fasterxml.jackson.core.JsonParser.Feature

spring.jackson.parser.<feature_name>

true, false

com.fasterxml.jackson.databind.SerializationFeature

spring.jackson.serialization.<feature_name>

true, false

com.fasterxml.jackson.annotation.JsonInclude.Include

spring.jackson.default-property-inclusion

always, non_null, non_absent, non_default, non_empty

例如,为了启用漂亮的打印,设置 spring.jackson.serialization.indent_output=true。注意,由于使用了松散绑定,INDENT_OUTPUT 的情况不必与对应的 enum 常数(即 indent_output)的情况匹配。

这种基于环境的配置应用于自动配置的 Jackson2ObjectMapperBuilder bean,并应用于通过使用构建器创建的任何映射器,包括自动配置的 ObjectMapper bean。

上下文的 Jackson2ObjectMapperBuilder 可以由一个或多个 Jackson2ObjectMapperBuilderCustomizer bean 定制。这样的定制器 bean 可以被订购(Boot 自己的定制器顺序是 0),允许在 Boot 的定制之前和之后应用额外的定制。

任何类型为 com.fasterxml.jackson.databind.Module 的 bean 都会自动在自动配置的 Jackson2ObjectMapperBuilder 中注册,并应用于它所创建的任何 ObjectMapper 实例。这为你在应用程序中添加新功能时提供了自定义模块的一种全局机制。

如果希望完全替换默认的 ObjectMapper,可以定义该类型的 @Bean 并将其标记为 @Primary,或者,如果喜欢基于构建器的方法,可以定义 Jackson2ObjectMapperBuilder @Bean。注意,无论在哪种情况下,这样做都禁用 ObjectMapper 的所有自动配置。

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

更多详细信息请参阅 “小节 77.4, “自定义 @ResponseBody 渲染”” 部分和 WebMvcAutoConfiguration 源代码。

77.4 自定义 @ResponseBody 渲染

Spring 使用 HttpMessageConverters 来渲染 @ResponseBody(或来自 @RestController 的响应)。可以通过在 Spring Boot 上下文中添加适当类型的 bean 来贡献额外的转换器。如果你添加的 bean 是一种默认情况下包含的类型(例如,用于 JSON 转换的 MappingJackson2HttpMessageConverter),那么它将替换默认值。提供了一个 HttpMessageConverters 类型的方便bean,如果使用默认 MVC 配置,它总是可用的。它有一些有用的方法来访问默认的和用户增强的消息转换器(例如,如果你想手动将它们注入自定义的 RestTemplate),它可能是有用的。

与常规 MVC 使用中一样,你提供的任何 WebMvcConfigurer bean 也可以通过重写 configureMessageConverters 方法来贡献转换器。但是,与常规 MVC 不同,你只能提供你需要的附加转换器(因为 Spring Boot 使用相同的机制来提供其默认值)。最后,如果通过提供自己的 @EnableWebMvc 配置,Spring Boot 默认 MVC 配置则选择退出 ,可以通过使用来自 WebMvcConfigurationSupport 的 getMessageConverters 完全控制并手动完成所有操作。

更多详细信息请参阅 WebMvcAutoConfiguration 源代码。

77.5 处理多文件上传

Spring Boot 包含 Servlet 3 javax.servlet.http.Part API 来支持上传文件。默认情况下,Spring Boot 配置每个文件的最大大小为 1MB,单个请求中最大文件数据为 10MB 的 Spring MVC。你可以通过使用 MultipartProperties 类中公开的属性,覆盖这些值、存储中间数据的位置(例如,到 /tmp 目录)以及将数据刷新到磁盘的阈值。例如,如果要指定文件是无限的,请将 spring.servlet.multipart.max-file-size 属性设置为 -1。

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

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

77.6 关闭 Spring MVC DispatcherServlet

Spring Boot 希望从应用程序的根(/)向下服务所有内容。如果你宁愿把你自己的 servlet 映射到那个URL,你可以这样做。但是,你可能会丢失一些其它 Boot MVC 特性。要添加自己的 servlet 并将其映射到根资源,请声明一个 Servlet 类型的 @Bean,并将其命名为 dispatcherServlet。(如果你想关闭它而不替换它,你也可以创建具有该名称的不同类型的bean。)

77.7 关闭默认 MVC 配置

要完全控制 MVC 配置,最简单的方法是为你自己的 @Configuration 提供 @EnableWebMvc 注解。这样做将把所有的 MVC 配置留在你的手中。

77.8 自定义视图解析器

ViewResolver 是 Spring MVC 的核心组件,将 @Controller 中的视图名称转换为实际 View 实现。注意,ViewResolvers 主要用于 UI 应用程序,而不是 REST 样式的服务(View 不用于呈现 @ResponseBody)。有许多 ViewResolver 的实现可供选择,Spring 本身并不选择武断你应该使用哪个。另一方面,Spring Boot 根据在类路径和应用程序上下文中找到的内容,为你安装一到两个。DispatcherServlet 使用它在应用程序上下文中找到的所有解析器,依次尝试每个解析器,直到得到结果,因此,如果添加自己的解析器,则必须知道添加解析器的顺序和位置。

WebMvcAutoConfiguration 将以下 ViewResolvers 添加到你的上下文中:

  • 一个名为 ‘defaultViewResolver’ 的 InternalResourceViewResolver。这一个使用 DefaultServlet 找出可渲染的物理资源(包括静态资源和 JSP 页面,如果你使用这些文件)。它向视图名称应用前缀和后缀,然后在 servlet 上下文中查找具有该路径的物理资源(默认值均为空,但可通过 spring.mvc.view.prefix 和 spring.mvc.view.suffix 进行外部配置)访问外部配置。可以通过提供相同类型的 bean 来覆盖它。
  • 一个名为 ‘beanNameViewResolver’ 的 BeanNameViewResolver。这是视图解析器链中有用的成员,并选择与解析的 View 具有相同名称的任何 bean。不必重写或替换它。
  • 一个名为 ‘viewResolver’ 的 ContentNegotiatingViewResolver,它只有在存在 View 类型 bean 的情况下才添加。这是一个主要的解析器,委托给所有其它解析器,并试图找到与客户端发送的 ‘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。前缀和后缀的默认值分别为空和 ‘.ftl’。可以通过提供同名 bean 来覆盖 FreeMarkerViewResolver。
  • 如果你使用 Groovy 模板(实际上,如果 groovy-templates 在类路径上),那么你还有一个名为 ‘groovyMarkupViewResolver’ 的 GroovyMarkupViewResolver。它通过将视图名称与前缀和后缀(外部化为 spring.groovy.template.prefix 和 spring.groovy.template.suffix)包围来查找加载程序路径中的资源。前缀和后缀分别具有 ‘classpath:/templates/’ 和 ‘.tpl’ 的默认值。可以通过提供同名 bean 来覆盖 GroovyMarkupViewResolver。

有关的更多细节请参阅以下章节:

  • WebMvcAutoConfiguration
  • ThymeleafAutoConfiguration
  • FreeMarkerAutoConfiguration
  • GroovyTemplateAutoConfiguration

78. Jersey

78.1 使用 Spring Security 保护 Jersey 节点

Spring Security 可以用于保护基于 Jersey 的 web 应用程序,其方式与用于保护基于 Spring MVC 的 web 应用程序的方式大致相同。但是,如果希望将 Spring Security 的方法级安全性用于 Jersey,则必须将 Jersey 配置为使用 setStatus(int) 而不是 sendError(int)。这防止了 Jersey 在 Spring Security 有机会向客户端报告身份验证或授权失败之前提交响应。

必须在应用程序的 RResourceConfig bean 上将 jersey.config.server.response.setStatusOverSendError 属性设置为 true,如下面的示例所示:

@Component
public class JerseyConfig extends ResourceConfig {

	public JerseyConfig() {
		register(Endpoint.class);
		setProperties(Collections.singletonMap(
				"jersey.config.server.response.setStatusOverSendError", true));
	}

}

79. HTTP 客户端

Spring Boot 提供了一些与 HTTP 客户端一起工作的启动器。本节回答有关使用它们的问题。

79.1 配置 RestTemplate 以使用代理

如 小节 33.1, “定制 RestTemplate” 中所述,可以使用 RestTemplateBuilder 的 RestTemplateCustomizer 来构建定制的 RestTemplate。这是创建配置为使用代理的 RestTemplate 的推荐方法。

代理配置的确切细节取决于正在使用的底层客户端请求工厂。下面的示例使用 HttpClient 配置 HttpComponentsClientRequestFactory,它使用除 192.168.0.5 之外的所有主机的代理:

static class ProxyCustomizer implements RestTemplateCustomizer {

	@Override
	public void customize(RestTemplate restTemplate) {
		HttpHost proxy = new HttpHost("proxy.example.com");
		HttpClient httpClient = HttpClientBuilder.create()
				.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {

					@Override
					public HttpHost determineProxy(HttpHost target,
							HttpRequest request, HttpContext context)
							throws HttpException {
						if (target.getHostName().equals("192.168.0.5")) {
							return null;
						}
						return super.determineProxy(target, request, context);
					}

				}).build();
		restTemplate.setRequestFactory(
				new HttpComponentsClientHttpRequestFactory(httpClient));
	}

}

80. 日志记录

Spring Boot 除了通常由 Spring Framework 的 spring-jcl 模块提供的 Commons Logging API 之外,没有强制性的日志依赖。要使用 Logback,你需要在类路径上包含它和 spring-jcl 。最简单的方法是通过启动器,所有这些都依赖于 spring-boot-starter-logging。对于 web 应用程序,只需要 spring-boot-starter-web,因为它依赖于日志记录启动器。如果使用 Maven,以下依赖项为你添加日志记录:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot 有一个 LoggingSystem 抽象,它试图根据类路径的内容来配置日志记录。如果 Logback 是可用的,这是第一选择。

如果对日志记录的唯一更改是设置各种日志记录器的级别,那么你可以在 application.properties 中使用 "logging.level" 前缀,如下面的示例所示:

logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR

还可以使用 "logging.file" 设置要写入日志的文件的位置(除了控制台)。

要配置日志系统的更细粒度设置,你需要使用 LoggingSystem 所支持的本地配置格式。默认情况下,Spring Boot 从系统的默认位置(例如,Logback 为 classpath:logback.xml)获取本地配置,但是你可以使用 "logging.config" 属性设置配置文件的位置。

80.1 配置 Logback 日志记录

如果将 logback.xml 放在类路径的根目录中,可以从那里获取它(或者从 logback-spring.xml 获取它,以便利用 Boot 提供的模板特性)。Spring Boot 提供了一个默认的基础配置,如果希望设置级别,可以包含该配置,如下面的示例所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/base.xml"/>
	<logger name="org.springframework.web" level="DEBUG"/>
</configuration>

如果你查看 spring-boot jar 中的 base.xml,可以看到它使用了 LoggingSystem 为你创建的一些有用的系统属性:

  • ${PID}: 当前进程 ID。
  • ${LOG_FILE}: logging.file 是否设置在 Boot 的外部配置中。
  • ${LOG_PATH}: 在 Boot 的外部配置中是否设置 logging.path(表示要记录的日志文件的目录)。
  • ${LOG_EXCEPTION_CONVERSION_WORD}: 在 Boot 的外部配置中是否设置了 logging.exception-conversion-word。

Spring Boot 还通过使用自定义 Logback 转换器在控制台(但不是在日志文件中)上提供了一些很好的 ANSI 彩色终端输出。有关的详细信息请参阅默认的 base.xml 配置。

如果 Groovy 在类路径上,那么您你该能够用 logback.groovy 配置 Logback。如果存在,则优先设置此设置。

80.1.1 配置 Logback 为仅文件输出

如果希望禁用控制台日志记录并只将输出写入文件,则需要导入 file-appender.xml 但不导入 console-appender.xml 的自定义 logback-spring.xml,如下面的示例所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/defaults.xml" />
	<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
	<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
	<root level="INFO">
		<appender-ref ref="FILE" />
	</root>
</configuration>

你还需要将 logging.file 文件添加到 application.properties 中,如下面的示例所示:

logging.file=myapplication.log

80.2 配置 Log4j 日志记录

Spring Boot 支持 Log4j 2 用于日志配置,如果它在类路径上。如果使用启动器来装配依赖项,则必须排除 Logback,然后再包含 log4j 2。如果不使用启动器,除了 Log4j 2 之外,还需要提供(至少)spring-jcl。

最简单的路径可能是通过启动器,尽管它需要一些不规则的微调。下面的示例演示如何在 Maven 中设置启动器:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

下面的示例展示了在 Gradle 中设置启动器的一种方法:

dependencies {
	compile 'org.springframework.boot:spring-boot-starter-web'
	compile 'org.springframework.boot:spring-boot-starter-log4j2'
}

configurations {
	all {
		exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
	}
}
[Note] Note

Log4j 启动器将公共日志记录需求的依赖项集合在一起(比如让 Tomcat 使用 java.util.logging ,但是使用 Log4j 2 配置输出)。请参阅 Log4j 2 执行器 示例以获得更多细节并在行动中查看。

[Note] Note

为了确保使用 java.util.logging 执行的调试日志被路由到 Log4j 2,通过将 java.util.logging.manager 系统属性设置为 org.apache.logging.log4j.jul.LogManager 来配置其 JDK 日志适配器。

80.2.1 使用 YAML 或 JSON 来配置 Log4j 2

除了默认的 XML 配置格式外,Log4j 2 还支持 YAML 和 JSON 配置文件。要配置 Log4j 2 以使用替代配置文件格式,请将适当的依赖项添加到类路径中,并将配置文件命名为与所选文件格式相匹配,如下例所示:

格式 依赖项 文件名

YAML

com.fasterxml.jackson.core:jackson-databind com.fasterxml.jackson.dataformat:jackson-dataformat-yaml

log4j2.yaml log4j2.yml

JSON

com.fasterxml.jackson.core:jackson-databind

log4j2.json log4j2.jsn

81. 数据访问

Spring Boot 包含多个启动数据源的启动器。本节回答与此相关的问题。

81.1 配置自定义数据源

若要配置自己的 DataSource,请在配置中定义该类型的 @Bean。Spring Boot 在需要的地方重用你的 DataSource,包括数据库初始化。如果需要外部化一些设置,可以将 DataSource 绑定到环境中(参阅 “小节 24.7.1, “第三方配置””)。

下面的示例演示如何在 bean 中定义数据源:

@Bean
@ConfigurationProperties(prefix="app.datasource")
public DataSource dataSource() {
	return new FancyDataSource();
}

下面的示例演示如何通过设置属性自定义数据源:

app.datasource.url=jdbc:h2:mem:mydb
app.datasource.username=sa
app.datasource.pool-size=30

假设你的 FancyDataSource 具有用于 URL、用户名和连接池大小的常规 JavaBean 属性,则在 DataSource 可用于其它组件之前,这些设置将自动绑定。定期的数据库初始化也会发生(因此 spring.datasource.* 的相关子集仍然可用于自定义配置)。

如果配置自定义 JNDI DataSource,则可以应用相同的原则,如下面的示例所示:

@Bean(destroyMethod="")
@ConfigurationProperties(prefix="app.datasource")
public DataSource dataSource() throws Exception {
	JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
	return dataSourceLookup.getDataSource("java:comp/env/jdbc/YourDS");
}

Spring Boot 还提供了一个名为 DataSourceBuilder 的实用程序构建器类,该类可用于创建标准数据源中的一个(如果位于类路径上)。构造器可以根据类路径上可用的信息来检测要使用的那个。它还自动检测基于 JDBC URL 的驱动程序。

下面的示例演示如何使用 DataSourceBuilder 创建数据源:

@Bean
@ConfigurationProperties("app.datasource")
public DataSource dataSource() {
	return DataSourceBuilder.create().build();
}

要使用该 DataSource 运行应用程序,你需要的是连接信息。也可以提供特定池设置。检查在运行时使用的实现的更多细节。

下面的示例演示如何通过设置属性自定义 JDBC 数据源:

app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.pool-size=30

然而,这有一个陷阱。因为连接池的实际类型没有公开,所以自定义 DataSource 的元数据中没有生成键,IDE 中也没有可用的实现(因为 DataSource 接口没有公开属性)。此外,如果类路径上有 Hikari,则这个基本设置不起作用,因为 Hikari 没有 url 属性(但是确实具有 jdbcUrl 属性)。在这种情况下,你必须重写你的配置如下:

app.datasource.jdbc-url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.maximum-pool-size=30

可以通过强制使用连接池并返回专用实现而不是 DataSource 来固定。不能在运行时更改实现,但选项列表将是显式的。

下面的示例演示如何用 DataSourceBuilder 创建 HikariDataSource:

@Bean
@ConfigurationProperties("app.datasource")
public HikariDataSource dataSource() {
	return DataSourceBuilder.create().type(HikariDataSource.class).build();
}

你甚至可以进一步利用 DataSourceProperties 为你所做的工作,即,如果没有提供 URL,则通过为默认嵌入式数据库提供合理的用户名和密码。你可以轻松地从任何 DataSourceProperties 对象的状态初始化 DataSourceBuilder,因此还可以注入 Spring Boot 自动创建的数据源。但是,这会将配置分成两个名称空间:spring.datasource 的 url、username、password、type 和 driver,以及你自定义名称空间(aapp.datasource)上的其余部分。为了避免这一点,你可以在自定义命名空间上重新定义自定义 DataSourceProperties,如下面的示例所示:

@Bean
@Primary
@ConfigurationProperties("app.datasource")
public DataSourceProperties dataSourceProperties() {
	return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource")
public HikariDataSource dataSource(DataSourceProperties properties) {
	return properties.initializeDataSourceBuilder().type(HikariDataSource.class)
			.build();
}

这个设置使你与 Spring Boot 默认为你所做的工作保持同步,只是选择了一个专用的连接池(在代码中),并且在相同的名称空间中公开了它的设置。因为 DataSourceProperties 正在为你处理 url/jdbcUrl 转译,你可以将其配置如下:

app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.maximum-pool-size=30
[Note] Note

因为你的自定义配置选择与 Hikari 一起使用,所以 app.datasource.type 没有任何效果。在实践中,构造函数用任何可能设置的值初始化,然后通过调用 .type() 来重写。

更多详细信息请参阅 “Spring Boot features” 部分中的 “Section 29.1, “Configure a DataSource”” 和 DataSourceAutoConfiguration 类。

81.2 配置两个数据源

如果需要配置多个数据源,可以应用前一节中描述的相同技巧。但是,你必须将一个 DataSource 实例标记为 @Primary,因为以后的各种自动配置都希望能够按类型获取一个。

如果你创建了自己的 DataSource,则自动配置退回。在下面的示例中,我们提供与主数据源上的自动配置提供的完全相同的特征集:

@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
	return new DataSourceProperties();
}

@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSource firstDataSource() {
	return firstDataSourceProperties().initializeDataSourceBuilder().build();
}

@Bean
@ConfigurationProperties("app.datasource.second")
public BasicDataSource secondDataSource() {
	return DataSourceBuilder.create().type(BasicDataSource.class).build();
}
[Tip] Tip

firstDataSourceProperties 必须标记为 @Primary,以便数据库初始化器特性使用你的副本(如果使用初始化器)。

两个数据源也绑定到高级定制。例如,可以将它们配置如下:

app.datasource.first.type=com.zaxxer.hikari.HikariDataSource
app.datasource.first.maximum-pool-size=30

app.datasource.second.url=jdbc:mysql://localhost/test
app.datasource.second.username=dbuser
app.datasource.second.password=dbpass
app.datasource.second.max-total=30

你也可以将相同的概念应用到第二个 DataSource ,如下面的示例所示:

@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
	return new DataSourceProperties();
}

@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSource firstDataSource() {
	return firstDataSourceProperties().initializeDataSourceBuilder().build();
}

@Bean
@ConfigurationProperties("app.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
	return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.second")
public DataSource secondDataSource() {
	return secondDataSourceProperties().initializeDataSourceBuilder().build();
}

前面的示例在自定义名称空间上配置两个数据源,其逻辑与 Spring Boot 在自动配置中使用的相同。

81.3 使用 Spring Data 库

Spring Data 可以创建各种种类的 @Repository 接口的实现。Spring Boot 为你处理所有这些问题,只要那些 @Repositories 包含在 @EnableAutoConfiguration 类的同一个包(或子包)中。

对于许多应用程序,你只需要在类路径上设置正确的 Spring Data 依赖项(JPA 的 spring-boot-starter-data-jpa,Mongodb 的 spring-boot-starter-data-mongodb),并创建一些库接口来处理 @Entity 对象。JPA 示例和 Mongodb 示例中的示例。

Spring Boot 尝试根据所找到的 @EnableAutoConfiguration 来猜测 @Repository 定义的位置。要获得更多的控制,请使用 @EnableJpaRepositories 注解(来自 Spring Data JPA)。

有关 Spring Data 的更多信息请参阅 Spring Data 项目页面。

81.4 从 Spring 配置分离 @Entity 定义

Spring Boot 尝试根据它找到的 @EnableAutoConfiguration 来猜测 @Entity 定义的位置。为了获得更多的控制,可以使用 @EntityScan 注解,如下面的示例所示:

@Configuration
@EnableAutoConfiguration
@EntityScan(basePackageClasses=City.class)
public class Application {

	//...

}

81.5 配置 JPA 属性

Spring Data JPA 已经提供了一些独立于供应商的配置选项(例如用于 SQL 日志记录的那些选项),Spring Boot 将这些选项以及 Hibernate 的一些其它选项公开为外部配置属性。其中一些是根据上下文自动检测的,因此不必设置它们。

spring.jpa.hibernate.ddl-auto 是一种特殊情况,因为根据运行时条件,它有不同的默认值。如果使用嵌入式数据库,并且没有模式管理器(如 Liquibase 或 Flyway)处理 DataSource,则默认情况下 create-drop。在所有其他情况下,默认值为 none。

还可以基于当前 DataSource 自动检测要使用的方言,但如果希望显式地进行设置,并在启动时绕过该检查,则可以自己设置 spring.jpa.database。

[Note] Note

指定 database 指导配置一个良好定义的 Hibernate 方言。几个数据库有不止一个 Dialect,这可能不符合你的需要。在这种情况下,你可以将 spring.jpa.database 设置为 default,以让 Hibernate 了解情况,或者通过设置 spring.jpa.database-platform 属性来设置方言。

在下面的示例中展示了最常见的设置选项:

spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy
spring.jpa.show-sql=true

此外,在创建本地 EntityManagerFactory 时,spring.jpa.properties.* 中的所有属性都作为普通 JPA 属性传递(前缀被剥离)。

[Tip] Tip

如果需要对 Hibernate 属性应用高级定制,请考虑注册将在创建 EntityManagerFactory 之前调用的 HibernatePropertiesCustomizer bean。这优先于自动配置应用的任何内容。

81.6 配置 Hibernate 命名策略

Hibernate 使用两种不同的命名策略来将名称从对象模型映射到相应的数据库名称。物理和隐式策略实现的完全限定类名可以通过分别设置 spring.jpa.hibernate.naming.physical-strategy 和 spring.jpa.hibernate.naming.implicit-strategy 属性来配置。或者,如果 ImplicitNamingStrategy 或 PhysicalNamingStrategy bean 在应用程序上下文中可用,则 Hibernate 将被自动配置为使用它们。

默认情况下,Spring Boot 用 SpringPhysicalNamingStrategy 配置物理命名策略。这个实现提供了与 Hibernate 4 相同的表结构:所有点都被下划线替换,驼峰式也被下划线替换。默认情况下,所有的表名都是以小写方式生成的,但是如果模式需要,则可以重写该标志。

例如,TelephoneNumber 实体被映射到 code class="literal">telephone_number 表。

如果你喜欢使用 Hibernate 5 的默认值,请设置以下属性:

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

或者,你可以配置以下 bean:

@Bean
public PhysicalNamingStrategy physicalNamingStrategy() {
	return new PhysicalNamingStrategyStandardImpl();
}

更多详细信息请参阅 HibernateJpaAutoConfiguration 和 JpaBaseConfiguration。

81.7 使用自定义实体管理工厂

要完全控制 EntityManagerFactory 的配置,需要添加一个名为 ‘entityManagerFactory’ 的 @Bean 。Spring Boot 自动配置在存在该类型 bean 的情况下关闭其实体管理器。

81.8 使用两个实体管理器

即使默认的 EntityManagerFactory 工作正常,你也需要定义一个新的。否则,该类型的第二个 bean 的存在会关闭默认值。为了便于操作,可以使用 Spring Boot 提供的方便的 EntityManagerBuilder。或者,你可以直接从 Spring ORM 中获取 LocalContainerEntityManagerFactoryBean,如下面的示例所示:

// add two data sources configured as above

@Bean
public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(
		EntityManagerFactoryBuilder builder) {
	return builder
			.dataSource(customerDataSource())
			.packages(Customer.class)
			.persistenceUnit("customers")
			.build();
}

@Bean
public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory(
		EntityManagerFactoryBuilder builder) {
	return builder
			.dataSource(orderDataSource())
			.packages(Order.class)
			.persistenceUnit("orders")
			.build();
}

上面的配置几乎是独立工作的。为了完成这个构想,你还需要为两个 EntityManagers 配置 TransactionManagers。如果将其中一个标记为 @Primary,则可以在 Spring Boot 中由默认的 JpaTransactionManager 拾取。另一个必须显式注入到新的实例中。或者,你可能可以使用跨越两者的 JTA 事务管理器。

如果使用 Spring Data,则需要相应地配置 @EnableJpaRepositories,如下面的示例所示:

@Configuration
@EnableJpaRepositories(basePackageClasses = Customer.class,
		entityManagerFactoryRef = "customerEntityManagerFactory")
public class CustomerConfiguration {
	...
}

@Configuration
@EnableJpaRepositories(basePackageClasses = Order.class,
		entityManagerFactoryRef = "orderEntityManagerFactory")
public class OrderConfiguration {
	...
}

81.9 使用传统的 persistence.xml 文件

Spring Boot 默认情况下不会搜索或使用 META-INF/persistence.xml。如果喜欢使用传统的 persistence.xml,则需要定义自己的 LocalEntityManagerFactoryBean 类型 @Bean(ID 为 ‘entityManagerFactory’),并在其中设置持久化单元名称。

默认的设置请参阅 JpaBaseConfiguration。

81.10 使用 Spring Data JPA 和 Mongo 存储库

Spring Data JPA 和 Spring Data Mongo 都可以自动为你创建存储库实现。如果它们都出现在类路径上,你可能需要做一些附加的配置来告诉 Spring Boot 要创建哪些存储库。最明确的方法是使用标准的 Spring Data @EnableJpaRepositories 和 @EnableMongoRepositories 注解,并提供 Repository 接口的位置。

还可以使用标志( spring.data.*.repositories.enabled 和 spring.data.*.repositories.type)在外部配置中切换自动配置的存储库。例如,如果希望关闭 Mongo 存储库并仍然使用自动配置的 MongoTemplate,那么这样做很有用。

对于其它自动配置的 Spring Data 存储库类型(Elasticsearch、Solr 和其它)也存在相同的障碍和相同的特性。要与它们一起工作,请相应地更改注解和标志的名称。

81.11 暴露 Spring Data 存储库为 REST 节点

Spring Data REST 可以为你将 Repository 实现暴露为 REST 节点,只要已经为应用程序启用了 Spring MVC。

Spring Boot 暴露了一组定制 RepositoryRestConfiguration 的有用属性(来自 spring.data.rest 命名空间)。如果需要提供额外的自定义,则应该使用 RepositoryRestConfigurer bean。

[Note] Note

如果没有在自定义 RepositoryRestConfigurer 中指定任何顺序,则在一个 Spring Boot 使用内部之后运行。如果需要指定一个顺序,请确保其高于 0。

81.12 配置 JPA 使用的组件

如果你想要配置 JPA 使用的组件,则需要确保组件在 JPA 之前初始化。当组件自动配置时,Spring Boot 会为你处理这个问题。例如,当 Flyway 是自动配置的,Hibernate 被配置为依赖于 Flyway,以便在 Hibernate 尝试使用它之前,Flyway 有机会初始化数据库。

如果你自己配置组件,则可以使用 EntityManagerFactoryDependsOnPostProcessor 子类作为设置必要依赖项的方便方法。例如,如果使用 Elasticsearch 的 Hibernate Search 作为其索引管理器,则必须将任何 EntityManagerFactory bean 配置为依赖于 elasticsearchClient bean,如下例所示:

/**
 * {@link EntityManagerFactoryDependsOnPostProcessor} that ensures that
 * {@link EntityManagerFactory} beans depend on the {@code elasticsearchClient} bean.
 */
@Configuration
static class ElasticsearchJpaDependencyConfiguration
		extends EntityManagerFactoryDependsOnPostProcessor {

	ElasticsearchJpaDependencyConfiguration() {
		super("elasticsearchClient");
	}

}

81.13 用两个数据源配置 jOOQ

如果你需要使用多个数据源的 jOOQ,你应该为每个数据源创建自己的 DSLContext。更多详细信息请参阅 JooqAutoConfiguration。

[Tip] Tip

特别是,JooqExceptionTranslator 和 SpringTransactionProvider 可以被重用,以提供与自动配置对单个 DataSource 所做的功能类似的功能。

82. 数据库初始化

SQL 数据库可以根据你的堆栈的不同以不同的方式初始化。当然,如果数据库是一个单独的进程,你也可以手动进行。

82.1 使用 JPA 初始化一个数据库

JPA 具有 DDL 生成的特性,这些设置可以在数据库启动时运行。这是通过两个外部属性来控制的:

  • spring.jpa.generate-ddl (boolean) 打开和关闭该功能,并且与供应商无关。
  • spring.jpa.hibernate.ddl-auto (enum) 是一个以更细粒度的方式控制行为的 Hibernate 特性。这个特性稍后将在本指南中更详细地描述。

82.2 使用 Hibernate 初始化一个数据库

你可以显式地设置 spring.jpa.hibernate.ddl-auto,并且标准的 Hibernate 属性值是 none、validate、update、create 和 create-drop。Spring Boot 根据它是否认为你的数据库被嵌入而为你选择默认值。如果没有检测到模式管理器,或者在所有其它情况下都不存在,则默认为 create-drop。通过查看 Connection 类型来检测嵌入式数据库。hsqldb、h2 和 derby 被嵌入,而其它则不被嵌入。当从内存中切换到真实数据库时,要小心,不要假设新平台中存在表和数据。你必须显式地设置 ddl-auto,或者使用其它机制之一初始化数据库。

[Note] Note

可以通过启用 org.hibernate.SQL 日志记录器来输出模式创建。如果启用调试模式,将自动为你完成此操作。

此外,如果 Hibernate 从头开始创建该模式(即,如果 ddl-auto 属性设置为 create 或 create-drop),则在启动时执行类路径根目录下名为 import.sql 的文件。这对于演示和测试是非常有用的,但是如果你很小心,那可能不是你想在生产过程中出现在类路径中的。它是 Hibernate 特性(与 Spring 无关)。

82.3 初始化数据库

Spring Boot 可以自动创建 DataSource 的模式(DDL 脚本)并初始化它(DML 脚本)。它从标准根类路径位置加载 SQL:分别是 schema.sql 和 data.sql。此外,Spring Boot 处理 schema-${platform}.sql 和 data-${platform}.sql 文件(如果存在),其中 platform 是 spring.datasource.platform 的值。这允许你在必要时切换到特定于数据库的脚本。例如,你可能会选择将其设置为数据库的供应商名称(hsqldb、h2、oracle、mysql、postgresql 等等)。

[Note] Note

Spring Boot 自动创建嵌入式 DataSource 的模式。此行为可以通过使用 spring.datasource.initialization-mode 属性进行自定义。例如,如果你想始终初始化 DataSource 而不考虑其类型:

spring.datasource.initialization-mode=always

默认情况下,Spring Boot 启用 Spring JDBC 初始化器的故障快速特性。这意味着,如果脚本导致异常,则应用程序无法启动。可以通过设置 spring.datasource.continue-on-error 来调整这种行为。

[Note] Note

在基于 JPA 的应用程序中,你可以选择让 Hibernate 创建模式或使用 schema.sql,但不能同时做到这两个。如果使用 schema.sql,请确保禁用 spring.jpa.hibernate.ddl-auto.

82.4 初始化 Spring 批处理数据库

如果使用 Spring 批处理,它会用 SQL 初始化脚本对最流行的数据库平台进行预打包。Spring Boot 可以检测数据库类型并在启动时执行这些脚本。如果使用嵌入式数据库,默认情况下会发生这种情况。还可以为任何数据库类型启用它,如下面的示例所示:

spring.batch.initialize-schema=always

还可以通过设置 spring.batch.initialize-schema=never 显式地关闭初始化。

82.5 使用更高级别的数据库迁移工具

Spring Boot 支持两种更高级的迁移工具:Flyway 和 Liquibase。

82.5.1 在启动时执行 Flyway 数据库迁移

若要在启动时自动运行 Flyway 数据库迁移,请将 org.flywaydb:flyway-core 添加到类路径中。

迁移是以 V<VERSION>__<NAME>.sql 的脚本(带有 <VERSION> 下划线分离的版本,例如 ‘1’ 或 ‘2_1’)。默认情况下,它们位于名为 classpath:db/migration 的文件夹中,但是你可以通过设置 spring.flyway.locations 来修改该位置。还可以添加特殊的 {vendor} 占位符来使用特定于供应商的脚本。假设如下:

spring.flyway.locations=db/migration/{vendor}

前面的配置没有使用 db/migration,而是根据数据库的类型(例如,MySQL 的 db/migration/mysql)设置要使用的文件夹。支持数据库的列表在 DatabaseDriver 中存在。

从 flyway-core 中 的 Flyway 类可以看到可用的设置的细节,如模式和其他。此外,Spring Boot 提供了一小组属性(在 FlywayProperties 中),可用于禁用迁移或关闭位置检查。Spring Boot 调用 Flyway.migrate() 来执行数据库迁移。如果你想要更多的控制,请提供一个实现 FlywayMigrationStrategy 的 @Bean。

Flyway 支持 SQL 和 Java callbacks。若要使用基于 SQL 的回调,请将回调脚本放在 classpath:db/migration 文件夹。要使用基于 Java 的回调,创建一个或多个实现 Callback 的 bean。任何这样的 bean 都会自动注册到 Flyway。它们可以通过使用 @Order 或实现 Ordered 来排序。也可以检测实现已弃用的 FlywayCallback 接口的bean,但是它们不能与 Callback bean 一起使用。

默认情况下, Flyway 在上下文中自动调用(@Primary)DataSource,并将其用于迁移。如果您喜欢使用不同的 DataSource,可以创建一个并将其 @Bean 标记为 @FlywayDataSource。如果这样做,并希望有两个数据源,请记住创建另一个数据源,并将其标记为 @Primary。或者,可以通过在外部属性中设置 spring.flyway.[url,user,password] 来使用 Flyway 的本地 DataSource。设置 spring.flyway.url 或 spring.flyway.user 足以使 Flyway 使用其自己的 DataSource。如果没有设置这三个属性中的任何一个,则将使用其等效 spring.datasource 属性的值。

这有一个 Flyway 示例,你可以看到如何设置的东西。

你还可以使用 Flyway 为特定场景提供数据。例如,你可以将特定于测试的迁移放在 src/test/resources 中,并且它们仅在应用程序开始进行测试时才运行。此外,你可以使用特定于配置文件的配置文件来定制 spring.flyway.locations,以便某些迁移仅在特定配置文件处于激活状态时才运行。例如,在 application-dev.properties 中,你可以指定以下设置:

spring.flyway.locations=classpath:/db/migration,classpath:/dev/db/migration

通过该设置,只有当 dev 配置文件处于激活状态时,dev/db/migration 中的迁移才会运行。

82.5.2 在启动时执行 Liquibase 数据库迁移

若要在启动时自动运行 Liquibase 数据库迁移,请将 org.liquibase:liquibase-core 添加到类路径中。

默认情况下,从 db/changelog/db.changelog-master.yaml 读取主要的更改日志,但是可以通过设置 spring.liquibase.change-log 来更改位置。除了 YAML 之外,Liquibase 还支持 JSON、XML 和 SQL 更改日志格式。

默认情况下,Liquibase 在你的上下文中调用(@Primary)DataSource,并将其用于迁移。如果需要使用不同的数据源,可以创建一个并将其 @Bean 标记为 @LiquibaseDataSource。如果这样做,并且你想要两个数据源,请记住创建另一个数据源,并将其标记为 @Primary。或者,可以通过在外部属性中设置 spring.liquibase.[url,user,password] 来使用 Liquibase 的本地 DataSource。设置 spring.liquibase.url 或 spring.liquibase.user 足以使 Liquibase 使用自己的 DataSource。如果没有设置这三个属性中的任何一个,则将使用其等效 spring.datasource 属性的值。

有关可用设置的详细信息,如上下文、默认模式 和 其它,请参阅 LiquibaseProperties。

这有一个 Liquibase 示例,这样你就可以看到如何设置。