, g: e' ]( g+ M) G1 I+ p public void setHost(String host) { , g5 g, ^) K# z& F( B this.host = host; 1 @- w: r( f) Q7 v: s }3 T3 ~4 m6 _: \; y
$ u2 H" s9 U7 G w- o) e
public String getUsername() { . Q8 K* N+ x$ b, T$ C return username;5 o8 P" t, j9 @
}5 m4 m+ l/ I. g9 E
4 O8 G( j2 |# W+ E* m public void setUsername(String username) {* D- X9 g7 N ~3 S& \, z- t
this.username = username;9 ?# ]$ }9 B8 R
} . D# m8 ?& H3 j# E' I6 T / w: `5 ]; `3 N5 \& |9 D public String getPassword() {8 y$ i+ u4 f) Y ^7 Z2 `+ V1 D2 i
return password; 4 |. P* d3 {, o9 ^; |& s6 s% h3 z9 U2 h } {, z" r0 Z; e! M
& ?2 \' b4 A* r( N5 `3 h1 e public void setPassword(String password) { ) M) Q1 h' F3 k/ E- ^8 S0 k3 z this.password = password;: x h+ K+ f+ K! @8 Y
} i+ j' \( l! ]0 L' }5 M7 C2 _1 x 6 s6 i7 n9 x( v' W) q: b* s! a5 x @Override- \) D/ D$ v( N$ h S
public String toString() {9 S- J) P0 w4 Y3 g- j! ^% y
return "MailConfig{" + 8 h4 A, I7 K) L: E3 ] ` "host='" + host + '\'' + 5 n6 G/ z" H: H2 x; N ", username='" + username + '\'' +3 l- X5 }# ^7 o' c
", password='" + password + '\'' +( e2 g6 D; L$ A5 q3 M
'}'; + W5 K5 B5 r: V' |$ D) ^2 i. G$ r } ' e4 J, d2 ]8 r}% K3 x ~) s! i% \. Z" i. L
再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中4 G8 F& L4 C0 b& z& R/ C( b
/ ^0 g8 U- J; y; p; a+ j; |. [package com.javacode2018.lesson002.demo18.test2; [* P! @- E: c7 y F 9 U! s; K9 Q; x9 Q' Himport java.util.HashMap; $ v! \* Z0 U: v/ ?' B, k/ D0 Wimport java.util.Map; & G) A5 l2 Q4 R ]# x0 C% h 1 M# Q9 w4 X2 A3 B6 i+ fpublic class DbUtil { ; g& z0 D( v5 d' m1 _ /**! d+ ^ b; g& e( |
* 模拟从db中获取邮件配置信息 # r3 t4 }4 T% h9 T * ) R! K, s( v; f3 d0 k8 ? * @return / l% L& b5 i& G2 `5 _ */, a( M% Y3 u8 U) N% U7 T" p* A! O, A
public static Map<String, Object> getMailInfoFromDb() { 7 q; {. ?4 W: f2 P# j2 S Map<String, Object> result = new HashMap<>();5 m* O5 P% ~" d- E1 c2 A, Q
result.put("mail.host", "smtp.qq.com");" L& k* s1 z. n
result.put("mail.username", "路人"); 3 K: k3 g. b Z& {2 {" N result.put("mail.password", "123"); 7 ^( n& a+ O$ E' {+ a$ h return result; p4 l" }6 z1 Z7 u# i8 v6 F! {9 `$ l/ }3 t
}* g- \" ~/ q# y& F* R) S: A
} t3 v* l1 n6 h/ E0 s6 K' o: Y
来个spring配置类 * N% W8 `; R1 B3 K, r6 }: B1 n" v) z1 z0 M0 F) E! i/ o. O4 `. N* j
package com.javacode2018.lesson002.demo18.test2; 6 L- Q( W6 h5 G5 J ; V& r4 b$ v2 ^& \" S: D& Eimport org.springframework.context.annotation.ComponentScan; 5 L, Z0 Y4 p& Yimport org.springframework.context.annotation.Configuration;0 m, l% V% b% L7 o* T
: n- }( ]/ O4 I
@Configuration9 c3 |; u* m5 }# s2 b
@ComponentScan - r) p2 Z- |9 A( M( t6 jpublic class MainConfig2 {( G5 }+ W: h0 g4 ^8 Q
}* m9 x' m: t0 Q. H
下面是重点代码 , y9 t% D e( d3 M; z4 e + M1 r0 U( ?" d0 p. K. W6 S@Test% c) d4 S- S9 j5 t; G7 t9 g2 ^5 Y
public void test2() { ( r! U; s" p; X; U: p AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();# r2 K+ A0 y0 J
1 a9 c W! ]1 R /*下面这段是关键 start*/( c- @0 C' m* g; k* O0 e9 O7 ]
//模拟从db中获取配置信息# |7 C5 q/ O# p+ v2 E
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); # l6 I6 P$ q3 s: G- y //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)1 s; N, i2 D/ {3 t
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); . J, ^* j4 `2 q1 h7 o$ g8 I //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高 5 E) M6 j2 x1 v" ?: l) M context.getEnvironment().getPropertySources().addFirst(mailPropertySource);5 V/ \) p7 c$ ^6 ]% _
/*上面这段是关键 end*/ ! F# P, T$ [' {1 ~8 L: h, I) `: O" ^- n- ^; A
context.register(MainConfig2.class); # ]" e' S4 y- p: T context.refresh();2 \: M$ H& n' q5 h0 {! M1 d
MailConfig mailConfig = context.getBean(MailConfig.class);5 E" `9 |4 l8 g; k% `' n7 O
System.out.println(mailConfig);8 ~0 ]0 o% j3 z) d, e% n* c/ e
} 6 \& Z+ `& h7 L: l6 E3 j- P- w注释比较详细,就不详细解释了。1 Q, r8 `0 }7 o% g4 |, T5 |" S
/ b9 i. q) N+ V* \3 ~# d! E# D5 E Lpackage com.javacode2018.lesson002.demo18.test4; : \& o+ z0 p R/ v 5 `. a; V9 M2 Mimport org.springframework.beans.factory.annotation.Value;/ X5 Q. L' d' f, ?! G
import org.springframework.stereotype.Component; ! w& Q1 V& j$ F7 b9 v O- r7 e; b! h" p# |! y1 `7 \, `9 c0 b9 e
/**, u& Q7 l. x( [* o8 E4 }9 d
* 邮件配置信息' x3 p; q W2 N4 o
*/ $ E p+ s5 X5 d- e7 t. B@Component9 W6 Z' U) _) T% m9 k
@RefreshScope //@1 5 K$ M" v p/ F/ _4 gpublic class MailConfig {$ ^/ X! z( n/ h, a
2 V* H+ U/ i; Z @Value("${mail.username}") //@28 T9 ]9 Q: {& `8 U1 S
private String username; % Y/ a/ c0 ~. c& b) C1 k. V5 I9 }6 V7 [+ W
public String getUsername() {7 X2 g/ {: Z5 a5 g1 M
return username; 5 e; b* y" s4 P/ N2 C; s2 @9 f5 } }2 Z9 l- ^) l: v/ h# C: X* ?; p
4 W. L) y# Q0 T: `( ^6 {2 w. D- m8 Y Y
public void setUsername(String username) { 5 _4 Q, S: W3 |0 V9 y$ r. ~ X. A this.username = username;8 k$ i, N' {- ^6 V* S/ q; \. K$ {
}- x4 o! ?3 F" W# C& b1 Y1 I( C, f
* \+ C6 r# T2 ` @Override, p5 D w( e @: a+ n3 H5 B
public String toString() {/ z% q. `5 y w! U4 e; o: o8 b1 `
return "MailConfig{" + 1 g! d0 H) D" s* Y5 S/ F+ Y! ` "username='" + username + '\'' +' n' d* N; \3 u1 d8 \3 z/ I
'}';& U& K$ P: E$ ~+ ^
}$ H l0 T, _/ p
} * s R8 G! L( Y# N6 S4 J@1:使用了自定义的作用域@RefreshScope " @& {: @; B% g2 s6 X7 F9 z ' }) c x4 T2 A: L' Q' j@2:通过@Value注入mail.username对一个的值& a2 R# X# w5 O$ {" E' ^
3 ]" E, @" P2 X
重写了toString方法,一会测试时候可以看效果。. }" s; o0 C7 ]+ ?/ O" G' J
( w$ G( _% A2 t4 r$ \& g
再来个普通的bean,内部会注入MailConfig & G, ]4 W; f: { N( t$ Y, K7 }' [, z
package com.javacode2018.lesson002.demo18.test4; 2 j7 X1 i5 S& v; f! {% w ) U# |3 g" Q, simport org.springframework.beans.factory.annotation.Autowired; 0 G$ [7 a% b9 D% y4 b: X" cimport org.springframework.stereotype.Component;. b% Y! b9 W, q! h% f; c$ q; X
* C3 L7 I% M' r& L$ k
@Component 5 w$ m" T1 _) t5 C, E- rpublic class MailService {! Q2 [) @9 R! J# \0 Q( j& Y, _
@Autowired ' F0 x* n% B3 D private MailConfig mailConfig; 1 ?8 g K6 a+ l6 M, Q4 x4 ` " @0 O9 X2 U1 y6 s5 ] @Override . [/ v6 s4 Y' k8 l5 r6 \ public String toString() { 3 C9 P" A) l/ t5 [" h return "MailService{" + 4 Z3 V) o/ T" x "mailConfig=" + mailConfig +& m/ ^2 O) W" _
'}'; 5 J8 V/ O: F# Y' @/ ?; U' X }% f M- Z H* _7 S+ p6 h* q
} $ @' [+ x/ d8 j代码比较简单,重写了toString方法,一会测试时候可以看效果。 X ? P4 _+ z# z) K; ]" Q # ?' h; y& a1 y1 _: Z% f来个类,用来从db中获取邮件配置信息+ f$ }5 E5 Z( X, d$ L
. S% F# U1 T8 I" U+ l) V0 ~
package com.javacode2018.lesson002.demo18.test4;1 X! Y9 h; ?& y1 N; |& ~
: N8 \6 v& z7 L3 K; r$ I$ eimport java.util.HashMap;) t" Q/ H7 F- R5 Q- ^9 h2 n6 y! @
import java.util.Map;# V" N5 d* e+ k( b
import java.util.UUID; - ~4 ?/ b/ v, q& v1 b5 G) B9 X5 ]0 ~8 Z% o1 [/ B7 ~
public class DbUtil {# g( w6 `8 }0 E
/** 6 W4 X: ]" o" y4 _% ~ * 模拟从db中获取邮件配置信息 7 t! m' S4 `5 A% g * ) @6 d, f8 Y5 M5 ]* K4 ^# V, X8 J * @return 0 F! b, p7 ^' F2 V8 M" S */ * g1 I. c# u; Y8 M* S public static Map<String, Object> getMailInfoFromDb() {& Z* l5 e1 ^* Q; P' C# y
Map<String, Object> result = new HashMap<>();7 A. _: F n6 ]
result.put("mail.username", UUID.randomUUID().toString());4 z0 j: g# X" `, P, d
return result;6 o5 L+ D1 t7 M, r! U$ g( o- J
} 5 {+ M0 M0 R0 P% k/ ~} 4 O5 L6 k' d7 N' F& u* ?( V6 [来个spring配置类,扫描加载上面的组件 8 U/ `" D' U3 q2 A E4 @$ S% N/ C2 c, g& B
package com.javacode2018.lesson002.demo18.test4; , a" @" _8 X2 p: ?' o " a6 _% n" K W$ V) ]: C" T, ~6 `. Himport org.springframework.context.annotation.ComponentScan;% F- ?1 K+ r" L' i
import org.springframework.context.annotation.Configuration; ; P$ G- R, p- R0 j4 B, \# N* A* x2 b: J8 i* `0 j
@Configuration8 \6 r' a: U& s" g, F
@ComponentScan 3 o# e& L& h0 x. lpublic class MainConfig4 {6 F) w5 Q. ^7 K1 r) @0 ~
}) }- L6 B! K0 b$ d- V! J( S7 @) Z
来个工具类 6 |$ T% c9 n: s! ?. f / |2 y. E& K5 h4 j2 s2 a内部有2个方法,如下: % d7 s+ D8 v: Q+ Q. p5 }2 Q2 a! ]# H0 A: ?% L& Z
package com.javacode2018.lesson002.demo18.test4; l* P6 `. g7 b/ f- l5 W" c' Z: N2 O
import org.springframework.context.support.AbstractApplicationContext; 5 ?0 q6 s3 S! l7 w# Iimport org.springframework.core.env.MapPropertySource; ; |5 m) {5 H+ q ]% T) h) s 5 m% T. i7 E5 z% t6 m6 D- \* Fimport java.util.Map; , ]0 M: j5 {, i( `! N1 X6 K" T % B; Z; N J/ q4 L: Ypublic class RefreshConfigUtil {3 l I) D$ l# J% D
/**1 g0 _+ L3 P+ V7 @
* 模拟改变数据库中都配置信息! W: u2 W' L! J" O, g3 j5 i9 L6 U
*/ 5 p( S. f O# N1 ?$ p7 W public static void updateDbConfig(AbstractApplicationContext context) { % d5 X* _) ]4 H' {/ D a //更新context中的mailPropertySource配置信息( s/ S7 O+ E( e0 s" N8 n- h6 T
refreshMailPropertySource(context); 8 y1 q0 T# `6 e t, ]% o7 D, t5 r3 C //清空BeanRefreshScope中所有bean的缓存" G) }, w" d1 f1 ^; A4 k
BeanRefreshScope.getInstance().clean(); 3 a8 V( J- D- l8 @" l }0 a+ a9 T' E0 Z3 Y, S: E$ i. C
- X" u% y" E5 _3 G0 S3 ? public static void refreshMailPropertySource(AbstractApplicationContext context) { ) i$ l2 g1 Q0 C* G7 j5 n Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); ( t, C. U: m" X4 ~ //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类); C7 t& Z) M2 A
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* T; T! a) o0 N# p( x3 {! O, C
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);4 [$ r/ e$ n6 n+ }
} * ?- Q0 \' \/ j9 Y y7 t5 g: i' \6 r/ }/ \
} ; k% u( M5 v lupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息; M7 ~, Q0 D& ^* D* _* c
* o, ~- U1 _/ s0 A& D) `
BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。 : @8 ?" I* }9 M# G' _& @0 l $ [- C- [, @! N6 O) Z% F2 l来个测试用例 9 u5 {7 G; ?2 q* s! G7 }2 f# h# o1 N( L8 C1 e" _/ X
@Test * G0 l; [# N& x* X. dpublic void test4() throws InterruptedException {- {, y6 V! J9 { F+ ~
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); $ L a: U- g$ J" Q( u context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());2 {4 a. M5 }, y7 w9 o
context.register(MainConfig4.class);9 f2 Q( x3 u- A- P* n, w: D
//刷新mail的配置到Environment1 c+ J! S* t3 `; M \2 Z4 ^
RefreshConfigUtil.refreshMailPropertySource(context); ' ~% Y4 \) s9 W2 }* } context.refresh(); - x4 G; R1 T- Y* V" O& X8 K" _# O" X5 q; v" u( k6 u- o4 b" W3 a& P
MailService mailService = context.getBean(MailService.class);2 I, E& Z8 u1 B6 l
System.out.println("配置未更新的情况下,输出3次");+ I4 G: M$ A& p0 z* G
for (int i = 0; i < 3; i++) { //@1 " k, ~& L: I3 i, K. d) n System.out.println(mailService); 2 M9 d* f! C2 E/ ~6 n TimeUnit.MILLISECONDS.sleep(200); $ U6 v- o; B0 n! N& z% y } ' r8 ?! b+ P9 a, L1 x m& r8 g2 V / x0 P, O0 L! M7 x& ~. n7 n System.out.println("模拟3次更新配置效果"); 8 I3 |% k3 g: s `: V1 {+ F for (int i = 0; i < 3; i++) { //@21 |5 Y9 y$ Q+ v+ d; P
RefreshConfigUtil.updateDbConfig(context); //@3# ?2 S+ p9 A' h/ d1 i
System.out.println(mailService);% P/ q8 E v3 P( v- |
TimeUnit.MILLISECONDS.sleep(200); % }$ E, ~0 _! K1 e } ) d! b' }1 Y4 T% J* _9 U} 0 F3 q, U+ {$ e: M! h@1:循环3次,输出mailService的信息 + u- m+ P' D/ F" H& ` D& Y4 W- s: h2 Q5 a' I
@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息 + z% F+ p2 `/ F 6 \) `+ w: a5 j" d6 T3 c见证奇迹的时刻,来看效果: s* R" Z' Z4 _) z& S5 @& R
) Z; b6 x- T- Q! h
配置未更新的情况下,输出3次 ' _7 {/ H+ ~* [0 p7 g2 @MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} 4 ?6 v, \( ?9 V: M# F/ {: eMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}/ k% p W9 {* \
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}8 r" d. w' w) X8 ^' [1 F
模拟3次更新配置效果/ D* D' `. R2 O9 }1 A. ~
MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}8 k, b- z* L3 D9 A7 _; R8 F" t
MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}. j3 _* `' a \' |
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}} # B5 J1 P* S" ?* t0 u' R& \上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。) [5 y3 u4 s8 j. r4 B
2 B5 Z1 g) \% S# o5 i( {小结 * H, y6 m. D q0 D2 A& w. q# h* {6 w1 E3 u, R- G
动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。6 g. L# C# l$ i9 b
6 E3 p: m3 B/ i! y# J N' h有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。 ) G% n4 n9 U1 q, D- \ 7 Y, b5 E4 m) }9 N5 S E4 h总结# s" \( q% l4 h6 N
7 f6 j& ^' H2 @ y本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流! 0 l0 m) o/ S" T5 k9 [8 M; K+ X* p q* M( ?, I% G6 k$ f
案例源码1 Y9 r4 T2 r5 C( P! k0 X- L
( u+ a. m4 R$ a/ I$ H5 shttps://gitee.com/javacode2018/spring-series # J3 z7 J( n/ v3 q' a" _% k路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。 8 Y# o2 U: V1 s; R7 c' f' ~ & p, Y: b0 V# x* ?. d. y+ `Spring系列 : t' d7 O: S- d' W0 ^( B / u2 `" v7 q: \Spring系列第1篇:为何要学spring? 4 E d, a2 ~, \2 I- x1 D( ]: p , G* V$ v3 ^& ?: cSpring系列第2篇:控制反转(IoC)与依赖注入(DI) * w" F0 A" ^$ X" a4 ?% u. C8 I7 H5 V0 o4 N9 z7 n
Spring系列第3篇:Spring容器基本使用及原理4 h9 i0 ], I8 a1 O