Spring Boot Application
本节包含与 Spring Boot 应用程序直接相关的主题。
创建你自己的 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 实现类的构造函数参数。
排查自动配置问题
Spring Boot 的自动配置会尽最大努力“做正确的事”,但有时仍会失败,而且很难弄清楚原因。
在任何 Spring Boot ApplicationContext 中,都提供了一个非常有用的 ConditionEvaluationReport。如果你启用 DEBUG 日志输出,就可以看到它。如果你使用了 spring-boot-actuator(参见 Actuator 章节),还会有一个 conditions 端点,以 JSON 格式呈现该报告。使用该端点来调试应用程序,并查看 Spring Boot 在运行时添加了哪些功能(以及哪些功能未被添加)。
通过查看源代码和 API 文档可以解答更多问题。在阅读代码时,请记住以下经验法则:
-
查找名为
*AutoConfiguration的类并阅读其源码。特别注意其中的@Conditional*注解,以了解它们启用了哪些功能以及在什么条件下启用。在命令行中添加--debug参数,或设置系统属性-Ddebug,即可在控制台输出应用程序中所有自动配置决策的日志。在启用了 Actuator 的运行应用中,也可以通过conditions端点(/actuator/conditions或其 JMX 等效接口)查看相同的信息。 -
查找使用了 @ConfigurationProperties 注解的类(例如 ServerProperties),从中了解可用的外部配置选项。@ConfigurationProperties 注解有一个
name属性,用作外部属性的前缀。因此,ServerProperties 的prefix="server",其配置属性包括server.port、server.address等。在启用了 Actuator 的运行应用中,可以查看configprops端点获取这些信息。 -
查找对 Binder 类中
bind方法的调用,这种方式可以以宽松的方式显式地从 Environment 中提取配置值。通常会配合一个前缀使用。 -
查找直接绑定到 Environment 的 @Value 注解。
-
查找 @ConditionalOnExpression 注解,它们会根据 SpEL 表达式动态开启或关闭某些功能,这些表达式通常会解析来自 Environment 的占位符。
在启动前自定义 Environment 或 ApplicationContext
一个 SpringApplication 包含用于对上下文或环境进行自定义的 ApplicationListener 和 ApplicationContextInitializer 实现。Spring Boot 会从 META-INF/spring.factories 中加载多个此类自定义配置以供内部使用。注册额外自定义配置的方式有多种:
-
以编程方式,针对每个应用,在运行 SpringApplication 之前调用其
addListeners和addInitializers方法。 -
以声明方式,针对所有应用,通过添加
META-INF/spring.factories文件,并打包一个所有应用都作为库使用的 jar 文件。
SpringApplication 会向监听器发送一些特殊的 ApplicationEvents(有些甚至在上下文创建之前就发送了),然后还会为 ApplicationContext 发布的事件注册这些监听器。完整列表请参见 “Spring Boot Features” 章节中的 Application Events and Listeners。
也可以通过使用 EnvironmentPostProcessor 在应用上下文刷新之前自定义 Environment。每个实现都应在 META-INF/spring.factories 中注册,如下例所示:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor
该实现可以加载任意文件并将它们添加到 Environment 中。例如,以下示例从 classpath 加载一个 YAML 配置文件:
- Java
- Kotlin
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(), () -> "'path' [%s] must exist".formatted(path));
try {
return this.loader.load("custom-resource", path).get(0);
}
catch (IOException ex) {
throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
}
}
}
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 默认加载的所有常规属性源。因此,可以从环境中获取文件的位置。上述示例将 custom-resource 属性源添加到列表末尾,以便在其他常规位置定义的键具有更高优先级。自定义实现可以定义不同的顺序。
虽然在你的 @SpringBootApplication 上使用 @PropertySource 看起来是一种方便的方式来加载自定义资源到 Environment 中,但我们不推荐这样做。这类属性源直到应用上下文开始刷新时才会被添加到 Environment 中。这已经太晚了,无法配置某些属性(例如 logging.* 和 spring.main.*),因为这些属性在刷新开始之前就已经被读取了。
构建 ApplicationContext 层次结构(添加父上下文或根上下文)
你可以使用 SpringApplicationBuilder 类来创建父子级的 ApplicationContext 层次结构。更多信息请参阅“Spring Boot 功能”部分中的 Fluent Builder API。
创建一个非 Web 应用程序
并非所有的 Spring 应用都必须是 Web 应用(或 Web 服务)。如果你希望在 main 方法中执行某些代码,同时又想引导启动一个 Spring 应用以设置所需的基础设施,你可以使用 Spring Boot 的 SpringApplication 功能。SpringApplication 会根据它是否认为需要 Web 应用,来改变其 ApplicationContext 的类型。
你可以做的第一件事是帮助它判断:将服务器相关的依赖(例如 Servlet API)从 classpath 中移除。如果你无法做到这一点(例如,你从同一套代码库中运行两个应用),那么你可以显式地在你的 SpringApplication 实例上调用 setWebApplicationType(WebApplicationType.NONE),或者通过 Java API 或外部属性设置 applicationContextClass 属性。
你希望作为业务逻辑运行的应用代码,可以实现为 CommandLineRunner,并以 @Bean 的形式注入到上下文中。