17. “如何做”指南
这部分提供一些常用的“我做这些…如何做”的问题的答案,当使用Spring Boot时,这些问题经常发生。它的覆盖范围并不全面,但确实涵盖了相当多的内容。
如果你有特别的问题,我们没有涵盖到,你可能想要检查stackoverflow.com来查看,如果某人已经提供了一个答案。这个也是一个非常好的地方回答新的问题(请使用spring-boot
标签)。
17.1. Spring Boot应用程序
这部分包含直接相关Spring Boot应用程序的主题。
17.1.1. 创建你自己的FailureAnalyzer
FailureAnalyzer
是一个很好的方式在启动时来拦截一个异常并将它转为一个人为可读取的信息。封装进一个FailureAnalysis
。Spring Boot提供这样的一个用于上下文相关异常,JSR-303校验甚至更多的解析器,。你也可以创建自己的。AbstractFailureAnalyzer
是FailureAnalyzer
一个非常方便的扩展,它检查要处理的异常中是否存在特定的异常类型。你可以从这个进行扩展以便你的实现获得一个机会来处理异常,只有当它正真存在的时候。如果处于某种原因,你不能处理这个异常,返回null
,让另一个实现有机会来处理异常。
FailureAnalyzer
实现必须在META-INF/spring.factories
中注册。以下示例注册ProjectConstraintViolationFailureAnalyzer
:
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.ProjectConstraintViolationFailureAnalyzer
如果你需要访问
BeanFactory
或者Environment
,你的FailureAnalyzer
可以依次实现BeanFactoryAware
或者EnvironmentAware
。
17.1.2. 排除自动配置问题
Spring Boot自动配置尽最大的努力“做正确的事情”,但是有时候失败,他告诉为什么是非常困难的。
在任何Spring Boot ApplicationContext
中存在一个非常有用的ConditionEvaluationReport
。如果你启用BEBUG
日志输出,你可以看到它。如果你使用spring-boot-actuator
(请查看Actuator章节),也有一个conditions
端点以JSON的格式呈现报告。使用这个端点来调试应用程序并查看Spring Boot在运行时已经添加了哪些特性(和哪个没有被添加)。
通过查看源码和Javadoc,更多的问题被解答。当阅读代码时,记住以下经验规则:
- 查找成为
*AutoConfiguration
的类和阅读他们源码。特别注意@Conditional*
注解找出他们何时启用了哪些特性。添加--debug
到命令行或者系统属性-Ddebug
来获取在所有应用程序中自动配置决策的控制台的日志。在一个启用actuator正在运行的应用程序中,查看conditions
端点(/actuator/conditions
或者等价的JMX)了解相同的信息。 - 查找
@ConfigurationProperties
类(例如ServerProperties
)并读来自可用的外部配置选项。@ConfigurationProperties
注解有name
属性,它作为外部属性前缀。因此,ServerProperties
有prefix="server"
并他的配置属性是server.prot
,server.address
和其他。在启用actuator的运行中的应用程序,可以在configprops
端点查看. - 查找在
Binder
上的bind
方法的用法,以轻松的方式显式地从Environment
中提取配置值。它通常带有前缀。 - 查找
@Value
注解,直接和Environment
绑定 - 查找
@ConditionalOnExpression
注解,它在响应SpEL表达式时,打开和关闭特性,通常使用从Environment
解析的占位符进行计算。
17.1.3. 在启动之前定制环境或者ApplicationContext
SpringApplication
有ApplicationListeners
和ApplicationContextInitializers
,他们被用来上下文和环境的定制。Spring Boot从META-INF/spring.factories
加载许多这样的定制以内部使用。有超过一种方式来注册额外的定制:
- 程序化的,每个应用程序,在你调用它之前,在
SpringApplication
中通过调用addListeners
和addInitializers
方法。 - 声明式的,每个应用程序,通过设置
context.initializer.class
或者context.listener.class
属性。 - 声明式的,对于所有应用程序,通过添加
META-INF/spring.factories
并打包一个jar文件,所有的应用程序将它作为一个类库使用。
SpringApplication
发送一些特定的ApplicationEvents
到监听器(一些甚至在上下文创建之前),然后也为通过ApplicationContext
发布的事件注册监听器。请查看在Spring Boot特性章节中的“应用程序事件和监听器”了解完整列表。
它也可以在应用程序上下文刷新之前使用EnvironmentPostProcessor
来定制Environment
。每一个实现应该在META-INF/spring.factories
注册,如下示例所示:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor
这个实现可以加载任意的文件并将他们添加到Environment
.例如,下面的示例加载来自类路径的YAML配置文件:
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);
}
}
}
Environment
已经使用所有的默认情况下Spring Boot加载的通常的属性源做好准备。因此可以从环境中获取文件的位置。前面的示例在列表的最后添加custom-resource
属性,以便在任何通常的其他位置定义的key有优先级。自定义的实现可以定义另一个顺序。
当在你的
@SpringBootApplication
上使用@PropertySource
可能看起来成为一个方便的方式来加载在Environment
中的一个自定义资源,我们不建议这样做。这些属性资源是不会被添加到Environment
,直到应用程序上下文已经刷新。对于配置某个属性来说,这样太晚了,例如logging.*
和spring.main.*
,他们在刷新开始之前被读取。
17.1.4. 构建一个ApplicationContext层次(添加一个Parent或者Root上下文)
你可以使用ApplicationBuilder
类来创建parent/child ApplicationContext
层次。请查看在Spring Boot特性章节“流式Builder API”了解更多信息。
17.1.5. 创建一个非web应用程序
并不是所有的Spring application必须是web应用程序(或者web服务)。如果你想在main
方法执行一些代码但是也引导一个Spring应用程序建立基础设施来使用,你可以使用Spring Boot的SpringApplication
特性。SpringApplication
改变它的ApplicationContext
类,取决于它是否认为它需要一个web应用程序。你可以做的第一件事来帮助它是将与服务器相关的依赖项(例如servlet API)从类路径中移除。如果你不能这么做(例如,基于相同的代码,你运行两个应用程序),然后你可以显示地在你的SpringApplication
实例上调用setWebApplicationType(WebApplicationType.NONE)
或者设置applicationContextClass
属性(通过Java API或者使用外部属性)。你想要作为你的业务逻辑执行的应用程序代码可以作为CommandLineRunner
实现并作为@Bean
定义放到上下文中。
17.2. 属性和配置
这部分包含的主题有:设置和读取属性,配置设置和他们与Spring Boot应用程序的相互作用。
17.2.1. 在构建时自动化扩展属性
你可以通过使用现有的构建配置自动扩展在你的项目构建配置中的一些属性,而不是硬编码他们。可以同时在Maven和Gradle中实现。
使用Maven自动化属性扩展
你可以通过使用资源过滤自动化扩展来自Maven项目的属性。如果你使用spring-boot-starter-parent
,你可以使用@...@
占位符依赖你的Maven项目属性,如下示例所示:
app:
encoding: "@project.build.sourceEncoding@"
java:
version: "@java.version@"
只有生产配置是这样过滤的(换句话说,没有对
src/test/resources
应用任何过滤)。
如果你启用
addResources
标识,spring-boot:run
目标可以直接添加src/main/resources
到类路径(出于热部署目的)。这样绕过了资源过滤和这个特性,你可以使用eec: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>
如果在配置中,你使用标准的Spring占位符(例如
${placeholder}
),useDefaultDelimiters
属性是重要的。如果该属性没有设置为false
,通过构建可能扩展这些属性。
使用Gradle自动化属性扩展
你可以通过配置Java 插件的processResources
任务自动化扩展来自Gradle项目的属性,如下示例所示:
tasks.named('processResources') {
expand(project.properties)
}
你可以通过使用占位符引用你的Gradle项目的属性,如下示例所示:
app:
name: "${name}"
description: "${description}"
Gradle的
expand
方法使用Groovy的SimpleTemplateEngine
,它转换${...}
符号。${...}
格式与Spring自己的属性占位符机制冲突。要与自动化扩展一起使用Spring属性占位符,转译Spring 属性占位符,如下\${..}
。
17.2.2. 外部化SpringApplication配置
SpringApplication
有bean属性设置器,所以你可以在创建应用程序时使用它的Java API来修改他的行为。或者,你可以通过设置在spring.main.*
属性外部化配置。例如,在application.properties
,你可能有以下设置:
spring:
main:
web-application-type: "none"
banner-mode: "off"
然后Spring Boot banner在启动时不会打印,并且应用程序不会启动内嵌的web应用服务。
在外部配置中定义的属性覆盖并替换使用Java API的指定的值,主要的源除外。主要的源是提供给SpringApplication
构造器那些:
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);
}
}
或者提供给SpringApplicationBuilder
的sources(...)
方法:
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);
}
}
上面所提供的示例,如果我们有以下配置:
spring:
main:
sources: "com.example.MyDatabaseConfig,com.example.MyJmsConfig"
banner-mode: "console"
真正的应用程序将展示banner(因为配置重写)并为ApplicationContext
使用了三个源。应用程序源是:
-
MyApplication
(来自代码) -
MyDatabaseConfig
(来自外部配置) -
MyJmsConfig
(来自外部配置)
17.2.3. 更改应用程序外部属性的位置
默认情况下,来自不同源的属性以被定义的顺序被添加到Spring Environment
(请查看在‘Spring Boot特性的章节’的“外部化配置”了解确切的顺序)。
你也可以提供以下系统属性(或者环境变量)来改变此行为:
-
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看了解更多详情。
17.2.4. 使用“短”命令行参数
一些人喜欢在命令行使用(例如)--port=9000
代替--server.port=9000
来设置配置属性。你可以通过使用在application.properties
中的占位符启用这个行为,如下示例所示:
server:
port: "${port:8080}"
如果你继承
spring-boot-starter-parent
POM,maven-resources-plugins
的默认的过滤器token已经从${*}
更改为@
(也就是,@maven.token
代替${maven.token}
)来避免与Spring风格占位符冲突。如果你已经为application.properties
直接启用Maven过滤器,你可能也想要改变默认的过滤器token以使用其他的界定符。
在特定的情况下,端口绑定工作在PaaS环境中,如Heroku或者Foundry。在这两个平台中,
PORT
环境变量被自动设置并且Spring可以绑定到Environment
属性的大写同义词。
17.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.yml
的文件和将它放入到根类路径。然后添加snakeyaml
到你的依赖(Maven 坐标org.yaml:snakeyaml
,如果你使用了spring-boot-starter
则已经包含)。YAML文件被转换为Java Map<String,Object>
(像JSON对象),并且Spring Boot平铺这个map以便他是一级深度并有分隔周期key,因为许多人在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特性章节中的“使用YAML工作”了解更多关于YAML信息。
17.2.6. 设置活跃的Spring配置文件
Spring Environment
有用于这个的API,但是你通常需要设置一个系统属性(spring.profiles.active
)或者一个OS环境变量(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特性章节中“配置文件”了解更多信息。
17.2.7. 设置默认的配置文件名称
默认的配置文件是一个如果没有配置文件是活跃被启用的配置文件。默认情况下,默认的配置文件的名称是default
,但是可以通过系统属性(spring.profiles.default
)或者OS环境变量(SPRING_PROFILES_DEFAULT
)更改。
在Spring Boot中,你也可以在application.properties
中设置默认的配置文件名称,如下示例所示:
spring:
profiles:
default: "dev"
请查看在Spring Boot特性章节中“配置文件”了解更多信息。
17.2.8. 依据环境修改配置
Spring Boot 支持多文档YAML和属性文件(请查看使用多文档文件工作了解细节),这些文件基于活跃的配置文件有条件的被激活。
如果一个文档包括spring.config.activate.on-profile
键,然后配置文件的值(逗号分隔的配置文件列表或者配置文件表达式)被输入到Spring Environment.acceptProfiles()
方法。如果配置文件表达式匹配,则该文档将包含在最终合并中(否则不包含),如下示例所示:
server:
port: 9000
---
spring:
config:
activate:
on-profile: "development"
server:
port: 9001
---
spring:
config:
activate:
on-profile: "production"
server:
port: 0
在上面的示例中,默认的端口是9000.然而,如果称为 'development’的Spring配置文件是活跃的,然后这个端口是9001.如果’production’是活跃的,然后端口是0.
文档以在文档中遇到的顺序的合并。最后的值覆盖前面的值。
17.2.9. 发现内建的选项用于外部属性
Spring Boot在运行时绑定来自application.properties
(或者.yml
文件和其他位置)的外部属性到应用程序。在单个位置没有(技术上也不可能)一个所有支持的属性详细的列表,因为贡献可以来自类路径中的额外jar文件。
一个使用Actuator特性的正在运行的应用程序有一个configprops
端点,它通过@ConfigurationProperties
展示了所有可用的绑定和可绑定的属性。
附录包括一个application.properties
示例,使用了大量的常用的Spring Boot支持的属性列表。最终的列表来自于搜索@ConfigurationProperties
和@Value
注解的源码以及偶尔使用的Binder
。要了解更多关于加载属性的完整顺序,请查看“外部化配置”。
17.3. 内嵌web服务器
每一个Spring Boot应用程序包含一个内嵌的web服务器。这个特性导致许多如果做问题,包括如果修改内嵌服务器和如何配置内嵌服务器。这部分将回答这些问题。
17.3.1. 使用另一个web服务器
许多Spring Boot启动器包含默认的内嵌容器:
- 对于servlet 栈应用程序,
spring-boot-starter-web
通过包括spring-boot-starter-tomcat
包含Tomcat,但是你可以使用spring-boot-starter-jetty
或者spring-boot-starter-undertomw
代替。 - 对于响应式栈应用程序,
spring-boot-starter-webflux
通过包含spring-boot-starter-reactor-netty
包含Reactor Netty,但是你可以使用spring-boot-starter-tomcat
,spring-boot-starter-jetty
,或者spring-boot-starter-undertomw
代替。
当转换到一个不同的HTTP服务器,你需要将默认的依赖项转换为你所需要的依赖项。为了帮助这个过程,Spring Boot为每一个支持的HTTP服务器提供一个单独的启动器。
以下Maven示例展示如何排除Tomcat和包含Jetty用于Spring MVC:
<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>
servlet API的版本已经被重写,因为与Tomcat 9和Undertow2不同,Jetty9.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启动器外,还需要排除两个特定的Jetty9依赖。
以下Gradle示例配置了必须的依赖和模块替换来使用Undertow代替Reator Netty用于Spring WebFlux:
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")
}
}
}
使用
WebClient
类需要spring-boot-starter-reactor-netty
,所以即使你需要包含一个不同的HTTP服务器,你可能需要保持对Netty的依赖。
17.3.2. 禁用web服务器
如果你的类路径包含必须的片段来启动一个web服务器,Spring Boot将自动启动它。要禁用这种行为,在application.properties
中配置WebApplicationType
,如下示例所示:
spring:
main:
web-application-type: "none"
17.3.3. 修改HTTP端口
在一个单独的应用程序中,主要的HTTP端口默认是8080
,但是可以使用server.port
设置(例如,在application.properties
或者系统属性)。感谢宽松的Environment
值绑定,你也可以使用SERVER_PORT
(例如,作为一个系统变量)。
要完全关闭HTTP端点但仍创建一个WebApplicationContext
,使用servet.prot=-1
(这样做有时对于测试是有用的)。
要了解更多详情,请查看在Spring Boot特性章节中的“定制内嵌servelt容器”,或者ServerProperties
源码。
17.3.4.使用一个随机的未赋值的HTTP端口
要对空闲端口(使用OS native防止冲突)扫描,使用servet.port=0
17.3.5. 在运行时发现HTTP端口
你可以从日志输出或者通过它的WebServer
从WebServerApplicationContext
访问正在运行服务器的端口。获取它并确保它被初始化的最好的方法是添加一个类型为ApplicationListener<WebServerInitializedEvent>
的@Bean
并在发布时将容器从事件中拉出。
使用@SpringBootTest(webEnvironment=WebEnvironment.RANDOW_PORT)
的测试也可以通过使用@LocalServerPort
注解注入真实的端口到一个字段:
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)
public class MyWebIntegrationTests {
@LocalServerPort
int port;
// ...
}
@LocalServerPort
是一个元注解用于@Value("${local.server.prot}")
。不要尝试注入这个端口到常规的应用程序。正如我们刚刚看到的,只有在容器已经被初始化之后才设置这个值。与测试相反,应用程序代码回调会提前处理(在这个值真正可用之前)。
17.3.6. 启用HTTP响应压缩
Jetty,Tomcat,Reactor Netty和Undertow支持HTTP响应压缩。他可以在applicaton.properties
被启用,如下:
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
属性配置这个行为。
17.3.7. 配置SSL
SSL可以通过设置多个servler.ssl.*
属性声明式配置,典型方式在application.properties
或者application.yml
.以下示例展示使用Java
- 文章目录
- 繁