2. Spring Cloud Context: 应用上下文服务
Spring Boot 对于如何用 Spring 构建应用程序有着独到的见解。例如,它具有用于公共配置文件的常规位置,并且具有用于公共管理和监视任务的端点。Spring Cloud 在此基础上构建,并添加了一些系统中所有组件都可能会使用或偶尔需要的特性。
Spring Cloud 应用通过创建一个 “bootstrap” 上下文来运行,该上下文是主应用程序的父上下文。它负责从外部源加载配置属性并解密本地外部配置文件中的属性。这两个上下文共享一个环境,这是任何 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.yml
或 application.yml
中)。
你可以通过设置 spring.cloud.bootstrap.enabled=false
(例如,在系统属性中)完全禁用引导过程。
如果从 SpringApplication
或 SpringApplicationBuilder
构建应用上下文,那么将引导上下文作为父上下文添加到该上下文。Spring 的一个特性是子上下文从父上下文继承属性源和配置文件,因此主应用上下文包含额外的属性源,而不使用 Spring Cloud Config 构建相同的上下文。附加属性源有:
-
“bootstrap”:如果在引导上下文中找到任何
PropertySourceLocators
,并且如果它们具有非空属性,则可选的 CompositePropertySource
以高优先级出现。示例是来自 Spring Cloud 配置服务器的属性。有关如何自定义此属性源的内容的说明,请参阅 小节 2.6, “自定义引导属性源”。
-
“applicationConfig: [classpath:bootstrap.yml]”(以及 Spring 配置组处于活动状态的相关文件):如果具有
bootstrap.yml
(或 .properties
),则这些属性用于配置引导上下文。然后,当父上下文被设置时,它们会添加到子上下文中。它们比 bootstrap.yml
(或 .properties
)和作为创建 Spring Boot 应用过程的正常部分添加到子级的任何其他属性源具有更低的优先级。有关如何定制这些属性源的内容的说明,请参阅 小节 2.3, “更改引导属性的位置”。
由于属性源的排序规则,“bootstrap” 条目优先。但是,请注意,它们不包含来自 bootstrap.yml
的任何数据,bootstrap.yml
的优先级非常低,但是可以用于设置默认值。
你可以通过设置你创建的任何 ApplicationContext
的父上下文来扩展上下文层次结构,例如,通过使用自己的接口,或者使用 SpringApplicationBuilder
便利方法(parent()
、child()
和 sibling()
)。引导上下文是你自己创建的最高级祖先的父。层次结构中的每个上下文都有自己的引导(可能为空)属性源,以避免无意中从父级到子级提升值。如果存在配置服务器,层次结构中的每个上下文也可以(原则上)具有不同的 spring.application.name
,因此具有不同的远程属性源。普通的 Spring 应用上下文行为规则适用于属性解析:来自子上下文的属性通过名称以及属性源名称覆盖父上下文中的属性。(如果该子对象具有与父类相同的属性源,则父级的值不包含在子级中)。
注意,SpringApplicationBuilder
允许你在整个层次结构中共享一个环境,但这不是默认的。因此,兄弟上下文尤其不需要具有相同的配置组文件或属性源,即使它们可能与父上下文共享公共值。
可以通过设置 spring.cloud.bootstrap.name
(default:bootstrap
)或 spring.cloud.bootstrap.location
(default:empty)来指定 bootstrap.yml
(或 .properties
)位置 — 例如,在系统属性中。这些属性的行为类似于具有相同名称的 spring.config.*
变体,实际上它们用于通过在其 Environment
中设置这些属性来设置引导 ApplicationContext
。如果有活动的配置组(来自 spring.profiles.active
或通过你正在构建的上下文中的 Environment
API),则该配置组中的属性也会被加载,这与常规 Spring Boot 应用中的属性相同,例如,来自 bootstrap-development.properties
的 development
配置组。
通过引导上下文添加到应用程序的属性源通常是远程的(例如,来自 Spring Cloud 配置服务器)。默认情况下,它们不能在本地上覆盖。如果希望让应用程序用自己的系统属性或配置文件覆盖远程属性,则远程属性源必须通过设置 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
注解来标记类(默认顺序为最后一个)。
![[Warning]](images/warning.png) |
Warning |
在添加自定义 BootstrapConfiguration 时,请注意,你添加的类不是 @ComponentScanned ,而错误地添加到主应用上下文中,其中可能不需要它们。为引导配置类使用单独的包名,并确保 @ComponentScan 或 @SpringBootApplication 注解的配置类没有覆盖该名称。
|
引导过程通过将初始化器注入主 SpringApplication
实例(这是正常的 Spring Boot 启动序列,无论是作为独立应用程序运行还是部署在应用服务器中)而结束。首先,从 spring.factories
中发现的类创建引导上下文。然后,在启动之前,将 ApplicationContextInitializer
类型的所有 @Beans
添加到主 SpringApplication
中。
由引导过程添加的外部配置的默认属性源是 Spring Cloud 配置服务器,但是你可以通过将 PropertySourceLocator
类型的 bean 添加到引导上下文(通过 spring.factories
)来添加其他的源。例如,可以从不同的服务器或数据库插入附加属性。
举个例子,参考下面的自定义定位器:
@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 提供的属性源,因此你可以使用它们来定位特定于此环境的属性源(例如,通过在 spring.application.name
上键入它,就像在默认的 Spring Cloud 配置服务器属性源定位器中所做的那样)。
如果使用该类创建一个 jar,然后添加包含以下内容的 META-INF/spring.factories
,则 customProperty
PropertySource
将出现在任何应用中,该应用中的类路径中包括该 jar:
org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator
如果要使用 Spring Boot 来配置日志设置,那么如果希望将此配置应用于所有事件,则应将该配置置于 bootstrap.[yml | properties] 中。
![[Note]](images/note.png) |
Note |
对于 Spring Cloud,要正确初始化日志配置,就不能使用自定义前缀。例如,初始化日志记录系统时,Spring Cloud 无法识别使用的 custom.loggin.logpath 。
|
应用监听 EnvironmentChangeEvent
,并以两种标准方式响应此更改(用户可以以正常方式将附加的 ApplicationListeners
添加为 @Beans
)。当观察到 EnvironmentChangeEvent
时,它有一个已经改变的键值列表,并且应用使用这些值:
-
在上下文中重新绑定任何
@ConfigurationProperties
bean
-
为
logging.level.*
中的任意属性设置日志记录级别。
注意,默认情况下,配置客户端不会对环境中的更改进行轮询。一般来说,我们不建议使用此方法检测更改(尽管你可以使用 @Scheduled
注解对其进行设置)。如果你有一个扩展了的客户端应用程,那么最好将 EnvironmentChangeEvent
广播到所有实例,而不是让它们轮询更改(例如,通过使用 Spring Cloud Bus)。
EnvironmentChangeEvent
覆盖了大量刷新用例,只要你能够实际更改环境并发布事件。注意,API 是公共的,也是 Spring 核心的一部分。你可以通过访问 /configprops
端点(一个普通的 Spring Boot 执行器特性)来验证更改绑定到 @ConfigurationProperties
bean。例如,DataSource
可以在运行时更改其 maxPoolSize
(Spring Boot 创建的默认 DataSource
是 @ConfigurationProperties
bean),并动态地增加容量。重新绑定 @ConfigurationProperties
没有覆盖用例的其他大类,其中需要对刷新进行更多控制,并且需要对整个 ApplicationContext
进行原子更改。为了解决这些问题,我们有 @RefreshScope
。
当配置改变时,标记为 @RefreshScope
的 Spring @Bean
得到特殊处理。此特性解决了状态 bean 的问题,只有在初始化时才注入它们的配置 bean。例如,如果 DataSource
在通过环境更改数据库 URL 时具有打开的连接,那么你可能希望这些连接的持有者能够完成他们正在做的事情。然后,下次从池中借用某个连接时,它会得到一个带有新 URL 的连接。
有时,甚至可能强制在某些 bean 上应用 @RefreshScope
注解,这些 bean 只能被初始化一次。如果 bean 是不可变的,则必须使用 @RefreshScope
对 bean 进行注解,或者在属性键 spring.cloud.refresh.extra-refreshable
下指定类名。
![[Important]](images/important.png) |
重点 |
如果你自己创建 DataSource bean,并且实现的是 HikariDataSource ,返回最特殊的类型,在本例中是 HikariDataSource 。不然,你将需要设置 spring.cloud.refresh.extra-refreshable=javax.sql.DataSource 。
|
刷新范围 bean 是懒代理,当使用它们时(即调用方法时)对其进行初始化,并且范围充当初始化值的缓存。若要强制 bean 重新初始化下一个方法调用,则必须使其缓存条目无效。
RefreshScope
是上下文中的一个 bean,它有一个公共 refreshAll()
方法,通过清除目标缓存来刷新范围中的所有 bean。/refresh
端点公开此功能(通过 HTTP 或 JMX)。要按名称刷新单个 bean,还存在 refresh(String)
方法。
要暴露 /refresh
端点,需要将以下配置添加到应用中:
management:
endpoints:
web:
exposure:
include: refresh
![[Note]](images/note.png) |
Note |
@RefreshScope (技术上)在一个 @Configuration 类上工作,但它可能会导致令人惊讶的行为。例如,这并不意味着在该类中定义的所有 @Beans 都在 @RefreshScope 中。特别地,任何依赖于这些 bean 的内容都不能依赖于启动刷新时更新它们,除非它本身在 @RefreshScope 中。在这种情况下,它在刷新时重建,并重新注入其依赖关系。在这一点上,它们从刷新的 @Configuration 重新初始化。
|
Spring Cloud 有一个用于在本地解密属性值的环境预处理器。它遵循与配置服务器相同的规则,并通过 encrypt.*
具有相同的外部配置。因此,你可以使用 {cipher}*
形式的加密值,并且只有一个有效的密钥,在主应用上下文获得环境设置之前对其进行解密。要在应用中使用加密特性,需要在类路径中包括 Spring Security RSA (Maven co-ordinates: "org.springframework.security:spring-security-rsa"),还需要在 JVM 中使用全强度的 JCE 扩展。
如果你由于 "Illegal key size" 而获得异常,并且使用 Sun 的 JDK,则需要安装 Java 加密扩展(JCE)无限制强度管辖权策略文件。有关的更多详细信息请参见以下链接:
将文件提取到你使用的对应版本的 JRE/JDK x64/x86 的 JDK/jre/lib/security 文件夹中。
对于 Spring Boot 执行器应用,可以使用一些额外的管理端点。你可以使用:
-
POST
到 /actuator/env
来更新 Environment
和重新绑定 @ConfigurationProperties
和日志级别。
-
/actuator/refresh
来重新加载引导上下文并刷新 @RefreshScope
bean。
-
/actuator/restart
来关闭 ApplicationContext
并重启它(默认禁用)。
-
/actuator/pause
和 /actuator/resume
来调用 Lifecycle
方法 (ApplicationContext
上的 stop()
和 start()
).
![[Note]](images/note.png) |
Note |
如果禁用 /actuator/restart 端点,那么也将禁用 /actuator/pause 和 /actuator/resume 端点,因为它们只是 /actuator/restart 的特殊情况。
|