前言

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

配置中心

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

  

public class ZookeeperPropertySource extends EnumerablePropertySource<Properties>

  

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

  

配置中心定义的属性变量

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

java属性配置映射类

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;
import java.util.Map;
import java.util.Objects; /**
* @author hujunzheng
* @create 2018-06-28 11:37
**/
@ConfigurationProperties(prefix = "message.center")
public class MessageCenterConstants { private List<Business> businesses; private List<Channel> channels; private Map<String, String> businesses2Channels; private Map<String, String> bizTypeForMsgType; public void setBusinesses(List<Business> businesses) {
this.businesses = businesses;
} public void setChannels(List<Channel> channels) {
this.channels = channels;
} public List<Business> getBusinesses() {
return businesses;
} public List<Channel> getChannels() {
return channels;
} public Map<String, String> getBusinesses2Channels() {
return businesses2Channels;
} public void setBusinesses2Channels(Map<String, String> businesses2Channels) {
this.businesses2Channels = businesses2Channels;
} public Map<String, String> getBizTypeForMsgType() {
return bizTypeForMsgType;
} public void setBizTypeForMsgType(Map<String, String> bizTypeForMsgType) {
this.bizTypeForMsgType = bizTypeForMsgType;
} public static class Business implements Comparable<Business> {
//业务类型
private String type;
//业务描述
private String desc;
//对应 kafka 的 topic
private String topic; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} public String getTopic() {
return topic;
} public void setTopic(String topic) {
this.topic = topic;
} @Override
public int compareTo(Business o) {
if (type.compareTo(o.type) == 0 || topic.compareTo(o.topic) == 0) {
return 0;
}
return Objects.hash(type, topic);
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Business business = (Business) o;
return Objects.equals(type, business.type) ||
Objects.equals(topic, business.topic);
} @Override
public int hashCode() {
return Objects.hash(type, topic);
} @Override
public String toString() {
return "Business{" +
"type='" + type + '\'' +
", desc='" + desc + '\'' +
", topic='" + topic + '\'' +
'}';
}
} public static class Channel implements Comparable<Channel> { //渠道类型
private String type;
//渠道描述
private String desc; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} @Override
public int compareTo(Channel o) {
return this.type.compareTo(o.type);
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Channel channel = (Channel) o;
return Objects.equals(type, channel.type);
} @Override
public int hashCode() {
return Objects.hash(type);
} @Override
public String toString() {
return "Channel{" +
"type='" + type + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
}

属性刷新方案

@Bean
public MergedProperties kafkaMessageMergedProperties() {
return ConfigCenterUtils.createToRefreshPropertiesBean(MergedProperties.class);
} public static class MergedProperties {
private Map<String, MessageCenterConstants.Business> businesses;
private Map<String, MessageCenterConstants.Channel> channels;
//业务映射渠道
private Map<String, String> businesses2Channels;
//消息类型映射业务类型
private Map<String, String> msgType2BizType; public MergedProperties() throws GeneralException {
this.refreshProperties();
} private void refreshProperties() throws GeneralException {
//获取到配置中心最新的propertySource
ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
MessageCenterConstants messageCenterConstants = null;
     //判断属性是否刷新
if (ConfigCenterUtils.propertySourceRefresh(propertySource)) {
       //将属性binding到带有@ConfigurationProperties注解的类中
messageCenterConstants =
RelaxedConfigurationBinder
.with(MessageCenterConstants.class)
.setPropertySources(propertySource)
.doBind();
}
     //以下是自定义处理,可忽略
if (!Objects.isNull(messageCenterConstants)) {
//Business.type <-> Business
this.setBusinesses(Maps.newHashMap(
Maps.uniqueIndex(Sets.newHashSet(messageCenterConstants.getBusinesses()), business -> business.getType())
));
//Channel.type <-> Channel
this.setChannels(Maps.newHashMap(
Maps.uniqueIndex(Sets.newHashSet(messageCenterConstants.getChannels()), channel -> channel.getType())
)); //business <-> channels
this.setBusinesses2Channels(messageCenterConstants.getBusinesses2Channels()); //消息类型映射业务类型
this.setMsgType2BizType(
messageCenterConstants.getBizTypeForMsgType().entrySet()
.stream().map(entry -> {
Map<String, String> tmpMap = Maps.newHashMap();
if (StringUtils.isBlank(entry.getValue())) {
return tmpMap;
}
Arrays.stream(entry.getValue().split(",")).forEach(value -> tmpMap.put(value, entry.getKey()));
return tmpMap;
}).flatMap(map -> map.entrySet().stream()).collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()))
); }
}

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

   //get方法上指定刷新属性
@ToRefresh(method = "catchRefreshProperties")
public Map<String, MessageCenterConstants.Business> getBusinesses() {
return businesses;
} public void setBusinesses(Map<String, MessageCenterConstants.Business> businesses) {
this.businesses = businesses;
} @ToRefresh(method = "catchRefreshProperties")
public Map<String, MessageCenterConstants.Channel> getChannels() {
return channels;
} public void setChannels(Map<String, MessageCenterConstants.Channel> channels) {
this.channels = channels;
} @ToRefresh(method = "catchRefreshProperties")
public Map<String, String> getBusinesses2Channels() {
return businesses2Channels;
} public void setBusinesses2Channels(Map<String, String> businesses2Channels) {
this.businesses2Channels = businesses2Channels;
} @ToRefresh(method = "catchRefreshProperties")
public Map<String, String> getMsgType2BizType() {
return msgType2BizType;
} public void setMsgType2BizType(Map<String, String> msgType2BizType) {
this.msgType2BizType = msgType2BizType;
}
}

工具类

ConfigCenterUtils

import com.cmos.cfg.core.ConfigHelper;
import com.cmos.cfg.zookeeper.ZookeeperPropertySource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method;
import java.util.Objects; /**
* @author hujunzheng
* @create 2018-07-04 15:45
**/
public class ConfigCenterUtils {
private static ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();

  //判断配置中心属性是否刷新
public synchronized static boolean propertySourceRefresh(ZookeeperPropertySource newPropertySource) {
if (propertySource.equals(newPropertySource)) {
return false;
} if (propertySource.hashCode() == newPropertySource.hashCode()) {
return false;
} propertySource = newPropertySource;
return true;
}

   //创建代理类,代理@ToRefresh注解的方法,调用相应的刷新方法
public static <T> T createToRefreshPropertiesBean(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
// 设置代理对象父类
enhancer.setSuperclass(clazz);
// 设置增强
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
ToRefresh toRefresh = AnnotationUtils.findAnnotation(method, ToRefresh.class);
if (Objects.isNull(toRefresh) || StringUtils.isBlank(toRefresh.method())) {
return methodProxy.invokeSuper(target, args);
}
Method refreshMethod = ReflectionUtils.findMethod(target.getClass(), toRefresh.method());
if (Objects.isNull(refreshMethod)) {
return methodProxy.invokeSuper(target, args);
}
refreshMethod = BridgeMethodResolver.findBridgedMethod(refreshMethod);
refreshMethod.setAccessible(true);
refreshMethod.invoke(target, null);
return methodProxy.invokeSuper(target, args);
}
});
return (T) enhancer.create();// 创建代理对象
}
}
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* @author hujunzheng
* @create 2018-07-06 9:59
**/
@Target({METHOD})
@Retention(RUNTIME)
@Documented
public @interface ToRefresh {
//刷新方法
String method() default StringUtils.EMPTY;
}

RelaxedConfigurationBinder

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

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

import com.cmos.common.exception.GeneralException;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.*;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter; import javax.validation.Validation; import static org.springframework.core.annotation.AnnotatedElementUtils.getMergedAnnotation; /**
* @author hujunzheng
* @create 2018-07-03 18:01
*
* 不强依赖ConfigurationProperties,进行配置注入
**/
public class RelaxedConfigurationBinder<T> {
private final PropertiesConfigurationFactory<T> factory; public RelaxedConfigurationBinder(T object) {
this(new PropertiesConfigurationFactory<>(object));
} public RelaxedConfigurationBinder(Class<T> type) {
this(new PropertiesConfigurationFactory<>(type));
} public static <T> RelaxedConfigurationBinder<T> with(T object) {
return new RelaxedConfigurationBinder<>(object);
} public static <T> RelaxedConfigurationBinder<T> with(Class<T> type) {
return new RelaxedConfigurationBinder<>(type);
} public RelaxedConfigurationBinder(PropertiesConfigurationFactory<T> factory) {
this.factory = factory;
ConfigurationProperties properties = getMergedAnnotation(factory.getObjectType(), ConfigurationProperties.class);
javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
factory.setValidator(new SpringValidatorAdapter(validator));
factory.setConversionService(new DefaultConversionService());
if (null != properties) {
factory.setIgnoreNestedProperties(properties.ignoreNestedProperties());
factory.setIgnoreInvalidFields(properties.ignoreInvalidFields());
factory.setIgnoreUnknownFields(properties.ignoreUnknownFields());
factory.setTargetName(properties.prefix());
factory.setExceptionIfInvalid(properties.exceptionIfInvalid());
}
} public RelaxedConfigurationBinder<T> setTargetName(String targetName) {
factory.setTargetName(targetName);
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(PropertySource<?>... propertySources) {
MutablePropertySources sources = new MutablePropertySources();
for (PropertySource<?> propertySource : propertySources) {
sources.addLast(propertySource);
}
factory.setPropertySources(sources);
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(Environment environment) {
factory.setPropertySources(((ConfigurableEnvironment) environment).getPropertySources());
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(PropertySources propertySources) {
factory.setPropertySources(propertySources);
return this;
} public RelaxedConfigurationBinder<T> setConversionService(ConversionService conversionService) {
factory.setConversionService(conversionService);
return this;
} public RelaxedConfigurationBinder<T> setValidator(Validator validator) {
factory.setValidator(validator);
return this;
} public RelaxedConfigurationBinder<T> setResolvePlaceholders(boolean resolvePlaceholders) {
factory.setResolvePlaceholders(resolvePlaceholders);
return this;
} public T doBind() throws GeneralException {
try {
return factory.getObject();
} catch (Exception ex) {
throw new GeneralException("配置绑定失败!", ex);
}
}
}

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. Database学习 - mysql 连接数据库 库操作

    连接数据库 语法格式: mysql -h 服务器IP -P 端口号 -u用户名 -p密码 --prompt 命令提示符 --delimiter 指定分隔符 示例: mysql -h 127.0.0.1 ...

  2. Java的三种代理模式:静态代理/JDK动态代理/Cglib动态代理

    1.静态代理:需要定义接口或者父类,目标对象与代理对象均实现同一接口或继承同一父类. 2.JDK动态代理:需要目标对象实现一个接口,通过动态反射的机制,生成代理对象,实现同一个接口 3.Cglib动态 ...

  3. Ngnix + Tomcat负载均衡架构

    一.nginx Nginx (发音同 engine x)是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.  其特点是占有内 ...

  4. JDK7与8内存参数解析与修改

    JDK内存 jdk内存实际是jvm内存,jvm有一个运行时数据区,其实就是对这一部分的大小分配. 运行时数据区通常包括这几个部分:程序计数器(Program Counter Register).Jav ...

  5. 【黑客免杀攻防】读书笔记8 - 软件逆向工程基础2(if-else,三目运算符)

    0x1 if-else分支 if-else分支4种状态 1.1 以常量为判断条件的简单if-else分支 C源代码: 单层if-else判断,常量为判断条件 int _tmain(int argc, ...

  6. Windows域帐户

    域的直观优点: 1.域帐户可以在任意一台已经加入域的电脑上登录. 2.将域用户组加入到SQL Server登录里,域用户组内所有人员便都可以使用域用户登录数据库,继承相关权限. 3.域用户登录Team ...

  7. Python创建、删除桌面、启动组快捷方式的例子分享

    一.Python创桌面建快捷方式的2个例子 例子一: 代码如下: import osimport pythoncomfrom win32com.shell import shell    from w ...

  8. 『转载』Matlab中fmincon函数获取乘子

    Matlab中fmincon函数获取乘子 一.输出结构 [x,fval,exitflag,output,lambda] = fmincon(......) 二.结构说明 lambda结构 说     ...

  9. mysql安装与卸载(绿色版)

    1.下载压缩包,解压 2.配置环境变量 PATH:%MYSQL_HOME%\bin 3.在安装目录下新建my.ini配置文件: [mysql] default-character-set=utf8 [ ...

  10. VS2017编译boost库

    1.http://www.boost.org/     下载boost库. 2.解压到 D:\ProgramFiles\boost 3.环境配变量配置     VS2017更加注重跨平台性,安装文件较 ...