SpringBoot @value注解动态刷新

参考资料

  1. Spring系列第25篇:@Value【用法、数据来源、动态刷新】
  2. 【基础系列】SpringBoot配置信息之配置刷新
  3. 【基础系列】SpringBoot之自定义配置源的使用姿势
  4. 【基础系列】SpringBoot应用篇@Value注解支持配置自动刷新能力扩展
  5. Spring Boot 中动态更新 @Value 配置

一. 应用场景

⏹在SpringBoot工程中,我们一般会将一些配置信息放到application.properties配置文件中,
然后创建一个配置类通过@value注解读取配置文件中的配置信息后,进行各种业务处理。
⏹但是有的情况下我们需要对配置信息进行更改,但是更改之后就需要重启一次项目,
影响客户使用。
⏹我们可以将配置信息存放到数据库中,但是每使用一次配置信息就要去数据库查询显然也不合适。
🤔@Value注解所对应的数据源来自项目的Environment中,我们可以将数据库或其他文件中的数据,加载到项目的Environment中,然后@Value注解就可以动态获取到配置信息了。


二. 前期准备

⏹模拟获取数据库(其他存储介质: 配置文件,redis等)中的配置数据

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;public class DbUtil {// 从数据库获取邮件的用户名信息public static Map<String, Object> getMailInfoFromDb() {// 模拟从数据库或者其他存储介质中获取到的用户名信息String username = UUID.randomUUID().toString().substring(0, 6);Map<String, Object> result = new HashMap<>();// 此处的"mail.username" 对应 @Value("${mail.username}")result.put("mail.username", username);return result;}
}

⏹配置类

  • @RefreshScope是我们自定义的注解,用来动态的从项目的Environment中更新@Value所对应的值。
  • application.properties中的配置信息最终会被读取到项目的Environment中,但是还有其他方式向Environment中手动放入值,${mail.username}的值来源于我们自己手动放入Environment中的值。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import lombok.Data;/*** 邮件配置信息*/
@Configuration
@RefreshScope
@Data
public class MailConfig {@Value("${mail.username}")private String username;
}

⏹前台页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>@value注解动态刷新</title>
</head>
<body><button id="btn1">点击发送请求,动态刷新@value注解</button>
</body>
<script th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script th:inline="javascript">$("#btn1").click(function() {$.ajax({url: "/test03/updateValue",type: 'POST',data: JSON.stringify(null),contentType: 'application/json;charset=utf-8',success: function (data, status, xhr) {console.log(data);}});});
</script>
</html>

三. 实现Scope接口,创建自定义作用域类

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.concurrent.ConcurrentHashMap;public class BeanRefreshScope implements Scope {public static final String SCOPE_REFRESH = "refresh";private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();// 用此map来缓存beanprivate ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>();// 禁止实例化private BeanRefreshScope() {}public static BeanRefreshScope getInstance() {return INSTANCE;}// 清理当前实例缓存的mappublic static void clean() {INSTANCE.beanMap.clear();}@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {Object bean = beanMap.get(name);if (bean == null) {bean = objectFactory.getObject();beanMap.put(name, bean);}return bean;}@Overridepublic Object remove(String name) {return beanMap.remove(name);}@Overridepublic void registerDestructionCallback(String name, Runnable callback) {}@Overridepublic Object resolveContextualObject(String key) {return null;}@Overridepublic String getConversationId() {return null;}}

四. 创建自定义作用域注解

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;import java.lang.annotation.*;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
// 使用自定义作用域
@Scope(BeanRefreshScope.SCOPE_REFRESH)
@Documented
public @interface RefreshScope {ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

五. 刷新配置类的工具类

  • @Value注解所对应的值来源于项目的Environment中,也就是来源于ConfigurableEnvironment 中。
  • 每当需要更新配置的时候,调用我们自定义的refreshMailPropertySource方法,从各种存储介质中获取最新的配置信息存储到项目的Environment中。
import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.stereotype.Component;import java.util.Map;@Component
public class RefreshConfigUtil {// 获取环境配置对象@Autowiredprivate ConfigurableEnvironment environment;private final static String MAIL_CONFIG_NAMW = "mail_config";// @Autowired// private GenericApplicationContext context;/*** 模拟改变数据库中的配置信息*/public void updateDbConfigInfo() {// 更新context中的mailPropertySource配置信息this.refreshMailPropertySource();// 清空BeanRefreshScope中所有bean的缓存BeanRefreshScope.getInstance();BeanRefreshScope.clean();}public void refreshMailPropertySource() {/*** @Value中的数据源来源于Spring的「org.springframework.core.env.PropertySource」中* 此处为获取项目中的全部@Value相关的数据*/MutablePropertySources propertySources = environment.getPropertySources();propertySources.forEach(System.out::println);// 模拟从数据库中获取配置信息Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();// 将数据库查询到的配置信息放到MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)MapPropertySource mailPropertySource = new MapPropertySource(MAIL_CONFIG_NAMW, mailInfoFromDb);// 将配置信息放入 环境配置对象中propertySources.addLast(mailPropertySource);}}

六. 配置类加载

  • 实现了CommandLineRunner接口,在项目启动的时候调用一次run方法。
  • 将自定义作用域 和 存储介质中的数据添加到项目中。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class ConfigLoad implements CommandLineRunner {@Autowiredprivate ConfigurableListableBeanFactory beanFactory;@Autowiredprivate RefreshConfigUtil refreshConfigUtil;@Overridepublic void run(String... args) throws Exception {// 将我们自定义的作用域添加到Bean工厂中beanFactory.registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());// 将从存储介质中获取到的数据添加到项目的Environment中。refreshConfigUtil.refreshMailPropertySource();}}

七. 测试

  • 进入测试页面的时候,获取3次配置类
  • 在测试页面点击更新按钮的时候,更新配置类之后,打印配置类,观察配置信息的变化。
import java.util.concurrent.TimeUnit;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;@Controller
@RequestMapping("/test03")
public class Test03Controller {@Autowiredprivate GenericApplicationContext context;@Autowiredprivate RefreshConfigUtil refreshConfigUtil;@Autowiredprivate MailConfig mailConfig;@GetMapping("/init")public ModelAndView init() throws InterruptedException {System.out.println("------配置未更新的情况下,输出3次开始------");for (int i = 0; i < 3; i++) {System.out.println(mailConfig);TimeUnit.MILLISECONDS.sleep(200);}System.out.println("------配置未更新的情况下,输出3次结束------");System.out.println("======================================================================");ModelAndView modelAndView = new ModelAndView();modelAndView.setViewName("test03");return modelAndView;}@PostMapping("/updateValue")@ResponseBodypublic void updateValue(@RequestBody Test03Form form) throws Exception {System.out.println("------配置未更新的情况下,输出1次开始------");MailConfig mailInfo = context.getBean(MailConfig.class);System.out.println(mailInfo);System.out.println("------配置未更新的情况下,输出1次开始------");System.out.println("------配置更新之后,输出开始------");refreshConfigUtil.updateDbConfigInfo();System.out.println(mailInfo);System.out.println("------配置更新之后,输出结束------");}}

在这里插入图片描述

在这里插入图片描述


注意事项:
本文只是进行了相关实践,相关原理请参照参考资料
特别是参考资料1的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/141653.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

什么记事本软件记录恋爱时间准确?恋爱时间计时器app有哪些?

对于很多情侣来说&#xff0c;恋爱的每一天都值得记录。所以在恋爱期间&#xff0c;情侣对恋爱天数的记录充满了浪漫和纪念的意义。记录每一天&#xff0c;不仅是对爱情的见证&#xff0c;更是对彼此之间承诺的延续。不过情侣想要记录恋爱天数&#xff0c;就需要先找到一款恋爱…

C++实现nms和softmax

最近在面试过程中遇到了手写nms的问题&#xff0c;结束后重新实现并调通了nms和softmax的代码。 1、NMS 原理&#xff08;通俗易懂&#xff09;&#xff1a; 先假设有6个候选框&#xff0c;根据分类器类别分类概率做排序&#xff0c;从小到大分别属于车辆的概率分别为A、B、C、…

Cookie 和 Session机制

Cookie HTTP 协议自身是属于 "无状态" 协议. "无状态" 的含义指的是: 默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系. 但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的. 例如登陆网站成功后, 第二次访…

MATLAB 安装额外工具包

接下里即可搜索并安装 “额外工具包”

从服务器指定位置下载文件

从服务器指定位置下载文件 下载文件转换成流&#xff0c;这里说两种流的方式:1. 文件流2. 字节流 下载文件转换成流&#xff0c;这里说两种流的方式: 1. 文件流 2. 字节流 一&#xff0c;字节流 String filePath“/opt/peoject/file/123/pdf”; //这个是你服务上存放文件位置…

Flutter实现PS钢笔工具,实现高精度抠图的效果。

演示&#xff1a; 代码&#xff1a; import dart:ui;import package:flutter/material.dart hide Image; import package:flutter/services.dart; import package:flutter_screenutil/flutter_screenutil.dart; import package:kq_flutter_widgets/widgets/animate/stack.dart…

UNet网络模型学习总结

github&#xff1a;Machine_Learning/网络模型/UNet at main golitter/Machine_Learning (github.com) 因为VOC数据集太大&#xff0c;上传github很慢&#xff0c;所以就没有上传VOC数据&#xff0c;只有参考的目录位置。 数据集自行下载&#xff1a;https://host.robots.ox.…

Python 逢七拍手小游戏1.0

"""逢七拍手游戏介绍&#xff1a;逢七拍手游戏的规则是&#xff1a;从1开始顺序数数&#xff0c;数到有7&#xff0c;或者是7的倍数时&#xff0c;就拍一手。例如&#xff1a;7、14、17......70......知识点&#xff1a;1、循环语句for2、嵌套条件语句if/elif/e…

旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著作想象和世界一样宽广

旅行季《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著作想象和世界一样宽广

FPGA设计时序约束一、主时钟与生成时钟

​目录 一、主时钟create_clock 1.1 定义 1.2 约束设置格式 1.3 Add this clock to the existing clock 1.4 示例 1.5 差分信号 二、生成时钟generate_clock 2.1 定义 2.2 格式 2.2.1 by clock frequency 2.2.2 by clock edges 2.2.3 示例 2.2.4 自动生成时钟 2.…

【操作系统笔记十】缓存一致性

CPU 核心之间数据如何传播 高速缓存中的值被修改了&#xff0c;那么怎么同步到内存中呢&#xff1f; ① 写直达&#xff08;Write-Through&#xff09;② 写回&#xff08;Write-Back&#xff09; 写直达&#xff08;Write-Through&#xff09; 简单&#xff0c;但是很慢&am…

springboot项目中定时任务注解@Scheduled未按cron表达式执行

springboot项目中定时任务注解Scheduled未按cron表达式执行 背景问题复现原因分析解决方法其他原因 背景 在将一个类注入到ioc后&#xff0c;其中定义了几个定时任务&#xff0c;分别是每隔十秒执行一次&#xff0c;但实际情况却是半小时才执行一次&#xff0c;故开始分析原因&…

SpringAOP入门案例

package com.elf.spring.aop.aspectj; /*** author 45* version 1.0*/ public interface UsbInterface {public void work(); }package com.elf.spring.aop.aspectj; import org.springframework.stereotype.Component; /*** author 45* version 1.0*/ Component //把Phone对象…

【C++笔记】C++ list类模拟实现

【C笔记】C list类模拟实现 一、初始化和各种构造1.1、准备工作1.2、各种构造和析构 二、插入和删除2.1、插入2.2、删除 三、迭代器3.1、正向迭代器3.2、反向迭代器3.3、提供迭代器位置 四、其他一些接口4.1、链表的长度和判空4.2、返回链表的头尾结点 一、初始化和各种构造 C…

李宏毅hw-10 ——adversarial attack

一、查漏补缺&#xff1a; 1.关于glob.glob的用法&#xff0c;返回一个文件路径的 列表&#xff1a; 当然&#xff0c;再套用1个sort&#xff0c;就是将所有的文件路径按照字母进行排序了 2.relpath relative_path返回相对于基准路径的相对路径的函数 二、代码剖析&#xff…

【红帽】跟着学习如何使用桌面访问命令行

今天我们分享一些红帽Linux的知识&#xff0c;记得关注&#xff0c;会一直更新~ ▶1、以student用户身份并使用student作为密码登录workstation 1.1.在workstation上&#xff0c;从GNOME登录屏幕中单击student用户帐户。系统提示输入密码时&#xff0c;请输入student。 1.2.…

JavaScript系列从入门到精通系列第九篇:JavaScript中赋值运算符和关系运算符以及Unicode编码介绍

一&#xff1a;赋值运算符 1&#xff1a; 右侧的值可以赋值给左侧的变量。 var a 123; console.log(a);//123 2&#xff1a; var a 10; a a 5; a 5; 上边这两个写法是一样的。 3&#xff1a;- var a 10; a a-5; a - 5; 上边这两个写法是一样的。 4&#xff1a;* …

数据备份文件生成--根据表名生成对应的sql语句文件

最近客户有个需求&#xff0c;希望在后台增加手动备份功能&#xff0c;将数据导出下载保存。 当然&#xff0c;此方法不适用于海量数据的备份&#xff0c;这只适用于少量数据的sql备份。 这是我生成的sql文件&#xff0c;以及sql文件里的insert语句&#xff0c;已亲测&#x…

Java抽象类、接口

1.抽象类 1.abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类那么该类就是抽象类。2.抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类3.抽象类,不能使用new关键字来创建对象,它是用来让子类继承的4.抽象方法,只有…

接口测试入门

1. 什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及相互逻辑依赖关系。其中接口协议分为HTTP,WebService,Dubbo,Thrift,Socket等类型&#xff0c;测试类型又主…