面試官:Spring中的@Value用過么,介紹一下
我:@Value可以標注在字段上面,可以將外部配置文件中的數據,比如可以將數據庫的一些配置信息放在配置文件中,然后通過@Value的方式將其注入到bean的一些字段中
面試官:那就是說@Value的數據來源於配置文件了?
我:嗯,我們項目最常用更多就是通過@Value來引用Properties文件中的配置
面試官:@Value數據來源還有其他方式么?
我:此時我異常開心,剛好問的我都研究過,我說:當然有,可以將配置信息放在db或者其他存儲介質中,容器啟動的時候,可以將這些信息加載到Environment中,@Value中應用的值最終是通過Environment來解析的,所以只需要擴展一下Environment就可以實現了。
面試官:不錯嘛,看來你對spring研究的還是可以,是不是喜歡研究spring源碼?
我:笑着說,嗯,平時有空的時候確實喜歡搗鼓搗鼓源碼,感覺自己對spring了解的還可以,不能算精通,也算是半精通吧
面試官:看着我笑了笑,那@Value的注入的值可以動態刷新么?
我:應該可以吧,我記得springboot中有個@RefreshScope注解就可以實現你說的這個功能
面試官:那你可以說一下@RefreshScope是如何實現的么,可以大概介紹一下?
我:嗯。。。這個之前看過一點,不過沒有看懂
面試官:沒關系,你可以回去了再研究一下;你期望工資多少?
我:3萬吧
面試官:今天的面試還算是可以的,不過如果@RefreshScope能回答上來就更好了,這塊是個加分項,不過也確實有點難度,2.5萬如何?
我:(心中默默想了想:2.5萬,就是一個問題沒有回答好,砍了5000,有點狠啊,我要回去再研究研究,3萬肯定是沒問題的),我說:最低2.9萬
面試官:那謝謝你,今天面試就到這里,出門右拐,不送!
我有個好習慣,每次面試回去之后,都會進行復盤,把沒有搞定的問題一定要想辦法搞定,這樣才不虛。
這次面試問題如下
-
@Value的用法
-
@Value數據來源
-
@Value動態刷新的問題
下面我們一個個來整理一下,將這幾個問題搞定,助大家在疫情期間面試能夠過關斬將,拿高薪。
@Value的用法
系統中需要連接db,連接db有很多配置信息。
系統中需要發送郵件,發送郵件需要配置郵件服務器的信息。
還有其他的一些配置信息。
我們可以將這些配置信息統一放在一個配置文件中,上線的時候由運維統一修改。
那么系統中如何使用這些配置信息呢,spring中提供了@Value注解來解決這個問題。
通常我們會將配置信息以key=value的形式存儲在properties配置文件中。
通過@Value("${配置文件中的key}")來引用指定的key對應的value。
@Value使用步驟
步驟一:使用@PropertySource注解引入配置文件
將@PropertySource放在類上面,如下
@PropertySource({"配置文件路徑1","配置文件路徑2"...})
@PropertySource注解有個value屬性,字符串數組類型,可以用來指定多個配置文件的路徑。
如:
@Component
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
public class DbConfig {
}
步驟二:使用@Value注解引用配置文件的值
通過@Value引用上面配置文件中的值:
語法
@Value("${配置文件中的key:默認值}")
@Value("${配置文件中的key}")
如:
@Value("${password:123}")
上面如果password不存在,將123作為值
@Value("${password}")
上面如果password不存在,值為${password}
假如配置文件如下
jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=javacode
jdbc.password=javacode
使用方式如下:
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
下面來看案例
案例
來個配置文件db.properties
jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=javacode
jdbc.password=javacode
來個配置類,使用@PropertySource引入上面的配置文件
package com.javacode2018.lesson002.demo18.test1;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
@Configurable
@ComponentScan
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
public class MainConfig1 {
}
來個類,使用@Value來使用配置文件中的信息
package com.javacode2018.lesson002.demo18.test1;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DbConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "DbConfig{" +
"url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
上面重點在於注解@Value注解,注意@Value注解中的
來個測試用例
package com.javacode2018.lesson002.demo18;
import com.javacode2018.lesson002.demo18.test1.DbConfig;
import com.javacode2018.lesson002.demo18.test1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ValueTest {
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig1.class);
context.refresh();
DbConfig dbConfig = context.getBean(DbConfig.class);
System.out.println(dbConfig);
}
}
運行輸出
DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
上面用起來比較簡單,很多用過的人看一眼就懂了,這也是第一個問題,多數人都是ok的,下面來看@Value中數據來源除了配置文件的方式,是否還有其他方式。
@Value數據來源
通常情況下我們@Value的數據來源於配置文件,不過,還可以用其他方式,比如我們可以將配置文件的內容放在數據庫,這樣修改起來更容易一些。
我們需要先了解一下@Value中數據來源於spring的什么地方。
spring中有個類
org.springframework.core.env.PropertySource
可以將其理解為一個配置源,里面包含了key->value的配置信息,可以通過這個類中提供的方法獲取key對應的value信息
內部有個方法:
public abstract Object getProperty(String name);
通過name獲取對應的配置信息。
系統有個比較重要的接口
org.springframework.core.env.Environment
用來表示環境配置信息,這個接口有幾個方法比較重要
String resolvePlaceholders(String text);
MutablePropertySources getPropertySources();
resolvePlaceholders用來解析${text}
的,@Value注解最后就是調用這個方法來解析的。
getPropertySources返回MutablePropertySources對象,來看一下這個類
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
}
內部包含一個propertySourceList
列表。
spring容器中會有一個Environment
對象,最后會調用這個對象的resolvePlaceholders
方法解析@Value。
大家可以捋一下,最終解析@Value的過程:
1. 將@Value注解的value參數值作為Environment.resolvePlaceholders方法參數進行解析
2. Environment內部會訪問MutablePropertySources來解析
3. MutablePropertySources內部有多個PropertySource,此時會遍歷PropertySource列表,調用PropertySource.getProperty方法來解析key對應的值
通過上面過程,如果我們想改變@Value數據的來源,只需要將配置信息包裝為PropertySource對象,丟到Environment中的MutablePropertySources內部就可以了。
下面我們就按照這個思路來一個。
來個郵件配置信息類,內部使用@Value注入郵件配置信息
package com.javacode2018.lesson002.demo18.test2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 郵件配置信息
*/
@Component
public class MailConfig {
@Value("${mail.host}")
private String host;
@Value("${mail.username}")
private String username;
@Value("${mail.password}")
private String password;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "MailConfig{" +
"host='" + host + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
再來個類DbUtil
,getMailInfoFromDb
方法模擬從db中獲取郵件配置信息,存放在map中
package com.javacode2018.lesson002.demo18.test2;
import java.util.HashMap;
import java.util.Map;
public class DbUtil {
/**
* 模擬從db中獲取郵件配置信息
*
* @return
*/
public static Map<String, Object> getMailInfoFromDb() {
Map<String, Object> result = new HashMap<>();
result.put("mail.host", "smtp.qq.com");
result.put("mail.username", "路人");
result.put("mail.password", "123");
return result;
}
}
來個spring配置類
package com.javacode2018.lesson002.demo18.test2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class MainConfig2 {
}
下面是重點代碼
@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
/*下面這段是關鍵 start*/
//模擬從db中獲取配置信息
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
//將其丟在MapPropertySource中(MapPropertySource類是spring提供的一個類,是PropertySource的子類)
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
//將mailPropertySource丟在Environment中的PropertySource列表的第一個中,讓優先級最高
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
/*上面這段是關鍵 end*/
context.register(MainConfig2.class);
context.refresh();
MailConfig mailConfig = context.getBean(MailConfig.class);
System.out.println(mailConfig);
}
注釋比較詳細,就不詳細解釋了。
直接運行,看效果
MailConfig{host='smtp.qq.com', username='路人', password='123'}
有沒有感覺很爽,此時你們可以隨意修改DbUtil.getMailInfoFromDb
,具體數據是從db中來,來時從redis或者其他介質中來,任由大家發揮。
上面重點是下面這段代碼,大家需要理解
/*下面這段是關鍵 start*/
//模擬從db中獲取配置信息
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
//將其丟在MapPropertySource中(MapPropertySource類是spring提供的一個類,是PropertySource的子類)
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
//將mailPropertySource丟在Environment中的PropertySource列表的第一個中,讓優先級最高
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
/*上面這段是關鍵 end*/
咱們繼續看下一個問題
如果我們將配置信息放在db中,可能我們會通過一個界面來修改這些配置信息,然后保存之后,希望系統在不重啟的情況下,讓這些值在spring容器中立即生效。
@Value動態刷新的問題的問題,springboot中使用@RefreshScope實現了。
實現@Value動態刷新
先了解一個知識點
這塊需要先講一個知識點,用到的不是太多,所以很多人估計不太了解,但是非常重要的一個點,我們來看一下。
這個知識點是自定義bean作用域
,對這塊不了解的先看一下這篇文章:bean作用域詳解
bean作用域中有個地方沒有講,來看一下@Scope這個注解的源碼,有個參數是:
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
這個參數的值是個ScopedProxyMode類型的枚舉,值有下面4中
public enum ScopedProxyMode {
DEFAULT,
NO,
INTERFACES,
TARGET_CLASS;
}
前面3個,不講了,直接講最后一個值是干什么的。
當@Scope中proxyMode為TARGET_CLASS的時候,會給當前創建的bean通過cglib生成一個代理對象,通過這個代理對象來訪問目標bean對象。
理解起來比較晦澀,還是來看代碼吧,容易理解一些,來個自定義的Scope案例。
自定義一個bean作用域的注解
package com.javacode2018.lesson002.demo18.test3;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(BeanMyScope.SCOPE_MY) //@1
public @interface MyScope {
/**
* @see Scope#proxyMode()
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
}
@1:使用了@Scope注解,value為引用了一個常量,值為my,一會下面可以看到。
@2:注意這個地方,參數名稱也是proxyMode,類型也是ScopedProxyMode,而@Scope注解中有個和這個同樣類型的參數,spring容器解析的時候,會將這個參數的值賦給@MyScope注解上面的@Scope注解的proxyMode參數,所以此處我們設置proxyMode值,最后的效果就是直接改變了@Scope中proxyMode參數的值。此處默認值取的是ScopedProxyMode.TARGET_CLASS
@MyScope注解對應的Scope實現如下
package com.javacode2018.lesson002.demo18.test3;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
/**
* @see MyScope 作用域的實現
*/
public class BeanMyScope implements Scope {
public static final String SCOPE_MY = "my"; //@1
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
return objectFactory.getObject(); //@3
}
@Nullable
@Override
public Object remove(String name) {
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Nullable
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Nullable
@Override
public String getConversationId() {
return null;
}
}
@1:定義了一個常量,作為作用域的值
@2:這個get方法是關鍵,自定義作用域會自動調用這個get方法來創建bean對象,這個地方輸出了一行日志,為了一會方便看效果
@3:通過objectFactory.getObject()獲取bean實例返回。
下面來創建個類,作用域為上面自定義的作用域
package com.javacode2018.lesson002.demo18.test3;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
@MyScope //@1
public class User {
private String username;
public User() {
System.out.println("---------創建User對象" + this); //@2
this.username = UUID.randomUUID().toString(); //@3
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
@1:使用了自定義的作用域@MyScope
@2:構造函數中輸出一行日志
@3:給username賦值,通過uuid隨機生成了一個
來個spring配置類,加載上面@Compontent標注的組件
package com.javacode2018.lesson002.demo18.test3;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan
@Configuration
public class MainConfig3 {
}
下面重點來了,測試用例
@Test
public void test3() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//將自定義作用域注冊到spring容器中
context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
context.register(MainConfig3.class);
context.refresh();
System.out.println("從容器中獲取User對象");
User user = context.getBean(User.class); //@2
System.out.println("user對象的class為:" + user.getClass()); //@3
System.out.println("多次調用user的getUsername感受一下效果\n");
for (int i = 1; i <= 3; i++) {
System.out.println(String.format("********\n第%d次開始調用getUsername", i));
System.out.println(user.getUsername());
System.out.println(String.format("第%d次調用getUsername結束\n********\n", i));
}
}
@1:將自定義作用域注冊到spring容器中
@2:從容器中獲取User對應的bean
@3:輸出這個bean對應的class,一會認真看一下,這個類型是不是User類型的
代碼后面又搞了3次循環,調用user的getUsername方法,並且方法前后分別輸出了一行日志。
見證奇跡的時候到了,運行輸出
從容器中獲取User對象
user對象的class為:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
多次調用user的getUsername感受一下效果
********
第1次開始調用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------創建User對象com.javacode2018.lesson002.demo18.test3.User@6a370f4
7b41aa80-7569-4072-9d40-ec9bfb92f438
第1次調用getUsername結束
********
********
第2次開始調用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------創建User對象com.javacode2018.lesson002.demo18.test3.User@1613674b
01d67154-95f6-44bb-93ab-05a34abdf51f
第2次調用getUsername結束
********
********
第3次開始調用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------創建User對象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
76d0e86f-8331-4303-aac7-4acce0b258b8
第3次調用getUsername結束
********
從輸出的前2行可以看出:
-
調用context.getBean(User.class)從容器中獲取bean的時候,此時並沒有調用User的構造函數去創建User對象
-
第二行輸出的類型可以看出,getBean返回的user對象是一個cglib代理對象。
后面的日志輸出可以看出,每次調用user.getUsername方法的時候,內部自動調用了BeanMyScope#get 方法和 User的構造函數。
通過上面的案例可以看出,當自定義的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的時候,會給這個bean創建一個代理對象,調用代理對象的任何方法,都會調用這個自定義的作用域實現類(上面的BeanMyScope)中get方法來重新來獲取這個bean對象。
動態刷新@Value具體實現
那么我們可以利用上面講解的這種特性來實現@Value的動態刷新,可以實現一個自定義的Scope,這個自定義的Scope支持@Value注解自動刷新,需要使用@Value注解自動刷新的類上面可以標注這個自定義的注解,當配置修改的時候,調用這些bean的任意方法的時候,就讓spring重啟初始化一下這個bean,這個思路就可以實現了,下面我們來寫代碼。
先來自定義一個Scope:RefreshScope
package com.javacode2018.lesson002.demo18.test4;
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; //@1
}
要求標注@RefreshScope注解的類支持動態刷新@Value的配置
@1:這個地方是個關鍵,使用的是ScopedProxyMode.TARGET_CLASS
這個自定義Scope對應的解析類
下面類中有幾個無關的方法去掉了,可以忽略
package com.javacode2018.lesson002.demo18.test4;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
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<>(); //@1
private BeanRefreshScope() {
}
public static BeanRefreshScope getInstance() {
return INSTANCE;
}
/**
* 清理當前
*/
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;
}
}
上面的get方法會先從beanMap中獲取,獲取不到會調用objectFactory的getObject讓spring創建bean的實例,然后丟到beanMap中
上面的clean方法用來清理beanMap中當前已緩存的所有bean
來個郵件配置類,使用@Value注解注入配置,這個bean作用域為自定義的@RefreshScope
package com.javacode2018.lesson002.demo18.test4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 郵件配置信息
*/
@Component
@RefreshScope //@1
public class MailConfig {
@Value("${mail.username}") //@2
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "MailConfig{" +
"username='" + username + '\'' +
'}';
}
}
@1:使用了自定義的作用域@RefreshScope
@2:通過@Value注入mail.username對一個的值
重寫了toString方法,一會測試時候可以看效果。
再來個普通的bean,內部會注入MailConfig
package com.javacode2018.lesson002.demo18.test4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MailService {
@Autowired
private MailConfig mailConfig;
@Override
public String toString() {
return "MailService{" +
"mailConfig=" + mailConfig +
'}';
}
}
代碼比較簡單,重寫了toString方法,一會測試時候可以看效果。
來個類,用來從db中獲取郵件配置信息
package com.javacode2018.lesson002.demo18.test4;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class DbUtil {
/**
* 模擬從db中獲取郵件配置信息
*
* @return
*/
public static Map<String, Object> getMailInfoFromDb() {
Map<String, Object> result = new HashMap<>();
result.put("mail.username", UUID.randomUUID().toString());
return result;
}
}
來個spring配置類,掃描加載上面的組件
package com.javacode2018.lesson002.demo18.test4;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class MainConfig4 {
}
來個工具類
內部有2個方法,如下:
package com.javacode2018.lesson002.demo18.test4;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.MapPropertySource;
import java.util.Map;
public class RefreshConfigUtil {
/**
* 模擬改變數據庫中都配置信息
*/
public static void updateDbConfig(AbstractApplicationContext context) {
//更新context中的mailPropertySource配置信息
refreshMailPropertySource(context);
//清空BeanRefreshScope中所有bean的緩存
BeanRefreshScope.getInstance().clean();
}
public static void refreshMailPropertySource(AbstractApplicationContext context) {
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
//將其丟在MapPropertySource中(MapPropertySource類是spring提供的一個類,是PropertySource的子類)
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
}
}
updateDbConfig方法模擬修改db中配置的時候需要調用的方法,方法中2行代碼,第一行代碼調用refreshMailPropertySource方法修改容器中郵件的配置信息
BeanRefreshScope.getInstance().clean()用來清除BeanRefreshScope中所有已經緩存的bean,那么調用bean的任意方法的時候,會重新出發spring容器來創建bean,spring容器重新創建bean的時候,會重新解析@Value的信息,此時容器中的郵件配置信息是新的,所以@Value注入的信息也是新的。
來個測試用例
@Test
public void test4() throws InterruptedException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
context.register(MainConfig4.class);
//刷新mail的配置到Environment
RefreshConfigUtil.refreshMailPropertySource(context);
context.refresh();
MailService mailService = context.getBean(MailService.class);
System.out.println("配置未更新的情況下,輸出3次");
for (int i = 0; i < 3; i++) { //@1
System.out.println(mailService);
TimeUnit.MILLISECONDS.sleep(200);
}
System.out.println("模擬3次更新配置效果");
for (int i = 0; i < 3; i++) { //@2
RefreshConfigUtil.updateDbConfig(context); //@3
System.out.println(mailService);
TimeUnit.MILLISECONDS.sleep(200);
}
}
@1:循環3次,輸出mailService的信息
@2:循環3次,內部先通過@3來模擬更新db中配置信息,然后在輸出mailService信息
見證奇跡的時刻,來看效果
配置未更新的情況下,輸出3次
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
模擬3次更新配置效果
MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
上面MailService輸出了6次,前3次username的值都是一樣的,后面3次username的值不一樣了,說明修改配置起效了。
小結
動態@Value實現的關鍵是@Scope中proxyMode參數,值為ScopedProxyMode.DEFAULT,會生成一個代理,通過這個代理來實現@Value動態刷新的效果,這個地方是關鍵。
有興趣的可以去看一下springboot中的@RefreshScope注解源碼,和我們上面自定義的@RefreshScope類似,實現原理類似的。
案例源碼
https://gitee.com/javacode2018/spring-series
路人甲java所有案例代碼以后都會放到這個上面,大家watch一下,可以持續關注動態。
參考:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648934401&idx=1&sn=98e726ec9adda6d40663f624705ba2e4&chksm=8862103fbf15992981183abef03b4774ab1dfd990a203a183efb8d118455ee4b477dc6cba50d&token=636643900&lang=zh_CN&scene=21#wechat_redirect