apollo與springboot集成實現動態刷新配置

2020-09-03 linyb極客之路

分布式apollo簡介

Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,能夠集中化管理應用不同環境、不同集群的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性。

本文主要介紹如何使用apollo與springboot實現動態刷新配置,如果之前不了解apollo可以查看如下文檔

https://github.com/ctripcorp/apollo

學習了解一下apollo,再來查看本文

正文

apollo與spring實現動態刷新配置本文主要演示2種刷新,一種基於普通欄位刷新、一種基於bean上使用了@ConfigurationProperties刷新

1、普通欄位刷新

a、pom.xml配置

<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.6.0</version> </dependency>

b、客戶端配置AppId,Apollo Meta Server

此配置有多種方法,本示例直接在application.yml配置,配置內容如下

app: id: ${spring.application.name}apollo: meta: http://192.168.88.128:8080,http://192.168.88.129:8080 bootstrap: enabled: true eagerLoad: enabled: true

c、項目中啟動類上加上@EnableApolloConfig註解,形如下

@SpringBootApplication@EnableApolloConfig(value = {&34;,&34;,&34;,&34;})public class ApolloApplication { public static void main(String[] args) { SpringApplication.run(ApolloApplication.class, args); }}

==@EnableApolloConfig不一定要加在啟動類上,加在被spring管理的類上即可==

d、在需刷新的欄位上配置@Value註解,形如

@Value(&34;) private String hello;

通過以上三步就可以實現普通欄位的動態刷新

2.bean使用@ConfigurationProperties動態刷新

bean使用@ConfigurationProperties註解目前還不支持自動刷新,得編寫一定的代碼實現刷新。目前官方提供2種刷新方案

  • 基於RefreshScope實現刷新
  • 基於EnvironmentChangeEvent實現刷新
  • 本文再提供一種,當bean上如果使用了@ConditionalOnProperty如何實現刷新

a、基於RefreshScope實現刷新

1、pom.xml要額外引入

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> <version>2.0.3.RELEASE</version> </dependency>

2、bean上使用@RefreshScope註解

@Component@ConfigurationProperties(prefix = &34;)@Data@AllArgsConstructor@NoArgsConstructor@Builder@RefreshScopepublic class Product { private Long id; private String productName; private BigDecimal price;}

3、利用RefreshScope搭配@ApolloConfigChangeListener監聽實現bean的動態刷新,其代碼實現如下

@ApolloConfigChangeListener(value=&34;,interestedKeyPrefixes = {&34;}) private void refresh(ConfigChangeEvent changeEvent){ refreshScope.refresh(&34;); PrintChangeKeyUtils.printChange(changeEvent); }

b、基於EnvironmentChangeEvent實現刷新

利用spring的事件驅動配合@ApolloConfigChangeListener監聽實現bean的動態刷新,其代碼如下

@Component@Slf4jpublic class UserPropertiesRefresh implements ApplicationContextAware { private ApplicationContext applicationContext; @ApolloConfigChangeListener(value=&34;,interestedKeyPrefixes = {&34;}) private void refresh(ConfigChangeEvent changeEvent){ applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); PrintChangeKeyUtils.printChange(changeEvent); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}

c、當bean上有@ConditionalOnProperty如何實現刷新

當bean上有@ConditionalOnProperty註解時,上述的兩種方案可以說失效了,因為@ConditionalOnProperty是一個條件註解,當不滿足條件註解時,bean是沒法註冊到spring容器中的。如果我們要實現此種情況的下的動態刷新,我們就得自己手動註冊或者銷毀bean了。其實現流程如下

1、當滿足條件註解時,則手動創建bean,然後配合@ApolloConfigChangeListener監聽該bean的屬性變化。當該bean屬性有變化時,手動把屬性注入bean。同時刷新依賴該bean的其他bean

2、當不滿足條件註解時,則手動從spring容器中移除bean,同時刷新依賴該bean的其他bean

其刷新核心代碼如下

public class OrderPropertiesRefresh implements ApplicationContextAware { private ApplicationContext applicationContext; @ApolloConfig(value = &34;) private Config config; @ApolloConfigChangeListener(value=&34;,interestedKeyPrefixes = {&34;},interestedKeys = {&34;}) private void refresh(ConfigChangeEvent changeEvent){ for (String basePackage : listBasePackages()) { Set<Class> conditionalClasses = ClassScannerUtils.scan(basePackage, ConditionalOnProperty.class); if(!CollectionUtils.isEmpty(conditionalClasses)){ for (Class conditionalClass : conditionalClasses) { ConditionalOnProperty conditionalOnProperty = (ConditionalOnProperty) conditionalClass.getAnnotation(ConditionalOnProperty.class); String[] conditionalOnPropertyKeys = conditionalOnProperty.name(); String beanChangeCondition = this.getChangeKey(changeEvent,conditionalOnPropertyKeys); String conditionalOnPropertyValue = conditionalOnProperty.havingValue(); boolean isChangeBean = this.changeBean(conditionalClass, beanChangeCondition, conditionalOnPropertyValue); if(!isChangeBean){ // 更新相應的bean的屬性值,主要是存在@ConfigurationProperties註解的bean applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); } } } } PrintChangeKeyUtils.printChange(changeEvent); printAllBeans(); } /** * 根據條件對bean進行註冊或者移除 * @param conditionalClass * @param beanChangeCondition bean發生改變的條件 * @param conditionalOnPropertyValue */ private boolean changeBean(Class conditionalClass, String beanChangeCondition, String conditionalOnPropertyValue) { boolean isNeedRegisterBeanIfKeyChange = this.isNeedRegisterBeanIfKeyChange(beanChangeCondition,conditionalOnPropertyValue); boolean isNeedRemoveBeanIfKeyChange = this.isNeedRemoveBeanIfKeyChange(beanChangeCondition,conditionalOnPropertyValue); String beanName = StringUtils.uncapitalize(conditionalClass.getSimpleName()); if(isNeedRegisterBeanIfKeyChange){ boolean isAlreadyRegisterBean = this.isExistBean(beanName); if(!isAlreadyRegisterBean){ this.registerBean(beanName,conditionalClass); return true; } }else if(isNeedRemoveBeanIfKeyChange){ this.unregisterBean(beanName); return true; } return false; } /** * bean註冊 * @param beanName * @param beanClass */ public void registerBean(String beanName,Class beanClass) { log.info(&34;,beanName,beanClass); BeanDefinitionBuilder beanDefinitionBurinilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); BeanDefinition beanDefinition = beanDefinitionBurinilder.getBeanDefinition(); setBeanField(beanClass, beanDefinition); getBeanDefinitionRegistry().registerBeanDefinition(beanName,beanDefinition); } /** * 設置bean欄位值 * @param beanClass * @param beanDefinition */ private void setBeanField(Class beanClass, BeanDefinition beanDefinition) { ConfigurationProperties configurationProperties = (ConfigurationProperties) beanClass.getAnnotation(ConfigurationProperties.class); if(ObjectUtils.isNotEmpty(configurationProperties)){ String prefix = configurationProperties.prefix(); for (String propertyName : config.getPropertyNames()) { String fieldPrefix = prefix + &34;; if(propertyName.startsWith(fieldPrefix)){ String fieldName = propertyName.substring(fieldPrefix.length()); String fieldVal = config.getProperty(propertyName,null); log.info(&34;,fieldName,fieldVal); beanDefinition.getPropertyValues().add(fieldName,fieldVal); } } } } /** * bean移除 * @param beanName */ public void unregisterBean(String beanName){ log.info(&34;,beanName); getBeanDefinitionRegistry().removeBeanDefinition(beanName); } public <T> T getBean(String name) { return (T) applicationContext.getBean(name); } public <T> T getBean(Class<T> clz) { return (T) applicationContext.getBean(clz); } public boolean isExistBean(String beanName){ return applicationContext.containsBean(beanName); } public boolean isExistBean(Class clz){ try { Object bean = applicationContext.getBean(clz); return true; } catch (BeansException e) { // log.error(e.getMessage(),e); } return false; } private boolean isNeedRegisterBeanIfKeyChange(String changeKey,String conditionalOnPropertyValue){ if(StringUtils.isEmpty(changeKey)){ return false; } String apolloConfigValue = config.getProperty(changeKey,null); return conditionalOnPropertyValue.equals(apolloConfigValue); } private boolean isNeedRemoveBeanIfKeyChange(String changeKey,String conditionalOnPropertyValue){ if(!StringUtils.isEmpty(changeKey)){ String apolloConfigValue = config.getProperty(changeKey,null); return !conditionalOnPropertyValue.equals(apolloConfigValue); } return false; } private boolean isChangeKey(ConfigChangeEvent changeEvent,String conditionalOnPropertyKey){ Set<String> changeKeys = changeEvent.changedKeys(); if(!CollectionUtils.isEmpty(changeKeys) && changeKeys.contains(conditionalOnPropertyKey)){ return true; } return false; } private String getChangeKey(ConfigChangeEvent changeEvent, String[] conditionalOnPropertyKeys){ if(ArrayUtils.isEmpty(conditionalOnPropertyKeys)){ return null; } String changeKey = null; for (String conditionalOnPropertyKey : conditionalOnPropertyKeys) { if(isChangeKey(changeEvent,conditionalOnPropertyKey)){ changeKey = conditionalOnPropertyKey; break; } } return changeKey; } private BeanDefinitionRegistry getBeanDefinitionRegistry(){ ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) applicationContext; BeanDefinitionRegistry beanDefinitionRegistry = (DefaultListableBeanFactory) configurableContext.getBeanFactory(); return beanDefinitionRegistry; } private List<String> listBasePackages(){ ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) applicationContext; return AutoConfigurationPackages.get(configurableContext.getBeanFactory()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public void printAllBeans() { String[] beans = applicationContext.getBeanDefinitionNames(); Arrays.sort(beans); for (String beanName : beans) { Class<?> beanType = applicationContext.getType(beanName); System.out.println(beanType); } }}

如果條件註解的值也是配置在apollo上,可能會出現依賴條件註解的bean的其他bean,在項目拉取apollo配置時,就已經注入spring容器中,此時就算條件註解滿足條件,則引用該條件註解bean的其他bean,也會拿不到條件註解bean。此時有2種方法解決,一種是在依賴條件註解bean的其他bean注入之前,先手動註冊條件註解bean到spring容器中,其核心代碼如下

@Component@Slf4jpublic class RefreshBeanFactory implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { Config config = ConfigService.getConfig(&34;); List<String> basePackages = AutoConfigurationPackages.get(configurableListableBeanFactory); for (String basePackage : basePackages) { Set<Class> conditionalClasses = ClassScannerUtils.scan(basePackage, ConditionalOnProperty.class); if(!CollectionUtils.isEmpty(conditionalClasses)){ for (Class conditionalClass : conditionalClasses) { ConditionalOnProperty conditionalOnProperty = (ConditionalOnProperty) conditionalClass.getAnnotation(ConditionalOnProperty.class); String[] conditionalOnPropertyKeys = conditionalOnProperty.name(); String beanConditionKey = this.getConditionalOnPropertyKey(config,conditionalOnPropertyKeys); String conditionalOnPropertyValue = conditionalOnProperty.havingValue(); this.registerBeanIfMatchCondition((DefaultListableBeanFactory)configurableListableBeanFactory,config,conditionalClass,beanConditionKey,conditionalOnPropertyValue); } } } } private void registerBeanIfMatchCondition(DefaultListableBeanFactory beanFactory,Config config,Class conditionalClass, String beanConditionKey, String conditionalOnPropertyValue) { boolean isNeedRegisterBean = this.isNeedRegisterBean(config,beanConditionKey,conditionalOnPropertyValue); String beanName = StringUtils.uncapitalize(conditionalClass.getSimpleName()); if(isNeedRegisterBean){ this.registerBean(config,beanFactory,beanName,conditionalClass); } } public void registerBean(Config config,DefaultListableBeanFactory beanFactory, String beanName, Class beanClass) { log.info(&34;,beanName,beanClass); BeanDefinitionBuilder beanDefinitionBurinilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); BeanDefinition beanDefinition = beanDefinitionBurinilder.getBeanDefinition(); setBeanField(config,beanClass, beanDefinition); beanFactory.registerBeanDefinition(beanName,beanDefinition); } private void setBeanField(Config config,Class beanClass, BeanDefinition beanDefinition) { ConfigurationProperties configurationProperties = (ConfigurationProperties) beanClass.getAnnotation(ConfigurationProperties.class); if(ObjectUtils.isNotEmpty(configurationProperties)){ String prefix = configurationProperties.prefix(); for (String propertyName : config.getPropertyNames()) { String fieldPrefix = prefix + &34;; if(propertyName.startsWith(fieldPrefix)){ String fieldName = propertyName.substring(fieldPrefix.length()); String fieldVal = config.getProperty(propertyName,null); log.info(&34;,fieldName,fieldVal); beanDefinition.getPropertyValues().add(fieldName,fieldVal); } } } } public boolean isNeedRegisterBean(Config config,String beanConditionKey,String conditionalOnPropertyValue){ if(StringUtils.isEmpty(beanConditionKey)){ return false; } String apolloConfigValue = config.getProperty(beanConditionKey,null); return conditionalOnPropertyValue.equals(apolloConfigValue); } private String getConditionalOnPropertyKey(Config config, String[] conditionalOnPropertyKeys){ if(ArrayUtils.isEmpty(conditionalOnPropertyKeys)){ return null; } String changeKey = null; for (String conditionalOnPropertyKey : conditionalOnPropertyKeys) { if(isConditionalOnPropertyKey(config,conditionalOnPropertyKey)){ changeKey = conditionalOnPropertyKey; break; } } return changeKey; } private boolean isConditionalOnPropertyKey(Config config,String conditionalOnPropertyKey){ Set<String> propertyNames = config.getPropertyNames(); if(!CollectionUtils.isEmpty(propertyNames) && propertyNames.contains(conditionalOnPropertyKey)){ return true; } return false; }}

其次利用懶加載的思想,在使用條件註解bean時,使用形如下方法

Order order = (Order) SpringContextUtils.getBean(&34;);

總結

本文主要介紹了常用的動態刷新,但本文的代碼示例實現的功能不局限於此,本文的代碼還實現如何通過自定義註解與apollo整合來實現一些業務操作,同時也實現了基於hystrix註解與apollo整合,實現基於線程隔離的動態熔斷,感興趣的朋友可以複製文末連結到瀏覽器,進行查看

apollo基本上是能滿足我們日常的業務開發要求,但是對於一些需求,比如動態刷新線上資料庫資源啥,我們還是得做一定的量的改造,好在攜程也提供了apollo-use-cases,在裡面可以找到常用的使用場景以及示例代碼,其連結如下

https://github.com/ctripcorp/apollo-use-cases

感興趣的朋友,可以查看下。

demo連結

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apollo

相關焦點

  • Kitty中的動態線程池支持Nacos,Apollo多配置中心了
    目錄回顧昨日nacos集成Spring Cloud Alibaba 方式Nacos Spring Boot 方式Apollo集成自研配置中心對接無配置中心對接實現源碼分析兼容Apollo和Nacos NoClassDefFoundErrorApollo
  • 攜程Apollo開源配置管理平臺詳解
    apollo項目基於springboot與springcloud,可以獨立部署Apollo GitHub地址:https://github.com/ctripcorp/apollo1、服務端部署:參考文檔:https://github.com/ctripcorp/apollo/wiki/Quick-Start可以進行
  • Apollo 配置中心安裝&使用
    Apollo 集群部署Apollo 的使用(Java)持續更新使用案例拿spring boot 項目簡單直白地說一下配置刷新的原理;spring boot 項目都有一個 application.yml/properties 的應用配置文件,裡面寫了各種配置
  • SpringBoot+GitLab+Docker+Jenkins實現持續集成下
    JenkinsJenkins是一個開源軟體項目,是基於Java開發的一種持續集成工具,用於監控持續重複的工作,旨在提供一個開放易用的軟體平臺,使軟體的持續集成變成可能。6.1. 安裝Jenkins6.1.1.
  • 3千字Apollo配置中心的總結,讓配置「智能」起來
    後面為了做到動態讀取配置信息,後面有人改進一下把配置信息存儲在資料庫的一張表,程序讀取表中的配置信息,這種方式很多公司都還在使用,因為簡單,而且靈活(修改配置只需要執行個SQL語句,不需要重新部署發布)。
  • NET Core 下使用 Apollo 配置中心
    Apollo(阿波羅)是攜程框架部門研發的分布式配置中心,能夠集中化管理應用不同環境、不同集群的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性,適用於微服務配置管理場景。服務端基於Spring Boot和Spring Cloud開發,打包後可以直接運行,不需要額外安裝Tomcat等應用容器。
  • springboot nacos使用配置
    前言隨著框架的不斷更新迭代,springboot、springcloud使用率越來越高,註冊中心、動態配置等成為日常開發中一項重要的存在,nacos這款外部應用,侵入性小的組件也越來被開發者所接受,今天小編來盤盤它☺
  • .NET Core+K8S+Apollo玩轉配置中心
    ,能夠集中化管理應用不同環境、不同集群的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性,適用於微服務配置管理場景。因此本文接下來將主要來介紹如何基於Helm快速部署Apollo集群至K8S,並與.NET Core應用進行集成,同時介紹下如何平滑遷移配置到Apollo。本文具有詳細的部署步驟,建議動手實操。部署Chart包和Demo已上傳至GitHub:K8S.NET.Apollo,可收藏備用。
  • SpringBoot集成Mybatis
    --mybatis集成SpringBoot框架起步依賴--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId
  • springboot jwt redis實現token刷新
    配合redis,就可以輕鬆的解決上面兩個問題token的續約伺服器主動失效指定的token接下來,我會演示一個實現Demo初始化一個工程<parent> <groupId>org.springframework.boot
  • 灰度實戰(三):Apollo配置中心(3)
    (2)》中講解了Apollo如何動態更改程序中通過@value配置值,在本篇博文中為大家帶來如何通過Apollo動態更新程序和中間件的連接。【實時推送演示】一、程序和第三方組件連接動態更改(在此以連接redis為例)---失敗版1、演示代碼(增加redis操作)package com.zhanghan.grayapollo.controller;import com.zhanghan.grayapollo.util.wrapper.WrapMapper
  • springboot+activiti+angular 集成activiti工作流實現,源碼分享
    springboot+activiti+angular 這是spring boot框架集成activiti工作流實現,採用目前流行的restful api接口調用,前端使用angular js框架實現關注轉之後私信回復【源碼】即可免費獲取到!
  • mybatis最全教程之springboot集成mybatis
    那麼首先我們要做的就是與spring進行集成。本節課使用springboot與mybatis進行集成。本文假設您已經新建好了springboot工程,那麼接下來我們直接做集成配置。1、在pom.xml中加入如下配置:
  • springboot+activiti+angular 集成activiti工作流實現,源碼分享
    springboot+activiti+angular 這是spring boot框架集成activiti工作流實現,採用目前流行的restful api接口調用,前端使用angular js框架實現關注轉之後私信回復【源碼】
  • springboot支付項目之springboot集成jpa
    springboot集成spring-jpa本文主要內容:1:spring boot怎麼集成spring-jpa以及第一個jpa查詢示例如jpa幾個常用註解、lombok註解使用2:怎麼設置idea中在pom中添加依賴的時候自動聯想。
  • 基礎:Springboot集成Druid
    作者 | 羅曉崢來源 | urlify.cn/M3qu6r1.Druid簡介java程序很大一部分要操作資料庫,為了提高性能操作資料庫的時候,又不得不使用資料庫連接池Druid是阿里巴巴開源平臺上一個資料庫連接池實現,結合了C3P0、DBCP等DB池的優點
  • 灰度實戰(二):Apollo配置中心(2)
    ,在本篇博文中為大家帶來程序如何通過Apollo讀取配置文件,以及配置中心一大亮點---配置修改後實時推送到應用端。;/version></dependency>2、啟動配置文件(application.yml)修改為(參數項說明參考:Java客戶端使用實例)app: id: ${app_ip:gray_apollo}apollo: cluster: ${apollo_cluster:default} bootstrap:
  • 基於ZooKeeper實現的一個配置中心系統,可動態發布配置 源碼分享
    主要是為了解決分布式系統中配置雜亂,無法集中管理,和頻繁修改配置項後需要重新發布服務的問題。目前提供了「全量/灰度發布、客戶端實例配置項訂閱情況實時監控、發布回滾、操作歷史日誌、配置項監聽、權限控制、多環境切換(開發、測試、預發、生產)"等功能。
  • Apollo配置中心源碼編譯及搭建
    Apollo(阿波羅)是攜程框架部門研發的分布式配置中心,能夠集中化管理應用不同環境、不同集群的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性,適用於微服務配置管理場景。服務端基於Spring Boot和Spring Cloud開發,打包後可以直接運行,不需要額外安裝Tomcat等應用容器。目前從官網看Apollo提供了Java和.Net的客戶端接入sdk。
  • Springboot + nacos 實現註冊中心和配置中心
    代碼實現spring-cloud-starter-alibaba-nacos-discovery</artifactId>            <version>2.2.1.RELEASE</version></dependency>操作springboot