' y0 d* U4 C. \& W& M@Value("${jdbc.username}")7 O6 C( f& g, A: c
private String username; 7 H" T2 Q* Z( C; V7 F , E Y8 U1 x+ ]6 `& u2 k@Value("${jdbc.password}") ! B3 X# Z6 @6 K+ J1 v& C% p: o3 Qprivate String password; ; P4 e. ~: g% f& B9 @/ @# o$ u下面来看案例( ]. d8 Q, u Q% E# Q n M3 K& t
9 P. O# w$ F6 j$ S案例5 X0 s% O/ L" i4 W4 l8 t$ C) o$ }% |
9 B Q5 O. O2 R; w* g7 b
来个配置文件db.properties # g2 w: O8 V* f8 N v5 { , X$ H0 Q8 z: { P, ]/ G$ o; b: ~jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8: |: U* p2 S& P8 A* b" O
jdbc.username=javacode 1 D$ [8 q9 N& a! njdbc.password=javacode9 r$ |5 ~, ^8 H1 p9 f. ]3 m
来个配置类,使用@PropertySource引入上面的配置文件 + Z# a: p' U- a \# D! [" R 8 `8 a1 \9 Z. n, r# tpackage com.javacode2018.lesson002.demo18.test1;; E) c3 K4 X( ?/ g9 N0 g1 \9 _% X6 H1 q
; m% u2 o2 B, f; d( H nimport org.springframework.beans.factory.annotation.Configurable; 4 `. ~# P' t: i5 C. A0 |9 {import org.springframework.context.annotation.ComponentScan;+ f# d; A0 I; d5 B
import org.springframework.context.annotation.PropertySource;; W$ |2 Q, G4 ^% f5 b z
- ~/ _/ ^6 x f2 ~( P2 |3 k@Configurable 8 q" {" z, U. i/ X7 c# d@ComponentScan + B4 w u+ P! c@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})6 A) K, l$ ~6 ~
public class MainConfig1 {! F8 Q8 b7 E6 A' ]1 D! N8 g, P$ }
} 0 n3 H6 o' `" P$ ^来个类,使用@Value来使用配置文件中的信息! _2 V, O' E7 @5 [" F- V; H
! S" J( w. n+ H' b+ E* Q' I
package com.javacode2018.lesson002.demo18.test1;( [& x% I' t. q
2 X& r2 X/ \5 p" A: x9 o
import org.springframework.beans.factory.annotation.Value; ' ?# H9 [# t+ M1 |import org.springframework.stereotype.Component;+ ^6 `2 b: ]- o
' s( F$ B& ?8 }
@Component% v/ P$ R! _( M* B% W; D9 d
public class DbConfig { 9 H5 J6 k4 j3 J/ a: w( L 1 _3 j+ J3 i1 g, k, ? @Value("${jdbc.url}") # O0 L; D0 r9 ~8 J private String url;8 V/ d9 N0 R0 P& k6 u* p
7 a9 f; T4 b% C( Y; w @Value("${jdbc.username}") 9 _ v$ w! h" ^ private String username;+ L V4 Y. k8 F1 ]
3 `+ g$ h1 p$ D7 _4 V* W9 u @Value("${jdbc.password}")& K: G! D. s) b* q4 G% e
private String password;: k" Z; s5 B; K6 Y5 l% h
: T, U% `, ^9 L5 x7 @: I$ m4 K public String getUrl() {% s+ c# c6 F/ S: T: b5 }
return url; , K! r3 t2 ?, y. e6 a9 S } 1 y2 u, Z# b% T+ M5 h$ c9 {& P- f) w" [+ e; }
public void setUrl(String url) { ' S* }* O. c1 \( K3 Z) r8 o- K- g this.url = url; 8 |* m# ^# L% q } 1 d3 v$ |6 [6 h: ?" S' F, ?: K8 F9 L/ a
public String getUsername() {' |! Y" C6 N4 n8 X( h, R7 r# K4 o6 M* E" C
return username;* m! U' C7 g% s+ R( F
}2 L1 v0 M) B; c1 k" ]! Z" c/ Y
. k% h ~. v4 x1 b8 |- P8 a public void setUsername(String username) { 5 T& p F9 C o$ }! U this.username = username;0 u. u9 V9 S: N8 C
}6 r9 W+ ]5 o6 s9 ]
" j6 a% j/ t" @) J/ J4 s- {" M public String getPassword() {1 b; N) Q0 T' d. z
return password;8 |+ |6 v: G# E/ e
} " P6 \0 E4 y: r/ t9 {8 L: E' i, U) J9 H: X
public void setPassword(String password) { ' O$ G8 v- O( S+ H! w+ Y+ S this.password = password; / g6 o5 x5 X& u }; w( Z3 k& c2 s! a" L# O8 `+ q
# [( \ F7 G: X5 ]2 L* e" `8 f: t
@Override3 s2 g! o6 s2 x" w, d; a* @
public String toString() { ( D( Q4 _- q0 ~9 _5 n return "DbConfig{" +: i) n+ {! Y! B5 x+ f4 i! D) k6 j
"url='" + url + '\'' + 4 G3 b0 p6 m& W1 X7 I1 p ", username='" + username + '\'' +1 U, Q: V, r& H, u& h, F0 ]
", password='" + password + '\'' + - _3 T3 @# P d+ i% T '}'; 7 W9 \3 W7 {+ \ }6 B" P+ q( C% J! E& G
} # f6 a' I3 |" E7 b1 q/ l上面重点在于注解@Value注解,注意@Value注解中的1 J5 {* z1 d* _0 {( M3 a% \: E
/ H G3 h; B v! O u来个测试用例1 J* B. K: V5 o0 u8 m6 \
! W9 ]/ D; k2 E& ^% w. Y9 ?
package com.javacode2018.lesson002.demo18;: N s3 B4 U6 G% j9 e9 {( \2 J
( _: S& P+ [8 ?. Q/ g
import com.javacode2018.lesson002.demo18.test1.DbConfig;( v6 w8 X) P1 R3 o! U
import com.javacode2018.lesson002.demo18.test1.MainConfig1; % N& t4 z; I1 d% y9 ` C+ x3 zimport org.junit.Test; 7 j2 o/ c3 ?5 S/ \, c6 S0 Rimport org.springframework.context.annotation.AnnotationConfigApplicationContext; ) ?, F- L; S% [* A ' w7 Z1 k3 ~2 S6 {public class ValueTest { / M) ^! N" [- y ]8 @8 U 4 q9 S! Z. a+ E; D' c# T @Test. j- x8 x! a$ H( `7 L5 d/ p5 h4 Z, A
public void test1() {% ~' }0 _/ N- K1 n; |
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 l! F+ c; {8 ^+ @& b, V
context.register(MainConfig1.class); 4 g$ A/ Z5 ^. A! o; G1 l) o context.refresh();: b8 V: P" t7 c% s0 l
4 H+ s& X; q: i; L( x8 [ DbConfig dbConfig = context.getBean(DbConfig.class);5 \8 }$ u3 O4 R5 D" i
System.out.println(dbConfig); ' a% b, C* F+ W* t2 ~ C. r }4 s' O1 c1 m7 j, i( f `# L
}$ [) k3 M. r7 R; F* H
运行输出2 [6 g4 R% r- a0 @$ m/ j
- d- }3 d4 C T) O5 w
DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}$ u: N8 S \# L# n( N* t% e
上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。! W% k6 Q( r4 M0 m+ H
2 A! ~. m& B$ _/ x0 O1 B, |1 \, P@Value数据来源- o) Z% u* s1 M7 w. d' q
9 B$ o/ s i3 D; _2 z通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。 1 j* q2 i! I2 n4 K9 e7 U7 D2 Y6 e, p
我们需要先了解一下@Value中数据来源于spring的什么地方。" o: m- O# ]) s
4 {& A3 c3 W8 {4 J [3 v! ~6 pspring中有个类 ; u6 e' O4 i$ z- J3 Y* r : p$ W* r2 m# T5 z" c5 p# ^; Dorg.springframework.core.env.PropertySource 4 A! q5 z7 f O) U- k可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息 0 L' C. z( N3 [( \7 o7 X" O- H* Q) y4 d9 i2 |
内部有个方法: 5 B) \& r* j/ s6 V& p% g7 i% a- v A7 Z, g @4 F: g
public abstract Object getProperty(String name); 7 L! S" ^+ U8 z5 G% |" E通过name获取对应的配置信息。 - ]. U! z/ U5 F" a, J$ S8 a; Z' \, O- L( u; g8 {
系统有个比较重要的接口 & B# h7 Y2 s5 p7 r- @2 l# K+ ]' s3 d. b e& u: n
org.springframework.core.env.Environment & m7 _! N n0 h6 V用来表示环境配置信息,这个接口有几个方法比较重要. t9 |$ @( o3 M* p+ v
) V, q1 t u9 `1 y# q- LString resolvePlaceholders(String text);+ p2 |/ A4 k# S! l# E8 E
MutablePropertySources getPropertySources(); p. W; Z7 X( e, e+ gresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。 q4 B( Z4 V, c5 T+ r
, s- ^. T1 r T4 y( n4 U5 |getPropertySources返回MutablePropertySources对象,来看一下这个类 8 e4 H c) Q7 @1 M * {4 l6 N% X6 ]; wpublic class MutablePropertySources implements PropertySources { # _# L% F. D/ r) H, @" n' K4 R7 T3 i4 U+ v
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();3 R0 d- W, u2 v' ?* n/ B% C" G6 ]
$ C. G/ C8 [8 ~7 m8 b
} ) X) V9 v7 q* w* R+ x" r$ M3 k内部包含一个propertySourceList列表。$ l8 m5 m/ L( c
4 b$ W W( T( q4 t
spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。 / W0 C% @, }9 F* Q( T! y4 @6 a ; A) L. O: L1 f: h$ l; d大家可以捋一下,最终解析@Value的过程:& o, t' S* W. V9 C1 [9 [0 u3 e
2 B- K; t- c, A# l2 K& W/ L
1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析- X* N3 n9 b$ r5 m( V+ M
2. Environment内部会访问MutablePropertySources来解析# L- u9 x5 { Y, S( b
3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值 % h( o6 N @; o/ a7 g+ m通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。 ! G! j4 `' Z; A/ D2 {9 {, R5 ]' R8 b" L) z9 o
下面我们就按照这个思路来一个。$ ^7 W( U; x0 A4 t4 ~( ~
3 |. ^, v0 X- [1 |. L
来个邮件配置信息类,内部使用@Value注入邮件配置信息 * ?# x" X) N; f4 D i ! _' ~5 \; t% }. X, jpackage com.javacode2018.lesson002.demo18.test2; 3 o* _( @, ]) R' h0 G1 l 5 N: Z; t! h* Z4 X7 r8 \+ r0 @7 cimport org.springframework.beans.factory.annotation.Value; & G% x% |5 Q. g! Q( {" vimport org.springframework.stereotype.Component;4 I* t, n _$ Z0 |! f1 q
' K( m2 ]0 c. i+ x% {) d0 \& u如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。4 D+ r m e' `4 Y( Q8 M, B& U
0 X: C6 \9 L1 m. Q; M4 k
@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。& B, ^7 T5 Q# ?" N6 Y2 Y: x
) z0 J" g8 D7 L9 L1 ?2 c/ i% |4 L" z实现@Value动态刷新 & k6 |, Q( _$ o& p & P6 m) Q7 B' A+ l! Z! d先了解一个知识点8 s8 V& L/ m. @9 ~9 S
/ F% E7 E" c% E2 P0 }2 R' bimport java.util.Map; % ~7 `* j8 l& J2 z: `0 g$ Y+ y' r; F4 ?& m8 f
public class RefreshConfigUtil { ! P5 z& V# t3 T% V( j/ }: O& N /** # Y, X h2 O# `! e+ n9 H; w * 模拟改变数据库中都配置信息& K x1 w' b, q8 b/ D2 E
*/8 C2 p& ]* r& d1 K+ p7 Y5 q
public static void updateDbConfig(AbstractApplicationContext context) { B6 r$ {) c, i6 q //更新context中的mailPropertySource配置信息- @0 [( w/ p5 O1 J
refreshMailPropertySource(context); . R9 h4 P/ |* r, E: c 2 p% @0 V' d% m" B2 ?7 m) Z //清空BeanRefreshScope中所有bean的缓存 & g, a/ g+ _4 P! F4 ^ BeanRefreshScope.getInstance().clean(); |4 \9 a8 ^- R* S, d: c) Q } 3 O& U" j1 U6 `" s! G; T# P5 w; d& W+ k! D8 H. X# y2 C& e0 A
public static void refreshMailPropertySource(AbstractApplicationContext context) { + h) R$ p" Q2 h3 O& i# O# y0 N Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); 3 I+ i, M5 A( Y0 S //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类): I. ^+ l3 y" d: o0 l
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); 0 |$ ]' e$ m8 U# L context.getEnvironment().getPropertySources().addFirst(mailPropertySource); & @' C+ t. z) H8 R% |5 z0 \ }' d1 d' U" u$ d7 Y
9 k! h. U& ?3 S ^} 7 K, |# u; B# Y, s" {( m# t7 \ t6 |updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息: \% S# c( E# q. D+ U$ q- X, x
/ r$ N1 c+ P V$ L) eBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。) N$ |: ], N( v- i3 f, E8 \; z
+ L8 c3 ~$ w3 }3 I
来个测试用例! A7 |5 n' s# K
b7 d4 E y& |0 u@Test ! I+ s0 Y. p: F/ Hpublic void test4() throws InterruptedException {3 n+ S' u3 F7 u
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();4 x; k( E" w: I- t( z
context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());, m$ o2 M' E7 A; n7 F
context.register(MainConfig4.class);6 B) T$ m3 e9 q3 J A4 @
//刷新mail的配置到Environment 5 g6 n: M6 d y" L& I; ]2 w5 M. k0 v RefreshConfigUtil.refreshMailPropertySource(context);1 a$ Q! i! F& o; G2 n9 V7 U& M u
context.refresh(); - h8 e* j& t/ k$ u8 d) }7 j5 s s$ @) d' H# W MailService mailService = context.getBean(MailService.class);+ P/ Q. ~% ]9 m; c% T) e4 T
System.out.println("配置未更新的情况下,输出3次"); y9 v) p1 y' c& [
for (int i = 0; i < 3; i++) { //@15 A( i ]! P% z; T# D
System.out.println(mailService);$ M# d V# V- G& p4 j0 t ^
TimeUnit.MILLISECONDS.sleep(200);, n( ]. H, U" q2 P/ E" Z
} ! r1 E/ C9 n9 y* ]% f; f * p/ O; x) K- i# u0 o/ P System.out.println("模拟3次更新配置效果"); * G2 q& I# A* b; O for (int i = 0; i < 3; i++) { //@2 2 b0 o1 Z& z: K+ _0 ?1 Q RefreshConfigUtil.updateDbConfig(context); //@3 3 P1 e6 Q; i0 X$ u5 S System.out.println(mailService); 2 c6 P, p& a7 r e! J. a8 n TimeUnit.MILLISECONDS.sleep(200);4 _2 C/ S$ d6 V$ q: E& g. ^
} / G$ X$ l" S3 ~' L* B( z} ; Y; {6 u! |" ^5 A A$ [4 i7 Z@1:循环3次,输出mailService的信息) j4 S0 x) |' \ z4 E+ T" N7 G( i
4 e# w$ |" p% m9 d% E% ]) ^
@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息 * T3 W+ Z; F; e% a. F* L: i; U) `% X6 J; i
见证奇迹的时刻,来看效果 : u$ R3 K& @# L& s K, v3 ? 6 w% Y% N9 ^) R! |. _3 c3 O配置未更新的情况下,输出3次 q9 ^& ~5 x5 t: m, ?' ` p0 o
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} % D2 G2 p& X/ zMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}$ S) e; |4 ^, O) h$ n/ [0 K
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} . [/ C+ X- ~- a0 i1 j1 H模拟3次更新配置效果 ( _) T2 x t" P! Y& a/ \! g( aMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}} ' N1 b/ T$ F2 t2 `4 Z2 kMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}, i/ [+ T. P, A3 W( B; P! R, B* e
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}} 1 q7 s: ^ {/ W上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。 2 b( T! J1 w4 O0 n: h2 E5 z( P; Y4 } ^- ^- O3 G5 x1 j
小结 3 F( b. _# [' r; c6 s1 n G9 g 9 M) @ i! R1 M* f$ x6 e, T动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。 % }% Z6 ?8 B/ a 0 L4 x! u, e, W9 r+ W O有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。 " Z8 _' D# P, d! H' \: M+ s/ P' |% Y3 o: {) E$ X
总结 * o: p1 {1 t" B% j: Y' n$ h" b% ` 6 R4 |8 M( V7 v本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!3 j/ U( N. |5 K: e) K: u
1 k0 r; W) w" g7 Z+ l. X
案例源码+ B) @! u N/ ^
4 w8 f: n* V/ C% g) D# o/ q
https://gitee.com/javacode2018/spring-series t" o3 A; ~2 D) F
路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。 ! [9 j; F0 }! n4 l# h/ R 0 K$ w* s( l- J* ?5 ]Spring系列7 O( T: V9 w v: O- ~
3 }+ O" i* d! V0 K4 B
Spring系列第1篇:为何要学spring? 3 B6 i! c% M0 [: H4 a0 ?6 { z- o+ e, {, l8 b: ^
Spring系列第2篇:控制反转(IoC)与依赖注入(DI) 4 O5 j/ a' Q: s+ O. |" ` # [( H4 Q2 U+ U& j, r% }Spring系列第3篇:Spring容器基本使用及原理 ' b: |9 n1 ]+ b2 k8 e4 s; r5 E0 y$ m* T% D3 N9 A3 A
Spring系列第4篇:xml中bean定义详解(-) ! q1 H- |4 U' ]" B8 v7 U% y% o4 L% y6 F; R9 G
Spring系列第5篇:创建bean实例这些方式你们都知道?) Z* H9 z( }7 \. y) E: l
' _% n0 M6 ~) d7 \! G+ }; \
Spring系列第6篇:玩转bean scope,避免跳坑里!1 }1 s* _2 L6 c/ I" S. U, ?
* t! V& U& v8 [7 m5 ^3 S& Q( G
Spring系列第7篇:依赖注入之手动注入$ h9 ^, w, [& k- d3 Q) {& K
5 S4 C; n6 t3 Z0 v: e$ W6 dSpring系列第8篇:自动注入(autowire)详解,高手在于坚持 & v8 c K& d \+ c1 g$ o- p, T3 ?. h& z8 ~. B0 r0 L, \* b
Spring系列第9篇:depend-on到底是干什么的? * N! I! V7 {' t( Y5 B; V& [7 X9 v h4 N% y& C8 `
Spring系列第10篇:primary可以解决什么问题?; X! p# [/ _! K9 Y0 i7 U: k( g% b
. d6 A; ]7 U5 T! B7 }Spring系列第11篇:bean中的autowire-candidate又是干什么的? 7 g. g( v J6 m% f T3 z / u4 Y. p4 ]6 [# eSpring系列第12篇:lazy-init:bean延迟初始化 , ]2 C" ]" N: A: h' I % p O @2 q& N$ ISpring系列第13篇:使用继承简化bean配置(abstract & parent)4 m; s" U1 K: Z; Z9 N
U: Q% D" m }3 f; U2 `Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的? " }9 D, |: B: ?# C4 b% J5 l4 k; G( l2 b: I
Spring系列第15篇:代理详解(Java动态代理&cglib代理)? 5 G/ W/ t; U$ ]. o3 Y% E ; } ]' M9 W( a/ P' xSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识) 8 m. k M) ~* C/ \2 Z, J0 z, r% A) |/ J5 p
Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册); }: I. e, U# a! G% `2 ?% _5 D
" R, g. {4 ? h6 t- ^" {Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)0 M! I. G' v! @% l0 x% l2 T