springboot项目接入配置中心,实现@ConfigurationProperties的bean属性刷新方案
前言
配置中心,通过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属性刷新方案的更多相关文章
- SpringBoot使用Nacos配置中心
本文介绍SpringBoot如何使用阿里巴巴Nacos做配置中心. 1.Nacos简介 Nacos是阿里巴巴集团开源的一个易于使用的平台,专为动态服务发现,配置和服务管理而设计.它可以帮助您轻松构建云 ...
- 【Nacos】Springboot整合Nacos配置中心(二) 多环境配置
本篇随笔接上一篇文章:Springboot整合Nacos配置中心(一),主要记录Nacos多环境的配置的方法 Nacos多环境的配置 方法一: 1.在项目中的bootstrap.yaml文件中配置激活 ...
- Spring-Boot项目中配置redis注解缓存
Spring-Boot项目中配置redis注解缓存 在pom中添加redis缓存支持依赖 <dependency> <groupId>org.springframework.b ...
- 携程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 ...
- SpringBoot项目实现配置实时刷新功能
需求描述:在SpringBoot项目中,一般业务配置都是写死在配置文件中的,如果某个业务配置想修改,就得重启项目.这在生产环境是不被允许的,这就需要通过技术手段做到配置变更后即使生效.下面就来看一下怎 ...
- 【Nacos】Springboot整合nacos配置中心(一)
一.本地Nacos安装环境: Win7 ,JDK8 ,maven3.5 1.下载安装包 2.启动nacos服务,bin文件下下面startup.cmd 3.访问 http://localhost:88 ...
- 基于springBoot项目如何配置多数据源
前言 有时,在一个项目中会用到多数据源,现在对自己在项目中多数据源的操作总结如下,有不到之处敬请批评指正! 1.pom.xml的依赖引入 <dependency> <groupId& ...
- springboot项目中配置swagger-ui
Git官方地址:https://github.com/SpringForAll/spring-boot-starter-swagger Demo:https://github.com/dyc87112 ...
- jasypt在springboot项目中遇到异常:Error creating bean with name 'enableEncryptablePropertySourcesPostProcessor' defined in class path resource
背景 在使用jasypt对spring boot的配置文件中的敏感信息进行加密处理时,使用stater直接启动时,遇到了一个异常 <dependency> <groupId>c ...
随机推荐
- Weblogic的安装与卸载
一.下载weblogic 到Oracle官网https://www.oracle.com/downloads/index.html,我在这里下载的是weblogic12C进行安装:https://ww ...
- Spring使用注解和struts集成
- K-means聚类算法原理和C++实现
给定训练集$\{x^{(1)},...,x^{(m)}\}$,想把这些样本分成不同的子集,即聚类,$x^{(i)}\in\mathbb{R^{n}}$,但是这是个无标签数据集,也就是说我们再聚类的时候 ...
- 《像计算机科学家一样思考Python》-递归
斐波那契数列 使用递归定义的最常见数学函数是 fibonacci (斐波那契数列),见其 定义 fibonacci(0) = 0 fibonacci(1) = 1 fibonacci(n) = fib ...
- 【网络编程4】网络编程基础-ARP响应(ARP欺骗之中间人攻击)
arp欺骗->arp响应 ARP 缓存中毒(ARP欺骗) arp传送原理在于主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址:收到返回消 ...
- linux 定期清除日志
clearLog.sh #!/bin/sh find /usr/local/apache/logs -mtime + 30 -name "*.log" -exec rm {} \; ...
- 没有备份怎么恢复被drop的表(利用undrop-for-innodb)
介绍: 也许大家都难以理解,这么重要的数据为啥不备份(或者备份不可用)?而且还任性的drop table了.显然有备份是最好的,但是它们并不总是可用的.这种情况令人恐惧,但并非毫无希望.在许多 ...
- ocos2d-x 3.0坐标系详解--透彻篇 ---- convertToWorldSpace:把基于当前节点的本地坐标系下的坐标转换到世界坐标系中。
convertToWorldSpace:把基于当前节点的本地坐标系下的坐标转换到世界坐标系中.重点说明:基于... 不一定要是真实的, convertToWorldSpace 的结果也只是一个新 ...
- js对象深拷贝
数组一维深拷贝:slice.concat.Array.from 对象一维深拷贝:Object.assign 一.利用扩展运算符...对数组中嵌套对象进行深拷贝 var arr=[{a:1,b:2},{ ...
- LeetCode(52):N皇后 II
Hard! 题目描述: n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 上图为 8 皇后问题的一种解法. 给定一个整数 n,返回 n 皇后不同的解决方 ...