背景系统对于首次学生认证的用户发放学生礼包权益需要发放积分礼包以及其他优惠券礼包如果用户已经有过奖励积分记录则无需发放积分礼包。礼包被配置成两个一个包含积分另外一个则不包含积分。积分的唯一标识配置在apollo之中由于配置时将两个权益的标识配置反了导致程序没有按预期的效果执行。并且由于更新管控比较严格更新窗口为每周二、周四。其他时间更新属于紧急更新需要产品与客户经理反馈更新原因并且协调更新人员进行更新。整个链路比较长紧急是因为一个简单配置导致应用需要重启更新所以希望可以做到apollo能够实现热更新。实现思路从官网上了解到使用ApolloConfigChangeListener可以监听到apollo的配置修改。定义apollo监听类并且监听方法加上ApolloConfigChangeListener即可可以获取到修改的值、key以及修改类型。拿到这三个信息我们就可以实现热更新了。我们可以使用观察者模式来完成系统某些配置的热更新。定义ApolloValueChangeEvent接口通过提供不同的ApolloValueChangeEvent实现类来完成各种因配置变化而产生的一些变更。例如更新Value注解字段的值等。具体实现1、定义AnoValueRefreshPostProcessor后置处理器将spring对象中的Value注解字段与springbean的映射关系建立起来方便后续对Value注解字段进行热更新。import com.xxx.xxx.xxx.log.ApiLogger; import com.xxx.framework.apollo.entity.FieldPair; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Date: 2023-11-1 16:37 */ Component public class AnoValueRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter { private MapString, ListFieldPair mapper new HashMap(); Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { processMetaValue(bean); return super.postProcessAfterInstantiation(bean, beanName); } /** * 这里主要的目的就是获取支持动态刷新的配置属性然后缓存起来 * * param bean */ private void processMetaValue(Object bean) { try { Class clz bean.getClass(); for (Field field : clz.getDeclaredFields()) { if (field.isAnnotationPresent(Value.class)) { Value val field.getAnnotation(Value.class); ListString keyList pickPropertyKey(val.value(), 0); for (String key : keyList) { mapper.computeIfAbsent(key, (k) - new ArrayList()) .add(new FieldPair(bean, field, val.value())); } } } } catch (Exception e) { ApiLogger.bizLog.warn(AnoValueRefreshPostProcessor processMetaValue warn : e); } } /** * 实现一个基础的配置文件参数动态刷新支持 * * param value * return 提取key列表 */ private ListString pickPropertyKey(String value, int begin) { int start value.indexOf(${, begin) 2; if (start 2) { return new ArrayList(); } int middle value.indexOf(:, start); int end value.indexOf(}, start); String key; if (middle 0 middle end) { // 包含默认值 key value.substring(start, middle); } else { // 不包含默认值 key value.substring(start, end); } ListString keys pickPropertyKey(value, end); keys.add(key); return keys; } public ListFieldPair fieldPairs(String key){ return mapper.get(key); } }2、定义ApolloValueChangeEvent接口public interface ApolloValueChangeEvent{ void nodify(PropertyChangeType optType, String key, String value); }3、定义FieldPair简化字段赋值import com.alibaba.fastjson.JSONObject; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.core.env.Environment; import org.springframework.util.PropertyPlaceholderHelper; import java.lang.reflect.Field; /** * Date: 2023-11-1 16:57 */ Data NoArgsConstructor AllArgsConstructor public class FieldPair { private static PropertyPlaceholderHelper propertyPlaceholderHelper new PropertyPlaceholderHelper(${, }, :, true); Object bean; Field field; String value; public void updateValue(Environment environment) { boolean access field.isAccessible(); if (!access) { field.setAccessible(true); } String updateVal propertyPlaceholderHelper.replacePlaceholders(value, environment::getProperty); try { if (field.getType() String.class) { field.set(bean, updateVal); } else { field.set(bean, JSONObject.parseObject(updateVal, field.getType())); } } catch (IllegalAccessException e) { e.printStackTrace(); } field.setAccessible(access); } }4、定义ValueRefreshEvent实现ApolloValueChangeEvent接口用于热更新Value字段。package com.xxx.xxxx.apollo.event; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableEnumerablePropertySourceWrapper; import com.xxx.xxx.core.log.ApiLogger; import com.xxx.xxx.core.util.SpringBeanTools; import com.xxx.framework.apollo.entity.FieldPair; import com.xxx.framework.spring.processor.AnoValueRefreshPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.List; /** * Date: 2023-11-6 10:11 */ Component public class ValueRefreshEvent implements ApolloValueChangeEvent{ private ConfigurableEnvironment environment; public ValueRefreshEvent(ConfigurableEnvironment environment) { this.environment environment; } private final String name ApolloBootstrapPropertySources; Override public void nodify(PropertyChangeType optType, String key, String value) { try { EncryptableEnumerablePropertySourceWrapper compositePropertySource (EncryptableEnumerablePropertySourceWrapper )environment.getPropertySources().get(name); compositePropertySource.refresh(); AnoValueRefreshPostProcessor anoValueRefreshPostProcessor SpringBeanTools.getBean(AnoValueRefreshPostProcessor.class); ListFieldPair list anoValueRefreshPostProcessor.fieldPairs(key); if (!CollectionUtils.isEmpty(list)) { list.forEach(f - f.updateValue(environment)); } }catch (Exception e){ ApiLogger.bizLog.info(ApolloValueChangeEventError e: e); } } }5、定义Apollo监听类ApolloListenerpackage com.xxx.framework.apollo.listener; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import com.xxx.xxx.core.log.ApiLogger; import com.xxx.framework.apollo.event.ApolloValueChangeEvent; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Objects; //监听阿波罗配置 Data Configuration public class ApolloListener { Autowired private ListApolloValueChangeEvent apolloValueChangeEventList; ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { try{ if(!CollectionUtils.isEmpty(apolloValueChangeEventList)){ changeEvent.changedKeys().stream().map(changeEvent::getChange).forEach(change - { String changeKey change.getPropertyName(); String newValue change.getNewValue(); PropertyChangeType changeType change.getChangeType(); for (ApolloValueChangeEvent apolloValueChangeEvent : apolloValueChangeEventList) { if(Objects.nonNull(apolloValueChangeEvent)){ apolloValueChangeEvent.nodify(changeType,changeKey,newValue); } } }); } }catch (Exception e){ ApiLogger.bizLog.warn(apollo listener warn : e); } } }测试1、测试接口2、配置apollo3、调用接口4、修改apollo配置5、再次调用测试接口可以看到接口返回值已经成功变更
Apollo配置热更新
发布时间:2026/5/27 20:54:02
背景系统对于首次学生认证的用户发放学生礼包权益需要发放积分礼包以及其他优惠券礼包如果用户已经有过奖励积分记录则无需发放积分礼包。礼包被配置成两个一个包含积分另外一个则不包含积分。积分的唯一标识配置在apollo之中由于配置时将两个权益的标识配置反了导致程序没有按预期的效果执行。并且由于更新管控比较严格更新窗口为每周二、周四。其他时间更新属于紧急更新需要产品与客户经理反馈更新原因并且协调更新人员进行更新。整个链路比较长紧急是因为一个简单配置导致应用需要重启更新所以希望可以做到apollo能够实现热更新。实现思路从官网上了解到使用ApolloConfigChangeListener可以监听到apollo的配置修改。定义apollo监听类并且监听方法加上ApolloConfigChangeListener即可可以获取到修改的值、key以及修改类型。拿到这三个信息我们就可以实现热更新了。我们可以使用观察者模式来完成系统某些配置的热更新。定义ApolloValueChangeEvent接口通过提供不同的ApolloValueChangeEvent实现类来完成各种因配置变化而产生的一些变更。例如更新Value注解字段的值等。具体实现1、定义AnoValueRefreshPostProcessor后置处理器将spring对象中的Value注解字段与springbean的映射关系建立起来方便后续对Value注解字段进行热更新。import com.xxx.xxx.xxx.log.ApiLogger; import com.xxx.framework.apollo.entity.FieldPair; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Date: 2023-11-1 16:37 */ Component public class AnoValueRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter { private MapString, ListFieldPair mapper new HashMap(); Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { processMetaValue(bean); return super.postProcessAfterInstantiation(bean, beanName); } /** * 这里主要的目的就是获取支持动态刷新的配置属性然后缓存起来 * * param bean */ private void processMetaValue(Object bean) { try { Class clz bean.getClass(); for (Field field : clz.getDeclaredFields()) { if (field.isAnnotationPresent(Value.class)) { Value val field.getAnnotation(Value.class); ListString keyList pickPropertyKey(val.value(), 0); for (String key : keyList) { mapper.computeIfAbsent(key, (k) - new ArrayList()) .add(new FieldPair(bean, field, val.value())); } } } } catch (Exception e) { ApiLogger.bizLog.warn(AnoValueRefreshPostProcessor processMetaValue warn : e); } } /** * 实现一个基础的配置文件参数动态刷新支持 * * param value * return 提取key列表 */ private ListString pickPropertyKey(String value, int begin) { int start value.indexOf(${, begin) 2; if (start 2) { return new ArrayList(); } int middle value.indexOf(:, start); int end value.indexOf(}, start); String key; if (middle 0 middle end) { // 包含默认值 key value.substring(start, middle); } else { // 不包含默认值 key value.substring(start, end); } ListString keys pickPropertyKey(value, end); keys.add(key); return keys; } public ListFieldPair fieldPairs(String key){ return mapper.get(key); } }2、定义ApolloValueChangeEvent接口public interface ApolloValueChangeEvent{ void nodify(PropertyChangeType optType, String key, String value); }3、定义FieldPair简化字段赋值import com.alibaba.fastjson.JSONObject; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.core.env.Environment; import org.springframework.util.PropertyPlaceholderHelper; import java.lang.reflect.Field; /** * Date: 2023-11-1 16:57 */ Data NoArgsConstructor AllArgsConstructor public class FieldPair { private static PropertyPlaceholderHelper propertyPlaceholderHelper new PropertyPlaceholderHelper(${, }, :, true); Object bean; Field field; String value; public void updateValue(Environment environment) { boolean access field.isAccessible(); if (!access) { field.setAccessible(true); } String updateVal propertyPlaceholderHelper.replacePlaceholders(value, environment::getProperty); try { if (field.getType() String.class) { field.set(bean, updateVal); } else { field.set(bean, JSONObject.parseObject(updateVal, field.getType())); } } catch (IllegalAccessException e) { e.printStackTrace(); } field.setAccessible(access); } }4、定义ValueRefreshEvent实现ApolloValueChangeEvent接口用于热更新Value字段。package com.xxx.xxxx.apollo.event; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableEnumerablePropertySourceWrapper; import com.xxx.xxx.core.log.ApiLogger; import com.xxx.xxx.core.util.SpringBeanTools; import com.xxx.framework.apollo.entity.FieldPair; import com.xxx.framework.spring.processor.AnoValueRefreshPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.List; /** * Date: 2023-11-6 10:11 */ Component public class ValueRefreshEvent implements ApolloValueChangeEvent{ private ConfigurableEnvironment environment; public ValueRefreshEvent(ConfigurableEnvironment environment) { this.environment environment; } private final String name ApolloBootstrapPropertySources; Override public void nodify(PropertyChangeType optType, String key, String value) { try { EncryptableEnumerablePropertySourceWrapper compositePropertySource (EncryptableEnumerablePropertySourceWrapper )environment.getPropertySources().get(name); compositePropertySource.refresh(); AnoValueRefreshPostProcessor anoValueRefreshPostProcessor SpringBeanTools.getBean(AnoValueRefreshPostProcessor.class); ListFieldPair list anoValueRefreshPostProcessor.fieldPairs(key); if (!CollectionUtils.isEmpty(list)) { list.forEach(f - f.updateValue(environment)); } }catch (Exception e){ ApiLogger.bizLog.info(ApolloValueChangeEventError e: e); } } }5、定义Apollo监听类ApolloListenerpackage com.xxx.framework.apollo.listener; import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import com.xxx.xxx.core.log.ApiLogger; import com.xxx.framework.apollo.event.ApolloValueChangeEvent; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Objects; //监听阿波罗配置 Data Configuration public class ApolloListener { Autowired private ListApolloValueChangeEvent apolloValueChangeEventList; ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { try{ if(!CollectionUtils.isEmpty(apolloValueChangeEventList)){ changeEvent.changedKeys().stream().map(changeEvent::getChange).forEach(change - { String changeKey change.getPropertyName(); String newValue change.getNewValue(); PropertyChangeType changeType change.getChangeType(); for (ApolloValueChangeEvent apolloValueChangeEvent : apolloValueChangeEventList) { if(Objects.nonNull(apolloValueChangeEvent)){ apolloValueChangeEvent.nodify(changeType,changeKey,newValue); } } }); } }catch (Exception e){ ApiLogger.bizLog.warn(apollo listener warn : e); } } }测试1、测试接口2、配置apollo3、调用接口4、修改apollo配置5、再次调用测试接口可以看到接口返回值已经成功变更