Spring Cloud Context:应用上下文服务
Spring Boot 对如何使用 Spring 构建应用程序有着自己的见解。例如,它为常见的配置文件提供了约定的位置,并为常见的管理和监控任务提供了端点。Spring Cloud 在此基础上构建,并添加了一些系统中许多组件会使用或偶尔需要的功能。
引导应用程序上下文
一个 Spring Cloud 应用程序通过创建一个“bootstrap”上下文来运行,该上下文是主应用程序的父上下文。该上下文负责从外部源加载配置属性,并解密本地外部配置文件中的属性。这两个上下文共享一个 Environment,它是任何 Spring 应用程序的外部属性的来源。默认情况下,bootstrap 属性(不是 bootstrap.properties,而是在 bootstrap 阶段加载的属性)以高优先级添加,因此它们不能被本地配置覆盖。
引导上下文(bootstrap context)在定位外部配置时采用了与主应用程序上下文不同的约定。与使用 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 构建应用程序上下文,Bootstrap 上下文将作为该上下文的父上下文添加。这是 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 的任何数据,bootstrap.yml 的优先级非常低,但可以用来设置默认值。
你可以通过设置你创建的任何 ApplicationContext 的父上下文来扩展上下文层次结构——例如,使用它自己的接口或通过 SpringApplicationBuilder 的便捷方法(parent()、child() 和 sibling())。引导上下文是你自己创建的最上层祖先的父上下文。层次结构中的每个上下文都有自己的“引导”(可能为空)属性源,以避免无意中将值从父级传播到子级。如果存在配置服务器,层次结构中的每个上下文也可以(原则上)拥有不同的 spring.application.name,从而拥有不同的远程属性源。Spring 应用上下文的行为规则适用于属性解析:子上下文中的属性会覆盖父上下文中的属性,无论是按名称还是按属性源名称。(如果子上下文具有与父上下文同名的属性源,则父上下文中的值不会包含在子上下文中)。
请注意,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。实际上,它们通过在 Environment 中设置这些属性来设置引导 ApplicationContext。如果有活动的配置文件(来自 spring.profiles.active 或通过你正在构建的上下文中的 Environment API),该配置文件中的属性也会被加载,就像在常规的 Spring Boot 应用程序中一样——例如,对于 development 配置文件,会从 bootstrap-development.properties 加载属性。
覆盖远程属性的值
由引导上下文添加到应用程序中的属性源通常是“远程”的(例如,来自 Spring Cloud Config Server)。默认情况下,它们不能在本地被覆盖。如果你希望让应用程序用自己的系统属性或配置文件覆盖远程属性,远程属性源必须通过设置 spring.cloud.config.allowOverride=true 来授予权限(在本地设置此属性是无效的)。一旦设置了该标志,两个更细粒度的设置将控制远程属性相对于系统属性和应用程序本地配置的位置:
- 
spring.cloud.config.overrideNone=true:覆盖任何本地属性源。
- 
spring.cloud.config.overrideSystemProperties=false:只有系统属性、命令行参数和环境变量(但不包括本地配置文件)可以覆盖远程设置。
自定义 Bootstrap 配置
通过在 /META-INF/spring.factories 下添加一个名为 org.springframework.cloud.bootstrap.BootstrapConfiguration 的键,可以设置引导上下文以执行任何你喜欢的操作。该键包含一个逗号分隔的 Spring @Configuration 类列表,这些类用于创建上下文。你可以在这里创建任何你希望在主应用程序上下文中可用的 bean,以便进行自动装配。对于类型为 ApplicationContextInitializer 的 @Beans,有一个特殊的约定。如果你想控制启动顺序,可以使用 @Order 注解标记类(默认顺序是 last)。
在添加自定义的 BootstrapConfiguration 时,请注意确保你添加的类不会无意中被 @ComponentScan 扫描到你的“主”应用程序上下文中,因为这些类可能并不需要在那里。为引导配置类使用一个单独的包名,并确保该包名没有被 @ComponentScan 或 @SpringBootApplication 注解的配置类所覆盖。
引导过程结束时会向主 SpringApplication 实例中注入初始化器(这是正常的 Spring Boot 启动顺序,无论它是作为独立应用程序运行还是部署在应用服务器中)。首先,从 spring.factories 中找到的类创建一个引导上下文。然后,在启动主 SpringApplication 之前,所有类型为 ApplicationContextInitializer 的 @Beans 都会被添加到其中。
自定义引导属性源
引导过程添加的外部配置的默认属性源是 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 正确初始化日志配置,你不能使用自定义前缀。例如,使用 custom.logging.logpath 时,Spring Cloud 在初始化日志系统时无法识别该配置。
环境变化
应用程序监听 EnvironmentChangeEvent 并以几种标准方式对变化做出反应(可以以常规方式添加额外的 ApplicationListeners 作为 @Beans)。当观察到 EnvironmentChangeEvent 时,它会有一个已更改的键值列表,应用程序使用这些键值来:
- 
重新绑定上下文中的任何 @ConfigurationProperties类型的 bean。
- 
为 logging.level.*中的任何属性设置日志级别。
需要注意的是,Spring Cloud Config Client 默认情况下不会轮询 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。
使用 @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 类上工作,但它可能会导致一些意外的行为。例如,这并不意味着在该类中定义的所有 @Bean 都会自动处于 @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 扩展。
如果你因为“非法密钥大小”而遇到异常,并且你使用的是 Sun 的 JDK,你需要安装 Java Cryptography Extension (JCE) 无限制强度管辖权策略文件。更多信息请参考以下链接:
将文件解压到你使用的 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/restart 端点,那么 /actuator/pause 和 /actuator/resume 端点也会被禁用,因为它们只是 /actuator/restart 的特殊情况。