zoukankan      html  css  js  c++  java
  • apollo与springboot集成实现动态刷新配置

    分布式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 = {"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要额外引入

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

    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<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("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> 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
    @Slf4j
    public class RefreshBeanFactory implements BeanFactoryPostProcessor {
    
    
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            Config config = ConfigService.getConfig("order.properties");
            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("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<String> 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

  • 相关阅读:
    迭代器与生成器
    函数
    Java多线程
    JVM垃圾回收
    JVM内存模型
    面向对象的特征和原则
    Java代码规范
    安装yum
    虚机ping:www.baidu.com报错
    创建好centos7虚拟机之后连xshell连不上虚机
  • 原文地址:https://www.cnblogs.com/linyb-geek/p/13059720.html
Copyright © 2011-2022 走看看