SpringCloud-Spring Cloud Context

A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. It is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files. The two contexts share an Environment, which is the source of external properties for any Spring application.

By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence, so they cannot be overridden by local configuration.

The bootstrap context uses a different convention for locating external configuration than the main application context. Instead of application.yml (or .properties), you can use bootstrap.yml, keeping the external configuration for bootstrap and main context nicely separate.

释文:Spring Cloud 应用程序通过创建“引导程序”上下文来运行,该上下文是主应用程序的父上下文共享一个 Environment**,它是任何Spring应用程序的外部属性的来源。

默认情况下,引导属性(不是bootstrap.properties,而是在引导阶段加载的属性)以高优先级添加,因此本地配置无法覆盖它们。

引导上下文使用与主应用程序上下文不同的外部配置约定。 因此使用 bootstrap.yml application.yml(或.properties)代替引导和主上下文的外部配置,保持引导程序和主上下文的外部配置很好地分开。

上面是 SpringCloud 关于引导上下文的一个解释,详见 这里

spring cloud 有自己的一套配置初始化机制,所以它实际上是自己启动了一个Spring 上下文,也就是我们说的引导上文。在上面的描述中有提到,引导上下文会以应用上下文的父类存在;在Spring中,如果上下文存在父子关系,也就意味着子上下文会集成父上下文的属性源和配置文件。在SpringBoot的启动过程中,prepareContext 这个操作会进行父子上下文的关系设置,调用栈如下:

setParent 方法代码片段如下:

1
2
3
4
5
6
7
8
9
 public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
}
}
}

这个可以看到,子上下文会合并掉父上下文的 Environment 。关于父子上下文是怎么关联起来的,下面来看。

1
2
3
4
5
6
7
8
public void initialize(ConfigurableApplicationContext context) {
while(context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext)context.getParent();
}

this.reorderSources(context.getEnvironment());
(new ParentContextApplicationContextInitializer(this.parent)).initialize(context);
}

BootstrapApplicationListener

上面的代码片段定位在 org.springframework.cloud.bootstrap.BootstrapApplicationListener 这个类;这个监听器监听的事件是 ApplicationEnvironmentPreparedEvent ,对应在SpringBoot启动过程,就是在执行 prepareEnvironment 时触发事件调用。

BootstrapApplicationListener 的 onApplicationEvent 回调方法中实际上就是用够构建和启动 Spring Cloud context 的。
spring cloud context 算是一个特殊的 spring boot context, 在分析代码的过程中(bootstrapServiceContext方法中)发现,它只扫描 BootstrapConfiguration 这个注解标注的组件。

这里就着重分析下 SpringCloud Context 的启动过程。

SpringCloud Context 启动过程

通过 spring.cloud.bootstrap.enabled 配置来禁用引导上下文

1
2
3
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,true)) {
return;
}

回调函数的开始就会对 spring.cloud.bootstrap.enabled 这个配置值进行校验,来决定是否需要禁止引导。这个在官方文档里面也有明确提到。

获取 configName

1
2
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");

可以使用 spring.cloud.bootstrap.name(默认“bootstrap”)或spring.cloud.bootstrap.location(默认为空)指定bootstrap.yml(或.properties)位置,例如在系统属性中。

bootstrapServiceContext 创建&启动

bootstrapServiceContext 是完成此过程的核心方法。

  • 加载 BootstrapConfiguration 自动配置类
1
2
3
4
5
6
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

PropertySourceBootstrapConfiguration 将会把 PropertySourceLocator 自定义属性值添加到引导上下文的环境当中,包括如何从远端仓库拉取配置等过程。

  • 构建 SpringApplicationBuilder 类

    1
    2
    3
    4
    5
    6
    SpringApplicationBuilder builder = new SpringApplicationBuilder()
    .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
    .environment(bootstrapEnvironment)
    // Don't use the default properties in this builder
    .registerShutdownHook(false).logStartupInfo(false)
    .web(WebApplicationType.NONE);
  • 构建 引导上下文并 run

    1
    final ConfigurableApplicationContext context = builder.run();

这个 build.run 实际执行的就是 SpringApplication.run 方法。

  • 为关联父子上下文准备
    1
    addAncestorInitializer(application, context);

这里会把 ParentContextApplicationContextInitializer 加到应用的 spring context 里,来把自己设置为应用的context 的 parent,具体是在SpringBoot启动过程的 prepareContext 中完成 。

重载远程属性

通过Bootstrap 上下文添加到应用程序的属性源通常是远程的,比如说来自配置中心的,一般情况下本地的配置文件不能覆盖这些远程属性源。

那么如果想覆盖远程属性源怎么办呢?可以通过启动命令行参数方式设定(启动命令行参数的优先级高于远程配置的优先级)。

如果想使用应用程序的系统属性或者配置文件覆盖远程属性,那么远程属性源必须设置为 spring.cloud.config.allowOverride = true,这个配置在本地设置时不会生效的。在远程属性源中设定上述配置后,就可以通过更为细粒度的设置来控制远程属性是否能被重载,具体配置如下:

1
2
3
4
5
spring:
cloud:
config:
overrideNone: true
overrideSystemProperties: false
  • overrideNone true,本地属性覆盖所有的远程属性
  • overrideSystemProperties ,仅覆盖远程属性源中的系统属性和环境变量

自定义 Bootstrap 属性源

默认情况下,Bootstrap 的外部配置属性源是 spring cloud config server ,也就是使用配置中心加载外部属性,但是Spring中也允许用户通过将 ProoertySourceLocator 类型的Bean实例添加到 Bootstrap 上下文,也就是在 spring.factories 中添加相应的配置类,来添加额外的属性源来源。这里可以通过SpringCloud里面提供的测试用例来看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected static class PropertySourceConfiguration implements PropertySourceLocator {
// 省略其他代码
@Override
public PropertySource<?> locate(Environment environment) {
if (this.name != null) {
assertEquals(this.name,
environment.getProperty("spring.application.name"));
}
if (this.fail) {
throw new RuntimeException("Planned");
}
return new MapPropertySource("testBootstrap", MAP);
}
// 省略其他代码
}

上面代码段中传入的Envirement 参数用于创建应用上下文,它具有 SpringBoot 提供的属性源,可以使用它们来加载指定的属性源。

最后将这个自定义的 PropertySourceLocator 配置到 spring.factories 中,这样应用程序就可以使用这个 PropertySourceConfiguration 作为其属性源了。

1
2
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
xx.xx.x.x.PropertySourceConfiguration

关于Enviroment 的变化

配置中心客户端(Spring Cloud Config Client) 应用会监听 EnviromentChangeEvent 事件,当监听到这个事件时,它将持有一个被改变的键值对列表,然后客户端应用会使用这些值来做一些事情:

  • 重新绑定所有的@ConfigurationProperties的Bean@ConfigurationProperties 实例,更新本地的配置属性。
  • 设置日志等级(logging.level.* 相关配置)

Spring Cloud 中,配置中心服务端使用 Spring Cloud Bus 将EnviromentChangeEvent 事件广播到所有的客户端中,通过这种方式来通过它们 Enviroment 发生变化。

RefreshScope

注解

RefreshScope 注解的作用是,当被这个注解标记的 Bean 实例在配置发生变化时可以重新进行初始化。这个注解很好的解决了状态Bean实例只能在初始化的时候才能进行属性注入的问题。

org.springframework.cloud.context.scope.refresh.RefreshScope 是上下文中的一个Bean实例,在它的 refreshAll 这个方法中,可以通过清除目标缓存来刷新作用域中的所有Bean实例。RefreshScope中也提供了一个 refresh方法,可以按照名字来刷新单个Bean。

作者

卫恒

发布于

2018-12-31

更新于

2022-04-23

许可协议

评论