實時刷新是什么意思,SpringBoot項目實現配置實時刷新功能

 2023-10-18 阅读 30 评论 0

摘要:需求描述:在SpringBoot項目中,一般業務配置都是寫死在配置文件中的,如果某個業務配置想修改,就得重啟項目。這在生產環境是不被允許的,這就需要通過技術手段做到配置變更后即使生效。下面就來看一下怎么實現這個功能。 實時刷新是什么意思

需求描述:在SpringBoot項目中,一般業務配置都是寫死在配置文件中的,如果某個業務配置想修改,就得重啟項目。這在生產環境是不被允許的,這就需要通過技術手段做到配置變更后即使生效。下面就來看一下怎么實現這個功能。

實時刷新是什么意思、來一張核心代碼截圖:

----------------------------------------------------------------------------

實現思路:
我們知道Spring提供了@Value注解來獲取配置文件中的配置項,我們也可以自己定義一個注解來模仿Spring的這種獲取配置的方式,
只不過@Value獲取的是靜態的配置,而我們的注解要實現配置能實時刷新。比如我使用@DynamicConf("${key}")來引用配置,在SpringBoot工程啟動的時候,
就掃描項目中所有使用了該注解的Bean屬性,將配置信息從數據庫中讀取出來放到本地緩存,然后挨個賦值給加了@DynamicConf注解的屬性。
當配置有變更時,就動態給這個屬性重新賦值。這就是最核心的思路,下面看如何用代碼實現。

?

1.創建一張數據表,用于存儲配置信息:

CREATE TABLE `s_system_dict` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵,唯一標識',`dict_name` varchar(64) NOT NULL COMMENT '字典名稱',`dict_key` varchar(255) NOT NULL COMMENT '字典KEY',`dict_value` varchar(2000) NOT NULL COMMENT '字典VALUE',`dict_type` int(11) NOT NULL DEFAULT '0' COMMENT '字典類型 0系統配置 1微信配置 2支付寶配置 3推送 4短信 5版本',`dict_desc` varchar(255) NOT NULL DEFAULT '' COMMENT '字典描述',`status` int(4) NOT NULL DEFAULT '1' COMMENT '字典狀態:0-停用 1-正常',`delete_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否刪除:0-未刪除 1-已刪除',`operator` int(11) NOT NULL COMMENT '操作人ID,關聯用戶域用戶表ID',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',`delete_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '刪除時間',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8 COMMENT='配置字典';

?

2.自定義注解

import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicConf {String value();String defaultValue() default "";boolean callback() default true;
}

?

3.配置變更接口

public interface DynamicConfListener {void onChange(String key, String value) throws Exception;}

?

4.配置變更實現:

public class BeanRefreshDynamicConfListener implements DynamicConfListener {public static class BeanField {private String beanName;private String property;public BeanField() {}public BeanField(String beanName, String property) {this.beanName = beanName;this.property = property;}public String getBeanName() {return beanName;}public void setBeanName(String beanName) {this.beanName = beanName;}public String getProperty() {return property;}public void setProperty(String property) {this.property = property;}}private static Map<String, List<BeanField>> key2BeanField = new ConcurrentHashMap<>();public static void addBeanField(String key, BeanField beanField) {List<BeanField> beanFieldList = key2BeanField.get(key);if (beanFieldList == null) {beanFieldList = new ArrayList<>();key2BeanField.put(key, beanFieldList);}for (BeanField item : beanFieldList) {if (item.getBeanName().equals(beanField.getBeanName()) && item.getProperty().equals(beanField.getProperty())) {return; // avoid repeat refresh
            }}beanFieldList.add(beanField);}/*** refresh bean field** @param key* @param value* @throws Exception*/@Overridepublic void onChange(String key, String value) throws Exception {List<BeanField> beanFieldList = key2BeanField.get(key);if (beanFieldList != null && beanFieldList.size() > 0) {for (BeanField beanField : beanFieldList) {DynamicConfFactory.refreshBeanField(beanField, value, null);}}}
}

?

5.用一個工程包裝一下

public class DynamicConfListenerFactory {/*** dynamic config listener repository*/private static List<DynamicConfListener> confListenerRepository = Collections.synchronizedList(new ArrayList<>());/*** add listener** @param confListener* @return*/public static boolean addListener(DynamicConfListener confListener) {if (confListener == null) {return false;}confListenerRepository.add(confListener);return true;}/*** refresh bean field** @param key* @param value*/public static void onChange(String key, String value) {if (key == null || key.trim().length() == 0) {return;}if (confListenerRepository.size() > 0) {for (DynamicConfListener confListener : confListenerRepository) {try {confListener.onChange(key, value);} catch (Exception e) {log.error(">>>>>>>>>>> refresh bean field, key={}, value={}, exception={}", key, value, e);}}}}}

?

6.對Spring的擴展,實現實時刷新功能最核心的部分

public class DynamicConfFactory extends InstantiationAwareBeanPostProcessorAdapter implements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware {

// 注入操作配置信息的業務類@Autowired
private SystemDictService systemDictService;@Overridepublic void afterPropertiesSet() {DynamicConfBaseFactory.init();
// 啟動時將數據庫中的配置緩存到本地(用一個Map存)LocalDictMap.setDictMap(systemDictService.all());
}@Overridepublic void destroy() {DynamicConfBaseFactory.destroy();}@Overridepublic boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException {if (!beanName.equals(this.beanName)) {ReflectionUtils.doWithFields(bean.getClass(), field -> {if (field.isAnnotationPresent(DynamicConf.class)) {String propertyName = field.getName();DynamicConf dynamicConf = field.getAnnotation(DynamicConf.class);String confKey = dynamicConf.value();confKey = confKeyParse(confKey);
            // 從本地緩存中獲取配置String confValue
= LocalDictMap.getDict(confKey);confValue = !StringUtils.isEmpty(confValue) ? confValue : "";BeanRefreshDynamicConfListener.BeanField beanField = new BeanRefreshDynamicConfListener.BeanField(beanName, propertyName); refreshBeanField(beanField, confValue, bean);if (dynamicConf.callback()) {BeanRefreshDynamicConfListener.addBeanField(confKey, beanField);}}});}return super.postProcessAfterInstantiation(bean, beanName);}public static void refreshBeanField(final BeanRefreshDynamicConfListener.BeanField beanField, final String value, Object bean) {if (bean == null) {try {
          // 如果你的項目使用了Aop,比如AspectJ,那么有些Bean可能會被代理,
          // 這里你獲取到的可能就不是真實的Bean而是被代理后的Bean,所以這里獲取真實的Bean;bean
= AopTargetUtils.getTarget(DynamicConfFactory.beanFactory.getBean(beanField.getBeanName()));} catch (Exception e) {log.error(">>>>>>>>>>>> Get target bean fail!!!!!");}}if (bean == null) {return;}BeanWrapper beanWrapper = new BeanWrapperImpl(bean);PropertyDescriptor propertyDescriptor = null;PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();if (propertyDescriptors != null && propertyDescriptors.length > 0) {for (PropertyDescriptor item : propertyDescriptors) {if (beanField.getProperty().equals(item.getName())) {propertyDescriptor = item;}}}if (propertyDescriptor != null && propertyDescriptor.getWriteMethod() != null) {beanWrapper.setPropertyValue(beanField.getProperty(), value);log.info(">>>>>>>>>>> refresh bean field[set] success, {}#{}={}", beanField.getBeanName(), beanField.getProperty(), value);} else {final Object finalBean = bean;ReflectionUtils.doWithFields(bean.getClass(), fieldItem -> {if (beanField.getProperty().equals(fieldItem.getName())) {try {Object valueObj = FieldReflectionUtil.parseValue(fieldItem.getType(), value);fieldItem.setAccessible(true);fieldItem.set(finalBean, valueObj);log.info(">>>>>>>>>>> refresh bean field[field] success, {}#{}={}", beanField.getBeanName(), beanField.getProperty(), value);} catch (IllegalAccessException e) {throw new RuntimeException(">>>>>>>>>>> refresh bean field[field] fail, " + beanField.getBeanName() + "#" + beanField.getProperty() + "=" + value);}}});}}private static final String placeholderPrefix = "${";private static final String placeholderSuffix = "}";/*** valid placeholder** @param originKey* @return*/private static boolean confKeyValid(String originKey) {if (originKey == null || "".equals(originKey.trim())) {throw new RuntimeException(">>>>>>>>>>> originKey[" + originKey + "] not be empty");}boolean start = originKey.startsWith(placeholderPrefix);boolean end = originKey.endsWith(placeholderSuffix);return start && end ? true : false;}/*** parse placeholder** @param originKey* @return*/private static String confKeyParse(String originKey) {if (confKeyValid(originKey)) {return originKey.substring(placeholderPrefix.length(), originKey.length() - placeholderSuffix.length());}return originKey;}private String beanName;@Overridepublic void setBeanName(String name) {this.beanName = name;}private static BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}}

?

7.配置Bean

@Configuration
public class DynamicConfConfig {@Beanpublic DynamicConfFactory dynamicConfFactory() {DynamicConfFactory dynamicConfFactory = new DynamicConfFactory();
return dynamicConfFactory;}}

?

8.使用方式

@RestController
@RequestMapping("/test")
public class TestController {@DynamicConf("${test.dynamic.config.key}")private String testDynamicConfig;
@GetMapping("/getConfig")public JSONObject testDynamicConfig(String key) {
// 從本地緩存獲取配置(就是一個Map)String value
= LocalDictMap.getDict(key);JSONObject json = new JSONObject();json.put(key, value);return json;}
// 通過接口來修改數據庫中的配置信息@GetMapping(
"/updateConfig")public String updateConfig(String key, String value) {SystemDictDto dictDto = new SystemDictDto();dictDto.setDictKey(key);dictDto.setDictValue(value);systemDictService.update(dictDto, 0);return "success";} }

?

9.配置變更后刷新

// 刷新Bean屬性
DynamicConfListenerFactory.onChange(dictKey, dictValue);
// TODO 刷新本地緩存 略

?

10.補上一個工具類)

public class AopTargetUtils {/*** 獲取目標對象** @param proxy 代理對象* @return 目標對象* @throws Exception*/public static Object getTarget(Object proxy) throws Exception {if (!AopUtils.isAopProxy(proxy)) {return proxy;}if (AopUtils.isJdkDynamicProxy(proxy)) {proxy = getJdkDynamicProxyTargetObject(proxy);} else {proxy = getCglibProxyTargetObject(proxy);}return getTarget(proxy);}private static Object getCglibProxyTargetObject(Object proxy) throws Exception {Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");h.setAccessible(true);Object dynamicAdvisedInterceptor = h.get(proxy);Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");advised.setAccessible(true);Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();return target;}private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {Field h = proxy.getClass().getSuperclass().getDeclaredField("h");h.setAccessible(true);AopProxy aopProxy = (AopProxy) h.get(proxy);Field advised = aopProxy.getClass().getDeclaredField("advised");advised.setAccessible(true);Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();return target;}}

?

轉載于:https://www.cnblogs.com/jun1019/p/11367639.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/145137.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息