SpringBoot @value注解动态刷新

news2024/11/25 4:52:12

参考资料

  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来缓存bean
    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>();
 
    // 禁止实例化
    private BeanRefreshScope() {
    }
 
    public static BeanRefreshScope getInstance() {
        return INSTANCE;
    }
 
    // 清理当前实例缓存的map
    public static void clean() {
        INSTANCE.beanMap.clear();
    }
 
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
    	
        Object bean = beanMap.get(name);
        if (bean == null) {
            bean = objectFactory.getObject();
            beanMap.put(name, bean);
        }
        
        return bean;
    }

	@Override
	public Object remove(String name) {
		return beanMap.remove(name);
	}

	@Override
	public void registerDestructionCallback(String name, Runnable callback) {
	}

	@Override
	public Object resolveContextualObject(String key) {
		return null;
	}

	@Override
	public 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 {
	
	// 获取环境配置对象
	@Autowired
	private 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 {
	
	@Autowired
	private ConfigurableListableBeanFactory beanFactory;
	
	@Autowired
	private RefreshConfigUtil refreshConfigUtil;
	
	@Override
	public 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 {
	
	@Autowired
	private GenericApplicationContext context;
	
	@Autowired
	private RefreshConfigUtil refreshConfigUtil;
	
	@Autowired
	private 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")
	@ResponseBody
	public 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.coloradmin.cn/o/1040648.html

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

相关文章

完成“重大项目”引进签约,美创科技正式落户中国(南京)软件谷

近日&#xff0c;美创科技正式入驻中国&#xff08;南京&#xff09;软件谷&#xff0c;并受邀出席中国南京“金洽会"之“雨花台区数字经济创新发展大会”。美创科技副总裁罗亮亮作为代表&#xff0c;在活动现场完成“重大项目”引进签约。 作为国家重要的软件产业与信息服…

Redis之set类型

文章目录 Redis之set类型1. 添加元素/获取集合中的所有元素/获取集合中元素个数2. 删除元素3. 判断元素是否在集合中3. 从集合中随机弹出一个元素&#xff0c;元素不删除4. 从集合中随机弹出元素&#xff0c;出一个删一个5. 将元素从一个集合转移到另外一个集合6. 集合的差集7.…

周记之重新开始

对于这周的学习&#xff0c;我进行了深刻的反思&#xff1a; 先来说说每天做了什么&#xff1a; 9.18号&#xff1a;把这个顶部的个人信息画出来了&#xff1b;然后记了两个单词&#xff08;这是能说的吗&#xff0c;这两个单词还是之前复习的&#xff09;现在都记忆犹新&…

31.下一个排列

方法&#xff1a;两遍扫描 举例&#xff1a; 4 5 2 6 3 1排列中较小数为2&#xff0c;较大数为3&#xff0c;交换两者得&#xff1a;4 5 3 6 2 1&#xff0c;将[i1,n)区间改成升序&#xff1a;得下一个排列&#xff1a; 4 5 3 1 2 6。 若第一步找不到较小数&#xff0c;即当前排…

基础算法--区间合并

区间合并简介 区间合并模型是一种竞赛里比较常见的模型&#xff0c;他的含义是&#xff0c;给你n个区间&#xff0c;要你合并所有有交集的区间&#xff0c;并求出合并后剩下的区间个数&#xff0c;如区间[1, 4]和[2, 3]可以合并成[1, 4]&#xff0c;但是[1, 2] 和 [3, 4] 不可…

SLAM从入门到精通(rviz的使用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在ros开发当中&#xff0c;rviz和tf都是用的比较多的一个工具。前者是为了实现传感器数据和计算结果的可视化&#xff0c;后者主要是为了显示各个传…

深度学习中什么是embedding

使用One-hot 方法编码的向量会很高维也很稀疏。假设我们在做自然语言处理(NLP)中遇到了一个包含2000个词的字典&#xff0c;当使用One-hot编码时&#xff0c;每一个词会被一个包含2000个整数的向量来表示&#xff0c;其中1999个数字是0&#xff0c;如果字典再大一点&#xff0c…

华为云云耀云服务器L实例评测|华为云上安装etcd

文章目录 华为云云耀云服务器L实例评测&#xff5c;华为云上安装etcd一、什么是etcd官方硬件建议 二、华为云主机准备三、etcd安装1. 安装预构建的二进制文件2. 从源代码构建 四、etcd服务注册与发现1. 配置etcd2. 使用systemctl 管理启动etcd服务3. 注册服务4. 发现服务 五、其…

python+opencv神经网络风格迁移--你也可以拥有梵高一样的画作

梵高画作 什么是神经网络的风格迁移,简单来件就是输入1张照片(自己的照片),输出具备另外一张照片(例子梵高画作)风格的图片,同时保留原本自己图片的元素,如下图片表明了神经网络风格迁移的过程,当然你也可以使用自己的神经网络训练自己的模型,本期教程利用了已经训练…

macOS使用官方安装包安装python

新手程序员可能想知道如何在 Mac 上正确安装 Python&#xff0c;这里介绍在 macOS 上安装 Python 的方法。 操作步骤 1.从 Python 官方网站 (python.org) 下载最新的 Python 版本. 单击 macOS 链接并选择最新的 Python 版本。 2.下载完成后&#xff0c;双击包开始安装Python…

使用 Ruby 语言来解析开放文档格式 OOXML 文件

在这篇文章中&#xff0c;我们将了解一个开发团队如何解决他们在应用程序中解析数据时遇到的问题。 为了测试 ONLYOFFICE 文档编辑器&#xff0c;我们用Ruby语言开发编写了个docx、xlsx、pptx文件解析器程序&#xff0c;它是免费开源的&#xff0c;被我们放在GitHub和RubyGems…

python运算函数

简 python输入输出函数input() :用户用于读取键盘输入的函数&#xff0c;返回值为“string”类型 运算函数abs(x) &#xff1a;x的绝对值int(x) &#xff1a;将x转换成整型(截掉小数部分)float(x):浮点数divmod(x,y):返回&#xff08;x//y,x%y&#xff09;complex(re,im):返回一…

A股风格因子看板 (2023.09 第08期)

要点预告:10月&#xff0c;天软课堂将添加新主题--天软超高频行情数据。针对市场上高频行情数据处理业务的相关痛点&#xff0c;直观的在线演示如何通过天软高频数仓及高性能计算能力&#xff0c;将其逐个击破&#xff0c;期待各位老师的参会。请持续关注天软课堂动态&#xff…

企业架构LNMP学习笔记60

Tomcat企业常见使用方法&#xff1b; 1&#xff09;简单代码测试&#xff1a; 将两个jsp文件上传到ROOT目录下。 查看下这个jsp代码&#xff1a; test.jsp <html> <head><title>Hello World</title> <% page language"java" contentT…

短视频矩阵系统,短视频矩阵源码技术

1、抖音开放平台申请账号&#xff0c;快手平台申请账号&#xff1b;阿里云混剪接口。 2、系统总台支持OEM代理&#xff0c;可以按点数管理。 3、代理功能。包括是否允许再次开二级代理、是否允许OEM等。 4、可支持一条龙搭建服务&#xff0c;抖音平台开放平台代申请等 开发…

《学术小白学习之路》论文常见方法:Doc2vec-句向量模型实现

1. 数据 用于文献的摘要的相似度的计算 ## 导包 import pandas as pd import jieba import gensim from gensim.models import Doc2Vec from gensim.models.doc2vec import TaggedDocument再定义停用词典,用于分词,还可以自己定义一个分词词典 ## 读入数据 papers = pd.&l…

JVS规则引擎,打造智能自动化决策的利器

在日常的项目中&#xff0c;实时数据处理和自动化决策是智能化业务、灵活化配置的关键能力。为了满足这一需求&#xff0c;JVS规则引擎应运而生&#xff0c;它是一种高效的低代码/零代码平台&#xff0c;能够帮助企业快速构建各种应用场景&#xff0c;实现自动化、智能化决策的…

iOS 17隐私设置指南

最近把手机升级到iOS 17了&#xff0c;升级后的设置里多了很多以前没注意到的指南&#xff0c;我发现特别是有关隐私相关配置的很多我没有启用。 那么&#xff0c;我就来扒一扒iOS中和隐私相关的配置&#xff0c;这些配置可能是iOS 17以后加入的&#xff0c;也可能是以前就有&a…

spring一个项目多个模块聚合打包问题解决方案

文章目录 1.问题描述&#xff1a;2.解决方案一、创建聚合父工程二、创建子模块&#xff08;module&#xff09;三、编写子模块代码1.模块1&#xff08;demo-one&#xff09;2.模块2&#xff08;demo-tow&#xff09; 四、创建聚合模块 &#xff08;demo-starter&#xff09;1. …