需求描述:在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 {
    // 注入操作配置信息的业务类@Autowiredprivate 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

SpringBoot项目实现配置实时刷新功能相关推荐

  1. SpringBoot项目中集成第三方登录功能

    SpringBoot项目中集成第三方登录功能 引言 1 环境准备 2 代码实现 3 第三方平台认证申请 4 打包和部署项目 5 第三方平台登录认证测试 6 参考文章 引言 最近想把自己在公众号上介绍过 ...

  2. Centos7云服务器部署SpringBoot项目(手动配置环境篇)

    文章目录 前言 一.部署Tomcat服务器 1.安装JDK1.8 2.安装与启动tomcat 配置安全组(8080端口) 二.安装JDK8 三.Mysql5.7安装 1.下载mysql 2.启动MyS ...

  3. Sublime Text 3 、WebStorm配置实时刷新

    本文所用软件版本Sublime Text 3(Build 3143).WebStorm 2017.2.4(Build #WS-172.4155.35).Google Chrome v61.0.3163 ...

  4. SpringBoot项目端点配置

    端点配置 开启端点 在SpringBoot中开启应用监控非常容易,只需要添加spring-boot-starter-actuator依赖即可,actuator(执行器)是制造业术语, 指一个用于移动或 ...

  5. 如何在Spring-Boot项目中配置资源文件夹?视频文件不想放在项目中,怎么做?前台上传文件后台如何访问?什么是资源文件夹?

    如何在Spring-boot项目配置资源文件夹 前言 2.properties文件 二.访问 1.放入资源 2.访问 方式1 方式2 前言 在什么情况下需要配置资源文件夹? 当项目需要读取静态文件夹以 ...

  6. 在springboot项目中配置hive-jdbc的maven依赖时遇到:Could not find artifact org.glassfish:javax.el:pom:3.0.1-b06-S

    解决方案 在配置时依赖时排除javax.el <dependency>     <groupId>org.apache.hive</groupId>     < ...

  7. springboot项目Banner配置

    一,springboot项目启动的时候默认会生成一个spring的banner图标,现在想换一个banner, 哈哈哈哈,就是玩 实现步骤: 1,在创建的springboot项目resouces目录下 ...

  8. springboot项目接入配置中心,实现@ConfigurationProperties的bean属性刷新方案

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

  9. 39、Springboot 项目集成Log日志输出功能,并按每小时分组分割文件

    Logback的引入 之所以引入logback ,最主要原因是因为它非常强大的兼容性,能够自然平滑的支持log4j或者其他日志组件,官方文档非常的充分,对初学者十分友好,能够快速上手,它当前分为三个模 ...

最新文章

  1. 关于成功的因素-----谨记
  2. 多线程编程(3) - 多线程同步之 CriticalSection(临界区)
  3. jquery 源码分析九 - Sizzle
  4. ES6新特性5:类(Class)和继承(Extends)
  5. Linux中zip压缩和unzip解压缩
  6. Spring Boot——自定义多个拦截器(HandlerInterceptor)配置方法与执行顺序
  7. Hadoop之资源调度器与任务推测执行
  8. Android系统原生应用解析之桌面闹钟及相关原理应用之时钟任务的应用(一)
  9. 【蓝桥杯单片机】NE555在CT107D上的使用
  10. Vuex之理解Modules
  11. Should I design my classes from the outside (interfaces first) or from the inside (data first)?
  12. PPT无法插入视频,验证编码解码器
  13. 前端页面实现时间显示
  14. 基于WinPcap的网络流量在线分析系统的设计与实现
  15. cesium加载S3M白膜,通过分层设色实现渐变效果,设置点光源
  16. iOS打包成ipa包
  17. mysql 8.0 配置文件my.cnf中文注解
  18. Bug算法(Bug Algorithms)简介(Bug1 Bug2 Tangent Bug)
  19. Linux命令整理(二)
  20. Inkscape如何将png图片转换为svg图片并且不失真

热门文章

  1. 随手练——打印折痕方向
  2. 【分治】动态点分治 ([ZJOI2007]捉迷藏)
  3. Flask中那些特殊的装饰器
  4. java打印条形码Code128C
  5. 前端进阶试题-CSS篇
  6. F - Warm up - hdu 4612(缩点+求树的直径)
  7. eclipse中添加svn插件
  8. Mac. IntelliJ IDEA maven+springmvc添加包时小插曲
  9. 解决TortoiseGit每次Pull或者Push都需要输用户名密码的问题
  10. 感觉养老金越涨差距越大,有人提议高于5000的不再上涨,合理吗?