(编写不易,转载请注明: 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; } }
相关推荐
在本文中,我们将深入探讨如何将Spring Boot与Kafka消费者整合,以便在JVM开发平台上构建高效的消息处理系统。Kafka是一种分布式流处理平台,而Spring Boot是Java领域中的一个微服务开发框架,两者结合可以简化开发...
通过深入理解和实践这个项目,你可以了解到分布式事务处理的原理和实践,包括两阶段提交(2PC)、分布式锁、 Saga模式等,同时也能掌握SpringBoot、Dubbo和Seata的集成使用,以及如何利用Zookeeper进行服务治理。...
Continue.continue-0.9.207@linux-arm64 领先的开源 AI 代码助手。您可以连接任何模型和任何上下文,以在 VSCODE IDE 中创建自定义自动完成和聊天体验
【描述】"在freescale单片机上实现TCPip协议栈的代码集成,TCP client实现"意味着开发人员需要将uIP的源代码移植到Freescale的MC9S12微控制器上,使其能够处理网络通信中的TCP协议。TCP客户端功能的实现意味着该设备...
Continue.continue-0.9.207@linux-x64.vsix Continue 是领先的开源 AI 代码助手。您可以连接任何模型和任何上下文,以在 VS Code 和 JetBrains 中构建自定义自动完成和聊天体验
在本文档中,我们将介绍如何使用 CDlinux-0.9.6.1 集成版 ISO 镜像创建 U 盘版的详细步骤。这个过程需要使用多个工具,包括 Unetbootin、Grubinst 和 Grub4dos。下面是详细的步骤解释: Step 1: 下载和安装 ...
总结来说,华为更新提取器0.9.9.5是华为设备用户和开发者不可或缺的辅助工具,其背后集成了多种强大的库和功能,确保了更新提取过程的安全、高效和灵活。无论是为了离线安装还是深入研究,这个工具都展现出了其强大...
【BSQL Hacker v0.9.0.9 中文汉化版】 BSQL Hacker是一款针对数据库安全检测...对于任何处理敏感数据的组织,定期使用此类工具进行安全检查是必不可少的。同时,用户应当持续关注软件更新,以便及时修补新的安全漏洞。
本文将详细介绍这个0.9.5.180825版本的Redis Desktop Manager,并讨论如何在Win10环境下使用它,以及如何关闭烦人的更新弹窗。 Redis Desktop Manager(简称RDM)是一款跨平台的Redis数据库管理应用,它提供了一个...
Apollo配置中心是一款由携程开源的企业级分布式配置管理平台,它主要负责集中管理和推送应用程序的配置,使得在分布式系统中能够方便、快捷地进行配置的更新和分发。本安装包包含Linux和Windows版本,适用于zip格式...
在"Typora_0.9(不收费版本)"中,我们可以理解到这是Typora的一个早期版本,并且这个版本是免费的,无需用户进行额外的破解操作即可使用。Typora通常会定期更新,增加新功能和修复已知问题,但旧版本可能缺乏某些新...
0.9.98版本无需付费,对于那些不希望支付订阅费用但仍想使用Typora的用户来说,这是一个不错的选择。 9. **兼容性问题**:随着软件的更新迭代,新版本可能会与某些旧系统或插件存在兼容性问题。0.9.98可能更适合...
总结来说,SpringMagic 0.9.0 是3DS MAX用户不可或缺的特效工具,它通过强大的骨骼系统和飘带模拟功能,简化了复杂动画的制作过程,提升了作品的视觉表现力。通过深入理解和熟练应用这个插件,动画师们可以在项目中...
6. **数据收发**:定义应用程序接口(API)用于数据的发送和接收,UIP0.9的TCP层会处理数据的分段、重传和确认,而应用程序只需要关注数据的逻辑处理。 7. **调试与优化**:移植完成后,通过网络嗅探工具(如...
这里的"0.9.6.1 SSE"是CDLinux的一个特定版本号,表明这是该发行版的一个迭代更新,SSE则代表Streaming SIMD Extensions,一种用于提高处理器处理浮点运算和多媒体数据的指令集。这个版本是2004年4月29日发布的。 ...
《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-messenger-client-0.9.jar,airavata,messenger,client,0.9,jar包下载,依赖包
目前最新的typora需要付费使用,但官方保持旧版本不收费,可以继续使用,功能与新版本相差不大,在压缩包中包括一个可用的免费旧版本资源 - 版本号0.9.98。 直接安装直接使用 1.0以后的版本均需付费使用。该版本...
不过,值得注意的是,尽管0.9.98版本在许多人心中是经典,但软件开发者通常会持续更新以修复问题和添加新功能,所以选择不升级可能会错过一些改进和更新。但对于那些追求稳定性和熟悉0.9.98操作习惯的用户来说,这个...