文档章节

Spring Cloud Config 规范

阿里云官方博客
 阿里云官方博客
发布于 2018/12/17 12:16
字数 2346
阅读 12
收藏 0

Spring Cloud Config 规范

首先Spring Cloud 是基于 Spring 来扩展的,Spring 本身就提供当创建一个Bean时可从Environment 中将一些属性值通过@Value的形式注入到业务代码中的能力。那Spring Cloud Config 要解决的问题就是:

  1. 如何将配置加载到 Environment 。
  2. 配置变更时,如何控制 Bean 是否需要 create,重新触发一次 Bean 的初始化,才能将 @Value 注解指定的字段从 Environment 中重新注入。
  3. 配置变更时,如何控制新的配置会更新到 Environment 中,才能保证配置变更时可注入最新的值。

要解决以上三个问题:Spring Cloud Config 规范中刚好定义了核心的三个接口:

  1. PropertySourceLocator:抽象出这个接口,就是让用户可定制化的将一些配置加载到 Environment。这部分的配置获取遵循了 Spring Cloud Config 的理念,即希望能从外部储存介质中来 loacte。
  2. RefreshScope: Spring Cloud 定义这个注解,是扩展了 Spring 原有的 Scope 类型。用来标识当前这个 Bean 是一个refresh 类型的 Scope。其主要作用就是可以控制 Bean 的整个生命周期。
  3. ContextRefresher:抽象出这个 Class,是让用户自己按需来刷新上下文(比如当有配置刷新时,希望可以刷新上下文,将最新的配置更新到 Environment,重新创建 Bean 时,就可以从 Environment 中注入最新的配置)。

Spring Cloud Config 原理

Spring Cloud Config 的启动过程

1、如何将配置加载到Environment:PropertySourceLocator

在整个 Spring Boot 启动的生命周期过程中,有一个阶段是 prepare environment。在这个阶段,会publish 一个 ApplicationEnvironmentPreparedEvent,通知所有对这个事件感兴趣的 Listener,提供对 Environment 做更多的定制化的操作。Spring Cloud 定义了一个BootstrapApplicationListener,在 BootstrapApplicationListener 的处理过程中有一步非常关键的操作如下所示:

private ConfigurableApplicationContext bootstrapServiceContext(
            ConfigurableEnvironment environment, final SpringApplication application,
            String configName) {
        //省略
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        List<String> names = new ArrayList<>(SpringFactoriesLoader
                .loadFactoryNames(BootstrapConfiguration.class, classLoader));
        //省略
    }

这是 Spring 的工厂加载机制,可通过在 META-INF/spring.factories 文件中配置一些程序中预定义的一些扩展点。比如 Spring Cloud 这里的实现,可以看到 BootstrapConfiguration 不是一个具体的接口,而是一个注解。通过这种方式配置的扩展点好处是不局限于某一种接口的实现,而是同一类别的实现。可以查看 spring-cloud-context 包中的 spring.factories 文件关于BootstrapConfiguration的配置,有一个比较核心入口的配置就是:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration

可以发现 PropertySourceBootstrapConfiguration 实现了 ApplicationContextInitializer 接口,其目的就是在应用程序上下文初始化的时候做一些额外的操作。在 Bootstrap 阶段,会通过 Spring Ioc 的整个生命周期来初始化所有通过key为_org.springframework.cloud.bootstrap.BootstrapConfiguration_ 在 spring.factories 中配置的 Bean。Spring Cloud Alibaba Nacos Config 的实现就是通过该key来自定义一些在Bootstrap 阶段需要初始化的一些Bean。在该模块的 spring.factories 配置文件中可以看到如下配置:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.alibaba.nacos.NacosConfigBootstrapConfiguration

在 Bootstrap 阶段初始化的过程中,会获取所有 ApplicationContextInitializer 类型的 Bean,并设置回SpringApplication主流程当中。如下 BootstrapApplicationListener 类中的部分代码所示:

private void apply(ConfigurableApplicationContext context,
        SpringApplication application, ConfigurableEnvironment environment) {
    @SuppressWarnings("rawtypes")
    //这里的 context 是一个 bootstrap 级别的 ApplicationContext,这里已经含有了在 bootstrap阶段所有需要初始化的 Bean。
    //因此可以获取 ApplicationContextInitializer.class 类型的所有实例
    List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
            ApplicationContextInitializer.class);
    //设置回 SpringApplication 主流程当中
    application.addInitializers(initializers 
            .toArray(new ApplicationContextInitializer[initializers.size()]));
    
    //省略...
}

这样一来,就可以通过在 SpringApplication 的主流程中来回调这些ApplicationContextInitializer 的实例,做一些初始化的操作。如下 SpringApplication 类中的部分代码所示:

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    //回调在BootstrapApplicationListener中设置的ApplicationContextInitializer实例
    applyInitializers(context);
    listeners.contextPrepared(context);
    //省略...
}

protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

在 applyInitializers 方法中,会触发 PropertySourceBootstrapConfiguration 中的 initialize 方法。如下所示:

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    CompositePropertySource composite = new CompositePropertySource(
            BOOTSTRAP_PROPERTY_SOURCE_NAME);
    AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
    boolean empty = true;
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    for (PropertySourceLocator locator : this.propertySourceLocators) {
        PropertySource<?> source = null;
        //回调所有实现PropertySourceLocator接口实例的locate方法,
        source = locator.locate(environment);
        if (source == null) {
            continue;
        }
        
        composite.addPropertySource(source);
        empty = false;
    }
    if (!empty) {
    //从当前Enviroment中获取 propertySources
        MutablePropertySources propertySources = environment.getPropertySources();
        //省略...
        //将composite中的PropertySource添加到当前应用上下文的propertySources中
        insertPropertySources(propertySources, composite);
        //省略...
    }

 

在这个方法中会回调所有实现 PropertySourceLocator 接口实例的locate方法,
locate 方法返回一个 PropertySource 的实例,统一add到CompositePropertySource实例中。如果 composite 中有新加的PropertySource,最后将composite中的PropertySource添加到当前应用上下文的propertySources中。Spring Cloud Alibaba Nacos Config 在 Bootstrap 阶段通过Java配置的方式初始化了一个 NacosPropertySourceLocator 类型的Bean。从而在 locate 方法中将存放在Nacos中的配置信息读取出来,将读取结果存放到 PropertySource 的实例中返回。具体如何从Nacos中读取配置信息可参考 NacosPropertySourceLocator 类的实现。

Spring Cloud Config 正是提供了PropertySourceLocator接口,来提供应用外部化配置可动态加载的能力。Spring Ioc 容器在初始化 Bean 的时候,如果发现 Bean 的字段上含有 @Value 的注解,就会从 Enviroment 中的PropertySources 来获取其值,完成属性的注入。

Spring Cloud Config 外部化配置可动态刷新

感知到外部化配置的变更这部分代码的操作是需要用户来完成的。Spring Cloud Config 只提供了具备外部化配置可动态刷新的能力,并不具备自动感知外部化配置发生变更的能力。比如如果你的配置是基于Mysql来实现的,那么在代码里面肯定要有能力感知到配置发生变化了,然后再显示的调用 ContextRefresher 的 refresh方法,从而完成外部化配置的动态刷新(只会刷新使用RefreshScope注解的Bean)。

例如在 Spring Cloud Alibaba Nacos Config 的实现过程中,Nacos 提供了对dataid 变更的Listener 回调。在对每个dataid 注册好了相应的Listener之后,如果Nacos内部通过长轮询的方式感知到数据的变更,就会回调相应的Listener,在 Listener 的实现过程中,就是通过调用 ContextRefresher 的 refresh方法完成配置的动态刷新。具体可参考 NacosContextRefresher 类的实现。

Sring Cloud Config的动态配置刷新原理图如下所示:

ContextRefresher的refresh的方法主要做了两件事:

  1. 触发PropertySourceLocator的locator方法,需要加载最新的值,并替换 Environment 中旧值
  2. Bean中的引用配置值需要重新注入一遍。重新注入的流程是在Bean初始化时做的操作,那也就是需要将refresh scope中的Bean 缓存失效,当再次从refresh scope中获取这个Bean时,发现取不到,就会重新触发一次Bean的初始化过程。

这两个操作所对应的代码如下所示:

public synchronized Set refresh() {
    Map<String, Object> before = extract(
            this.context.getEnvironment().getPropertySources());
    //1、加载最新的值,并替换Envrioment中旧值
    addConfigFilesToEnvironment();
    Set<String> keys = changes(before,
            extract(this.context.getEnvironment().getPropertySources())).keySet();
    this.context.publishEvent(new EnvironmentChangeEvent(context, keys));
    //2、将refresh scope中的Bean 缓存失效: 清空
    this.scope.refreshAll();
    return keys;
}

addConfigFilesToEnvironment 方法中发生替换的代码如下所示:

ConfigurableApplicationContext addConfigFilesToEnvironment() {
    ConfigurableApplicationContext capture = null;
    try {
        //省略...
        //1、这里会重新触发PropertySourceLoactor的locate的方法,获取最新的外部化配置
        capture = (SpringApplicationBuilder)builder.run();
        
        MutablePropertySources target = this.context.getEnvironment()
                .getPropertySources();
        String targetName = null;
        for (PropertySource<?> source : environment.getPropertySources()) {
            String name = source.getName();
            //省略..
            
            //只有不是标准的 Source 才可替换
            if (!this.standardSources.contains(name)) {
                if (target.contains(name)) {
                    //开始用新的PropertySource替换旧值
                    target.replace(name, source);
                }
                //
            }
        }
    }
    //
    return capture;
}

this.scope.refreshAll() 清空缓存的操作代码如下所示:

@Override
    public void destroy() {
        List<Throwable> errors = new ArrayList<Throwable>();
        //清空Refresh Scope 中的缓存
        Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
        //省略...
    }

为了验证每次配置刷新时,Bean 是新创建的,特意写了一个Demo 验证了下,如下所示:

Acm Properties: beijing-region
//刷新前
Object Instance is :com.alibaba.demo.normal.ConfigProperties@1be9634
2018-11-01 19:16:32.535  INFO 27254 --- [gPullingdefault] startup date [Thu Nov 01 19:16:32 CST 2018]; root of context hierarchy
Acm Properties: qingdao-region
//刷新后
Object Instance is :com.alibaba.demo.normal.ConfigProperties@2c6965e0

Spring Cloud Config 扩展Scope的核心类:RefreshScope

可以看到上面的代码中有 this.scope.refreshAll(),其中的scope就是RefreshScope。是用来存放scope类型为refresh类型的Bean(即使用RefreshScope注解标识的Bean),也就是说当一个Bean既不是singleton也不是prototype时,就会从自定义的Scope中去获取(Spring 允许自定义Scope),然后调用Scope的get方法来获取一个实例,Spring Cloud 正是扩展了Scope,从而控制了整个 Bean 的生命周期。当配置需要动态刷新的时候, 调用this.scope.refreshAll()这个方法,就会将整个RefreshScope的缓存清空,完成配置可动态刷新的可能。

更多关于Scope的分析请参考 这里

后续

关于ContextRefresh 和 RefreshScope的初始化配置是在RefreshAutoConfiguration类中完成的。而RefreshAutoConfiguration类初始化的入口是在spring-cloud-context中的META-INF/spring.factories中配置的。从而完成整个和动态刷新相关的Bean的初始化操作。

原文链接

© 著作权归作者所有

共有 人打赏支持
阿里云官方博客
粉丝 150
博文 672
码字总数 1510852
作品 0
杭州
程序员
私信 提问
Spring Cloud Config 规范

Spring Cloud Config 规范 首先Spring Cloud 是基于 Spring 来扩展的,Spring 本身就提供当创建一个Bean时可从Environment 中将一些属性值通过@Value的形式注入到业务代码中的能力。那Sprin...

中间件小哥
2018/12/12
0
0
白话SpringCloud | 第七章:分布式配置中心的使用

前言 介绍完服务的容错保护处理,接下来我们来了解下关于分布式配置中心的相关知识和使用。众所周知,随着项目的越来越多,日益庞大,每个子项目都会伴随着不同的配置项,于此也就多了很多的...

oKong
2018/10/10
0
0
Spring Cloud Config 基础示例

Spring Cloud Config 简介 什么是Srping Cloud Config? Spring Cloud Config 是一种分布式配置中心框架, 为分布式系统中的外部化配置提供服务器和客户端支持。(同类技术还有vault,zookeep...

lc_fly1
2018/11/27
0
0
SpringCloud配置中心高可用搭建

本文通过config server连接git仓库来实现配置中心,除了git还可以使用svn或者系统本地目录都行。 引入依赖 spring-cloud-config-server这个就是配置中心server的依赖。 配置中心做到高可用本...

Java技术栈
2018/05/22
0
0
Spring Cloud 大版本 Spring Cloud Edgware.SR2 发布

Spring Cloud Edgware.SR2 已发布,该版本可以在 Maven Central 找到,本次更新主要是对其包含的一些模块进行了升级,或查看 Edgware 发布说明以获取更多信息。 以下模块作为 Edgware.SR2 的...

局长
2018/02/10
5.5K
5

没有更多内容

加载失败,请刷新页面

加载更多

如何限制用户仅通过HTTPS方式访问OSS?

一、当前存在的问题 当前OSS支持用户使用HTTPS/HTTP协议访问Bucket。但由于HTTP存在安全漏洞。大型企业客户都要求使用HTTPS方式访问OSS,并且拒绝HTTP访问请求。 目前OSS可以通过RAM policy方...

阿里云官方博客
17分钟前
0
0
详解深度学习之经典网络架构——LeNet

一、基本简介 LeNet-5出自论文Gradient-Based Learning Applied to Document Recognition,是一种用于手写体字符识别的非常高效的卷积神经网络。 二、LeNet网络的基本结构 LeNet5 这个网络虽...

AI女神
20分钟前
0
0
日志服务Python消费组实战(二):实时分发数据

场景目标 使用日志服务的Web-tracking、logtail(文件极简)、syslog等收集上来的日志经常存在各种各样的格式,我们需要针对特定的日志(例如topic)进行一定的分发到特定的logstore中处理和...

阿里云云栖社区
21分钟前
1
0
LVM 增加磁盘扩容

sudo parted /dev/sdeparted> mklabel gptparted> mkpart primary lvm ext4 %0 %100parted> printsudo lvmlvm> pvcreate /dev/sde1lvm> vgextend vg-data /dev/sde1lvm> lve......

仪山湖
24分钟前
0
0
Linux挂载本地iso镜像,不联网使用yum命令

上传iso镜像文件到/mnt 目录下 在/mnt目录下创建Server目录 mkdir Server 备份 /etc/yum.repos.d/目录下的repo文件 cd /etc/yum.repos.dmkdir repobakmv *.repo repobak/ 挂载本地iso文件...

AustinYe
25分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部