" n& o8 P- n S$ {+ \面试官:那就是说@Value的数据来源于配置文件了?( V8 o9 p* }4 n' K
1 ]* ]+ s3 P6 @4 P' R
我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置4 q7 S. K& ^ z; N" p" ]
2 n3 v3 h1 V1 T% s面试官:@Value数据来源还有其他方式么? . V( B# _3 m9 H# S4 c' K5 t! J/ M* t
我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。 9 B5 @& @ ~+ x' q; N, O' g + ^) m+ M0 A* D. c- ^5 _面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?3 {# y- i) O' s
) o) Q* s& {, w Z. w我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧2 p6 U! E( j1 o' w9 x( ?! ]
4 O( a6 y& O L+ y# b" c
面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?# u+ {+ R6 Y. _3 L
) B: a0 y+ o/ t, I我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能1 F, h \2 I6 I! H
2 u3 u! x3 W; ]2 G2 Z% f5 s
面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下? $ a) d% H4 l; J* j. K & \8 N$ U3 I) k1 k我:嗯。。。这个之前看过一点,不过没有看懂 3 G2 A9 v9 o J& e. B3 O1 s" @ ' G, s; b9 ?: {* t) y面试官:没关系,你可以回去了再研究一下;你期望工资多少?7 Q! g. O3 k; n
* y* f' t% l+ o我:3万吧5 P( D0 m1 L; |, }1 \, k j& q0 g
! \2 l- @0 O8 i3 ?" Y+ u' L, S
面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?# [ { }/ g9 o: h0 ` p5 }3 C" f
; T! P& I3 g$ `9 ~- q. J
我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万 4 r% a7 H; f; \% K& C 2 @$ i, F' r$ _/ A" n) `面试官:那谢谢你,今天面试就到这里,出门右拐,不送!: C# w- Y! V. a3 W) H
" \2 Z( e9 U! c
我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。 $ b$ I9 N: Z2 x' f 3 \+ A8 [$ [' E, w5 e- v" _) |1 s2 @3 v这次面试问题如下 , S4 C" Y% c" b" V0 `$ M. x4 D" `% p5 j# U; ?
@Value的用法 ( x- U! |6 c9 S" y* J/ p- h3 C 7 ]3 O) W8 u4 d3 \0 L$ u9 R1 O@Value数据来源 " Y: K6 \5 c5 K* m5 o' e% m/ G' X; e' [. b3 [ a T# g. C5 u1 H6 X
@Value动态刷新的问题, i6 w* j# y$ ~. A2 t' {
5 b$ n) A/ F u9 X, K" g下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。$ d! t1 X# o0 h) f0 Y
( j8 m/ [" }6 t& L
@Value的用法1 U: m3 b6 K' ^5 J
* d/ O' f+ q+ O8 l2 ~9 g系统中需要连接db,连接db有很多配置信息。' T6 q5 u. f3 L4 e; m2 `
# E! W" V4 D1 I; e$ s4 W* Z$ Y
系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。$ z$ } P; Z% B/ R( w
$ U1 T I, N F6 A( O! c还有其他的一些配置信息。 8 y1 j: M& U0 v! F * x3 w, Y3 V1 J' |我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。& A. F/ i- t8 K0 _
. j7 v3 ?. E) \- p1 Q
那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。2 L% W7 o# f( Q. ?, l% s/ W
0 t+ @. b# ?$ a6 d" z5 _
通常我们会将配置信息以key=value的形式存储在properties配置文件中。 ' d2 R! [3 w; c7 ~4 A 3 G" v9 a# a0 h& r通过@Value("${配置文件中的key}")来引用指定的key对应的value。 ) P$ _9 p( E6 Q! z/ i2 i& V7 @5 ]/ u. q0 |8 e
@Value使用步骤 % [+ L9 i- w0 |& q; ?2 p. }) I) h- G9 i7 A( h
步骤一:使用@PropertySource注解引入配置文件 $ ~; N* V% C+ N. ~4 v 0 T4 E% m; O# D; N5 _+ K将@PropertySource放在类上面,如下 4 }5 O/ N% x, n% K; c7 i ) A+ q( q+ `) ~2 ]' {@PropertySource({"配置文件路径1","配置文件路径2"...}) ! h @* r+ E8 u0 z p# L@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。 ) f, n' N6 |0 M: N; o9 V8 {- j/ Q0 K; G
如: ! k8 Z1 W5 Z6 r6 x& n$ b3 C1 Q 0 p( c8 g. Y9 d- m@Component 2 \! q4 u8 \4 ~8 ?4 c; D \@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})) @1 A* o0 i7 C: N
public class DbConfig { / a- d+ G& }; ?! C} 0 L: o2 v' |$ W( j步骤二:使用@Value注解引用配置文件的值2 O( Y3 M2 |; C) V
! d& U9 T( W! ` s6 t5 C通过@Value引用上面配置文件中的值: ' Y; C9 F' y' A; H0 i( K1 |) e8 @3 V3 R & O3 @. l" x d0 F, n+ T* w语法* r3 \! S1 p/ c. M9 n+ F* W. Q
5 N& Q: m$ ~0 I1 C" t' Z@Test . Y) Z+ d4 {% npublic void test3() throws InterruptedException {4 [) J0 Y( N1 w7 H' o7 ~7 n
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); 2 X2 Y7 _ R0 X! p% ~6 N- n //将自定义作用域注册到spring容器中 ! O S! P% z: j1 L, F" t+ g; g8 d+ Z context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@19 c3 z/ ?9 o7 M1 N: Y1 e5 C
context.register(MainConfig3.class); t8 o* \5 x6 \9 S# q. H+ l
context.refresh();; O4 f, n1 ^$ x4 W8 f
0 }/ }6 {! |; [; J7 ^' v2 H Q) U
System.out.println("从容器中获取User对象"); ; P% }2 P* J* i8 p6 C$ o5 d# c User user = context.getBean(User.class); //@2 + e4 B T- N2 r1 D; s System.out.println("user对象的class为:" + user.getClass()); //@3 ! {2 L' R8 \1 \( z6 S2 @) m0 a; X ! |; g. S, O5 Z! A; m7 {* f6 }% ~: g System.out.println("多次调用user的getUsername感受一下效果\n"); / w# L7 P- C0 @3 G for (int i = 1; i <= 3; i++) { 7 w" q$ a1 c- W7 ?5 B System.out.println(String.format("********\n第%d次开始调用getUsername", i));, E% ?! O0 a- d- a
System.out.println(user.getUsername()); 3 u) I) _9 G$ A4 I, V System.out.println(String.format("第%d次调用getUsername结束\n********\n", i)); % Q1 h- L3 D9 F( P3 ~ }3 p: Z Y5 H7 ^. b$ B0 c
}2 |' L \+ U4 e# ]4 w! D. y& R4 J
@1:将自定义作用域注册到spring容器中- Z) U+ `0 C! n
' s. ?7 I8 k b) ` E
@2:从容器中获取User对应的bean 9 R d* ^1 r- ?# {7 F' I1 f% Y- a l# A' ?6 o. D/ u
@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的 5 w+ l' [, A0 ^! t: o 5 j4 t& j; w3 @, I: N代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。# K6 }: G' r% s2 w. `) Q
" L& Q H* q' f$ X. s' e见证奇迹的时候到了,运行输出" |0 k# `& L! ~. p$ N; f9 M
% g2 u/ q( _ s, C1 A5 F( D
从容器中获取User对象 ! }+ O' a9 J+ k0 F, z8 Y7 Vuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331279 l+ e2 ?* _5 F! c0 c. Y
多次调用user的getUsername感受一下效果: ? a/ w% s, N& d( R
% }( A) A g% S9 |
******** ) p+ a9 e0 G: ]- m4 p3 W) y6 m第1次开始调用getUsername [7 r4 N# @) K* a! |
BeanMyScope >>>>>>>>> get:scopedTarget.user; _& g7 D) S2 D; N5 e& d. X
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4 $ G x2 e2 S3 U P5 T U# b7b41aa80-7569-4072-9d40-ec9bfb92f4386 F* e) K2 `/ K& A
第1次调用getUsername结束 0 N, x% Z0 p P********! t4 K8 v1 j: `1 E0 L' l8 X2 l$ H
( V, C3 S7 \; o********3 y! t. K1 N K: Q) J3 e1 Z! J
第2次开始调用getUsername ( h! Z, S+ q# F. g! a8 O+ s, @BeanMyScope >>>>>>>>> get:scopedTarget.user4 l8 j% Y, S# [
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b) w y- Q) i9 T$ W% t& r
01d67154-95f6-44bb-93ab-05a34abdf51f# a Z2 l3 j& R0 `" H. ]$ A+ A
第2次调用getUsername结束 0 o' i& o- U3 R3 s/ m******** . ?. V- C; B; c# d4 K: A' f' M, u5 h* w
******** * A! L' R1 m" t/ B第3次开始调用getUsername - B' U/ B7 |) o6 F& s6 jBeanMyScope >>>>>>>>> get:scopedTarget.user- J% C. M3 m, t6 P H
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15 5 {) ^ h# v& ]$ r+ k8 Q y- Z76d0e86f-8331-4303-aac7-4acce0b258b8 8 @/ H+ [9 \5 Z/ o- d+ a! \第3次调用getUsername结束 / E& M7 s5 I" w9 g4 j0 p******** ; g8 _4 H4 s& W从输出的前2行可以看出:9 t' m/ P% x( E9 J% L! f$ R
; f+ E' S3 h4 _2 M2 T3 r
调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象 % q& G) \3 b8 }4 ?5 r, m/ \# x/ b6 H, l8 O) `7 ~9 ~
第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。 8 k4 {: x! R4 j" M% d3 l2 Q3 l2 ]( H: o( k
后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。 $ S o+ X- F( \+ M: [3 d3 H$ s* f$ c2 v. _* F5 B% v: I
通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。+ y0 x: {3 {) d/ _6 E! o( M! V
0 U" b# h, g6 Y2 o3 P4 B! u& r
动态刷新@Value具体实现; A- s1 A( f A6 w
' U, I! P. O% i) m那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。4 b4 W) v9 f2 ~9 a) W+ n7 W
& l) E& N* G( g3 q8 k: u% c! x# P先来自定义一个Scope:RefreshScope - Q, g* p+ w8 z- v- n/ i3 X) [/ e& i+ u( g8 [0 M% {
package com.javacode2018.lesson002.demo18.test4; 6 W2 F& N, N0 f$ O& d* o$ ^( [% ^# T7 z1 `
import org.springframework.context.annotation.Scope;$ C) @0 P. ~1 `+ W# c" c
import org.springframework.context.annotation.ScopedProxyMode;( O; J$ Z& h4 Z6 w% e4 V5 [8 ^( C
1 |6 d# W" a5 l* T1 [ g7 H. x1 D
import java.lang.annotation.*; ' C: t9 C& j6 F% U) t ' l9 X6 O% H$ ~9 L# A4 i+ _@Target({ElementType.TYPE, ElementType.METHOD}) # ? U! r5 H; q4 B* B@Retention(RetentionPolicy.RUNTIME)) {9 S( q- w8 e' m5 J) T
@Scope(BeanRefreshScope.SCOPE_REFRESH) 4 W( a! R, @! J* }: O$ N/ U% X$ p@Documented) `2 i5 C/ v" {5 ^- }* I. ^
public @interface RefreshScope {$ e7 g/ h# m' p9 x9 Q$ H3 D
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1+ E* C4 [9 ` h3 n' b
} 4 A' [# t2 l ^要求标注@RefreshScope注解的类支持动态刷新@Value的配置 2 ~ Y9 O! E$ }" x' o8 E 6 X4 l P4 R* r+ @2 C' ~@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS - Q) i4 k; }) C7 X0 w# ]& \ ( S( f% w v- Q f- i$ M+ v& B这个自定义Scope对应的解析类$ G# A) z( r0 \& s& ^) a3 o
, E# Y a) L% B' B* I `% L! h下面类中有几个无关的方法去掉了,可以忽略0 M* L+ Z$ b# K7 k; G
: o2 P g7 }! x3 K/ A8 ? lpackage com.javacode2018.lesson002.demo18.test4; ! K# v* ?) _& x5 L, ~/ v' l7 L( V: T+ V8 }% K
; h: T u3 a$ }6 v
import org.springframework.beans.factory.ObjectFactory; * @# Y( ^$ c7 w& c# Y1 ]/ U+ oimport org.springframework.beans.factory.config.Scope;3 O8 M. R B2 |9 M7 f6 ?: E+ S
import org.springframework.lang.Nullable; 5 t7 W1 Y: f6 s8 l9 g& Z3 F / g- v) s' v& K, f" Simport java.util.concurrent.ConcurrentHashMap;' }+ l6 [3 w8 Z: v* K
5 y( M- Q% H5 Q$ V7 k& w: k# _
public class BeanRefreshScope implements Scope { ( D2 ^4 Z8 d4 n7 m: c; K6 x: x' N0 b/ b3 h! X/ R0 N9 }% }
public static final String SCOPE_REFRESH = "refresh"; " u; Y; V8 v1 i+ ]$ V- h# [5 _ * e, i% Y% Q- X: a; S2 N3 p private static final BeanRefreshScope INSTANCE = new BeanRefreshScope(); , O `) r3 E( |- c0 N9 X( R6 o1 C4 B7 E. N: s# ?! I% V
//来个map用来缓存bean B- _! M6 }/ w; I" @% l private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1$ S8 y( n- k: a' o
9 e3 B4 o! h( q/ v private BeanRefreshScope() { 0 d1 ~4 Y9 Z+ h; N& X* s. R; f$ W! r } 8 b# t- P% ]7 A" H4 V 4 F/ P/ @3 b. v6 }8 D; X public static BeanRefreshScope getInstance() { . A$ m- F, H8 H1 L3 P8 U7 | return INSTANCE;$ t v8 k2 j! h) h% Q$ R# `
} 0 W: z- }& M$ O$ ]2 a3 o. V& w% S/ K% P% W
/**8 }% H; v$ L8 }0 W5 X8 C3 e
* 清理当前 # K, ?& C& [+ P */ + U% B ?7 Z+ }, f- u public static void clean() {4 [# }* v6 k) N2 r2 H
INSTANCE.beanMap.clear(); 5 n! G Y5 L0 d' o% L# i5 D5 M }5 Y( p+ l8 d9 a# }" X
' b0 h0 S' u# i: m& l0 ]
@Override. `/ h, q/ _2 |
public Object get(String name, ObjectFactory<?> objectFactory) {7 d T( B/ O2 p. z/ ]
Object bean = beanMap.get(name);6 f5 i$ [* X; z' n) B
if (bean == null) { g0 I" I+ j W J- ^: x bean = objectFactory.getObject();) n; g" d% |8 b8 W4 j5 q
beanMap.put(name, bean);3 x5 N- I0 N$ ~4 G6 J0 T$ ~9 C3 z
} ( U5 ^% B$ `4 q# h- t' Y return bean;! \4 R; ]! R' R# e7 o
}, A1 I6 d3 Y( N! j
* `2 C. l- K/ y* Q: l/ n/ C" w}; ? J: V9 G; v! y
上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中* w' Y x! Q) E1 [
) N+ d9 ~3 |3 U3 f5 C上面的clean方法用来清理beanMap中当前已缓存的所有bean% W3 y4 |$ p6 V8 ~0 x# B
/ j1 X7 j- v; z
来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope 5 B0 ~4 U1 O. c : f- j: V$ O7 _& o' G' j3 |package com.javacode2018.lesson002.demo18.test4;! }; Q4 Z2 E# t: z2 V! o
8 s: ?- d) ^) O, G$ ^' O8 iimport org.springframework.beans.factory.annotation.Value; 8 F% u% Y6 d+ W! b- P, simport org.springframework.stereotype.Component; " |! h7 m; { ]% L2 b( L, q5 R% L; J* b( p& W
/**2 a I: }& ?$ R9 P
* 邮件配置信息 ) s) t# F, C/ r/ e2 X- m *// @: a7 p/ d: m1 o6 B9 T0 }- Z
@Component& ^5 y* q! A$ D
@RefreshScope //@1 , U0 n: P: X Lpublic class MailConfig {% O. l6 l0 d9 X9 _1 f% Z
0 A5 V2 E! ]; y( V @Value("${mail.username}") //@2 1 Z: u, [5 `* \ private String username;! R3 Y0 R( Q/ H% V P6 N, M% Y N) i
% N2 Y1 J$ |! q" {: D! ?( g3 H
public String getUsername() {% C, q4 I, [' K* {. \$ H+ d- ?" j
return username;! k$ {: q- ^( Y$ t7 l/ ~ b/ k
}7 n. C+ G! D$ J8 ^ ?
7 d Q; {( Y$ E: N# Q public void setUsername(String username) {9 X7 _; b# W! e, y: K* A) h
this.username = username;! |8 l4 u3 y) S2 E# j
} 2 ?- I; _) w+ @. Q+ F4 E6 Q" F4 }$ ?% m
@Override) b( \* Y* H! i' J1 ?7 C
public String toString() {9 O l4 Q, k) X! r9 V1 _; O
return "MailConfig{" + ; q& d& P+ | P, y+ b "username='" + username + '\'' + 9 O6 r( z! Z7 j9 S& |0 \ '}'; 5 j: t! q8 f" u0 _8 o } , j" R3 d/ y$ c2 @7 t} " L+ u9 s) o B1 t. }% s@1:使用了自定义的作用域@RefreshScope 2 Z2 @1 I$ r4 ?5 ^. \/ } # {( h6 o) D9 C+ y' K@2:通过@Value注入mail.username对一个的值 - z" S/ t( }* S. [$ }4 T' L , G; i! g0 z7 k* F$ I5 g# N( h重写了toString方法,一会测试时候可以看效果。 - ?+ ~4 ~& T0 |1 e- @0 c4 d. \* S% t. X5 I! o. w- w; n
再来个普通的bean,内部会注入MailConfig7 K; d* b# i( M2 [+ w
& J O- ` b0 Q3 ]
package com.javacode2018.lesson002.demo18.test4; % ]6 T) H. d' L% r& v& X: a8 n! w' ?7 w" K
import org.springframework.beans.factory.annotation.Autowired; 2 P" E5 A8 h: I" E- Rimport org.springframework.stereotype.Component;, a( t, ]/ ]+ C! R9 [
9 x M) m3 w) c% o. ~2 t9 E2 W a7 W
@Component( k: @& }* Z2 A- ^& X
public class MailService { & Q7 B2 q$ C& F; G | @Autowired- Y4 M6 I6 x% i( B- a+ S' Y
private MailConfig mailConfig;1 Q, y3 Q) S; }* p# z1 }; U" F* M' i
1 \( g6 \( ?" ?- U% K0 `& s @Override 8 H; l- K- P5 v) T public String toString() {2 @, T7 q9 h% j* k+ x5 V, ~8 l
return "MailService{" +6 J5 y: G2 |0 w/ ~: @5 K& x2 O
"mailConfig=" + mailConfig + # E6 b# a+ A' u( G0 y% @ '}';$ Y7 }0 z8 Y# {0 p7 I! b1 ^1 r4 f$ l
} # x$ @" j) @, h/ o* h G8 \: U8 f! @} 3 ^1 G9 d d* ~- R代码比较简单,重写了toString方法,一会测试时候可以看效果。 ( a& S# Y# W1 T L 8 E6 E4 W( E; Y来个类,用来从db中获取邮件配置信息" A* S$ n4 C. w9 H9 f, ~0 v
1 w3 G$ l9 e) C8 H8 {. Z, c6 [package com.javacode2018.lesson002.demo18.test4; 6 M' |& \3 A" X1 ^1 u+ v ) y9 S, B, t0 W2 ^$ M8 b4 f8 m0 timport java.util.HashMap; % E6 |8 S& f) O+ G- a* N3 Iimport java.util.Map;# R2 Q$ ]/ D9 I) e; O5 Y$ \1 C4 h
import java.util.UUID; ' Z7 p0 K! O, s" q- [! o6 {* L, g( f# o) Y9 P0 P1 N9 ]
public class DbUtil {9 T: i, @& m% Z% \. n, z
/** 8 @1 B. L0 u& r6 P" B8 j * 模拟从db中获取邮件配置信息- |, ^5 S* Y1 p* ? E# B
* 1 @& X8 c6 e3 I% ~ * @return 3 j1 L' u3 \5 b; f% J2 p */4 X w: z/ L; B; t d9 j
public static Map<String, Object> getMailInfoFromDb() {2 j2 |1 F1 l8 [& w( {& ~/ F
Map<String, Object> result = new HashMap<>();1 k# p. b* a% k9 {- u% U
result.put("mail.username", UUID.randomUUID().toString());* |' C; I4 O8 k$ u" D
return result;! u: A6 K6 \6 J" x! u7 F7 V; p
} 9 S! w! f' }. b. y# F6 T2 a3 L} ! s }) o, m y$ T1 B! v来个spring配置类,扫描加载上面的组件 0 f$ F) M: N. I: j' u/ s$ G" E" i' g2 h5 O! a- u# G
package com.javacode2018.lesson002.demo18.test4; 5 a( j) I0 ?2 e' v% K0 B& B i5 l4 N. k: D4 V
import org.springframework.context.annotation.ComponentScan; # ^: V( b& y, c# y& G. ^import org.springframework.context.annotation.Configuration; 0 o) n; K- T* u, [& m3 S" b " Y, m8 {+ T* l7 o@Configuration 6 D; j" ?: ?7 M' r. W@ComponentScan + P& s* {3 M* }( x Kpublic class MainConfig4 {! |$ R1 `0 O/ {0 p& i! Q+ _# V
}" y5 E* c9 g v( l& w7 P' X
来个工具类 ; }2 I( r4 v" q G" ~8 U8 I' c0 F+ J6 [9 I. U( l0 o
内部有2个方法,如下:! \) u. G; D' f4 J* b
+ i, V' h# X5 p
package com.javacode2018.lesson002.demo18.test4; . S, [4 l( C2 F; v: \ 4 M& o0 l5 F) V9 Nimport org.springframework.context.support.AbstractApplicationContext; 4 j' Y% F0 N; ]0 Qimport org.springframework.core.env.MapPropertySource; % r( a9 [9 Y4 G 7 v/ @" Y5 H. }* g0 G/ R/ Mimport java.util.Map; 0 r* d: K7 b/ Q3 y! S n9 H* b+ G, E$ p( A" z& D
public class RefreshConfigUtil {, r- j* g$ [+ z8 i8 J" O+ P
/** ( G# m# ~1 ]2 N * 模拟改变数据库中都配置信息( n# L/ a4 a. i/ ]' k
*/: V( \' t& C+ k7 [7 @" j( q
public static void updateDbConfig(AbstractApplicationContext context) { ; v" \& q: O1 c3 H G //更新context中的mailPropertySource配置信息1 C6 U7 K. x: Z' m- G7 z/ h- y
refreshMailPropertySource(context); - E. o+ ~, L5 q8 U; L$ X9 x6 G! j3 n
//清空BeanRefreshScope中所有bean的缓存 5 h9 F0 c1 n& K BeanRefreshScope.getInstance().clean(); - e. D0 [3 ^# C* X' s. I! |/ i } & `$ S J5 I/ a( Z 1 p$ l, H5 ?) v0 V public static void refreshMailPropertySource(AbstractApplicationContext context) { * m C' s; a6 \ Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); # Q( N% W% x) ? //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& l# P) d- N5 @' y6 I
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);; P7 S3 ?5 c3 _& U
context.getEnvironment().getPropertySources().addFirst(mailPropertySource); : K+ V- p& m% X8 d$ ?& n5 a9 z } # y1 O2 c7 {2 u t. n% A3 G f1 V' N& h5 g$ j} 4 b! R% Y% K" ~. a- GupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息 2 ]6 L7 O. o0 u6 W0 [3 r- l 7 I" I/ b, @3 w9 V/ H" @. ]1 P5 a+ oBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。 8 Z+ D; p, Z8 C: j8 V# N0 n/ y & {2 |8 }) _; a4 I% u U1 d来个测试用例6 b, Y+ |7 G3 Q
6 D. q- i2 H- U" [@Test $ ?; p' L9 C5 i1 hpublic void test4() throws InterruptedException {+ P, ]( ]" u2 M
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); 3 `/ j, g$ d1 V( \ context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance()); # [5 c/ v$ W0 H5 Z$ M( Y+ B context.register(MainConfig4.class);( E# G& _3 d4 K+ s
//刷新mail的配置到Environment4 P. F( j j7 m+ U5 v- r/ v5 g
RefreshConfigUtil.refreshMailPropertySource(context); ( R9 _( B0 {8 c9 ?" p context.refresh(); / x9 Z [4 ?% Y v# ] 1 x! Y* W) O& b7 D, k MailService mailService = context.getBean(MailService.class);. ?, y" _; e3 Y+ S9 Y0 L: t5 p# a
System.out.println("配置未更新的情况下,输出3次"); 0 f& p' G0 V1 v for (int i = 0; i < 3; i++) { //@1 ' C; V: n$ t* c2 K7 f3 F/ z System.out.println(mailService);/ W* p# Q& b: e: A" `# l8 F; g
TimeUnit.MILLISECONDS.sleep(200); 7 c0 v! U. t4 ] } 8 T& g+ C- x+ r4 U h 1 |4 q2 \1 `+ K" }# o& E$ c System.out.println("模拟3次更新配置效果"); ) h! L( h7 P2 l% b; U, `! d, k0 A: _ for (int i = 0; i < 3; i++) { //@2! F5 M3 X9 Y. X# d6 o
RefreshConfigUtil.updateDbConfig(context); //@38 x6 k) y, b, r9 d
System.out.println(mailService); B; O- u3 E2 W# ~6 y, r1 i
TimeUnit.MILLISECONDS.sleep(200);5 P0 h5 c- G: G3 ~/ F* B3 A2 a* _; g2 Q& t
} $ L3 g5 g8 B- u} . h5 D: c; X! h) K) _@1:循环3次,输出mailService的信息 6 i( i) t6 N: `0 R/ D+ g) Q1 w1 v$ X
@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息9 Z2 s0 e+ a% T( o2 f
* m" `7 v. i7 u. A/ I& j见证奇迹的时刻,来看效果" M6 e7 B+ @. q) c
" V2 u I" x( }) ?# o7 c0 l配置未更新的情况下,输出3次 3 U2 b4 B. K2 l# P* g* _% tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}' d1 }! r w( B+ T3 o
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}4 l' }. K+ `; _ q, g, b
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} . c- @+ d) r$ ~+ v2 |6 M( m+ H模拟3次更新配置效果 4 A J7 G) t& P/ JMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}} 4 Q' K3 ?3 r* \2 |/ M) b! UMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}8 @0 R& ?$ o; M# H' X
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}1 s, K( ]0 |+ C
上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。4 Q' F4 k/ d9 V" i L
, {, V3 T+ z- Q$ o2 {; D O
小结 . Y% a3 N* I2 R7 T2 ~+ K' w ! f0 I7 }- T: |动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。- J" r2 \! V$ Q7 ~# `: a
- {# V3 m3 @3 d( G/ ?2 y& x% T( X有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。 ) s9 {6 O0 e! |3 | f5 \1 J* d6 }/ q) B( J& P7 U0 G" c
总结 $ K: ]% u# F. N* }2 [4 z4 w$ Y4 T1 W X5 a& n- w
本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!' q F- |. F* T$ W4 C
8 Q3 z) I2 k) G" l' v% h
案例源码 / n) O- c% |/ r7 ~# g / k- [- N: r) N8 z2 x9 e; qhttps://gitee.com/javacode2018/spring-series ) q6 F7 ~5 R- V! G; }+ L路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。 7 c% G1 i6 ^1 _4 h' P* D. h& E* q) j1 f- B" _/ W
Spring系列: H. Q I+ A9 e& u# M1 v8 g" z2 Z
2 g0 G8 L% F4 e, Y$ A" a" w/ [9 f; s
Spring系列第1篇:为何要学spring? 2 w0 ^# N) i. t: D' K( h/ E# w6 z; n# P n9 r
Spring系列第2篇:控制反转(IoC)与依赖注入(DI) ) g" h3 p' S9 j* ^% R$ ~8 S 9 L r$ [9 ~9 A; w' QSpring系列第3篇:Spring容器基本使用及原理 % u% Q- h1 m' A: i3 ~$ R ! v% H# r% m1 N# T2 p& P$ _; w5 eSpring系列第4篇:xml中bean定义详解(-)& Q* D5 V8 q; }6 X' e