热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

apollo与springboot集成实现动态刷新配置的教程详解

这篇文章主要介绍了apollo与springboot集成实现动态刷新配置,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

分布式apollo简介

Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。

本文主要介绍如何使用apollo与springboot实现动态刷新配置,如果之前不了解apollo可以查看如下文档

https://github.com/ctripcorp/apollo

学习了解一下apollo,再来查看本文

正文

apollo与spring实现动态刷新配置本文主要演示2种刷新,一种基于普通字段刷新、一种基于bean上使用了@ConfigurationProperties刷新

1、普通字段刷新

a、pom.xml配置

 
  com.ctrip.framework.apollo
  apollo-client
  1.6.0
 

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 = {"application","user.properties","product.properties","order.properties"})
public class ApolloApplication {

	public static void main(String[] args) {

		SpringApplication.run(ApolloApplication.class, args);
	}

}

@EnableApolloConfig不一定要加在启动类上,加在被spring管理的类上即可

d、在需刷新的字段上配置@Value注解,形如

 @Value("${hello}")
 private String hello;

通过以上三步就可以实现普通字段的动态刷新

2.bean使用@ConfigurationProperties动态刷新

bean使用@ConfigurationProperties注解目前还不支持自动刷新,得编写一定的代码实现刷新。目前官方提供2种刷新方案

  • 基于RefreshScope实现刷新
  • 基于EnvironmentChangeEvent实现刷新
  • 本文再提供一种,当bean上如果使用了@ConditionalOnProperty如何实现刷新

a、基于RefreshScope实现刷新

1、pom.xml要额外引入

 
  org.springframework.cloud
  spring-cloud-context
  2.0.3.RELEASE
 

2、bean上使用@RefreshScope注解

@Component
@ConfigurationProperties(prefix = "product")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@RefreshScope
public class Product {

 private Long id;

 private String productName;

 private BigDecimal price;

}

3、利用RefreshScope搭配@ApolloConfigChangeListener监听实现bean的动态刷新,其代码实现如下

@ApolloConfigChangeListener(value="product.properties",interestedKeyPrefixes = {"product."})
 private void refresh(ConfigChangeEvent changeEvent){

 refreshScope.refresh("product");

 PrintChangeKeyUtils.printChange(changeEvent);
 }

b、基于EnvironmentChangeEvent实现刷新

利用spring的事件驱动配合@ApolloConfigChangeListener监听实现bean的动态刷新,其代码如下

@Component
@Slf4j
public class UserPropertiesRefresh implements ApplicationContextAware {

 private ApplicationContext applicationContext;

 @ApolloConfigChangeListener(value="user.properties",interestedKeyPrefixes = {"user."})
 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 = "order.properties")
 private Config config;

 @ApolloConfigChangeListener(value="order.properties",interestedKeyPrefixes = {"order."},interestedKeys = {"model.isShowOrder"})
 private void refresh(ConfigChangeEvent changeEvent){
 for (String basePackage : listBasePackages()) {
  Set 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("registerBean->beanName:{},beanClass:{}",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 + ".";
  if(propertyName.startsWith(fieldPrefix)){
   String fieldName = propertyName.substring(fieldPrefix.length());
   String fieldVal = config.getProperty(propertyName,null);
   log.info("setBeanField-->fieldName:{},fieldVal:{}",fieldName,fieldVal);
   beanDefinition.getPropertyValues().add(fieldName,fieldVal);
  }
  }
 }
 }

 /**
 * bean移除
 * @param beanName
 */
 public void unregisterBean(String beanName){
 log.info("unregisterBean->beanName:{}",beanName);
 getBeanDefinitionRegistry().removeBeanDefinition(beanName);
 }

 public  T getBean(String name) {
 return (T) applicationContext.getBean(name);
 }

 public  T getBean(Class 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 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 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<&#63;> beanType = applicationContext.getType(beanName);
  System.out.println(beanType);
 }
 }
}

如果条件注解的值也是配置在apollo上,可能会出现依赖条件注解的bean的其他bean,在项目拉取apollo配置时,就已经注入spring容器中,此时就算条件注解满足条件,则引用该条件注解bean的其他bean,也会拿不到条件注解bean。此时有2种方法解决,一种是在依赖条件注解bean的其他bean注入之前,先手动注册条件注解bean到spring容器中,其核心代码如下

@Component
@Slf4j
public class RefreshBeanFactory implements BeanFactoryPostProcessor {

 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
 Config cOnfig= ConfigService.getConfig("order.properties");
 List basePackages = AutoConfigurationPackages.get(configurableListableBeanFactory);
 for (String basePackage : basePackages) {
  Set 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("registerBean->beanName:{},beanClass:{}",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 + ".";
  if(propertyName.startsWith(fieldPrefix)){
   String fieldName = propertyName.substring(fieldPrefix.length());
   String fieldVal = config.getProperty(propertyName,null);
   log.info("setBeanField-->fieldName:{},fieldVal:{}",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 propertyNames = config.getPropertyNames();
 if(!CollectionUtils.isEmpty(propertyNames) && propertyNames.contains(conditionalOnPropertyKey)){
  return true;
 }
 return false;
 }
}

其次利用懒加载的思想,在使用条件注解bean时,使用形如下方法

Order order = (Order) 
SpringContextUtils.getBean("order");

总结

本文主要介绍了常用的动态刷新,但本文的代码示例实现的功能不局限于此,本文的代码还实现如何通过自定义注解与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

到此这篇关于apollo与springboot集成实现动态刷新配置的文章就介绍到这了,更多相关apollo与springboot集成动态刷新配置内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • Git管理工具SourceTree安装与使用指南
    本文详细介绍了Git管理工具SourceTree的安装、配置及团队协作方案,旨在帮助开发者更高效地进行版本控制和项目管理。 ... [详细]
  • 本文探讨了Java编程的核心要素,特别是其面向对象的特性,并详细介绍了Java虚拟机、类装载器体系结构、Java类文件和Java API等关键技术。这些技术使得Java成为一种功能强大且易于使用的编程语言。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本文介绍了如何利用 Spring Boot 和 Groovy 构建一个灵活且可扩展的动态计算引擎,以满足钱包应用中类似余额宝功能的推广需求。我们将探讨不同的设计方案,并最终选择最适合的技术栈来实现这一目标。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 简化报表生成:EasyReport工具的全面解析
    本文详细介绍了EasyReport,一个易于使用的开源Web报表工具。该工具支持Hadoop、HBase及多种关系型数据库,能够将SQL查询结果转换为HTML表格,并提供Excel导出、图表显示和表头冻结等功能。 ... [详细]
  • 深入解析Serverless架构模式
    本文将详细介绍Serverless架构模式的核心概念、工作原理及其优势。通过对比传统架构,探讨Serverless如何简化应用开发与运维流程,并介绍当前主流的Serverless平台。 ... [详细]
  • 云计算的优势与应用场景
    本文详细探讨了云计算为企业和个人带来的多种优势,包括成本节约、安全性提升、灵活性增强等。同时介绍了云计算的五大核心特点,并结合实际案例进行分析。 ... [详细]
  • C#设计模式学习笔记:观察者模式解析
    本文将探讨观察者模式的基本概念、应用场景及其在C#中的实现方法。通过借鉴《Head First Design Patterns》和维基百科等资源,详细介绍该模式的工作原理,并提供具体代码示例。 ... [详细]
  • 本文详细介绍如何使用CSS自定义HTML5视频播放器的样式,涵盖常见属性及跨浏览器兼容性问题。发布时间:2020-09-14 14:46:29;来源:亿速云;阅读量:58;作者:小新。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 本文深入分析了 USDC 的稳定性和可能的救援措施,探讨了在硅谷银行破产后 USDC 面临的风险以及行业内的反应。 ... [详细]
  • 基于Node.js、Express、MongoDB和Socket.io的实时聊天应用开发
    本文详细介绍了使用Node.js、Express、MongoDB和Socket.io构建的实时聊天应用程序。涵盖项目结构、技术栈选择及关键依赖项的配置。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 嵌入式开发环境搭建与文件传输指南
    本文详细介绍了如何为嵌入式应用开发搭建必要的软硬件环境,并提供了通过串口和网线两种方式将文件传输到开发板的具体步骤。适合Linux开发初学者参考。 ... [详细]
author-avatar
小丑阿沁_134
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有