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

文章介绍了如何在SpringBoot应用中实现配置自动刷新功能,通过BeanPostProcessor监控带有@ConfigRefresh注解的字段,并使用长轮询从ConfigCenter获取最新配置。实现中涉及到`ConfigRefreshAnnotationBeanPostProcessor`和`ConfigCenterClient`的使用。
摘要由CSDN通过智能技术生成

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

分布式事务是指跨多个数据库或服务的事务操作,保证数据一致性和可靠性。在 Java 中手写分布式事务可以使用以下几种方式实现: 1. 两阶段提交(2PC):在分布式环境中,协调者(通常是一个中心节点)与参与者(各个分布式节点)进行协调来保证事务的一致性。具体实现中,需要定义协议、消息的传递和处理等。这种方式实现相对复杂,但能够保证数据的强一致性。 2. 补偿事务(TCC):通过预先定义事务的 try、confirm 和 cancel 三个阶段,来实现自动或手动进行事务的补偿。如果某个参与者失败,则可以通过 cancel 阶段回滚之前的操作。这种方式实现相对简单,但可能会引入一定的不一致性。 3. 消息队列:使用消息队列可以将分布式事务拆解为独立的事务操作,并通过消息中间件来保证最终一致性。具体实现中,可以使用事务消息或者可靠消息传递机制,确保消息的可靠传递和处理。 4. 分布式锁:使用分布式锁可以在分布式环境下保证对共享资源的互斥访问。通过获取锁来进行事务操作,可以保证在同一时刻只有一个节点能够执行某个操作,从而保证数据的一致性。 需要注意的是,手写分布式事务比较复杂且易出现问题,建议使用成熟的分布式事务框架或者中间件,如 Spring Cloud、Atomikos、Seata 等来简化开发和保证数据一致性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值