+ ~+ i. T8 n! ]" \6 q2 H/ e private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); " ], Q w' Q1 l8 v9 j9 O* _' K 8 z8 G. w# p) {5 p. B& M* D A' |} 8 b$ s( E* o, d4 F) T! F+ W内部包含一个propertySourceList列表。; ~5 n. ^# g! P/ y- B
: G* X2 c/ a2 N d' wspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。 7 W+ {) S( i0 i6 N0 g! B, [) W) K+ U/ @
大家可以捋一下,最终解析@Value的过程: - r. v: i/ ^3 {+ j3 i$ U4 \ 9 |' }2 }5 V: A7 W6 \9 O1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析 . S( v) J# d% | R$ u2. Environment内部会访问MutablePropertySources来解析2 P* n' m; J, Y5 T
3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值5 C6 G4 R9 m9 M: z+ c
通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。9 L( W3 ~, r0 A: ~9 w4 ?
/ Y3 o3 z5 F ]4 U% n
下面我们就按照这个思路来一个。 " y4 S( i4 t. Q1 \* Y3 c 0 J& U! v6 g2 |4 x8 W6 {2 h来个邮件配置信息类,内部使用@Value注入邮件配置信息& k9 s7 ]& L+ x
* i( G) R/ r( {+ D
package com.javacode2018.lesson002.demo18.test2;6 P9 f/ \0 e2 d" ]# M) N4 Z4 o
+ O8 D9 h. Q+ k; A( G9 Y0 r; L
import org.springframework.beans.factory.annotation.Value; 6 z3 Z e5 w- m+ v6 D( Pimport org.springframework.stereotype.Component; # i/ U" o8 }2 [+ h, [) h5 _3 b 7 H7 Q+ E( O3 i3 Y: _, H; j/** - U8 M' b4 e( {& h3 E; K * 邮件配置信息 ) W; T4 H6 _7 F) T */ / ~: P& `% G0 t* F@Component 9 G; D8 ~: Q& `' ]8 Gpublic class MailConfig { 1 j S7 F6 a& A2 S; t 8 t2 b. L& [1 n: D" {6 c @Value("${mail.host}") 6 G& B- f1 K) i, a private String host; 2 U( b" i7 e; U A- r- P" Z! ^0 t, U( [( a
@Value("${mail.username}")) r! v: k: P# X0 b V( n. j
private String username;$ [9 h: I u8 A, J& i6 Y
" A% T7 i) g2 t4 a4 K- y- n
@Value("${mail.password}")% x+ X! M) H, K5 b7 k) Y+ {
private String password; j8 U$ V, O4 I* h+ l n5 i: [$ B; H$ {+ @2 `7 S2 q
public String getHost() { 8 [/ W$ ]$ \' G/ b& O5 y return host; : R9 Q( o+ I0 u2 ?7 V( u/ Y } 2 d/ `% Z3 }8 A8 s' }8 `' q5 {- W5 r( e8 _% {0 h
public void setHost(String host) {% L/ x+ R, m d3 Y z
this.host = host;) \6 O6 r! q; Q: [0 X' a8 C* K
} - ?% [4 V& f% p% Z% ~, k. W9 @ 8 V+ Q) Q3 V$ U: F8 X" G public String getUsername() {/ r" O; z) D0 n! k! M# `2 S# ~
return username;) t& E' P3 x! t+ R6 c- ^
}2 X) @7 t Q7 q; f7 Q' l
, ]9 ]" H% S" B- k" [ public void setUsername(String username) {1 b I. N3 [1 W- n8 s8 f
this.username = username;8 W/ m8 @8 _0 C+ _: G) Q! \3 i
}0 j" H% T& k) ?# A1 K2 n
$ F6 r, F Y1 y0 D4 k7 Y8 y public String getPassword() { 1 N' D* } z4 j: U+ A return password; 2 @! x, X9 \6 b: a9 O( d } U7 z: S S3 I0 w: P
* m5 S) ?% Q- R/ @5 s6 b: q public void setPassword(String password) {1 ?( f% M+ o) }. C; p: L7 O5 Z
this.password = password;$ c5 b8 x ?- F) b4 q
} 4 o" @# _3 z4 L* y; t. {/ _) o1 k, d1 @) v2 p" o5 d. |' \
@Override 4 T5 Z! e+ ]. H t public String toString() {3 }* e- z- U4 Z
return "MailConfig{" + ! f. w0 j% S3 u0 N% B' a "host='" + host + '\'' +. j/ U, o4 _; O h. S4 L3 ~9 M
", username='" + username + '\'' +2 T9 E5 d% l# ~2 ~; z, A7 B5 Y) b
", password='" + password + '\'' + . J& X! E& `, `6 ~ '}';8 Q4 R0 v K" L, `2 ^5 c1 `
} % j1 e) q+ P9 E9 h0 L} 2 {% c* l% s d- H再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中, Q. z6 l% z) w; x+ C
& D V1 k6 i+ ]2 B! G! [$ E
package com.javacode2018.lesson002.demo18.test2; 3 y6 C: x9 c) k5 |& J7 L + D0 }3 ` M5 [2 z5 S, himport java.util.HashMap;" Q3 }2 T. Q/ k4 _! |) Q
import java.util.Map;) Q8 T1 c2 t ~
+ w. s4 B7 }% _; p( T, O/ a0 xpublic class DbUtil { ( _/ C' @9 U0 C /**' H. [% C" @+ K+ E
* 模拟从db中获取邮件配置信息 1 n8 A/ y+ K# o6 d2 r0 |/ z# [. e2 F * , [, T& S/ R1 e9 [% t0 n/ h * @return 9 V3 ~) W& d" Y) W5 R6 @2 R+ F; J */+ F. s) ]9 S2 K5 T, J0 k
public static Map<String, Object> getMailInfoFromDb() { ( Q- B" ~5 O% K c/ t6 [ Map<String, Object> result = new HashMap<>(); 6 p6 T7 y3 |# K" d result.put("mail.host", "smtp.qq.com"); 7 i8 z/ I$ y0 Y3 ?5 {5 b* I: k result.put("mail.username", "路人");0 z% Y, f; S t8 w. A Q# J
result.put("mail.password", "123");! j+ V8 N5 a: \0 E& p9 E; q
return result; 6 _6 A8 q+ x, D6 h } 8 U) |$ ^7 J4 A& Q4 Q- l$ @} & [: s3 m, C5 \! G i来个spring配置类5 x" f( q5 F+ ~, p" E# J
6 `) _/ \7 H7 C. l; M9 @
package com.javacode2018.lesson002.demo18.test2; $ X, y' B' i* y6 \" H. a, x/ f" f / c$ e" E4 d, W9 R, O& q: Gimport org.springframework.context.annotation.ComponentScan;9 u, W6 D9 o$ K4 t& e% Q* N# b
import org.springframework.context.annotation.Configuration; ( N! e& a: t- B9 _" f; S% W6 M, i% I. X3 A4 m( W) d
@Configuration " M: Q3 O3 `% l2 Y u0 [@ComponentScan; l8 @8 T. ^- `
public class MainConfig2 { 1 r% c! m; j" u) u6 c} / V; W R1 }( O& T下面是重点代码/ Z4 H4 g& j B a
, A# n R2 a" j6 ~- {
@Test ' \) ?5 P* w# I; v9 _public void test2() {& j5 R6 f' n; L
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();, M* Z2 V" c/ t
. I* K9 {8 j) N. C& @
/*下面这段是关键 start*/7 X: t2 ?& s7 I; y$ J& ~5 a8 A) r
//模拟从db中获取配置信息 # C& @4 L' a/ l7 s2 s Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); , ^4 W' J& I5 m# C3 J0 D( V5 ^ //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类) 9 D" v; c' u' `+ a- J+ F! z5 i MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); ( a# W& w: Q. R. g* t# p3 G //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高, z! h5 p/ w K8 Y
context.getEnvironment().getPropertySources().addFirst(mailPropertySource); ( F* ~# Z/ \' D /*上面这段是关键 end*/ 0 L! y. E! I! F9 o# a8 ^. P8 C* S5 m7 ~0 a
context.register(MainConfig2.class); , {* y2 n# g, E0 N, Q$ W) f2 ? context.refresh();, o8 ?, j9 Y+ [: U4 O, G) H
MailConfig mailConfig = context.getBean(MailConfig.class); 4 G8 r1 c/ q9 P9 C! ^ System.out.println(mailConfig);0 g% ?9 [7 S& o
} 5 r% Z6 k& T6 I8 M: d/ |注释比较详细,就不详细解释了。 4 Z% a+ g2 \- a* a4 h6 Q Y$ A7 A 3 d b2 C& l% U+ ?8 H6 P直接运行,看效果3 x! {0 ~4 J$ m) [4 i* l5 f
; `6 f3 y1 T# s8 z2 N, i2 SMailConfig{host='smtp.qq.com', username='路人', password='123'}: ^# e7 I' Y1 \9 G/ S9 A, f
有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。5 c( s5 s6 o9 q4 C
$ {2 t i5 ~' f/ k9 y8 ?! w上面重点是下面这段代码,大家需要理解9 c$ ]# `* e& b% x+ j2 Z! k _ S
Z$ f( y2 U* g/ k
/*下面这段是关键 start*/ ' K% b/ I c% R- c T& b; ` z//模拟从db中获取配置信息7 D* U( |# t) N$ V6 r
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); ! x: z% w( ^( ?% q( P9 e+ H3 l//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)* g& s. e- c) `& i# ?# ~
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);6 |. v1 d$ _9 ^. p, |# @
//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高 ' {8 n# z9 e4 P2 S* v9 J: a4 ?' Hcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);2 s/ r2 z4 j2 _4 n
/*上面这段是关键 end*/ 7 ~5 E" e8 M2 u% I" G# |咱们继续看下一个问题 9 ^( f# Q% i# j2 {% M 4 ]9 @" o P) b4 g* P3 S3 U2 @6 I$ k如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。8 k8 `0 x+ `/ X+ z9 ]$ y% J
5 B u6 U3 c F6 N
@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。9 O, d* q8 ^/ d3 O2 Q
6 v5 d4 }3 a3 T: ^0 N
实现@Value动态刷新+ k$ l7 o+ t4 ?
& t5 e1 K2 ?2 G7 B) e. t% \* q1 e, C先了解一个知识点 6 ~' @# y* z+ b6 _0 T8 J # k0 o" D/ P G& i2 G这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。, |) d; D" A) c+ N. w( q5 A% P
9 \0 I* ? }# P这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解8 E4 c- l6 M* D
; L* }, F- g. B. {2 h! \
bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:4 w3 Z0 d; O+ `+ I- t% A
4 k# X' n, n: }9 ^) d) a; }5 k" l% vScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; ' h$ E. ]/ n h2 Q' X5 h3 D) W( D* r' R这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中 $ S9 k. U! c) H& a& K( R1 b7 q3 y" g! {) R1 X( y
public enum ScopedProxyMode {2 a& o& U1 X. g
DEFAULT,0 a( L- F1 B. U8 @* c4 |/ @8 G
NO,8 |7 Q* p0 C! K3 I- u% D; e0 s
INTERFACES, % i# f, B0 s- S6 |0 p# t% H0 A O TARGET_CLASS;' h, {6 S* Y! e, M& r7 ]% s
} & Y' ?2 E2 h. p0 {前面3个,不讲了,直接讲最后一个值是干什么的。 & ^9 P% F2 Q$ i2 m. Y" C1 X& _' V2 Z+ h8 n) O# E( ]/ ~$ S2 V
当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。 % q1 j7 @5 r+ S' h( P' K' o' l& T: k) h: R6 T
理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。& g) f/ y/ Z; }$ D' S2 c
1 f/ m" @$ g# z1 H# s
自定义一个bean作用域的注解4 S8 x2 Y. _* O7 ]) S
3 I, k m T+ ~% |4 g) Tpackage com.javacode2018.lesson002.demo18.test3; ) o& F( {7 [" f9 p' |4 W B 1 U( y' ~2 P- M+ mimport org.springframework.context.annotation.Scope;7 m3 U4 u* r' {; Z- I6 ^- t
import org.springframework.context.annotation.ScopedProxyMode;) g/ n/ x. O# H! B
# g/ a* H* O/ Limport java.lang.annotation.*;" c& j) D- t. b9 `( g) c
8 ?; r5 }* u8 r: m+ D
@Target({ElementType.TYPE, ElementType.METHOD}) 6 g% B+ A+ l! A, X- T2 H@Retention(RetentionPolicy.RUNTIME)+ ]$ ~. U) e, P5 p _3 J
@Documented8 K5 P \, ^) n& L5 y+ ~; _1 d T4 x1 k
@Scope(BeanMyScope.SCOPE_MY) //@10 z" }% h6 W* {4 q. h& W% }3 u/ {3 l( p
public @interface MyScope { : L- r, }& M4 j6 ^2 J /** , c3 p, \/ e W7 i- f8 m; R- e; W * @see Scope#proxyMode()7 T2 p8 F" R: g7 @& c
*/ - Q0 b( X/ ~* b# n ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@20 C9 D, t1 c, o# v
}6 U i6 d- f; J+ N4 @
@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。 0 V, C7 F2 }, Y0 g, b% a+ y3 F & o1 l* p* ^9 W( Q/ h% C3 ?! {5 ~! R@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS+ ?4 W9 ]- d$ Z
6 T- Q; P# T* ?1 K5 S
@MyScope注解对应的Scope实现如下 ' o7 Z f" n6 | ) E/ V" M( ~- v4 Tpackage com.javacode2018.lesson002.demo18.test3;/ x" N3 ]: i' c" E9 v! }9 |
4 L7 T( u! I% q) E7 M' y public String getUsername() { # E# r+ N5 b Y4 V- n* K7 w2 g2 }2 x return username;4 v6 O! y6 Q$ L: u; D: K
}1 T# `+ f, V9 ~3 N- @7 R
( n% T# ?# M" H$ g+ A+ O+ s public void setUsername(String username) {) I m" E1 @: T& B8 N9 ]- @
this.username = username; * K- e0 M7 _! k }( M: [8 x6 n* f* V, Y
0 I5 g# Z) [( o0 ? @Override / R F/ Y' w+ h9 E m2 u public String toString() {5 M1 o0 H+ d2 w% \
return "MailConfig{" + 5 _$ r' x4 o. S" v& a "username='" + username + '\'' + 5 v* l1 S; X* M; r '}'; : F. V. c! t- {7 [ } 4 H& |, ?6 x/ i9 g" r! Z& _} 7 y4 d1 ^; _% v@1:使用了自定义的作用域@RefreshScope$ n5 V; g: W: [8 L1 K4 a
( f) F k; Y" e
@2:通过@Value注入mail.username对一个的值4 N9 {* c* T1 ^6 H7 A9 {. h
2 i; n( z+ F( S9 m% l. H
重写了toString方法,一会测试时候可以看效果。/ K5 ^! i" }# l
, d2 {, ?. Q4 k- W2 \. K
再来个普通的bean,内部会注入MailConfig& ~2 d9 g. ^4 D: Z
1 z h4 t9 L+ g! ~package com.javacode2018.lesson002.demo18.test4;/ Y$ P8 E6 ~- ]9 f5 Y; _
% {: M* t; g$ ~6 f1 S8 \
import org.springframework.beans.factory.annotation.Autowired; 8 |$ k9 O1 h% F- i$ limport org.springframework.stereotype.Component;! g# L7 E" Q1 |) h* y7 v4 Z
# ^! X" e& z, h
@Component ; X1 b7 b. A9 i# v f! ppublic class MailService { : i z7 H' o; F @Autowired1 R# Z$ o$ a) p) s7 o* N+ A) L
private MailConfig mailConfig;' S$ V+ {' y- v' G
1 @8 _5 C% e4 I" ~) D
@Override : [9 X2 w9 t% l5 H; ^* ] public String toString() {4 j( Z( q2 \& W% n$ ^4 ?9 W) @' u
return "MailService{" + x' V0 J3 X1 v6 O$ K1 i8 ?8 B
"mailConfig=" + mailConfig +- Q; g+ [" c. R8 [" ]1 s5 }
'}';3 |. {/ P3 V" d$ F- \' y
} # l; }, Y( f: B: w& @} 6 c$ x" A- A- P. ~代码比较简单,重写了toString方法,一会测试时候可以看效果。 ( O: {1 G' v$ v7 @1 ?2 G+ F9 i. d6 d * M: N( Z4 t7 t来个类,用来从db中获取邮件配置信息8 q, O. T; G0 o& K. j T( Y
/ P& y U7 Y# T: m/ n% `9 F
package com.javacode2018.lesson002.demo18.test4; ; @ T6 C9 \4 v( ^+ A' }! p1 Y8 l% G, r T9 w( ?$ M H. n2 W1 S; I& {$ f
import java.util.HashMap; , Q0 W' K/ b+ X) o$ Kimport java.util.Map;& C6 G" u8 f# H2 O7 r3 O$ G
import java.util.UUID;! X$ G" T5 H1 F& m2 N* t
9 h/ z8 \: E! {0 bpublic class DbUtil {: y+ F1 E1 D! j- i. c! A7 ^7 x& Q
/**/ @7 I H- {5 x+ \0 y1 k5 g
* 模拟从db中获取邮件配置信息 9 Z4 K8 b; D1 f& H- b) W, }" L f * % q, J2 w/ l4 { * @return 4 S) z: i0 n9 \7 ^! R5 ^ */$ F: j) F; P5 ^
public static Map<String, Object> getMailInfoFromDb() {1 D$ K0 `# a& V- o+ [) V
Map<String, Object> result = new HashMap<>(); 1 r, V: R- A. p- X, P, S result.put("mail.username", UUID.randomUUID().toString()); 4 G' Z/ F- f/ A return result; % f5 N" v8 f9 A+ J& D! U) ~ }; I# `7 J4 Q9 s# D6 ^5 E
} $ O6 T T% O2 a/ d/ ]& R' }来个spring配置类,扫描加载上面的组件' a1 G2 n, g/ W& O. `: C \) V- `
M" y0 @1 I3 B3 I5 U
package com.javacode2018.lesson002.demo18.test4;" W, L* J2 m5 v
6 j6 m' g* ?/ a6 ^( T" n
import org.springframework.context.annotation.ComponentScan;* F1 j3 [9 i. j% z) |
import org.springframework.context.annotation.Configuration; / R. J P1 @, f 4 S5 `- B. U c: J' }@Configuration& s- ]% X& o: B3 L
@ComponentScan% N u& G6 W0 w' P5 |4 c1 M7 x
public class MainConfig4 {1 }( {0 E. j5 C( C( [
}( l* J" O7 r( u' \1 Z
来个工具类3 y+ t% n( `" `9 C7 k0 f2 f' t, D
) F; M4 X5 |- E) [% j
内部有2个方法,如下: ! p5 k$ _7 u8 o* n5 T! {& m& E0 S# i5 U; ~
package com.javacode2018.lesson002.demo18.test4; , ?6 g! G$ z9 P4 U5 U P: e $ s+ d9 n) k' H B! |9 limport org.springframework.context.support.AbstractApplicationContext;: C: J" h+ d5 @" B# l% ]
import org.springframework.core.env.MapPropertySource;& L$ D: m% ^8 s3 l7 Q3 X& k! C
4 Z" y6 v F2 C
import java.util.Map; ( z1 R6 c; R6 g8 ] ( a* Q/ u5 z( L9 Bpublic class RefreshConfigUtil { 2 T$ e4 T' ]2 w) I* J+ D3 J! n% |% r /**) v6 @4 T$ G% W
* 模拟改变数据库中都配置信息. @& R4 R) N, z. t/ {% X0 c
*/ " D/ C) s5 _$ r( f' C public static void updateDbConfig(AbstractApplicationContext context) { 4 @) R3 [$ Z8 T //更新context中的mailPropertySource配置信息 & m9 J2 M' x* Q/ r: z5 h# [2 ` refreshMailPropertySource(context); 7 ]0 H3 Q4 C2 ^& B & C& H: ~) j5 ?4 {, D //清空BeanRefreshScope中所有bean的缓存 ! I( t2 i8 F2 r BeanRefreshScope.getInstance().clean();3 z& a7 q# A/ @/ ~" d/ }6 t+ D$ m
} 5 _) y! Q" z! L ; |! j) E$ _* u9 L: S- K public static void refreshMailPropertySource(AbstractApplicationContext context) { 9 ? D0 k4 F) ]! N0 S Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();" W R! a# p( ]& Q
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)) F9 D: i! n' Y7 j
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); 8 M* v# X0 c+ s( L$ ~ context.getEnvironment().getPropertySources().addFirst(mailPropertySource); , E' T# g( i9 P2 Y6 V7 u6 Y/ z }0 W" L# w- M3 m+ k% P7 M/ L
; b$ W" X# J+ I {* u, ~8 r9 w
} ; Q$ U, a8 W% Q( Z1 NupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息6 _$ |: w" [. Y3 D% p0 |
+ I. r. n+ h5 B3 _8 gBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。$ s6 }" k% s: E+ u
( N4 A. m# p. \5 w+ }来个测试用例: A1 T+ Z! ~3 Y1 O. e
' c! \' v% c5 N/ ]@Test ) r, u# i+ V; M) j, g/ P# s6 W. Q+ ypublic void test4() throws InterruptedException { " s2 U! f3 w9 h4 k* F AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();( e# k: a/ J! f
context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance()); - c' I' }$ Q+ I" z5 k context.register(MainConfig4.class);8 Z" ]! C) @' O/ a/ [
//刷新mail的配置到Environment, l2 L5 |3 B! Z0 f1 G/ w; S
RefreshConfigUtil.refreshMailPropertySource(context);# w, j5 S6 N# o. d
context.refresh(); 3 v/ D8 ^! H& k; ~! t 1 e+ R6 y1 t2 N/ j5 I- [ MailService mailService = context.getBean(MailService.class);" N4 ]7 Q" @* o" x
System.out.println("配置未更新的情况下,输出3次");$ v( `, \8 m$ d$ y5 P M
for (int i = 0; i < 3; i++) { //@1 ( [9 i5 J) w+ \; ` System.out.println(mailService);0 k) \( o" y( e8 X5 d
TimeUnit.MILLISECONDS.sleep(200);. ^" _3 k9 v) a* s0 V3 i) t
} . L9 d5 z6 S3 b Y" [* r4 ? 0 K- Z# N0 R% l! n- { System.out.println("模拟3次更新配置效果"); 4 L1 ~9 g' c% y6 _* I! h for (int i = 0; i < 3; i++) { //@2 4 @ g1 m$ r+ p: } RefreshConfigUtil.updateDbConfig(context); //@3: G* Z) y# C+ p2 m8 T1 f: M
System.out.println(mailService); $ a4 V n3 I" R1 l TimeUnit.MILLISECONDS.sleep(200);! v( L5 f/ q/ k
}9 D: b& l3 f( X9 n" x1 j" I
}2 c% i8 S- |( H1 }1 w/ U
@1:循环3次,输出mailService的信息 3 U! l1 ]/ X$ m0 k- e$ D' x G# \/ h i" c6 ]0 s
@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息3 T2 c! J9 b# @# c J
8 ?8 `! }% J+ b* e
见证奇迹的时刻,来看效果- V" U$ i5 k3 x" d# y% Z: Z
$ J. F. K* [& C1 i配置未更新的情况下,输出3次7 O. S, }: S$ k d0 M4 s
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} " p Q' `( n, o3 F9 e- H) dMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} 1 v" a! a5 o1 m: z7 NMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ p& s6 K1 i* B7 F9 j$ r8 C& E
模拟3次更新配置效果 8 W! e% j1 _3 t2 [- |; sMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}} 4 P r. J) ^& z1 v9 [$ }% XMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}} - |/ Y0 [6 G; \ bMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}1 [$ ~ n0 ~) _, G, D' [! l
上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。4 v5 |& o0 K- r2 c" w
4 l7 M; }- [6 e5 s% w( m小结 # {: U2 A0 z; Z" V4 T! W# v$ ^2 {9 i# V4 U$ T' r
动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。 - @1 i+ V3 b l7 q+ V! \ + d# |! y5 G; i/ A有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。 0 d2 E) C1 h Y$ j d4 @6 l9 u' n0 a+ w0 u0 V
总结 5 X) `. Y( p! f5 B0 ? $ B* B H6 ~0 {+ O; a0 s* ^本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流! * {/ o3 ?, y, q$ [+ U 1 {+ @4 s, Q/ i6 V案例源码 2 L9 I* I2 t3 U2 [$ S5 m 4 w7 }5 `6 p7 p$ y! J% Nhttps://gitee.com/javacode2018/spring-series q8 L( k I7 [9 c/ ^& h+ A: T
路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。3 {( ]/ @) f6 @" E
; X W1 F! N5 z7 t
Spring系列 1 E6 |3 S5 V' [. ], i9 } 8 h0 @, i/ V6 j3 G' w7 j- @$ y1 JSpring系列第1篇:为何要学spring? % ^) p, S7 O$ M; L( p. H, ?0 e0 \& o5 K
Spring系列第2篇:控制反转(IoC)与依赖注入(DI)7 c& o1 u! } u