从 Spring 的环境到 Spring Cloud 的配置

admin

 

需要

无声无休,web 开发已经进入 “微服务”、”分布式” 的时代,致力于挑供通用 Java 开发解决方案的 Spring 自然不甘人后,挑出了 Spring Cloud 来扩大 Spring 在微服务方面的影响,也取得了市场的认可,在吾们的营业中也有行使。

前些天,吾在一个需要中也遇到了 spring cloud 的有关题目。吾们在用的是 Spring Cloud 的 config 模块,它是用来声援分布式配置的,正本单机配置在行使了 Spring Cloud 之后,能够声援第三方存储配置和配置的动态修改和重新添载,本身在营业代码里实现配置的重新添载,Spring Cloud 将整个流程抽离为框架,并很益的融入到 Spring 原有的配置和 Bean 模块内。

固然在解决需要题目时走了些曲路,但也借此机会晓畅了 Spring Cloud 的一片面,抽空总结一下题目和在查询题目中晓畅到的知识,分享出来让再遇到此题目的同学少踩坑吧。

背景和题目

吾们的服务正本有一批单机的配置,原由联相符 key 的配置太长,于是将其配置为数组的样式,并行使 Spring Boot 的 @ConfigurationProperties 和 @Value 注明来解析为 Bean 属性。

properties 文件配置像:

test.config.elements[0]=value1  test.config.elements[1]=value2  test.config.elements[2]=value3 

在行使时:

@ConfigurationProperties(prefix="test.config")  Class Test{      @Value("${#elements}")      private String[] elements;  } 

云云,Spring 会对 Test 类自动注入,将数组 [value1,value2,value3] 注入到 elements 属性内。

而吾们行使 Spring Cloud 自动添载配置的姿势是云云:

@RefreshScope  class Test{      @Value("${test.config.elements}")      private String[] elements;  } 

行使 @RefreshScope 注明的类,在环境变量有转折后会自动重新添载,将最新的属性注入到类属性内,但它却不声援数组的自动注入。

而吾的现在标是能找到一栽手段,使其即声援注入数组类型的属性,又能行使 Spring Cloud 的自动刷新配置的特性。

环境和属性

不论Spring Cloud 的特性如何特出,在 Spring 的地盘,照样要入乡顺俗,和 Spring 的基础组件打成一片。以是为了晓畅整个流程,吾们就要先晓畅 Spring 的基础。

Spring 是一个大容器,它不但存储 Bean 和其中的倚赖,还存储着整个行使内的配置,相对于 BeanFactory 存储着各栽 Bean,Spring 管理环境配置的容器就是 Environment,从 Environment 内,吾们能按照 key 获取一切配置,还能按照迥异的场景(Profile,如 dev,test,prod)来切换配置。

但 Spring 管理配置的最幼单位并不是属性,而是 PropertySource (属性源),吾们能够理解 PropertySource 是一个文件,或是某张配置数据外,Spring 在 Environment 内维护一个 PropertySourceList,当吾们获取配置时,Spring 从这些 PropertySource 内查找到对答的值,并行使 ConversionService 将值转换为对答的类型返回。

Spring Cloud 配置刷新机制

分布式配置

Spring Cloud 内挑供了 PropertySourceLocator 接口来对接 Spring 的 PropertySource 系统,议定 PropertySourceLocator,吾们就拿到一个”自定义”的 PropertySource,Spring Cloud 里还有一个实现 ConfigServicePropertySourceLocator,议定它,吾们能够定义一个长途的 ConfigService,议定公用这个 ConfigService 来实现分布式的配置服务。

从 ConfigClientProperties 这个配置类吾们能够望得出来,它也为长途配置预设了用户名暗号等坦然限制选项,还有 label 用来区分服务池等配置。

scope 配置刷新

长途配置有了,接下来就是对转折的监测和基于配置转折的刷新。

Spring Cloud 挑供了 ContextRefresher 来协助吾们实现环境的刷新,其主要逻辑在 refreshEnvironment 手段和 scope.refreshAll() 手段,吾们睁开来望。

吾们先来望 spring cloud 声援的 scope.refreshAll 手段。

public void refreshAll() {   super.destroy();   this.context.publishEvent(new RefreshScopeRefreshedEvent());  } 

scope.refreshAll 则更”强横”一些,直接烧毁了 scope,并发布了一个 RefreshScopeRefreshedEvent 事件,scope 的烧毁会导致 scope 内(被 RefreshScope 注明)一切的 bean 都会被烧毁。而这些被强制竖立为 lazyInit 的 bean 再次创建时,也就完善了新配置的重新添载。

ConfigurationProperties 配置刷新

然后再回过头来望 refreshEnvironment 手段。

Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());    addConfigFilesToEnvironment();    Set<String> keys = changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet();    this.context.publishEvent(new EnvironmentChangeEvent(context, keys));    return keys; 

它读取了环境内一切 PropertySource 内的配置后,重新创建了一个 SpringApplication 以刷新配置,再次读取一切配置项并得到与前线保存的配置项的对比,末了将前后配置差发布了一个 EnvironmentChangeEvent 事件。

而 EnvironmentChangeEvent 的监听器是由 ConfigurationPropertiesRebinder 实现的,其主要逻辑在 rebind 手段。

Object bean = this.applicationContext.getBean(name);  if (AopUtils.isAopProxy(bean)) {   bean = ProxyUtils.getTargetObject(bean);  }  if (bean != null) {  this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);               this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);   return true; 

能够望到它的处理逻辑,就是把其内部存储的 ConfigurationPropertiesBeans 挨次实走烧毁逻辑,再实走初首化逻辑实现属性的重新绑定。

这边能够清新,Spring Cloud 在进走配置刷新时是考虑过 ConfigurationProperties 的,经过测试,在 ContextRefresher 刷新上下文后,ConfigurationProperties 注明类的属性是会进走动态刷新的。

测试一次就解决的事情,感觉有些白忙活了。。不过既然查到这边了,就再去下深入一些。

Bean 的创建与环境

接着吾们再来望一下,环境里的属性都是怎么在 Bean 创建时被行使的。

吾们清新,Spring 的 Bean 都是在 BeanFactory 内创建的,创建逻辑的入口在 AbstractBeanFactory.doGetBean(name, requiredType, args, false) 手段,而详细实现在 AbstractAutowireCapableBeanFactory.doCreateBean 手段内,在这个手段里,实现了 Bean 实例的创建、属性填充、初首化手段调用等逻辑。

在这边,有一个专门复杂的步骤就是调用全局的 BeanPostProcessor,这个接口是 Spring 为 Bean 创建准备的勾子接口,实现这个接口的类能够对 Bean 创建时的操作进走修改。它是一个专门主要的接口,是吾们精干涉 Spring Bean 创建流程的主要入口。

吾们要说的是它的一栽详细实现 ConfigurationPropertiesBindingPostProcessor,它议定调用链 ConfigurationPropertiesBinder.bind() --> Binder.bindObject() --> Binder.findProperty() 手段查找环境内的属性。

private ConfigurationProperty findProperty(ConfigurationPropertyName name,     Context context) {    if (name.isEmpty()) {     return null;    }    return context.streamSources()      .map((source) -> source.getConfigurationProperty(name))      .filter(Objects::nonNull).findFirst().orElse(null);   } 

找到对答的属性后,再行使 converter 将属性转换为对答的类型注入到 Bean 骨。

private <T> Object bindProperty(Bindable<T> target, Context context,    ConfigurationProperty property) {   context.setConfigurationProperty(property);   Object result = property.getValue();   result = this.placeholdersResolver.resolvePlaceholders(result);   result = context.getConverter().convert(result, target);   return result;  } 
一栽 trick 手段

由上面能够望到,Spring 是声援 @ConfigurationProperties 属性的动态修改的,但在查询流程时,吾也找到了一栽比较 trick 的手段。

吾们先来清理动态属性注入的关键点,再从这些关键点里找可修改点。

 PropertySourceLocator 将 PropertySource 从长途数据源引入,倘若这时吾们能修改数据源的效果就能达到主意,可是 Spring Cloud 的长途资源定位器 ConfigServicePropertySourceLocator 和 长途调用工具 RestTemplate 都是实现类,倘若生硬地对其继承并修改,代码很不优雅。  Bean 创建时会挨次行使 BeanPostProcessor 对上下文进走操作。这时增补一个 BeanPostProcessor,能够手动实现对 Bean 属性的修改。但这栽手段 实现首来很复杂,而且原由每一个 BeanPostProcessor 在一切 Bean 创建时都会调用,能够会有坦然题目。  Spring 会在解决类属性注时兴,行使 PropertyResolver 将配置项解析为类属性指定的类型。这时候增补属性解析器 PropertyResolver 或类型转换器 ConversionService 能够插手属性的操作。但它们都只负责处理一个属性,原由吾的现在标是”众个”属性变成一个属性,它们也无能为力。

吾这边能想到的手段是借用 Spring 自动注入的能力,把 Environment Bean 注入到某个类中,然后在类的初首化手段里对 Environment 内的 PropertySource 里进走修改,也能够达成主意,这边贴一下假代码。

@Component  @RefreshScope  // 借用 Spring Cloud 实现此 Bean 的刷新  public class ListSupportPropertyResolver {      @Autowired     ConfigurableEnvironment env; // 将环境注入到 Bean 内是修改环境的主要前挑      @PostConstruct      public void init() {          // 将属性键值对从环境内掏出          Map<String, Object> properties = extract(env.getPropertySources());          // 解析环境里的数组,抽掏出其中的数组配置          Map<String, List<String>> listProperties = collectListProperties(properties)          Map<String, Object> propertiesMap = new HashMap<>(listProperties);          MutablePropertySources propertySources = env.getPropertySources();          // 把数组配置生成一个 PropertySource 并放到环境的 PropertySourceList 内          propertySources.addFirst(new MapPropertySource("modifiedProperties", propertiesMap));      }  } 

云云,在创建 Bean 时,就能第一优先级行使吾们修改过的 PropertySource 了。

自然了,有了比较”正途”的手段后,吾们不消要对 PropertySource 进走修改,毕竟全局修改等于未知风险或埋坑。

幼结

查找答案的过程中,吾更深切地理解到 Environment、BeanFactory 这些才是 Spring 的基石,框架挑供的各栽花式功能都是基于它们实现的,对这些知识的掌握,对于理解它外现出来的高级特性很有协助,之后再查找框架题目也会更有倾向。

鸿蒙官方战略配相符共建——HarmonyOS技术社区


Powered by 2021十大排行最污直播-日韩4438x全国最大-十大污的软 @2018 RSS地图 HTML地图

Copyright 站群 © 2013-2021 365建站器 版权所有