前言

  配置中心,通过key=value的形式存储环境变量。配置中心的属性做了修改,项目中可以通过配置中心的依赖(sdk)立即感知到。需要做的就是如何在属性发生变化时,改变带有@ConfigurationProperties的bean的相关属性。

配置中心

  在读配置中心源码的时候发现,里面维护了一个Environment,以及ZookeeperPropertySource。当配置中心属性发生变化的时候,清空ZookeeperPropertySource,并放入最新的属性值。

  

  1. public class ZookeeperPropertySource extends EnumerablePropertySource<Properties>

  

  ZookeeperPropertySource重写了equals和hahscode方法,根据这两个方法可以判定配置中心是否修改了属性。

  

配置中心定义的属性变量

  1. message.center.channels[0].type=HELIUYAN
  2. message.center.channels[0].desc=和留言系统
  3. message.center.channels[1].type=EC_BACKEND
  4. message.center.channels[1].desc=电商后台
  5. message.center.channels[2].type=BILL_FLOW
  6. message.center.channels[2].desc=话费和流量提醒
  7. message.center.channels[3].type=INTEGRATED_CASHIER
  8. message.center.channels[3].desc=综合收银台
  9.  
  10. message.center.businesses[0].type=BIZ_EXP_REMINDER
  11. message.center.businesses[0].desc=业务到期提醒
  12. message.center.businesses[0].topic=message-center-biz-expiration-reminder-topic
  13. message.center.businesses[1].type=RECHARGE_TRANSACTION_PUSH
  14. message.center.businesses[1].desc=充值交易实时推送
  15. message.center.businesses[1].topic=message-center-recharge-transaction-push-topic
  16.  
  17. message.center.businesses2Channels[BIZ_EXP_REMINDER]=EC_BACKEND
  18. message.center.businesses2Channels[RECHARGE_TRANSACTION_PUSH]=INTEGRATED_CASHIER
  19.  
  20. message.center.bizTypeForMsgType[RECHARGE_TRANSACTION_PUSH]=data.type:pay-finish,data.type:rechr-finish,data.type:refund-finish

java属性配置映射类

  1. import org.springframework.boot.context.properties.ConfigurationProperties;
  2.  
  3. import java.util.List;
  4. import java.util.Map;
  5. import java.util.Objects;
  6.  
  7. /**
  8. * @author hujunzheng
  9. * @create 2018-06-28 11:37
  10. **/
  11. @ConfigurationProperties(prefix = "message.center")
  12. public class MessageCenterConstants {
  13.  
  14. private List<Business> businesses;
  15.  
  16. private List<Channel> channels;
  17.  
  18. private Map<String, String> businesses2Channels;
  19.  
  20. private Map<String, String> bizTypeForMsgType;
  21.  
  22. public void setBusinesses(List<Business> businesses) {
  23. this.businesses = businesses;
  24. }
  25.  
  26. public void setChannels(List<Channel> channels) {
  27. this.channels = channels;
  28. }
  29.  
  30. public List<Business> getBusinesses() {
  31. return businesses;
  32. }
  33.  
  34. public List<Channel> getChannels() {
  35. return channels;
  36. }
  37.  
  38. public Map<String, String> getBusinesses2Channels() {
  39. return businesses2Channels;
  40. }
  41.  
  42. public void setBusinesses2Channels(Map<String, String> businesses2Channels) {
  43. this.businesses2Channels = businesses2Channels;
  44. }
  45.  
  46. public Map<String, String> getBizTypeForMsgType() {
  47. return bizTypeForMsgType;
  48. }
  49.  
  50. public void setBizTypeForMsgType(Map<String, String> bizTypeForMsgType) {
  51. this.bizTypeForMsgType = bizTypeForMsgType;
  52. }
  53.  
  54. public static class Business implements Comparable<Business> {
  55. //业务类型
  56. private String type;
  57. //业务描述
  58. private String desc;
  59. //对应 kafka 的 topic
  60. private String topic;
  61.  
  62. public String getType() {
  63. return type;
  64. }
  65.  
  66. public void setType(String type) {
  67. this.type = type;
  68. }
  69.  
  70. public String getDesc() {
  71. return desc;
  72. }
  73.  
  74. public void setDesc(String desc) {
  75. this.desc = desc;
  76. }
  77.  
  78. public String getTopic() {
  79. return topic;
  80. }
  81.  
  82. public void setTopic(String topic) {
  83. this.topic = topic;
  84. }
  85.  
  86. @Override
  87. public int compareTo(Business o) {
  88. if (type.compareTo(o.type) == 0 || topic.compareTo(o.topic) == 0) {
  89. return 0;
  90. }
  91. return Objects.hash(type, topic);
  92. }
  93.  
  94. @Override
  95. public boolean equals(Object o) {
  96. if (this == o) return true;
  97. if (o == null || getClass() != o.getClass()) return false;
  98. Business business = (Business) o;
  99. return Objects.equals(type, business.type) ||
  100. Objects.equals(topic, business.topic);
  101. }
  102.  
  103. @Override
  104. public int hashCode() {
  105. return Objects.hash(type, topic);
  106. }
  107.  
  108. @Override
  109. public String toString() {
  110. return "Business{" +
  111. "type='" + type + '\'' +
  112. ", desc='" + desc + '\'' +
  113. ", topic='" + topic + '\'' +
  114. '}';
  115. }
  116. }
  117.  
  118. public static class Channel implements Comparable<Channel> {
  119.  
  120. //渠道类型
  121. private String type;
  122. //渠道描述
  123. private String desc;
  124.  
  125. public String getType() {
  126. return type;
  127. }
  128.  
  129. public void setType(String type) {
  130. this.type = type;
  131. }
  132.  
  133. public String getDesc() {
  134. return desc;
  135. }
  136.  
  137. public void setDesc(String desc) {
  138. this.desc = desc;
  139. }
  140.  
  141. @Override
  142. public int compareTo(Channel o) {
  143. return this.type.compareTo(o.type);
  144. }
  145.  
  146. @Override
  147. public boolean equals(Object o) {
  148. if (this == o) return true;
  149. if (o == null || getClass() != o.getClass()) return false;
  150. Channel channel = (Channel) o;
  151. return Objects.equals(type, channel.type);
  152. }
  153.  
  154. @Override
  155. public int hashCode() {
  156. return Objects.hash(type);
  157. }
  158.  
  159. @Override
  160. public String toString() {
  161. return "Channel{" +
  162. "type='" + type + '\'' +
  163. ", desc='" + desc + '\'' +
  164. '}';
  165. }
  166. }
  167. }

属性刷新方案

  1. @Bean
  2. public MergedProperties kafkaMessageMergedProperties() {
  3. return ConfigCenterUtils.createToRefreshPropertiesBean(MergedProperties.class);
  4. }
  5.  
  6. public static class MergedProperties {
  7. private Map<String, MessageCenterConstants.Business> businesses;
  8. private Map<String, MessageCenterConstants.Channel> channels;
  9. //业务映射渠道
  10. private Map<String, String> businesses2Channels;
  11. //消息类型映射业务类型
  12. private Map<String, String> msgType2BizType;
  13.  
  14. public MergedProperties() throws GeneralException {
  15. this.refreshProperties();
  16. }
  17.  
  18. private void refreshProperties() throws GeneralException {
    //获取到配置中心最新的propertySource
  19. ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
  20. MessageCenterConstants messageCenterConstants = null;
         //判断属性是否刷新
  21. if (ConfigCenterUtils.propertySourceRefresh(propertySource)) {
           //将属性binding到带有@ConfigurationProperties注解的类中
  22. messageCenterConstants =
  23. RelaxedConfigurationBinder
  24. .with(MessageCenterConstants.class)
  25. .setPropertySources(propertySource)
  26. .doBind();
  27. }
         //以下是自定义处理,可忽略
  28. if (!Objects.isNull(messageCenterConstants)) {
  29. //Business.type <-> Business
  30. this.setBusinesses(Maps.newHashMap(
  31. Maps.uniqueIndex(Sets.newHashSet(messageCenterConstants.getBusinesses()), business -> business.getType())
  32. ));
  33. //Channel.type <-> Channel
  34. this.setChannels(Maps.newHashMap(
  35. Maps.uniqueIndex(Sets.newHashSet(messageCenterConstants.getChannels()), channel -> channel.getType())
  36. ));
  37.  
  38. //business <-> channels
  39. this.setBusinesses2Channels(messageCenterConstants.getBusinesses2Channels());
  40.  
  41. //消息类型映射业务类型
  42. this.setMsgType2BizType(
  43. messageCenterConstants.getBizTypeForMsgType().entrySet()
  44. .stream().map(entry -> {
  45. Map<String, String> tmpMap = Maps.newHashMap();
  46. if (StringUtils.isBlank(entry.getValue())) {
  47. return tmpMap;
  48. }
  49. Arrays.stream(entry.getValue().split(",")).forEach(value -> tmpMap.put(value, entry.getKey()));
  50. return tmpMap;
  51. }).flatMap(map -> map.entrySet().stream()).collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()))
  52. );
  53.  
  54. }
  55. }

  56.    //刷新方法
  57. private void catchRefreshProperties() {
  58. try {
  59. this.refreshProperties();
  60. } catch (Exception e) {
  61. LOGGER.error("KafkaMessageConfig 配置中心属性刷新失败", e);
  62. }
  63. }

  64.    //get方法上指定刷新属性
  65. @ToRefresh(method = "catchRefreshProperties")
  66. public Map<String, MessageCenterConstants.Business> getBusinesses() {
  67. return businesses;
  68. }
  69.  
  70. public void setBusinesses(Map<String, MessageCenterConstants.Business> businesses) {
  71. this.businesses = businesses;
  72. }
  73.  
  74. @ToRefresh(method = "catchRefreshProperties")
  75. public Map<String, MessageCenterConstants.Channel> getChannels() {
  76. return channels;
  77. }
  78.  
  79. public void setChannels(Map<String, MessageCenterConstants.Channel> channels) {
  80. this.channels = channels;
  81. }
  82.  
  83. @ToRefresh(method = "catchRefreshProperties")
  84. public Map<String, String> getBusinesses2Channels() {
  85. return businesses2Channels;
  86. }
  87.  
  88. public void setBusinesses2Channels(Map<String, String> businesses2Channels) {
  89. this.businesses2Channels = businesses2Channels;
  90. }
  91.  
  92. @ToRefresh(method = "catchRefreshProperties")
  93. public Map<String, String> getMsgType2BizType() {
  94. return msgType2BizType;
  95. }
  96.  
  97. public void setMsgType2BizType(Map<String, String> msgType2BizType) {
  98. this.msgType2BizType = msgType2BizType;
  99. }
  100. }

工具类

ConfigCenterUtils

  1. import com.cmos.cfg.core.ConfigHelper;
  2. import com.cmos.cfg.zookeeper.ZookeeperPropertySource;
  3. import org.apache.commons.lang3.StringUtils;
  4. import org.springframework.cglib.proxy.Enhancer;
  5. import org.springframework.cglib.proxy.MethodInterceptor;
  6. import org.springframework.cglib.proxy.MethodProxy;
  7. import org.springframework.core.BridgeMethodResolver;
  8. import org.springframework.core.annotation.AnnotationUtils;
  9. import org.springframework.util.ReflectionUtils;
  10.  
  11. import java.lang.reflect.Method;
  12. import java.util.Objects;
  13.  
  14. /**
  15. * @author hujunzheng
  16. * @create 2018-07-04 15:45
  17. **/
  18. public class ConfigCenterUtils {
  19. private static ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();

  20.   //判断配置中心属性是否刷新
  21. public synchronized static boolean propertySourceRefresh(ZookeeperPropertySource newPropertySource) {
  22. if (propertySource.equals(newPropertySource)) {
  23. return false;
  24. }
  25.  
  26. if (propertySource.hashCode() == newPropertySource.hashCode()) {
  27. return false;
  28. }
  29.  
  30. propertySource = newPropertySource;
  31. return true;
  32. }

  33.    //创建代理类,代理@ToRefresh注解的方法,调用相应的刷新方法
  34. public static <T> T createToRefreshPropertiesBean(Class<T> clazz) {
  35. Enhancer enhancer = new Enhancer();
  36. // 设置代理对象父类
  37. enhancer.setSuperclass(clazz);
  38. // 设置增强
  39. enhancer.setCallback(new MethodInterceptor() {
  40. @Override
  41. public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  42. ToRefresh toRefresh = AnnotationUtils.findAnnotation(method, ToRefresh.class);
  43. if (Objects.isNull(toRefresh) || StringUtils.isBlank(toRefresh.method())) {
  44. return methodProxy.invokeSuper(target, args);
  45. }
  46. Method refreshMethod = ReflectionUtils.findMethod(target.getClass(), toRefresh.method());
  47. if (Objects.isNull(refreshMethod)) {
  48. return methodProxy.invokeSuper(target, args);
  49. }
  50. refreshMethod = BridgeMethodResolver.findBridgedMethod(refreshMethod);
  51. refreshMethod.setAccessible(true);
  52. refreshMethod.invoke(target, null);
  53. return methodProxy.invokeSuper(target, args);
  54. }
  55. });
  56. return (T) enhancer.create();// 创建代理对象
  57. }
  58. }
  1. import org.apache.commons.lang3.StringUtils;
  2.  
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.Target;
  6.  
  7. import static java.lang.annotation.ElementType.METHOD;
  8. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  9.  
  10. /**
  11. * @author hujunzheng
  12. * @create 2018-07-06 9:59
  13. **/
  14. @Target({METHOD})
  15. @Retention(RUNTIME)
  16. @Documented
  17. public @interface ToRefresh {
  18. //刷新方法
  19. String method() default StringUtils.EMPTY;
  20. }

RelaxedConfigurationBinder

  动态将propertysource绑定到带有@ConfigurationProperties注解的bean中

  参考:org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor

  1. import com.cmos.common.exception.GeneralException;
  2. import org.springframework.boot.bind.PropertiesConfigurationFactory;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.core.convert.ConversionService;
  5. import org.springframework.core.convert.support.DefaultConversionService;
  6. import org.springframework.core.env.*;
  7. import org.springframework.validation.Validator;
  8. import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
  9.  
  10. import javax.validation.Validation;
  11.  
  12. import static org.springframework.core.annotation.AnnotatedElementUtils.getMergedAnnotation;
  13.  
  14. /**
  15. * @author hujunzheng
  16. * @create 2018-07-03 18:01
  17. *
  18. * 不强依赖ConfigurationProperties,进行配置注入
  19. **/
  20. public class RelaxedConfigurationBinder<T> {
  21. private final PropertiesConfigurationFactory<T> factory;
  22.  
  23. public RelaxedConfigurationBinder(T object) {
  24. this(new PropertiesConfigurationFactory<>(object));
  25. }
  26.  
  27. public RelaxedConfigurationBinder(Class<T> type) {
  28. this(new PropertiesConfigurationFactory<>(type));
  29. }
  30.  
  31. public static <T> RelaxedConfigurationBinder<T> with(T object) {
  32. return new RelaxedConfigurationBinder<>(object);
  33. }
  34.  
  35. public static <T> RelaxedConfigurationBinder<T> with(Class<T> type) {
  36. return new RelaxedConfigurationBinder<>(type);
  37. }
  38.  
  39. public RelaxedConfigurationBinder(PropertiesConfigurationFactory<T> factory) {
  40. this.factory = factory;
  41. ConfigurationProperties properties = getMergedAnnotation(factory.getObjectType(), ConfigurationProperties.class);
  42. javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
  43. factory.setValidator(new SpringValidatorAdapter(validator));
  44. factory.setConversionService(new DefaultConversionService());
  45. if (null != properties) {
  46. factory.setIgnoreNestedProperties(properties.ignoreNestedProperties());
  47. factory.setIgnoreInvalidFields(properties.ignoreInvalidFields());
  48. factory.setIgnoreUnknownFields(properties.ignoreUnknownFields());
  49. factory.setTargetName(properties.prefix());
  50. factory.setExceptionIfInvalid(properties.exceptionIfInvalid());
  51. }
  52. }
  53.  
  54. public RelaxedConfigurationBinder<T> setTargetName(String targetName) {
  55. factory.setTargetName(targetName);
  56. return this;
  57. }
  58.  
  59. public RelaxedConfigurationBinder<T> setPropertySources(PropertySource<?>... propertySources) {
  60. MutablePropertySources sources = new MutablePropertySources();
  61. for (PropertySource<?> propertySource : propertySources) {
  62. sources.addLast(propertySource);
  63. }
  64. factory.setPropertySources(sources);
  65. return this;
  66. }
  67.  
  68. public RelaxedConfigurationBinder<T> setPropertySources(Environment environment) {
  69. factory.setPropertySources(((ConfigurableEnvironment) environment).getPropertySources());
  70. return this;
  71. }
  72.  
  73. public RelaxedConfigurationBinder<T> setPropertySources(PropertySources propertySources) {
  74. factory.setPropertySources(propertySources);
  75. return this;
  76. }
  77.  
  78. public RelaxedConfigurationBinder<T> setConversionService(ConversionService conversionService) {
  79. factory.setConversionService(conversionService);
  80. return this;
  81. }
  82.  
  83. public RelaxedConfigurationBinder<T> setValidator(Validator validator) {
  84. factory.setValidator(validator);
  85. return this;
  86. }
  87.  
  88. public RelaxedConfigurationBinder<T> setResolvePlaceholders(boolean resolvePlaceholders) {
  89. factory.setResolvePlaceholders(resolvePlaceholders);
  90. return this;
  91. }
  92.  
  93. public T doBind() throws GeneralException {
  94. try {
  95. return factory.getObject();
  96. } catch (Exception ex) {
  97. throw new GeneralException("配置绑定失败!", ex);
  98. }
  99. }
  100. }

springboot项目接入配置中心,实现@ConfigurationProperties的bean属性刷新方案的更多相关文章

  1. SpringBoot使用Nacos配置中心

    本文介绍SpringBoot如何使用阿里巴巴Nacos做配置中心. 1.Nacos简介 Nacos是阿里巴巴集团开源的一个易于使用的平台,专为动态服务发现,配置和服务管理而设计.它可以帮助您轻松构建云 ...

  2. 【Nacos】Springboot整合Nacos配置中心(二) 多环境配置

    本篇随笔接上一篇文章:Springboot整合Nacos配置中心(一),主要记录Nacos多环境的配置的方法 Nacos多环境的配置 方法一: 1.在项目中的bootstrap.yaml文件中配置激活 ...

  3. Spring-Boot项目中配置redis注解缓存

    Spring-Boot项目中配置redis注解缓存 在pom中添加redis缓存支持依赖 <dependency> <groupId>org.springframework.b ...

  4. 携程Apollo(阿波罗)配置中心本地开发模式不接入配置中心进行本地开发

    官方教程:https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8 ...

  5. SpringBoot项目实现配置实时刷新功能

    需求描述:在SpringBoot项目中,一般业务配置都是写死在配置文件中的,如果某个业务配置想修改,就得重启项目.这在生产环境是不被允许的,这就需要通过技术手段做到配置变更后即使生效.下面就来看一下怎 ...

  6. 【Nacos】Springboot整合nacos配置中心(一)

    一.本地Nacos安装环境: Win7 ,JDK8 ,maven3.5 1.下载安装包 2.启动nacos服务,bin文件下下面startup.cmd 3.访问 http://localhost:88 ...

  7. 基于springBoot项目如何配置多数据源

    前言 有时,在一个项目中会用到多数据源,现在对自己在项目中多数据源的操作总结如下,有不到之处敬请批评指正! 1.pom.xml的依赖引入 <dependency> <groupId& ...

  8. springboot项目中配置swagger-ui

    Git官方地址:https://github.com/SpringForAll/spring-boot-starter-swagger Demo:https://github.com/dyc87112 ...

  9. jasypt在springboot项目中遇到异常:Error creating bean with name 'enableEncryptablePropertySourcesPostProcessor' defined in class path resource

    背景 在使用jasypt对spring boot的配置文件中的敏感信息进行加密处理时,使用stater直接启动时,遇到了一个异常 <dependency> <groupId>c ...

随机推荐

  1. Weblogic的安装与卸载

    一.下载weblogic 到Oracle官网https://www.oracle.com/downloads/index.html,我在这里下载的是weblogic12C进行安装:https://ww ...

  2. Spring使用注解和struts集成

  3. K-means聚类算法原理和C++实现

    给定训练集$\{x^{(1)},...,x^{(m)}\}$,想把这些样本分成不同的子集,即聚类,$x^{(i)}\in\mathbb{R^{n}}$,但是这是个无标签数据集,也就是说我们再聚类的时候 ...

  4. 《像计算机科学家一样思考Python》-递归

    斐波那契数列 使用递归定义的最常见数学函数是 fibonacci (斐波那契数列),见其 定义 fibonacci(0) = 0 fibonacci(1) = 1 fibonacci(n) = fib ...

  5. 【网络编程4】网络编程基础-ARP响应(ARP欺骗之中间人攻击)

    arp欺骗->arp响应 ARP 缓存中毒(ARP欺骗) arp传送原理在于主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址:收到返回消 ...

  6. linux 定期清除日志

    clearLog.sh #!/bin/sh find /usr/local/apache/logs -mtime + 30 -name "*.log" -exec rm {} \; ...

  7. 没有备份怎么恢复被drop的表(利用undrop-for-innodb)

    介绍:     也许大家都难以理解,这么重要的数据为啥不备份(或者备份不可用)?而且还任性的drop table了.显然有备份是最好的,但是它们并不总是可用的.这种情况令人恐惧,但并非毫无希望.在许多 ...

  8. ocos2d-x 3.0坐标系详解--透彻篇 ---- convertToWorldSpace:把基于当前节点的本地坐标系下的坐标转换到世界坐标系中。

    convertToWorldSpace:把基于当前节点的本地坐标系下的坐标转换到世界坐标系中.重点说明:基于...   不一定要是真实的,  convertToWorldSpace 的结果也只是一个新 ...

  9. js对象深拷贝

    数组一维深拷贝:slice.concat.Array.from 对象一维深拷贝:Object.assign 一.利用扩展运算符...对数组中嵌套对象进行深拷贝 var arr=[{a:1,b:2},{ ...

  10. LeetCode(52):N皇后 II

    Hard! 题目描述: n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 上图为 8 皇后问题的一种解法. 给定一个整数 n,返回 n 皇后不同的解决方 ...