|
此版本仍在开发中,目前尚不被视为稳定版本。如需最新稳定版本,请使用 Spring Cloud Commons 5.0.1! |
Spring Cloud 上下文:应用上下文服务
Spring Boot 对如何使用 Spring 构建应用程序持有观点明确的见解。</p><p>例如,它为常见的配置文件设定了约定的位置,并提供了用于常见管理和监控任务的端点。</p><p>Spring Cloud 在此基础上进一步构建,并添加了一些许多系统组件可能经常使用或偶尔需要的功能。
Bootstrap 应用上下文
Spring Cloud 应用通过创建一个“引导”上下文来运行,该上下文是主应用程序的父上下文。
此上下文负责从外部源加载配置属性,并解密本地外部配置文件中的属性。
这两个上下文共享一个 Environment,它为任何 Spring 应用提供外部属性的来源。
默认情况下,引导阶段属性(不是 bootstrap.properties,而是指在引导阶段加载的属性)以高优先级添加,因此无法被本地配置覆盖。
引导上下文采用了一种不同的约定来定位外部配置,与主应用上下文不同。
您不再使用 application.yml(或 .properties),而可以使用 bootstrap.yml,从而将引导上下文和主上下文的外部配置清晰地分离开来。
以下列表展示了一个示例:
spring:
application:
name: foo
cloud:
config:
uri: ${SPRING_CONFIG_URI:http://localhost:8888}
如果您的应用程序需要从服务器获取任何特定于应用的配置,建议在 spring.application.name(位于 bootstrap.yml 或 application.yml 中)进行设置。要使属性 spring.application.name 用作应用的上下文 ID,您必须在 bootstrap.[properties | yml] 中进行设置。
如果您想要检索特定的配置文件设置,还应在 bootstrap.[properties | yml] 中设置 spring.profiles.active。
您可以通过将 spring.cloud.bootstrap.enabled=false(例如在系统属性中)设置来完全禁用引导过程。
应用上下文层次结构
如果您从 SpringApplication 或 SpringApplicationBuilder 构建应用上下文,则会将引导上下文作为该上下文的父级添加。
这是 Spring 的一项特性:子上下文会从其父上下文中继承属性源和配置文件,因此“主”应用上下文相比不使用 Spring Cloud Config 构建相同上下文时包含额外的属性源。
额外的属性源包括:
-
“bootstrap”:如果在引导上下文中找到任何
PropertySourceLocators,并且它们具有非空属性,则会以高优先级出现一个可选的CompositePropertySource。例如,来自 Spring Cloud Config Server 的属性即为此类情况。有关如何自定义此属性源内容,请参阅“自定义引导属性源”。
在 Spring Cloud 2022.0.3 之前,PropertySourceLocators(包括 Spring Cloud Config 的相关版本)是在主应用上下文中运行的,而非在引导上下文中运行。您可以通过在 bootstrap.[properties | yaml] 中设置 spring.cloud.config.initialize-on-context-refresh=true 来强制使 PropertySourceLocators 在引导上下文中运行。 |
-
“applicationConfig: [classpath:bootstrap.yml]”(以及相关文件,如果启用了 Spring 配置文件):如果您有
bootstrap.yml(或.properties),这些属性将用于配置引导上下文。然后,当其父上下文被设置时,它们会被添加到子上下文中。它们的优先级低于application.yml(或.properties)以及任何其他在创建 Spring Boot 应用程序过程中作为正常流程的一部分被添加到子上下文中的属性源。有关如何自定义这些属性源内容,请参阅“更改引导属性的位置”。
由于属性源的排序规则,“bootstrap”条目具有优先级。然而,请注意,这些条目不包含来自 bootstrap.yml 的任何数据,后者优先级极低,但可用于设置默认值。
您可以通过设置任意 ApplicationContext 的父级上下文来扩展上下文层次结构——例如,通过其自身接口或使用 SpringApplicationBuilder 的便捷方法(parent()、child() 和 sibling())。
请注意,SpringApplicationBuilder 允许您在整个层次结构中共享一个 Environment,但这并非默认行为。因此,子上下文(尤其是)无需具有相同的配置文件或属性源,即使它们可能与父上下文共享某些共同值。
更改 Bootstrap 属性的位置
位置 bootstrap.yml(或 .properties)可以通过设置 spring.cloud.bootstrap.name(默认值:bootstrap)、spring.cloud.bootstrap.location(默认值:空字符串)或 spring.cloud.bootstrap.additional-location(默认值:空字符串)来指定——例如,在系统属性中。
这些属性的行为类似于同名的 spring.config.* 变体。
使用 spring.cloud.bootstrap.location 时,默认位置会被替换,仅使用指定的位置。
若要将位置添加到默认位置列表中,可使用 spring.cloud.bootstrap.additional-location。
实际上,它们用于通过在引导 ApplicationContext 中设置这些属性来配置启动过程,具体是在其 Environment 中进行设置。
如果存在活动的配置文件(来自 spring.profiles.active 或通过您正在构建的上下文中的 Environment API),则该配置文件中的属性也会被加载,与常规 Spring Boot 应用相同——例如,从 bootstrap-development.properties 加载 development 配置文件的属性。
覆盖远程属性的值
由引导上下文为您的应用程序添加的属性源通常来自“远程”位置(例如,来自 Spring Cloud Config Server)。
默认情况下,它们无法在本地被覆盖。
如果您希望应用程序能够使用自己的系统属性或配置文件来覆盖远程属性,则远程属性源必须通过设置 spring.cloud.config.allowOverride=true 来授予此权限(在本地设置此值无效)。
一旦该标志被设置,两个更细粒度的设置将控制远程属性相对于系统属性和应用程序本地配置的位置:
-
spring.cloud.config.overrideNone=true: 重写来自任何本地属性源的内容。 -
spring.cloud.config.overrideSystemProperties=false: 仅系统属性、命令行参数和环境变量(但不包括本地配置文件)应覆盖远程设置。
自定义 Bootstrap 配置
可以通过在名为 org.springframework.cloud.bootstrap.BootstrapConfiguration 的键下添加条目来设置引导上下文,以实现您希望执行的任何操作。此内容包含一个由逗号分隔的 Spring @Configuration 类列表,这些类用于创建上下文。您可以在此处创建任何希望供主应用上下文自动装配使用的 Bean。对于类型为 ApplicationContextInitializer 的 @Beans 存在一个特殊契约。如果您希望控制启动顺序,可以使用 @Order 注解标记类(默认顺序为 last)。
在添加自定义 BootstrapConfiguration 时,请注意,您添加的类不应意外地被 @ComponentScanned 加入到您的“主”应用上下文中,因为它们可能并不需要。为启动配置类使用独立的包名,并确保该名称未被您的 @ComponentScan 或 @SpringBootApplication 注解配置类所覆盖。 |
引导过程以将初始化器注入到主 SpringApplication 实例中结束(这是标准的 Spring Boot 启动流程,无论它作为独立应用运行还是部署在应用服务器中)。
首先,根据在 spring.factories 中找到的类创建一个引导上下文。
然后,在启动主 SpringApplication 之前,将所有类型为 ApplicationContextInitializer 的 @Beans 添加到其中。
自定义 Bootstrap 属性源
引导过程添加的外部配置默认属性源是 Spring Cloud Config Server,但您可以通过向引导上下文(通过 spring.factories)中添加类型为 PropertySourceLocator 的 Bean 来添加其他属性源。例如,您可以从不同的服务器或数据库中插入额外的属性。
例如,考虑以下自定义定位器:
@Configuration
public class CustomPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
return new MapPropertySource("customProperty",
Collections.<String, Object>singletonMap("property.from.sample.custom.source", "worked as intended"));
}
}
传入的 Environment 是即将创建的 ApplicationContext 对应的属性源——换句话说,即我们为其提供额外属性源的那个属性源。它已包含 Spring Boot 默认提供的标准属性源,因此您可以利用这些属性源来定位针对该 Environment 的特定属性源(例如,通过键值 spring.application.name 进行定位,这正是默认 Spring Cloud Config Server 属性源定位器所采用的方式)。
如果您将此类打包到 JAR 文件中,然后添加一个包含以下设置的 META-INF/spring.factories,那么 customProperty PropertySource 将出现在任何将该 JAR 添加到其类路径中的应用程序中:
org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator
截至 Spring Cloud 2022.0.3 版本,Spring Cloud 现在将两次调用 PropertySourceLocators。第一次获取将检索任何无配置文件的属性源。这些属性源将有机会使用 spring.profiles.active 激活配置文件。主应用上下文启动后,PropertySourceLocators 将被再次调用,此时会包含任何已激活的配置文件,从而允许 PropertySourceLocators 查找任何带有配置文件的额外 PropertySources。
日志配置
如果您使用 Spring Boot 配置日志设置,应将此配置放置在 bootstrap.[yml | properties] 中,以便它适用于所有事件。
| 为了使 Spring Cloud 能够正确初始化日志配置,您不能使用自定义前缀。 例如,在初始化日志系统时,Spring Cloud 不会识别使用 |
环境变更
应用程序监听 EnvironmentChangeEvent,并以几种标准方式对变化作出响应(可按常规方式添加额外的 ApplicationListeners 作为 @Beans)。
当观察到 EnvironmentChangeEvent 时,它会有一组已发生变化的键值列表,应用程序将利用这些键值进行以下操作:
-
重新绑定上下文中任何
@ConfigurationProperties个 Bean。 -
为
logging.level.*中的任意属性设置日志记录器级别。
请注意,Spring Cloud Config 客户端默认情况下不会轮询 Environment 中的更改。
通常,我们不建议采用此方法来检测变更(尽管您可以通过设置 @Scheduled 注解来实现)。
如果您有一个扩展部署的客户端应用程序,建议将 EnvironmentChangeEvent 广播至所有实例,而不是让它们轮询变更(例如,可通过使用 Spring Cloud Bus 实现)。
该 EnvironmentChangeEvent 涵盖了大量刷新用例,只要您确实能够对 Environment 进行更改并发布事件即可。注意,这些 API 是公开的,并且属于核心 Spring 的一部分。您可以通过访问 /configprops 端点(Spring Boot Actuator 的一个标准功能)来验证这些更改是否已绑定到 @ConfigurationProperties 类型的 Bean 上。例如,一个 DataSource 可以在运行时更改其 maxPoolSize(Spring Boot 创建的默认 DataSource 是一个 @ConfigurationProperties 类型的 Bean),并动态扩展其容量。重新绑定 @ConfigurationProperties 并不适用于另一大类用例,即当您需要对刷新过程拥有更多控制权,且要求整个 ApplicationContext 的变更具有原子性时。为解决这些问题,我们提供了 @RefreshScope。
Java 记录(Records)若标注了 @ConfigurationProperties,则无法刷新。 |
刷新作用域
当配置发生更改时,标记为 @RefreshScope 的 Spring @Bean 会受到特殊处理。此功能解决了状态化 Bean 在初始化时才注入配置的问题。例如,如果一个 DataSource 在数据库 URL 通过 Environment 更改时仍存在开放连接,您可能希望这些连接的持有者能够完成其正在进行的操作。随后,当下次有其他组件从连接池中借用连接时,将获得带有新 URL 的连接。
有时,甚至可能强制要求在某些只能初始化一次的 Bean 上应用 @RefreshScope 注解。如果一个 Bean 是“不可变的”,您必须要么使用 @RefreshScope 注解该 Bean,要么在属性键 spring.cloud.refresh.extra-refreshable 下指定其类名。
如果您有一个 DataSource 类型的 Bean,它是一个 HikariDataSource,则无法刷新。这是 spring.cloud.refresh.never-refreshable 的默认值。如果您需要它可刷新,请选择不同的 DataSource 实现。 |
刷新作用域的 Bean 是懒加载代理,它们在被使用时(即调用某个方法时)才进行初始化,而作用域则作为已初始化值的缓存。</p><p>要强制在下一次方法调用时重新初始化该 Bean,您必须使其中的缓存条目失效。
其中 RefreshScope 是上下文中的一个 Bean,并具有一个公共的 refreshAll() 方法,用于通过清除目标缓存来刷新作用域内所有 Bean。/refresh 端点通过 HTTP 或 JMX 暴露此功能。若要按名称刷新单个 Bean,则还提供了一个 refresh(String) 方法。
要暴露 /refresh 端点,您需要在应用程序中添加以下配置:
management:
endpoints:
web:
exposure:
include: refresh
@RefreshScope 在 @Configuration 类上(技术上)是有效的,但可能导致意外的行为。例如,这并不意味着该类中定义的所有 @Beans 都本身位于 @RefreshScope 中。具体而言,任何依赖于这些 Bean 的组件都无法依赖它们在刷新操作启动时被更新,除非它自身也位于 @RefreshScope 中。在这种情况下,它会在刷新时被重新构建,其依赖项也会被重新注入。 此时,它们将从已刷新的 @Configuration 中重新初始化。 |
| 删除一个配置值,然后执行刷新操作,将不会更新该配置值的存在状态。在刷新后,必须存在该配置属性才能更新其值。如果您在应用程序中依赖于某个值的存在,可能需要将您的逻辑改为依赖于其不存在的状态。另一种选择是依赖于值发生变更,而不是依赖于该值在应用程序配置中不存在。 |
上下文刷新不支持 Spring AOT 转换和原生镜像。对于 AOT 和原生镜像,spring.cloud.refresh.enabled 需要设置为 false。 |
重启时刷新作用域
在重启时无缝刷新 Bean 尤其适用于使用 JVM 检查点恢复(如 Project CRaC)的应用程序。为启用此功能,我们现在会实例化一个 RefreshScopeLifecycle 类型的 Bean,该 Bean 在重启时会触发上下文刷新,从而重新绑定配置属性并刷新任何标注了 @RefreshScope 的 Bean。您可以通过将 spring.cloud.refresh.on-restart.enabled 设置为 false 来禁用此行为。
加密与解密
Spring Cloud 在本地解密属性值时,具有一个 Environment 预处理器。它遵循与 Spring Cloud Config Server 相同的规则,并通过 encrypt.* 提供相同的外部配置。因此,您可以在形式为 {cipher}* 的加密值中使用加密数据,只要存在有效的密钥,这些值就会在主应用上下文获取 Environment 配置之前被解密。要在应用程序中使用加密功能,您需要将 Spring Security RSA 添加到类路径中(Maven 坐标: org.springframework.security:spring-security-rsa),并且您的 JVM 还需要包含完整强度的 JCE 扩展。
如果您因“非法密钥大小”异常而遇到问题,并且使用的是 Sun 的 JDK,则需要安装 Java 加密扩展(JCE)无限制权限策略文件。有关更多信息,请参阅以下链接:
将文件解压到您所使用的JRE/JDK版本(x64/x86)的JDK/jre/lib/security目录中。
端点
对于 Spring Boot Actuator 应用程序,一些额外的管理端点可用。您可以使用:
-
POST到/actuator/env可用于更新Environment并重新绑定@ConfigurationProperties及日志级别。
要启用此端点,您必须设置management.endpoint.env.post.enabled=true。 -
/actuator/refresh用于重新加载引导上下文并刷新@RefreshScope中的 Bean。 -
/actuator/restart用于关闭ApplicationContext并重新启动它(默认处于禁用状态)。 -
/actuator/pause和/actuator/resume用于调用Lifecycle方法(stop()和start()在ApplicationContext上)。
虽然为 POST 方法启用 /actuator/env 端点可为管理应用环境变量提供灵活性和便利性,但必须确保该端点已得到保护并受到监控,以防止潜在的安全风险。请添加 spring-boot-starter-security 依赖项,以配置对操作员(actuator)端点的访问控制。 |
如果您禁用 /actuator/restart 端点,则 /actuator/pause 和 /actuator/resume 端点也将被禁用,因为它们只是 /actuator/restart 的特殊情况。 |