javascript
Spring Cloud Config 规范
Spring Cloud Config 規范
首先Spring Cloud 是基于 Spring 來擴展的,Spring 本身就提供當創建一個Bean時可從Environment 中將一些屬性值通過@Value的形式注入到業務代碼中的能力。那Spring Cloud Config 要解決的問題就是:
要解決以上三個問題:Spring Cloud Config 規范中剛好定義了核心的三個接口:
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 duplicatesList<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中獲取 propertySourcesMutablePropertySources 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的方法主要做了兩件事:
這兩個操作所對應的代碼如下所示:
?
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() 清空緩存的操作代碼如下所示:
@Overridepublic 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@2c6965e0Spring 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的初始化操作。
?
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的Spring Cloud Config 规范的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 直面PHP微服务架构挑战
- 下一篇: 阿里云明确生态边界:不做SaaS、被集成