SpringBoot @value注解動態(tài)刷新問題小結(jié)
一. 應(yīng)用場景
- ?在SpringBoot工程中,我們一般會將一些配置信息放到
application.properties配置文件中,然后創(chuàng)建一個配置類通過@value注解讀取配置文件中的配置信息后,進行各種業(yè)務(wù)處理。 - ?但是有的情況下我們需要對配置信息進行更改,但是更改之后就需要重啟一次項目,影響客戶使用。
- ?我們可以將配置信息存放到數(shù)據(jù)庫中,但是每使用一次配置信息就要去數(shù)據(jù)庫查詢顯然也不合適。
- ??
@Value注解所對應(yīng)的數(shù)據(jù)源來自項目的Environment中,我們可以將數(shù)據(jù)庫或其他文件中的數(shù)據(jù),加載到項目的Environment中,然后@Value注解就可以動態(tài)獲取到配置信息了。
二. 前期準(zhǔn)備
?模擬獲取數(shù)據(jù)庫(其他存儲介質(zhì): 配置文件,redis等)中的配置數(shù)據(jù)
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class DbUtil {
// 從數(shù)據(jù)庫獲取郵件的用戶名信息
public static Map<String, Object> getMailInfoFromDb() {
// 模擬從數(shù)據(jù)庫或者其他存儲介質(zhì)中獲取到的用戶名信息
String username = UUID.randomUUID().toString().substring(0, 6);
Map<String, Object> result = new HashMap<>();
// 此處的"mail.username" 對應(yīng) @Value("${mail.username}")
result.put("mail.username", username);
return result;
}
}?配置類
- @RefreshScope是我們自定義的注解,用來動態(tài)的從項目的
Environment中更新@Value所對應(yīng)的值。 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注解動態(tài)刷新</title>
</head>
<body>
<button id="btn1">點擊發(fā)送請求,動態(tài)刷新@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>三. 實現(xiàn)Scope接口,創(chuàng)建自定義作用域類
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;
}
// 清理當(dāng)前實例緩存的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;
}
}四. 創(chuàng)建自定義作用域注解
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注解所對應(yīng)的值來源于項目的Environment中,也就是來源于ConfigurableEnvironment中。- 每當(dāng)需要更新配置的時候,調(diào)用我們自定義的
refreshMailPropertySource方法,從各種存儲介質(zhì)中獲取最新的配置信息存儲到項目的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 {
// 獲取環(huán)境配置對象
@Autowired
private ConfigurableEnvironment environment;
private final static String MAIL_CONFIG_NAMW = "mail_config";
// @Autowired
// private GenericApplicationContext context;
/**
* 模擬改變數(shù)據(jù)庫中的配置信息
*/
public void updateDbConfigInfo() {
// 更新context中的mailPropertySource配置信息
this.refreshMailPropertySource();
// 清空BeanRefreshScope中所有bean的緩存
BeanRefreshScope.getInstance();
BeanRefreshScope.clean();
}
public void refreshMailPropertySource() {
/**
* @Value中的數(shù)據(jù)源來源于Spring的「org.springframework.core.env.PropertySource」中
* 此處為獲取項目中的全部@Value相關(guān)的數(shù)據(jù)
*/
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.forEach(System.out::println);
// 模擬從數(shù)據(jù)庫中獲取配置信息
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
// 將數(shù)據(jù)庫查詢到的配置信息放到MapPropertySource中(MapPropertySource類是spring提供的一個類,是PropertySource的子類)
MapPropertySource mailPropertySource = new MapPropertySource(MAIL_CONFIG_NAMW, mailInfoFromDb);
// 將配置信息放入 環(huán)境配置對象中
propertySources.addLast(mailPropertySource);
}
}六. 配置類加載
- 實現(xiàn)了
CommandLineRunner接口,在項目啟動的時候調(diào)用一次run方法。 - 將自定義作用域 和 存儲介質(zhì)中的數(shù)據(jù)添加到項目中。
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());
// 將從存儲介質(zhì)中獲取到的數(shù)據(jù)添加到項目的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次結(jié)束------");
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("------配置更新之后,輸出結(jié)束------");
}
}

注意事項:本文只是進行了相關(guān)實踐,相關(guān)原理請參照參考資料。
參考資料
- Spring系列第25篇:@Value【用法、數(shù)據(jù)來源、動態(tài)刷新】
- 【基礎(chǔ)系列】SpringBoot配置信息之配置刷新
- 【基礎(chǔ)系列】SpringBoot之自定義配置源的使用姿勢
- 【基礎(chǔ)系列】SpringBoot應(yīng)用篇@Value注解支持配置自動刷新能力擴展
- Spring Boot 中動態(tài)更新 @Value 配置
到此這篇關(guān)于SpringBoot @value注解動態(tài)刷新的文章就介紹到這了,更多相關(guān)SpringBoot 動態(tài)刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea導(dǎo)入springboot項目沒有maven的解決
這篇文章主要介紹了idea導(dǎo)入springboot項目沒有maven的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
關(guān)于cron表達式每天整點執(zhí)行一次的問題
這篇文章主要介紹了關(guān)于cron表達式每天整點執(zhí)行一次的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12
SpringBoot集成Validation參數(shù)校驗
這篇文章主要為大家詳細(xì)介紹了SpringBoot集成Validation參數(shù)校驗,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
springboot結(jié)合前端實現(xiàn)網(wǎng)頁跳轉(zhuǎn)功能實例
今天處理Springboot統(tǒng)一異常攔截的時候,遇到了頁面跳轉(zhuǎn)的問題,這篇文章主要給大家介紹了關(guān)于springboot結(jié)合前端實現(xiàn)網(wǎng)頁跳轉(zhuǎn)功能的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12

