Spring Cloud Context:应用上下文服务

Spring Boot 对于如何使用 Spring 构建应用程序有自己的主张。例如,它为常见的配置文件提供了约定的位置,并为常见的管理和监控任务提供了端点。Spring Cloud 在此基础上构建,并添加了一些系统中许多组件会使用或偶尔需要的功能。

引导应用上下文

Spring Cloud 应用程序通过创建一个“引导”上下文来运行,该上下文是主应用程序的父上下文。此上下文负责从外部源加载配置属性,并解密本地外部配置文件中的属性。这两个上下文共享一个 Environment,它是任何 Spring 应用程序的外部属性源。默认情况下,引导属性(不是 bootstrap.properties,而是在引导阶段加载的属性)以高优先级添加,因此它们不能被本地配置覆盖。

与主应用程序上下文不同,引导上下文使用不同的约定来定位外部配置。您可以使用 bootstrap.yml 代替 application.yml(或 .properties),从而将引导和主上下文的外部配置很好地分开。以下清单显示了一个示例:

bootstrap.yml
spring:
  application:
    name: foo
  cloud:
    config:
      uri: ${SPRING_CONFIG_URI:http://localhost:8888}

如果您的应用程序需要来自服务器的任何特定于应用程序的配置,最好设置 spring.application.name(在 bootstrap.ymlapplication.yml 中)。要将属性 spring.application.name 用作应用程序的上下文 ID,必须在 bootstrap.[properties | yml] 中设置它。

如果要检索特定的 profile 配置,还应在 bootstrap.[properties | yml] 中设置 spring.profiles.active

您可以通过设置 spring.cloud.bootstrap.enabled=false(例如,在系统属性中)完全禁用引导过程。

应用上下文层次结构

如果使用 SpringApplicationSpringApplicationBuilder 构建应用上下文,则引导上下文会作为该上下文的父级添加。Spring 的一个特性是子上下文继承其父级的属性源和 profile,因此与不使用 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 profiles 处于活动状态的相关文件):如果您有一个 bootstrap.yml(或 .properties),这些属性将用于配置引导上下文。然后,当其父级设置后,它们会被添加到子上下文。它们具有比 application.yml(或 .properties)和作为创建 Spring Boot 应用程序正常过程添加到子级的任何其他属性源更低的优先级。有关如何自定义这些属性源的内容,请参阅“更改引导属性的位置”。

由于属性源的排序规则,“bootstrap”条目具有优先权。但是请注意,这些条目不包含来自 bootstrap.yml 的任何数据,bootstrap.yml 的优先级非常低,但可用于设置默认值。

您可以通过设置创建的任何 ApplicationContext 的父上下文来扩展上下文层次结构,例如,使用其自身的接口或使用 SpringApplicationBuilder 便利方法(parent()child()sibling())。引导上下文是您自己创建的最顶层祖先的父上下文。层次结构中的每个上下文都有其自己的“bootstrap”(可能为空)属性源,以避免无意中将值从父级传播到其后代。如果存在配置服务器,层次结构中的每个上下文原则上也可以有不同的 spring.application.name,从而有不同的远程属性源。属性解析遵循正常的 Spring 应用程序上下文行为规则:来自子上下文的属性会按名称和属性源名称覆盖父上下文中的属性。(如果子上下文中的属性源与父上下文同名,则父上下文中的值不包含在子上下文中)。

请注意,SpringApplicationBuilder 允许您在整个层次结构中共享一个 Environment,但这并非默认行为。因此,兄弟上下文(特别是)不需要具有相同的 profiles 或属性源,即使它们可能与其父级共享通用值。

更改引导属性的位置

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。实际上,它们通过在引导 ApplicationContextEnvironment 中设置这些属性来配置引导 ApplicationContext。如果存在活动 profile(来自 spring.profiles.active 或通过正在构建的上下文中的 Environment API),则该 profile 中的属性也会加载,与常规 Spring Boot 应用相同,例如,对于 development profile,加载自 bootstrap-development.properties

覆盖远程属性的值

引导上下文添加到应用程序的属性源通常是“远程”的(例如,来自 Spring Cloud Config Server)。默认情况下,它们不能在本地被覆盖。如果您希望应用程序能够用自己的系统属性或配置文件覆盖远程属性,则远程属性源必须通过设置 spring.cloud.config.allowOverride=true 授权(在本地设置此属性无效)。设置此标志后,两个更细粒度的设置控制远程属性相对于系统属性和应用程序本地配置的位置:

  • spring.cloud.config.overrideNone=true:从任何本地属性源覆盖。

  • spring.cloud.config.overrideSystemProperties=false:只有系统属性、命令行参数和环境变量(而非本地配置文件)应覆盖远程设置。

自定义引导配置

引导上下文可以通过在 /META-INF/spring.factories 中添加键为 org.springframework.cloud.bootstrap.BootstrapConfiguration 的条目来配置为执行任何您想做的操作。该键的值是一个逗号分隔的 Spring @Configuration 类列表,用于创建上下文。您希望主应用程序上下文可用于自动注入的任何 bean 都可以在此处创建。对于 ApplicationContextInitializer 类型的 @Beans 有一个特殊的约定。如果要控制启动顺序,可以使用 @Order 注解标记类(默认顺序为 last)。

添加自定义 BootstrapConfiguration 时,请小心,不要错误地将添加的类被 @ComponentScanned 扫描到您的“主”应用上下文,在那里它们可能不需要。为引导配置类使用一个单独的包名,并确保该名称不被您的 @ComponentScan@SpringBootApplication 注解的配置类覆盖。

引导过程结束时,将初始化器注入主 SpringApplication 实例中(这是正常的 Spring Boot 启动序列,无论它是作为独立应用程序运行还是部署在应用服务器中)。首先,从 spring.factories 中找到的类创建一个引导上下文。然后,在启动主 SpringApplication 之前,所有 ApplicationContextInitializer 类型的 @Beans 都会被添加到其中。

自定义引导属性源

引导过程添加的外部配置的默认属性源是 Spring Cloud Config Server,但您可以通过向引导上下文添加 PropertySourceLocator 类型的 bean(通过 spring.factories)来添加额外的源。例如,您可以从不同的服务器或数据库中插入额外的属性。

例如,考虑以下自定义 locator:

@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 两次。第一次获取将检索没有任何 profile 的任何属性源。这些属性源将有机会使用 spring.profiles.active 激活 profile。主应用上下文启动后,将第二次调用 PropertySourceLocators,这次带有任何活动 profile,允许 PropertySourceLocators 定位带有 profile 的任何其他 PropertySources

日志配置

如果您使用 Spring Boot 配置日志设置,如果您希望它适用于所有事件,则应将此配置放在 bootstrap.[yml | properties] 中。

为了使 Spring Cloud 正确初始化日志配置,您不能使用自定义前缀。例如,使用 custom.loggin.logpath 在初始化日志系统时不会被 Spring Cloud 识别。

环境变化

应用程序监听 EnvironmentChangeEvent 并以几种标准方式对变化做出反应(可以按正常方式将额外的 ApplicationListeners 作为 @Beans 添加)。当观察到 EnvironmentChangeEvent 时,它会包含已更改的键值列表,应用程序会利用这些信息进行以下操作:

  • 重新绑定上下文中的所有 @ConfigurationProperties beans。

  • 设置 logging.level.* 中任何属性的日志级别。

请注意,Spring Cloud Config Client 默认情况下不会轮询 Environment 的变化。通常,我们不建议使用这种方法来检测变化(尽管您可以使用 @Scheduled 注解进行设置)。如果您有一个扩展的客户端应用程序,最好将 EnvironmentChangeEvent 广播到所有实例,而不是让它们轮询变化(例如,使用 Spring Cloud Bus)。

EnvironmentChangeEvent 涵盖了很大一部分刷新用例,只要您能够实际更改 Environment 并发布事件即可。请注意,这些 API 是公共的,并且是 Spring 核心的一部分。您可以通过访问 /configprops 端点(标准的 Spring Boot Actuator 功能)来验证更改是否绑定到 @ConfigurationProperties beans。例如,DataSourcemaxPoolSize 可以在运行时更改(Spring Boot 创建的默认 DataSource 是一个 @ConfigurationProperties bean),并动态增长容量。重新绑定 @ConfigurationProperties 并不能涵盖另一大类用例,在这种情况下,您需要对刷新有更多控制,并且需要更改对整个 ApplicationContext 具有原子性。为了解决这些问题,我们提供了 @RefreshScope

使用 @ConfigurationProperties 注解的 Java Records 无法刷新。

刷新作用域

当配置发生更改时,标记为 @RefreshScope 的 Spring @Bean 会受到特殊处理。此功能解决了仅在初始化时注入配置的有状态 bean 的问题。例如,如果 DataSource 在通过 Environment 更改数据库 URL 时仍有打开的连接,您可能希望持有这些连接的对象能够完成当前正在执行的操作。然后,下次从连接池借用连接时,它会获得使用新 URL 的连接。

有时,可能甚至必须对只能初始化一次的某些 bean 应用 @RefreshScope 注解。如果一个 bean 是“不可变的”,您必须使用 @RefreshScope 注解该 bean,或在属性键 spring.cloud.refresh.extra-refreshable 下指定类名。

如果您有一个 DataSource bean 是 HikariDataSource,则无法刷新。它是 spring.cloud.refresh.never-refreshable 的默认值。如果您需要刷新它,请选择不同的 DataSource 实现。

刷新范围 bean 是懒惰的代理,它们在使用时(即调用方法时)初始化,并且该范围充当已初始化值的缓存。要强制 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 Checkpoint Restore(例如 Project CRaC)运行的应用程序特别有用。为了实现此功能,我们现在实例化一个 RefreshScopeLifecycle 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 扩展。

如果您由于“Illegal key size”而收到异常,并且您使用的是 Sun 的 JDK,则需要安装 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files。有关更多信息,请参阅以下链接:

将文件解压到您使用的任何版本的 JRE/JDK x64/x86 的 JDK/jre/lib/security 文件夹中。

端点

对于 Spring Boot Actuator 应用程序,可以使用一些额外的管理端点。您可以使用:

  • /actuator/env 发送 POST 请求以更新 Environment 并重新绑定 @ConfigurationProperties 和日志级别。要启用此端点,必须设置 management.endpoint.env.post.enabled=true

  • /actuator/refresh 用于重新加载引导上下文并刷新 @RefreshScope beans。

  • /actuator/restart 用于关闭 ApplicationContext 并重新启动它(默认禁用)。

  • /actuator/pause/actuator/resume 用于调用 Lifecycle 方法(ApplicationContext 上的 stop()start())。

虽然启用 /actuator/env 端点的 POST 方法可以为管理应用程序环境变量提供灵活性和便利性,但确保该端点安全并受到监控以防止潜在的安全风险至关重要。添加 spring-boot-starter-security 依赖项以配置对 actuator 端点的访问控制。
如果您禁用 /actuator/restart 端点,则 /actuator/pause/actuator/resume 端点也将被禁用,因为它们只是 /actuator/restart 的特例。