`

Springboot集成ApolloClient0.9使用@Value不更新处理

阅读更多

 

(编写不易,转载请注明: https://www.iteye.com/blog/user/shihlei/blog/2521438)

 

1 背景

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,现在已经众多互联网公司普及。

 

SpringBoot集成ApolloClient配置中心,在中心更改,本地配置也会更新,即热更新。

 

项目实践中发现,公司用的版本0.9,居然不支持热更新,需要手动写Listener监听变化,网上查看,人更新要到1.0以后才支持。升级apollo-client版本到1.0不好使。

 

于是干脆自己写了个热更新组件,用来支持@Value热更新,及@ApolloJsonValue功能,提升开发效率。

 

注:

由于这个程序注定要淘汰,所以目前只支持Bean的成员变量更新,有条件的建议整体升级Apollo

 

2 代码实现

2.1 maven版本

 

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
	<dependency>
	    <groupId>com.ctrip.framework.apollo</groupId>
	    <artifactId>apollo-client</artifactId>
	    <version>0.9.2.3</version>
	</dependency>

	<dependency>
	    <groupId>com.google.guava</groupId>
	    <artifactId>guava</artifactId>
	    <version>${guava.version}</version>
	</dependency>
	<dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	    <version>${lombok.version}</version>
	</dependency>
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>fastjson</artifactId>
	    <version>${fastjson.version}</version>
	</dependency>
	<dependency>
	    <groupId>org.apache.commons</groupId>
	    <artifactId>commons-lang3</artifactId>
	    <version>${commons-lang3.version}</version>
	</dependency>

	<dependency>
	    <groupId>org.apache.commons</groupId>
	    <artifactId>commons-collections4</artifactId>
	    <version>${commons-collections4.version}</version>
	</dependency>
</dependencies>

 

 2.2  程序

(1)@ApolloJsonValue

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApolloJsonValue {
    String value();

    String namespace() default ConfigConsts.NAMESPACE_APPLICATION;
}

 

 

(2)ApolloConfig

@Slf4j
@Component
@EnableApolloConfig
public class ApolloConfig implements BeanPostProcessor {

    public static final String PLACEHOLDER_PREFIX = "${";

    public static final String PLACEHOLDER_SUFFIX = "}";

    public static final String VALUE_SEPARATOR = ":";

    private Multimap<String, ValuedField> valuedFields = HashMultimap.create();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 读取 @value
        Class<?> clazz = bean.getClass();

        processFields(bean, clazz.getDeclaredFields());

        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @ApolloConfigChangeListener(ConfigConsts.NAMESPACE_APPLICATION)
    private void onChange(ConfigChangeEvent changeEvent) {
        changeEvent.changedKeys().forEach(
                key -> {
                    Collection<ValuedField> sameNameValuedFields = valuedFields.get(key);
                    if (CollectionUtils.isEmpty(sameNameValuedFields)) {
                        return;
                    }

                    valuedFields.get(key)
                            .forEach(valuedField -> {
                                ConfigChange configChange = changeEvent.getChange(key);

                                String strValue = configChange.getNewValue();

                                Object propertyValue = null;
                                if (valuedField.isJson) {
                                    propertyValue = pareseJsonValue(strValue, valuedField.field.getGenericType());
                                } else {
                                    propertyValue = parseValue(strValue, valuedField.field.getType());
                                }

                                ReflectionUtils.makeAccessible(valuedField.field);
                                ReflectionUtils.setField(valuedField.field, valuedField.bean, propertyValue);

                                log.info("[config]:{}", configChange);
                            });
                }
        );
    }

    private Object parseValue(String strValue, Class type) {
        if (type == Integer.class || type == int.class) {
            return Integer.parseInt(strValue);
        } else if (type == String.class) {
            return strValue;
        } else if (type == Long.class || type == long.class) {
            return Long.parseLong(strValue);
        } else if (type == Double.class || type == double.class) {
            return Double.parseDouble(strValue);
        } else if (type == Boolean.class || type == boolean.class) {
            return Boolean.parseBoolean(strValue);
        } else if (type == Float.class || type == float.class) {
            return Float.parseFloat(strValue);
        } else {
            throw new RuntimeException("not support:" + type);
        }
    }

    private void processFields(Object bean, Field[] declaredFields) {
        for (Field field : declaredFields) {
            processValue(bean, field);
            processApolloJsonValue(bean, field);
        }
    }

    private void processValue(Object bean, Field field) {
        Value valueAnnotation = AnnotationUtils.getAnnotation(field, Value.class);
        if (Objects.isNull(valueAnnotation)) {
            return;
        }

        String placeHolder = extractPlaceHoler(valueAnnotation.value());

        if (StringUtils.contains(placeHolder, VALUE_SEPARATOR)) {
            placeHolder = StringUtils.substringBefore(placeHolder, VALUE_SEPARATOR);
        }

        valuedFields.put(placeHolder, new ValuedField(bean, field, false));
    }

    private void processApolloJsonValue(Object bean, Field field) {
        ApolloJsonValue apolloJsonValueAnnotation = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class);
        if (Objects.isNull(apolloJsonValueAnnotation)) {
            return;
        }

        String placeHolder = extractPlaceHoler(apolloJsonValueAnnotation.value());

        String propertyName = placeHolder;
        String defaultValue = null;
        if (StringUtils.contains(placeHolder, VALUE_SEPARATOR)) {
            propertyName = StringUtils.substringBefore(placeHolder, VALUE_SEPARATOR);
            defaultValue = StringUtils.substringAfter(placeHolder, VALUE_SEPARATOR);
        }

        // read apollo
        Config config = ConfigService.getConfig(apolloJsonValueAnnotation.namespace());
        String apolloJsonValue = config.getProperty(propertyName, "");

        Object propertyValue = null;
        if (StringUtils.isNotBlank(apolloJsonValue)) {
            propertyValue = pareseJsonValue(apolloJsonValue, field.getGenericType());
        } else {
            propertyValue = pareseJsonValue(defaultValue, field.getGenericType());
        }

        // set value
        ReflectionUtils.makeAccessible(field);
        ReflectionUtils.setField(field, bean, propertyValue);

        valuedFields.put(propertyName, new ValuedField(bean, field, true));
    }

    private Object pareseJsonValue(String json, Type type) {
        if (StringUtils.isBlank(json)) {
            return null;
        }
        return JSON.parseObject(json, type);
    }

    static private String extractPlaceHoler(String text) {
        if (!StringUtils.startsWith(text, PLACEHOLDER_PREFIX)) {
            return text;
        }

        if (!StringUtils.endsWith(text, PLACEHOLDER_SUFFIX)) {
            return text;
        }

        text = StringUtils.substringAfter(text, PLACEHOLDER_PREFIX);
        text = StringUtils.substringBeforeLast(text, PLACEHOLDER_SUFFIX);
        return text;
    }


    @AllArgsConstructor
    static class ValuedField {
        private Object bean;
        private Field field;
        private boolean isJson;
    }

}

 

 

 

 

 

 

0
0
分享到:
评论

相关推荐

    springboot kafka整合

    在本文中,我们将深入探讨如何将Spring Boot与Kafka消费者整合,以便在JVM开发平台上构建高效的消息处理系统。Kafka是一种分布式流处理平台,而Spring Boot是Java领域中的一个微服务开发框架,两者结合可以简化开发...

    分布式事务-seata0.9版本(springboot-dubbo-seata-zk).zip

    通过深入理解和实践这个项目,你可以了解到分布式事务处理的原理和实践,包括两阶段提交(2PC)、分布式锁、 Saga模式等,同时也能掌握SpringBoot、Dubbo和Seata的集成使用,以及如何利用Zookeeper进行服务治理。...

    Continue.continue-0.9.207@linux-arm64

    Continue.continue-0.9.207@linux-arm64 领先的开源 AI 代码助手。您可以连接任何模型和任何上下文,以在 VSCODE IDE 中创建自定义自动完成和聊天体验

    uip0.9协议栈集成

    【描述】"在freescale单片机上实现TCPip协议栈的代码集成,TCP client实现"意味着开发人员需要将uIP的源代码移植到Freescale的MC9S12微控制器上,使其能够处理网络通信中的TCP协议。TCP客户端功能的实现意味着该设备...

    Continue.continue-0.9.207@linux-x64.vsix

    Continue.continue-0.9.207@linux-x64.vsix Continue 是领先的开源 AI 代码助手。您可以连接任何模型和任何上下文,以在 VS Code 和 JetBrains 中构建自定义自动完成和聊天体验

    CDlinux-0.9.6.1集成版.iso__U盘版制作说明定义.pdf

    在本文档中,我们将介绍如何使用 CDlinux-0.9.6.1 集成版 ISO 镜像创建 U 盘版的详细步骤。这个过程需要使用多个工具,包括 Unetbootin、Grubinst 和 Grub4dos。下面是详细的步骤解释: Step 1: 下载和安装 ...

    HuaweiUpdateExtractor_0.9.9.5

    总结来说,华为更新提取器0.9.9.5是华为设备用户和开发者不可或缺的辅助工具,其背后集成了多种强大的库和功能,确保了更新提取过程的安全、高效和灵活。无论是为了离线安装还是深入研究,这个工具都展现出了其强大...

    BSQL Hacker v0.9.0.9 中文汉化版

    【BSQL Hacker v0.9.0.9 中文汉化版】 BSQL Hacker是一款针对数据库安全检测...对于任何处理敏感数据的组织,定期使用此类工具进行安全检查是必不可少的。同时,用户应当持续关注软件更新,以便及时修补新的安全漏洞。

    redis-desktop-manager-0.9.5.180825关闭更新弹窗版.zip

    本文将详细介绍这个0.9.5.180825版本的Redis Desktop Manager,并讨论如何在Win10环境下使用它,以及如何关闭烦人的更新弹窗。 Redis Desktop Manager(简称RDM)是一款跨平台的Redis数据库管理应用,它提供了一个...

    Apollo配置中心软件安装包

    Apollo配置中心是一款由携程开源的企业级分布式配置管理平台,它主要负责集中管理和推送应用程序的配置,使得在分布式系统中能够方便、快捷地进行配置的更新和分发。本安装包包含Linux和Windows版本,适用于zip格式...

    Typora_0.9(不收费版本)

    在"Typora_0.9(不收费版本)"中,我们可以理解到这是Typora的一个早期版本,并且这个版本是免费的,无需用户进行额外的破解操作即可使用。Typora通常会定期更新,增加新功能和修复已知问题,但旧版本可能缺乏某些新...

    Typora老版本(0.9.98)

    0.9.98版本无需付费,对于那些不希望支付订阅费用但仍想使用Typora的用户来说,这是一个不错的选择。 9. **兼容性问题**:随着软件的更新迭代,新版本可能会与某些旧系统或插件存在兼容性问题。0.9.98可能更适合...

    springmagic_0.9_0_springmagic_0.9_0_

    总结来说,SpringMagic 0.9.0 是3DS MAX用户不可或缺的特效工具,它通过强大的骨骼系统和飘带模拟功能,简化了复杂动画的制作过程,提升了作品的视觉表现力。通过深入理解和熟练应用这个插件,动画师们可以在项目中...

    C8051F120移植UIP0.9

    6. **数据收发**:定义应用程序接口(API)用于数据的发送和接收,UIP0.9的TCP层会处理数据的分段、重传和确认,而应用程序只需要关注数据的逻辑处理。 7. **调试与优化**:移植完成后,通过网络嗅探工具(如...

    CDlinux-0.9.6.1-SSE-0429.zip

    这里的"0.9.6.1 SSE"是CDLinux的一个特定版本号,表明这是该发行版的一个迭代更新,SSE则代表Streaming SIMD Extensions,一种用于提高处理器处理浮点运算和多媒体数据的指令集。这个版本是2004年4月29日发布的。 ...

    PyPI 官网下载 | opentracing-python-kafka-client-0.9.tar.gz

    《PyPI上的opentracing-python-kafka-client-0.9.tar.gz:Python中的分布式追踪与Kafka集成详解》 在Python的世界里,PyPI(Python Package Index)是开发者获取和分享开源软件的主要平台。今天我们要关注的是PyPI...

    airavata-rest-client-0.9.jar

    标签:airavata-rest-client-0.9.jar,airavata,rest,client,0.9,jar包下载,依赖包

    airavata-messenger-client-0.9.jar

    标签:airavata-messenger-client-0.9.jar,airavata,messenger,client,0.9,jar包下载,依赖包

    Typora免费版本(0.9.98)无需破解

    目前最新的typora需要付费使用,但官方保持旧版本不收费,可以继续使用,功能与新版本相差不大,在压缩包中包括一个可用的免费旧版本资源 - 版本号0.9.98。 直接安装直接使用 1.0以后的版本均需付费使用。该版本...

    Markdown神器 Typora0.9.98 最后的免费版本

    不过,值得注意的是,尽管0.9.98版本在许多人心中是经典,但软件开发者通常会持续更新以修复问题和添加新功能,所以选择不升级可能会错过一些改进和更新。但对于那些追求稳定性和熟悉0.9.98操作习惯的用户来说,这个...

Global site tag (gtag.js) - Google Analytics