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-topicmessage.center.businesses2Channels[BIZ_EXP_REMINDER]=EC_BACKEND
message.center.businesses2Channels[RECHARGE_TRANSACTION_PUSH]=INTEGRATED_CASHIERmessage.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 的 topicprivate 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;}@Overridepublic int compareTo(Business o) {if (type.compareTo(o.type) == 0 || topic.compareTo(o.topic) == 0) {return 0;}return Objects.hash(type, topic);}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(type, topic);}@Overridepublic 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;}@Overridepublic int compareTo(Channel o) {return this.type.compareTo(o.type);}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(type);}@Overridepublic 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 {
//獲取到配置中心最新的propertySourceZookeeperPropertySource 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 <-> Businessthis.setBusinesses(Maps.newHashMap(Maps.uniqueIndex(Sets.newHashSet(messageCenterConstants.getBusinesses()), business -> business.getType())));//Channel.type <-> Channelthis.setChannels(Maps.newHashMap(Maps.uniqueIndex(Sets.newHashSet(messageCenterConstants.getChannels()), channel -> channel.getType())));//business <-> channelsthis.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() {@Overridepublic 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);}}
}

?

轉載于:https://www.cnblogs.com/hujunzheng/p/9272866.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/531226.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/531226.shtml
英文地址,請注明出處:http://en.pswp.cn/news/531226.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

jackson實現java對象轉支付寶/微信模板消息

一、支付寶消息模板大致長這樣 {"to_user_id": "","telephone": "xxxxx","template": {"template_id": "xxxxxx","context": {"head_color": "#85be53","url"…

簡單封裝kafka相關的api

一、針對于kafka版本 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>0.8.2.2</version></dependency><dependency><groupId>org.apache.kafka</groupId>…

springmvc controller動態設置content-type

springmvc RequestMappingHandlerAdapter#invokeHandlerMethod 通過ServletInvocableHandlerMethod#invokeAndHandle調用目標方法&#xff0c;并處理返回值。 如果return value &#xff01; null&#xff0c;則通過returnvalueHandlers處理&#xff0c;內部會調用MessageConv…

springboot2.0 redis EnableCaching的配置和使用

一、前言 關于EnableCaching最簡單使用&#xff0c;個人感覺只需提供一個CacheManager的一個實例就好了。springboot為我們提供了cache相關的自動配置。引入cache模塊&#xff0c;如下。 二、maven依賴 <dependency><groupId>org.springframework.boot</groupId…

依賴配置中心實現注有@ConfigurationProperties的bean相關屬性刷新

配置中心是什么 配置中心&#xff0c;通過keyvalue的形式存儲環境變量。配置中心的屬性做了修改&#xff0c;項目中可以通過配置中心的依賴&#xff08;sdk&#xff09;立即感知到。需要做的就是如何在屬性發生變化時&#xff0c;改變帶有ConfigurationProperties的bean的相關屬…

java接口簽名(Signature)實現方案

預祝大家國慶節快樂&#xff0c;趕快迎接美麗而快樂的假期吧&#xff01;&#xff01;&#xff01; 前言 在為第三方系統提供接口的時候&#xff0c;肯定要考慮接口數據的安全問題&#xff0c;比如數據是否被篡改&#xff0c;數據是否已經過時&#xff0c;數據是否可以重復提交…

Git rebase命令實戰

一、前言 一句話&#xff0c;git rebase 可以幫助項目中的提交歷史干凈整潔&#xff01;&#xff01;&#xff01; 二、避免合并出現分叉現象 git merge操作 1、新建一個 develop 分支 2、在develop分支上新建兩個文件 3、然后分別執行 add、commit、push 4、接著切換到master分…

HttpServletRequestWrapper使用技巧(自定義session和緩存InputStream)

一、前言 javax.servlet.http.HttpServletRequestWrapper 是一個開發者可以繼承的類&#xff0c;我們可以重寫相應的方法來實現session的自定義以及緩存InputStream&#xff0c;在程序中可以多次獲取request body的內容。 二、自定義seesion import javax.servlet.http.*;publi…

spring注解工具類AnnotatedElementUtils和AnnotationUtils

一、前言 spring為開發人員提供了兩個搜索注解的工具類&#xff0c;分別是AnnotatedElementUtils和AnnotationUtils。在使用的時候&#xff0c;總是傻傻分不清&#xff0c;什么情況下使用哪一個。于是我做了如下的整理和總結。 二、AnnotationUtils官方解釋 功能 用于處理注解&…

windows系統nexus3安裝和配置

一、前言 為什么要在本地開發機器上安裝nexus&#xff1f;首先聲明公司內部是有自己的nexus倉庫&#xff0c;但是對上傳jar包做了限制&#xff0c;不能暢快的上傳自己測試包依賴。于是就自己在本地搭建了一個nexus私服&#xff0c;即可以使用公司nexus私服倉庫中的依賴&#xf…

Springmvc借助SimpleUrlHandlerMapping實現接口開關功能

一、接口開關功能 1、可配置化&#xff0c;依賴配置中心 2、接口訪問權限可控 3、springmvc不會掃描到&#xff0c;即不會直接的將接口暴露出去 二、接口開關使用場景 和業務沒什么關系&#xff0c;主要方便查詢系統中的一些狀態信息。比如系統的配置信息&#xff0c;中間件的狀…

log4j平穩升級到log4j2

一、前言 公司中的項目雖然已經用了很多的新技術了&#xff0c;但是日志的底層框架還是log4j&#xff0c;個人還是不喜歡用這個的。最近項目再生產環境上由于log4j引起了一場血案&#xff0c;于是決定升級到log4j2。 二、現象 雖然生產環境有多個結點分散高并發帶來的壓力&…

Springboot集成ES啟動報錯

報錯內容 None of the configured nodes are available elasticsearch.yml配置 cluster.name: ftest node.name: node-72 node.master: true node.data: true network.host: 112.122.245.212 http.port: 39200 transport.tcp.port: 39300 discovery.zen.ping.unicast.hosts: [&…

高效使用hibernate-validator校驗框架

一、前言 高效、合理的使用hibernate-validator校驗框架可以提高程序的可讀性&#xff0c;以及減少不必要的代碼邏輯。接下來會介紹一下常用一些使用方式。 二、常用注解說明 限制說明Null限制只能為nullNotNull限制必須不為nullAssertFalse限制必須為falseAssertTrue限制必須為…

kafka-manager配置和使用

kafka-manager配置 最主要配置就是用于kafka管理器狀態的zookeeper主機。這可以在conf目錄中的application.conf文件中找到。 kafka-manager.zkhosts"my.zookeeper.host.com:2181" 當然也可以聲明為zookeeper集群。 kafka-manager.zkhosts"my.zookeeper.host.co…

kafka告警簡單方案

一、前言 為什么要設計kafka告警方案&#xff1f;現成的監控項目百度一下一大堆&#xff0c;KafkaOffsetMonitor、KafkaManager、 Burrow等&#xff0c;具體參考&#xff1a;kafka的消息擠壓監控。由于本小組的項目使用的kafka集群并沒有被公司的kafka-manager管理&#xff0c;…

RedisCacheManager設置Value序列化器技巧

CacheManager基本配置 請參考博文&#xff1a;springboot2.0 redis EnableCaching的配置和使用 RedisCacheManager構造函數 /*** Construct a {link RedisCacheManager}.* * param redisOperations*/ SuppressWarnings("rawtypes") public RedisCacheManager(RedisOp…

Nginx配置以及域名轉發

工程中的nginx配置 #user nobody; worker_processes 24; error_log /home/xxx/opt/nginx/logs/error.log; pid /home/xxx/opt/nginx/run/nginx.pid;events {use epoll;worker_connections 102400; }http {include /home/xxx/opt/nginx/conf.d/mime.types;default_…

java接口簽名(Signature)實現方案續

一、前言 由于之前寫過的一片文章 &#xff08;java接口簽名(Signature)實現方案 &#xff09;收獲了很多好評&#xff0c;此次來說一下另一種簡單粗暴的簽名方案。相對于之前的簽名方案&#xff0c;對body、paramenter、path variable的獲取都做了簡化的處理。也就是說這種方式…

支付寶敏感信息解密

支付寶官方解密文檔&#xff1a;https://docs.alipay.com/mini/introduce/aes String response "小程序前端提交的";//1. 獲取驗簽和解密所需要的參數 Map<String, String> openapiResult JSON.parseObject(response,new TypeReference<Map<String, St…