章节 IX. ‘How-to’ 指南

本节提供了一些常见的 ‘how do I do that…​’ 的问题的答案,这些问题在使用 Spring Boot 时经常出现。它的覆盖面不是详尽无遗的,但它涵盖了很多。

如果你有一个特定的问题,我们这里没有介绍,你可能希望查看 stackoverflow.com,看看是否有人已经提供了答案。这也是一个询问新问题的好地方(请使用 spring-boot 标签)。

我们也很乐意延长这一部分。如果你想添加一个 ‘how-to’,请向我们发送一个推送请求。

74. Spring Boot 应用程序

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

74.1 创建你自己的失败分析器

FailureAnalyzer 是一种在启动时拦截异常并将其转换为人工可读消息的好方法,它包装在 FailureAnalysis 中。 Spring Boot 提供了应用程序上下文相关异常的分析器。JSR-303 验证等提供了这样的分析器。你也可以创建你自己的。

AbstractFailureAnalyzer 是 FailureAnalyzer 的简便扩展,用于检查要处理的异常中是否存在指定的异常类型。你可以从其中扩展你的实现,以便只有在实际存在时才有机会处理异常。如果由于任何原因无法处理异常,则返回 null 以给另一个实现处理异常的机会。

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

org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.ProjectConstraintViolationFailureAnalyzer
[Note] Note

如果需要访问 BeanFactory 或 Environment,则 FailureAnalyzer 可以简单地分别实现 BeanFactoryAware 或 EnvironmentAware。

74.2 自动配置疑难解答

Spring Boot 自动配置尽力做正确的事情,但有时事情会失败,并且很难解释原因。

在任何 Spring Boot ApplicationContext 中都有一个非常有用的 ConditionEvaluationReport。如果启用 DEBUG 日志输出,则可以看到它。如果使用 spring-boot-actuator(参见 执行器那章),那么还有一个 conditions 节点在 JSON 中呈现报告。使用该节点来调试应用程序,并查看 Spring Boot 在运行时添加(以及没有添加)了哪些特性。

通过查看源代码和 Javadoc 可以回答更多的问题。当阅读代码时,记住以下的经验法则:

  • 查找称为 *AutoConfiguration 的类并读取它们的源代码。特别注意 @Conditional* 注解,找出它们启用的特性和时间。添加 --debug 到命令行或系统属性 -Ddebug,以在控制台上获取应用程序中做出的所有自动配置决策的日志。在运行的执行器应用程序中,查看相同信息的 conditions 节点( /actuator/conditions 或同等的 JMX)。
  • 查找 @ConfigurationProperties(例如 ServerProperties)的类,并从那里读取可用的外部配置选项。@ConfigurationProperties 注解有一个 name 属性,用作外部属性的前缀。因此,ServerProperties 具有 prefix="server",其配置属性是 server.port、server.address,等等。在运行的执行器应用程序中,查看 configprops 节点。
  • 在 Binder 上查找 bind 方法的使用,以宽松的方式显式地将配置值从 Environment 中拉出。它通常与前缀一起使用。
  • 查找与 Environment 直接绑定的 @Value 注解。
  • 查找 @ConditionalOnExpression 注解,这些注解根据 SpEL 表达式打开和关闭特性,通常使用 Environment 解析的占位符进行评估。

74.3 在启动之前自定义环境或应用程序上下文

SpringApplication 具有 ApplicationListeners 和 ApplicationContextInitializers,它们将用于应用定制到上下文或环境。Spring Boot 加载了许多这样的定制,以便在 META-INF/spring.factories 内部使用。有多种方法来注册附加的自定义:

  • 在应用程序之前,通过在运行 SpringApplication 之前调用 addListeners 和 addInitializers 方法来编程。
  • 通过设置 context.initializer.classes 或 context.listener.classes 属性,每个应用程序声明性地使用。
  • 通过添加 META-INF/spring.factories 并打包应用程序都用作库的 jar 文件,每个应用程序声明性地使用。

SpringApplication 将一些特殊的 ApplicationEvents 发送到监听器(有些甚至在创建上下文之前),然后为 ApplicationContext 发布的事件注册监听器。完整的列表请参阅 ‘Spring Boot 特性’ 部分中的 “小节 23.5, “应用程序事件和监听器””。

在使用 EnvironmentPostProcessor 刷新应用程序上下文之前,还可以自定义 Environment。每一个实现都应该在 META-INF/spring.factories 中注册,如下面的例子所示:

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

这个实现可以加载任意文件并将它们添加到 Environment 中。例如,下面的示例从类路径加载 YAML 配置文件:

public class EnvironmentPostProcessorExample 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) {
		if (!path.exists()) {
			throw new IllegalArgumentException("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);
		}
	}

}
[Tip] Tip

Environment 默认已经准备好了所有的 Spring Boot 加载的普通属性源。因此,可以从环境中获得文件的位置。前面的示例在列表的末尾添加了 custom-resource 属性源,以便在任何其他常见位置中定义的键优先。自定义实现可以定义另一个顺序。

[Caution] 警告

虽然在你的 @SpringBootApplication 上使用 @PropertySource 似乎是在环境中加载自定义资源的一种方便和容易的方法,但我们不推荐使用它,因为 Spring Boot 在刷新 ApplicationContext 之前准备 Environment。使用 @PropertySource 定义的任何键都加载得太晚,不会对自动配置产生任何影响。

74.4 构建应用程序上下文层次结构(添加父或根上下文)

可以使用 ApplicationBuilder 类创建父/子 ApplicationContext 层次结构。有关的更多信息请参阅 ‘Spring Boot 特性’ 部分中的 “小节 23.4, “Fluent 构建 API””。

74.5 创建非 web 应用程序

并非所有的 Spring 应用程序都必须是 web 应用程序(或 web 服务)。如果你想在 main 方法中执行一些代码,但是也希望引导 Spring 应用程序来设置要使用的基础设施,那么可以使用 Spring Boot 的 SpringApplication 特性。SpringApplication 根据其是否认为需要 Web 应用程序更改其 ApplicationContext 类。你可以做的第一件事是帮助将与服务器相关的依赖项(例如 servlet API)从类路径中删除。如果你不能这样做(例如,你从同一代码库运行两个应用程序),那么可以在 SpringApplication 实例上显式调用 setWebApplicationType(WebApplicationType.NONE),或者通过设置 applicationContextClass 属性(通过 Java API 或外部属性)。你希望作为业务逻辑运行的应用程序代码可以作为 CommandLineRunner 实现,并作为 @Bean 定义放入上下文中。

75. 属性和配置

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

75.1 在构建时自动扩展属性

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

75.1.1 使用 Maven 自动扩展属性

可以通过使用资源筛选来自动扩展 Maven 项目中的属性。如果使用 spring-boot-starter-parent,那么可以使用 @..@ 占位符引用 Maven 项目属性,如下面的示例所示:

app.encoding=@project.build.sourceEncoding@
app.java.version=@java.version@
[Note] Note

只有这样的生产配置被过滤(换句话说,没有对 src/test/resources 应用过滤)。

[Tip] Tip

如果启用了 addResources 标志,spring-boot:run 目标可以直接将 src/main/resources 添加到类路径(用于热重载的目的)。这样做绕过了资源过滤和这个特性。相反,你可以使用 exec:java 目标或自定义插件的配置。更多细节请参见插件使用页。

如果不使用启动器父级,则需要在 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>
[Note] Note

如果在配置中使用标准 Spring 占位符(如 ${placeholder}),则 useDefaultDelimiters 属性非常重要。 如果该属性未设置为 false,则可以通过构建扩展这些属性。

75.1.2 使用 Gradle 自动扩展属性

你可以通过配置 Java 插件的 processResources 任务来自动扩展 Gradle 项目中的属性,如下面的示例所示:

processResources {
	expand(project.properties)
}

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

app.name=${name}
app.description=${description}
[Note] Note

Gradle 的 expand 方法使用 Groovy 的 SimpleTemplateEngine,它转换 ${..} 令牌。${..} 样式与 Spring 自己的属性占位符机制冲突。若要将 Spring 属性占位符与自动扩展一起使用,请转换 Spring 属性占位符如下: \${..}。

75.2 外部化 SpringApplication 的配置

SpringApplication 具有 bean 属性(主要是 setter),因此你可以在创建应用程序时使用它的 Java API 来修改其行为。或者,可以通过在 spring.main.* 中设置属性来实现配置的外部化。例如,在 application.properties 中,你可能具有以下设置:

spring.main.web-application-type=none
spring.main.banner-mode=off

然后在启动时不打印 Spring Boot 横幅,应用程序不启动嵌入式 Web 服务器。

外部配置中定义的属性重写了用 Java API 指定的值,除了用于创建 ApplicationContext 的源的显著例外。考虑以下应用:

new SpringApplicationBuilder()
	.bannerMode(Banner.Mode.OFF)
	.sources(demo.MyApp.class)
	.run(args);

现在考虑以下配置:

spring.main.sources=com.acme.Config,com.acme.ExtraConfig
spring.main.banner-mode=console

实际的应用程序现在显示横幅(通过配置覆盖),并使用 ApplicationContext 的三个源(按照以下顺序):demo.MyApp、com.acme.Config 和 com.acme.ExtraConfig。

75.3 更改应用程序外部属性的位置

默认情况下,来自不同源的属性按照定义的顺序添加到 Spring Environment(参见 ‘Spring Boot 特性’ 部分中的 “小节 24, 外部配置” 以获得确切的顺序)。

扩充和修改此排序的一个好方法是向应用程序源添加 @PropertySource 注解。传递给 SpringApplication 静态便利方法的类和使用 setSources() 添加的类将检查它们是否有 @PropertySources。如果这样做,那么这些属性将足够早地添加到 Environment 中,以便在 ApplicationContext 生命周期的所有阶段中使用。以这种方式添加的属性比使用默认位置(如 application.properties)、系统属性、环境变量或命令行添加的任何优先级要低。

还可以提供以下系统属性(或环境变量)来改变行为:

  • spring.config.name(SPRING_CONFIG_NAME):默认为 application 作为文件名的根。
  • spring.config.location(SPRING_CONFIG_LOCATION):要加载的文件(如类路径资源或URL)。为此文档设置了一个单独的 Environment 属性源,可以通过系统属性、环境变量或命令行覆盖它。

无论你在环境中设置什么,Spring Boot 总是加载 application.properties。默认情况下,如果使用 YAML,则将带有 ‘.yml’ 扩展名的文件添加到列表中。

Spring Boot 记录在 DEBUG 级别加载的配置文件和在 TRACE 级别未找到的候选文件。

更多详细内容请参阅 ConfigFileApplicationListener。

75.4 使用短命令行参数

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

server.port=${port:8080}
[Tip] Tip

如果从 spring-boot-starter-parent POM 继承,则 maven-resources-plugins 的默认筛选器令牌已经从 ${*} 更改为 @(即,@maven.token@ 替代 ${maven.token}), 以防止与 Spring 样式占位符冲突。如果你已经直接为 application.properties 启用了 Maven 筛选,那么你可能还希望更改默认筛选器令牌以使用其他分隔符。

[Note] Note

在这种特定的情况下,端口绑定工作在 PaaS 环境中,例如 Heroku 或 Cloud Foundry。在这两个平台中,PORT 环境变量是自动设置的,Spring 可以绑定到 Environment 属性的大写同义词。

75.5 使用 YAML 外部属性

YAML 是一个 JSON 的超集,因此是用于以分层格式存储外部属性的简便语法,如下面的示例所示:

spring:
	application:
		name: cruncher
	datasource:
		driverClassName: com.mysql.jdbc.Driver
		url: jdbc:mysql://localhost/test
server:
	port: 9000

创建一个名为 application.yml 的文件,并将其放入类路径的根目录中。然后将 snakeyaml 添加到依赖项(Maven 组合 org.yaml:snakeyaml,如果使用 spring-boot-starter,则已经包含了)。YAML 文件被解析为 Java Map<String,Object>(像 JSON 对象),Spring Boot 折叠该映射,使其为一个级别深且具有周期分隔的键,因为许多人习惯于使用 Java 中的 Properties 文件。

前面的示例 YAML 对应于以下 application.properties 文件:

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

有关 YAML 的更多信息请参阅 ‘Spring Boot 特性’ 部分中的 “小节 24.6, “使用 YAML 替代 Properties””。

75.6 设置激活的 Spring 配置文件

Spring Environment 有一个 API,但是通常要设置系统属性(spring.profiles.active)或 O S环境变量(SPRING_PROFILES_ACTIVE)。同样,你可以用 -D 参数启动应用程序(记住把它放在主类或 jar 归档之前),如下:

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

在 Spring Boot 中,还可以在 application.properties 中设置激活的配置文件,如下面的示例所示:

spring.profiles.active=production

这样设置的值由系统属性或环境变量设置代替,而不是由 SpringApplicationBuilder.profiles() 方法替换。因此,后一个 Java API 可以用来增加配置文件而不改变默认值。

更多详细信息请参阅 “Spring Boot 特性” 部分中的 “小节 25, 配置文件”。

75.7 根据环境改变配置

YAML 文件实际上是由 --- 行分隔的文档序列,每个文档分别被解析为折叠映射。

如果 YAML 文档包含 spring.profiles 键,那么将配置文件值(一个逗号分隔的配置文件列表)导入 Spring Environment.acceptsProfiles() 方法。如果这些配置文件中的任何一个是激活的,则该文档包含在最终合并中(否则,它不是),如下面的示例所示:

server:
	port: 9000
---

spring:
	profiles: development
server:
	port: 9001

---

spring:
	profiles: production
server:
	port: 0

在前面的示例中,默认端口为 9000。然而,如果称为 ‘development’ 的 Spring 配置文件是活动的,那么端口是 9001。如果 ‘production’ 是激活的,那么端口是 0。

[Note] Note

YAML 文档按照它们遇到的顺序合并。后面的值重写较早的值。

若要使用属性文件执行相同的操作,可以使用 application-${profile}.properties 属性指定特定于配置文件的值。

75.8 发现外部属性的内置选项

Spring Boot 在运行时将 application.properties(或 .yml 文件和其它位置)的外部属性绑定到应用程序中。由于可能来自类路径上的附加 jar 文件贡献,因此不存在(技术上不能)单个位置中所有支持的属性的穷举列表。

带有执行器特性的运行应用程序有一个 configprops 节点,该节点显示 @ConfigurationProperties 中可用的所有绑定和可绑定属性。

附录包含一个 application.properties 示例,其中列出了 Spring Boot 支持的最常见的属性。最终的列表来自于搜索源代码中的 @ConfigurationProperties 和 @Value 注解以及偶尔使用 Binder。有关加载属性的确切排序请参阅 "小节 24, 外部配置"。

76. 嵌入式 Web 服务器

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

76.1 使用其它 Web 服务器

许多 Spring Boot 启动器包含默认嵌入式容器。

  • 对于 servlet 堆栈应用程序,spring-boot-starter-web 通过包含 spring-boot-starter-tomcat 来包含 Tomcat,但是你可以使用 spring-boot-starter-jetty 或 spring-boot-starter-undertow 来代替。
  • 对于响应式堆栈应用程序,spring-boot-starter-webflux 通过包含 spring-boot-starter-reactor-netty 来包括响应式 Netty,但是你可以使用 spring-boot-starter-tomcat、spring-boot-starter-jetty 或 spring-boot-starter-undertow 来代替。

当切换到不同的 HTTP 服务器时,除了包含所需的依赖项之外,还需要排除默认依赖项。Spring Boot 为 HTTP 服务器提供独立的启动程序,以帮助尽可能容易地使该过程。

下面的 Maven 示例演示如何排除 Tomcat 并包含 Spring MVC 的 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>
		<!-- 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>
</dependency>
[Note] Note

Servlet API 的版本已经被重写,不像 Tomcat 9 和 Undertow 2.0,Jetty 9.4 不支持 Servlet 4.0。

下面的 Gradle 示例演示了如何排除 Netty 并包含 Spring WebFlux:

configurations {
	// exclude Reactor Netty
	compile.exclude module: 'spring-boot-starter-reactor-netty'
}

dependencies {
	compile 'org.springframework.boot:spring-boot-starter-webflux'
	// Use Undertow instead
	compile 'org.springframework.boot:spring-boot-starter-undertow'
	// ...
}
[Note] Note

spring-boot-starter-reactor-netty 需要使用 WebClient 类,所以即使需要包含不同的 HTTP 服务器,也可能需要保持对 Netty 的依赖性。

76.2 禁用 Web 服务器

如果你的类路径包含启动 Web 服务器所需的位,Spring Boot 将自动启动它。若要禁用此行为,请在 application.properties 中配置 WebApplicationType,如以下示例所示:

spring.main.web-application-type=none

76.3 更改 HTTP 端口

在独立应用程序中,主 HTTP 端口默认为 8080,但是可以使用 server.port 设置(例如,在 application.properties 中或作为系统属性)。由于环境值的松散绑定,你还可以使用 SERVER_PORT(例如,作为 OS 环境变量)。

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

更多详细信息,请参阅 ‘Spring Boot features’ 部分中的 “小节 27.4.4, “自定义嵌入式 Servlet 容器””,或 ServerProperties 源代码。

76.4 使用随机未使用的 HTTP 端口

要扫描一个自由端口(使用 OS 本地的来防止冲突)请使用 server.port=0。

76.5 运行时发现 HTTP 端口

可以通过日志输出或 ServletWebServerApplicationContext 的 WebServer 访问服务器正在运行的端口。获得并确保它已被初始化的最好方法是添加 ApplicationListener<ServletWebServerInitializedEvent> 类型的 @Bean, 并在发布容器时将容器从事件中拉出。

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

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

	@Autowired
	ServletWebServerApplicationContext server;

	@LocalServerPort
	int port;

	// ...

}
[Note] Note

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

76.6 启用 HTTP 响应压缩

HTTP 响应压缩由 Jetty、Tomcat 和 Undertow支持。它可以在 application.properties 中启用,如下:

server.compression.enabled=true

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

默认情况下,只有当内容类型是下列类型之一时,才对响应进行压缩:

  • text/html
  • text/xml
  • text/plain
  • text/css

可以通过设置 server.compression.mime-types 属性配置此行为。

76.7 配置 SSL

SSL 可以通过设置各种 server.ssl.* 属性(通常在 application.properties 或 application.yml 中).下面的示例展示在 application.properties 中设置 SSL 属性:

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

有关所有支持的属性的详细信息请参阅 Ssl。

使用例如前面示例的配置意味着应用程序不再支持端口 8080 上的纯HTTP连接器。Spring Boot 不支持通过 application.properties 同时配置 HTTP 连接器和 HTTPS 连接器。如果你想同时拥有这两个,则需要以编程方式配置它们中的一个。我们建议使用 application.properties 配置 HTTPS,因为 HTTP 连接器更容易通过编程进行配置。以 spring-boot-sample-tomcat-multi-connectors 示例项目为例。

76.8 配置 HTTP/2

你可以在 Spring Boot 应用程序中启用 HTTP/2 支持,使用 server.http2.enabled 配置属性。这种支持取决于所选的 Web 服务器和应用程序环境,因为 JDK8 不支持现成的协议。

[Note] Note

Spring Boot 不支持 h2c,HTTP/2 协议的明文版本。因此,必须首先配置 SSL。

76.8.1 Undertow 的 HTTP/2

在 Undertow 1.4.0+ 中,HTTP/2 被支持,而对 JDK8 没有任何附加要求。

76.8.2 Jetty 的 HTTP/2

至于 Jetty 9.4.8,HTTP/2 也通过 Conscrypt library 被支持。为了实现这种支持,你的应用程序需要另外两个依赖项:org.eclipse.jetty:jetty-alpn-conscrypt-server 和 org.eclipse.jetty.http2:http2-server。

76.8.3 Tomcat 的 HTTP/2

当使用 JDK 9 或更高版本时,Spring Boot 默认使用 Tomcat 9.0.x,它支持 HTTP/2 的开箱即用。或者,如果 libtcnative 库及其依赖项安装在主机操作系统上,则可以在 JDK 8 上使用 HTTP/2。

库文件夹必须在 JVM 库路径中可用,如果还没有的话。你可以使用JVM参数,如 -Djava.library.path=/usr/local/opt/tomcat-native/lib。更多关于此的信息在官方 Tomcat 文档的内容中。

在没有本地支持的 JDK 8 上启动 Tomcat 9.0.x,记录以下错误:

ERROR 8787 --- [           main] o.a.coyote.http11.Http11NioProtocol      : The upgrade handler [org.apache.coyote.http2.Http2Protocol] for [h2] only supports upgrade via ALPN but has been configured for the ["https-jsse-nio-8443"] connector that does not support ALPN.

此错误不是致命的,应用程序仍然从 HTTP/1.1 SSL 支持开始。

76.9 配置 Web 服务器

通常,你应该首先考虑使用许多可用配置键中的一个,并通过在 application.properties(或 application.yml,或环境等。请参阅 “小节 75.8, “发现外部属性的内置选项””)中添加新条目来定制 web 服务器。server.* 名称空间非常有用,它包含用于服务器特定特性的名称空间,如 server.tomcat.*、server.jetty.* 等。见 Appendix A, 常用的应用程序属性 的列表。

前面的部分已经涵盖了许多常见的用例,例如压缩、SSL 或 HTTP/2。但是,如果配置的键不存在于你的用例中,那么你应该查看 WebServerFactoryCustomizer。你可以声明这样的组件并访问与你的选择相关的服务器工厂:你应该选择为所选的服务器(Tomcat、Jetty、Reactor Netty、Undertow)和所选择的 Web 堆栈(Servlet 或 Reactive)变体。

下面的示例是针对 Tomcat 的 spring-boot-starter-web(Servlet 堆栈):

@Component
public class MyTomcatWebServerCustomizer
		implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

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

此外,Spring Boot 提供:

服务器 Servlet 堆栈 Reactive 堆栈

Tomcat

TomcatServletWebServerFactory

TomcatReactiveWebServerFactory

Jetty

JettyServletWebServerFactory

JettyReactiveWebServerFactory

Undertow

UndertowServletWebServerFactory

UndertowReactiveWebServerFactory

Reactor

N/A

NettyReactiveWebServerFactory

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

作为最后的手段,你也可以声明自己的 WebServerFactory 组件,它将覆盖 Spring Boot 供的组件。在这种情况下,你不能再依赖于 server 命名空间中的配置属性。

76.10 为应用程序中添加一个 Servlet, 过滤器或监听器

在 servlet 堆栈应用程序中,即使用 spring-boot-starter-web,有两种方法可以将 Servlet、Filter、ServletContextListener 和 Servlet API 支持的其它监听器添加到应用程序中:

  • 小节 76.10.1, “使用 Spring Bean 添加一个 Servlet, 过滤器或监听器”
  • 小节 76.10.2, “使用类路径扫描添加 Servlet, 过滤器或监听器”

76.10.1 使用 Spring Bean 添加一个 Servlet, 过滤器或监听器

要使用 Spring bean 添加 Servlet、Filter, 或 Servlet *Listener,必须为其提供一个 @Bean 定义。当你想注入配置或依赖项时,这样做非常有用。但是,你必须非常小心,它们不会引起许多其它需要的 bean 初始化,因为它们必须在应用程序生命周期的早期安装在容器中。(例如,让它们依赖于你的 DataSource 或 JPA 配置不是一个好主意。)你可以通过在首次使用时懒初始化 bean 来绕过这些限制,而不是在初始化时这样做。

在 Filters 和 Servlets 的情况下,还可以通过添加 FilterRegistrationBean 或 ServletRegistrationBean 来添加 映射和初始化参数,而不是添加基础组件。

[Note] Note

如果在过滤器注册上没有指定 dispatcherType,则使用 REQUEST。这与 servlet 规范的默认调度器类型相一致。

与其它 Spring bean 一样,你可以定义 Servlet 过滤器 bean 的顺序;请确保查看“名为 “注册 Servlets、过滤器和监听器 为 Spring Beans” 的部分”。

禁用注册 Servlet 或过滤器

如前面所述,任何 Servlet 或 Filter bean 都会自动注册到 servlet 容器中。要禁用特定 Filter 或 Servlet bean 的注册,请为其创建一个注册 bean,并将其标记为禁用,如下面的示例所示:

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

76.10.2 使用类路径扫描添加 Servlet, 过滤器或监听器

@WebServlet、@WebFilter 和 @WebListener 注解的类可以通过使用 @ServletComponentScan 注解 @Configuration 类并指定包含要注册的组件的包来自动注册到嵌入式 servlet 容器。默认情况下, @ServletComponentScan 从带有这些注解的类的包中扫描。

76.11 配置访问日志记录

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

例如,以下设置使用自定义模式在 Tomcat 上进行日志访问。

server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
[Note] Note

日志的默认位置是相对于 Tomcat 基本目录的 logs 目录。默认情况下,logs 目录是临时目录,因此你可能希望固定 Tomcat 的基础目录或使用日志的绝对路径。在前面的示例中,日志在 my-tomcat/logs 中相对于应用程序的工作目录是可用的。

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

server.undertow.accesslog.enabled=true
server.undertow.accesslog.pattern=%t %a "%r" %s (%D ms)

日志存储在 logs 目录中,与应用程序的工作目录相对应。可以通过设置 server.undertow.accesslog.directory 属性来自定义此位置。

最后,Jetty 的访问日志也可以被配置如下:

server.jetty.accesslog.enabled=true
server.jetty.accesslog.filename=/var/log/jetty-access.log

默认情况下,日志被重定向到 System.err。有关的详细信息请参阅 Jetty 文档。

76.12 在前端代理服务器后面运行

你的应用程序可能需要发送 302 重定向或将具有绝对链接的内容呈现返回自身。当运行在代理后面时,调用者希望有一个到代理的链接,而不是到托管应用程序的机器的物理地址。通常,这种情况是通过与代理的协议来处理的,该协议添加了报头以告诉后端如何构造到其自身的链接。

如果代理添加了传统的 X-Forwarded-For 和 X-Forwarded-Proto 报头(大多数代理服务器都这样做),则绝对链接应该正确呈现,只要在 application.properties 中将 server.use-forward-headers 设置为 true。

[Note] Note

如果你的应用程序运行在 Cloud Foundry 或 Heroku 中,server.use-forward-headers 属性默认为 true。在所有其它实例中,它默认为 false。

76.12.1 自定义 Tomcat 的代理配置

如果使用 Tomcat,还可以配置用于携带转发信息的报头的名称,如下面的示例所示:

server.tomcat.remote-ip-header=x-your-remote-ip-header
server.tomcat.protocol-header=x-your-protocol-header

Tomcat 还配置有与要信任的内部代理相匹配的默认正则表达式。默认情况下,10/8、192.168/16、169.254/16 和 127/8 中的 IP 地址是可信的。你可以通过向 application.properties 添加条目来定制阀门的配置,如下面的示例所示:

server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
[Note] Note

只有当您使用属性文件进行配置时,才需要双反斜杠。如果使用 YAML,则单个反斜杠就足够了,与前面示例中所示的值等效的值将是 192\.168\.\d{1,3}\.\d{1,3}。

[Note] Note

你可以通过将 internal-proxies 设置为空(但在生产中不这样做)来信任所有代理。

通过关闭自动配置(为此,设置 server.use-forward-headers=false)并在 TomcatServletWebServerFactory bean 中添加新的阀实例,可以完全控制 Tomcat 的 RemoteIpValve 的配置。

76.13 使用 Tomcat 启用多个连接器

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

@Bean
public ServletWebServerFactory servletContainer() {
	TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
	tomcat.addAdditionalTomcatConnectors(createSslConnector());
	return tomcat;
}

private Connector createSslConnector() {
	Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
	Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
	try {
		File keystore = new ClassPathResource("keystore").getFile();
		File truststore = new ClassPathResource("keystore").getFile();
		connector.setScheme("https");
		connector.setSecure(true);
		connector.setPort(8443);
		protocol.setSSLEnabled(true);
		protocol.setKeystoreFile(keystore.getAbsolutePath());
		protocol.setKeystorePass("changeit");
		protocol.setTruststoreFile(truststore.getAbsolutePath());
		protocol.setTruststorePass("changeit");
		protocol.setKeyAlias("apitester");
		return connector;
	}
	catch (IOException ex) {
		throw new IllegalStateException("can't access keystore: [" + "keystore"
				+ "] or truststore: [" + "keystore" + "]", ex);
	}
}

76.14 使用 Tomcat 的 LegacyCookieProcessor

默认情况下,Spring Boot 使用的嵌入式 Tomcat 不支持 Cookie 格式的 "Version 0",因此你可能会看到以下错误:

java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value

如果可能的话,你应该考虑更新代码,只存储符合以后 Cookie 规范的值。但是,如果不能更改 Cookie 的写入方式,你可以使用 LegacyCookieProcessor 配置 Tomcat。若要切换到 LegacyCookieProcessor,请使用 WebServerFactoryCustomizer bean,并添加 TomcatContextCustomizer,如下面的示例所示:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
	return (factory) -> factory.addContextCustomizers(
			(context) -> context.setCookieProcessor(new LegacyCookieProcessor()));
}

76.15 使用 Undertow 启用多监听器

向 UndertowServletWebServerFactory 添加 UndertowBuilderCustomizer ,并向 Builder 添加监听器,如下面的示例所示:

@Bean
public UndertowServletWebServerFactory servletWebServerFactory() {
	UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
	factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {

		@Override
		public void customize(Builder builder) {
			builder.addHttpListener(8080, "0.0.0.0");
		}

	});
	return factory;
}

76.16 使用 @ServerEndpoint 创建 WebSocket 节点

如果要在使用嵌入式容器的 Spring Boot 应用程序中使用 @ServerEndpoint ,则必须声明单个 ServerEndpointExporter @Bean,如下面的示例所示:

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

前面示例中所示的 bean 将任何 @ServerEndpoint 注解 bean 与基础 WebSocket 容器注册。当部署到独立的 servlet 容器时,此角色由 servlet 容器初始化器执行,并且不需要 ServerEndpointExporter bean。