4 W$ U( m0 W9 u' w$ y面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?3 J. l! }1 w2 s" A5 t
! d# P9 j: K" D* O. q5 H我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能9 {* I, h* W$ _ W/ h# P' @
7 n/ s, h/ G4 G
面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?0 S" G* s3 y, J/ o& x; z
3 x+ e9 k. e0 y
我:嗯。。。这个之前看过一点,不过没有看懂 4 E4 g" r7 A: A7 \8 @4 S# n$ V2 \! q: }+ ?# d- ~( B
面试官:没关系,你可以回去了再研究一下;你期望工资多少?+ E# c+ o/ Q5 }! M- ?/ ~1 T9 ^' i
$ ?$ v& i2 F5 F5 m& I5 v, p我:3万吧# n9 T2 [1 m8 v
0 O2 e/ h* {9 W% m面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何? ' E5 d- i2 v2 V4 t( P$ `) j' V/ U8 E( ]8 y" B- w8 K/ g
我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万 q( ?" {$ a* g7 ?$ g Z9 }2 P
. L/ b7 C2 O( |, Z6 X
面试官:那谢谢你,今天面试就到这里,出门右拐,不送!! ?. h( a* g! `* R7 }. i) i
0 s- F5 U& y7 b+ b# d' b- g
我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。( h' X+ {' |# x: p& s2 e( Y) u5 G
0 k& H9 k" {" n h
这次面试问题如下 / e0 t# N9 ?7 \: M! W! x4 k: Y S
@Value的用法 & E' _! l0 G8 _+ E1 N, P2 D9 X' n( j% m
@Value数据来源# X* |, \' H# F3 S: W9 o
* a; c6 U) V% K% \
@Value动态刷新的问题 1 B- q, w+ I8 u c0 Q s, z 7 U( z) V3 @5 a. W+ q# f下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。6 b3 W. W( V6 n3 q4 V& b8 h
* e- l) o' `* K! ]# b7 B@Value的用法( ~3 h$ Y' b) D! v
' S6 x! b- j0 K系统中需要连接db,连接db有很多配置信息。 % f3 Z0 A$ y7 l! B3 m. L ! ?& n6 N' B. B1 U9 X# h* X系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。 * e' M. |: P2 }6 N! ~& z9 G0 k% |+ X4 _& r6 J) f& k# B: R4 O
还有其他的一些配置信息。 " f* x! t% v2 j3 v( z1 D h$ F3 i+ w! j, U" H! |
我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。 - ]$ F: u' ^+ E4 a: q$ U2 E5 c. U* u, l8 {
那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。7 d; R6 V$ t" X9 A# [3 J
1 |( g9 n1 D! P. G/ _4 b* j
通常我们会将配置信息以key=value的形式存储在properties配置文件中。3 [+ a, t c$ q1 S, @
. n7 S$ S$ x) e# g. J1 Q
通过@Value("${配置文件中的key}")来引用指定的key对应的value。 5 \' }% u2 p: h: |! G" `' \$ C2 M' e; f+ H. p: N" j% C
@Value使用步骤 ( d! \ A4 w; o; ]$ b8 T- Q! x" `' L8 G6 z! e' `
步骤一:使用@PropertySource注解引入配置文件 + t, m" M% |: _5 {* j, y! R) Z9 d- V% V
将@PropertySource放在类上面,如下 [5 g, b- X# E6 I5 L5 a! ~: g' H5 H7 ]
@PropertySource({"配置文件路径1","配置文件路径2"...}) 0 C4 a6 A# U( ~2 Z6 z* {% z@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。 # k0 k( Y! Y0 S3 d. [" o: I4 |( L, G* _& W$ {
如:! U* p8 m5 o9 u! d4 L
1 ~' x. @) |# z( P. B- x3 Y4 I. W@Component# I& R" s3 t- z/ Q C y
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}) : M3 \3 a% |4 opublic class DbConfig {$ {* }5 j+ \2 { m) J
}) j, Y5 `% m K- q, }6 ]) f
步骤二:使用@Value注解引用配置文件的值 6 O \' J# J" Q) p H5 @( ]3 v6 w& a' ]4 m. t
通过@Value引用上面配置文件中的值:8 P* N- Q, K' ^6 N/ {) m0 U% s8 C
V2 O2 ]6 z1 ~ I( {" p3 w' o语法' h, |8 W* h# ^" s
7 F2 a9 N2 F5 z5 v/ T3 ~
@Value("${配置文件中的key:默认值}")' [" t4 L: ?+ b, l7 w) {* ^1 k
@Value("${配置文件中的key}")1 J) B; G8 e: ~ a( [2 q4 u0 b, ?: p/ ?
如:8 d) z9 h4 ]0 J* C8 w4 H+ {
8 a3 Q; `' A8 s1 r0 z1 `8 [; Z
@Value("${password:123}") 9 I. i0 m B, g7 Y. L% H9 A上面如果password不存在,将123作为值4 e; H [2 L" R" I3 r
: {) @5 `! D; ]% W
@Value("${password}") / i8 A5 `, H9 S$ D! i上面如果password不存在,值为${password} $ l* B; v( e2 u6 e* K+ L/ H0 Q$ X; c- K9 y9 Y! Y- B
假如配置文件如下 + T- m: y- i5 s B 8 m, [* { a# b: P Ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8 2 v& D( M" }3 `1 m9 y+ xjdbc.username=javacode 7 j* o# i8 E% u9 P7 \1 ?3 g+ ^jdbc.password=javacode0 G9 f- z8 B9 [2 H. Z2 T
使用方式如下:' Z# m P* Z v
# W* A; f& I+ H; E* ~9 f% U* b@Value("${jdbc.url}")! w Z/ z0 W0 }* x( ~/ I- S
private String url;% {) h8 v, g( d
6 z. s, r) q5 g@Value("${jdbc.username}")# ~+ D$ |0 y5 l7 X2 U
private String username;/ n! E, s# r+ [
/ U, g+ Q8 T+ V0 T8 a@Value("${jdbc.password}") ! I6 J& k6 ~- s; k% k) U2 [private String password;8 H8 R$ X& b, y9 w* \
下面来看案例 ( f( u" b2 b9 `% o ; }- \2 D" o# \1 k5 X, A- ?3 O) l案例 5 j# ^, b8 R C/ |" m) o8 x/ G& R; ?* S) F7 {) O# z: G
来个配置文件db.properties ; V' _* V3 Q7 Y% S) P2 N0 C, e/ P. p) e/ k. t
jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8) ?$ O# `1 m; S* V- l
jdbc.username=javacode C* x3 h9 [7 e6 }# D$ y
jdbc.password=javacode; d0 C: ]( L/ @; o: n# @
来个配置类,使用@PropertySource引入上面的配置文件5 F7 f0 h1 q W' s$ O2 I) t
) B& ^5 f9 {9 S" Q0 h& o
package com.javacode2018.lesson002.demo18.test1;* t; F9 p7 ^ C; u8 U
J1 J" \3 H) U# v% J
import org.springframework.beans.factory.annotation.Configurable;% ^' p3 s! [4 l1 u
import org.springframework.context.annotation.ComponentScan;3 D ]# D/ n3 C
import org.springframework.context.annotation.PropertySource;. I2 j6 O; u' y' E2 T
/ q3 t- ?& M; C6 E@Configurable 2 W# ]& t9 e; V! L% {. g; q8 @. e@ComponentScan" k& F9 b$ k6 a& j5 m& X
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})% B1 j/ c. b8 o' p7 z$ N9 V4 [' l }: a
public class MainConfig1 {0 V% t" E2 q6 g
}4 H* Y6 c9 i7 S" W" }# \: o
来个类,使用@Value来使用配置文件中的信息 5 u3 U0 j& M8 e- Z 1 w1 d4 Y* V: z3 ? Ipackage com.javacode2018.lesson002.demo18.test1; ! g4 F" {% U& k$ F 2 }2 `7 c3 w2 G+ \import org.springframework.beans.factory.annotation.Value;/ h9 t$ A+ J6 q4 w( [" w
import org.springframework.stereotype.Component;3 J9 }( Q% z' ]7 |2 L
; g; Q8 y% P! t1 M% E- F6 {" w@Component- W: L* H r- q+ m1 R* D5 v& h2 f
public class DbConfig {2 s, k/ D1 N$ N/ L
2 w% n: }; e, @
@Value("${jdbc.url}")% ]+ k% }7 X2 n/ J* z
private String url; 0 R5 i) |8 p6 b/ S6 X. G* p" L5 G) L
@Value("${jdbc.username}") * ?3 a3 F$ a' e5 C% r! D9 m private String username; % H5 ~" N5 i2 ?1 W! c8 { 0 d4 P9 M) A! x0 E. R* u @Value("${jdbc.password}")6 u0 C; u* g+ L7 N+ n1 c, V# L
private String password; + h+ L: d0 S1 S' p# m; D/ U& r& `9 N8 s2 [, Y
public String getUrl() { , ?! v- W, X' [5 Y1 j8 x return url; 1 B1 J* m- _# T. B4 h5 n } Q9 C& K% z: E2 @7 p; t
: V2 ?4 Y, i2 K* j public void setUrl(String url) { % u O- s7 j: ~( D3 _8 y this.url = url;8 V" _% |2 N& |& e& z
}. z u% ?7 @+ }1 _
! D% \. t3 t1 X4 U; v+ c public String getUsername() {8 K1 S" `, ~: ?
return username;" k# h: P; J) P. L/ A$ i3 v
}) N+ v: s9 u/ n2 w
5 R! n, F: [5 ]. ?9 e
public void setUsername(String username) {& @# s( |; {* s w( ~4 U
this.username = username; & y+ w1 L% f! G4 E }& K8 f9 a, |2 N+ P i7 D
9 C4 b5 m" q$ {0 q7 ~ public String getPassword() {, v- F+ ^8 o, \4 c
return password;: z7 H9 e/ _ {
}1 ^( b: K8 q3 U- @
! E+ T# K' H+ r; M1 n% h0 p9 _
public void setPassword(String password) {# f& C8 T: a2 y
this.password = password; 0 Z* K1 y: R* [0 g }: Z$ b, z. X; }& D2 ]
8 K9 p L6 O) N0 U. g3 {
@Override % H. h$ r% d- c. Z public String toString() { 4 k( x s, \! u6 g return "DbConfig{" + 3 q* ?0 D2 T" e% k1 D "url='" + url + '\'' + 5 J) i5 o5 M' l# {+ h% A+ o1 @& S ", username='" + username + '\'' + . q; b- ^* }' V5 d% B8 w3 ` ", password='" + password + '\'' + 6 d) {8 w: K, \( H& E* ] '}';9 B7 W8 P* n9 r$ ^
}! g0 U6 { X1 c8 q& p
} 3 f) }: Z+ z+ U5 K9 |2 M: S% j+ r上面重点在于注解@Value注解,注意@Value注解中的3 W3 V/ ^7 N3 Q8 n4 ~
1 ?- S6 H5 v9 H9 Q
来个测试用例 m' y4 x5 D' A, A4 P
$ `5 z1 t( c4 c1 x7 N$ N* w
package com.javacode2018.lesson002.demo18;; p* C/ F5 f6 W7 z
. W1 z/ k: r2 C4 bimport com.javacode2018.lesson002.demo18.test1.DbConfig; $ L" r4 {* N- n$ v$ l) ?' F# \& aimport com.javacode2018.lesson002.demo18.test1.MainConfig1;& u S+ k0 m# C3 T: [
import org.junit.Test;! g' |9 F+ A# b0 g% R' P
import org.springframework.context.annotation.AnnotationConfigApplicationContext;3 L- a+ m+ i8 U. ]8 y
' c6 p9 s8 u& s8 z) A% Z
public class ValueTest { : o1 k G4 l/ N% W t- j + [/ a3 ~: g3 i% S( ~ @Test f8 A) ]* \5 V6 k2 E
public void test1() { . [( i4 K5 h- [3 F5 e+ Z! _7 @ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); ' Z4 c, L/ S( O% } X context.register(MainConfig1.class); 7 q2 [# m1 t& s context.refresh(); 7 W" @! w& Z9 K7 e. Z) K$ |' s5 H: A( }3 Y. m3 }
DbConfig dbConfig = context.getBean(DbConfig.class); 0 M7 C8 a7 E0 [, ~* j r System.out.println(dbConfig); M$ O( q- ?0 U* V }( \& h' C, q6 m) S7 I# p
} 2 q i' T4 V/ g运行输出 2 e/ H" Q' Y; U0 v2 W) ~( l; S& l1 L" t9 G# ^2 @1 y7 w
DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'} # E$ ?: X! j' }7 O1 r, i上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。4 A* b4 U1 Y& O, U2 [9 X7 E
" r% E& g. \8 G4 ?@Value数据来源 - ]0 t. A" f/ l# x N/ z" d 5 Q7 p" P. g/ v通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。 ; v! ]* p" g3 c3 a0 b4 _- R) K' t) W: @0 J
我们需要先了解一下@Value中数据来源于spring的什么地方。 9 U; x3 r' d! b0 J+ J& ]. x& N$ Q0 L( g$ X% b2 T# M9 G7 S
spring中有个类! {+ Q* Y& ?7 K- l$ t( |6 y4 a/ M
9 d' _9 j5 _; dorg.springframework.core.env.PropertySource# G Z* h6 g+ h
可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息 ( ?' L$ Z0 c8 p% D" |% s) G8 ~9 `1 m, o! f2 r7 E$ n
内部有个方法: ' ~5 V3 T/ x# ?- h* ]1 _! S/ I: E, m l$ p
public abstract Object getProperty(String name); : Z/ V* _ z# M3 l3 G% J# m8 _通过name获取对应的配置信息。 ( ~6 v. }5 B7 Y0 z. v, U. A! R% h7 {* L- X6 |5 E7 j
系统有个比较重要的接口% N1 f, e8 h* y4 V
- ]/ {7 `- o0 U- U( v9 [" Y1 X
org.springframework.core.env.Environment7 \$ i6 h8 j: b- ?( X
用来表示环境配置信息,这个接口有几个方法比较重要% F8 J8 s; V! m* I" K S
+ [. \0 k! R1 D4 R4 TString resolvePlaceholders(String text);' W& l, S- F& V( ]8 d; G
MutablePropertySources getPropertySources();2 U8 m1 Y. y2 _; k1 T/ L) O5 i
resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。 5 k2 Z, e8 j7 @+ \0 ] V5 y1 A 4 D e9 U. g* N) r& Q7 dgetPropertySources返回MutablePropertySources对象,来看一下这个类% U5 ]% |) ]6 p! G
: X( _' t' D8 Opublic class MutablePropertySources implements PropertySources {' l x- k6 j8 t+ y) u( F
7 M" F% R) i5 p3 [ private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();% o& h* O$ v9 M$ p8 W- f1 m) N
7 p% v' l5 D+ |/ d
} @5 _8 P9 D( b4 W$ N1 p# h内部包含一个propertySourceList列表。; \. ?( k* E, y9 @5 N0 |: x
9 L/ M: z0 z; r/ ]) S' |) rspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。; G9 ]9 v( s& u
! L& r) U2 q' J% Kimport java.lang.annotation.*;8 @3 ?: J* z9 G! B, D( N# j/ J
1 V, Q" y; b2 |0 C2 h
@Target({ElementType.TYPE, ElementType.METHOD}) 2 H& z$ q6 V# o+ o@Retention(RetentionPolicy.RUNTIME) 5 f) W: K6 M8 y. V@Documented; I+ y. J- e( x% l( l0 q4 u
@Scope(BeanMyScope.SCOPE_MY) //@1* ~# e) V [- U
public @interface MyScope { " T' a9 X1 K; I) g /**1 B# N9 O8 B$ y( i+ J' w8 F
* @see Scope#proxyMode()3 @# M" G1 G8 v5 m' h
*/ 4 g* V) F7 [1 ?/ B1 B: f% z+ W, O ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2- Y. V3 g* Z$ ^1 x! r5 R8 R; b
}6 d4 b1 J) ?5 K3 Q
@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。 * ]! i A% o# n# Q+ f! [# _! Z9 h+ v7 }7 ?. G1 u8 }0 O
@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS " G( i7 B& ], h+ ], w/ ? : M) G/ i5 n9 t6 ]@MyScope注解对应的Scope实现如下, n) i p6 K! W# d" \
) J+ v* @# ^+ S9 N4 c. m. L* e
package com.javacode2018.lesson002.demo18.test3;1 v: v4 W" X# I* l- Z/ q5 Y: _$ G
0 M2 U, ~3 a0 p) ]" f3 f" H1 w
import org.springframework.beans.factory.ObjectFactory; . `; t9 |% z* I! Vimport org.springframework.beans.factory.config.Scope; 6 n& o" k+ I5 j* Mimport org.springframework.lang.Nullable;# r% m7 K0 J# M
- |/ {; B3 |% t. Z/**# n1 `8 M2 @* v! _" t
* @see MyScope 作用域的实现" S6 u# N3 A h" O* l& z k
*/! J/ ]2 a' P5 h0 I4 N$ K
public class BeanMyScope implements Scope {& v/ X/ M, T+ Z5 A
. y8 h, i3 o; J# I4 U public static final String SCOPE_MY = "my"; //@1/ }; f, a6 P; }7 i
. s/ p# P! ^6 d @Override 5 g; d0 e+ I6 ?* M% I2 i public Object get(String name, ObjectFactory<?> objectFactory) { ! ?2 `) w* |$ d2 j G, B System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2 ! _' N3 X$ W4 V2 b* j( A return objectFactory.getObject(); //@3 : x" K" y: u" G: L8 L3 Q/ i } / R. ~# W, H% ]( s5 n* X/ `# d( X5 @6 Q& h2 i& G
@Nullable 5 N) B0 g& h7 g/ w. j @Override. a* A# ~ K% M0 v* p9 ~- c
public Object remove(String name) { * m2 o0 a9 }, n$ j return null;! f7 J) E' B# B. ]# Y2 C
}) m! B2 ~% N4 @
* n% O, k+ Y8 }' U, S8 Q, E. `
@Override 3 \" Y0 X) R9 p, S8 \ public void registerDestructionCallback(String name, Runnable callback) {+ a) M. R+ x& H* K
9 Z' M6 d7 E" [, h+ V6 i \
} 6 G" K9 y. u$ J' M# i ( ?2 H8 S9 z" f) j0 n @Nullable& A# A5 X! c9 B
@Override! c- t( L& ~" S; s6 `' M: e$ t
public Object resolveContextualObject(String key) { * d* W/ q! A) o# D2 K/ w1 J return null;( |2 |* P8 ?; s" n- b" _
}" Y# E+ m& f- Y% \
3 `# n- w6 H- w; b8 r8 ` @Nullable ( d/ W$ F- f5 J# B" d0 ? @Override6 Y9 E8 D9 g/ @2 Z
public String getConversationId() { " j) Z$ A5 x% M return null;0 A5 v! H. y; ^
} & u2 j$ K. S* S) f& t} " o7 i" S- J- v3 \7 I# o@1:定义了一个常量,作为作用域的值- `- z1 F4 h- U5 x" d