SpringBoot @value注解动态刷新问题小结

 更新时间:2023年09月26日 10:41:57   作者:fengyehongWorld  
@Value注解 所对应的数据源来自项目的 Environment 中,我们可以将数据库或其他文件中的数据,加载到项目的 Environment 中,然后 @Value注解 就可以动态获取到配置信息了,这篇文章主要介绍了SpringBoot @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. Spring系列第25篇:@Value【用法、数据来源、动态刷新】
  2. 【基础系列】SpringBoot配置信息之配置刷新
  3. 【基础系列】SpringBoot之自定义配置源的使用姿势
  4. 【基础系列】SpringBoot应用篇@Value注解支持配置自动刷新能力扩展
  5. Spring Boot 中动态更新 @Value 配置

到此这篇关于SpringBoot @value注解动态刷新的文章就介绍到这了,更多相关SpringBoot 动态刷新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java利用布隆过滤器实现快速检查元素是否存在

    Java利用布隆过滤器实现快速检查元素是否存在

    布隆过滤器是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。本文就来详细说说实现的方法,需要的可以参考一下
    2022-10-10
  • spring AOP实现@Around输出请求参数和返回参数

    spring AOP实现@Around输出请求参数和返回参数

    这篇文章主要介绍了spring AOP实现@Around输出请求参数和返回参数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • SpringBoot中使用Swagger的超简单方法

    SpringBoot中使用Swagger的超简单方法

    大家一致认为springBoot使用swagger太麻烦了,每次都需要编写config,今天小编告诉大家一种超简单配置方法,教大家如何整合swagger,感兴趣的朋友跟随小编一起看看吧
    2021-07-07
  • java Split 实现去除一个空格和多个空格

    java Split 实现去除一个空格和多个空格

    这篇文章主要介绍了java Split 实现去除一个空格和多个空格,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • JVM的类加载过程以及双亲委派模型详解

    JVM的类加载过程以及双亲委派模型详解

    这篇文章主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。,需要的朋友可以参考下
    2019-06-06
  • SpringBoot整合aws的示例代码

    SpringBoot整合aws的示例代码

    本文通过实例代码给大家介绍SpringBoot整合aws的全过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-12-12
  • Java实现按照大小写字母顺序排序的方法

    Java实现按照大小写字母顺序排序的方法

    这篇文章主要介绍了Java实现按照大小写字母顺序排序的方法,涉及java数组遍历、编码转换、判断等相关操作技巧,需要的朋友可以参考下
    2017-12-12
  • SpringBoot详细介绍SPI机制示例

    SpringBoot详细介绍SPI机制示例

    这篇文章主要介绍了深入解析Spring Boot的SPI机制详情,SPI是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要用于框架中开发,更多相关介绍,感兴趣的小伙伴可以参考一下下面文章内容
    2022-08-08
  • Java整数和字符串相互转化实例详解

    Java整数和字符串相互转化实例详解

    这篇文章主要介绍了Java整数和字符串相互转化实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • java之static关键字用法实例解析

    java之static关键字用法实例解析

    这篇文章主要介绍了java之static关键字用法实例解析,包括了static关键字的原理讲解及用法分析,并附带了实例说明,需要的朋友可以参考下
    2014-09-09

最新评论