SpringBoot @value注解動(dòng)態(tài)刷新問題小結(jié)
一. 應(yīng)用場(chǎng)景
- ?在SpringBoot工程中,我們一般會(huì)將一些配置信息放到
application.properties
配置文件中,然后創(chuàng)建一個(gè)配置類通過@value
注解讀取配置文件中的配置信息后,進(jìn)行各種業(yè)務(wù)處理。 - ?但是有的情況下我們需要對(duì)配置信息進(jìn)行更改,但是更改之后就需要重啟一次項(xiàng)目,影響客戶使用。
- ?我們可以將配置信息存放到數(shù)據(jù)庫中,但是每使用一次配置信息就要去數(shù)據(jù)庫查詢顯然也不合適。
- ??
@Value注解
所對(duì)應(yīng)的數(shù)據(jù)源來自項(xiàng)目的Environment
中,我們可以將數(shù)據(jù)庫或其他文件中的數(shù)據(jù),加載到項(xiàng)目的Environment
中,然后@Value注解
就可以動(dòng)態(tài)獲取到配置信息了。
二. 前期準(zhǔn)備
?模擬獲取數(shù)據(jù)庫(其他存儲(chǔ)介質(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ù)庫或者其他存儲(chǔ)介質(zhì)中獲取到的用戶名信息 String username = UUID.randomUUID().toString().substring(0, 6); Map<String, Object> result = new HashMap<>(); // 此處的"mail.username" 對(duì)應(yīng) @Value("${mail.username}") result.put("mail.username", username); return result; } }
?配置類
- @RefreshScope是我們自定義的注解,用來動(dòng)態(tài)的從項(xiàng)目的
Environment
中更新@Value
所對(duì)應(yīng)的值。 application.properties
中的配置信息最終會(huì)被讀取到項(xiàng)目的Environment
中,但是還有其他方式向Environment
中手動(dòng)放入值,${mail.username}
的值來源于我們自己手動(dòng)放入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; }
?前臺(tái)頁面
<!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注解動(dòng)態(tài)刷新</title> </head> <body> <button id="btn1">點(diǎn)擊發(fā)送請(qǐng)求,動(dòng)態(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>
三. 實(shí)現(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<>(); // 禁止實(shí)例化 private BeanRefreshScope() { } public static BeanRefreshScope getInstance() { return INSTANCE; } // 清理當(dāng)前實(shí)例緩存的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
注解所對(duì)應(yīng)的值來源于項(xiàng)目的Environment
中,也就是來源于ConfigurableEnvironment
中。- 每當(dāng)需要更新配置的時(shí)候,調(diào)用我們自定義的
refreshMailPropertySource
方法,從各種存儲(chǔ)介質(zhì)中獲取最新的配置信息存儲(chǔ)到項(xiàng)目的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)境配置對(duì)象 @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」中 * 此處為獲取項(xiàng)目中的全部@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提供的一個(gè)類,是PropertySource的子類) MapPropertySource mailPropertySource = new MapPropertySource(MAIL_CONFIG_NAMW, mailInfoFromDb); // 將配置信息放入 環(huán)境配置對(duì)象中 propertySources.addLast(mailPropertySource); } }
六. 配置類加載
- 實(shí)現(xiàn)了
CommandLineRunner
接口,在項(xiàng)目啟動(dòng)的時(shí)候調(diào)用一次run
方法。 - 將自定義作用域 和 存儲(chǔ)介質(zhì)中的數(shù)據(jù)添加到項(xiàng)目中。
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()); // 將從存儲(chǔ)介質(zhì)中獲取到的數(shù)據(jù)添加到項(xiàng)目的Environment中。 refreshConfigUtil.refreshMailPropertySource(); } }
七. 測(cè)試
- 進(jìn)入測(cè)試頁面的時(shí)候,獲取3次配置類
- 在測(cè)試頁面點(diǎn)擊更新按鈕的時(shí)候,更新配置類之后,打印配置類,觀察配置信息的變化。
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é)束------"); } }
注意事項(xiàng):本文只是進(jìn)行了相關(guān)實(shí)踐,相關(guān)原理請(qǐng)參照參考資料
。
參考資料
- Spring系列第25篇:@Value【用法、數(shù)據(jù)來源、動(dòng)態(tài)刷新】
- 【基礎(chǔ)系列】SpringBoot配置信息之配置刷新
- 【基礎(chǔ)系列】SpringBoot之自定義配置源的使用姿勢(shì)
- 【基礎(chǔ)系列】SpringBoot應(yīng)用篇@Value注解支持配置自動(dòng)刷新能力擴(kuò)展
- Spring Boot 中動(dòng)態(tài)更新 @Value 配置
到此這篇關(guān)于SpringBoot @value注解動(dòng)態(tài)刷新的文章就介紹到這了,更多相關(guān)SpringBoot 動(dòng)態(tài)刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea導(dǎo)入springboot項(xiàng)目沒有maven的解決
這篇文章主要介紹了idea導(dǎo)入springboot項(xiàng)目沒有maven的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04關(guān)于cron表達(dá)式每天整點(diǎn)執(zhí)行一次的問題
這篇文章主要介紹了關(guān)于cron表達(dá)式每天整點(diǎn)執(zhí)行一次的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12SpringBoot集成Validation參數(shù)校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了SpringBoot集成Validation參數(shù)校驗(yàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01springboot結(jié)合前端實(shí)現(xiàn)網(wǎng)頁跳轉(zhuǎn)功能實(shí)例
今天處理Springboot統(tǒng)一異常攔截的時(shí)候,遇到了頁面跳轉(zhuǎn)的問題,這篇文章主要給大家介紹了關(guān)于springboot結(jié)合前端實(shí)現(xiàn)網(wǎng)頁跳轉(zhuǎn)功能的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12