5 E' u2 F% [+ t* A' C% T% c. |/ G面试官:没关系,你可以回去了再研究一下;你期望工资多少? " p% T1 |2 |4 n7 i" ~$ J$ ^, e9 s0 A7 z; B' M2 J- H" s3 u5 ^+ z6 Q
我:3万吧+ O2 _- U- m% H. B' M
& V' T; [/ K1 u9 x# a) F
面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何? ) \% j: o' ^/ V! N 1 }2 s7 L1 _/ u5 f* {我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万 0 L/ C/ x1 R3 J; ^2 {' ]. X! Q' _% H* U- c6 V, k0 X/ I' R$ _( l- q) d
面试官:那谢谢你,今天面试就到这里,出门右拐,不送! e( J: h! ~$ ? g, Y# A+ d+ \
% \6 y& J! c1 }' r1 G9 e/ n
我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。 ; ^' s! n3 t' F, Z$ Q 0 \ O4 G$ \+ ^4 V X& ^这次面试问题如下, y" e: @; B9 Q" [- g+ q1 z
$ v4 o( |- O1 I@Value的用法! N3 ?/ x9 e, Z/ d: c
& A2 M3 `+ h) T6 h
@Value数据来源 5 d% U2 [, B! U* q/ V0 D% k/ J + f- q3 {2 w- F) l@Value动态刷新的问题6 K' l2 I2 S3 Z/ e
5 f8 X2 z8 J) U$ J: S O' x
下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。 & w% q9 L. x: s% }: Z $ m% l* P. j. e8 a4 C@Value的用法+ h/ w8 H4 f, h$ o% D2 M& N' ~! z
( O0 b( |( G2 _, e3 J: A# N) E1 u
系统中需要连接db,连接db有很多配置信息。 2 Q, Y/ r" [! e, A8 _ . n& u v( P) w# O系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。 - W% H2 s: ~$ b, b* |+ W2 K6 H& h- P
还有其他的一些配置信息。 2 H s$ C) ?3 [8 p, a, N* w9 x0 w. L* `0 o6 Q5 G) {
我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。0 ^7 c2 l" }( \# S, |* y/ A
- ~/ i' s- \4 R/ Y$ M' m
那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。3 q$ d8 C, S* ^% o u9 ^
, u5 J1 Z, F3 Y6 O) }* s) K
通常我们会将配置信息以key=value的形式存储在properties配置文件中。0 _# f- g1 o5 J* w% k
4 C/ A; ?3 g. A& ]7 h通过@Value("${配置文件中的key}")来引用指定的key对应的value。 d. s3 Y2 q% @8 G* ]- c7 k
! C% q) Q% _/ L o- n& l' P. Q$ l, u
@Value使用步骤; b+ {$ t) J2 b' s& O- `
" i, } ]7 D6 f9 ?步骤一:使用@PropertySource注解引入配置文件 ) X( M A% f7 W$ k- n- J% H2 ?4 z0 x8 _2 b
将@PropertySource放在类上面,如下 # x- J, i5 Y) a! d, E 8 u$ k( b' ]! |2 q" g@PropertySource({"配置文件路径1","配置文件路径2"...}) 6 I& N+ c. u- z4 b+ [8 Q/ r' p@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。) M2 b* O% d" ?+ E4 M
9 l t' {0 P6 S! ]8 |3 ]0 e$ N
如: ( _& `9 b4 z+ W4 A4 b3 r6 h3 j8 K4 o/ l X2 h% P6 B
@Component , C* A; Z2 e+ X+ z9 p+ e@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}) 8 i4 m/ @2 b- d0 Spublic class DbConfig {8 }# I" u! {; Y `6 R& m/ y
} ! p1 d" d9 e% O# f8 I: k0 p步骤二:使用@Value注解引用配置文件的值8 Z" X1 m; p" C U( m6 m
2 n7 p" a( e) m9 ?' f0 L6 W6 F n) vjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8 ( u* a9 {! G2 R4 ?jdbc.username=javacode 7 N; x+ ^3 K# G& i4 {& fjdbc.password=javacode ' ]' R: W7 [! m使用方式如下: . v3 K% N: X8 } , p0 H6 ~( y d w@Value("${jdbc.url}") 5 t: j2 R4 C a" |/ O) t/ Iprivate String url; " J( y6 K$ D" l2 q3 H 3 H, `% m" h0 b7 `% H@Value("${jdbc.username}")# p" v7 Y5 P! A4 Y2 N) i P0 q
private String username; " }# G5 u9 L( ?. Y- f6 e/ U( v . a- s' h# k: j5 ]2 O6 j1 n@Value("${jdbc.password}") + \# h3 Z/ v+ ~, @: k- C. T! {private String password;3 f0 a) S7 @5 ~( c& g8 q
下面来看案例 7 k4 j6 x( J; }- H* t. z% Q( M2 D5 f3 E
案例 % `& D& P" [& o: s. T0 P8 h* B) r0 n ) b8 L) P+ K: N/ t& s( ^5 g/ e5 R来个配置文件db.properties * s. g5 p% k d3 B3 s/ m3 ^ 1 x* I4 W( }# g; g+ Ejdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8' O3 f6 u! y9 m! [) P5 G
jdbc.username=javacode$ `; p" D; }" G, N5 K9 I- f
jdbc.password=javacode/ |& C& I+ C7 J; r; m: L
来个配置类,使用@PropertySource引入上面的配置文件 $ J8 p" n# E4 c6 s6 q- n4 y' k- C5 R' d8 S+ \1 O$ R
package com.javacode2018.lesson002.demo18.test1;. q% g+ Z" v7 _. Z+ t
$ @" S0 a# ~( I9 m7 p' C2 ~
import org.springframework.beans.factory.annotation.Configurable;) @. X( a5 A$ D
import org.springframework.context.annotation.ComponentScan; + I" X8 _3 l, q9 v5 _import org.springframework.context.annotation.PropertySource; . ^% s4 K6 w$ L% w! ]% M, d5 r. a( S# {- _& `
@Configurable , |, z- P1 Y* p4 o@ComponentScan5 x5 u t0 F/ C* T! \0 b
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}) 7 K; [* v" h, M5 E- d2 z2 ]public class MainConfig1 { % f e$ h. B0 G% }4 l} 1 M- s1 y/ y R8 Q/ R$ s/ E2 {, v P) ?来个类,使用@Value来使用配置文件中的信息! o8 h' T; H6 p5 m
, d5 r0 a4 ?9 S$ R9 G
package com.javacode2018.lesson002.demo18.test1;5 w7 l% i, B) A3 H3 v5 }
( U. b* ^3 i, \import org.springframework.beans.factory.annotation.Value;- k; d7 {8 G) j, K
import org.springframework.stereotype.Component;; L7 U$ L* N% z1 y R+ L" Y
+ B2 N5 i, ^% G, x4 _+ w@Component7 y+ y+ z9 _. \: T3 Z# G
public class DbConfig { 0 O9 \$ [& I3 f/ d1 n' x O 0 ]' p3 t$ F' j2 h @Value("${jdbc.url}")3 y$ B& D( L4 D4 q6 _/ L
private String url;! t8 n9 n$ ^& B7 s
) K0 E6 q5 }- s
@Value("${jdbc.username}")7 l3 V0 l$ Z& d8 q! R+ `
private String username; 1 G. s, y+ x, S+ B& i9 ?7 o: k3 c. D7 X! n: _8 ~# r
@Value("${jdbc.password}")5 F U* E/ j# o6 M/ e% |( T4 {
private String password;7 g6 A4 w! |- w5 d
$ I( k0 h% \: f! t8 g2 f+ E2 t public String getUrl() {+ ^8 H: m. j9 P/ j' r& v+ n
return url;7 T) H! Z' v3 z) z- O
}9 P4 X7 @% P% v+ v1 ], a6 j [
$ z7 q! l+ u8 U- O
public void setUrl(String url) {/ [9 V8 T' _3 ]2 a' u& w5 G, g
this.url = url; 9 W+ i6 K4 Z# {2 u } & X+ t7 K5 @; [- i! K8 Y$ s o9 P! O7 r9 J5 R/ ~
public String getUsername() {7 y' C9 k2 ~1 M) a/ t5 q. Z( J
return username; 6 A" b- u. i, z# E* U) S }8 F7 m$ l1 `; x$ k: G g
( v& n6 ? @% ^, [% {9 W
public void setUsername(String username) { . [0 J2 ]+ U0 F0 x this.username = username; " O/ ?% [# Q% `% C1 `% l- r: i( j }) o6 m9 S% g( E( T$ ~
- w3 x( B9 m6 M/ {( @
public String getPassword() {* d+ ]2 E- ?3 m9 n2 W/ S" n
return password;7 v% l9 f+ b3 E' A7 |' K u. S
}8 u/ i/ X5 K2 R# k7 _+ p
; Q7 [% O, `, a+ t; `* [5 |
public void setPassword(String password) { " Q( m$ w; V+ P3 A this.password = password; - y5 l, l, x# ?8 \6 Q9 j! ~3 ?* H( W1 ] }3 Z, D1 @4 C4 f
4 z5 B& k, ]) ? @Override ( e* H; W/ f8 g. w* L public String toString() { / I6 D: E9 A0 c$ g( i% \& [ return "DbConfig{" +, Q2 x7 |7 @8 @$ n! k
"url='" + url + '\'' + 0 ^& _! i0 ?1 u ", username='" + username + '\'' + " J5 M$ h6 b' z9 y ", password='" + password + '\'' + 9 C/ Q! c; k) k2 u '}';7 {$ f" Y3 x# y0 f9 {4 U
}0 {$ b3 z1 \0 Y y, M$ L- o- U
}- f0 ]" |: n* e
上面重点在于注解@Value注解,注意@Value注解中的2 B8 U5 \, d! S m
/ w. B& d, ?$ Y- A
来个测试用例 . V$ u. z% H8 E+ h " J( G; D7 c4 ppackage com.javacode2018.lesson002.demo18; # \, P$ s. b: D0 e: G6 g+ Z ! r& R! f7 y9 cimport com.javacode2018.lesson002.demo18.test1.DbConfig; 9 w" e2 [9 K' g6 ^import com.javacode2018.lesson002.demo18.test1.MainConfig1; ) u8 U) a" a$ v* v& L- e; \import org.junit.Test;( K2 [% i/ g4 } R2 I% X1 k3 q
import org.springframework.context.annotation.AnnotationConfigApplicationContext;5 A7 V( i+ w' O" m* f
8 ~/ B; L v: g+ ^0 l
public class ValueTest {$ k7 i1 w! G& K5 e9 F4 H$ [0 C
8 p9 e/ i' C8 |2 C4 N @Test" q! W+ D9 `, O/ I" \5 F! i; ~! M' r3 V
public void test1() { i% `/ m) e! S
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); " U7 I4 N8 |. y/ R$ U3 Z context.register(MainConfig1.class);/ Q- \, T9 M2 U+ m7 H1 |7 Q. A; E
context.refresh(); , w+ A' f! \$ n# f" }* U& V" N; c9 J- q+ }6 k v: Q
DbConfig dbConfig = context.getBean(DbConfig.class); S: P6 n9 `) Q! z$ ~6 I/ b0 \
System.out.println(dbConfig); 3 H6 a! w( F' O8 a) E }; S" n V/ E: W: }* U" [3 h5 \
}1 r# _2 {/ V( M8 f* [; S/ _
运行输出5 t+ t: }! W( M$ r
* p7 K' w) }0 S' Z, w1 r: E( u8 f, r% @% W
DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}( v" `6 `0 o; J7 }% W, K3 _3 J& P0 k; ]
上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。, E8 f7 {0 W F8 C1 w R
6 ]/ l0 P4 c: {@Value数据来源 ( m( c$ K& T6 f% `. j5 T) @) s, R3 Z& Y6 ^9 f
通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。' B. C, V2 m8 _/ G/ K) e
9 o+ W4 I4 M; u8 ? z/ s5 n% B
我们需要先了解一下@Value中数据来源于spring的什么地方。; q& v/ U; }5 B+ F- |1 f
# p7 l/ _6 M7 U. l) ], X$ v- ]
spring中有个类 ' c* ^, o% l. ?' u( q6 a z8 R3 X9 l: y L2 Z, t0 C
org.springframework.core.env.PropertySource1 ^0 X2 K5 P/ T8 k" J( i4 C
可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息 3 ?% j8 J# R$ U) F5 }) v' d . t8 I) g" s( i" g) W内部有个方法:3 T4 c. P! ]8 i; t
. } H4 Q0 U0 T& [; K
public abstract Object getProperty(String name); & K6 S$ u( E4 a通过name获取对应的配置信息。2 A" ~0 W# m7 }( ?( P% Y( C
w9 J) r# p6 A
系统有个比较重要的接口 4 N& {, N; ?! f5 o$ A8 h$ b! K- H% [
org.springframework.core.env.Environment4 _2 }+ i) z+ { Z
用来表示环境配置信息,这个接口有几个方法比较重要' d7 W" p0 V" [+ q' Q2 _; _
: U C4 J" y$ \) z
String resolvePlaceholders(String text);" W+ g/ h" Q. }2 c: X5 H; q; R
MutablePropertySources getPropertySources(); ! ~1 J* G' Q6 e3 G+ X1 t- W* YresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。 $ E( j+ D; N% S w4 I# C x8 E! K3 p# N, ?. U4 O; F
getPropertySources返回MutablePropertySources对象,来看一下这个类3 n- h9 b- C: O/ j8 m% P/ _
; k5 d6 _- F. dpublic class MutablePropertySources implements PropertySources {! R# p2 v- Y4 v( o& p6 Z* r4 ~
; ?0 A8 ?4 @4 n4 X. |- J1 }
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();% y0 S9 y0 x& b! C- h# N8 {( I" c; A
% C3 \' S y. v, h, Q9 C" A}: n: r9 J. Y7 L* Z
内部包含一个propertySourceList列表。 * f! T! f1 B7 B, r/ J3 N* E/ q* Y' d- Q- I3 p+ S+ Y& W% J
spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。 3 m5 G0 |" i+ |& z3 O+ B7 Y9 z : F5 Z8 G- \8 Y1 G* Z* y- o( n大家可以捋一下,最终解析@Value的过程: * f! M' F& g, { O2 c3 s' U% m' |# s# I$ y: ~
1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析* w4 H- a' g( E* P2 T2 A6 q' G7 A
2. Environment内部会访问MutablePropertySources来解析 ! U+ l( F* v6 i% ^3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值 . F: r0 Y8 `' z: ~% ^) F, u( [通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。 ' _8 q M* N/ j, W) P# ~( S% g; B* f5 w; Y- U( k
下面我们就按照这个思路来一个。 4 d# @7 |+ O+ F, I6 t6 |! |9 d, E7 p" m- r& d1 T
来个邮件配置信息类,内部使用@Value注入邮件配置信息8 w- P+ D" i1 p7 T, ?' m
% L: j! ^$ ], \# Y9 x0 f5 K# l
package com.javacode2018.lesson002.demo18.test2;! I, ^7 ^1 |& N6 o1 O
& _+ S! B: J' _9 x/ G$ G
import org.springframework.beans.factory.annotation.Value; ; f& w& y! S, K0 B2 ]- ^2 d5 ?import org.springframework.stereotype.Component;( m# G4 ]: d, E9 g2 }
C6 a$ ~8 J' V+ r7 Z) J* L
/**+ b7 V4 o4 U& ]
* 邮件配置信息$ _: W! P9 ]: k# b7 [* G+ n
*/ ; L' f8 H/ p4 G: L# m% t: A@Component , U) l) {' A( W. r: x+ opublic class MailConfig { 7 _" R! C. v8 o/ V" y) V0 g" x: ?$ b
@Value("${mail.host}")6 X6 U+ J: F2 c. W) a6 @1 o
private String host; ; _7 G: f* w& [0 E4 `6 H( R9 ` G1 l- ~; q- a
@Value("${mail.username}")6 g8 i# E8 {+ c( Z. X x4 v
private String username; + ~' t/ D4 q7 O$ E n1 v1 y6 Y2 b7 _0 b+ |7 i- F' I
@Value("${mail.password}")+ o1 h7 a4 ?, j2 m1 R9 h
private String password;% T* B" Q$ }9 c |1 u
- ~; N) M# P/ s/ {, { public String getHost() {; G; m) c& c9 E8 b# h' y. b
return host;* [( u3 B4 f7 W2 E* e
}8 ~' U- j4 Z3 S0 B$ N9 f/ c/ v: W7 h
' n, _# L6 R( A! P% f+ ^ public void setHost(String host) { 2 d, c5 h( D: \ this.host = host;: ~7 w w6 X5 I; w$ d
}: z- q/ u* B& D; k
+ `; C `6 f: \: C7 @* V public String getUsername() { / D* @! G) P5 w return username; 1 b, M8 g/ `3 {% t* l4 _# N } 0 g) y$ Z8 G" P% A6 q/ @4 f& R0 ]% @: m% ?4 b7 [! ]
public void setUsername(String username) {3 j. H) b7 ]) U" D) v
this.username = username; " o# C; c! f& g8 q$ L6 r7 q8 X } & Q5 q, I. Z5 h4 R3 b* X0 @ . ~4 E# J; B) P% U. \ public String getPassword() { ' a9 R! V# k! E: g6 U return password; 0 s5 a4 F6 s5 ^- G$ Q }! R% f/ j$ v: z y' a
2 J: [) Q9 W: N public void setPassword(String password) {7 a! E0 I1 X0 X4 I; R+ a) y
this.password = password; 5 z9 ^+ N( m$ p5 E } " V, {( v4 N' r1 j2 z $ ~8 V4 C8 T8 ]3 ^. i* o% P @Override5 t* l' `( h: L! g2 r2 T' h1 i C% J
public String toString() { & s3 k" e* x# N& v. d( ]! g return "MailConfig{" + & {9 k- A) b/ \ "host='" + host + '\'' +" P+ x5 [6 ~; m0 q+ g1 Y" I
", username='" + username + '\'' +% V' f4 t7 M4 r
", password='" + password + '\'' + * ^0 @7 b1 {+ {' {. Q& u0 q '}'; 6 Q: p/ i6 k, _' Y }$ `( x } 2 F8 G& q$ f3 Z0 O) [5 P1 ?& K: ]$ S* x} ' o. W- B/ l d c, C再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中9 [4 T# ~; j0 v
$ x! _0 Y% _# L& Ypackage com.javacode2018.lesson002.demo18.test2; & u! _* [& t8 y9 ?5 z: t$ e 3 [4 m u. i- h5 vimport java.util.HashMap;# q! w6 [$ m( b5 ?
import java.util.Map;8 a" M" Q* p) E8 G$ m5 q2 p* D, [
8 @0 V. R% f, @+ U- d) [
public class DbUtil { ) y1 U4 }. o7 W/ u, D1 U /** & D& G' x" L; k, ? * 模拟从db中获取邮件配置信息& F( D$ B! v* `5 i3 g
*6 }5 e' l/ n/ G( v- V- p+ `
* @return1 ^9 E5 m3 u" r( R( @1 W. E# A9 ?
*/+ v& b( r4 W$ R
public static Map<String, Object> getMailInfoFromDb() {: p. G# ]4 V9 G. J8 A
Map<String, Object> result = new HashMap<>(); 3 Z5 ]0 b7 U# z; z4 q# U result.put("mail.host", "smtp.qq.com"); 2 t; @( r+ K& R T7 d. o result.put("mail.username", "路人");$ w, U8 o: P# ?6 p8 u4 a; F
result.put("mail.password", "123"); 0 W3 D* M3 V; s0 f/ R0 E return result; ; e9 g, v% v9 a } 2 G0 g% r: }8 M2 t9 h+ ]0 N+ F9 {}0 S) L; e- n1 Y( P" M4 c5 i f. ?
来个spring配置类 " M" ^) C2 ]1 S0 S! i+ |) {! f% w$ w) `" c! [0 {' M9 Q
package com.javacode2018.lesson002.demo18.test2; 1 @! X- N; h3 v: F% q9 U+ k! |" D' U( b J* _; N( ^
import org.springframework.context.annotation.ComponentScan; 6 r0 {/ D; F' U. qimport org.springframework.context.annotation.Configuration;$ g- \3 _& I; J4 R, N( H
; X5 l- j" X9 Z/ ~. e n y) u@Configuration % O1 f& O5 D D, [( t% H9 E0 w0 C5 S@ComponentScan : M3 [5 R& m" L5 Vpublic class MainConfig2 {# w% _: y& p& s2 g2 b# o& ?+ c
}5 B8 b8 @. a$ n5 ^! }' T
下面是重点代码4 ^) N3 t, V, ?- f0 `, }, J/ C7 ^' F5 k
& N n" G, Q# a( r
@Test8 m9 j7 d0 c# _6 y
public void test2() {% G0 |& g" i" L( _
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();; t' G$ U! A9 }, ?, w
0 e+ b; N# F3 O. c /*下面这段是关键 start*/ 7 C! \1 h- I4 ^9 V //模拟从db中获取配置信息 ! T. B# D6 G4 E Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); + C. G9 v5 ]3 S6 x1 E //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类) / x8 g. R1 w& X9 f' U ^6 s: { MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); & ] D4 j1 b) d% Z0 Z( M //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高 u+ f- t9 Z. E# ?6 d
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);7 J9 }" P, r8 Q
/*上面这段是关键 end*/ / u# P$ E, |7 b9 Z 5 C! b. X# O' o- n% Y( P context.register(MainConfig2.class);; ~; G+ n @7 x9 u, P( D+ l/ i
context.refresh(); . p% h! ^$ Y# u F3 K+ L/ x" q MailConfig mailConfig = context.getBean(MailConfig.class); 2 e4 D7 P: ?0 h) H7 @5 I System.out.println(mailConfig);7 `" B4 Q, I( t& A2 Q; Y% O7 W) |
}7 Y$ b* |4 I( r# B! p
注释比较详细,就不详细解释了。0 y9 J5 r. u. y D4 s9 g
3 g( n1 ?3 m; ?7 L直接运行,看效果5 [$ W( m1 } j" [8 S- j8 V! g" W
4 D, f9 B1 s% M5 Q0 m5 p$ j$ d9 dimport org.springframework.beans.factory.annotation.Autowired;: v- {) q* H n) V
import org.springframework.stereotype.Component; - x1 N3 I: V' {# k- C- r% ]" i Y! o2 A3 V
@Component K0 @! e. t0 [3 k
public class MailService { , z: Z" \; g0 ~2 y, L- n1 G @Autowired$ i5 W1 ~0 o; W' P; J* ^; t \ \
private MailConfig mailConfig;' _ c1 x% Z! E- U/ A, ]$ B! ~
* d1 `; ?+ }/ U T) V0 x8 p @Override1 e* s/ z. o7 `# i# ~
public String toString() { . ]! A# V1 F# w8 I6 U return "MailService{" + 0 R) m% A0 f0 z$ ?) W% {& k "mailConfig=" + mailConfig +, \8 u: E8 V+ m- G/ z
'}'; 5 s% B" Q5 c5 a, o' s } 0 Z K" @; O) m4 a* `} : d9 C E' U/ b* ~7 ]代码比较简单,重写了toString方法,一会测试时候可以看效果。 ) b& R: {( ^; W7 O& a( X5 v+ g8 S* |0 x V
来个类,用来从db中获取邮件配置信息4 Z* ]% n8 c0 H2 ]' X& K V+ S
1 {/ b% `5 ~ {package com.javacode2018.lesson002.demo18.test4; ' g. ~% |" l1 k) C! n7 T! P; t) \5 {1 \: n
import java.util.HashMap;5 D, _$ z8 S& e, d# {
import java.util.Map; / {: ~' ^ N0 p# Uimport java.util.UUID; 7 B6 X" U9 Y4 d4 T. _( r- r- W 4 y4 p9 Q# b4 A& {# Q* zpublic class DbUtil {5 p& M9 w' E) @6 x
/** 7 O" V" b3 Y3 [2 x2 x( I: T * 模拟从db中获取邮件配置信息 9 P H0 f8 c0 t, F/ w2 b *, d( G: }: i& K
* @return . e# z3 ?, _/ u */ $ q) p ~) b$ D* J0 o6 w( h! l public static Map<String, Object> getMailInfoFromDb() { % i) P8 p/ w9 Z& q: q7 |4 w% I Map<String, Object> result = new HashMap<>(); 9 t8 w: `6 R7 @& D result.put("mail.username", UUID.randomUUID().toString()); {9 z- h' s+ C: y9 w
return result;- P" A' Y: \$ T$ L' `( r
}7 M; B: Z: O4 P3 f3 M( |
}7 s( H2 }/ M& R% O+ M }! i
来个spring配置类,扫描加载上面的组件 " j% W- U/ g" S$ f- m/ j A9 Z" R- L# k# ~4 V4 D* w0 Lpackage com.javacode2018.lesson002.demo18.test4;9 M7 t. _$ D/ k+ W) p- Q1 X
$ I8 F& r8 q* \$ s& s4 w+ Q. E
import org.springframework.context.annotation.ComponentScan; + Z6 {& z4 U, ]3 `$ {+ v7 t7 @- Uimport org.springframework.context.annotation.Configuration;+ h6 }6 R( ^# e9 i3 u t6 W
, j1 l4 o3 l, @@Configuration e% z/ Z/ V1 M' m& a. E
@ComponentScan! S* b( f4 ] z5 A
public class MainConfig4 { , V5 E9 U& ?# O' c/ A T$ u% q! S}- m7 E; H+ y) J* t4 i% f
来个工具类 ) y$ b/ k& f6 B0 G: P: Z1 K1 l& X0 u% h" e8 k* A' ~
内部有2个方法,如下: 9 a ?- t$ }& f, n/ N! k r. j. w6 F8 j
package com.javacode2018.lesson002.demo18.test4; $ n. T/ S1 Q8 Z" N, m$ o% x, V! j 6 Z5 P0 Z) Q9 e' `import org.springframework.context.support.AbstractApplicationContext; + i: z- K+ @7 V, e& Eimport org.springframework.core.env.MapPropertySource;$ }% x$ [- {4 C, V- S/ E( R
. h1 F8 m- z: x+ P; U
import java.util.Map;# X7 l: {3 u% Z. _: V
/ O$ m. g, a9 G6 u1 K& S+ J; S7 Fpublic class RefreshConfigUtil { , e% O2 B# s; E" ~( c /** / j4 |9 g1 Y0 f& i * 模拟改变数据库中都配置信息+ s: E* N0 d) r8 y) e* E& n' g
*/ 5 K# [' M- f) b7 u& R public static void updateDbConfig(AbstractApplicationContext context) {5 B8 j( H# G$ m! f
//更新context中的mailPropertySource配置信息 ( |# P; N: y2 u: C refreshMailPropertySource(context);8 z9 d0 W5 T8 ? w# W. E
# W* E F( d! R+ i6 g; d( ^- L" l
//清空BeanRefreshScope中所有bean的缓存 . z+ v0 L0 X) T6 r9 \ BeanRefreshScope.getInstance().clean();. y+ N& S; }7 ~" \8 `" g1 H/ p) a
} ) |* N l6 i, d/ N. ]% b3 m/ I9 ]' p- t G
public static void refreshMailPropertySource(AbstractApplicationContext context) { + D% u) j8 |3 W# t) A4 w( L8 B* H Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();) i6 m W) M, o3 L+ Y/ S( Q
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 m" d! J( z7 o( {
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);9 f2 p' k+ F3 @) y: _
context.getEnvironment().getPropertySources().addFirst(mailPropertySource); # c0 X1 ?# U) f* R6 ~- J1 H8 o } * G5 _' a z/ }4 S2 x ( U. m9 O% v# F) X; D( J% i$ l} " {) {# Z! T; ]' oupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息 , @4 _6 `% r# Y. S 7 N/ ] |* }/ W$ JBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。 A/ _3 e( v6 z+ o) |
/ E6 Y5 h8 e0 S0 q! C/ b3 F" S k
来个测试用例6 Y6 }" Y* R K1 ~% s
6 H7 v/ N# Y" |+ }& B9 ^
@Test+ L \+ } b! w/ o" U
public void test4() throws InterruptedException { 8 w5 G8 J2 i, g' R AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();% P( V' e) u, d9 m& _
context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());1 [: ^- s9 Q/ t) U1 n) R1 \/ Q. ^' E! \
context.register(MainConfig4.class); & S' h2 G" |( S4 V0 q //刷新mail的配置到Environment ! c# h, [; F3 }# O+ _ RefreshConfigUtil.refreshMailPropertySource(context);: Z; l9 R! W' s8 U y1 q, N
context.refresh();; j1 T5 E8 l/ ^! h
7 F+ h5 A5 O$ K; Z! { MailService mailService = context.getBean(MailService.class); 5 Y7 C" u; i; [, g# W System.out.println("配置未更新的情况下,输出3次"); 2 f \9 Q" I& @ for (int i = 0; i < 3; i++) { //@1' ?% b% }) T4 [% x
System.out.println(mailService); 6 D8 k7 E8 m: l, Q( n TimeUnit.MILLISECONDS.sleep(200); + o+ L( u5 @6 ^) {! h } 0 I: ]* T& f7 ]' _4 i& Z8 @# J- x& n4 B4 B0 R1 Q3 p1 j
System.out.println("模拟3次更新配置效果"); A* N C: n& {* t( g) f+ [8 ^ for (int i = 0; i < 3; i++) { //@2 + d" H1 S" n1 r RefreshConfigUtil.updateDbConfig(context); //@3 + c. w- l: b6 M7 g3 f& y System.out.println(mailService);( B( W5 C( b" ?1 M1 [
TimeUnit.MILLISECONDS.sleep(200); + b" [; u7 G) r9 z9 \ }4 H+ A p/ t+ n, n! M
} ; l# U, A. ?" N2 e@1:循环3次,输出mailService的信息 $ c8 u5 S* Y+ d: Y% }8 S " C! S, E: z9 o4 z. T@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息- W7 _/ y6 w' `+ M
* H8 L/ u! K# t# V
见证奇迹的时刻,来看效果( ]% r) R" E4 w) e% T$ x# [1 F" K
% p. P( k9 S2 K% r8 n
配置未更新的情况下,输出3次 r( H, J2 x4 c4 d8 tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} 0 |5 x1 Y" m' {: J9 UMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}) `4 N$ Q- C8 l$ e; V# M5 r
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}0 X: ~$ _" T+ V/ H7 ^
模拟3次更新配置效果/ W4 \' F; U6 v0 d7 q7 b) q2 Q, W
MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}% }& O( m7 v0 N* A7 C
MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}} # I7 [& u4 z% ~: aMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}} : J, G$ e4 [/ m+ i3 _上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。 4 g0 j% F1 f+ N- a7 {, I8 w! J$ `, x4 E5 q( B4 }% V! ?
小结# \3 G; W" y5 x7 q
" U: v" v9 T p2 Q- b% \动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。% c- S$ {9 L ?% V. t1 l2 R* e2 h
. v* L0 r" I# g$ ?7 `* r: f有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。 3 D' W8 P; h" T Z0 G; E- g- T3 G. ]
总结 [7 `5 p+ K1 [6 C, y7 Z
. \% e/ Y1 m5 z( g6 M+ U2 Q
本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!8 K9 r. Q/ M, K+ o0 Y
4 P7 ~1 B: ~' W6 c! Y" i案例源码! i( L- P: ~9 d% Y4 c0 T% c
8 u" a; u& I4 h3 T& B5 i! z% f% k- |
https://gitee.com/javacode2018/spring-series7 ~: g5 v* e) }: J a
路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。 L. K- U) N4 a( i+ G% P) D! B, v; `, b8 c C; c
Spring系列 * d+ P, G( ~# G, n 5 | \, N, x1 C' [Spring系列第1篇:为何要学spring? ; t& v! P# X* E; O; n2 K1 e $ q3 h. O* ]) j) YSpring系列第2篇:控制反转(IoC)与依赖注入(DI) 0 y" R' Y- P- c0 j9 i9 e# T" V# `% c% P' ?5 ~5 K5 i1 P+ k Z, `
Spring系列第3篇:Spring容器基本使用及原理: t: C& J9 g8 N+ X# ~; |
. l( l) N0 ~: _$ G( O# u
Spring系列第4篇:xml中bean定义详解(-); h6 P1 F5 A/ p% e0 ^
& }) q5 m8 a6 D, r2 h; A3 a. R
Spring系列第5篇:创建bean实例这些方式你们都知道?( Y6 c; N: P) i2 ?0 D! J
3 G: Q. s3 }) z
Spring系列第6篇:玩转bean scope,避免跳坑里! ; m5 i3 s8 S% @( f* U6 p) F: A4 r1 ?" m2 N& p0 t
Spring系列第7篇:依赖注入之手动注入# q. ^1 L0 j Y3 X9 l+ o. ~: W W
6 A1 H& O/ Y+ M) j# W! {1 Z/ d; ?
Spring系列第8篇:自动注入(autowire)详解,高手在于坚持& A# \# {4 E% W# j
* x& V8 d0 G' B {% r5 `
Spring系列第9篇:depend-on到底是干什么的? : {2 o% o- p5 W6 n/ K 7 r' H& ?( O( @3 W- \/ N7 {' r- e8 OSpring系列第10篇:primary可以解决什么问题?/ A* p# X# {1 C" q1 B
* @' r. U0 W) P% a$ O
Spring系列第11篇:bean中的autowire-candidate又是干什么的?) d/ u& l n& x4 `
& e- j& p! E6 \ U y- q
Spring系列第12篇:lazy-init:bean延迟初始化 2 _$ ^6 \7 K# w5 q3 y& M! Z5 {% M% U+ k/ D7 b
Spring系列第13篇:使用继承简化bean配置(abstract & parent)% h A- m$ G' u+ A