本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

本节为一些在使用Spring Boot时经常出现的 "如何做…​…​ " 问题提供答案。 它的覆盖面并不全面,但确实涵盖了相当多的内容。

如果你有一个特定的问题,而我们在这里没有涉及,你可能想检查一下 stackoverflow.com ,看看是否有人已经提供了答案。这也是一个提出新问题的好地方(请使用 spring-boot 标签)。

我们也非常乐意扩展这个部分。如果你想添加一个 ‘how-to’,请给我们发送一个 pull request

1. Spring Boot 应用

本节包括与Spring Boot应用程序直接相关的主题。

1.1. 创建你自己的故障分析器(FailureAnalyzer)

FailureAnalyzer 是一种很好的方法,可以在启动时拦截异常,并将其转化为人类可读的消息,并将其包裹在 FailureAnalysis 中。Spring Boot为应用 application context 相关的异常、JSR-303验证等提供了这样一个analyzer。你也可以创建你自己的。

AbstractFailureAnalyzerFailureAnalyzer 的一个方便的扩展,它可以检查要处理的异常中是否有指定的异常类型。 你可以在此基础上进行扩展,这样你的实现只有在实际存在的情况下才有机会处理该异常。 如果由于某种原因,你不能处理这个异常,那么返回 null,让其他实现有机会处理这个异常。

FailureAnalyzer 的实现必须在 META-INF/spring.factories 中注册。 下面的例子注册了 ProjectConstraintViolationFailureAnalyzer

org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.ProjectConstraintViolationFailureAnalyzer
如果你需要访问 BeanFactoryEnvironment,你的 FailureAnalyzer 可以分别实现 BeanFactoryAwareEnvironmentAware

1.2. 排除“自动配置”的故障

Spring Boot的自动配置尽力 "做正确的事",但有时会失败,而且很难说清原因。

在任何Spring Boot ApplicationContext 中都有一个非常有用的 ConditionEvaluationReport。如果你启用 DEBUG日 志输出,你就可以看到它。如果你使用 spring-boot-actuator (见Actuator章节),也有一个 conditions 端点,可以将报告以JSON格式呈现出来。使用该端点来调试应用程序,看看Spring Boot在运行时添加了哪些功能(以及哪些没有添加)。

还有很多问题可以通过查看源代码和Javadoc来回答。 在阅读代码时,请记住以下经验法则。

  • 寻找名为 *AutoConfiguration 的类,并阅读其来源。 特别注意 @Conditional* 注解,找出它们启用的功能和时间。 在命令行中添加 --debug 或系统属性(System property) -Ddebug,以便在控制台中获得所有在你的应用程序中做出的自动配置决定的日志。 在一个启用了执行器的运行中的应用程序中,查看 conditions 端点(/actuator/conditions 或相当于JMX)以获得相同的信息。

  • 寻找属于 @ConfigurationProperties 的类( ServerProperties),并从那里读取可用的外部配置选项。 @ConfigurationProperties 注解有一个 name 属性,作为外部属性的前缀。 因此,ServerPropertiesprefix="server",它的配置属性是 server.portserver.address,以及其他。 在一个启用了actuator的运行中的应用程序中,查看 configprops 端点。

  • 寻找 Binder 上的 bind 方法的用途,以轻松的方式从 Environment 中明确提取配置值。 它经常与前缀一起使用。

  • 寻找直接与 Environment 绑定的 @Value 注解。

  • 寻找 @ConditionalOnExpression 注解,该注解在响应SpEL表达式时打开或关闭功能,通常用从 Environment 中解析的占位符进行评估。

1.3. 在启动前定制环境(Environment)或应用上下文(ApplicationContext)

一个 SpringApplicationApplicationListenersApplicationContextInitializers,用于对上下文或环境进行定制。 Spring Boot从 META-INF/spring.factories 中加载了许多这样的定制,供内部使用。 有不止一种方法可以注册额外的定制。

  • 在运行之前,通过调用 SpringApplication 上的 addListenersaddInitializers 方法,以编程的方式对每个application进行操作。

  • 通过设置 context.initializer.classescontext.listener.classes 属性,以声明的方式,在每个application中。

  • 通过添加 META-INF/spring.factories 和打包一个jar文件,为所有的application声明性地提供一个库。

SpringApplication 向监听器发送一些特殊的 ApplicationEvents (有些甚至在context创建之前),然后为 ApplicationContext 发布的事件也注册监听器。完整的列表见 "Spring Boot特性" 部分的 “Application Event 和监听器”。

也可以通过使用 EnvironmentPostProcessor 在应用上下文刷新之前定制 Environment。 每个实现都应该在 META-INF/spring.factories 中注册,如以下例子所示。

org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor

实现可以加载任意文件并将其添加到 Environment 中。 例如,下面的例子从classpath加载一个YAML配置文件。

Java
import java.io.IOException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Resource path = new ClassPathResource("com/example/myapp/config.yml");
        PropertySource<?> propertySource = loadYaml(path);
        environment.getPropertySources().addLast(propertySource);
    }

    private PropertySource<?> loadYaml(Resource path) {
        Assert.isTrue(path.exists(), () -> "Resource " + path + " does not exist");
        try {
            return this.loader.load("custom-resource", path).get(0);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
        }
    }

}
Kotlin
import org.springframework.boot.SpringApplication
import org.springframework.boot.env.EnvironmentPostProcessor
import org.springframework.boot.env.YamlPropertySourceLoader
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.PropertySource
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.util.Assert
import java.io.IOException

class MyEnvironmentPostProcessor : EnvironmentPostProcessor {

    private val loader = YamlPropertySourceLoader()

    override fun postProcessEnvironment(environment: ConfigurableEnvironment, application: SpringApplication) {
        val path: Resource = ClassPathResource("com/example/myapp/config.yml")
        val propertySource = loadYaml(path)
        environment.propertySources.addLast(propertySource)
    }

    private fun loadYaml(path: Resource): PropertySource<*> {
        Assert.isTrue(path.exists()) { "Resource $path does not exist" }
        return try {
            loader.load("custom-resource", path)[0]
        } catch (ex: IOException) {
            throw IllegalStateException("Failed to load yaml configuration from $path", ex)
        }
    }

}
Environment 已经准备好了Spring Boot默认加载的所有常用属性源。 因此,可以从environment中获取文件的位置。 前面的例子将 custom-resource 属性源加在了列表的最后,这样一来,在任何一个通常的其他位置定义的键就会被优先考虑。 一个自定义的实现可以定义另一个顺序。
虽然在你的 @SpringBootApplication 上使用 @PropertySource 似乎是在 Environment 中加载自定义资源的一种便捷方式,但我们并不推荐这样做。 这样的属性源直到应用上下文被刷新时才会被添加到 Environment 中。 这对于配置某些属性来说已经太晚了,例如 logging.*spring.main.*,这些属性在刷新开始前就已经被读取。

1.4. 建立一个有层次结构的ApplicationContext(添加一个父级或根级Context)

你可以使用 ApplicationBuilder 类来创建父/子 ApplicationContext 层次结构。参见 "Spring Boot特性 "部分的 “features.html” 以了解更多信息。

1.5. 创建一个非WEB应用程序

并非所有的Spring应用都必须是WEB应用(或WEB服务)。如果你想在一个 main 方法中执行一些代码,但同时也要启动一个Spring应用来设置使用的基础设施,你可以使用Spring Boot的 SpringApplication 功能。一个SpringApplication会改变它的 ApplicationContext 类,这取决于它是否认为它需要一个Web应用程序。你可以做的第一件事是帮助它把与服务器相关的依赖(如servlet API)从classpath中移除。如果你不能这样做(例如,你从同一个代码库中运行两个应用程序),那么你可以明确地在 SpringApplication 实例上调用 setWebApplicationType(WebApplicationType.NONE) 或者设置 applicationContextClass 属性(通过Java API或者使用外部属性)。你想作为业务逻辑运行的应用程序代码可以作为 CommandLineRunner 实现,并作为 @Bean 定义丢入上Context。

2. 属性(Properties)和配置(Configuration)

本节包括关于设置和读取属性和配置设置以及它们与Spring Boot应用程序的互动的主题。

2.1. 在构建时自动扩展属性

与其硬编码一些在项目构建配置中也被指定的属性,不如使用现有的构建配置来自动扩展它们。 这在Maven和Gradle中都可以实现。

2.1.1. 使用Maven的自动属性扩展

你可以通过使用 resource filtering 从Maven项目中自动扩展属性。 如果你使用 spring-boot-starter-parent,你就可以用 @..@ 占位符来引用你的Maven ‘project properties’,如下例所示。

Properties
app.encoding=@project.build.sourceEncoding@
app.java.version=@java.version@
Yaml
app:
  encoding: "@project.build.sourceEncoding@"
  java:
    version: "@java.version@"
只有生产配置是这样被过滤的(换句话说,在 src/test/resources 上没有应用过滤)。
如果你启用 addResources 标志,spring-boot:run 目标可以直接将 src/main/resources 添加到classpath(用于热重载)。 这样做规避了 resource filtering 和这个功能。 相反,你可以使用 exec:java goal或自定义插件的配置。 更多细节请参见 插件使用页面。

如果你不使用 parent starter,你需要在你的 pom.xml 中的 <build/> 元素内包含以下元素。

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
    </resource>
</resources>

你还需要在 <plugins/> 中包含以下元素。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>2.7</version>
    <configuration>
        <delimiters>
            <delimiter>@</delimiter>
        </delimiters>
        <useDefaultDelimiters>false</useDefaultDelimiters>
    </configuration>
</plugin>
如果你在配置中使用标准的Spring占位符(如 ${placeholder}),那么 useDefaultDelimiters 属性很重要。 如果该属性没有设置为 false,这些可能会被构建时扩展。

2.1.2. 使用Gradle的自动属性扩展

你可以通过配置Java插件的 processResources task 来自动展开Gradle项目的属性,如下例所示。

tasks.named('processResources') {
    expand(project.properties)
}

然后你可以通过使用占位符来引用你的Gradle项目的属性,如下面的例子所示。

Properties
app.name=${name}
app.description=${description}
Yaml
app:
  name: "${name}"
  description: "${description}"
Gradle的 expand 方法使用Groovy的 SimpleTemplateEngine,它可以转换 ${..} 的标记。 ${..} 风格与Spring自己的属性占位符机制相冲突。 要将Spring的属性占位符与自动扩展一起使用,请将Spring的属性占位符转义如下。\${..}.

2.2. 将SpringApplication的配置外部化

SpringApplication 有bean属性setter,所以你可以在创建应用程序时使用其Java API来修改其行为。 另外,你也可以通过在 spring.main.* 中设置属性来外部化配置。 例如,在 application.properties 中,你可以有以下设置。

Properties
spring.main.web-application-type=none
spring.main.banner-mode=off
Yaml
spring:
  main:
    web-application-type: "none"
    banner-mode: "off"

那么Spring Boot的Banner就不会在启动时被打印出来,而且应用程序也不会启动嵌入式Web服务器。

外部配置中定义的属性覆盖并取代了用Java API指定的值,但 primary sources 是个明显的例外。 primary sources 是提供给 SpringApplication 构造函数的来源。

Java
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
object MyApplication {

    @JvmStatic
    fun main(args: Array<String>) {
        val application = SpringApplication(MyApplication::class.java)
        application.setBannerMode(Banner.Mode.OFF)
        application.run(*args)
    }

}

或者 SpringApplicationBuildersources(…​) 方法。

Java
import org.springframework.boot.Banner;
import org.springframework.boot.builder.SpringApplicationBuilder;

public class MyApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder()
            .bannerMode(Banner.Mode.OFF)
            .sources(MyApplication.class)
            .run(args);
    }

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.builder.SpringApplicationBuilder

object MyApplication {

    @JvmStatic
    fun main(args: Array<String>) {
        SpringApplicationBuilder()
            .bannerMode(Banner.Mode.OFF)
            .sources(MyApplication::class.java)
            .run(*args)
    }

}

鉴于上述例子,如果我们有以下配置。

Properties
spring.main.sources=com.example.MyDatabaseConfig,com.example.MyJmsConfig
spring.main.banner-mode=console
Yaml
spring:
  main:
    sources: "com.example.MyDatabaseConfig,com.example.MyJmsConfig"
    banner-mode: "console"

实际 application 将显示 banner(如配置所覆盖),并使用三个来源的 ApplicationContext。 应用程序的来源是。

  1. MyApplication (来自代码)

  2. MyDatabaseConfig (来自扩展配置)

  3. MyJmsConfig(来自扩展配置)

2.3. 改变应用程序的外部属性的位置

默认情况下,来自不同来源的属性会按照定义的顺序添加到Spring Environment 中(具体顺序见 “Spring Boot特性的” 中的 “features.html” 部分)。

你也可以提供以下System properties(或环境变量)来改变行为。

  • spring.config.nameSPRING_CONFIG_NAME)。默认为应用程序作为文件名的root。

  • spring.config.locationSPRING_CONFIG_LOCATION)。要加载的文件(如classpath资源或URL)。 为该文件设置了一个单独的 Environment 属性源,它可以被system properties、环境变量或命令行覆盖。

无论你在environment中设置了什么,Spring Boot总是按照上述方法加载 application.properties。 默认情况下,如果使用YAML,那么扩展名为 ‘.yml’,‘.yaml’ 的文件也会被添加到列表中。

如果你想了解正在加载的文件的详细信息,你可以将 org.springframework.boot.context.config 的日志级别设置trace

2.4. 使用 "简短" 的命令行参数

有些人喜欢使用(例如)--port=9000 而不是 --server.port=9000 来在命令行上设置配置属性。你可以通过在 application.properties 中使用占位符来启用这种行为,如下面的例子所示。

Properties
server.port=${port:8080}
Yaml
server:
  port: "${port:8080}"
如果你继承了 spring-boot-starter-parent POM, maven-resources-plugins 的默认过滤标记已从 ${*} 改为 @ (即 @maven.token@ 而不是 ${maven.token}),以防止与Spring风格的占位符冲突。如果你直接为 application.properties 启用了 Maven filtering 功能,你可能还想改变默认的过滤 token,使用 其他分隔符
在这种特定情况下,端口绑定在 Heroku 或 Cloud Foundry 这样的PaaS环境中工作。 在这两个平台中,PORT 环境变量是自动设置的,Spring可以与 Environment 属性的大写同义词进行绑定。

2.5. 对外部属性使用YAML

YAML是JSON的超集,因此,它是一种方便的语法,用于以分层格式存储外部属性,如下例所示。

spring:
  application:
    name: "cruncher"
  datasource:
    driver-class-name: "com.mysql.jdbc.Driver"
    url: "jdbc:mysql://localhost/test"
server:
  port: 9000

创建一个名为 application.yaml 的文件,并将其放在classpath的根目录中。 然后将 snakeyaml 添加到你的依赖项中(Maven 座标 org.yaml:snakeyaml,如果你使用 spring-boot-starter 就已经包含了)。 YAML文件被解析为Java的 Map<String,Object> (就像JSON对象),Spring Boot会将该Map扁平化,使其只有一层,并且有句点分隔的键,就像许多人习惯于使用Java中的 Properties 文件一样。

前面的YAML例子对应于下面的 application.properties 文件。

spring.application.name=cruncher
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/test
server.port=9000

参见 "Spring Boot特性" 部分的 “features.html” ,了解更多YAML信息。

2.6. 设置活动的 Spring Profile

Spring Environment 有这方面的API,但你通常会设置一个系统属性(spring.profiles.active)或一个操作系统环境变量(SPRING_PROFILES_ACTIVE)。另外,你可以用 -D 参数来启动你的应用程序(记得把它放在main类或jar存档之前),如下所示。

$ java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar

在Spring Boot中,你也可以在 application.properties 中设置活动配置文件,如下例所示。

Properties
spring.profiles.active=production
Yaml
spring:
  profiles:
    active: "production"

以这种方式设置的值会被系统属性或环境变量设置取代,但不会被 SpringApplicationBuilder.profiles() 方法取代。 因此,后者的Java API可以用来增加配置文件而不改变默认值。

更多信息见 "Spring Boot特性" 部分的 “features.html” 。

2.7. 设置默认配置文件(Profile)名称

默认配置文件是在没有激活配置文件的情况下启用的配置文件。 默认情况下,默认配置文件的名称是 default,但可以使用系统属性(spring.profiles.default)或操作系统环境变量(SPRING_PROFILES_DEFAULT)来改变它。

在Spring Boot中,你也可以在 application.properties 中设置默认的配置文件名称,如下例所示。

Properties
spring.profiles.default=dev
Yaml
spring:
  profiles:
    default: "dev"

更多信息见 "Spring Boot特性" 部分的 “features.html”。

2.8. 根据环境改变配置

Spring Boot支持多文档YAML和Properties文件(详见 features.html),可以根据活动配置文件有条件地激活。

如果一个文档包含 spring.config.activate.on-profile key,那么 profiles 值(逗号分隔的profile列表或profile表达式)被送入 Environment.acceptsProfiles() 方法。如果配置文件表达式匹配,那么该文档就会被包含在最终的合并中(否则就不包含),如下面的例子所示。

Properties
server.port=9000
#---
spring.config.activate.on-profile=development
server.port=9001
#---
spring.config.activate.on-profile=production
server.port=0
Yaml
server:
  port: 9000
---
spring:
  config:
    activate:
      on-profile: "development"
server:
  port: 9001
---
spring:
  config:
    activate:
      on-profile: "production"
server:
  port: 0

在前面的例子中,默认的端口是9000。 然而,如果名为 ‘development’ 的 Spring profile 处于活动状态,那么端口是9001。 如果 ‘production’ 处于活动状态,那么端口就是0。

文件是按照遇到的顺序合并的。 后面的值覆盖前面的值。

2.9. 发现外部属性的内置选项

Spring Boot在运行时将来自 application.properties(或 YAML 文件和其他地方)的外部属性绑定到应用程序中。 这里没有(技术上也不可能有)一个单一位置的所有支持属性的详尽列表,因为贡献可能来自你classpath上的其他jar文件。

一个运行中的具有Actuator功能的应用程序有一个 configprops 端点,显示所有通过 @ConfigurationProperties 可用的绑定和可绑定的属性。

附录中包括一个 application.properties 的例子,列出了Spring Boot支持的最常见的属性。确定的列表来自于搜索源代码中的 @ConfigurationProperties@Value 注解,以及偶尔使用的Binder。关于加载属性的确切顺序,请参阅 "features.html"。

3. 嵌入式WEB服务器

每个Spring Boot Web应用程序都包括一个嵌入式Web服务器。 这一特性导致了一些操作问题,包括如何改变嵌入式服务器以及如何配置嵌入式服务器。 本节将回答这些问题。

3.1. 使用其他的Web服务器

许多Spring Boot starter 包括默认的嵌入式容器。

  • 对于 servlet stack 应用程序,spring-boot-starter-web 通过包含 spring-boot-starter-tomcat 来包含Tomcat,但是你可以使用 spring-boot-starter-jettyspring-boot-starter-undertow 来代替。

  • 对于 reactive stack 应用程序,spring-boot-starter-webflux 通过包括 spring-boot-starter-reactor-netty 包括Reactor Netty,但你可以使用 spring-boot-starter-tomcatspring-boot-starter-jetty,或 spring-boot-starter-undertow 代替。

当切换到不同的HTTP服务器时,你需要把默认的依赖关系换成你需要的那些。 为了帮助完成这一过程,Spring Boot为每个支持的HTTP服务器提供了一个单独的starter。

下面的Maven示例展示了如何为Spring MVC排除Tomcat并包含Jetty。

<properties>
    <servlet-api.version>3.1.0</servlet-api.version>
</properties>
<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>
<!-- 使用 Jetty 代替 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
servlet API的版本已被覆盖,因为与Tomcat 9和Undertow 2不同,Jetty 9.4不支持Servlet 4.0。

如果你想使用Jetty 10,它确实支持servlet 4.0,你可以按照下面的例子来做。

<properties>
    <jetty.version>10.0.8</jetty.version>
</properties>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- Exclude the Tomcat dependency -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <exclusions>
        <!-- Exclude the Jetty-9 specific dependencies -->
        <exclusion>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-server</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>javax-websocket-server-impl</artifactId>
        </exclusion>
    </exclusions>
</dependency>

请注意,除了排除Tomcat starter之外,还需要排除一些Jetty9特有的依赖关系。

下面的Gradle例子配置了必要的依赖关系和 模块替换,以便在Spring WebFlux中使用Undertow代替Reactor Netty。

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-undertow"
    implementation "org.springframework.boot:spring-boot-starter-webflux"
    modules {
        module("org.springframework.boot:spring-boot-starter-reactor-netty") {
            replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Reactor Netty")
        }
    }
}
spring-boot-starter-reactor-netty 需要使用 WebClient 类,所以你可能需要保持对Netty的依赖,即使你需要包含一个不同的HTTP服务器。

3.2. 禁用Web服务器

如果你的classpath包含启动Web服务器的必要位,Spring Boot将自动启动它。 要禁用这种行为,请在你的 application.properties 中配置 WebApplicationType,如以下例子所示。

Properties
spring.main.web-application-type=none
Yaml
spring:
  main:
    web-application-type: "none"

3.3. 修改HTTP端口

在一个独立的应用程序中,主要的HTTP端口默认为 8080,但可以用 server.port 来设置(例如,在 application.properties 中或作为一个系统属性)。 由于放松了对 Environment 值的绑定,你也可以使用 SERVER_PORT(例如,作为一个操作系统环境变量)。

要完全关闭HTTP端点,但仍然创建一个 WebApplicationContext,使用 server.port=-1 (这样做有时对测试很有用)。

更多细节,请参阅 "Spring Boot特性" 部分的 “web.html”,或 ServerProperties 源代码。

3.4. 使用一个随机的未分配的HTTP端口

要扫描一个空闲的端口(使用操作系统的本地端口以防止冲突),使用 server.port=0

3.5. 在运行时发现HTTP端口

你可以从日志输出或从 WebServerApplicationContext 中通过其 WebServer 访问服务器正在运行的端口。 获得该信息并确定其已被初始化的最好方法是添加一个 @Bean 类型的 ApplicationListener<WebServerInitializedEvent> 并在事件发布时将容器拉出。

使用 @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) 的测试也可以通过使用 @LocalServerPort 注解将实际端口注入一个字段,如下例所示。

Java
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {

    @LocalServerPort
    int port;

    // ...

}
Kotlin
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.server.LocalServerPort

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyWebIntegrationTests {

    @LocalServerPort
    var port = 0

    // ...

}

@LocalServerPort@Value("${local.server.port}") 的一个元注解。 不要试图在普通应用程序中注入端口。 正如我们刚刚看到的,该值只在容器被初始化后才被设置。 与测试相反,应用程序代码回调会被提前处理(在值实际可用之前)。

3.6. 启用HTTP响应压缩

Jetty、Tomcat、Reactor Netty和Undertow都支持HTTP响应压缩。 它可以在 application.properties 中启用,如下所示。

Properties
server.compression.enabled=true
Yaml
server:
  compression:
    enabled: true

默认情况下,响应必须至少有2048字节的长度才能进行压缩。 你可以通过设置 server.compression.min-response-size 属性来配置这种行为。

默认情况下,只有当响应的内容类型是下列之一时才会被压缩。

  • text/html

  • text/xml

  • text/plain

  • text/css

  • text/javascript

  • application/javascript

  • application/json

  • application/xml

你可以通过设置 server.compression.mime-types 属性来配置这种行为。

3.7. 配置 SSL

SSL可以通过设置各种 server.ssl.* 属性来声明性地配置,通常在 application.propertiesapplication.yaml 中。 下面的例子显示了使用Java KeyStore文件设置SSL属性。

Properties
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-password=another-secret
Yaml
server:
  port: 8443
  ssl:
    key-store: "classpath:keystore.jks"
    key-store-password: "secret"
    key-password: "another-secret"

下面的例子显示了使用PEM编码的证书和私钥文件设置SSL属性。

Properties
server.port=8443
server.ssl.certificate=classpath:my-cert.crt
server.ssl.certificate-private-key=classpath:my-cert.key
server.ssl.trust-certificate=classpath:ca-cert.crt
Yaml
server:
  port: 8443
  ssl:
    certificate: "classpath:my-cert.crt"
    certificate-private-key: "classpath:my-cert.key"
    trust-certificate: "classpath:ca-cert.crt"

另外,SSL信任材料可以配置在一个 SSL bundle 中,并应用到Web服务器,如本例所示:

Properties
server.port=8443
server.ssl.bundle=example
Yaml
server:
  port: 8443
  ssl:
    bundle: "example"

关于所有支持的属性的细节,请参见 Ssl

使用像前面例子那样的配置意味着应用程序不再支持8080端口的普通HTTP连接器。 Spring Boot不支持通过 application.properties 同时配置HTTP连接器和HTTPS连接器。 如果你想同时拥有,你需要以编程方式配置其中一个。 我们建议使用 application.properties 来配置HTTPS,因为HTTP连接器是两者中比较容易编程配置的。

3.8. 配置 HTTP/2

你可以通过 server.http2.enabled 配置属性在你的Spring Boot应用程序中启用HTTP/2支持。h2(HTTP/2 over TLS)和 h2c(HTTP/2 over TCP)都被支持。要使用 h2,必须同时启用SSL。当SSL未被启用时,将使用 h2c。例如,当你的应用程序在一个执行 TLS termination的代理服务器后面运行时,你可能想使用 h2c

3.8.1. Tomcat 和 HTTP/2

Spring Boot 默认使用 Tomcat 10.1.x,它支持 h2ch2。 另外,如果主机操作系统上安装了库和其依赖项,你可以使用 libtcnative 来支持 h2

库目录必须被提供给JVM的库路径,如果还没有的话。你可以通过JVM参数来做到这一点,如 -Djava.library.path=/usr/local/opt/tomcat-native/lib。更多信息请见 官方Tomcat文档

3.8.2. Jetty 和 HTTP/2

为了支持HTTP/2,Jetty需要额外的 org.eclipse.jetty.http2:http2-server 依赖项。 要使用 h2c 不需要其他依赖。 要使用 h2,你还需要根据你的部署选择以下依赖项之一。

  • org.eclipse.jetty:jetty-alpn-java-server 以使用JDK内置支持。

  • org.eclipse.jetty:jetty-alpn-conscrypt-serverConscrypt库

3.8.3. Reactor Netty 和 HTTP/2

spring-boot-webflux-starter 默认使用 Reactor Netty 作为服务器。 Reactor Netty支持 h2ch2,开箱即用。 为了获得最佳的运行性能,该服务器还支持带有本地库的 h2

Spring Boot为 io.netty:netty-tcnative-boringssl-static "uber jar" 管理版本,包含所有平台的本地库。开发者可以选择使用 classifier 只导入所需的依赖项(见 Netty官方文档)。

3.8.4. Undertow 和 HTTP/2

Undertow支持 h2ch2,开箱即用。

3.9. 配置Web服务器

一般来说,你应该首先考虑使用许多可用的配置key之一,并通过在 application.propertiesapplication.yaml 文件中添加新条目来定制你的Web服务器。(见 “发现外部属性的内置选项”)。server.* 命名空间在这里相当有用,它包括 server.tomcat.*server.jetty.* 等命名空间,用于服务器的特定功能。参见 application-properties.html 的列表。

前面的章节已经涵盖了许多常见的用例,如压缩、SSL或HTTP/2。 然而,如果你的用例不存在配置密钥,那么你应该看看 WebServerFactoryCustomizer。 你可以声明这样一个组件,并获得与你的选择相关的服务器工厂的访问权:你应该为所选择的服务器(Tomcat, Jetty, Reactor Netty, Undertow)和所选择的Web栈(servlet或reactive)选择变量。

下面的例子是针对Tomcat的 spring-boot-starter-web(servlet stack)。

Java
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        // customize the factory here
    }

}
Kotlin
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component

@Component
class MyTomcatWebServerCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory?> {

    override fun customize(factory: TomcatServletWebServerFactory?) {
        // customize the factory here
    }

}
Spring Boot在内部使用该基础设施来自动配置服务器。 自动配置的 WebServerFactoryCustomizer Bean的顺序为 0,将在任何用户定义的自定义器之前被处理,除非它有一个明确的顺序说明。

一旦你使用定制器获得了对 WebServerFactory 的访问权,你就可以用它来配置特定的部分,如连接器、服务器资源或服务器本身—​所有这些都使用服务器特定的API。

此外,Spring Boot还提供了。

Server Servlet stack Reactive stack

Tomcat

TomcatServletWebServerFactory

TomcatReactiveWebServerFactory

Jetty

JettyServletWebServerFactory

JettyReactiveWebServerFactory

Undertow

UndertowServletWebServerFactory

UndertowReactiveWebServerFactory

Reactor

N/A

NettyReactiveWebServerFactory

作为最后的手段,你也可以声明你自己的 WebServerFactory bean,它将覆盖Spring Boot提供的那个。 当你这样做的时候,自动配置的自定义器仍然会应用在你的自定义工厂上,所以要小心使用这个选项。

3.10. 添加 Servlet、 Filter、 Listener 到应用

在servlet stack应用程序中,也就是使用 spring-boot-starter-web,有两种方法可以将 ServletFilterServletContextListener 以及Servlet API支持的其他监听器添加到你的应用程序。

3.10.1. 使用 Spring Bean 添加 Servlet、Filter、Listener

要通过使用Spring Bean来添加 ServletFilter 或Servlet *Listener,你必须为它提供一个 @Bean 定义。当你想注入配置或依赖时,这样做会非常有用。然而,你必须非常小心,不要让它们导致太多其他Bean的急切初始化,因为它们必须在应用生命周期的早期安装在容器中。(例如,让它们依赖你的 DataSource 或 JPA configuration 并不是一个好主意)。你可以通过在第一次使用时懒加载地初始化Bean而不是在初始化时初始化来解决这些限制。

在 Filter 和 Servlet 的情况下,你也可以通过添加 FilterRegistrationBeanServletRegistrationBean 来添加映射和初始参数,代替或补充底层组件。

如果在filter注册中没有指定 dispatcherType,就会使用 REQUEST。 这与servlet规范的默认dispatcher类型一致。

像其他Spring Bean一样,你可以定义servlet过滤器bean的顺序;请确保检查 “web.html” 部分。

禁用注册一个Servlet或Filter

前所述,任何 ServletFilter Bean都会自动在Servlet容器中注册。要禁用某个 FilterServlet Bean的注册,请为其创建一个注册Bean,并将其标记为禁用,如下面的例子中所示。

Java
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {

    @Bean
    public FilterRegistrationBean<MyFilter> registration(MyFilter filter) {
        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(filter);
        registration.setEnabled(false);
        return registration;
    }

}
Kotlin
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {

    @Bean
    fun registration(filter: MyFilter): FilterRegistrationBean<MyFilter> {
        val registration = FilterRegistrationBean(filter)
        registration.isEnabled = false
        return registration
    }

}

3.10.2. 通过扫描类路径添加 Servlet、Filter、Listener

@WebServlet@WebFilter@WebListener 注解的类可以通过用 @ServletComponentScan 注解一个 @Configuration 类,并指定包含你想注册的组件的包来自动注册到嵌入式Servlet容器。 默认情况下, @ServletComponentScan 从被注解的类的包中扫描。

3.11. 配置访问日志

可以通过各自的命名空间为Tomcat、Undertow和Jetty配置访问日志。

例如,以下设置在Tomcat上用 自定义 pattern 记录访问。

Properties
server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a %r %s (%D ms)
Yaml
server:
  tomcat:
    basedir: "my-tomcat"
    accesslog:
      enabled: true
      pattern: "%t %a %r %s (%D ms)"

NOTE:日志的默认位置是相对于Tomcat基础目录的 logs 目录。 默认情况下,logs 目录是一个临时目录,所以你可能想固定Tomcat的基本目录,或者为日志使用一个绝对路径。 在前面的例子中,相对于应用程序的工作目录,日志在 my-tomcat/logs 中可用。

Undertow的访问日志可以用类似的方式配置,如下面的例子所示。

Properties
server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a %r %s (%D ms)
server.undertow.options.server.record-request-start-time=true
Yaml
server:
  undertow:
    accesslog:
      enabled: true
      pattern: "%t %a %r %s (%D ms)"
    options:
      server:
        record-request-start-time: true

注意,除了启用访问日志和配置其模式外,还启用了记录请求开始时间。 当在访问日志模式中包括响应时间(%D)时,这是必需的。 日志被存储在相对于应用程序工作目录的 logs 目录中。 你可以通过设置 server.undertow.accesslog.dir 属性自定义这个位置。

最后,Jetty的访问日志也可以按以下方式配置。

Properties
server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log
Yaml
server:
  jetty:
    accesslog:
      enabled: true
      filename: "/var/log/jetty-access.log"

默认情况下,日志被重定向到 System.err。 更多细节,请参见Jetty文档。

3.12. 运行在代理服务器后面

如果你的应用程序在代理、负载均衡器或云端运行,请求信息(如主机、端口、scheme…​…​)可能会沿途改变。 你的应用程序可能在 10.10.10.10:8080 上运行,但HTTP客户应该只看到 example.org

RFC7239 "Forwarded Headers" 定义了转发的HTTP头;代理可以使用这个头来提供关于原始请求的信息。你可以配置你的应用程序来读取这些头信息,并在创建链接并在HTTP 302响应、JSON文档或HTML页面中发送给客户端时自动使用这些信息。还有一些非标准的头信息,如 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix

如果代理添加了常用的 X-Forwarded-ForX-Forwarded-Proto 头信息,将 server.forward-headers-strategy 设置为 NATIVE 就可以支持这些。 有了这个选项,web服务器本身就支持这个功能;你可以查看它们的具体文档来了解具体的行为。

如果这还不够,Spring框架提供了一个 ForwardedHeaderFilter。 你可以通过设置 server.forward-headers-strategyFRAMEWORK,将其注册为应用程序中的Servlet filter。

如果你使用Tomcat并在代理处终止SSL, server.tomcat.redirect-context-root 应该设置为 false。 这允许在执行任何重定向之前尊重 X-Forwarded-Proto 头。
如果你的应用程序在 Cloud Foundry 或 Heroku 中运行, server.forward-headers-strategy 属性默认为 NATIVE。 在所有其他情况下,它默认为 NONE

3.12.1. 自定义Tomcat的代理配置

如果你使用Tomcat,你可以额外配置用于携带 “forwarded” 信息的 header 名称,如下例所示。

Properties
server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-header
Yaml
server:
  tomcat:
    remoteip:
      remote-ip-header: "x-your-remote-ip-header"
      protocol-header: "x-your-protocol-header"

Tomcat还配置了一个正则表达式,用于匹配要信任的内部代理。其默认值见 附录中的 server.tomcat.remoteip.internal-proxies 条目。你可以通过在 application.properties 中添加一个条目来定制valve的配置,如下面的例子所示。

Properties
server.tomcat.remoteip.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
Yaml
server:
  tomcat:
    remoteip:
      internal-proxies: "192\\.168\\.\\d{1,3}\\.\\d{1,3}"
你可以通过设置 internal-proxies 为empty来信任所有代理(但在生产中不要这样做)。

你可以完全控制Tomcat的 RemoteIpValve 的配置,方法是关闭自动的(要这样做,设置 server.forward-headers-strategy=NONE),并使用 WebServerFactoryCustomizer bean添加一个新的valve实例。

3.13. Tomcat 启用多个连接器(Connector)

你可以在 TomcatServletWebServerFactory 中添加 org.apache.catalina.connector.Connector,它可以允许多个连接器,包括HTTP和HTTPS连接器,如下例所示。

Java
import org.apache.catalina.connector.Connector;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyTomcatConfiguration {

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> connectorCustomizer() {
        return (tomcat) -> tomcat.addAdditionalTomcatConnectors(createConnector());
    }

    private Connector createConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setPort(8081);
        return connector;
    }

}
Kotlin
import org.apache.catalina.connector.Connector
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyTomcatConfiguration {

    @Bean
    fun connectorCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
        return WebServerFactoryCustomizer { tomcat: TomcatServletWebServerFactory ->
            tomcat.addAdditionalTomcatConnectors(
                createConnector()
            )
        }
    }

    private fun createConnector(): Connector {
        val connector = Connector("org.apache.coyote.http11.Http11NioProtocol")
        connector.port = 8081
        return connector
    }

}

3.14. 启用 Tomcat 的 MBean Registry

嵌入式Tomcat的MBean registry默认是禁用的。 这样可以最大限度地减少Tomcat的内存占用。 如果你想使用Tomcat的MBeans,例如让Micrometer使用它们来暴露指标,你必须使用 server.tomcat.mbeanregistry.enabled 属性来做到这一点,如下例所示。

Properties
server.tomcat.mbeanregistry.enabled=true
Yaml
server:
  tomcat:
    mbeanregistry:
      enabled: true

3.15. Undertow 启用多个 Listener

UndertowServletWebServerFactory 中添加一个 UndertowBuilderCustomizer,并在 Builder 中添加一个 listener,如下例所示。

Java
import io.undertow.Undertow.Builder;

import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyUndertowConfiguration {

    @Bean
    public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowListenerCustomizer() {
        return (factory) -> factory.addBuilderCustomizers(this::addHttpListener);
    }

    private Builder addHttpListener(Builder builder) {
        return builder.addHttpListener(8080, "0.0.0.0");
    }

}
Kotlin
import io.undertow.Undertow
import org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyUndertowConfiguration {

    @Bean
    fun undertowListenerCustomizer(): WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
        return WebServerFactoryCustomizer { factory: UndertowServletWebServerFactory ->
            factory.addBuilderCustomizers(
                UndertowBuilderCustomizer { builder: Undertow.Builder -> addHttpListener(builder) })
        }
    }

    private fun addHttpListener(builder: Undertow.Builder): Undertow.Builder {
        return builder.addHttpListener(8080, "0.0.0.0")
    }

}

3.16. 使用@ServerEndpoint创建WebSocket端点

如果你想在使用嵌入式容器的Spring Boot应用程序中使用 @ServerEndpoint,你必须声明一个 ServerEndpointExporter @Bean,如下例所示。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration(proxyBeanMethods = false)
public class MyWebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.socket.server.standard.ServerEndpointExporter

@Configuration(proxyBeanMethods = false)
class MyWebSocketConfiguration {

    @Bean
    fun serverEndpointExporter(): ServerEndpointExporter {
        return ServerEndpointExporter()
    }

}

前面例子中显示的Bean将任何 @ServerEndpoint 注解的Bean与底层WebSocket容器注册。 当部署到独立的servlet容器时,这个角色由servlet容器的初始化器执行,而不需要 ServerEndpointExporter bean。

4. Spring MVC

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

4.1. 编写一个JSON REST服务

只要Jackson2在classpath上,Spring Boot应用程序中的任何Spring @RestController 都应该默认渲染JSON响应,如以下例子所示。

Java
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();
    }

}
Kotlin
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 就会默认提供它的JSON表示。 请注意,在浏览器中,你有时可能会看到XML响应,因为浏览器倾向于发送accept头信息,更倾向于XML。

4.2. 编写一个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可用,那么可以用附加的要求来渲染XML,即把 MyThing 注解为 @XmlRootElement,如下面的例子所示。

Java
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;
    }

}
Kotlin
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 头(或使用浏览器)。

4.3. 自定义 Jackson ObjectMapper

Spring MVC(客户端和服务器端)使用 HttpMessageConverters 来协商HTTP交换中的内容转换。 如果Jackson在classpath上,你已经得到了由 Jackson2ObjectMapperBuilder 提供的默认converter,它的一个实例是为你自动配置的。

ObjectMapper(或Jackson XML converter 的 XmlMapper)实例(默认创建)有以下自定义属性。

  • MapperFeature.DEFAULT_VIEW_INCLUSION 已被禁用

  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 已被禁用

  • SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 已被禁用

Spring Boot也有一些功能,使其更容易定制这种行为。

你可以通过使用environment来配置 ObjectMapperXmlMapper 实例。 Jackson提供了一套广泛的开/关特性,可用于配置其处理的各个方面。 这些特性在六个枚举中描述(在Jackson中),它们映射到environment中的properties。

Enum Property Values

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

例如,要启用pretty print,请设置 spring.jackson.serialization.indent_output=true 。注意,由于使用了宽松绑定indent_output 的情况不一定要与相应的枚举常量的情况相匹配,即 INDENT_OUTPUT

这个基于environment的配置被应用于自动配置的 Jackson2ObjectMapperBuilder Bean,并应用于任何通过使用 builder 创建的 mapper,包括自动配置的 ObjectMapper Bean。

context的 Jackson2ObjectMapperBuilder 可以由一个或多个 Jackson2ObjectMapperBuilderCustomizer Bean 来定制。 这种定制器Bean可以被排序(Boot自己的定制器的排序为0),让额外的定制在Boot的定制之前和之后都可以应用。

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

如果你想完全取代默认的 ObjectMapper,可以定义一个该类型的 @Bean 并将其标记为 @Primary,或者,如果你喜欢基于builder的方法,可以定义一个 Jackson2ObjectMapperBuilder @Bean。 请注意,无论哪种情况,这样做都会使 ObjectMapper 的所有自动配置失效。

如果你提供了任何 MappingJackson2HttpMessageConverter 类型的 @Beans,它们将取代MVC配置中的默认值。另外,还提供了一个 HttpMessageConverters 类型的便利 Bean(如果你使用默认的MVC配置,它总是可用的)。它有一些有用的方法来访问默认和用户增强的message converter。

更多细节请参见 “自定义@ResponseBody的渲染方式” 部分和 WebMvcAutoConfiguration 源代码。

4.4. 自定义@ResponseBody的渲染方式

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

与正常的MVC使用一样,你提供的任何 WebMvcConfigurer bean也可以通过覆盖 configureMessageConverters 方法来贡献converter。 然而,与普通的MVC不同,你只能提供你需要的额外converter(因为Spring Boot使用相同的机制来贡献其默认值)。 最后,如果你通过提供自己的 @EnableWebMvc 配置来选择退出Spring Boot默认的MVC配置,你可以完全控制并通过使用 WebMvcConfigurationSupportgetMessageConverters 手动完成一切。

更多细节请参见 WebMvcAutoConfiguration 源代码。

4.5. 处理 Multipart File 文件上传

Spring Boot接纳了servlet 5 jakarta.servlet.http.Part API来支持上传文件。默认情况下,Spring Boot为Spring MVC配置了每个文件的最大尺寸为1MB,单个请求中的文件数据最大为10MB。你可以通过使用 MultipartProperties 类中暴露的属性来覆盖这些值、中间数据的存储位置(例如,存储到 /tmp 目录),以及超过数据被刷入磁盘的阈值。例如,如果你想指定文件不受限制,请将 spring.servlet.multipart.max-file-size 属性设为 -1

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

更多细节参见 MultipartAutoConfiguration 源代码。

建议使用容器内置的对multipart上传的支持,而不是引入一个额外的依赖,如Apache Commons File Upload。

4.6. 关闭 Spring MVC DispatcherServlet

默认情况下,所有内容都是从你的应用程序的根(/)提供的。

如果你想映射到一个不同的路径,你可以按以下方式配置。

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

如果你有额外的Servlet,你可以为每个Servlet声明一个 @Bean 类型的 ServletServletRegistrationBean,Spring Boot将以透明的方式向容器注册它们。 因为servlet是以这种方式注册的,所以它们可以被映射到 DispatcherServlet 的子context中,而无需调用它。

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

4.7. 关闭默认的的 MVC 配置(Configuration)

完全控制MVC配置的最简单的方法是提供你自己的 @Configuration@EnableWebMvc 注解。 这样做让所有的MVC配置都掌握在你手中。

4.8. 自定义 ViewResolver

ViewResolver 是Spring MVC的一个核心组件,它将 @Controller 中的视图(view)名称转换为实际的 View 实现。 请注意,ViewResolvers 主要用于UI应用程序,而不是REST风格的服务(View 不用于渲染 @ResponseBody)。 有很多 ViewResolver 的实现可以选择,Spring本身对你应该使用哪些实现没有意见。 另一方面,Spring Boot会根据它在classpath和application context中发现的情况,为你安装一个或两个。 DispatcherServlet 使用它在应用上下文中发现的所有解析器,依次尝试每个解析器,直到得到一个结果。 如果你添加你自己的,你必须注意你的解析器被添加的顺序和位置。

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

  • 一个名为 ‘defaultViewResolver’' 的 InternalResourceViewResolver。 这个定位可以通过使用 DefaultServlet 渲染的物理资源(包括静态资源和JSP页面,如果你使用这些)。 它对视图名称应用前缀和后缀,然后在servlet上下文中寻找具有该路径的物理资源(默认值都是空的,但可以通过 spring.mvc.view.prefixspring.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。 前缀和后缀的默认值分别为空和 ‘.ftlh’。 你可以通过提供一个同名的bean来覆盖 FreeMarkerViewResolver

  • 如果你使用Groovy模板(实际上,如果 groovy-templates 在你的classpath上),你也有一个 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

更多细节请见以下章节。

5. Jersey

5.1. 用Spring Security保护Jersey端点

Spring Security可以用来保护基于Jersey的Web应用,其方式与保护基于Spring MVC的Web应用的方式基本相同。 然而,如果你想在Jersey中使用Spring Security的方法级安全,你必须将Jersey配置为使用 setStatus(int) 而不是 sendError(int)。 这可以防止Jersey在Spring Security有机会向客户端报告认证或授权失败之前提交响应。

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

import java.util.Collections;

import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class JerseySetStatusOverSendErrorConfig extends ResourceConfig {

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

}

5.2. 和另一个 web 框架一起使用 Jersey

要将Jersey与另一个Web框架(如Spring MVC)一起使用,应该对它进行配置,使其允许其他框架处理它无法处理的请求。 首先,通过配置 spring.jersey.type application property 的值为 filter,将Jersey配置为使用filter而不是servlet。 其次,配置你的 ResourceConfig 来转发那些会导致404的请求,如下面的例子所示。

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletProperties;

import org.springframework.stereotype.Component;

@Component
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
        register(Endpoint.class);
        property(ServletProperties.FILTER_FORWARD_ON_404, true);
    }

}

6. HTTP 客户端

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

6.1. 配置RestTemplate以使用代理

正如 io.html 中所描述的,你可以使用 RestTemplateCustomizerRestTemplateBuilder 来建立一个定制的 RestTemplate。这是创建配置为使用代理的 RestTemplate 的推荐方法。

6.2. 配置基于Reactor Netty的WebClient所使用的TcpClient

当Reactor Netty在classpath上时,基于Reactor Netty的 WebClient 会被自动配置。 为了定制客户端对网络连接的处理,可以提供一个 ClientHttpConnector bean。 下面的例子配置了60秒的连接超时并添加了一个 ReadTimeoutHandler

Java
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import reactor.netty.http.client.HttpClient;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;

@Configuration(proxyBeanMethods = false)
public class MyReactorNettyClientConfiguration {

    @Bean
    ClientHttpConnector clientHttpConnector(ReactorResourceFactory resourceFactory) {
        HttpClient httpClient = HttpClient.create(resourceFactory.getConnectionProvider())
                .runOn(resourceFactory.getLoopResources())
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)
                .doOnConnected((connection) -> connection.addHandlerLast(new ReadTimeoutHandler(60)));
        return new ReactorClientHttpConnector(httpClient);
    }

}
Kotlin
import io.netty.channel.ChannelOption
import io.netty.handler.timeout.ReadTimeoutHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ClientHttpConnector
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.http.client.reactive.ReactorResourceFactory
import reactor.netty.http.client.HttpClient

@Configuration(proxyBeanMethods = false)
class MyReactorNettyClientConfiguration {

    @Bean
    fun clientHttpConnector(resourceFactory: ReactorResourceFactory): ClientHttpConnector {
        val httpClient = HttpClient.create(resourceFactory.connectionProvider)
            .runOn(resourceFactory.loopResources)
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)
            .doOnConnected { connection ->
                connection.addHandlerLast(ReadTimeoutHandler(60))
            }
        return ReactorClientHttpConnector(httpClient)
    }

}
注意连接提供者和事件循环资源使用了 ReactorResourceFactory。 这确保了接收请求的服务器和发出请求的客户端有效地共享资源。

7. 日志(Logging)

除了通常由Spring Framework的 spring-jcl 模块提供的Commons Logging API,Spring Boot没有强制性的日志依赖。要使用 Logback,你需要在classpath上包含它和 spring-jcl。推荐的方法是通过starter,它们都依赖于 spring-boot-starter-logging。对于Web应用,你只需要 spring-boot-starter-web,因为它间接依赖于logging starter。如果你使用Maven,下面的依赖可以为你添加日志。

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

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

如果你需要对日志进行的唯一改变是设置各种日志的级别,你可以在 application.properties 中通过使用 "logging.level" 前缀来实现,如以下例子所示。

Properties
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
Yaml
logging:
  level:
    org.springframework.web: "debug"
    org.hibernate: "error"

你也可以通过使用 logging.file.name 来设置写入日志的文件的位置(除了控制台)。

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

7.1. 配置 Logback 用于日志记录

如果你需要对logback进行定制,而不是用 application.properties 来实现,你将需要添加一个标准的logback配置文件。你可以在你的classpath的根部添加一个 logback.xml 文件,让logback找到。如果你想使用 Spring Boot Logback扩展,你也可以使用 logback-spring.xml

Logback文档有一个 专门的章节,涉及配置的一些细节。

Spring Boot提供了一些可以 included 在你自己的配置中的logback配置。

这些 included 旨在允许重用某些常见的Spring Boot惯例。

以下文件在 org/springframework/boot/logging/logback/ 下提供。

  • defaults.xml - 提供转换规则, pattern properties 和常见的 logger配置.

  • console-appender.xml - 使用 CONSOLE_LOG_PATTERN 添加一个 ConsoleAppender

  • file-appender.xml - 使用 FILE_LOG_PATTERNROLLING_FILE_NAME_PATTERN 加上适当的设置,添加一个 RollingFileAppender

此外,还提供了一个传统的 base.xml 文件,以便与Spring Boot的早期版本兼容。

一个典型的自定义 logback.xml 文件看起来会是这样的。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
    <logger name="org.springframework.web" level="DEBUG"/>
</configuration>

你的logback配置文件也可以利用系统属性(System properties),LoggingSystem 负责为你创建。

  • ${PID}: 当前进程的ID。

  • ${LOG_FILE}: 无论 Boot的外部配置中是否设置了 logging.file.name

  • ${LOG_PATH}: 无论 logging.file.path (代表日志文件所在的目录)是否在Boot的外部配置中被设置。

  • ${LOG_EXCEPTION_CONVERSION_WORD}: 无论 Boot的外部配置中是否设置了 logging.exception-conversion-word

  • ${ROLLING_FILE_NAME_PATTERN}: 无论 Boot的外部配置中是否设置了 logging.pattern.rolling-file-name

通过使用自定义的Logback converter,Spring Boot还在控制台提供了一些漂亮的ANSI彩色终端输出(但不是在日志文件中)。 请看 defaults.xml 配置中的 CONSOLE_LOG_PATTERN 的例子。

如果Groovy在classpath上,你应该也能用 logback.groovy 来配置Logback。 如果存在,这个设置会被优先考虑。

Spring extensions are not supported with Groovy configuration. Any logback-spring.groovy files will not be detected.

7.1.1. 配置只用于文件输出的Logback

如果你想禁用控制台日志,只将输出写入文件,你需要一个自定义的 logback-spring.xml,导入 file-appender.xml 而不是 console-appender.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>

你还需要在你的 application.propertiesapplication.yaml 中添加 logging.file.name,如下例所示。

Properties
logging.file.name=myapplication.log
Yaml
logging:
  file:
    name: "myapplication.log"

7.2. 配置 Log4j 用于日志记录

如果 Log4j 2 在classpath上,Spring Boot支持Log4j 2进行日志配置。如果你使用starter来组装依赖关系,你必须排除Logback,然后包括Log4j 2。如果你不使用starter,除了Log4j 2,你还需要提供(至少) spring-jcl

推荐的路径是通过starter,尽管这需要一些调整。 下面的例子展示了如何在Maven中设置starter。

<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提供了几种不同的方法来设置starter。 一种方法是使用 模块替换。 要做到这一点,需要声明对Log4j 2 starter的依赖,并告诉Gradle任何出现的默认日志starter都应该被Log4j 2 starter替换,如下面的例子所示。

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-log4j2"
    modules {
        module("org.springframework.boot:spring-boot-starter-logging") {
            replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
        }
    }
}
Log4j starter将常见的日志要求的依赖聚集在一起(例如让Tomcat使用 java.util.logging,但使用Log4j 2配置输出)。
为了确保使用 java.util.logging 执行的调试日志被路由到Log4j 2,通过将 java.util.logging.manager 系统属性设置为 org.apache.logging.log4j.jul.LogManager 来配置其 JDK日志适配器

7.2.1. 使用YAML或JSON来配置Log4j 2

除了默认的XML配置格式外,Log4j 2还支持YAML和JSON配置文件。 要配置Log4j 2使用其他的配置文件格式,请在classpath中添加适当的依赖项,并命名你的配置文件以匹配你选择的文件格式,如下面的例子所示。

格式 依赖 文件名称

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

7.2.2. 使用复合配置来配置Log4j 2

Log4j 2支持将多个配置文件合并为一个复合配置。 要在Spring Boot中使用这一支持,请在 logging.log4j2.config.override 中配置一个或多个次级配置文件的位置。 二级配置文件将与一级配置合并,无论一级的来源是Spring Boot的默认值、标准位置如 log4j.xml,还是 logging.config 属性所配置的位置。

8. 数据访问

Spring Boot包括一些用于处理数据源的starter。 本节回答与此相关的问题。

8.1. 配置自定义数据源

要配置你自己的 DataSource,请在你的配置中定义一个该类型的 @Bean。 Spring Boot会在任何需要 DataSource 的地方重用它,包括数据库初始化。 如果你需要外部化一些设置,你可以将你的 DataSource 绑定到environment中(见 “features.html”)。

下面的例子显示了如何在Bean中定义一个数据源。

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

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

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "app.datasource")
    fun dataSource(): SomeDataSource {
        return SomeDataSource()
    }

}

下面的例子显示了如何通过设置属性来定义一个数据源。

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

假设 SomeDataSource 有常规的JavaBean属性,用于URL、用户名和池大小,在 DataSource 被提供给其他组件之前,这些设置被自动绑定。

Spring Boot还提供了一个名为 DataSourceBuilder 的实用builder,可用于创建标准数据源之一(如果它在classpath上)。 builder可以根据classpath上的可用内容检测要使用的数据源。 它还可以根据JDBC的URL自动检测驱动程序。

下面的例子显示了如何通过使用 DataSourceBuilder 来创建一个数据源。

Java
import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

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

}
Kotlin
import javax.sql.DataSource

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

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

}

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

下面的例子显示了如何通过设置属性来定义一个JDBC数据源。

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

然而,有一个问题。 因为连接池的实际类型没有暴露,在你的自定义 DataSource 的元数据中没有生成key,在你的IDE中也没有完成度(因为 DataSource 接口没有暴露属性)。 另外,如果你碰巧在classpath上有 Hikari,这个基本设置就不工作了,因为Hikari没有 url 属性(但有 jdbcUrl 属性)。 在这种情况下,你必须重写你的配置,如下所示。

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

你可以通过强制连接池使用并返回一个专门的实现而不是 DataSource 来解决这个问题。 你不能在运行时改变实现,但选项列表将是明确的。

下面的例子显示了如何用 DataSourceBuilder 创建一个 HikariDataSource

Java
import com.zaxxer.hikari.HikariDataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

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

}
Kotlin
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @ConfigurationProperties("app.datasource")
    fun dataSource(): HikariDataSource {
        return DataSourceBuilder.create().type(HikariDataSource::class.java).build()
    }

}

你甚至可以更进一步,利用 DataSourceProperties 为你做的事情—​也就是说,如果没有提供URL,就提供一个默认的嵌入式数据库,并提供合理的用户名和密码。 你可以从任何 DataSourceProperties 对象的状态中轻松地初始化 DataSourceBuilder,所以你也可以注入Spring Boot自动创建的DataSource。 然而,这将把你的配置分成两个命名空间:urlusernamepasswordtypedriverspring.datasource 上,其余的在你的自定义命名空间( app.datasource)。 为了避免这种情况,你可以在你的自定义命名空间上重新定义一个自定义的 DataSourceProperties,如下面的例子所示。

Java
import com.zaxxer.hikari.HikariDataSource;

import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

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

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

}
Kotlin
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource")
    fun dataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    @ConfigurationProperties("app.datasource.configuration")
    fun dataSource(properties: DataSourceProperties): HikariDataSource {
        return properties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
    }

}

这种设置使你与Spring Boot的默认做法同步,只是(在代码中)选择了一个专用的连接池,其设置在 app.datasource.configuration 子命名空间中公开。 因为 DataSourceProperties 为你处理了 url/jdbcUrl 的转换,你可以按以下方式配置它。

Properties
app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.configuration.maximum-pool-size=30
Yaml
app:
  datasource:
    url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
    configuration:
      maximum-pool-size: 30
Spring Boot将向 spring.datasource.hikari 暴露Hikari的特定设置。 这个例子使用了一个更通用的 configuration 子命名空间,因为这个例子不支持多个数据源的实现。
因为你的自定义配置选择了Hikari,app.datasource.type 没有影响。 在实践中,builder被初始化为你可能设置的任何值,然后被对 .type() 的调用所重写。

详情见 "Spring Boot特性" 部分的 “data.html” 和 DataSourceAutoConfiguration 类。

8.2. 配置两个数据源

如果你需要配置多个数据源,你可以应用上一节所述的相同技巧。 然而,你必须将其中一个 DataSource 实例标记为 @Primary,因为接下来的各种自动配置都希望能够按类型获得一个。

如果你创建了你自己的 DataSource,自动配置就会退缩。 在下面的例子中,我们提供了与自动配置在主数据源上提供完全相同的功能集。

Java
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration(proxyBeanMethods = false)
public class MyDataSourcesConfiguration {

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

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first.configuration")
    public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
        return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

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

}
Kotlin
import com.zaxxer.hikari.HikariDataSource
import org.apache.commons.dbcp2.BasicDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary

@Configuration(proxyBeanMethods = false)
class MyDataSourcesConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first")
    fun firstDataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first.configuration")
    fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource {
        return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
    }

    @Bean
    @ConfigurationProperties("app.datasource.second")
    fun secondDataSource(): BasicDataSource {
        return DataSourceBuilder.create().type(BasicDataSource::class.java).build()
    }

}
firstDataSourceProperties 必须被标记为 @Primary,以便数据库初initializer功能使用你的副本(如果你使用初initializer)。

这两个数据源也都是为高级定制而绑定的。 例如,你可以对它们进行如下配置。

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

app.datasource.second.url=jdbc:mysql://localhost/second
app.datasource.second.username=dbuser
app.datasource.second.password=dbpass
app.datasource.second.max-total=30
Yaml
app:
  datasource:
    first:
      url: "jdbc:mysql://localhost/first"
      username: "dbuser"
      password: "dbpass"
      configuration:
        maximum-pool-size: 30

    second:
      url: "jdbc:mysql://localhost/second"
      username: "dbuser"
      password: "dbpass"
      max-total: 30

你也可以将同样的概念应用于二级 DataSource,如下面的例子所示。

Java
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration(proxyBeanMethods = false)
public class MyCompleteDataSourcesConfiguration {

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

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first.configuration")
    public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProperties) {
        return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

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

    @Bean
    @ConfigurationProperties("app.datasource.second.configuration")
    public BasicDataSource secondDataSource(
            @Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) {
        return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build();
    }

}
Kotlin
import com.zaxxer.hikari.HikariDataSource
import org.apache.commons.dbcp2.BasicDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary

@Configuration(proxyBeanMethods = false)
class MyCompleteDataSourcesConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first")
    fun firstDataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    @Primary
    @ConfigurationProperties("app.datasource.first.configuration")
    fun firstDataSource(firstDataSourceProperties: DataSourceProperties): HikariDataSource {
        return firstDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
    }

    @Bean
    @ConfigurationProperties("app.datasource.second")
    fun secondDataSourceProperties(): DataSourceProperties {
        return DataSourceProperties()
    }

    @Bean
    @ConfigurationProperties("app.datasource.second.configuration")
    fun secondDataSource(secondDataSourceProperties: DataSourceProperties): BasicDataSource {
        return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource::class.java).build()
    }

}

前面的例子在自定义命名空间上配置了两个数据源,其逻辑与Spring Boot在自动配置中使用的一样。 请注意,每个 configuration 子命名空间都根据所选择的实现提供高级设置。

8.3. 使用 Spring Data Repository

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

对于许多应用程序来说,你所需要的只是将正确的Spring Data依赖关系放在你的classpath上。 有一个 spring-boot-starter-data-jpa 用于JPA,spring-boot-starter-data-mongodb 用于Mongodb,还有其他各种支持技术的starter。 为了开始工作,创建一些 Repository 接口来处理你的 @Entity 对象。

Spring Boot试图根据它发现的 @EnableAutoConfiguration 来猜测你的 @Repository 定义的位置。 要获得更多的控制,可以使用 @EnableJpaRepositories 注解(来自Spring Data JPA)。

关于Spring Data的更多信息,请参阅 Spring Data项目页面

8.4. 将@Entity定义从Spring配置中分离出来

Spring Boot会根据它发现的 @EnableAutoConfiguration,试图猜测你的 @Entity 定义的位置。 为了获得更多控制,你可以使用 @EntityScan 注解,如下例所示。

Java
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = City.class)
public class MyApplication {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = [City::class])
class MyApplication {

    // ...

}

8.5. 配置JPA属性

Spring Data JPA已经提供了一些独立于供应商的配置选项(例如用于SQL日志的配置),Spring Boot将这些选项和Hibernate的一些其他选项作为外部配置属性公开。 其中一些是根据上下文自动检测出来的,所以你不应该设置它们。

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

使用的方言是由JPA提供者检测的。 如果你喜欢自己设置方言,请设置 spring.jpa.database-platform 属性。

要设置的最常见的选项如下例所示。

Properties
spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy
spring.jpa.show-sql=true
Yaml
spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: "com.example.MyPhysicalNamingStrategy"
    show-sql: true

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

你需要确保在 spring.jpa.properties.* 下定义的名称与你的JPA提供者所期望的完全一致。 Spring Boot不会尝试对这些条目进行任何形式的宽松绑定。

例如,如果你想配置Hibernate的 batch size,你必须使用 spring.jpa.properties.hibernate.jdbc.batch_size。 如果你使用其他形式,如 batchSizebatch-size,Hibernate将不会应用该设置。

如果你需要对Hibernate属性进行高级定制,可以考虑注册一个 HibernatePropertiesCustomizer bean,它将在创建 EntityManagerFactory 之前被调用。 这将优先于任何由自动配置应用的东西。

8.6. 配置Hibernate的命名策略

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

默认情况下,Spring Boot用 CamelCaseToUnderscoresNamingStrategy 配置 physical 命名策略。 使用这种策略,所有的点都被下划线所取代,驼峰大小写也被下划线所取代。 此外,默认情况下,所有的表名都以小写生成。 例如,一个 TelephoneNumber 实体被映射到 telephone_number 表。 如果你的模式需要混合大小写的标识符,请定义一个自定义的 CamelCaseToUnderscoresNamingStrategy bean,如下例所示。

Java
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyHibernateConfiguration {

    @Bean
    public CamelCaseToUnderscoresNamingStrategy caseSensitivePhysicalNamingStrategy() {
        return new CamelCaseToUnderscoresNamingStrategy() {

            @Override
            protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
                return false;
            }

        };
    }

}
Kotlin
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {

    @Bean
    fun caseSensitivePhysicalNamingStrategy(): CamelCaseToUnderscoresNamingStrategy {
        return object : CamelCaseToUnderscoresNamingStrategy() {
            override fun isCaseInsensitive(jdbcEnvironment: JdbcEnvironment): Boolean {
                return false
            }
        }
    }

}

如果你喜欢用Hibernate的默认值来代替,请设置以下属性。

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

或者,你可以配置以下Bean。

Java
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {

    @Bean
    PhysicalNamingStrategyStandardImpl caseSensitivePhysicalNamingStrategy() {
        return new PhysicalNamingStrategyStandardImpl();
    }

}
Kotlin
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
internal class MyHibernateConfiguration {

    @Bean
    fun caseSensitivePhysicalNamingStrategy(): PhysicalNamingStrategyStandardImpl {
        return PhysicalNamingStrategyStandardImpl()
    }

}

8.7. 配置Hibernate 二级缓存

Hibernate 二级缓存 可以为一系列的缓存提供者进行配置。 与其配置Hibernate再次查找缓存提供者,不如尽可能地提供上下文中可用的那个缓存提供者。

要使用JCache,首先要确保 org.hibernate.orm:hibernate-jcache 在classpath上可用。 然后,添加一个 HibernatePropertiesCustomizer bean,如下例所示。

Java
import org.hibernate.cache.jcache.ConfigSettings;

import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyHibernateSecondLevelCacheConfiguration {

    @Bean
    public HibernatePropertiesCustomizer hibernateSecondLevelCacheCustomizer(JCacheCacheManager cacheManager) {
        return (properties) -> properties.put(ConfigSettings.CACHE_MANAGER, cacheManager.getCacheManager());
    }

}
Kotlin
import org.hibernate.cache.jcache.ConfigSettings
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer
import org.springframework.cache.jcache.JCacheCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyHibernateSecondLevelCacheConfiguration {

    @Bean
    fun hibernateSecondLevelCacheCustomizer(cacheManager: JCacheCacheManager): HibernatePropertiesCustomizer {
        return HibernatePropertiesCustomizer { properties ->
            properties[ConfigSettings.CACHE_MANAGER] = cacheManager.cacheManager
        }
    }

}

这个自定义器将配置Hibernate使用与应用程序使用的相同的 CacheManager。 也可以使用单独的 CacheManager 实例。 详情请见 Hibernate用户指南

8.8. 在Hibernate组件中使用依赖注入

默认情况下,Spring Boot注册了一个使用 BeanFactoryBeanContainer 实现,以便 converter 和 entity listener 可以使用常规的依赖注入。

你可以通过注册一个 HibernatePropertiesCustomizer 来禁用或调整这种行为,它可以删除或改变 hibernate.resource.beans.container 属性。

8.9. 使用一个自定义的 EntityManagerFactory

为了完全控制 EntityManagerFactory 的配置,你需要添加一个名为 entityManagerFactory@Bean。 Spring Boot的自动配置会在出现该类型的Bean时关闭其 entity manager。

8.10. 使用多个 EntityManagerFactory

如果你需要针对多个数据源使用JPA,你可能需要每个数据源有一个 EntityManagerFactory。 Spring ORM的 LocalContainerEntityManagerFactoryBean 允许你为你的需求配置一个 EntityManagerFactory。 你也可以重复使用 JpaProperties 来为每个 EntityManagerFactory 绑定设置,如下例所示。

Java
import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

@Configuration(proxyBeanMethods = false)
public class MyEntityManagerFactoryConfiguration {

    @Bean
    @ConfigurationProperties("app.jpa.first")
    public JpaProperties firstJpaProperties() {
        return new JpaProperties();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource,
            JpaProperties firstJpaProperties) {
        EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(firstJpaProperties);
        return builder.dataSource(firstDataSource).packages(Order.class).persistenceUnit("firstDs").build();
    }

    private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
        JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
    }

    private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
        // ... map JPA properties as needed
        return new HibernateJpaVendorAdapter();
    }

}
Kotlin
import javax.sql.DataSource

import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.orm.jpa.JpaVendorAdapter
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter

@Configuration(proxyBeanMethods = false)
class MyEntityManagerFactoryConfiguration {

    @Bean
    @ConfigurationProperties("app.jpa.first")
    fun firstJpaProperties(): JpaProperties {
        return JpaProperties()
    }

    @Bean
    fun firstEntityManagerFactory(
        firstDataSource: DataSource?,
        firstJpaProperties: JpaProperties
    ): LocalContainerEntityManagerFactoryBean {
        val builder = createEntityManagerFactoryBuilder(firstJpaProperties)
        return builder.dataSource(firstDataSource).packages(Order::class.java).persistenceUnit("firstDs").build()
    }

    private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
        val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties)
        return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.properties, null)
    }

    private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter {
        // ... map JPA properties as needed
        return HibernateJpaVendorAdapter()
    }

}

上面的例子使用一个名为 firstDataSource 的bean创建了一个 EntityManagerFactory。 它扫描了与 Order 相同的包中的实体。 可以使用 app.first.jpa 命名空间来映射其他JPA属性。

当你为 LocalContainerEntityManagerFactoryBean 自己创建一个Bean时,在创建自动配置的 LocalContainerEntityManagerFactoryBean 时应用的任何定制都会丢失。 例如,在Hibernate的情况下, spring.jpa.hibernate 前缀下的任何属性都不会自动应用到你的 LocalContainerEntityManagerFactoryBean。 如果你依赖这些属性来配置诸如命名策略或DDL模式,你将需要在创建 LocalContainerEntityManagerFactoryBean bean时明确配置。

你应该为你需要JPA访问的任何其他数据源提供类似的配置。 为了使画面完整,你需要为每个 EntityManagerFactory 配置一个 JpaTransactionManager。 或者,你也可以使用一个跨越两者的JTA事务管理器(transaction manager)。

如果你使用Spring Data,你需要相应地配置 @EnableJpaRepositories,如以下例子所示。

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "firstEntityManagerFactory")
public class OrderConfiguration {

}
Kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = [Order::class], entityManagerFactoryRef = "firstEntityManagerFactory")
class OrderConfiguration
Java
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Customer.class, entityManagerFactoryRef = "secondEntityManagerFactory")
public class CustomerConfiguration {

}
Kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = [Customer::class], entityManagerFactoryRef = "secondEntityManagerFactory")
class CustomerConfiguration

8.11. 使用传统的 persistence.xml 文件

Spring Boot默认不会搜索或使用 META-INF/persistence.xml。 如果你喜欢使用传统的 persistence.xml,你需要定义你自己的 @Bean,类型为 LocalEntityManagerFactoryBean(ID为 entityManagerFactory),并在那里设置 persistence unit name。

默认设置见 JpaBaseConfiguration

8.12. 使用 Spring Data JPA 和 Mongo Repository

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

还有一些标志(spring.data.*.repositories.enabledspring.data.*.repositories.type),你可以用来在外部配置中打开和关闭自动配置的repository。 这样做是有用的,例如,如果你想关闭Mongo repository,但仍然使用自动配置的 `MongoTemplate'。

其他自动配置的Spring Data资源库类型(Elasticsearch、Redis和其他)也存在同样的障碍和同样的功能。 要与它们一起工作,要相应地改变注解和标志(flag)的名称。

8.13. 自定义 Spring Data 的 Web 支持

Spring Data提供了WEB支持,简化了Spring Data Repository在WEB应用中的使用。 Spring Boot在 spring.data.web 命名空间中提供了用于定制其配置的属性。 注意,如果你使用Spring Data REST,你必须使用 spring.data.rest 命名空间中的属性来代替。

8.14. 将 Spring Data Repository 作为REST端点进行公开

Spring Data REST可以将 Repository 的实现作为REST端点公开给你。 只要应用程序启用了Spring MVC。

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

如果你不在你的自定义 RepositoryRestConfigurer 上指定任何顺序,它就会在Spring Boot内部使用的顺序之后运行。 如果你需要指定一个顺序,请确保它高于0。

8.15. 配置一个被JPA使用的组件

如果你想配置JPA使用的组件,那么你需要确保该组件在JPA之前被初始化。 当组件被自动配置时,Spring Boot会帮你解决这个问题。 例如,当Flyway被自动配置时,Hibernate被配置为依赖Flyway,这样Flyway就有机会在Hibernate试图使用它之前初始化数据库。

如果你自己配置一个组件,你可以使用 EntityManagerFactoryDependsOnPostProcessor 子类作为设置必要的依赖关系的方便方法。 例如,如果你使用Hibernate Search与Elasticsearch作为其索引管理器,任何 EntityManagerFactory Bean 必须被配置为依赖 elasticsearchClient Bean,如以下例子所示。

Java
import jakarta.persistence.EntityManagerFactory;

import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.stereotype.Component;

/**
 * {@link EntityManagerFactoryDependsOnPostProcessor} that ensures that
 * {@link EntityManagerFactory} beans depend on the {@code elasticsearchClient} bean.
 */
@Component
public class ElasticsearchEntityManagerFactoryDependsOnPostProcessor
        extends EntityManagerFactoryDependsOnPostProcessor {

    public ElasticsearchEntityManagerFactoryDependsOnPostProcessor() {
        super("elasticsearchClient");
    }

}
Kotlin
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor
import org.springframework.stereotype.Component

@Component
class ElasticsearchEntityManagerFactoryDependsOnPostProcessor :
    EntityManagerFactoryDependsOnPostProcessor("elasticsearchClient")

8.16. 用两个数据源配置jOOQ

如果你需要用多个数据源来使用jOOQ,你应该为每个数据源创建你自己的 `DSLContext'。 更多细节请参见 JooqAutoConfiguration

特别是,JooqExceptionTranslatorSpringTransactionProvider 可以被重用,以提供类似于自动配置对单一 DataSource 所做的功能。

9. 数据库初始化

一个SQL数据库可以通过不同的方式进行初始化,这取决于你的堆栈是什么。 当然,你也可以手动进行,前提是数据库是一个独立的进程。 建议使用单一的机制来生成schema。

9.1. 使用JPA初始化一个数据库

JPA具有生成DDL的功能,这些功能可以被设置为在启动时针对数据库运行。 这是由两个外部属性控制的。

  • spring.jpa.generate-ddl (boolean)可以打开和关闭该功能,并且与供应商无关。

  • spring.jpa.hibernate.ddl-auto (enum) 是Hibernate的一个功能,它以更精细的方式控制行为。本指南后面将详细介绍这一特性。

9.2. 使用Hibernate初始化数据库

你可以明确设置 spring.jpa.hibernate.ddl-auto,标准的Hibernate属性值是 nonevalidateupdatecreatecreate-drop。 Spring Boot根据它认为你的数据库是否被嵌入,为你选择了一个默认值。 如果没有检测到schema manager,它默认为 create-drop,在所有其他情况下为 none。 嵌入式数据库是通过查看 Connection 类型和JDBC网址来检测的。 hsqldbh2derby 是候选的,其他的不是。 当从内存中切换到 "真实" 数据库时要小心,不要对新平台中的表和数据的存在做出假设。 你必须明确设置 ddl-auto,或者使用其他机制来初始化数据库。

你可以通过启用 org.hibernate.SQL 日志器来输出schema的创建。 如果你启用 debug 模式,这将自动为你完成。

此外,如果Hibernate从头开始创建schema(也就是说,如果 ddl-auto 属性被设置为 createcreat-drop),在classpath根部的一个名为 import.sql 的文件在启动时被执行。 这对于演示和测试来说是很有用的,如果你很小心的话,但在生产中你可能不希望出现在classpath上。 这是一个Hibernate的特性(与Spring无关)。

9.3. 使用基本SQL脚本初始化数据库

Spring Boot可以自动创建你的JDBC DataSource 或R2DBC ConnectionFactory 的schema(DDL脚本)并初始化它(DML脚本)。 它从标准的根classpath位置加载SQL。schema.sqldata.sql,分别地。

此外,Spring Boot还处理 schema-${platform}.sqldata-${platform}.sql 文件(如果存在),其中 platformspring.sql.init.platform 的值。 这允许你在必要时切换到数据库特定的脚本。 例如,你可以选择将其设置为数据库的供应商名称(hsqldbh2oraclemysqlpostgresql,等等)。 默认情况下,SQL数据库初始化只在使用嵌入式内存数据库时执行。

要始终初始化一个SQL数据库,无论其类型如何,请将 spring.sql.init.mode 设置为 always。 同样,要禁用初始化,将 spring.sql.init.mode 设为 never。 默认情况下,Spring Boot启用其基于脚本的数据库初始化器的故障快速功能。 这意味着,如果脚本导致异常,应用程序将无法启动。 你可以通过设置 spring.sql.init.continue-on-error 来调整这一行为。

基于脚本的 DataSource 初始化默认在创建任何JPA EntityManagerFactory Bean之前进行。 schema.sql 可以用来为JPA管理的实体创建schema,data.sql 可以用来填充它。 虽然我们不推荐使用多种数据源初始化技术,但如果你希望基于脚本的 DataSource 初始化能够建立在Hibernate执行的schema创建之上,请将 spring.jpa.defer-datasource-initialization 设置为 true。 这将推迟数据源初始化,直到任何 EntityManagerFactory Bean 被创建和初始化之后。 然后,schema.sql 可以用来对Hibernate执行的任何schema创建进行补充,data.sql 可以用来填充它。

如果你正在使用 高级数据库迁移工具,如Flyway或Liquibase,你应该单独使用它们来创建和初始化schema。不建议在Flyway或Liquibase旁边使用基本的 schema.sqldata.sql 脚本,在未来的版本中会取消支持。

9.4. 初始化Spring Batch数据库

如果你使用Spring Batch,它预装了大多数流行数据库平台的SQL初始化脚本。 Spring Boot可以检测你的数据库类型并在启动时执行这些脚本。 如果你使用的是嵌入式数据库,默认情况下是这样的。 你也可以为任何数据库类型启用它,如下面的例子所示。

Properties
spring.batch.jdbc.initialize-schema=always
Yaml
spring:
  batch:
    jdbc:
      initialize-schema: "always"

你也可以通过设置 spring.batch.jdbc.initialize-schemanever 来明确关闭初始化。

9.5. 使用高级数据库迁移工具

Spring Boot支持两个高级的迁移工具。 FlywayLiquibase

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

为了在启动时自动运行Flyway数据库迁移,请将 org.flywaydb:flyway-core 添加到你的classpath。

通常,迁移是以 V<VERSION>__<NAME>.sql 的形式出现的脚本(其中 VERSION> 是用下划线分隔的版本,例如 ‘1’ 或 ‘2_1’ )。 默认情况下,它们在一个名为 classpath:db/migration 的目录中,但你可以通过设置 spring.flyway.locations 来修改这个位置。 这是一个逗号分隔的列表,包含一个或多个 classpath:filesystem: 位置。 例如,下面的配置将搜索默认classpath位置和 /opt/migration 目录下的脚本。

Properties
spring.flyway.locations=classpath:db/migration,filesystem:/opt/migration
Yaml
spring:
  flyway:
    locations: "classpath:db/migration,filesystem:/opt/migration"

你也可以添加一个特殊的 {vendor} 占位符来使用特定供应商的脚本。 假设如下。

Properties
spring.flyway.locations=classpath:db/migration/{vendor}
Yaml
spring:
  flyway:
    locations: "classpath:db/migration/{vendor}"

前面的配置不是使用 db/migration,而是根据数据库的类型设置要使用的目录(比如对于MySQL来说是 db/migration/mysql)。 支持的数据库列表可以在 DatabaseDriver 中找到。

迁移也可以用Java编写。 Flyway将自动配置任何实现 JavaMigration 的bean。

FlywayProperties 提供了Flyway的大部分设置和一小部分额外的属性,可以用来禁用迁移或关闭位置检查。 如果你需要对配置进行更多的控制,可以考虑注册一个 FlywayConfigurationCustomizer bean。

Spring Boot调用 Flyway.migrate() 来执行数据库迁移。 如果你想获得更多的控制权,可以提供一个实现 FlywayMigrationStrategy@Bean

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

默认情况下,Flyway会自动连接你的上下文中的(@PrimaryDataSource,并使用它进行迁移。 如果你想使用不同的 DataSource,你可以创建一个,并将其 @Bean 标记为 @FlywayDataSource。 如果你这样做了,并且想要两个数据源,记得再创建一个,并将其标记为 @Primary。 另外,你可以通过在外部属性中设置 spring.flyway.[url,user,password] 来使用Flyway的本地 DataSource。 设置 spring.flyway.urlspring.flyway.user 足以使Flyway使用它自己的 DataSource。 如果这三个属性中的任何一个没有被设置,将使用其对应的 spring.datasource 属性的值。

你也可以使用Flyway为特定场景提供数据。 例如,你可以将特定于测试的迁移放在 src/test/resources 中,它们只在应用程序启动测试时运行。 另外,你可以使用特定的配置文件配置来定制 spring.flyway.locations,使某些迁移只在特定的配置文件激活时运行。 例如,在 application-dev.properties 中,你可以指定以下设置。

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

在这样的设置下,dev/db/migration 中的迁移只在 dev 配置文件处于活动状态时运行。

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

为了在启动时自动运行Liquibase数据库迁移,请将 org.liquibase:liquibase-core 添加到你的classpath。

当你把 org.liquibase:liquibase-core 添加到你的classpath时,数据库迁移默认在应用程序启动时和测试运行前运行。 这种行为可以通过使用 spring.liquibase.enabled 属性,在 maintest 配置中设置不同的值来定制。 不可能使用两种不同的方式来初始化数据库(例如Liquibase用于应用程序启动,JPA用于测试运行)。

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

默认情况下,Liquibase会在你的上下文中自动连接(@PrimaryDataSource,并使用它进行迁移。 如果你需要使用一个不同的 DataSource,你可以创建一个,并将其 @Bean 标记为 @LiquibaseDataSource。 如果你这样做了,并且你想要两个数据源,记得再创建一个,并把它标记为 @Primary。 另外,你可以通过在外部属性中设置 spring.liquibase.[driver-class-name,url,user,password] 来使用Liquibase的本地 DataSource。 设置 spring.liquibase.urlspring.liquibase.user 足以使Liquibase使用它自己的 DataSource。 如果这三个属性中的任何一个没有被设置,将使用其对应的 spring.datasource 属性的值。

参见 LiquibaseProperties 以了解可用的设置,如上下文、默认schema和其他。

9.6. 依赖于一个初始化的数据库

数据库初始化是在应用程序启动时作为应用程序上下文刷新的一部分进行的。 为了允许在启动期间访问初始化的数据库,自动检测作为数据库初始化器的Bean和需要数据库被初始化的Bean。 那些初始化依赖于数据库已经被初始化的Bean被配置为依赖于那些初始化数据库的Bean。 如果在启动过程中,你的应用程序试图访问数据库,但它还没有被初始化,你可以配置额外的检测那些初始化数据库和要求数据库被初始化的bean。

9.6.1. 检测数据库初始化程序(Initializer)

Spring Boot会自动检测到初始化SQL数据库的以下类型的bean。

  • DataSourceScriptDatabaseInitializer

  • EntityManagerFactory

  • Flyway

  • FlywayMigrationInitializer

  • R2dbcScriptDatabaseInitializer

  • SpringLiquibase

如果你使用的是数据库初始化库的第三方启动器,它可能会提供一个检测器,这样其他类型的Bean也会被自动检测。 要让其他Bean被检测到,请在 META-INF/spring.factories 中注册一个 DatabaseInitializerDetector 的实现。

9.6.2. 检测依赖数据库初始化的 Bean

Spring Boot将自动检测以下类型的Bean,这取决于数据库的初始化。

  • AbstractEntityManagerFactoryBean (除非spring.jpa.defer-datasource-initialization被设置为 true。)

  • DSLContext (jOOQ)

  • EntityManagerFactory (除非 spring.jpa.defer-datasource-initialization 被设置为 true。)

  • JdbcOperations

  • NamedParameterJdbcOperations

如果你使用的是第三方starter数据访问库,它可能会提供一个检测器,这样其他类型的Bean也会被自动检测到。 要想检测其他Bean,请在 META-INF/spring.factories 中注册 DependsOnDatabaseInitializationDetector 的实现。 或者,用 @DependsOnDatabaseInitialization 来注释Bean的类或其 @Bean 方法。

10. NoSQL

Spring Boot提供了许多支持NoSQL技术的 starter。本节将回答在Spring Boot中使用NoSQL时出现的问题。

10.1. 用 Jedis 代替 Lettuce

默认情况下,Spring Boot starter(spring-boot-starter-data-redis)使用 Lettuce。你需要排除这个依赖,而导入 Jedis 的依赖。Spring Boot 管理着这两个依赖,所以你可以切换到Jedis,而不需要指定一个版本。

下面的例子说明了如何在Maven中这样做:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

下面的例子展示了如何在Gradle中这样做:

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-data-redis') {
        exclude group: 'io.lettuce', module: 'lettuce-core'
    }
    implementation 'redis.clients:jedis'
    // ...
}

11. Messaging

Spring Boot提供了许多支持消息传递的starter。 本节将回答与Spring Boot一起使用消息传递时出现的问题。

11.1. 禁用事务 JMS Session

如果你的JMS broker不支持事务session,你必须完全禁用对事务的支持。 如果你创建了自己的 JmsListenerContainerFactory,那就没什么可做的了,因为,默认情况下,它不能进行事务。 如果你想使用 DefaultJmsListenerContainerFactoryConfigurer 来重新使用Spring Boot的默认,你可以禁用事务会话,如下所示。

Java
import jakarta.jms.ConnectionFactory;

import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;

@Configuration(proxyBeanMethods = false)
public class MyJmsConfiguration {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory,
            DefaultJmsListenerContainerFactoryConfigurer configurer) {
        DefaultJmsListenerContainerFactory listenerFactory = new DefaultJmsListenerContainerFactory();
        configurer.configure(listenerFactory, connectionFactory);
        listenerFactory.setTransactionManager(null);
        listenerFactory.setSessionTransacted(false);
        return listenerFactory;
    }

}
Kotlin
import jakarta.jms.ConnectionFactory
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jms.config.DefaultJmsListenerContainerFactory

@Configuration(proxyBeanMethods = false)
class MyJmsConfiguration {

    @Bean
    fun jmsListenerContainerFactory(connectionFactory: ConnectionFactory?,
            configurer: DefaultJmsListenerContainerFactoryConfigurer): DefaultJmsListenerContainerFactory {
        val listenerFactory = DefaultJmsListenerContainerFactory()
        configurer.configure(listenerFactory, connectionFactory)
        listenerFactory.setTransactionManager(null)
        listenerFactory.setSessionTransacted(false)
        return listenerFactory
    }

}

前面的例子覆盖了默认的工厂,它应该适用于你的应用程序定义的任何其他工厂,如果有的话。

12. 批(Batch)处理应用

当人们在Spring Boot应用程序中使用Spring Batch时,经常会出现一些问题。 本节将讨论这些问题。

12.1. 指定一个批处理数据源

默认情况下,批处理应用程序需要一个 DataSource 来存储 job detail。 Spring Batch默认期望有一个单一的 DataSource。 要让它使用除应用程序的主 DataSource 以外的 DataSource,请声明一个 DataSource Bean,用`@BatchDataSource` 注解其 @Bean 方法。 如果你这样做了,并且想要两个数据源,记得要把另一个标记为 @Primary。 要想获得更大的控制权,请将 @EnableBatchProcessing 添加到你的 @Configuration 类中或继承 DefaultBatchConfiguration。参见 @EnableBatchProcessingDefaultBatchConfiguration 的Javadoc获取更多细节。

关于Spring Batch的更多信息,请参阅 Spring Batch项目页面

12.2. 在启动时运行Spring批处理作业

通过在应用程序的classpath中添加 spring-boot-starter-batch,可以启用Spring Batch自动配置。

如果在 application context 中发现单个 Job,它将在启动时被执行(详情见 JobLauncherApplicationRunner)。 如果发现多个 Job,必须使用 spring.batch.job.name 指定应该执行的Job。

要禁止运行在应用程序上下文中发现的 Job,请将 spring.batch.job.enabled 设置为 false

12.3. 从命令行运行

Spring Boot将任何以 -- 开头的命令行参数转换为属性,添加到 Environment 中,见访问命令行属性。 这不应该被用来传递参数给批处理作业。 要在命令行上指定批处理参数,请使用常规格式(即没有 --),如下面的例子所示。

$ java -jar myapp.jar someParameter=someValue anotherParameter=anotherValue

如果你在命令行中指定了 Environment 的属性,它就会被Job所忽略。 考虑一下下面的命令。

$ java -jar myapp.jar --server.port=7070 someParameter=someValue

这只为 batch job 提供一个参数:someParameter=someValue

12.4. 储存 Job Repository

Spring Batch需要为 Job repository 建立一个 data store。 如果你使用Spring Boot,你必须使用一个实际的数据库。 注意,它可以是一个内存数据库,见 配置Job Repository

13. Actuator

Spring Boot包括Spring Boot Actuator。 本节回答了在使用它时经常出现的问题。

13.1. 改变Actuator端点的HTTP端口或地址

在一个独立的应用程序中,执行器的HTTP端口默认为与主HTTP端口相同。 要使应用程序监听一个不同的端口,可以设置外部属性: management.server.port。 要在一个完全不同的网络地址上进行监听(例如,当你有一个内部网络用于管理,而一个外部网络用于用户应用程序),你也可以将 management.server.address 设置为一个有效的IP地址,服务器能够与之绑定。

更多细节,请参见 ManagementServerProperties 源代码和 “生产就绪特性” 中的 “actuator.html” 部分。

13.2. 自定义 ‘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容器中注册处理程序的细节,也请参见 “异常处理” 一节。

13.3. 对敏感数据进行脱敏

/env/configprops/quartz 端点返回的信息可能有些敏感。 默认情况下,所有的值都经过脱敏处理(即用 ****** 代替)。 查看未经脱敏的原始值可以使用每个端点的 showValues 属性进行配置。 这个属性可以被配置为有以下值。

  • ALWAYS - 所有的值都以其未脱敏的形式显示给所有的用户

  • NEVER - 所有的值都是经过脱敏处理的(即用 ****** 代替)。

  • WHEN_AUTHORIZED - 所有的值都以其未脱敏的形式显示给授权用户。

对于HTTP端点来说,如果用户已经认证并拥有端点的角色属性所配置的角色,则被认为是授权的。 默认情况下,任何经过认证的用户都是授权的。 对于JMX端点,所有用户总是被授权。

Properties
management.endpoint.env.show-values=WHEN_AUTHORIZED
management.endpoint.env.roles=admin
Yaml
management:
  endpoint:
    env:
      show-values: WHEN_AUTHORIZED
      roles: "admin"

上面的配置使所有具有 admin 角色的用户能够从 /env 端点查看所有原始形式的值。

show-values 被设置为 ALWAYSWHEN_AUTHORIZED 时,任何由 SanitizingFunction 应用的脱敏处理仍将被应用。

13.3.1. 自定义脱敏

为了控制脱敏,定义一个 SanitizingFunction bean。调用该函数的 SanitizableData 提供了对key和value以及它们所来自的 PropertySource 的访问。这允许你,例如,对来自一个特定 property source 的每个值进行脱敏。每个 SanitizingFunction 被依次调用,直到一个函数改变了可脱敏数据的值。

13.4. 将健康指标(Health Indicator)映射到 Micrometer Metric

Spring Boot的健康指标返回一个 Status 类型,以显示整个系统的健康状况。 如果你想监测或警告某个特定应用程序的健康水平,你可以用 Micrometer 把这些状态导出为指标。 默认情况下,Spring Boot使用状态代码 “UP”、“DOWN”、“OUT_OF_SERVICE” 和 “UNKNOWN”。 要导出这些,你需要将这些状态转换为一些数字集,以便它们可以被 Micrometer Gauge 使用。

下面的例子显示了编写这种exporter的一种方法。

Java
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;

import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyHealthMetricsExportConfiguration {

    public MyHealthMetricsExportConfiguration(MeterRegistry registry, HealthEndpoint healthEndpoint) {
        // This example presumes common tags (such as the app) are applied elsewhere
        Gauge.builder("health", healthEndpoint, this::getStatusCode).strongReference(true).register(registry);
    }

    private int getStatusCode(HealthEndpoint health) {
        Status status = health.health().getStatus();
        if (Status.UP.equals(status)) {
            return 3;
        }
        if (Status.OUT_OF_SERVICE.equals(status)) {
            return 2;
        }
        if (Status.DOWN.equals(status)) {
            return 1;
        }
        return 0;
    }

}
Kotlin
import io.micrometer.core.instrument.Gauge
import io.micrometer.core.instrument.MeterRegistry
import org.springframework.boot.actuate.health.HealthEndpoint
import org.springframework.boot.actuate.health.Status
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyHealthMetricsExportConfiguration(registry: MeterRegistry, healthEndpoint: HealthEndpoint) {

    init {
        // This example presumes common tags (such as the app) are applied elsewhere
        Gauge.builder("health", healthEndpoint) { health ->
            getStatusCode(health).toDouble()
        }.strongReference(true).register(registry)
    }

    private fun getStatusCode(health: HealthEndpoint): Int {
        val status = health.health().status
        if (Status.UP == status) {
            return 3
        }
        if (Status.OUT_OF_SERVICE == status) {
            return 2
        }
        if (Status.DOWN == status) {
            return 1
        }
        return 0
    }

}

14. Security

本节讨论使用Spring Boot时的安全问题,包括在Spring Boot中使用Spring Security产生的问题。

关于Spring Security的更多信息,请参阅 Spring Security项目页面

14.1. 关闭 Spring Boot Security 配置

如果你在应用程序中定义了一个带有 SecurityFilterChain bean 的 @Configuration,它将关闭Spring Boot中默认的webapp安全设置。

14.2. 修改 UserDetailsService 和添加用户账户

如果你提供了一个 @Bean 类型的 AuthenticationManagerAuthenticationProviderUserDetailsService,那么 InMemoryUserDetailsManager 的默认 @Bean 不会被创建。 这意味着你拥有Spring Security的全部功能集(如 各种认证选项)。

添加用户账户的最简单方法是提供你自己的 UserDetailsService bean。

14.3. 运行在代理服务器后面时启用HTTPS

确保你的所有主要端点都只能通过HTTPS来使用,这对任何应用程序来说都是一项重要的工作。 如果你使用Tomcat作为servlet容器,那么Spring Boot如果检测到一些环境设置,就会自动添加Tomcat自己的 RemoteIpValve,而且你应该能够依靠 HttpServletRequest 来报告它是否安全(甚至在处理真正的SSL termination的代理服务器的下游)。 标准行为是由某些请求头(x-forwarded-forx-forwarded-proto)的存在与否决定的,其名称是常规的,所以它应该与大多数前端代理一起工作。 你可以通过在 application.properties 中添加一些条目来打开这个valve,如下例所示。

Properties
server.tomcat.remoteip.remote-ip-header=x-forwarded-for
server.tomcat.remoteip.protocol-header=x-forwarded-proto
Yaml
server:
  tomcat:
    remoteip:
      remote-ip-header: "x-forwarded-for"
      protocol-header: "x-forwarded-proto"

(这两个属性中的任何一个的存在,都会开启valve。 另外,你可以通过使用 WebServerFactoryCustomizer Bean 来定制 TomcatServletWebServerFactory 来添加 RemoteIpValve。)

要配置Spring Security,使所有(或部分)请求都需要安全通道,可以考虑添加你自己的 SecurityFilterChain bean,该bean添加了以下 HttpSecurity 配置。

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class MySecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Customize the application security ...
        http.requiresChannel((channel) -> channel.anyRequest().requiresSecure());
        return http.build();
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
class MySecurityConfig {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        // Customize the application security ...
        http.requiresChannel { requests -> requests.anyRequest().requiresSecure() }
        return http.build()
    }

}

15. 热交换

Spring Boot支持热交换。 本节回答关于它如何工作的问题。

15.1. 重新加载静态内容

有几种热重载的选择。推荐的方法是使用 spring-boot-devtools,因为它提供了额外的开发时功能,如支持快速应用重启和LiveReload,以及合理的开发时配置(如模板缓存)。Devtools通过监控classpath的变化而工作。这意味着静态资源的变化必须被 "built" 以使其生效。默认情况下,在Eclipse中,当你保存你的更改时,这将自动发生。在IntelliJ IDEA中,Make Project命令会触发必要的构建。由于默认的重启排除,对静态资源的更改不会触发应用程序的重启。然而,它们确实会触发实时重载。

另外,在IDE中运行(尤其是在调试开启的情况下)也是一种很好的开发方式(所有现代的IDE都允许重新加载静态资源,通常也允许热交换Java类的变化)。

最后,可以对 Maven和Gradle插件 进行配置(见 addResources 属性),以支持从命令行运行,直接从源码重新加载静态文件。如果你用更高级别的工具编写该代码,你可以用外部css/js编译器进程来使用。

15.2. 在不重启容器的情况下重新加载模板

Spring Boot支持的大多数模板技术都包括一个禁用缓存的配置选项(本文稍后描述)。如果你使用 spring-boot-devtools 模块,这些属性会在开发时自动为你配置

15.2.1. Thymeleaf 模板

如果你使用Thymeleaf,将 spring.thymeleaf.cache 设置为 false。 参见 ThymeleafAutoConfiguration 了解其他Thymeleaf定制选项。

15.2.2. FreeMarker 模板

如果你使用FreeMarker,将 spring.freemarker.cache 设置为 false。 参见 FreeMarkerAutoConfiguration,了解其他FreeMarker定制选项。

15.2.3. Groovy 模板

如果你使用Groovy模板,请将 spring.groovy.template.cache 设置为 false。 参见 GroovyTemplateAutoConfiguration 了解其他Groovy定制选项。

15.3. 快速重启应用程序

spring-boot-devtools 模块包括对自动重新启动应用程序的支持。虽然没有像 JRebel 这样的技术那么快,但通常比 "冷启动" 快得多。在研究本文档后面讨论的一些更复杂的重载选项之前,你也许应该先试一下它。

更多细节,见 using.html 部分。

15.4. 在不重启容器的情况下重新加载Java类

许多现代IDE(Eclipse、IDEA等)支持字节码的热交换。 因此,如果你做了一个不影响类或方法签名的改变,它应该干净地重新加载,没有副作用。

16. 测试

Spring Boot包括一些测试工具和支持类,以及一个专门的starter,提供常见的测试依赖。 本节回答关于测试的常见问题。

16.1. 用Spring Security进行测试

Spring Security提供了对作为特定用户运行测试的支持。 例如,下面的代码段中的测试将以拥有 ADMIN 角色的认证用户运行。

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

@WebMvcTest(UserController.class)
class MySecurityTests {

    @Autowired
    private MockMvc mvc;

    @Test
    @WithMockUser(roles = "ADMIN")
    void requestProtectedUrlWithUser() throws Exception {
        this.mvc.perform(get("/"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.security.test.context.support.WithMockUser
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders

@WebMvcTest(UserController::class)
class MySecurityTests(@Autowired val mvc: MockMvc) {

    @Test
    @WithMockUser(roles = ["ADMIN"])
    fun requestProtectedUrlWithUser() {
        mvc.perform(MockMvcRequestBuilders.get("/"))
    }

}

Spring Security提供了与Spring MVC Test的全面集成,在使用 @WebMvcTest 片段和 MockMvc 测试controller时也可以使用。

关于Spring Security的测试支持的其他细节,请参阅Spring Security的 参考文档

16.2. 结构化 "@Configuration" 类,以纳入片段测试中

片段测试通过限制Spring框架的组件扫描,使其在有限的组件类型的基础上工作。 对于任何不是通过组件扫描创建的Bean,例如,使用 @Bean 注解创建的Bean,切片测试将无法从应用上下文中包含/排除它们。 考虑一下这个例子。

import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
        return http.build();
    }

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

}

对于具有上述 @Configuration 类的应用程序的 @WebMvcTest 来说,你可能希望在应用程序上下文中有 SecurityFilterChain bean,这样你就可以测试你的控制器端点是否被正确保护。 然而,MyConfiguration 没有被 @WebMvcTest 的组件扫描过滤器接收,因为它不符合过滤器所指定的任何类型。 你可以通过在测试类中注解 @Import(MyConfiguration.class) 来明确包含配置。 这将加载 MyConfiguration 中的所有Bean,包括 BasicDataSource Bean,在测试Web层时不需要。 将配置类分成两个,就可以只导入security configuration。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
        return http.build();
    }

}
import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDatasourceConfiguration {

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

}

当某个领域的Bean需要包含在片段测试中时,拥有一个单一的配置类可能是低效的。 相反,将应用程序的配置结构化为多个带有特定领域的Bean的细化类,可以只为特定的片断测试导入这些Bean。

17. 构建

Spring Boot包括Maven和Gradle的构建插件。 本节回答有关这些插件的常见问题。

17.1. 生成构建信息

Maven插件和Gradle插件都允许生成包含项目的坐标、名称和版本的构建信息。 这些插件还可以通过配置来增加额外的属性。 当有这样的文件时,Spring Boot会自动配置一个 BuildProperties bean。

要用Maven生成构建信息,请为 build-info goal 添加一个execution,如下例所示。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.2.0-SNAPSHOT</version>
            <executions>
                <execution>
                    <goals>
                        <goal>build-info</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

下面的例子也是用Gradle做的。

springBoot {
    buildInfo()
}
更多细节请参见 Spring Boot Gradle插件文档

17.2. 生成Git信息

Maven和Gradle都允许生成 git.properties 文件,其中包含项目构建时 git 源代码库的状态信息。

对于Maven用户,spring-boot-starter-parent POM包含一个预先配置的插件,用于生成 git.properties 文件。要使用它,请在你的POM中为 Git Commit Id Plugin 添加以下声明。

<build>
    <plugins>
        <plugin>
            <groupId>io.github.git-commit-id</groupId>
            <artifactId>git-commit-id-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Gradle用户可以通过使用 gradle-git-properties 插件实现同样的结果,如下面的例子所示。

plugins {
    id "com.gorylenko.gradle-git-properties" version "2.4.1"
}

Maven和Gradle插件都允许配置 git.properties 中包含的属性。

git.properties 中的提交时间应符合以下格式。yyyy-MM-dd’T’HH:mm:ssZ。 这是上面列出的两个插件的默认格式。 使用这种格式可以将时间解析为 Date,当序列化为JSON时,其格式由Jackson的日期序列化配置设置控制。

17.3. 自定义依赖版本

spring-boot-dependencies POM管理常见依赖的版本。 Maven和Gradle的Spring Boot插件允许使用构建属性来定制这些管理的依赖版本。

每个Spring Boot版本都是针对这组特定的第三方依赖关系设计和测试的。 覆盖版本可能会导致兼容性问题。

要用Maven覆盖依赖版本,请参见Maven插件文档的 这一部分

要在Gradle中覆盖依赖版本,请看Gradle插件的 这部分 文档。

17.4. 用Maven创建可执行的JAR

spring-boot-maven-plugin 可以用来创建一个可执行的 “fat” JAR。 如果你使用 spring-boot-starter-parent POM,你可以声明该插件,你的jar被repackaged,如下所示。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

如果你不使用父POM,你仍然可以使用该插件。 但是,你必须另外添加一个 <executions> 部分,如下所示。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.2.0-SNAPSHOT</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

有关完整的使用细节,请参见 插件文档

17.5. 将Spring Boot应用作为依赖(Dependency)使用

与war文件一样,Spring Boot应用程序不打算作为一个依赖关系使用。 如果你的应用程序包含你想与其他项目共享的类,建议的方法是将这些代码移到一个单独的模块中。 然后,这个单独的模块就可以被你的应用程序和其他项目所依赖了。

如果你不能按照上面的建议重新安排你的代码,必须对Spring Boot的Maven和Gradle插件进行配置,以产生一个适合作为依赖的独立工件。可执行归档文件不能作为依赖使用,因为 可执行jar格式 将 application 类打包在 BOOT-INF/classes 中。这意味着,当可执行jar被用作依赖时,它们无法被找到。

为了产生两个工件,一个可以作为依赖,一个是可执行的,必须指定一个 classifier(分类器)。 这个分类器被应用于可执行归档文件的名称,留下默认的归档文件作为依赖使用。

要在Maven中配置 exec 的分类器,你可以使用以下配置。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </plugin>
    </plugins>
</build>

17.6. 当一个可执行的Jar运行时提取特定的库

大多数嵌套在可执行jar中的库不需要解压就可以运行。 然而,某些库会有问题。 例如,JRuby包括它自己的嵌套jar支持,它假定 jruby-complete.jar 总是作为一个文件直接可用。

为了处理任何有问题的库,你可以标记特定的嵌套jar在可执行jar第一次运行时应自动解包。 这些嵌套的jar被写在由 java.io.tmpdir 系统属性(system property)确定的临时目录下面。

应注意确保你的操作系统被配置成在应用程序仍在运行时不会删除已解压到临时目录的jar。

例如,为了表明JRuby应被标记为使用Maven插件进行解包,你可以添加以下配置。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <requiresUnpack>
                    <dependency>
                        <groupId>org.jruby</groupId>
                        <artifactId>jruby-complete</artifactId>
                    </dependency>
                </requiresUnpack>
            </configuration>
        </plugin>
    </plugins>
</build>

17.7. 用 Exclusions 创建一个不可执行的JAR

通常,如果你有一个可执行的和一个不可执行的jar作为两个独立的构建产品,可执行的版本有额外的配置文件,这些文件在库jar中是不需要的。 例如,application.yaml 配置文件可能被排除在非可执行JAR中。

在Maven中,可执行jar必须是主要工件,你可以为库添加一个 classified (分类) jar,如下所示。

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <artifactId>maven-jar-plugin</artifactId>
            <executions>
                <execution>
                    <id>lib</id>
                    <phase>package</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                    <configuration>
                        <classifier>lib</classifier>
                        <excludes>
                            <exclude>application.yaml</exclude>
                        </excludes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

17.8. 用Maven开始远程调试Spring Boot应用程序

要在用Maven启动的Spring Boot应用程序上附加一个远程调试器,可以使用 maven插件jvmArguments 属性。

更多细节见 本例

17.9. 在不使用spring-boot-antlib的情况下从Ant构建可执行文件

要用Ant进行构建,你需要抓取依赖项,进行编译,然后创建一个jar或war归档文件。 为了使其可执行,你可以使用 spring-boot-antlib 模块,或者你可以按照这些说明。

  1. 如果你正在构建一个jar,将应用程序的类和资源打包在一个嵌套的 BOOT-INF/classes 目录下。 如果你正在构建一个war,像往常一样将应用程序的类打包在一个嵌套的 WEB-INF/classes 目录下。

  2. 对于jar来说,在一个嵌套的 BOOT-INF/lib 目录下添加运行时依赖项,对于war来说,在 WEB-INF/lib 目录下添加运行时依赖项。 记住 不要 压缩存档中的条目。

  3. 在嵌套的 BOOT-INF/lib 目录中添加 provided(嵌入式容器)依赖,如果是jar,则添加 WEB-INF/lib-provided 目录,如果是war,则添加 WEB-INF/lib-provided。 记住 不要 压缩存档中的条目。

  4. 在存档的根部添加 spring-boot-loader 类(以便 Main-Class 可用)。

  5. 使用适当的 launcher(比如jar文件的 JarLauncher)作为manifest中的 Main-Class 属性,并将其需要的其他属性指定为清单条目—​主要是通过设置 Start-Class 属性。

下面的例子显示了如何用Ant构建一个可执行的归档文件。

<target name="build" depends="compile">
    <jar destfile="target/${ant.project.name}-${spring-boot.version}.jar" compress="false">
        <mappedresources>
            <fileset dir="target/classes" />
            <globmapper from="*" to="BOOT-INF/classes/*"/>
        </mappedresources>
        <mappedresources>
            <fileset dir="src/main/resources" erroronmissingdir="false"/>
            <globmapper from="*" to="BOOT-INF/classes/*"/>
        </mappedresources>
        <mappedresources>
            <fileset dir="${lib.dir}/runtime" />
            <globmapper from="*" to="BOOT-INF/lib/*"/>
        </mappedresources>
        <zipfileset src="${lib.dir}/loader/spring-boot-loader-jar-${spring-boot.version}.jar" />
        <manifest>
            <attribute name="Main-Class" value="org.springframework.boot.loader.JarLauncher" />
            <attribute name="Start-Class" value="${start-class}" />
        </manifest>
    </jar>
</target>

18. 超前(Ahead-of-time)处理

人们在使用Spring Boot应用程序的超前处理时,经常会出现一些问题。本节讨论这些问题。

18.1. Condition

超前处理可以优化应用程序,并在构建时根据环境评估 conditionsProfiles 是通过 condition 实现的,因此也会受到影响。

如果你想在一个超前优化的应用程序中根据条件创建Bean,你必须在构建应用程序时设置好环境。在构建时超前处理时创建的Bean在运行应用程序时总是被创建的,并且不能被关闭。要做到这一点,你可以设置在构建应用程序时应该使用的 profiles。

对于Maven来说,这可以通过设置 spring-boot-maven-plugin:process-aot execution 的 profiles 配置来实现:

<profile>
    <id>native</id>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>process-aot</id>
                            <configuration>
                                <profiles>profile-a,profile-b</profiles>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</profile>

对于 Gradle,你需要配置 ProcessAot 任务:

tasks.withType(org.springframework.boot.gradle.tasks.aot.ProcessAot).configureEach {
    args('--spring.profiles.active=profile-a,profile-b')
}

在运行超前优化的应用程序时,支持只改变不影响条件的配置属性的配置文件,不受限制。

19. 传统的部署方式

Spring Boot支持传统的部署,也支持更现代的部署形式。 本节回答关于传统部署的常见问题。

19.1. 创建一个可部署的WAR文件

由于Spring WebFlux并不严格依赖servlet API,而且应用程序默认部署在嵌入式Reactor Netty服务器上,因此WebFlux应用程序不支持War部署。

制作可部署的war文件的第一步是提供一个 SpringBootServletInitializer 子类并重写其 configure 方法。 这样做可以利用Spring框架对servlet 3.0的支持,让你在Servlet容器启动时配置你的应用程序。 通常,你应该更新你的应用程序的main类来扩展 SpringBootServletInitializer,如下面的例子所示。

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(MyApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer

@SpringBootApplication
class MyApplication : SpringBootServletInitializer() {

    override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
        return application.sources(MyApplication::class.java)
    }

}

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

下一步是更新你的构建配置,使你的项目产生一个war文件而不是jar文件。 如果你使用Maven和 spring-boot-starter-parent(它为你配置了Maven的war插件),你只需修改 pom.xml,将打包改为war,如下所示。

<packaging>war</packaging>

如果你使用Gradle,你需要修改 build.gradle 以将war插件应用到项目中,如下所示。

apply plugin: 'war'

这个过程的最后一步是确保嵌入式servlet容器不会干扰war文件所部署的servlet容器。 要做到这一点,你需要把嵌入式servlet容器的依赖标记为 provided。

如果你使用Maven,下面的例子将servlet容器(本例中为Tomcat)标记为 provided。

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <!-- ... -->
</dependencies>

如果你使用Gradle,下面的例子将servlet容器(本例中为Tomcat)标记为 provided。

dependencies {
    // ...
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    // ...
}
providedRuntime 比Gradle的 compileOnly 配置要好。 在其他限制中,compileOnly 的依赖不在测试的classpath上,所以任何基于web的集成测试都会失败。

如果你使用 Spring Boot构建工具,将嵌入式servlet容器的依赖性标记为 provided,会产生一个可执行的war文件,并将提供的依赖打包在 lib-provided 目录下。这意味着,除了可以部署到servlet容器外,你还可以通过在命令行上使用 java -jar 来运行你的应用程序。

19.2. 将现有的应用程序转换为Spring Boot

要将现有的非web Spring应用转换为Spring Boot应用,请替换创建 ApplicationContext 的代码,并将其替换为对 SpringApplicationSpringApplicationBuilder 的调用。Spring MVC Web应用程序通常可以先创建一个可部署的war应用程序,之后再将其迁移到可执行的war或jar中。请参阅 《入门指南》中关于将jar转换为war的内容

要通过扩展 SpringBootServletInitializer(例如,在一个名为 Application 的类中)并添加Spring Boot @SpringBootApplication 注解来创建一个可部署的war,请使用与下面例子中所示类似的代码。

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        // Customize the application or call application.sources(...) to add sources
        // Since our example is itself a @Configuration class (through
        // @SpringBootApplication)
        // we actually do not need to override this method.
        return application;
    }


}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer

@SpringBootApplication
class MyApplication : SpringBootServletInitializer() {

    override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
        // Customize the application or call application.sources(...) to add sources
        // Since our example is itself a @Configuration class (through @SpringBootApplication)
        // we actually do not need to override this method.
        return application
    }

}

请记住,无论你在 sources 中放了什么,都只是一个Spring的 ApplicationContext。 通常情况下,任何已经工作的东西都应该在这里工作。 可能有一些Bean你可以稍后删除,让Spring Boot为它们提供自己的默认值,但在你需要这么做之前,应该可以得到一些工作。

静态资源可以移到classpath根中的 /public(或 /static/resources/META-INF/resources)。 这同样适用于 messages.properties(Spring Boot自动检测到classpath根部)。

对Spring DispatcherServlet 和Spring Security的简单使用应该不需要进一步修改。 如果你的应用程序有其他功能(例如,使用其他Servlet或过滤器),你可能需要在 Application 上下文中添加一些配置,替换 web.xml 中的那些元素,如下所示。

  • 一个 @Bean 类型的 ServletServletRegistrationBean 在容器中安装该Bean,就像在 web.xml 中的 <servlet/><servlet-mapping/> 一样。

  • 一个 @Bean 类型的 FilterFilterRegistrationBean 的行为类似(作为一个 <filter/><filter-mapping/>)。

  • 一个XML文件中的 ApplicationContext 可以通过 @ImportResource 添加到你的 Application 中。 另外,在注解配置(configuration)已经被大量使用的情况下,可以在几行中重新创建 @Bean 定义。

一旦战war件开始工作,你可以通过在你的 Application 中添加一个 main 方法使其可执行,如下面的例子所示。

Java
public static void main(String[] args) {
    SpringApplication.run(MyApplication.class, args);
}
Kotlin
fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

如果你打算以war或可执行程序的形式启动你的应用程序,你需要在一个方法中分享构建器的定制,该方法既可用于 SpringBootServletInitializer 回调,也可用于类似以下类中的`main`方法。

Java
import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return customizerBuilder(builder);
    }

    public static void main(String[] args) {
        customizerBuilder(new SpringApplicationBuilder()).run(args);
    }

    private static SpringApplicationBuilder customizerBuilder(SpringApplicationBuilder builder) {
        return builder.sources(MyApplication.class).bannerMode(Banner.Mode.OFF);
    }

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer

@SpringBootApplication
class MyApplication : SpringBootServletInitializer() {

    override fun configure(builder: SpringApplicationBuilder): SpringApplicationBuilder {
        return customizerBuilder(builder)
    }

    companion object {

        @JvmStatic
        fun main(args: Array<String>) {
            customizerBuilder(SpringApplicationBuilder()).run(*args)
        }

        private fun customizerBuilder(builder: SpringApplicationBuilder): SpringApplicationBuilder {
            return builder.sources(MyApplication::class.java).bannerMode(Banner.Mode.OFF)
        }

    }

}

应用程序可以归入一个以上的类别。

  • Servlet 3.0以上的应用程序,没有 web.xml

  • web.xml 的应用程序。

  • 有context层次结构的应用程序。

  • 没有context层次结构的应用程序。

所有这些都应该是可以转换的,但每个人可能需要稍微不同的技术。

如果Servlet 3.0+应用程序已经使用了Spring Servlet 3.0+初始化器支持类,那么它们可能会很容易转化。通常情况下,现有 WebApplicationInitializer 的所有代码都可以移到 SpringBootServletInitializer 中。如果你现有的应用程序有一个以上的 ApplicationContext(例如,如果它使用 AbstractDispatcherServletInitializer),那么你也许可以将所有的上下文源合并到一个 SpringApplication 中。你可能遇到的主要复杂情况是,如果合并不成功,你需要维护上下文的层次结构。请参阅关于构建层次结构的条目,以了解实例。一个包含Web特定功能的现有父级上下文通常需要被拆分,以便所有 ServletContextAware 组件都在子级上下文中。

还不是 Spring application 的应用程序可能会被转换为 Spring Boot application,前面提到的指导可能会有所帮助。但是,你可能还会遇到问题。在这种情况下,我们建议 在Stack Overflow上提问,标签为 spring-boot

19.3. 将一个WAR部署到WebLogic上

要将Spring Boot应用程序部署到WebLogic,你必须确保你的servlet initializer 直接 实现 WebApplicationInitializer(即使你从已经实现它的基类中扩展)。

一个典型的 WebLogic 的 initializer 应该类似于下面的例子。

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.WebApplicationInitializer;

@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer implements WebApplicationInitializer {

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
import org.springframework.web.WebApplicationInitializer

@SpringBootApplication
class MyApplication : SpringBootServletInitializer(), WebApplicationInitializer

如果你使用Logback,你还需要告诉WebLogic喜欢打包的版本,而不是预先安装在服务器上的版本。 你可以通过添加一个 WEB-INF/weblogic.xml 文件来做到这一点,其内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
    xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd
        http://xmlns.oracle.com/weblogic/weblogic-web-app
        https://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
    <wls:container-descriptor>
        <wls:prefer-application-packages>
            <wls:package-name>org.slf4j</wls:package-name>
        </wls:prefer-application-packages>
    </wls:container-descriptor>
</wls:weblogic-web-app>

20. Docker Compose

本节包括与Spring Boot中的Docker Compose支持有关的主题。

20.1. 自定义JDBC URL

在 Docker Compose 中使用 JdbcConnectionDetails 时,JDBC URL的参数可以通过对服务应用 org.springframework.boot.jdbc.parameters label 来定制。比如说:

services:
  postgres:
    image: 'postgres:15.3'
    environment:
      - 'POSTGRES_USER=myuser'
      - 'POSTGRES_PASSWORD=secret'
      - 'POSTGRES_DB=mydb'
    ports:
      - '5432:5432'
    labels:
      org.springframework.boot.jdbc.parameters: 'ssl=true&sslmode=require'

有了这个 Docker Compose 文件,使用的JDBC URL是 jdbc:postgresql://127.0.0.1:5432/mydb?ssl=true&sslmode=require