9 {) c, o6 L0 O9 X还有其他的一些配置信息。. A0 X; T1 \) l
8 v& d7 Y! ?( V5 B& K, e0 ^) s- X
我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。 - ]6 [1 ~- C+ Y' L0 q & G, J7 k5 m# z" B那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。 ' c, J& L. [ i6 y6 h2 O+ f6 x" R" m6 L A! b
通常我们会将配置信息以key=value的形式存储在properties配置文件中。 ; E/ k4 f5 J, g* `4 I4 h* w& ?" `, F: |$ ?8 w
通过@Value("${配置文件中的key}")来引用指定的key对应的value。 + g$ b/ {3 g C$ ?) W# m / T# I- ~8 y# [" `* |@Value使用步骤% T) E c7 g9 o& m% d% R6 z
h. r+ ?# m) O4 C: C0 l步骤一:使用@PropertySource注解引入配置文件 2 U2 @8 s" \2 w4 C$ |" [2 ?( V) W8 I1 d' K6 P" `+ ~9 V
将@PropertySource放在类上面,如下 5 C h5 V, ~+ A; I" y3 o$ t! l 0 h/ |1 | c6 |@PropertySource({"配置文件路径1","配置文件路径2"...})2 w5 L: `: M+ C% J( x
@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。: t* w* P! L! H& t
4 Y: G B' H9 r D+ e. g% h
如:' g* z H& K J
3 v. P" K3 l- W( Z@Component 3 P9 {1 {# B8 N: ]- D# J@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}) / d. U3 l# z) j: q Hpublic class DbConfig { 4 r0 l' E- C6 \+ b% t0 p}# b! Q( S2 K) r' ~
步骤二:使用@Value注解引用配置文件的值 R1 _6 d ]- c( u! S! C
4 M1 H( v7 _$ u1 g! F) N0 l
通过@Value引用上面配置文件中的值: ) I! b7 B" H3 r6 W2 P0 ^! R ]5 `+ u& [! P+ m- R7 R$ V
语法 & W' y9 r/ q' A8 A2 y; H' i: x' h& k! @- p. [' b( g, t
@Value("${配置文件中的key:默认值}")1 }+ o% u2 z4 w. _
@Value("${配置文件中的key}") ' q Z, s# i4 P5 P9 b如:2 |! d9 M$ q. P, M/ c# p2 ]9 f
8 N6 d5 [& z: p/ P4 b
@Value("${password:123}")" K5 y0 y6 d# i7 a$ N% Z
上面如果password不存在,将123作为值 : Q/ [ I" j- _( Y9 B; v ?' {" s: |+ S4 C; c8 F7 Q@Value("${password}") 2 z/ V7 b+ h* m2 R上面如果password不存在,值为${password} 5 b3 l9 U& @' `# `6 O. N2 y2 o, E) ]! X" e, g, I' r2 E
假如配置文件如下 . {7 ^3 V; t, j7 d7 y5 w* k7 A! [$ V
jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8% M5 e9 F, x5 [/ H" | x
jdbc.username=javacode/ @5 P% T& h/ m2 h; v6 k; F" i
jdbc.password=javacode3 X; ~, E: m8 \: j; C+ E+ X U
使用方式如下:7 i" I# j% k9 V- t- O0 ]
! P5 ?* o x* @/ F& ]: }@Value("${jdbc.url}")( w: Y: V. m$ Z: [
private String url; ' a( y* a' T, @( } g; t I) N9 h! p) E3 w
@Value("${jdbc.username}") 8 ] h8 p: L1 {7 X! q& K: eprivate String username; 6 J# W L$ j4 D" J ! [9 @4 ` u7 y0 W, [( T7 T& _@Value("${jdbc.password}")" l2 Q4 z" }9 g# t
private String password;# e& D+ \ O( c( r& B1 F
下面来看案例 7 q* x7 Z3 G0 J2 a # Y5 N& x' _9 }6 S8 N案例 + o$ o% P5 r4 _0 W % o3 X: `8 C3 j! u& S来个配置文件db.properties 1 O2 f% E* o% C6 Y" j/ U % K' e) I8 R0 J. {jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8 0 \0 T Q' t' zjdbc.username=javacode5 s6 I$ P3 Z% F7 ^9 l' J
jdbc.password=javacode * f& O; u( f. p6 R# p来个配置类,使用@PropertySource引入上面的配置文件 ; P7 x6 L' g( }: h' z# U- s8 V/ A+ W3 X7 c1 P' A1 Q
package com.javacode2018.lesson002.demo18.test1; ( h5 N+ V' Z/ N& d . }" _' E# p. O2 I" j, Pimport org.springframework.beans.factory.annotation.Configurable;+ z+ W0 J, d* i3 i
import org.springframework.context.annotation.ComponentScan;$ n5 b0 ^$ C) m- J5 ?6 V
import org.springframework.context.annotation.PropertySource;& c9 e1 ^( L2 E+ d( K) |
7 J, {. M& k+ D9 W$ M, i
@Configurable, P( v: p6 ^% _* S& O: B1 y( `
@ComponentScan# l5 M7 z% L$ b4 ]: i: U9 u
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})4 s7 \" b! M9 A2 R( Y1 y- S
public class MainConfig1 { ! P$ d: k# {: M" U}3 F' X* o3 h$ J, O; v1 g1 l
来个类,使用@Value来使用配置文件中的信息 ; L: \6 ~/ U- b$ R3 V+ f3 r( w3 C1 k e+ s0 q; w
package com.javacode2018.lesson002.demo18.test1;, N$ v* [6 \" T3 f# S3 U- q! h
3 }% Z. D2 G# C$ o7 Yimport org.springframework.beans.factory.annotation.Value;" C! K6 Y+ d! y6 B0 D! y
import org.springframework.stereotype.Component;) t. y1 L4 g$ e( i
( M: x6 G* |2 ]% V. P# T@Component s8 ~8 X1 p, d2 u
public class DbConfig { 9 a2 \: o. c/ k" _0 o8 W% a$ R, R o1 M3 D! S- H9 f) C8 I$ x
@Value("${jdbc.url}")% N5 K$ g9 }# g, [6 n/ d3 R# Y
private String url; ' ~/ h2 v/ B2 s a4 ~5 R: |$ \ & Q/ p) M- V+ z* j* {* w0 O @Value("${jdbc.username}")' ]9 Z/ ~, f0 O
private String username;) e7 Y+ m, h' x8 r8 W7 m
. h2 C# _; Q/ t& q8 m+ {
@Value("${jdbc.password}") + C% u, Y# u6 K @ private String password;! b# R w; ~% b* e: J) o. a% |. n# B
: {2 t. J N' }; y" g- k$ o public String getUrl() {! I9 B5 E$ z. D. `( P/ \- _
return url;0 h1 W# @8 T: ^2 i4 V
} / a) N/ M$ i8 S$ b: K. d( T M8 m! y/ x& z8 L. A
public void setUrl(String url) { 1 A0 w0 D) I9 ^9 Q; D* c/ ]3 } this.url = url;1 v6 l- P$ d: h3 L: _
} : E B& |6 p3 |2 C2 `4 m1 g3 I) Z1 E; b
public String getUsername() { " X+ r( q) E3 q return username; 0 p5 n; j' {$ {! { } . [( Y8 D( b$ O1 ~; d8 L1 `! S9 y$ b Z( h7 f' i
public void setUsername(String username) {. Z- q# r7 l# ^
this.username = username; 4 ~ j- K P$ ]4 m* h } 9 i2 g% `% F% }* R& ` : }; q8 w7 j+ b' w" \& g/ q R public String getPassword() { 5 A0 r' i' O% Q; W! z* `1 o4 e return password; , M& o( j" S; }" k. {& y } Z$ Z% i; M- p. P4 G0 ]
! O! k. w5 A* O6 f2 u public void setPassword(String password) { 8 y- s G1 N0 U5 |* F2 V this.password = password;6 u8 P; U7 D$ |& ]( S" d
} 9 V$ U7 T" ]6 d `% q/ K . m) I* J2 D4 x$ r @Override 3 P o2 D9 _$ `$ d& x public String toString() {5 P' K! m: j, i' {+ y9 S% ]
return "DbConfig{" +- Q u P1 l$ U$ ?7 p* k
"url='" + url + '\'' +; B& l! M7 ~5 M ~( G: a8 {
", username='" + username + '\'' ++ ~/ Y* U6 D) E/ a" M1 f
", password='" + password + '\'' +, d$ [( n0 I+ s0 X! y
'}'; , ]2 R: f5 j: f, K' i8 ]- X }8 N- |! J0 Y4 o8 F4 A8 n
} 3 k2 Q! n/ i. w8 s) D- P: n: O& T上面重点在于注解@Value注解,注意@Value注解中的 x8 A0 F3 P! i 7 P% k( i6 c7 w% N: P, _. Y来个测试用例 U. D, F6 _% C6 L1 R0 [
) d# Q/ m: K0 ]package com.javacode2018.lesson002.demo18; 6 I) J5 F1 M) [" ~7 f! ~$ x 3 E1 L, @ z& r, _; Y+ |7 qimport com.javacode2018.lesson002.demo18.test1.DbConfig; . [$ T3 D: H1 Oimport com.javacode2018.lesson002.demo18.test1.MainConfig1; $ ?! z' t7 h( a/ a* S4 T* Qimport org.junit.Test; 1 f6 ?0 s0 y3 N- mimport org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 x# D. j3 b5 l : B% _* V$ r* s/ y& F F5 e4 C* gpublic class ValueTest {/ Q) U" G7 \- T. |$ l7 u
. n. `% Z& v+ q @Test% ]: ?( c+ Y3 H4 P
public void test1() {0 O9 Z$ c7 A; V
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. U- k2 |7 v% U
context.register(MainConfig1.class);9 g/ J a6 A0 N
context.refresh();6 g6 z0 j% J! p" n% W
& O! T" `0 P0 }! H9 D DbConfig dbConfig = context.getBean(DbConfig.class);5 {" e5 B: ^" R0 I' c8 b+ ~
System.out.println(dbConfig);- ~' ?4 r: m: E7 u4 c3 w. X _
} 2 q5 Z: s. K4 x} 5 _& X: Z8 w) Y# u9 ^8 z运行输出& s, \5 k. K) J5 V5 r* ]/ {& c% h6 F
5 @) [. v$ N' p. bDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}# x' `) I& w' a; P9 m+ O
上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。 & V1 c$ ]8 P- J, ^- g0 y* z ( ]6 h8 L# w8 F- P H@Value数据来源 ) c* ~4 y* ~% H1 \9 j ' U2 ^( {, `# \2 k( R7 h通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。 ' [8 j. |9 k5 h; U/ P* O5 j5 `) e' Y% G6 s G% T- u0 X# C# r, n1 k
我们需要先了解一下@Value中数据来源于spring的什么地方。% x# O, h' |0 J7 l
' K& j3 Q6 s# P7 f
spring中有个类3 _; W$ K$ ?0 I2 C
. ^$ B) W) T q1 s
org.springframework.core.env.PropertySource! S1 G9 t( Z" a" @$ l: E
可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息 ) F, O5 _; l% |" `" x1 e. g' Z k/ @
内部有个方法:. T# x% S6 O# v
, h: }9 s0 S) l. ?1 z6 xpublic abstract Object getProperty(String name);1 E( p/ ?8 r. m: I7 r/ x
通过name获取对应的配置信息。 2 A" g: Q- X+ k6 L/ D 6 K( {9 S K; S4 }$ j2 @. k系统有个比较重要的接口 `; Q2 M6 f6 \" d# w
) z$ o2 L/ `5 c- @& I
org.springframework.core.env.Environment$ r( f+ J' U) r' t% I/ |
用来表示环境配置信息,这个接口有几个方法比较重要 3 U! f/ l; h5 E! [8 h $ k9 S3 g! h: @7 A t6 I. H1 nString resolvePlaceholders(String text);) d$ v+ J- k$ R* _6 C
MutablePropertySources getPropertySources();0 |8 e; Y) K% o; x
resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。$ N. N7 S/ u5 Z+ t* _: b2 x
2 e A3 m3 X5 S" V5 E `$ \7 N- p3 `getPropertySources返回MutablePropertySources对象,来看一下这个类( D# x+ h, ~# `7 @# U
s5 y! v6 { P! B( opublic class MutablePropertySources implements PropertySources { 0 K- }- b7 }! m8 i. r6 S ) J. B( N1 [3 e4 |, S: o/ } private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); ! V; h2 V5 q+ g" H s$ ]2 g: F5 j- V/ s# [) u7 t# h `
} E1 U, L4 ], M2 f
内部包含一个propertySourceList列表。 & f6 I+ j0 E' e/ M$ K9 j; d8 d1 V- S& \0 e6 c0 S" B$ v L3 [
spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。 7 v" Z' D3 T4 ?- G: ]' y2 Y3 k$ `3 I# G X# W% w. l" @ K6 z
大家可以捋一下,最终解析@Value的过程:$ @* `& _5 Y; h- C
5 i* v- L2 D+ ~& W1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析 ; ~- S5 N4 g% I2 H+ ?9 P2. Environment内部会访问MutablePropertySources来解析 7 B' _% S0 J% u3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值$ ^) u& ]/ }" \' W- X
通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。8 u# o) [9 a4 B o' L
5 L2 b. V% D' n- H
下面我们就按照这个思路来一个。 8 ~0 j8 |$ }. T( j) d, W . w4 e' y0 u1 ~& q来个邮件配置信息类,内部使用@Value注入邮件配置信息5 n3 l7 Y3 x4 |
, m. ~5 u w' d& Z- }; r7 J8 g% lpackage com.javacode2018.lesson002.demo18.test2;4 N' p- P& U7 C7 _
, ~$ w; T8 o* b1 A********# Y* @3 x% t- W O
第1次开始调用getUsername6 w6 v7 q! j9 ]4 q
BeanMyScope >>>>>>>>> get:scopedTarget.user " _7 h+ s$ e6 Z Y6 _---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4 o) d+ f2 c' b' |4 z3 Q- o
7b41aa80-7569-4072-9d40-ec9bfb92f438% D# L) }1 S; }
第1次调用getUsername结束: N3 _. v8 ]# z( \3 A" v8 s
******** ; a2 B* i1 i: h1 A3 u 0 Q; i' H7 _( J f" w/ V******** - y* e( |8 r0 i! T' b5 x1 }& P3 w$ F第2次开始调用getUsername. l) n2 p0 w# V6 f, o1 x0 p+ g% i
BeanMyScope >>>>>>>>> get:scopedTarget.user ; M; r4 m2 ]! {0 ]% k---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b 2 d4 c! W9 ], e2 G4 A- a5 E01d67154-95f6-44bb-93ab-05a34abdf51f, V$ U3 @7 u9 V) t7 e3 i m
第2次调用getUsername结束" @* H+ ~7 a4 e1 m% `8 m1 I" |
******** - L* K2 m' }+ V1 J% x! k }7 J5 C8 d% r: o' m8 [' E
********- ?; R) p" G/ q% X* n' c
第3次开始调用getUsername ) h$ @/ ]" O0 X" T* eBeanMyScope >>>>>>>>> get:scopedTarget.user / [8 A5 R# [' x: t( g% M* b8 p* f---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15/ P S' ?+ P/ q
76d0e86f-8331-4303-aac7-4acce0b258b8 1 a1 Z* f" o3 y% h第3次调用getUsername结束3 C' R g& D8 { j1 O
********& n6 l& V( k! i# V% @7 _
从输出的前2行可以看出:+ A" ] e S( F: T* h0 x* W+ |5 m
9 c) u+ w" M3 ]* b8 [5 T
调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象2 H# v5 F+ C: F. [
% Z* g7 ^2 Y& n# O' g, D; S+ o第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。# ~: i/ i E7 w; x3 O6 ?: {) k
' w" G% r7 E% X$ O) f( Q
后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。 5 C3 u; O! T9 @/ [; u8 N + N3 b2 D8 j1 x' w通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。 ' o5 |% l9 ^5 R$ X o( j8 p9 w ; K" M `, r! p: {4 g动态刷新@Value具体实现9 S0 \" _4 R; N$ f
4 J& D% m( p* }+ r: }" @4 v
那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。 v. J% Y+ B; a8 } s( T 4 R H! s) X- p# j' d先来自定义一个Scope:RefreshScope* q) u" w9 \2 x2 P9 L
/ v; Q; Y: m# b# t3 L/ E; ?7 U" W
package com.javacode2018.lesson002.demo18.test4; ) q' w3 g( B/ V( _5 P9 D3 e : E5 D. C" B1 B x: u9 ~8 oimport org.springframework.context.annotation.Scope; 6 x, e) e2 u8 ^import org.springframework.context.annotation.ScopedProxyMode; ! j7 F1 u* x* M( m% p v ' }( C5 i2 k8 ?6 ximport java.lang.annotation.*;0 V( o$ [ ]& w' |& o4 Q z. T
4 ], n' T8 u5 I! ]6 t' C: z
@Target({ElementType.TYPE, ElementType.METHOD})5 D$ O- L1 X8 T5 n- H4 j+ S/ F
@Retention(RetentionPolicy.RUNTIME)- c' }- u7 {# P0 ~- _( L4 ]* R$ d& l
@Scope(BeanRefreshScope.SCOPE_REFRESH) 7 D- j W% G( \$ l' J( L' S@Documented 2 |% P# n9 X/ W- O6 Zpublic @interface RefreshScope { $ n8 u! _ T$ z9 O& W& ?3 D+ U" o# H$ q ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1 % B- t' N/ _! B3 D: I2 m}& m2 X( u1 L# Q1 E
要求标注@RefreshScope注解的类支持动态刷新@Value的配置 * J1 E/ z. ^1 q! F' p. W. C n D: r# |. r" j- n0 F
@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS ( m0 o% Z: B5 [* q # A% _* W. [ N1 w1 N这个自定义Scope对应的解析类 ! H0 y" p* Y, u$ O( T, F- Z2 o; z; e+ a: F. r3 C: g8 W: e! c
下面类中有几个无关的方法去掉了,可以忽略 ; U* ?4 W ?% I; P2 c* E& u9 q* M5 p, ^' ~) u' r6 c: b1 y6 o' ]
package com.javacode2018.lesson002.demo18.test4;7 b& X* Y/ x4 ]1 e! b$ x" Q0 i
! }$ t3 o4 T3 X; \3 L. T+ U& F b% U8 w M: T# N @
import org.springframework.beans.factory.ObjectFactory; * B1 q* P( I. |8 F6 }5 aimport org.springframework.beans.factory.config.Scope; ( k5 l, s1 M$ Dimport org.springframework.lang.Nullable;4 P" Z9 I( z. E8 H$ t* s
X* K3 I; H' s5 A O
import java.util.concurrent.ConcurrentHashMap;! N O, H$ z) v- U% `1 i
( ?. K/ k( s) P. J) T9 Y; }public class BeanRefreshScope implements Scope {& A. `# E. B5 X
4 A6 T' }# S+ ^ public static final String SCOPE_REFRESH = "refresh";1 K, h- e2 A, {. F, B# \
|! @8 ^$ P, u# @* Y V- y
private static final BeanRefreshScope INSTANCE = new BeanRefreshScope(); % X) l- _. i2 ]$ G/ I _, O: X* h3 I. H) K; h/ Z, g //来个map用来缓存bean* M3 C& B9 [: b/ @# g" n4 p. I" G
private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1 ! t' e$ h2 X/ f7 }9 J 0 H2 i9 z- n! E6 O8 F: A% j( }( ~ private BeanRefreshScope() {' `% }& K, ?% ~6 y% Y/ Q) R3 Y
}8 _+ B+ H, R: \ v
& s' n2 J$ G6 p9 [$ t' \ public static BeanRefreshScope getInstance() { . I+ s, F- Y% W4 Y" i1 S return INSTANCE; : X) ]% T+ o; h2 I4 T0 H9 U }8 L" R& S" q/ ^( O, x5 a
2 _: M6 ~6 q" I& E! i /** ' Y9 I: r) P5 D# N. t# p2 V * 清理当前 1 x5 P# o* t3 W" S */7 T/ ~& g( c! X+ A) y [1 k8 H
public static void clean() {& L- u( a& b' q: E% `, j7 q
INSTANCE.beanMap.clear(); % m% ?! K2 e+ D6 o* v- t, P _6 B } ; e" f4 {2 c& c+ \, a0 X* E% D* n" w W. T/ t: V+ H, R+ P+ l
@Override9 I8 ~7 o5 O( i6 ?, {6 Z
public Object get(String name, ObjectFactory<?> objectFactory) { 5 H8 n9 o7 t, B- U6 e4 V Object bean = beanMap.get(name); 0 v8 V6 h1 m! z h2 s+ z7 H if (bean == null) { 8 c. k b# A. f% ?& D" ?, y5 r7 b1 w bean = objectFactory.getObject();; R& l8 R4 s5 G% l: \
beanMap.put(name, bean);4 g5 [+ k9 B9 W; |
} 4 ?" F0 A1 e6 {) f( ]' n) f% c return bean; / H( } S: d- R$ ] } 7 @& Z7 K2 p5 V# V9 k 0 t6 O: \( R, N' i9 e) J} $ C0 x7 [# Q ]' m0 y上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中 : K+ Z3 _! @8 E. k; q$ V2 N/ L* h( r+ ^) }6 D: c( q. s
上面的clean方法用来清理beanMap中当前已缓存的所有bean ) O. [# V; L3 S+ y( j' W8 m& i7 d4 G
来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope - K8 c3 F [0 q0 L/ P5 m3 A ; O& h% w. K' p4 ?9 Jpackage com.javacode2018.lesson002.demo18.test4;5 b3 p3 y, k' g6 `
1 K* t9 H2 E: [
import org.springframework.beans.factory.annotation.Value; % E- E$ s: y3 G: W2 ~3 m' g% gimport org.springframework.stereotype.Component;% O9 @0 t; v4 Z) y1 z
/ Z$ Q$ V# N: L) r/**6 r/ g4 A+ ?; j: \2 n
* 邮件配置信息* b$ K" L3 X' v$ U
*/+ S! N+ d! w' b6 n3 R# F4 V# H: T3 o
@Component / \0 C' `8 C9 r& d$ p$ e@RefreshScope //@1 & l0 Q, r3 g2 S# N0 xpublic class MailConfig {6 K; y; ]* N* W- }
2 ^5 u6 m5 [ e5 }6 m* i9 g8 W$ v
@Value("${mail.username}") //@2& F/ f% y7 W! Z+ P3 @1 R
private String username; $ i2 Z/ X; ?3 @# q# f# c) U* [ 0 I) K/ F9 \9 _ public String getUsername() { ! u7 n6 X, r8 O' F7 L+ ^ return username;- o% L2 ?& X* Z6 r
} + @0 j' C2 j- n' ?" P! v5 }1 X% }. B
public void setUsername(String username) { ( a4 b8 u. Z9 D/ ]/ y& ?& w" T this.username = username;6 s4 Q- u# R8 s# E% Y
} 0 [& j: A3 K! D2 Y, t' I; M4 K, ~! n- z8 T# \$ [3 J' {, p
@Override 2 R0 r/ k( {! ~( [: ~0 z public String toString() { % M, S; W- h0 \ return "MailConfig{" + ; ?* Y) I7 G! D7 d! j "username='" + username + '\'' + 9 ]% k% g8 {; D3 ~ '}';6 L# D+ ~' w% P1 o) k% M+ G4 B x
} $ g8 I. F, s7 e} 6 H- X: \- q8 s/ q4 a@1:使用了自定义的作用域@RefreshScope, O" Z' I: d. u+ n! p/ M
2 t( V& B- [- `/ Z
@2:通过@Value注入mail.username对一个的值 ; M( t( R4 r* t9 H6 O6 m6 H( F . q+ }- c* w$ z! Z/ y) ^重写了toString方法,一会测试时候可以看效果。 O5 w. W/ X& T- z8 P) _: G6 c1 g7 a+ H1 V) L3 H
再来个普通的bean,内部会注入MailConfig $ x; `3 F- A% a& k $ A8 W8 ^# {4 N+ [6 u$ s/ Gpackage com.javacode2018.lesson002.demo18.test4;5 z: |+ E& f* B: G2 K: }/ I7 Q
6 y) w8 o- @8 H, m9 V" B
import org.springframework.beans.factory.annotation.Autowired; % f/ X ?4 E$ g) G$ e0 C; Oimport org.springframework.stereotype.Component; % `. M1 e6 b7 h) \6 T* b 0 j* C- N; G) w/ r7 A3 M6 a. E@Component - f, v+ @- k! Q( Ypublic class MailService { 0 U& G% P# o* ` @Autowired/ p8 ?- Y9 \; o* U! O0 z! w
private MailConfig mailConfig;6 W! P' a) s6 `/ ?. e) i! p. D
5 c0 I" c1 F8 C, n
@Override9 F% {3 P5 R& t
public String toString() { ! m7 D2 \3 J& k, R% H7 j% |$ o! I) H return "MailService{" + & D0 Y( l, B6 i+ H( r% z "mailConfig=" + mailConfig +9 M- ^7 v6 ~" Z& B6 P0 r
'}'; 8 X) G' F3 a5 x1 h. M6 K }0 m: D3 j& B% R4 h, _2 ]4 w
} , j7 V1 p) q" x/ E8 C2 T0 d, V代码比较简单,重写了toString方法,一会测试时候可以看效果。 - m0 T8 S+ {/ v% c8 F6 N! L ( g" Z5 ]# K7 D- o( y3 ~来个类,用来从db中获取邮件配置信息# ]' J; @ i8 |* g: e% U% ]- ?
7 y% N; T. N) n# b( i
package com.javacode2018.lesson002.demo18.test4; 0 r& }3 `; o7 j" o- ]9 F3 |8 }2 X, z5 n1 X- ] `
import java.util.HashMap; 0 W+ m4 Q1 p: Z! i9 m* k' S) aimport java.util.Map; , |6 n7 y5 v$ b9 Yimport java.util.UUID; ! D3 s1 I+ u% [( o1 Y' C8 o: X, p4 s3 g0 o) Q4 h- k7 [
public class DbUtil {# E4 P5 D# Y9 t' m
/** ' V. V! P! z: B, B * 模拟从db中获取邮件配置信息 ' o! h7 ]7 F6 Y5 c! Z. y$ r1 W( y * % {- A- v6 m1 r. z; n- M# k4 b; A1 h * @return8 e" R! [& [ d$ A* J
*/. j0 J2 o }) ^6 q. a
public static Map<String, Object> getMailInfoFromDb() { ' m @- @, f" A5 B Map<String, Object> result = new HashMap<>();6 p/ d d% G' Q; r6 s( g
result.put("mail.username", UUID.randomUUID().toString()); - {& V+ p, O( @3 c; M! s! [- V2 \+ u; X return result;4 Z, C4 g1 R, Z6 B3 M) o
} / @# A( ^' s8 I4 h1 h/ `& F3 i} : }; y0 c& S3 `" e, c来个spring配置类,扫描加载上面的组件7 ]) y. W$ n2 m& r' a8 w# U! a. Q8 r
7 B5 h' g2 }. Cpackage com.javacode2018.lesson002.demo18.test4;9 F H" g# T5 u7 j+ O s
- k1 ]: o1 ?) J& G9 D, q2 _4 x
import org.springframework.context.annotation.ComponentScan; % N, U6 [" R, C% g2 h7 A2 e" zimport org.springframework.context.annotation.Configuration; 8 `1 S* ~7 |3 l+ K% v6 F/ h1 a& q9 y0 S0 t- W* a( S3 i
@Configuration 8 r! `: B4 {4 W( x) d* Y' Y0 _) c8 R@ComponentScan4 k) k/ K( p' Q7 f
public class MainConfig4 {# w8 t9 Y7 G1 O# G6 r
} 5 L8 ?3 T. T7 L2 \来个工具类8 Z$ \+ k; k" U: v. I9 P+ @( x7 v
+ v8 x3 i( L2 j1 |$ ~0 n( R内部有2个方法,如下: , s) v o; p D4 Y( l 8 V+ v/ q1 O$ c: E, Zpackage com.javacode2018.lesson002.demo18.test4;( Y8 [1 D. U, _/ z) Q: R# V% E# |
& C4 s& b9 O) y# b" C* Gimport org.springframework.context.support.AbstractApplicationContext; 1 n3 N% ]% G8 E- l6 d$ d& Yimport org.springframework.core.env.MapPropertySource;/ f: U8 a1 E4 H( @( H3 l0 O
/ U, M% Q: s9 n w3 z F, w
import java.util.Map;% X5 H0 E: H) K. T. {" ?
! a. }3 |, J% \1 `/ d& {
public class RefreshConfigUtil {7 v1 y6 T4 {3 I$ {( i& H( P
/**3 N l8 `: W4 v* g2 e% D
* 模拟改变数据库中都配置信息 5 q5 V N( j% K- B6 b+ k */ 2 o$ V- A( n- j9 s/ G7 ] public static void updateDbConfig(AbstractApplicationContext context) {9 P/ W7 W! e6 M/ r
//更新context中的mailPropertySource配置信息& i- W1 t1 o% y7 g3 O5 C5 J8 X; n7 u
refreshMailPropertySource(context); / }$ _4 I% W# B/ E) N& ^( j" r% M& Y; j! x3 i/ I E; a% `. f( C% B2 m
//清空BeanRefreshScope中所有bean的缓存 , f2 S9 ?: g& ?5 L BeanRefreshScope.getInstance().clean(); " X- S' H+ j( ]" H2 y8 o( v2 N }) o) n) Z; l8 z0 z0 v' _
- W) M) [& |" ~ public static void refreshMailPropertySource(AbstractApplicationContext context) {8 ?3 A% r& m& u! ]% Y4 M: E" Z
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();8 l1 }4 J3 H: t: ^$ G
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类) * S/ K p* F4 [8 a9 s MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); 5 Y$ E+ l+ s5 l# H context.getEnvironment().getPropertySources().addFirst(mailPropertySource); ; p% l9 ?3 k9 Q1 l5 R1 } }. s, f, _% u8 E. }# B
6 m- C, H* e/ ]: ?6 r% h. Z} . [' T6 A5 r- q3 F4 | [) FupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息: J. ?, U5 R9 L' B, T& m
0 D0 ^: a4 z$ b8 w$ d0 jBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。 \5 j; H0 l; v. q: r
% i) o; I& Z8 m. _' o" T/ Y
来个测试用例 3 |) C0 \, ]8 H( _) C: E% j' C2 a5 z# J
@Test 3 S% I, ]9 p9 v# Rpublic void test4() throws InterruptedException { & j U# Q# g7 a8 l AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();2 g+ H& r* ?" T2 b( I- y0 H
context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance()); " i1 k: U7 w: J; I context.register(MainConfig4.class); 2 ~9 P- W: x% K+ Y D! g //刷新mail的配置到Environment 0 J3 N6 c6 c3 |7 T RefreshConfigUtil.refreshMailPropertySource(context);0 V( n% h- W; |# o% F& U
context.refresh();! S, ~. U4 W: ]5 \( p X o
. a% N- L6 N* k6 z- R3 s- O MailService mailService = context.getBean(MailService.class); " B6 H; @- P7 @* s$ K: i System.out.println("配置未更新的情况下,输出3次"); ' o5 P% X; {4 J for (int i = 0; i < 3; i++) { //@1 : |% Q. v; ~! @* q4 {$ K System.out.println(mailService);. H m- B# V+ e
TimeUnit.MILLISECONDS.sleep(200);$ \) u0 X" H& G/ K
} 5 B. A7 S, J/ P2 q; D% I0 d2 G6 t* Z9 a8 m
System.out.println("模拟3次更新配置效果"); + z1 T4 a* J* S% ^, T for (int i = 0; i < 3; i++) { //@2' y+ x; p, K! m* C; J) q
RefreshConfigUtil.updateDbConfig(context); //@34 D' B" R- H: ]7 I
System.out.println(mailService);: t7 Y# D1 R9 G; p0 @6 D' ]; m
TimeUnit.MILLISECONDS.sleep(200); " W. D& r t) L+ Z' [ }) \! P2 R! B1 ~$ J
} 9 S: i* Q7 V# c. [7 Q@1:循环3次,输出mailService的信息 Z) q) \: D4 R$ i 1 q8 s; T7 U4 x! s& [@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息$ q1 j% y! {- Y
9 t$ [' W) m% x* d+ q; W
见证奇迹的时刻,来看效果 3 _( B6 n" F# q! @' I F& o& t6 R1 C# N
配置未更新的情况下,输出3次3 G" M, l+ C( t
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ U) v7 C& X# Y
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} 5 @5 }) l4 ~7 u0 I+ q+ Y0 l6 k0 KMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}} Q, x1 `) L' j' E5 L R4 I, @ @模拟3次更新配置效果2 r6 L' N% G# t5 y$ r
MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}} 1 r# Z& g$ m. y6 EMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}} " z3 Y6 r5 z. J" y+ [ @MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}} " M" s9 a5 @3 A) Q* n上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。 # _# e0 H6 [: b9 X L ]: g/ a! P4 r
小结; u* D+ |% o1 M5 X) L
& p3 L& G# D. X) [
动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。 3 w; T8 C5 S' N. Z ) x+ H; e$ D. o: C7 b& q$ y8 v( s有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。: F! b$ \+ Y5 _- Y7 ]) k
7 M+ l3 P# v6 H7 `; Y1 f
总结% M& o* G# C- R' Q7 g8 H7 K& N
9 _2 m* N- h C% o; u; o- q6 ~本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流! : _, a* `. L% C4 Q6 h$ Q$ K) F+ Y; ~0 n X. z! [/ d8 s u6 v/ r. q( t
案例源码% J% a- `; d4 F* y" x
. D/ k0 `- G- P! O
https://gitee.com/javacode2018/spring-series. v9 ?! T0 v0 K; n+ ^% M0 a! K$ P
路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。 " K9 w8 [ }. |% I8 | 6 R7 r+ ?3 t+ fSpring系列 8 G8 }6 u: ]8 r9 {% s( P, h ; l0 c: u) L. X8 z# C3 h+ iSpring系列第1篇:为何要学spring? % G$ E+ m" j8 z- ~5 a1 l4 [4 I * B2 k N; @) a( ^4 [/ X& B5 nSpring系列第2篇:控制反转(IoC)与依赖注入(DI) ( Z; t# B7 s, \, r$ x1 ]/ N& r I* N4 W3 U! M+ c6 m4 Q7 r% K5 n
Spring系列第3篇:Spring容器基本使用及原理 1 c; T% e% ~. u+ U. Z5 V, j% m0 t
Spring系列第4篇:xml中bean定义详解(-) , z3 \- F- q4 r, C& f2 ?+ w . E8 ~2 T! }: L. j" _/ ~Spring系列第5篇:创建bean实例这些方式你们都知道? W6 ^5 z: O) |& l( A
/ N0 N4 F" h- Y- Q2 c2 eSpring系列第6篇:玩转bean scope,避免跳坑里! 3 W9 D3 I& R% v + E6 e L, L+ _0 a' \Spring系列第7篇:依赖注入之手动注入 3 \2 |6 V& r1 y4 i) b2 t' D ( T1 w6 f" F/ x4 USpring系列第8篇:自动注入(autowire)详解,高手在于坚持 & p- s8 z7 B( J* @; S3 O' C" t0 Y1 Q! _/ b
Spring系列第9篇:depend-on到底是干什么的? & D' _- n1 o4 G5 Y8 z5 m/ o! Y9 g) g
Spring系列第10篇:primary可以解决什么问题?9 J% ^% C& H1 i% p
7 ]% _ _" r- f9 u8 d: a$ s
Spring系列第11篇:bean中的autowire-candidate又是干什么的? ! {- A0 k' [: G) T & q& t9 L' S3 z3 G( oSpring系列第12篇:lazy-init:bean延迟初始化 6 p* _" M' O7 W8 l8 _: y6 _: [7 l/ w
Spring系列第13篇:使用继承简化bean配置(abstract & parent)4 B4 n. A3 Y5 n0 A4 ^: {
) C1 N, a9 [0 \/ q7 J5 `5 D1 F
Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的? . H' F( i# G) l/ T* }3 U. ?0 o, H8 c5 i) M
Spring系列第15篇:代理详解(Java动态代理&cglib代理)?5 t# q% V) V0 W9 D
% U% d$ N0 O* q) G0 ]
Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识) . x" s& \/ J/ U! |0 n $ s5 v8 m2 g2 i6 p0 xSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)+ G/ i9 W- o O* K6 Z( T6 H
Q s" [" r I* Y; q+ U( z
Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)0 G; e7 ?* r# e$ P