$ d+ @7 V3 R+ u" T8 l* J c private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();/ P ]- _7 G1 ^& l/ W
9 ~, M) `! D1 _' `- N
} 5 }+ c. h) Q w; i- q0 `内部包含一个propertySourceList列表。# t" o! x2 E" I2 \8 Y2 h
, y1 T3 Q" n. T' W# C
spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。1 C$ R: Q: Y7 @* g: K$ E3 h! x! \
, ], d# E9 a; k5 C) A大家可以捋一下,最终解析@Value的过程:$ M% M9 o6 h9 i" I) E
# {! u w1 i) Y v6 W ~1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析 ! o5 `/ r; Z0 T( L, W! ~7 u4 [: ~3 A2. Environment内部会访问MutablePropertySources来解析 , c- i) R: c3 O8 B3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值5 [5 [; S- j8 y
通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。$ N1 g: {$ ?4 c6 r
5 i6 d0 h% c7 R, |
下面我们就按照这个思路来一个。3 i, X2 S6 E# e
/ E& i% @2 H. b
来个邮件配置信息类,内部使用@Value注入邮件配置信息1 h0 u I! I1 R/ |. \6 k: E
% ]9 {7 d6 C& }package com.javacode2018.lesson002.demo18.test2; 0 z! s& ^1 T3 T; i5 k. s% |/ B- j" }4 G: {# j5 K- P8 x
import org.springframework.beans.factory.annotation.Value;, w2 t4 B) p8 R
import org.springframework.stereotype.Component; $ q! A& o4 G# Q) ~2 v 0 K9 Z1 f: k3 x9 e/**7 O' ~. s, J2 y" V- T2 j
* 邮件配置信息- }: b+ b, t/ S1 d! A; ]9 V
*/& K6 }; i" q" H8 m1 m
@Component7 o; f- b" I, T$ T1 E6 i
public class MailConfig { 9 u( i t6 H a ! V2 H7 @2 B! o+ K; z& y @Value("${mail.host}") $ \6 U! Z! C4 W; R I$ L' V private String host;$ J$ z% |$ g2 ^$ z1 u
9 B) L* l: S7 o$ c3 v. Y% i( z @Value("${mail.username}")) T: E) e9 \8 V" X7 k3 f4 |
private String username; 8 B o3 U" L3 \2 E 0 a% t. t! U, k! d, H$ l @Value("${mail.password}")" p# _9 t* u D
private String password;1 f# ?, ^! Q1 V$ F, Z( Z
" m/ b0 d5 y6 u5 L public String getHost() {2 D4 }. B) [2 g- V2 I" R% E1 j+ `
return host;! V5 b0 E% z0 @
} " B) P/ G- N" i8 g# A( W : ]3 o8 ~, L4 l2 F- x: W public void setHost(String host) { - `, s3 f+ Y" _0 f/ W, V this.host = host; 8 o. z6 ?8 C' k' @ } * m% K F/ ` |& Z- D" l; V* |/ Q4 v w' v: i- C* D
public String getUsername() {( U/ ^* l1 O; ?
return username;; n8 v8 k4 P) _ |0 G
}" R8 x: T1 C# _& R
9 [4 y, u& p/ b t% q7 N public void setUsername(String username) {) A% X0 H/ U/ f
this.username = username; ' U5 W( B/ j9 x$ r# G) P } : l5 R- O1 {7 d1 s! d4 O3 l 1 Y X* J+ W8 ?0 T7 I public String getPassword() {2 o& Z8 J8 v1 W* }# T
return password; ! N& p# ?, N V$ O1 X! A- G$ ], C } ; y' H% q0 V1 V& W! S& z- W: f4 y- w' T. X+ J
public void setPassword(String password) {/ g" [/ F# s e0 N$ C0 e) k+ O% i, |
this.password = password;# e: [( D) i* c% k6 ~
} * c& B: i% }5 g1 W# J9 E& O6 g! d/ ^ D% l' h
@Override $ e$ D7 F; Y- P public String toString() { ( a6 L0 d4 O- c& m- a0 K M return "MailConfig{" + ! u, u: D$ s( u% ?9 b; k* N; o "host='" + host + '\'' +. \1 i! ~/ V A. r; I; {4 I
", username='" + username + '\'' + T$ K+ P2 E8 l7 b* }
", password='" + password + '\'' +8 p o1 N3 K4 `# A
'}';$ t6 J! I7 q1 V/ z5 ~6 f2 z% C) c
}9 |8 `2 ]& W0 @- G0 a1 ^9 W
} 5 c! k4 \& J" N3 I& N6 N* h再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中. L7 A4 {4 ^+ Q9 a! Y" e+ U4 L
& B7 [# o* z% P1 ]" C
package com.javacode2018.lesson002.demo18.test2; # p7 P I- `5 m! G9 ~# J% _ Y- e 4 ]6 m8 ~6 f5 y: E) N0 z' fimport java.util.HashMap; / G6 ]5 G9 s/ r* m. { g: }4 I- cimport java.util.Map; ) Y; T7 l# i/ a- ]) H- y 0 i# \5 a* i7 d8 ]' dpublic class DbUtil { - N$ N" h' }8 d5 Z' U; B+ y /**- F: A& o3 S R. j. z
* 模拟从db中获取邮件配置信息 }1 \9 ]2 o. P; n * T/ w9 R5 }: k5 o( J' `6 D * @return% H0 B0 X( T5 Z$ ]
*/. d. W/ X$ B! q: k# U
public static Map<String, Object> getMailInfoFromDb() {0 e: j0 B/ V& W% e! H- [3 _
Map<String, Object> result = new HashMap<>(); , F, r$ g7 i& E4 \1 H9 A result.put("mail.host", "smtp.qq.com"); 7 @" y g" X* l$ l F result.put("mail.username", "路人"); 5 B+ v( b2 ?5 b0 @ result.put("mail.password", "123"); : `8 G5 h! d4 _& r return result; : s8 G5 S9 s. r0 E9 p: _" d; U3 N1 O }) E( g9 O0 I0 t$ \! s& l
}/ J7 D+ F2 T8 x' G
来个spring配置类/ h: W' ]$ k8 P6 R$ l# \, L$ t
9 F! w3 N' x" F7 L. V ]+ Lpackage com.javacode2018.lesson002.demo18.test2; ! O1 p# ~( w: G' K& [ s' N2 L+ h3 F+ e2 \+ \+ @, h
import org.springframework.context.annotation.ComponentScan;0 ~6 h% i. V. ^; r5 E1 d
import org.springframework.context.annotation.Configuration;0 Q% U" @) i. O; w9 B
0 T8 Y- c, ~; z7 q" ~@Configuration 8 c) F8 |+ w: K@ComponentScan2 A% [: Q( J7 \1 @# e# W
public class MainConfig2 {5 h& U9 Y9 ^+ J, C/ V
}( l" u; c$ O( x
下面是重点代码 g6 Z; g6 n. U5 o 8 p- H6 u O6 T! l@Test1 `' Q: l5 W! J! T3 A
public void test2() {7 ?; |2 h; S+ l6 a6 ^3 I Z; p
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();8 W" p- K) r' R, C$ Q. L6 Q
; t1 e/ M6 k# b5 m& N /*下面这段是关键 start*/' ?0 S; ]/ r1 I. v& U
//模拟从db中获取配置信息) L1 R' J; a4 `! e
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();4 |$ f2 T9 P/ C8 p
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类) 9 Q4 Z j7 Z0 F: |; g MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);1 C' Y$ Y( i: f0 \: i4 a& F
//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高 % |1 |$ p+ S. c$ B; G context.getEnvironment().getPropertySources().addFirst(mailPropertySource);* y. p6 T; x# K) p# R' a
/*上面这段是关键 end*/5 N) G& G' k7 V' T& T9 F1 P3 U
9 t) ]) c! a0 a4 l
context.register(MainConfig2.class);) V# @+ t; _" v5 d
context.refresh(); " z2 X0 J' [8 A. G/ v! }5 {; G MailConfig mailConfig = context.getBean(MailConfig.class); - ?' I- j; H" d, k! i6 g1 A System.out.println(mailConfig);/ v8 ^" q( Q! Y( I0 c4 c0 O" r
} % N8 J0 Z! j& A O注释比较详细,就不详细解释了。4 O) k3 w7 a S
! d9 A4 \ B1 h8 I6 b1 {
直接运行,看效果 9 ~) A- \6 S8 i8 R# e: Z# y3 Z" ~ b" d) [
MailConfig{host='smtp.qq.com', username='路人', password='123'} 0 ^& g" i2 Z# G0 K4 Q& p有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。- B7 Q8 H: m' E; E6 T2 L7 I9 G6 R! B
% H0 Z9 L& |/ q$ @, |. Q
上面重点是下面这段代码,大家需要理解 % {4 A1 P! ?. ~% u2 Z: Z4 ]: N5 Z' s% j8 B# y
/*下面这段是关键 start*/. V, g7 W% `- f P. S8 h& g
//模拟从db中获取配置信息+ x# o4 Z' ?8 a
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb(); O) c) q: K+ m/ ]) x% @3 `
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类) % d0 L* o9 i$ B1 Q! yMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb); # _; W: M! S/ ?' A9 D//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高 6 f! b7 w+ W4 l, J$ W9 Mcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource); & r; v) g# f0 H/*上面这段是关键 end*/& @/ a# V2 T. f% [1 F% I S
咱们继续看下一个问题" }' g) J+ f- T) o2 F
1 S1 g: |$ q) t. N6 W1 ]5 V如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。 t }& {+ x7 B5 u3 A* D& l3 _
8 z* p" y9 e: L' L( r+ i6 N
@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。 + i: e8 E/ [2 r( R" e6 A ( X6 m* x" B2 ]. e4 C实现@Value动态刷新 $ q. L. Y& a) n) V5 m) ~. Z5 y @3 W) v7 K" X# c6 R% k& \/ a
先了解一个知识点 " R' L9 @& w: e, I, K0 N8 v! ?: B. h* q* f' s3 A8 ]5 p( \& }+ O: Z
这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。 : L c8 }; }& B5 y5 y+ f; T; C8 t. Y4 m! X
这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解- t9 I5 y' b! l( C- ?
5 f! W+ A+ q8 `- s8 C4 D
bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是: # i' u; [ B$ X5 N: f5 x! ` ( z$ Y/ T& c' }ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; ; Z% k$ s, D9 l3 D这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中- d$ f! d9 J! q. S
) r/ ?% A4 Z3 `1 L: K
public enum ScopedProxyMode { & r. N8 H, L2 n7 r- j- v DEFAULT,4 O2 h b% C8 M4 W: m% I5 v, \& p( [
NO,, u" p W/ F/ }3 c% w# j
INTERFACES,9 P# w" k9 _% ^& d3 E3 T
TARGET_CLASS; - H% Q, n( ~7 ~) q- x! H1 B% y4 h+ P} 6 e* \: m5 e, _ l. s3 [* C前面3个,不讲了,直接讲最后一个值是干什么的。 . R) q% \# F# c3 |4 h/ ]1 \& |% F4 j( t- Q# a2 S, W4 O
当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。 ; t3 J/ v! t! L$ B2 p% H d7 Y: T5 T. ]. w& y t, S; I( l
理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。 0 h) k' g9 l# D/ E o5 e7 J; }' }# m. g& \: c
自定义一个bean作用域的注解 3 f+ N A9 l: N$ e3 N7 Q. w 3 ^" t& |+ N+ ~2 S$ j" G% W) Ipackage com.javacode2018.lesson002.demo18.test3; 2 M* O6 Q1 i4 C" H0 R! v2 U, O3 A+ c( z! ~) q
import org.springframework.context.annotation.Scope;* z, v) P/ }' M2 A' T
import org.springframework.context.annotation.ScopedProxyMode;2 p6 j/ r+ i" P0 }
9 H1 S/ k. h$ N1 I* {$ c) V' Fimport java.lang.annotation.*; / D/ X; p1 x& T" ` 3 V3 A, O9 _% A; o( [0 }6 w@Target({ElementType.TYPE, ElementType.METHOD}) 1 Z( x, ~! _5 c0 i+ k( r L) s@Retention(RetentionPolicy.RUNTIME)/ Y8 q% m6 F) W4 T s
@Documented & i+ q* V( G7 S) Y0 ~@Scope(BeanMyScope.SCOPE_MY) //@1! o V. k9 S! ?9 t/ [* X! _' s
public @interface MyScope {! ]) a! H8 q# x2 Q
/**7 C6 \% r/ b3 S( C2 ?
* @see Scope#proxyMode() # q7 p( O; f E& L */ $ b- @0 a# \0 L# N" G ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2 ' T# M) z9 ]- n0 g9 D7 d* l' F$ B}! O! Q; |! j4 o: E! S# [- e
@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。0 ]7 p' y4 r* M+ @7 A9 T
/ W: |! `8 U3 @% g1 i; S5 o( y
@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS / t, D1 @( ^$ n0 Q& j( i1 }0 j6 _+ C9 h* \* ]+ ~) @7 a- t* Y
@MyScope注解对应的Scope实现如下6 x5 j* R; L, k3 Q5 y3 K1 }
- |" y) n7 g8 G# |, y+ E& C& y
package com.javacode2018.lesson002.demo18.test3; 5 }# e4 x' f& e( @2 ]* V5 D$ P. k, N( @2 n& c- w
import org.springframework.beans.factory.ObjectFactory; C8 ?8 t# L n1 ?- X( Y8 }
import org.springframework.beans.factory.config.Scope;$ @$ X% k4 K: B9 y1 Z6 z
import org.springframework.lang.Nullable; / k+ [' A7 @7 M. x8 r% O $ J1 C/ l& U2 }7 F7 k0 k; w0 v0 a/**& v, P9 ^( T8 b$ G; @
* @see MyScope 作用域的实现 8 J4 `; o5 S( U2 l6 {* \! a' n */3 k9 n: }: C7 b1 }2 V
public class BeanMyScope implements Scope {! P+ ^# Y/ O, S" w' n8 G e2 R
# ]! C. ~, p' r$ q( j+ L- r public static final String SCOPE_MY = "my"; //@1 5 ^7 v- G8 z5 ^" u1 G! } : R+ N# C# I) \' Z' ^/ R7 i @Override/ \- |0 z) ?) p4 o1 ^% P
public Object get(String name, ObjectFactory<?> objectFactory) { : R6 P. y/ n* E; U0 X4 A
System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2 : T( v. l5 g! u return objectFactory.getObject(); //@3# c5 x. b# Z' T( R1 |5 J
} 5 g/ L v5 `7 |7 b9 T# a i0 r/ B; k4 W; L- S
@Nullable5 w F3 }, z8 ~% k% ^
@Override ) q% m% [3 l' M( j; M. i public Object remove(String name) {& Z5 q7 G3 I8 \% a; H8 F, U2 w
return null; ! }, I! ` l9 _- J }6 {% B2 b- S4 N
& ^3 _9 [* t- S) {# s" s6 t @Override 4 Y! f6 E) l7 K+ l1 ^4 C public void registerDestructionCallback(String name, Runnable callback) {. J: ^8 f- D- D0 K! l G# B7 z
# t0 S9 x# A0 l, q# J }8 w ]' p4 \8 Q; C1 _: {
! Q3 Y8 [" z+ t* r
@Nullable 1 e4 R- y! S Y- ] @Override7 g0 s4 `+ y) n' h
public Object resolveContextualObject(String key) { 2 v/ U5 t& }4 H# v return null;+ t2 C1 ~$ q2 ~- \0 g8 O
} $ C; R2 C6 \5 }" M e1 ]$ x+ g, I- d8 y& @' {4 v
@Nullable 8 |. N |7 Z9 t+ w @Override , _) d" G0 q4 f: M, x* M$ r public String getConversationId() { ) c, C' w9 F# K return null; : b' T9 ~& x9 C }3 p0 W/ |0 e3 @5 g
}/ o2 @8 S7 F2 A+ f2 S" i" ]2 C
@1:定义了一个常量,作为作用域的值 7 S# ~2 Z4 w6 S% s* x F : f$ s0 z+ p! X0 Y' a, }@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果/ k; O% C: p! S
# G) b% ~- _5 z- {# Q, J& J9 i8 d@3:通过objectFactory.getObject()获取bean实例返回。 5 W1 R4 ]' |+ G# f* ]! n- f0 U; o5 b9 A. d4 D6 o% r8 y. [4 q7 M
下面来创建个类,作用域为上面自定义的作用域 - b5 W) J/ Q. t" A! K , Y0 O5 J6 a# L% a0 Ypackage com.javacode2018.lesson002.demo18.test3;2 H+ ]/ {7 Q' M5 p: ]
1 T" s5 O3 |+ ^ a- K& v$ n! x
import org.springframework.stereotype.Component;; n8 e; U2 o1 s9 u: y
2 Z: y/ k- `+ k) M) o5 Uimport java.util.UUID;, F+ a0 x7 d) u
3 a3 n4 J% j2 f6 W
@Component 4 C* `/ ]. W, C9 d7 f: _4 G@MyScope //@1 7 U$ K9 j9 B6 e
public class User { & N9 W. J; c5 n: n3 R( Y ; D3 G8 S. t2 X# z8 x% E5 n7 i private String username;0 E/ r% t7 ~" t3 z9 _5 @& Z0 {2 y
) P' Z4 I5 E8 V8 ? public User() { ) Z# K7 } [: N; j, ^$ P% l System.out.println("---------创建User对象" + this); //@26 A" v" Q9 T: Z7 U
this.username = UUID.randomUUID().toString(); //@3 9 ^5 }* O1 P5 V% ?0 ? } % d' p8 l, M! A. s' W' G( C% c6 F2 ^, i% b3 k4 l
public String getUsername() {! M8 {( _1 k! s+ d
return username;4 L) B8 L3 g/ A; [% v7 o
} 9 O: n6 b3 ~0 D1 W6 ^ o/ k# I" W8 y* D. R5 P% e7 {) U, _
public void setUsername(String username) { " w6 r; \+ c3 z' G. ~8 ~ this.username = username;, P9 T N9 `4 Z# d
}0 M, c5 U3 `, z6 @4 e. X( E
?: ?" M- j% u5 C3 K} 5 r- m) `4 B! y+ Z# Q2 _1 S# [4 U@1:使用了自定义的作用域@MyScope ( a N8 p- s) y- ^* Z9 _0 b. \. j8 ^% v- o$ |5 |# a, e
@2:构造函数中输出一行日志 0 v0 a& @5 I2 H2 x6 L* c; z/ W. t1 G, Y7 X4 a4 l! x+ o S% k: @
@3:给username赋值,通过uuid随机生成了一个- E, t$ z( v5 d( z