歡迎您光臨本站 註冊首頁

apollo與springboot集成實現動態刷新配置的教程詳解

←手機掃碼閱讀     ml5rwbikls @ 2020-06-11 , reply:0

分佈式apollo簡介

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

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

https://github.com/ctripcorp/apollo

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

正文

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

1、普通字段刷新

a、pom.xml配置

   com.ctrip.framework.apolloapollo-client1.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.cloudspring-cloud-context2.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()) {    SetconditionalClasses = 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);   }     publicT getBean(String name) {   return (T) applicationContext.getBean(name);   }     publicT getBean(Classclz) {   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){   SetchangeKeys = 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 ListlistBasePackages(){   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  @Slf4j  public class RefreshBeanFactory implements BeanFactoryPostProcessor {     @Override   public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {   Config config = ConfigService.getConfig("order.properties");   ListbasePackages = AutoConfigurationPackages.get(configurableListableBeanFactory);   for (String basePackage : basePackages) {    SetconditionalClasses = 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){   SetpropertyNames = 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

 

                                                     

   


[ml5rwbikls ] apollo與springboot集成實現動態刷新配置的教程詳解已經有249次圍觀

http://coctec.com/docs/java/show-post-237972.html