手写分布式配置中心(六)整合springboot(自动刷新)

对于springboot配置自动刷新,原理也很简单,就是在启动过程中用一个BeanPostProcessor去收集需要自动刷新的字段,然后在springboot启动后开启轮询任务即可。
不过需要对之前的代码再次做修改,因为springboot的配置注入@value("${}"),允许多个${}和嵌套,所以不能确定简单的确定用到了那个配置,本文为了简单就把所有的配置都认为需要动态刷新,实际用的时候可以在application.yml中配置需要动态刷新的配置id列表。代码在https://gitee.com/summer-cat001/config-center。其中设计到的原理都在之前的一篇文章中,感兴趣可以去看看springboot配置注入增强(二)属性注入的原理_springboot bean属性增强-CSDN博客

新增注解

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigRefresh {
}

加上这个注解的字段并且字段上有@value注解就会自动刷新

收集自动刷新的字段

这里会收集自动刷新的字段,并加到ConfigCenterClient的refreshFieldValueList中。长轮询会从这里取数据进行对比,如果发生变化就更新bean中的字段

@Slf4j
public class ConfigRefreshAnnotationBeanPostProcessor implements ApplicationRunner, BeanPostProcessor, BeanFactoryAware, EnvironmentAware {

    private Environment environment;

    private ConfigurableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {

        if (!(beanFactory instanceof ConfigurableBeanFactory)) {
            log.warn("ConfigurableBeanFactory requires a ConfigurableListableBeanFactory");
            return;
        }
        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, final String beanName) throws BeansException {
        if (beanFactory != null) {
            ReflectionUtils.doWithFields(bean.getClass(), field -> {
                try {
                    ConfigRefresh configRefresh = AnnotationUtils.getAnnotation(field, ConfigRefresh.class);
                    if (configRefresh == null) {
                        return;
                    }
                    Value valueAnnotation = AnnotationUtils.getAnnotation(field, Value.class);
                    if (valueAnnotation == null) {
                        return;
                    }
                    String value = valueAnnotation.value();
                    String relValue = beanFactory.resolveEmbeddedValue(value);

                    ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
                    configCenterClient.addRefreshFieldValue(bean, field, relValue);
                } catch (Exception e) {
                    log.error("set bean field fail,beanName:{},fieldName:{}", bean.getClass().getName(), field.getName(), e);
                }
            });
        }
        return bean;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void run(ApplicationArguments args) {
        ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
        configCenterClient.startSpringBootLongPolling((ConfigurableEnvironment) environment, beanFactory);
    }
}

把该bean注入到springboot中,即在spring.factories中加入自动注入

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.config.center.autoconfigure.ConfigAutoConfiguration

这是一个ImportSelector会自动注入返回的类

@Import(ConfigAutoConfiguration.class)
public class ConfigAutoConfiguration implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ConfigRefreshAnnotationBeanPostProcessor.class.getName()};
    }
}

启动长轮询

springboot启动完成后会发一个ApplicationRunner事件,我们只要在实现这个接口的bean中启动即可

@Override
    public void run(ApplicationArguments args) {
        ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
        configCenterClient.startSpringBootLongPolling((ConfigurableEnvironment) environment, beanFactory);
    }
    public void startSpringBootLongPolling(ConfigurableEnvironment environment, ConfigurableBeanFactory beanFactory) {
        if (configMap.isEmpty() || refreshFieldValueList.isEmpty()) {
            log.info("configMap.size:{} refreshFieldValueList.size:{}", configMap.size(), refreshFieldValueList.size());
            return;
        }
        MutablePropertySources propertySources = environment.getPropertySources();
        MapPropertySource configCenter = (MapPropertySource) propertySources.get(PROPERTY_SOURCE_NAME);
        if (configCenter == null) {
            log.warn("configCenter is null");
            return;
        }
        Map<String, Object> source = configCenter.getSource();
        Thread thread = new Thread(() -> {
            while (!Thread.interrupted()) {
                try {
                    Map<String, Integer> configIdMap = configMap.values().stream().collect(Collectors.toMap(c -> c.getId() + "", ConfigBO::getVersion));
                    HttpRespBO httpRespBO = HttpUtil.httpPostJson(url + "/config/change/get/long", JSON.toJSONString(configIdMap), 30000);
                    List<ConfigVO> configList = httpResp2ConfigVOList(httpRespBO);
                    if (configList.isEmpty()) {
                        continue;
                    }
                    configList.forEach(configVO -> {
                        Map<String, Object> result = new HashMap<>();
                        DataTransUtil.buildFlattenedMap(result, configVO.getConfigData(), "");
                        ConfigBO configBO = this.configMap.get(configVO.getId());
                        configBO.setVersion(configVO.getVersion());

                        List<ConfigDataBO> configDataList = configBO.getConfigDataList();
                        Map<String, ConfigDataBO> configDataMap = configDataList.stream()
                                .collect(Collectors.toMap(ConfigDataBO::getKey, Function.identity()));
                        result.forEach((key, value) -> {
                            ConfigDataBO configDataBO = configDataMap.get(key);
                            if (configDataBO == null) {
                                configDataList.add(new ConfigDataBO(key, value.toString()));
                            } else {
                                configDataBO.setValue(value.toString());
                                source.put(key, value);
                            }
                        });
                    });

                    refreshFieldValueList.forEach(refreshFieldBO -> {
                        try {
                            Field field = refreshFieldBO.getField();
                            Value valueAnnotation = AnnotationUtils.getAnnotation(field, Value.class);
                            if (valueAnnotation == null) {
                                return;
                            }
                            String value = valueAnnotation.value();
                            String relValue = beanFactory.resolveEmbeddedValue(value);
                            if(relValue.equals(refreshFieldBO.getValue())){
                                return;
                            }
                            field.setAccessible(true);
                            field.set(refreshFieldBO.getBean(), relValue);
                        } catch (Exception e) {
                            log.error("startSpringBootLongPolling set Field error", e);
                        }
                    });
                } catch (Exception e) {
                    log.error("startSpringBootLongPolling error", e);
                }
            }
        });
        thread.setName("startSpringBootLongPolling");
        thread.setDaemon(true);
        thread.start();
    }

效果

@Value

@Data
@Component
public class ConfigTest {

    @ConfigRefresh
    @Value("${user.name}")
    private String name;

}
    @Autowired
    private ConfigTest configTest;

    @Test
    public void configTest() throws InterruptedException {
        while (true) {
            System.out.println(configTest.getName());
            Thread.sleep(1000);
        }
    }

@ConfigurationProperties

增加同时有@ConfigurationProperties和@ConfigRefresh的收集

ConfigRefresh configRefresh = AnnotationUtils.findAnnotation(bean.getClass(), ConfigRefresh.class);
            if (configRefresh != null) {
                ConfigurationProperties configurationProperties = AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class);
                if (configurationProperties != null) {
                    ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
                    configCenterClient.addRefreshBeanList(bean);
                }
            }

在长轮询的返回中对@ConfigurationProperties重新绑定

refreshBeanList.forEach(refreshBean -> {
                        ConfigurationProperties configurationProperties = AnnotationUtils.findAnnotation(refreshBean.getClass(), ConfigurationProperties.class);
                        if (configurationProperties == null) {
                            log.warn("refreshBeanList refreshBean configurationProperties is null, class:{}", refreshBean.getClass());
                            return;
                        }
                        Binder binder = Binder.get(environment);
                        binder.bind(configurationProperties.prefix(), Bindable.ofInstance(refreshBean));
                    });

完整代码

@Slf4j
public class ConfigRefreshAnnotationBeanPostProcessor implements ApplicationRunner, BeanPostProcessor, BeanFactoryAware, EnvironmentAware {

    private Environment environment;

    private ConfigurableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {

        if (!(beanFactory instanceof ConfigurableBeanFactory)) {
            log.warn("ConfigurableBeanFactory requires a ConfigurableListableBeanFactory");
            return;
        }
        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, final String beanName) throws BeansException {
        if (beanFactory != null) {
            ReflectionUtils.doWithFields(bean.getClass(), field -> {
                try {
                    ConfigRefresh configRefresh = AnnotationUtils.getAnnotation(field, ConfigRefresh.class);
                    if (configRefresh == null) {
                        return;
                    }
                    Value valueAnnotation = AnnotationUtils.getAnnotation(field, Value.class);
                    if (valueAnnotation == null) {
                        return;
                    }
                    String value = valueAnnotation.value();
                    String relValue = beanFactory.resolveEmbeddedValue(value);

                    ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
                    configCenterClient.addRefreshFieldValue(bean, field, relValue);
                } catch (Exception e) {
                    log.error("set bean field fail,beanName:{},fieldName:{}", bean.getClass().getName(), field.getName(), e);
                }
            });

            ConfigRefresh configRefresh = AnnotationUtils.findAnnotation(bean.getClass(), ConfigRefresh.class);
            if (configRefresh != null) {
                ConfigurationProperties configurationProperties = AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class);
                if (configurationProperties != null) {
                    ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
                    configCenterClient.addRefreshBeanList(bean);
                }
            }
        }
        return bean;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void run(ApplicationArguments args) {
        ConfigCenterClient configCenterClient = ConfigCenterClient.getInstance(null);
        configCenterClient.startSpringBootLongPolling((ConfigurableEnvironment) environment, beanFactory);
    }
}
 public void startSpringBootLongPolling(ConfigurableEnvironment environment, ConfigurableBeanFactory beanFactory) {
        if (configMap.isEmpty() || refreshFieldValueList.isEmpty()) {
            log.info("configMap.size:{} refreshFieldValueList.size:{}", configMap.size(), refreshFieldValueList.size());
            return;
        }
        MutablePropertySources propertySources = environment.getPropertySources();
        MapPropertySource configCenter = (MapPropertySource) propertySources.get(PROPERTY_SOURCE_NAME);
        if (configCenter == null) {
            log.warn("configCenter is null");
            return;
        }
        Map<String, Object> source = configCenter.getSource();
        Thread thread = new Thread(() -> {
            while (!Thread.interrupted()) {
                try {
                    Map<String, Integer> configIdMap = configMap.values().stream().collect(Collectors.toMap(c -> c.getId() + "", ConfigBO::getVersion));
                    HttpRespBO httpRespBO = HttpUtil.httpPostJson(url + "/config/change/get/long", JSON.toJSONString(configIdMap), 30000);
                    List<ConfigVO> configList = httpResp2ConfigVOList(httpRespBO);
                    if (configList.isEmpty()) {
                        continue;
                    }
                    configList.forEach(configVO -> {
                        Map<String, Object> result = new HashMap<>();
                        DataTransUtil.buildFlattenedMap(result, configVO.getConfigData(), "");
                        ConfigBO configBO = this.configMap.get(configVO.getId());
                        configBO.setVersion(configVO.getVersion());

                        List<ConfigDataBO> configDataList = configBO.getConfigDataList();
                        Map<String, ConfigDataBO> configDataMap = configDataList.stream()
                                .collect(Collectors.toMap(ConfigDataBO::getKey, Function.identity()));
                        result.forEach((key, value) -> {
                            ConfigDataBO configDataBO = configDataMap.get(key);
                            if (configDataBO == null) {
                                configDataList.add(new ConfigDataBO(key, value.toString()));
                            } else {
                                configDataBO.setValue(value.toString());
                                source.put(key, value);
                            }
                        });
                    });

                    refreshFieldValueList.forEach(refreshFieldBO -> {
                        try {
                            Field field = refreshFieldBO.getField();
                            Value valueAnnotation = AnnotationUtils.getAnnotation(field, Value.class);
                            if (valueAnnotation == null) {
                                return;
                            }
                            String value = valueAnnotation.value();
                            String relValue = beanFactory.resolveEmbeddedValue(value);
                            if (relValue.equals(refreshFieldBO.getValue())) {
                                return;
                            }
                            field.setAccessible(true);
                            field.set(refreshFieldBO.getBean(), relValue);
                        } catch (Exception e) {
                            log.error("startSpringBootLongPolling set Field error", e);
                        }
                    });

                    refreshBeanList.forEach(refreshBean -> {
                        ConfigurationProperties configurationProperties = AnnotationUtils.findAnnotation(refreshBean.getClass(), ConfigurationProperties.class);
                        if (configurationProperties == null) {
                            log.warn("refreshBeanList refreshBean configurationProperties is null, class:{}", refreshBean.getClass());
                            return;
                        }
                        Binder binder = Binder.get(environment);
                        binder.bind(configurationProperties.prefix(), Bindable.ofInstance(refreshBean));
                    });
                } catch (Exception e) {
                    log.error("startSpringBootLongPolling error", e);
                }
            }
        });
        thread.setName("startSpringBootLongPolling");
        thread.setDaemon(true);
        thread.start();
    }

效果

@Component
@ConfigRefresh
@ConfigurationProperties(prefix = "user")
public class ConfigTest2 {
    private String name;
    private int age;
    private List<String> education;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<String> getEducation() {
        return education;
    }

    public void setEducation(List<String> education) {
        this.education = education;
    }
}
   @Autowired
    private ConfigTest2 configTest2;

    @Test
    public void configTest() throws InterruptedException {
        while (true) {
            System.out.println(configTest2.getName() + "-" + configTest2.getAge() + "-" + configTest2.getEducation());
            Thread.sleep(1000);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/442335.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pytest教程-15-多个fixture以及重命名

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了fixture的yield关键字&#xff0c;本小节我们讲解一下使用多个fixture的方法。 使用多个fixture 如果用例需要用到多个fixture的返回数据&#xff0c;fixture也可以return一个元组、list或字…

[嵌入式系统-37]:龙芯1B 开发学习套件 -7-MIPS指令集

目录 一、MIPS指令分类 二、常用指令详解 三、常用MIPS指令集及格式&#xff1a; 四、CPU内部的32个寄存器&#xff1a; 一、MIPS指令分类 MIPS&#xff08;Microprocessor without Interlocked Pipeline Stages&#xff09;指令集是一种广泛用于教学和嵌入式系统的指令集…

在线部署ubuntu20.04服务器,安装jdk、mysql、redis、nginx、minio、开机自启微服务jar包

一、服务器 1、查看服务器版本 查看服务器版本为20.04 lsb_release -a2、服务器信息 服务器初始账号密码 sxd / 123456 首先,更改自身密码都输入123456 sudo passwd 创建最高权限root账号&#xff0c;密码为 123456 su root 3、更新服务器源 1、更新源列表 sudo apt-g…

【golang】Windows与Linux交叉编译保姆级教程

【golang】Windows与Linux交叉编译 大家好 我是寸铁&#x1f44a; 总结了一篇【golang】Windows与Linux交叉编译的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 问题背景 今天寸铁想将Windows中的程序部到Linux下跑&#xff0c;我们知道在从Windows与Linux下要进行交叉编译…

11、Linux-安装和配置Redis

目录 第一步&#xff0c;传输文件和解压 第二步&#xff0c;安装gcc编译器 第三步&#xff0c;编译Redis 第四步&#xff0c;安装Redis服务 第五步&#xff0c;配置Redis ①开启后台启动 ②关闭保护模式&#xff08;关闭之后才可以远程连接Redis&#xff09; ③设置远程…

Java基础 - 8 - 算法、正则表达式、异常

一. 算法 什么是算法&#xff1f; 解决某个实际问题的过程和方法 学习算法的技巧&#xff1f; 先搞清楚算法的流程&#xff0c;再直接去推敲如何写算法 1.1 排序算法 1.1.1 冒泡排序 每次从数组中找出最大值放在数组的后面去 public class demo {public static void main(S…

21 easy 1. 两数之和

//给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 // // 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 // // 你可以…

java Spring boot简述jetcache 并叙述后续文章安排

我们之前 讲了 Spring boot 整合 cache 使用 simple(默认) redis Ehcache memcached的几种方式 但是 始终有人觉得不够完善 提出了一些问题 例如 觉得 当前spring boot 对缓存过期的控制过于松散 不严谨 比较明显的体现就是 memcached过期时间在逻辑代码中控制 Ehcache的过期时…

Tomcat的安装

下载Tomcat&#xff08;这里以Tomcat8.5为例&#xff09; 直接进入官网进行下载&#xff0c;Tomcat官网 选择需要下载的版本&#xff0c;点击下载这里一定要注意&#xff1a;下载路径一定要记住&#xff0c;并且路径中尽量不要有中文&#xff01;&#xff01;&#xff01;&…

Prompt Engineering、Finetune、RAG:OpenAI LLM 应用最佳实践

一、背景 本文介绍了 2023 年 11 月 OpenAI DevDay 中的一个演讲&#xff0c;演讲者为 John Allard 和 Colin Jarvis。演讲中&#xff0c;作者对 LLM 应用落地过程中遇到的问题和相关改进方案进行了总结。虽然其中用到的都是已知的技术&#xff0c;但是进行了很好的总结和串联…

神经网络的矢量化,训练与激活函数

我们现在再回到我们的神经元部分&#xff0c;来看我们如何用python进行正向传递。 单层的正向传递&#xff1a; 我们回到我们的线性回归的函数。我们每个神经元通过上述的方法&#xff0c;就可以得到我们的激发值&#xff0c;从而可以继续进行下一层。 我们用这个方法就可以得…

排序算法的对比

类别排序方法时间复杂度空间复杂度稳定性平均情况特殊情况 插入 排序 插入排序基本有序最优稳定希尔排序不稳定 选择 排序 选择排序不稳定堆排序不稳定 交换 排序 冒泡排序稳定快速排序基本有序最差不稳定归并排序稳定基数排序稳定

0环PEB断链实现

截止到昨天那里我们的思路就清晰了&#xff0c;通过EPROCESS找到我们要隐藏的进程的ActiveProcessLinks&#xff0c;将双向链表的值修改&#xff0c;就可以将我们想要隐藏的这个进程的ActiveProcessLinks从双向链表中抹去的效果&#xff0c;这里的话如果在windbg里面直接使用ed…

用Python实现一个简单的——人脸相似度对比

近几年来&#xff0c;兴起了一股人工智能热潮&#xff0c;让人们见到了AI的能力和强大&#xff0c;比如图像识别&#xff0c;语音识别&#xff0c;机器翻译&#xff0c;无人驾驶等等。总体来说&#xff0c;AI的门槛还是比较高&#xff0c;不仅要学会使用框架实现&#xff0c;更…

Day33|贪心算法part3

k次取反后最大的数组元素和 思路&#xff1a;贪心&#xff0c;局部最优&#xff0c;让绝对值大的负数变正数&#xff0c;当前数值达到最大&#xff0c;整体最优&#xff1b;整个数组和达到最大。如果把序列中所有负数都转换为正数了&#xff0c;k还没耗尽&#xff0c;就是k还大…

AWS的CISO:GenAI只是一个工具,不是万能钥匙

根据CrowdStrike的年度全球威胁报告,尽管研究人员预计人工智能将放大对防御者和攻击者的影响,但威胁参与者在其行动中使用人工智能的程度有限。该公司上个月在报告中表示:“在整个2023年,很少观察到GenAI支持恶意计算机网络运营的开发和/或执行。” 对于GenAI在网络安全中的…

Python:在 Ubuntu 上安装 pip的方法

目录 1、检测是否已安装pip 2、更新软件源 3、安装 4、检测是否安装成功 pip和pip3都是Python包管理工具&#xff0c;用于安装和管理Python包。 在Ubuntu上&#xff0c;pip和pip3是分别针对Python2和Python3版本的pip工具。 pip3作用&#xff1a;自动下载安装Python的库文…

2024年HW技术总结

我们都知道&#xff0c; 护网行动 是国家应对网络安全问题所做的重要布局之一。至今已经是8个年头了&#xff0c;很多公司在这时候人手不够&#xff0c;因此不得不招募一些网安人员来参加护网。 红队 扮演攻击的角色&#xff0c;蓝队 扮演防守、溯源的角色&#xff0c;紫队当然…

Java 的 System 类常用方法介绍

Java 中的 System 类是一个final类&#xff0c;它提供了与系统相关的属性和方法。它是一个内置的类&#xff0c;可以直接使用&#xff0c;不需要实例化。System 类提供了标准输入、标准输出和错误输出流&#xff0c;以及对外部定义的属性和系统环境的访问。下面是 System 类的一…

如何在Android 上查找恢复删除/隐藏的照片?5 个值得尝试的方法分享

Android 设备上的删除/隐藏图像有多种用途&#xff0c;无论是保护隐私还是优化存储空间。然而&#xff0c;在某些情况下&#xff0c;用户可能由于意外隐藏、删除或仅仅是出于好奇而需要取消删除/隐藏这些照片。在这种情况下&#xff0c;了解如何在 Android 上查找删除/隐藏的照…