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 { // 注入操作配置信息的业务类@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项目实现配置实时刷新功能相关推荐
- SpringBoot项目中集成第三方登录功能
SpringBoot项目中集成第三方登录功能 引言 1 环境准备 2 代码实现 3 第三方平台认证申请 4 打包和部署项目 5 第三方平台登录认证测试 6 参考文章 引言 最近想把自己在公众号上介绍过 ...
- Centos7云服务器部署SpringBoot项目(手动配置环境篇)
文章目录 前言 一.部署Tomcat服务器 1.安装JDK1.8 2.安装与启动tomcat 配置安全组(8080端口) 二.安装JDK8 三.Mysql5.7安装 1.下载mysql 2.启动MyS ...
- Sublime Text 3 、WebStorm配置实时刷新
本文所用软件版本Sublime Text 3(Build 3143).WebStorm 2017.2.4(Build #WS-172.4155.35).Google Chrome v61.0.3163 ...
- SpringBoot项目端点配置
端点配置 开启端点 在SpringBoot中开启应用监控非常容易,只需要添加spring-boot-starter-actuator依赖即可,actuator(执行器)是制造业术语, 指一个用于移动或 ...
- 如何在Spring-Boot项目中配置资源文件夹?视频文件不想放在项目中,怎么做?前台上传文件后台如何访问?什么是资源文件夹?
如何在Spring-boot项目配置资源文件夹 前言 2.properties文件 二.访问 1.放入资源 2.访问 方式1 方式2 前言 在什么情况下需要配置资源文件夹? 当项目需要读取静态文件夹以 ...
- 在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> < ...
- springboot项目Banner配置
一,springboot项目启动的时候默认会生成一个spring的banner图标,现在想换一个banner, 哈哈哈哈,就是玩 实现步骤: 1,在创建的springboot项目resouces目录下 ...
- springboot项目接入配置中心,实现@ConfigurationProperties的bean属性刷新方案
前言 配置中心,通过key=value的形式存储环境变量.配置中心的属性做了修改,项目中可以通过配置中心的依赖(sdk)立即感知到.需要做的就是如何在属性发生变化时,改变带有@Configuratio ...
- 39、Springboot 项目集成Log日志输出功能,并按每小时分组分割文件
Logback的引入 之所以引入logback ,最主要原因是因为它非常强大的兼容性,能够自然平滑的支持log4j或者其他日志组件,官方文档非常的充分,对初学者十分友好,能够快速上手,它当前分为三个模 ...
最新文章
- 关于成功的因素-----谨记
- 多线程编程(3) - 多线程同步之 CriticalSection(临界区)
- jquery 源码分析九 - Sizzle
- ES6新特性5:类(Class)和继承(Extends)
- Linux中zip压缩和unzip解压缩
- Spring Boot——自定义多个拦截器(HandlerInterceptor)配置方法与执行顺序
- Hadoop之资源调度器与任务推测执行
- Android系统原生应用解析之桌面闹钟及相关原理应用之时钟任务的应用(一)
- 【蓝桥杯单片机】NE555在CT107D上的使用
- Vuex之理解Modules
- Should I design my classes from the outside (interfaces first) or from the inside (data first)?
- PPT无法插入视频,验证编码解码器
- 前端页面实现时间显示
- 基于WinPcap的网络流量在线分析系统的设计与实现
- cesium加载S3M白膜,通过分层设色实现渐变效果,设置点光源
- iOS打包成ipa包
- mysql 8.0 配置文件my.cnf中文注解
- Bug算法(Bug Algorithms)简介(Bug1 Bug2 Tangent Bug)
- Linux命令整理(二)
- Inkscape如何将png图片转换为svg图片并且不失真