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

news2024/11/16 23:59:29

对于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);
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

746. 使用最小花费爬楼梯 (Swift版本)

题目 给你一个整数数组 cost&#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 限制条件 2…

6. 虚拟机及Linux安装

虚拟机及Linux安装 进行嵌入式项目开发&#xff0c;第一步就是要建立嵌入式开发环境&#xff0c;主要包括安装 Bootloader 工具、不同平台的交叉编译器&#xff08;如ARM 平台的arm-linux-gcc&#xff09;、内核源码树&#xff08;在需要编译和配置内核时&#xff09;、在调试…

Python-sklearn.datasets-make_blobs

​​​​​​sklearn.datasets.make_blobs()函数形参详解 """ Title: datasets for regression Time: 2024/3/5 Author: Michael Jie """from sklearn import datasets import matplotlib.pyplot as plt# 产生服从正态分布的聚类数据 x, y, cen…

Ps:渐变工具 - 经典渐变

渐变工具 Gradient Tool常用于背景填充和界面元素设计&#xff0c;可创建平滑的颜色过渡效果。也可用于图层蒙版上&#xff0c;控制图像或效果的平滑混合。 快捷键&#xff1a;G 渐变工具选项栏中有两种模式&#xff1a;渐变 Gradient和经典渐变 Classic gradient&#xff0c;经…

CSS中em/px/rem/vh/vw区别详解

文章目录 一、介绍二、单位pxemremvh、vw 三、总结 一、介绍 传统的项目开发中&#xff0c;我们只会用到px、%、em这几个单位&#xff0c;它可以适用于大部分的项目开发&#xff0c;且拥有比较良好的兼容性 从CSS3开始&#xff0c;浏览器对计量单位的支持又提升到了另外一个境…

信息系统项目管理师--进度管理

项⽬进度管理是为了保证项⽬按时完成&#xff0c;对项⽬所需的各个过程进⾏管理&#xff0c;包括规划进度、 定义活动、排列活动顺序、估算活动持续时间、制订项⽬进度计划和控制进度。⼩型项⽬中&#xff0c; 定义活动、排列活动顺序、估算活动持续时间及制定进度模型形成进度…

【办公类-21-09】三级育婴师 视频转音频Python

背景需求&#xff1a; 用AI对话工具试试能否Python将MP4视频转成音频&#xff0c;再转成文字docx&#xff08;不用格式工厂转&#xff09; 结果&#xff1a; 视频MP4转音频wav 视频MP4转音频wav 作者&#xff1a;AI对话大师&#xff0c; 时间&#xff1a;2024年3月8日 impo…

利用GPT开发应用004:从GPT-1到GPT-3

文章目录 一、GPT-1二、GPT-2三、GPT-3四、从GPT-3到InstructGPT 一、GPT-1 2018年年中&#xff0c;就在变换器架构问世一年后&#xff0c;OpenAI发表了一篇题为“通过生成式预训练改进语言理解”的论文&#xff0c;作者是Radford, Alec等人。在这篇论文中&#xff0c;该公司介…

ODI报错

三月 08, 2024 1:20:09 下午 oracle.odi.mapping 信息: Start generation of map physical design: MapPhysicalDesign New_Mapping.物理 三月 08, 2024 1:20:09 下午 oracle.odi.mapping 信息: Finished generation of map physical design: MapPhysicalDesign New_Mapping.物…

BUUCTF-Misc4

镜子里面的世界1 1.打开附件 解压&#xff0c;是一张图片 2. zsteg工具 用zsteg分析图片 3.得到flag ningen1 1.打开附件 是一张图片 2.binwalk 用binwalk -e 分离文件 3.ARCHPR工具 打开分离后的文件夹&#xff0c;有一个加密的压缩包&#xff0c;用ARCHPR解密 4.解密 将…

sheng的学习笔记-AI-多分类学习:ECOC,softmax

目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 基本术语&#xff1a; 若我们欲预测的是离散值&#xff0c;例如“好瓜”“坏瓜”&#xff0c;此类学习任务称为“分类”(classification)&#xff1b; 若欲预测的是连续值&#xff0c;例如西瓜成熟度0.95、0.37&#xff0c;…

HNU-计算机网络-甘晴void学习感悟

前言 计算机网络其实我没太学懂&#xff0c; 仅从应试来说&#xff0c;考试成绩也不太好。 这也是为什么一直没有更新这一学科的学习感悟。 大三下还是有点闲&#xff0c;一周三天小长假&#xff0c;闲来无事还是给写了。 教材使用这本&#xff1a; 总领 期中考试 30% 期…

排序——希尔排序、插入排序

本节复习排序中的希尔排序&#xff0c;希尔排序属于插入排序。 希尔排序的代码和插入排序非常类似。 思想却相对于插入排序来说复杂。 在复习希尔排序之前&#xff0c; 我们需要先复习一下插入排序。 目录 插入排序 插入过程 代码实现 希尔排序 希尔排序的思想 代码实…

#onenet网络请求http(GET,POST)

参考博文&#xff1a; POST: https://blog.csdn.net/qq_43350239/article/details/104361153 POST请求&#xff08;用串口助手测试&#xff09;&#xff1a; POST /devices/1105985351/datapoints HTTP/1.1 api-key:AdbrV5kCRsKsRCfjboYOCVcF9FY Host:api.heclouds.com Con…

C++自创题目——几点了 very hard ver.

题目难度 普及 题目描述 一个老外用一口不流利的中文问你&#xff1a;“Xian zai ji dian le?”你看了一眼表&#xff0c;知道了现在是&#xff0c;你准备用这样的形式写在纸上&#xff1a; Now is m past/to h. 如果你看不懂&#xff0c;举个例子&#xff1a; 当h10&#…

python基础10_转义类型转换

这篇博客我们来学一下转义字符 首先什么是转义字符呢? 转义字符就是在编程中用于表示一些特殊的字符,比如说换行,在字符串中,需要换行吧,然后是不是有些时候还要在字符串中按tab键, 或者是enter键, 或者是引号,这些都是特殊字符,然后就是通过转义.把这些从普通字符转成具有特…

力扣面试经典150 —— 11-15题

力扣面试经典150题在 VScode 中安装 LeetCode 插件即可使用 VScode 刷题&#xff0c;安装 Debug LeetCode 插件可以免费 debug本文使用 python 语言解题&#xff0c;文中 “数组” 通常指 python 列表&#xff1b;文中 “指针” 通常指 python 列表索引 文章目录 11. [中等] H指…

哪里可以下载动态短视频素材?短视频素材资源下载网站有哪些?

嘿&#xff0c;朋友们&#xff01;做短视频的时候&#xff0c;找到那些既有范儿又不会被告侵权的素材简直就是一项技术活。不过别担心&#xff0c;我这就给你们揭秘几个下载动态短视频素材的神秘网站&#xff0c;让你的短视频创作事半功倍&#xff01; 1&#xff0c;蛙学府资源…

ROS——VirtualBox下载

下载&安装Virtualbox Oracle VM VirtualBox 根据电脑系统版本下载。 注意&#xff1a;前提是电脑cpu要开启虚拟化 根据自己的需求下载 双击开始安装 浏览可以更改下载位置&#xff0c;默认在C盘 然后一直点&#xff0c;是或下一步就好了 下载拓展包 后续需要连接使…

直击现场 | 人大金仓携手中国大地保险上线核心超A系统

2023年底 中国大地保险 卡园三路59号办公室里 一群技术精英们正忙碌着 他们的眼中 闪烁着对即将到来的胜利的期待 这是大地保险超A系统 项目上线的关键时刻 也是通过科技创新 引领行业服务新趋势的一场征程 项目现场 #1 一次颠覆 改变传统保险服务模式 超A平台&#xff0c;是由…