- 在线时间
- 1630 小时
- 最后登录
- 2024-1-29
- 注册时间
- 2017-5-16
- 听众数
- 82
- 收听数
- 1
- 能力
- 120 分
- 体力
- 564701 点
- 威望
- 12 点
- 阅读权限
- 255
- 积分
- 174633
- 相册
- 1
- 日志
- 0
- 记录
- 0
- 帖子
- 5313
- 主题
- 5273
- 精华
- 3
- 分享
- 0
- 好友
- 163
TA的每日心情 | 开心 2021-8-11 17:59 |
|---|
签到天数: 17 天 [LV.4]偶尔看看III 网络挑战赛参赛者 网络挑战赛参赛者 - 自我介绍
- 本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。
 群组: 2018美赛大象算法课程 群组: 2018美赛护航培训课程 群组: 2019年 数学中国站长建 群组: 2019年数据分析师课程 群组: 2018年大象老师国赛优 |
太狠了,疫情期间面试,一个问题砍了我5000!
) |5 o) T9 ]0 S* Q% D# }% n% v疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!& L. ^2 y% ^' A
' |* ]2 G# N0 m+ g
面试官:Spring中的@Value用过么,介绍一下
) }( p7 k I$ ?; [* t4 }" ^, |* M3 s- X7 P0 w9 O
我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
0 t0 W' Z0 V0 H' ^% y( ~. @: z% v5 Y' I* d5 Z
面试官:那就是说@Value的数据来源于配置文件了?
, k7 y* o" P2 c' l" [) t5 |- ~# f+ H9 t! l! R+ F
我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置2 I& t) [% d/ j* S
n/ j1 N3 R* V" g5 K面试官:@Value数据来源还有其他方式么?. |6 @3 R1 z( f) b( R6 U$ g
- S! a7 E% G- m) `# @. u
我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
7 h" X' q* y Z" a4 \
9 w2 M8 h+ W" n5 ^' w面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
- c* }) l" A8 J9 S5 c# s6 B( L/ q8 q; u/ ^
我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
5 e5 B" T! E6 H$ b# g% x
+ h9 |/ i2 k- Z4 R& p面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
8 `" n: n! G1 h, o" m& a5 f- _7 x
6 Q0 e4 s! c1 f$ y$ Z我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
9 \6 ]& z5 f4 h Z) R6 }+ [" ^& r4 `
- Y- j7 ~$ ~, L面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?% W/ b, S, e7 y7 D- }) W
2 D( Z2 _* ?6 `我:嗯。。。这个之前看过一点,不过没有看懂
* f/ f3 O! {; e' J; F
, w. D( _5 u7 f, E& E0 A面试官:没关系,你可以回去了再研究一下;你期望工资多少?; ]# }1 W k! M3 E
6 ]0 `6 U6 n3 O2 ]/ Q D) K
我:3万吧8 s) s; s8 ^) o: {
/ W! ~( X# t- X9 D面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
3 Q7 F' Y1 F0 Y/ k
7 H' `' S/ ^2 G8 ^) U( }6 M我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万' M0 ^: I' Z- l# L5 v
" L: ~/ ?2 x1 K- t: }! {- y
面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
$ a, b! k- ~1 i. Z. ^, K! n* G, Z+ R: X0 y+ E
我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
3 C2 R" C% x) E; D% P
- H9 ^+ n0 `- C" L( S# n6 t$ y# q这次面试问题如下
3 p. ]% \& t5 ]# w- }; S% a( M4 O; U0 m$ U
@Value的用法
2 {8 m( ^/ G- D. B' ]/ z! [: `; r1 K3 {+ ~8 x( Q p0 ?
@Value数据来源
8 J" T" S/ _1 t" k' ^; D. E/ {; C2 J
@Value动态刷新的问题
6 I1 P8 V: o; J3 Z- x6 i
) U9 {4 ]" C5 r下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
* ?, W& m6 o/ z3 T( u1 x9 C2 R( g6 l# D
@Value的用法2 k' r- H4 n4 I8 N% }( Q
! u8 v$ v _7 w系统中需要连接db,连接db有很多配置信息。- n2 s4 d0 L$ V6 |
& e6 W4 `0 x& d系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
2 l/ d; p! V" A& k6 ]2 w* w4 }; l% o
还有其他的一些配置信息。. V* V' E0 }+ W: n
/ b0 f) P' ]5 f* @2 s6 `
我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
, s. J: Z7 |3 f0 J. i( X5 t! x6 ]( {# V! |
那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。' e, s1 t( y5 u; X
' H' D, u' D9 T# U2 v% c
通常我们会将配置信息以key=value的形式存储在properties配置文件中。. K b6 z5 h- J; X
8 o5 V- d" p7 Q4 y* n通过@Value("${配置文件中的key}")来引用指定的key对应的value。
' X- G& i) w7 q) ~% [# J1 Z
& H0 P8 E& A* Y6 |: }8 I2 q2 u; l@Value使用步骤6 \6 l7 R9 }. _
# H( h! ?( S. n: R9 j4 O步骤一:使用@PropertySource注解引入配置文件- f$ g6 j" e8 i. S; U
- `2 k, W/ {2 x/ o
将@PropertySource放在类上面,如下 I5 n) @ B* C, I7 }
& t8 G4 i9 b+ h3 @3 q@PropertySource({"配置文件路径1","配置文件路径2"...})
* N, ]+ Y" n: |' N8 B3 O@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。0 u1 u4 [2 z4 s/ e6 Z* v/ r, k
# f& W3 Z/ i& `2 I) `( p9 q: R# q( w0 m如:
6 W; m' n3 L) r' T$ m
1 f1 T/ J* B1 d- B8 D- C@Component6 W ^' ?: b0 B3 S7 v
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})' \! Z c3 } x
public class DbConfig {/ e- [" i0 n% E& ~' v/ w
}5 T; H- K2 p2 T
步骤二:使用@Value注解引用配置文件的值: A5 l% p2 s x: }" h
1 ]4 e0 }1 n. [. d+ }8 Z B1 Z通过@Value引用上面配置文件中的值:
% K I- B& n. K* O G* n
7 h& o6 _. I9 ]4 H3 ?8 U; t语法
" F8 C8 ^/ \/ z( |8 w
x! F* k1 h* Y4 [2 b- C9 V@Value("${配置文件中的key:默认值}")# P: m& f( q/ D' p" d2 U* C# y
@Value("${配置文件中的key}")/ D+ N0 d6 Q, z: Q! p
如:
. e0 G/ `5 r. t$ i' @) L0 o6 O& B2 I2 z2 \5 ]- p/ `2 c2 B, \
@Value("${password:123}")
) c, z; ]% i8 b) q- b M9 o; O/ u1 t上面如果password不存在,将123作为值
9 Y7 I4 Y$ E- d+ h) v* |- E$ K. K
: u, \ ]- g1 z* M@Value("${password}")0 [5 A) K& \' `' y2 f& }8 z
上面如果password不存在,值为${password}
. |5 n6 |" J6 {8 [- P T
# N `1 C; N: V( o, X; }. A8 n假如配置文件如下6 z& f- \4 c; K1 T( Y
7 ?/ K9 q- _# Y% B: ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-82 @2 _ T* S# x9 M4 Q. m
jdbc.username=javacode
; G8 P" \* {2 p# F! @jdbc.password=javacode& p9 T2 @ U; o1 J1 i
使用方式如下:6 F# M9 L- Q2 P, Z% S, `' H
: b/ I( i e2 T
@Value("${jdbc.url}")( ?, J/ q- D) U* h9 L$ z. E4 Z
private String url;
5 ^5 F4 \+ {4 g
. ?/ A. N+ \3 h$ q( }6 X, A@Value("${jdbc.username}")1 d) r1 }2 ]# p# O6 ?% t
private String username;& O1 E1 l* Z! q2 x8 Q( m& D; c
0 r0 ], {% S8 e w
@Value("${jdbc.password}")
' Q3 d/ ?3 Y# `/ o1 hprivate String password;& a- L) B1 A5 o# l# l; p
下面来看案例
. b6 o2 M! a3 ?2 Y# u' Q# V% d$ C+ ^6 C1 e
案例4 N5 L5 ^' O9 M/ V5 l
; N7 E$ b: {# I E0 H) Z: V
来个配置文件db.properties
$ f; Q# J% Q" C' T" {# _
( `2 a' m6 |1 wjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
0 d7 @1 j+ c* Zjdbc.username=javacode
2 H: c! _0 l, j+ s8 q) s& a. djdbc.password=javacode/ R$ G/ L5 L) j/ u$ U
来个配置类,使用@PropertySource引入上面的配置文件
: S! T& x$ ?5 V; E
2 O4 X9 T9 U. cpackage com.javacode2018.lesson002.demo18.test1;
, k- o$ W1 q( g" Q4 F# H8 z( G5 p) F" A2 O6 f. G) O9 W+ H- p. B
import org.springframework.beans.factory.annotation.Configurable;
m. e5 B7 H1 H1 Kimport org.springframework.context.annotation.ComponentScan;/ B, o, e$ D3 o4 w1 C/ Z
import org.springframework.context.annotation.PropertySource;( M: d3 r7 K% y& w$ A3 M/ ?
6 ~/ x: k! f* W6 c! r@Configurable
, m2 Z7 `; z" C5 p& [) S@ComponentScan% Y) T2 s4 V" g1 r( e/ l$ `! C
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})8 j, A& h. q6 L5 a0 l' i% g
public class MainConfig1 {5 I+ W( [3 Z/ n( u5 \6 m: H- z( o" s
}8 [5 t- @* m3 n' Y
来个类,使用@Value来使用配置文件中的信息
$ k& F7 x" A% w1 F1 Z% f" Q5 C
) @" z6 M& a) Q- H4 C/ C% M2 upackage com.javacode2018.lesson002.demo18.test1;
- J1 A {7 z1 O8 I& Q' ]# M$ a0 I) }: ?) W9 E
import org.springframework.beans.factory.annotation.Value;
* F+ @! W. ~# Jimport org.springframework.stereotype.Component;
! q/ h, P5 o$ t4 R6 g# I8 Y' v4 q
5 ^* J( c; {; ?1 M" z7 C4 q5 j@Component5 A3 E5 F3 g- V1 s6 U! \
public class DbConfig {/ |3 u$ `3 h" j7 z8 B0 a5 B
- C4 D) v* _+ d% ] @Value("${jdbc.url}"). K) P+ @2 B% Z+ e7 \# e
private String url;
& d5 ^" g) a# H+ g; a0 b5 {2 {& k2 _: X; [7 J
@Value("${jdbc.username}")
! e2 s( N: I. m7 n private String username;" G) g3 N2 B4 Q( v+ ` b5 F
$ ]$ q6 l1 y4 P+ x @Value("${jdbc.password}")$ j: [$ h1 U4 P: `
private String password;
6 O, _$ N6 u7 D3 k& c `2 n9 V" P4 a9 _" y& m' q( w, D
public String getUrl() {4 `& C: ~2 O, a$ q
return url;" x) }# y. Y: y8 O+ ?4 C
}
0 k1 l, q4 }6 k/ u: M! p8 Q
M' {7 ~8 h# ]1 p/ z' I" y public void setUrl(String url) {
+ n7 R1 I" a7 P9 U% s this.url = url;; m9 o1 o' g4 G7 f7 G
}
7 `9 H [) F6 U( T. k- r0 L9 R$ C7 J0 P. Z1 U
public String getUsername() {
5 X7 K p+ f' ]% O return username;
! q- S0 j6 Z3 G6 g }
/ B5 b3 r' p7 S$ C1 @# N* o, b! @2 E4 _' l ]! G, W
public void setUsername(String username) {# x* i0 U- U* p
this.username = username;2 r2 d: z4 k& b8 p* F$ f1 l
}/ |; a& K4 k- a* y) E
" J& U* k6 Z- G' y public String getPassword() {% c" y, b) `' x0 q. _# T
return password;1 P3 X1 F' w4 k. W4 Y( p; B
}
4 |6 m" d% g. \' q2 ^) Y* b% ~ [$ J# c* }& K! l( ?$ P( ]
public void setPassword(String password) {, C/ d1 `5 \! D$ X! W
this.password = password;
4 c* F& ?! c' F' w7 L l; t }5 x- ~7 K3 _: D/ g
3 ]/ i' h4 Q- |) V' N/ x$ a/ ~ @Override7 v, ]# Q# z$ @3 h8 S5 A
public String toString() {
" \& {2 u' ?9 B: i4 L, J6 T return "DbConfig{" +
) D7 c- M; m; i2 S, g/ N3 f "url='" + url + '\'' +2 S$ G% L% [# z* e3 }
", username='" + username + '\'' +5 N5 l3 T+ W" {( H* Y& ^
", password='" + password + '\'' +
/ I, D+ f' U: F+ X '}';
# O% Z3 _4 J9 Z& G& t5 u }
6 ^9 T d$ E% a% V; p}3 D9 Q- e: v2 [9 N( I
上面重点在于注解@Value注解,注意@Value注解中的8 h6 o' F Y/ y5 v+ G. |% b. e
# ~2 j9 B( {- ^" W来个测试用例, X `% ~( R3 \8 Z
* H. M; b+ w) P8 z- C5 S z
package com.javacode2018.lesson002.demo18;
; |0 k2 W2 a) e; j% h8 X: K" A# d- G
import com.javacode2018.lesson002.demo18.test1.DbConfig;5 w1 n# `, }0 T7 y: h
import com.javacode2018.lesson002.demo18.test1.MainConfig1;
& k+ y6 T( f8 E& J& [/ L# eimport org.junit.Test;7 A! R+ i% H8 x# D6 L
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
1 w* x9 b v0 f* i5 `1 ]4 ?1 v, R7 ?6 P/ ^6 ?$ W
public class ValueTest {
1 G+ B5 M7 c/ N7 r; O4 |5 n- k* ~- Y3 m3 R1 q
@Test/ B" o0 D8 F$ A: S# x4 d
public void test1() {/ ]* W( p: _( ^* o H3 y* {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 f! |% T. p1 R, m, l7 g0 a
context.register(MainConfig1.class);
p- O% `/ g! z6 K$ W, l" v( W context.refresh();
3 C) e, n' x# d! s! {
" a. b$ ?5 ]2 q' Z' U L DbConfig dbConfig = context.getBean(DbConfig.class);
+ \% @/ Y4 z& P: u$ z/ i4 ] System.out.println(dbConfig);
1 A" U/ ^4 r' i, [) {+ E }* H! r; o) B) {1 F. h* G4 ~
}
! x$ l9 R( L. Q( i5 Y4 v* @. J运行输出+ \/ }- C/ j; y2 ~/ l- ~
. |' N L5 Z# a$ w1 b hDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
( @* v- n! a9 l1 N上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
+ L+ K+ h" V4 s; Y+ C
0 u7 i9 {8 B; R9 q. r@Value数据来源
: V. ?1 k7 j& T: o, ?
/ c& _) g! q. W. d7 P$ X# w通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
4 ~- Z }4 W; X" d5 s( [ `& V. a1 X( x% R4 D! Y5 j
我们需要先了解一下@Value中数据来源于spring的什么地方。8 c G2 ~" d( w$ A& |3 O
" B i5 s0 _. N" v
spring中有个类/ a4 P5 p9 l# ?. J* ]/ o
0 N3 p3 q* W! \
org.springframework.core.env.PropertySource: y$ F% s6 m5 p( F! {
可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
- G8 ^! S7 T H9 n& s: i( p% H( C/ o3 ]3 ]0 |, C, ]" F" B
内部有个方法:
, W2 Q) C1 f: k
2 e3 d5 L( L. K4 G2 }+ F5 Qpublic abstract Object getProperty(String name);) e, d+ [, y* ~9 ^4 g% ]
通过name获取对应的配置信息。
7 z+ j: F: Z! i' @% q( t
# h1 p- T( `& `# n( }' A' s系统有个比较重要的接口0 _+ j- k2 B: i$ ?
+ T% X T4 g; b8 R& n+ b$ eorg.springframework.core.env.Environment
- r( t" o% T+ s用来表示环境配置信息,这个接口有几个方法比较重要4 C* R& W! n/ J* r0 ?0 _4 L& F
q. v a. i* aString resolvePlaceholders(String text);
# N- J7 F' y+ H8 g. m% ^* J4 PMutablePropertySources getPropertySources();" r, ^: ?$ F8 G& K
resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
. u4 F- C8 n _1 I8 _
$ x/ Q8 c4 L. CgetPropertySources返回MutablePropertySources对象,来看一下这个类
7 S5 Y5 _$ [: P `3 \
6 [3 c. o' a( f6 j; @public class MutablePropertySources implements PropertySources {
& v- F# x/ W- z. t6 ~! Z1 `1 Q+ m+ ]$ a3 H4 o( `% M6 i4 t
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); q! O, T5 G9 }
* Y, n" Q* r s, x) d y
} D7 O9 F5 \/ ~/ `
内部包含一个propertySourceList列表。
2 J0 i5 c) F0 m# r/ h9 M4 m. @; D2 w! N( v5 f1 {
spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
, a- G& q5 B2 y. c9 a1 _ ^# D
, M2 |) ^- y2 Q大家可以捋一下,最终解析@Value的过程:
9 [4 n, u: ^1 y# ~) B4 l4 G0 v5 l
1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
7 E5 ?, d( l' x0 e9 c. G0 O+ k3 E2. Environment内部会访问MutablePropertySources来解析
$ s2 I* G! c/ C: _: M) w/ [3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值$ y2 [2 Z6 M9 D' o
通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
% ]3 D7 H/ T' q5 }4 S, T' N
- `' H, M9 D( A+ S( [5 y% b3 D下面我们就按照这个思路来一个。$ v; X# E# }# p2 R4 `4 e3 A
: I& f5 k0 I9 E
来个邮件配置信息类,内部使用@Value注入邮件配置信息
4 o1 e, T; l, H8 t& j. j: T9 n
s; i0 G5 w& c/ cpackage com.javacode2018.lesson002.demo18.test2;. w+ [; t) |+ `( n$ _" K
6 }5 ?. w+ o: N
import org.springframework.beans.factory.annotation.Value;; l2 T) ?4 L% A7 N2 N ^
import org.springframework.stereotype.Component;
, p6 C f& L* N+ { v
' z3 E1 k0 U: w" x% G3 K/**0 ?* _, e7 q8 R6 j
* 邮件配置信息
3 d3 h3 w9 w k2 p */
3 h6 V. }4 P0 t. j@Component
/ n1 X$ o1 e" X5 T C/ a; T9 W0 {% R/ ]public class MailConfig {
1 a9 _* f# h/ u4 e/ ?: G' Q8 I5 U: v) x9 a) ?' B( B) x9 o. {/ M
@Value("${mail.host}")
( S9 N+ c# G" T" P private String host;
% U1 U7 k% K ]- k1 { x( U$ L4 I3 }# Y6 i# l* O
@Value("${mail.username}")
- f& s$ o" |- ?4 Q( w private String username;. @; `" ~# U% W/ [* R5 }. V
3 K4 L v# }; D) L. p! p @Value("${mail.password}")
' M- V- u( e2 Q private String password;
$ z2 o4 }; R) a
8 c5 l) D' j0 i4 ]) D* g public String getHost() {
8 }2 Q$ w! F: A( `% `6 g% O return host;( S6 n) _' I2 i* X# d% u9 U
}* c, G; ~3 a V" s2 U+ \4 B
& T/ S M" O$ O
public void setHost(String host) {) Y: \$ H$ ^6 s; x6 g
this.host = host;( W, |( ]: f' x- f( C2 Y4 L
}6 E7 z: s9 V/ A( F; m
& {7 _4 Y# g! a$ u0 q public String getUsername() {
( u8 O# K6 ~( S* f* r8 L return username;
1 \6 N$ s4 i( p( B* j }
" j" u1 R( L3 K Z o$ t3 Y+ M2 u+ G& r9 O2 ]: V
public void setUsername(String username) {. L! A% C' E2 m
this.username = username;2 l/ g8 [9 C6 w
}
/ k+ O& c8 r B$ Y' S2 Y1 S! C4 d( b+ e( H' u+ q' o
public String getPassword() {
, W! f3 q1 D+ N& O6 D( y return password;
8 y( ~ A. q4 L' e/ [, v: Y }8 ^3 N4 j0 K& T* Z
& m0 |( @. M! E6 _5 W$ U
public void setPassword(String password) {
0 d' B/ ] g* C, b( r3 M this.password = password;
. v' j, A7 b) N! \4 C D9 f }$ N) o o8 V7 V C( r* X+ E0 u% p) a+ W
0 G; @8 S. S+ S6 m/ c* \
@Override% }! U$ [ r& i' I3 N) R
public String toString() {
$ G9 T* _; O3 w7 M. t return "MailConfig{" +% j- Z4 n% [0 B* m$ z
"host='" + host + '\'' +; t0 }& _4 X5 z+ t
", username='" + username + '\'' +; I/ b. h1 W: b6 b# d8 a3 _
", password='" + password + '\'' +
q8 B5 B1 s' i( u '}';
! |! M5 l! r& C+ J, ^+ |* e }( ^! l8 c* ] R$ H H9 ]. W- z
}
. Q! @* n" r/ v) [0 R/ B. H再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中) ?' @( h( V3 I5 B
; D) {; C/ m) j3 apackage com.javacode2018.lesson002.demo18.test2;
" Q7 `5 s, p0 {) C! F5 X( T% k n. P# q( P
import java.util.HashMap;
0 V: | Q9 w0 S+ v( wimport java.util.Map;
! p$ i6 Q% v% d8 C1 z* b, M5 y N* p
public class DbUtil {" c* T* t' k0 C" }. h/ D" X$ U$ n
/**
# X9 |6 Q8 @3 l, Y6 T * 模拟从db中获取邮件配置信息
. U: _5 d0 G- g+ l. C$ [ *1 Z* R4 i {8 i* [% I" O* Y
* @return* s$ r1 G' x* }8 w# i$ r
*/
! Z) m, h3 {7 u# T( ], {9 _9 B4 ` public static Map<String, Object> getMailInfoFromDb() {
' \# R, g: @+ G. O% P; R Map<String, Object> result = new HashMap<>();
. E& y( z7 r* o/ P) D* Q result.put("mail.host", "smtp.qq.com");! [- |# c' m# x' E p
result.put("mail.username", "路人");
7 l B8 `" b) k; K4 q result.put("mail.password", "123");
& B+ r3 Z6 r7 r( J7 K" O return result;! Y; a. [% }& }. h/ l! ?- i( R2 ]- w* ^& U
}8 O2 ^, v; X) l' } {; w
}5 G: ] O) n8 ?* K
来个spring配置类3 k0 H3 U1 Z2 A& ~. ]9 U6 z
: V; H- o! U0 n6 b+ \8 @
package com.javacode2018.lesson002.demo18.test2;
6 o+ j! W$ T! t( b) Y( E; V- G2 X9 t6 @
import org.springframework.context.annotation.ComponentScan;
; c% c! r6 ~* x% g2 wimport org.springframework.context.annotation.Configuration;
8 y0 R$ [' p5 r: ]. f. K
8 s: V) S. l0 J6 i9 a. w* j, V6 T6 Y@Configuration; I% N4 m$ |) P- b4 \: X9 j4 v
@ComponentScan. w8 w2 {( h R/ d
public class MainConfig2 {. g) z2 l: c2 g& K8 v
}
2 l4 F: {7 h3 s. p下面是重点代码
) f' T. P) j4 O6 {% D7 I8 }1 H# g
, m0 x3 J% a+ U: h0 w) e@Test
P& r$ Z/ N0 t' gpublic void test2() {: r3 s; @; k* F
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
1 S% \: i: S, ]. L9 a( [7 t+ |
/ s. i2 D/ z2 t+ ] /*下面这段是关键 start*/" g, s2 h) {3 }( F* D
//模拟从db中获取配置信息* `( i! D, U3 Y
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 j! M6 j5 j( K; b/ m1 D0 Z
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
0 {* `. ], V# Y% r' j: _1 l1 K MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);8 ` M6 K3 m) y$ ~
//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高- ~7 @* m4 i' o
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
6 Y( N7 P/ F* a7 G/ n /*上面这段是关键 end*/
+ |/ k" O% ~9 p; O/ C8 E; v& P" w9 r) J# I; d( w1 ^
context.register(MainConfig2.class);$ s$ |. i( ~2 {' C! H# G
context.refresh();
9 X: G. w! A8 Q, X( _. Y( ~4 G MailConfig mailConfig = context.getBean(MailConfig.class);
/ J" {4 [4 X( r% j/ \0 Z% r System.out.println(mailConfig);0 U8 c( M/ N l k. X: B6 J
}
! E1 \* h8 N# I2 D, g' D- L注释比较详细,就不详细解释了。
' p5 I: A9 C- \! l* h3 l& ]0 Q) y: F3 E7 B' P1 N
直接运行,看效果
7 U/ {/ v( Z( u r8 d
0 Q! K6 F3 x5 F! p% MMailConfig{host='smtp.qq.com', username='路人', password='123'}; z! g9 ^0 {# P" M) g0 y7 o
有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
8 u: f( \7 s( ?$ Y/ ^
& Y/ l# `8 P2 I, V# ?" L上面重点是下面这段代码,大家需要理解( c- n& e' a. [ X7 Y6 l+ y
& y; _ Z; z4 H! {/*下面这段是关键 start*/( `4 e1 k2 y( a& Q3 J2 z/ p
//模拟从db中获取配置信息- A- o9 ]' |/ n! t+ M8 f6 j
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
}0 K4 s. o/ C$ z1 e1 Y//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)8 l4 z# H, f# F' x/ u; d' l' v
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);1 z' }; @8 \- @9 S; M2 {
//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
5 d$ z5 l8 p5 J$ Xcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);9 t, M. U4 \5 g0 K) g p9 p
/*上面这段是关键 end*/. v$ X* [7 g( X$ q
咱们继续看下一个问题
0 b" P" \5 Q1 o2 F5 P+ A* B) g
* V5 ~! V# I! @1 j/ o6 b如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。7 @# R5 d. C$ s' L
; L# V8 {* O# d@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
/ w+ k9 M& I' m. R9 s$ B' s$ s7 l9 g1 A! W" I6 V
实现@Value动态刷新: A, {: c# F: k! X
" D( D6 I/ {7 W# H5 T先了解一个知识点
' f+ A/ i4 v) T
( }5 h. q- ]* J5 z: M2 J这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。# ]5 }9 ~. b Y* H
3 V. `$ ^7 G3 {' U6 p2 H7 h
这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解0 o& T3 u+ ?" }: P7 [6 B
; O5 d' Y3 P* H* a' z
bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
3 m! S& X' e8 n" z c! y1 @0 Q+ r' w* J+ ~
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;7 A& B* l# G3 H ?
这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
8 M: @, N* H z% M0 j" M& d- b7 B" ?
public enum ScopedProxyMode {
* H& o1 @* z( V/ D7 L0 y DEFAULT,+ S8 s( k5 L6 j; L
NO,
* o, c, r0 T# L" ^. }4 K! C INTERFACES,
9 R6 C+ h: I1 M" ] TARGET_CLASS;: v$ r0 w" D3 Z1 G* T
}
1 |1 i5 {/ {0 [* \3 J前面3个,不讲了,直接讲最后一个值是干什么的。* o( E/ y1 ^6 V9 E
9 |* f% a" _2 }- W当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
1 j e" X& w4 K D4 K# s! P
8 M) |$ Q7 X, U理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。/ ?* d- T1 H3 Y& A; e& D0 s% u
% E! z2 g; R7 `1 D! V+ T/ t自定义一个bean作用域的注解
( ?/ l% d/ P" p5 o2 T0 s' x1 U5 H5 w b& _' R, I$ J2 y# K4 e9 H
package com.javacode2018.lesson002.demo18.test3;) a3 U" Z; t9 n8 K4 c
: Q; W4 b+ L. f7 Rimport org.springframework.context.annotation.Scope;
1 B: d+ U& z6 d0 ~# u3 z9 ximport org.springframework.context.annotation.ScopedProxyMode;
`6 `) [ `: O- R! Y5 x8 F Y- u$ r5 N$ y* l
import java.lang.annotation.*;
. i. \, g. p$ Q* X+ w+ S# y4 g
' w" I9 a: o4 s/ n) N$ S@Target({ElementType.TYPE, ElementType.METHOD})% Z+ r0 G! T: w
@Retention(RetentionPolicy.RUNTIME)
3 O) I0 | n% y H M A" @& D* Q@Documented K# i) n1 Y9 z$ Q+ m8 Q* a2 u
@Scope(BeanMyScope.SCOPE_MY) //@1
9 d+ V5 r% X6 b% k- g) Q, vpublic @interface MyScope {9 t6 Y" m: T" H
/**( O' Y; J! T* v. x0 `& l; j7 w
* @see Scope#proxyMode()
2 n% D J1 O3 m7 I */2 i7 f5 O. {$ k; n3 a: L' A% |; x
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
, ~' g+ W+ z2 j) ~9 B y0 x}
Q: p: X8 G% @. L) r; H0 Z@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
# P* l6 L) a3 s" ^0 x# q% [. u9 h4 L! j1 X, s& i! ~6 A# X3 m$ F0 s; x, m) `
@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS ?* b C6 J1 \9 Y5 g7 S
" w# `" T, B" U$ `$ u' Y" Y@MyScope注解对应的Scope实现如下
, b) ~3 l9 c; _1 g$ o K% K, f: S; v9 T/ V
package com.javacode2018.lesson002.demo18.test3;
8 }! {2 ~+ N/ M( n; x# D n, ]6 e, N0 N4 R/ a1 f: L
import org.springframework.beans.factory.ObjectFactory;) n/ b6 V! D1 ~: y1 F: a
import org.springframework.beans.factory.config.Scope;
. `3 S2 u" f# w8 k( bimport org.springframework.lang.Nullable;
4 Q$ |6 B8 d& i' j9 J: u4 S5 @5 \& g: ~. @
/**
" G( B& t C8 b* b+ i0 ?1 w * @see MyScope 作用域的实现
" w1 g; ]7 I8 A! w; c */) Z2 Q& s4 Q% J0 z; G* i8 V
public class BeanMyScope implements Scope {
& ~1 B& G0 I1 M e5 A5 ~' v% o/ j* Z8 v6 ]3 V1 F
public static final String SCOPE_MY = "my"; //@1: E/ }9 F1 C7 q9 f3 v
/ N8 `0 K P+ j3 K% p
@Override) |4 C( K/ w% b2 F+ K8 t% @
public Object get(String name, ObjectFactory<?> objectFactory) { * a2 ], f! B4 q$ O1 v% a
System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
: y1 [! U2 B- I& [ F( w/ D* o# L, ? return objectFactory.getObject(); //@32 _- A* e) Y) J# j4 T6 P" G
}
6 G( r! o$ l, B0 x8 Y
8 H; G! y Z3 ~7 p4 ^1 c @Nullable' b* q+ _) s, e
@Override
/ d1 G. t8 R1 [$ n9 g+ n' H- w/ Q/ | public Object remove(String name) {
# o& A9 t6 m, }6 q7 D8 X return null;3 }6 }! k( | Y4 t% v! d0 s, Y% s
}
! D' n1 P/ A+ r6 `- \ {: Q8 d+ _5 h2 g! C
@Override
: Q' L N9 Z4 L$ e$ f public void registerDestructionCallback(String name, Runnable callback) {
4 y6 `; k9 G8 E: I y( ?% ^2 d5 ^6 ^4 Q1 x% D2 K, \' K" \' ^
}+ C( H0 B; b+ k
% D4 ?4 r8 G9 Q P# W, ?) c" `" _8 A
@Nullable# n% d" d* G8 `6 h$ z
@Override
9 X/ `- S* _: C' Y% x; r( U public Object resolveContextualObject(String key) {
! a) h" F/ E' l! }8 g return null;. b! q0 ?! V1 k* E, [+ m
}( p+ Q, j- \/ N( g( v C
8 z" S$ _( k3 C# k! j- z% Y @Nullable
2 o; v/ [5 }% _! u @Override
0 k! q1 v) D9 D! i) e public String getConversationId() {
/ n- ~6 J: p. y' I return null;9 S4 w! P& x y* P: I
}9 L8 \8 c' A# K6 N6 A. p. Z* E
}0 _' Y7 `0 o, q# ]( h$ }+ A
@1:定义了一个常量,作为作用域的值- Y# \, F% d; [4 K
! G: z: ?, A' D* b1 v) `9 j
@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
" c6 @9 x/ [( O: [. _$ w4 m5 E$ O
@3:通过objectFactory.getObject()获取bean实例返回。8 O; Q/ t* z& H8 y
$ X* y" P+ [5 p) v9 h; {# a8 ]下面来创建个类,作用域为上面自定义的作用域
) l7 [0 h: D6 q3 b9 A$ d0 `$ v/ W f9 J) w
package com.javacode2018.lesson002.demo18.test3;
; d4 J$ Q+ X' L
) y+ a! {" S# ~+ R% J6 W+ t+ iimport org.springframework.stereotype.Component;. }& O5 m7 h* q. P* C- q2 ~; M
+ m* u5 C6 R* o% ~! [
import java.util.UUID;1 v I$ n, {7 z# f( Z
1 B1 _; m( [, u+ p N
@Component7 ^0 n& M& E/ n# R4 A j* Y8 S
@MyScope //@1 $ k( j- b' j. T) ^6 f5 x3 }: H
public class User {1 C c/ N% f) U
" w5 W- b; h) W; w7 ^6 G& ` private String username;& l" S+ H2 C: f
! G# I6 v, h1 z3 W$ y3 R public User() { $ Q7 x6 g& E* C, y- v& Y0 H
System.out.println("---------创建User对象" + this); //@2! x" x! ]7 A$ u) j, G
this.username = UUID.randomUUID().toString(); //@33 s/ I4 s$ a3 _$ H! I9 ~$ W% ]8 z
} z4 s7 `# P/ x0 P! G" P) i
3 F% F! `6 i3 g: q+ g2 ]) G, X
public String getUsername() {7 j' ^5 q, ]( z6 f7 B4 ?) g
return username;8 L8 v+ M/ W' V
}
3 z/ U7 A3 I2 w/ ?- W; O/ X& U5 M* d
public void setUsername(String username) {
% \ R5 d, s5 `5 M this.username = username;
) D. Y8 D- v) @* t3 ~ }
% h. }. c/ P5 F- e8 U2 m- Z) g0 U: P" G, ]! t4 A# ]& H
}
; T2 t8 P2 }2 ]. d0 y8 D@1:使用了自定义的作用域@MyScope
) `4 z2 M, ]: o2 ?' G( W5 C
% F" S* J4 U: E" R0 C- e@2:构造函数中输出一行日志4 h) C8 p" O3 m
3 w7 L- H) k2 e' _
@3:给username赋值,通过uuid随机生成了一个
3 n0 Z& D% w+ M& }4 r/ ^, ]% t9 {3 j: h* A" C" u9 [
来个spring配置类,加载上面@Compontent标注的组件$ J6 M5 d1 Z( i* |3 V4 Y
# T5 h9 U" S8 n+ L1 F
package com.javacode2018.lesson002.demo18.test3;
7 l. B, ]% o" p3 N8 U. o; V
& p8 w- {. {- }1 Oimport org.springframework.context.annotation.ComponentScan;! W& N+ O+ ] o. J5 f
import org.springframework.context.annotation.Configuration; E3 P& x! M) x: W5 A
3 s5 l5 a& ^/ [. k, x$ g
@ComponentScan
; X" {6 R: c3 l% X@Configuration
2 Q2 q' F0 L/ k, u( ]$ g) U5 a. ] Wpublic class MainConfig3 {+ D/ F7 z( _1 N0 R1 w1 q
}! G, L7 `; V+ Z P4 ~5 a
下面重点来了,测试用例5 |# Y, D: ~( D$ {2 u$ R* `
& r8 K( F1 g) D: }4 Y
@Test
1 |* w- v3 Q$ \$ npublic void test3() throws InterruptedException {
* J4 g! t1 k$ T1 Y+ ~ w Y& | AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 O8 p$ X: f+ l7 k- ^' _0 U7 f
//将自定义作用域注册到spring容器中; s7 \' K% `1 z4 W; S
context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
$ m0 `7 n0 U( H* {$ W context.register(MainConfig3.class);/ f f; [5 R5 T% g+ j) r6 p
context.refresh();
& p) K. j1 u+ y3 b, K
- {/ J: a$ l- `) a System.out.println("从容器中获取User对象");& ^: ~$ j" j+ S& i% W" g' u
User user = context.getBean(User.class); //@2
5 u* U& L$ Z# T# X" Y7 ~! R0 e System.out.println("user对象的class为:" + user.getClass()); //@3
. O" G& l. R3 S: K+ |# w$ @; \+ x
* I% ^; U. N5 R6 X System.out.println("多次调用user的getUsername感受一下效果\n");3 ?+ h7 W. B- o* |8 H
for (int i = 1; i <= 3; i++) {$ t- O6 b& [9 h# R
System.out.println(String.format("********\n第%d次开始调用getUsername", i));4 ? |: d" W `8 m
System.out.println(user.getUsername());7 v" N4 X h0 E7 Y% m/ a
System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
7 x$ S4 N/ B1 M/ L- S& o }
# m% U; `" P- L2 S1 a. s! d2 ^}& G, ^7 B7 U+ I* {
@1:将自定义作用域注册到spring容器中3 W* U. l# M+ ]2 g
6 T5 |! N2 N5 V {% _9 V
@2:从容器中获取User对应的bean
2 E% Q3 D0 {# b$ R5 w L( }
5 l9 |( i' Q0 [7 J@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的5 v/ C+ n1 i6 j+ f1 A+ M
; W5 {0 Z8 s) |* S/ T8 V+ e代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
' z2 p6 Q7 \" @' u" A# a$ h4 G5 c- f& {9 R) n* o& M9 e) P
见证奇迹的时候到了,运行输出
7 z2 y; g9 J! X& b O
6 F ^: v4 {) D8 j$ K2 q4 \从容器中获取User对象5 n2 h% [* ~1 X# `; D
user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331273 x$ Q V4 x, k$ r% H! m2 J
多次调用user的getUsername感受一下效果
5 A# q2 v, i0 S' f0 R
) g& U8 v9 ?9 J& F********
4 I: P% D0 j& | p- a第1次开始调用getUsername
% k( N: o: J. ^$ I- Y* p+ }( h& hBeanMyScope >>>>>>>>> get:scopedTarget.user9 n8 `! a0 L7 V$ j
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
- u, V) o0 f" r7b41aa80-7569-4072-9d40-ec9bfb92f438
) Y% I5 ~1 S# q6 P" x4 Y2 g) G2 o第1次调用getUsername结束0 F% K6 N( s( {9 O" @" d
********' c% o. H8 g5 j: t5 H+ ]
p; Y! i' O' H) [( i( k9 L********5 k/ @# R8 `7 }9 Q0 ?2 K
第2次开始调用getUsername
* ?1 [3 R C l4 I+ LBeanMyScope >>>>>>>>> get:scopedTarget.user$ Y4 I* ^$ Q" I- e
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
; Z5 M# M% f! U7 }5 V3 N: m/ ]* E0 |( h01d67154-95f6-44bb-93ab-05a34abdf51f
; V: N6 [- r6 X+ ]/ X第2次调用getUsername结束 Z% a4 f q0 @) r- a
********
8 ]! e2 O/ h' |9 l9 t
1 S% a7 ^+ }5 _% B6 [" j********/ G/ N" \% A. G5 J8 B
第3次开始调用getUsername
& c) p+ g3 W# O3 i3 t" [( oBeanMyScope >>>>>>>>> get:scopedTarget.user
' d6 j" H, _/ z) s" d. V; j---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
8 E& s8 Y9 j3 r, z: B76d0e86f-8331-4303-aac7-4acce0b258b85 X) o# Z, A4 J2 t1 n0 H {. ~
第3次调用getUsername结束
2 s7 Z& W4 m' A********
) B1 f) T1 _% W& e5 i. o8 L. u& e从输出的前2行可以看出:
+ R; N5 G) e- e- T3 H K
3 C1 O8 t2 e0 _2 G l, O" d调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
2 ` y5 M% v: E& M; c* z5 n6 B# Q
2 P) s2 i0 h1 ]( N& R第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。1 {2 K7 O( \' U( a
3 z( h& H1 X; H' |. }后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。! ~9 o$ x7 _' [
: |3 c6 R8 |$ K; Q2 x" g5 X. L通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
5 V* ]& r) W( R$ K# J ~: Z9 a5 {+ l# `. u( a( [& Y8 Z
动态刷新@Value具体实现
9 a# \% j/ A1 Z) L& P$ v
$ q: A7 a" I: E) y- f那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。" A* l; \6 b, r: [6 w4 \ P
- M9 U! |. j, u6 Z4 \: w. f先来自定义一个Scope:RefreshScope
7 M2 ^1 ~4 d. H6 E1 v t, u6 M9 t8 Q2 f; D! P1 }
package com.javacode2018.lesson002.demo18.test4;- m9 ^0 k$ k: X6 R6 U
/ V; H' I0 T9 N0 z8 f6 cimport org.springframework.context.annotation.Scope;4 a8 ]) w9 k9 H6 K, i3 X3 ?3 G, X
import org.springframework.context.annotation.ScopedProxyMode;8 q7 v* \- Y% |4 k( J, y, t2 E
0 a6 W6 o& A) C. W$ D& z
import java.lang.annotation.*;
3 J$ @! y! t E0 a6 b* E* K$ ` T2 ~: c
@Target({ElementType.TYPE, ElementType.METHOD}). g2 D' f2 b6 D- _0 s( d. A% x/ G! T' e
@Retention(RetentionPolicy.RUNTIME)' g7 D( Z5 f1 g( c
@Scope(BeanRefreshScope.SCOPE_REFRESH)& Z$ g. b( ]6 ~& X2 {. f+ o& D9 X2 \
@Documented
* a1 v3 v1 M2 K) Z- m! xpublic @interface RefreshScope {
5 Y! i9 K: _8 ?; t ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
) l) |; Y E' ~# c$ `- y! D}
4 O0 j& G2 Q1 c) x O9 j9 X# p要求标注@RefreshScope注解的类支持动态刷新@Value的配置+ D( a3 s8 \- n. r' C
" e7 P" g* ?1 C! @4 i/ L
@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS/ R7 e- A! h0 s7 y K
1 w i0 t( t1 V X4 E这个自定义Scope对应的解析类
9 u, K/ a% m3 V- b( \, l' [) u3 Y! @# m+ V, w2 |3 u7 e: ~
下面类中有几个无关的方法去掉了,可以忽略1 a+ [% }0 j0 c6 k2 q- y
( T6 f5 ~0 ^1 z: r# [$ y1 [( C0 Mpackage com.javacode2018.lesson002.demo18.test4;
f" k5 y$ G1 J3 Q
; ]/ o7 I9 x' v, A: i( X3 q' q* E5 V( k% o' m. p R) X8 s. S. \4 B
import org.springframework.beans.factory.ObjectFactory;( {- i9 r4 K. U' D; b2 q
import org.springframework.beans.factory.config.Scope;" N) r5 L1 x2 P8 x l' ?+ I# y/ y
import org.springframework.lang.Nullable;
6 |. I1 [9 P) X! }" `5 K# Y$ d5 \
1 X8 X% i2 t, W! r! A: |3 jimport java.util.concurrent.ConcurrentHashMap;
4 K, }2 J( ]1 V
( F# Z# D' n7 F3 ypublic class BeanRefreshScope implements Scope {
, w5 W4 g: s( g6 p" ?1 h) E( ]& s) O# N1 V% x
public static final String SCOPE_REFRESH = "refresh";3 r8 t- a, A! i e
; |! u! S8 r$ D( u* n- h, a
private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
/ E0 T7 {# q2 U5 Y2 t3 A9 [6 W1 F6 V2 R9 X
//来个map用来缓存bean
' d3 [' }/ ~8 r" V" s2 N; b private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
8 g4 x. K3 {, Z7 R
, S+ b) p$ V: I private BeanRefreshScope() {& l" U& d, F `: ]
}
2 K1 B" s' T0 I7 r9 {6 y8 }1 L5 E" M5 y- ~) R( P# K
public static BeanRefreshScope getInstance() {. ~0 P1 q" p& a4 g( i/ o( r
return INSTANCE;
( `, r3 _, i, h3 x+ b1 h3 a }
2 n" I n8 R2 a) c) F1 x( r. ]& J8 ?( `# r% f% w& w
/**
% \0 q* f# `7 J; }6 y * 清理当前- G$ b1 G1 ?! l/ l. B. {- F, C
*/; Z, ?5 @& y* \
public static void clean() {
9 v9 }$ l Z7 T INSTANCE.beanMap.clear();) q' x- B% d9 J9 T8 J( S1 ~2 Z# [
}
) }$ ]9 |9 g" {. s( Z# f
I- G' A; W1 l* M1 v/ D( U9 } @Override% s. z; S7 ?# Q9 i2 s
public Object get(String name, ObjectFactory<?> objectFactory) {
8 l+ n: Z. x; o6 \' l$ z" Y Object bean = beanMap.get(name);
3 L/ J3 V$ {! Y- w* |6 K6 Y% ^1 q* C+ w if (bean == null) {4 T( W8 d% x% K% O9 B- N
bean = objectFactory.getObject();
9 G. N, M- o/ K7 w0 V) d beanMap.put(name, bean);
9 n/ X3 q+ A6 Q& B# Y# _; ^, H }
+ G- k& b( A. C* i2 [ return bean;
3 h+ d! P7 @. w2 C3 Q! G! _9 m }; Y6 _! m! D* ^4 F$ Q, W5 P
4 t; R. v7 o1 L
}
- w" P- E' ~# i4 ?" T上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中- Q# I* V8 f4 M) \" }: E4 ^! l
5 B+ K4 `) x4 {$ U! t5 B
上面的clean方法用来清理beanMap中当前已缓存的所有bean
h8 s) Q- Z2 w" [6 K0 D, ]/ ~3 ]: ~
来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
) G% Y& u+ B7 ]4 s& M4 L. r+ g1 M& |+ d
package com.javacode2018.lesson002.demo18.test4;; p+ P/ Z9 ^1 ?6 t7 \, v1 r
9 M; ^1 K) R, _import org.springframework.beans.factory.annotation.Value;
: A, @4 u: o9 ~2 mimport org.springframework.stereotype.Component;1 D3 T" F l) v2 \! D
R6 G' ^$ J" h5 A8 H: f/**( [0 N% e4 O; m9 Z+ }' O
* 邮件配置信息
( X/ d8 J/ b: O. ^' [ Y6 e */! |2 X( Z0 y- m1 P( K, O+ Z
@Component
7 {9 x4 W, T% p% f4 \- F* @@RefreshScope //@19 m2 B& s1 }9 X6 d4 E& e+ P! U! z M$ w
public class MailConfig {
7 U, t/ p- E2 h
+ v, f F: @! s6 l0 x' I. t) L& A# O @Value("${mail.username}") //@2# Y4 x, N: C0 n" W
private String username;
" n& U2 i( I; _: w/ u; [5 ?( [3 N2 R/ ~2 J- T
public String getUsername() {5 f; j% s7 x- M0 O8 }6 r
return username;; ~6 ]# C$ g5 o0 R
}
& H5 ]2 j% n7 p) T7 Y+ m4 A+ V' ^& z6 k+ Q' y( j* b; @; E: e3 x* d
public void setUsername(String username) {
$ |. u, H& e9 [+ h6 i" h3 g, G this.username = username;
, a2 U! `( |9 i6 o }
! W1 ?% W# S) n8 x7 S. w5 ]" E9 I( \& Y, |5 s
@Override' i* \( L; E2 e+ o. E+ o
public String toString() {
0 A1 c& w8 a/ B% L: Y. ~! [8 @( ~ return "MailConfig{" +
; ^$ `) w. T/ l: Y6 O9 A; n3 d "username='" + username + '\'' +
+ \ U' I$ z9 `& S& n '}';
6 ]+ ^- m* U V# ?7 Z9 b7 B# _$ N1 { }
/ e" O# V% U1 G6 h+ C}& w# k m: H7 L9 o, X
@1:使用了自定义的作用域@RefreshScope
# @3 U* X3 M E4 ?% R/ K! r9 l N7 U r
@2:通过@Value注入mail.username对一个的值, D3 q) g" R( @$ j3 w+ T; V
2 I! q6 q; D: l7 @2 z9 j' E9 A7 ~4 h重写了toString方法,一会测试时候可以看效果。
. t2 L8 D T* L$ P2 s8 d8 @1 ^* o3 U" |& N6 z0 t3 c! c
再来个普通的bean,内部会注入MailConfig' _: ]" c. a* m2 @ Y
) d+ n4 \) O: R& Y) l9 ]* G% @. spackage com.javacode2018.lesson002.demo18.test4;
N0 t. G( y" R4 N0 x+ o" R0 @8 X7 C- z& Y
import org.springframework.beans.factory.annotation.Autowired;
: e! F% f0 S$ {! Pimport org.springframework.stereotype.Component;8 |! y+ p1 W0 ]% C1 u" H
' z2 P3 K( @4 P5 @0 s@Component# S( e# b" f* E+ ^2 ]5 }+ M; V4 f: h
public class MailService {4 O/ }7 B: {7 U; N
@Autowired2 G! k x( k# A# C7 O
private MailConfig mailConfig;0 a3 X5 i9 I& b/ b6 x6 G1 T
+ e3 B5 x M5 L |
@Override
/ d8 I t# {2 }# i3 M public String toString() {1 l. i6 {& ~0 \ u4 v8 |
return "MailService{" +# X3 q4 S4 i' `" K$ T0 Z, i- E4 \
"mailConfig=" + mailConfig + ~) ?) n/ O1 R
'}';% m6 O }9 z5 v
}
K, V5 R! }6 r9 N}
4 b7 q. Z: C' |; d' _代码比较简单,重写了toString方法,一会测试时候可以看效果。* R; t# @2 o3 S+ `6 |
5 t6 ~1 O, ^( O @ g, }* N来个类,用来从db中获取邮件配置信息- R n+ b, K7 D( `- m8 H
z' |9 }) m( @9 u" Ipackage com.javacode2018.lesson002.demo18.test4;
& T2 A4 L# F! H& K( s# W$ B
: f1 y+ A: I0 oimport java.util.HashMap;
0 L4 L! s6 O% R9 F' I$ X1 z6 y, oimport java.util.Map;
+ H* ~$ i& d$ y$ ^) D Fimport java.util.UUID;
. o9 u+ p( v, M" ]$ K1 T
$ F8 ~- t: ?& Y5 J: spublic class DbUtil {
; e F7 b9 @! K4 x /**6 b* e/ m% A" Z( q
* 模拟从db中获取邮件配置信息
5 O/ r# `- g2 {/ x* O' K% e *
$ `+ O8 g2 J$ _* {" |& E * @return
1 X: z, q' h8 L, b */# O$ x# w6 @/ a: l
public static Map<String, Object> getMailInfoFromDb() {9 W% w& d! Q5 j4 J
Map<String, Object> result = new HashMap<>();
( o; p8 ^! m2 U( w& w7 l result.put("mail.username", UUID.randomUUID().toString());
& I; |6 _! o1 H2 J& F( ~- x' ?4 {( ? return result;& A+ ?5 w& y0 P n
}
6 ?5 U/ a4 n1 \}
* D; N: f2 W% O! m7 h来个spring配置类,扫描加载上面的组件
( h' H) Z% [% r. u
5 k9 }( N: C' A5 `; T8 }package com.javacode2018.lesson002.demo18.test4;8 V: L; c) `% c5 Y) d2 R
8 _6 u; T, ]. a* T
import org.springframework.context.annotation.ComponentScan;
4 Z2 U) n1 p% rimport org.springframework.context.annotation.Configuration;/ ?+ D4 F4 d7 K) P$ I
8 @3 A- o" j1 O0 u
@Configuration
* e) D* ^& U* b; ~( {@ComponentScan
- c! _9 r" q% e) O8 @public class MainConfig4 {
$ q- P9 I# U; u3 B& o( _}, \( o6 Q% d9 ?/ z9 a9 z) m! K3 u
来个工具类
$ q; H7 h |9 j6 a, h$ X
: [/ Q# j$ i5 X8 a; ^" z* {! C内部有2个方法,如下:" H* ^; \. D- [8 z: s; D" i& H
5 H, [: ^3 p5 Vpackage com.javacode2018.lesson002.demo18.test4;
) l' S7 J2 e+ A5 Z: A+ p. K! j/ T3 @
import org.springframework.context.support.AbstractApplicationContext;
( S4 L8 f" v% Q/ d- h6 simport org.springframework.core.env.MapPropertySource;' a' K) p8 |- t; {% a+ y6 O
' _; {% l2 F4 e+ H: s0 A, H
import java.util.Map;( {& T% J' I, N2 p- r) k6 Z1 Y
. m+ t7 t5 A J& | q% v8 \' I
public class RefreshConfigUtil {
+ ~) }2 R7 U8 f( G; M2 C /**
4 j+ w; D# R; R( ? * 模拟改变数据库中都配置信息
( K3 K7 F# a' v& ~ */
7 L: L% c) Q$ m6 r0 ~+ E: r+ v public static void updateDbConfig(AbstractApplicationContext context) {& K, P: X5 V4 T3 v& l" O
//更新context中的mailPropertySource配置信息
7 W: s( N& T6 i% W' _- r refreshMailPropertySource(context);+ Y6 _& t# ?. U+ b/ p
3 K" C7 G& ~# ?+ X/ V: R
//清空BeanRefreshScope中所有bean的缓存
+ p9 t. H# Y' J; { BeanRefreshScope.getInstance().clean();
. S$ H, ~# X; ]0 q0 W# _- j8 e }
& H6 g( E1 n' E3 y! L# _ h. ]( }6 R+ I
public static void refreshMailPropertySource(AbstractApplicationContext context) {7 ]! L4 A% G$ G/ d) o& ?8 E
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();/ c# J. U3 D+ Q* h* E4 f1 B6 c L
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
, [. `/ }! Y7 ?' G$ @/ F. ^ MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
/ N7 ^$ ~0 }& r$ R; W* @ context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
6 u/ p, ~' E) A6 b/ _( p }* E- T9 _: A! s, F5 W
) r& O# L* `; x0 [! v}" P6 n5 p& w$ q! Q! g& Q
updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息% E* g8 l/ g- K( A" y1 z4 D$ S
2 s5 i" u6 _) G7 I0 V, {, C% x+ l
BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。. [$ V& w! j6 A
( ^% T$ u' _* h0 u% ~, c来个测试用例" ]. y5 p* }2 L, k. k
; m9 } o' a3 t% \! l@Test- a3 V7 T4 g- k D; g" ?# S$ F
public void test4() throws InterruptedException {% I4 K1 r5 Q+ H0 @% J8 r4 B( s
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
2 L, `2 G" p# s/ \5 S" _ context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());0 Z) T$ g0 ~9 ~! n
context.register(MainConfig4.class);
5 P b& _# ~- t, b$ V; G6 E" Y* B //刷新mail的配置到Environment# s. \2 r+ P) n3 o) B7 I, l. I
RefreshConfigUtil.refreshMailPropertySource(context);( v/ t/ @: t. Q( l: ~3 e
context.refresh();
! V4 H% E/ J5 f* Y
0 ]8 E% W) K* G/ r' }) d MailService mailService = context.getBean(MailService.class);
% d' t# x6 ]' R) j t6 O7 A System.out.println("配置未更新的情况下,输出3次");* N9 Q4 h3 a/ D9 `2 d
for (int i = 0; i < 3; i++) { //@1
2 z' W$ `0 e0 k6 ~" {4 Y, f0 \ System.out.println(mailService); ]6 M0 Q& n) M# i. O1 p
TimeUnit.MILLISECONDS.sleep(200); `2 Y; _" L3 r) i" d+ V0 t' l
}3 S! K# y5 u/ N# u4 ]* |9 x
: x4 a9 w7 A5 f/ e8 @0 J* R7 l! {
System.out.println("模拟3次更新配置效果");# K+ X) |& I" a' z$ \$ s0 H) |& W
for (int i = 0; i < 3; i++) { //@2
% N" p% {& H% }1 N; ^ RefreshConfigUtil.updateDbConfig(context); //@3( u$ n. L( Z' @/ [3 M# k, U: T; g
System.out.println(mailService);8 d1 I2 d: Q& `9 z9 o/ @
TimeUnit.MILLISECONDS.sleep(200);- B) I( T$ U+ Q% n i2 \' n; ? j
}# A. ]. @; u* i' \* v, O2 H/ D
}
, C& q. D/ o6 _( t2 Y" m@1:循环3次,输出mailService的信息7 M& `. T2 X3 L
$ a' Q: w7 U! x/ c
@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
7 a" X' |( O( W, S3 s/ X, l5 ]% A s; I: i" X; F$ V- f
见证奇迹的时刻,来看效果
% k8 o) g' w1 ?9 F( j. A2 R3 v. R1 F, k# x8 s
配置未更新的情况下,输出3次3 J* U$ m& j9 z1 X4 F
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}0 K4 O# A! q1 Y
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
* Y) c5 Q+ q; ^: cMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}4 E' Q$ a0 _; O2 Z8 q- J5 _+ _
模拟3次更新配置效果
9 `- A3 s7 R& _/ g! i! s* W6 P4 EMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}7 p+ S [8 S* u% a5 x: }
MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}- F3 v, _. T) s; W# ~
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
( L' U4 ~" \& s2 [% i0 |上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
: s, k! ^8 v9 c1 q7 L, t4 ]
, A! Y9 J$ o4 y1 O小结
( s: f! k6 j4 c v% ~6 g+ }
4 v c0 q2 D5 F X P动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。, d' e: q) M, D- c4 e" N$ K* N) ?5 v
4 M7 j- D8 J5 e3 m3 }有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。- ^ T: r9 G2 O) n& ]2 m) e' {% s
3 Z. R4 u4 @9 ~
总结! \; f2 h; I1 z( K5 Q2 y s
/ ?; r9 {( I$ r7 x4 Z3 n/ C4 {
本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
$ A" A) U6 Z$ t4 Z5 s3 v6 X
# B4 M! {6 p% X/ I$ @- |5 j案例源码
- M6 P( n7 r, \8 _
5 K l+ w) L3 k0 y" ], f( F( Jhttps://gitee.com/javacode2018/spring-series
+ t& } U+ N* \# s# {2 q路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
& C# I7 b3 y& v6 `
+ b! l; G% [. u$ d# L& {9 sSpring系列9 _! ~% u2 l7 [% @: O
& L) f7 j1 X& J& w! O( o
Spring系列第1篇:为何要学spring?
* v, {, `5 \2 f7 `
% ~/ O9 a7 G3 t7 o) o5 |Spring系列第2篇:控制反转(IoC)与依赖注入(DI)+ G8 }4 U2 _8 L7 r( J; o5 P
8 {/ p$ j9 S1 Z5 [2 P+ {Spring系列第3篇:Spring容器基本使用及原理: w7 i4 d7 D" [. Q- c& B9 U. ?$ G9 \
7 @% Z9 _0 y7 p# F1 w; aSpring系列第4篇:xml中bean定义详解(-), J1 h: V9 `( {- v- \) f$ S+ B
5 i7 m; x; s' ?4 y/ F
Spring系列第5篇:创建bean实例这些方式你们都知道?# R& {! ?2 f# \/ g4 D+ ~
) |8 D' @: U2 }; ~/ `Spring系列第6篇:玩转bean scope,避免跳坑里!" `2 A3 V1 ] B5 ]& j
1 K& r2 z8 T4 v$ n$ M m# i& NSpring系列第7篇:依赖注入之手动注入7 f G0 b2 b( z- L5 }1 `
6 G+ v5 v& I. Z9 C' A
Spring系列第8篇:自动注入(autowire)详解,高手在于坚持5 ?9 F" O: L( E$ y
, p; ?0 A+ s& d) R- u8 a+ CSpring系列第9篇:depend-on到底是干什么的? W: q6 Q2 R0 ~
* N( A6 J8 a* k- @# I1 s% T2 H
Spring系列第10篇:primary可以解决什么问题?0 \5 |9 ?8 u1 E. _
, x- p2 P" L7 l( i( }! ISpring系列第11篇:bean中的autowire-candidate又是干什么的?
) q7 ?6 v0 Z5 ]. Y$ ^$ B# o" |; `5 `/ v8 W- ~) r4 `/ u- t
Spring系列第12篇:lazy-init:bean延迟初始化
7 m0 p u6 \3 P" I ?! g% V" I- z& g2 z
Spring系列第13篇:使用继承简化bean配置(abstract & parent)' k9 E5 H7 ^0 a2 v0 {
! _, ]) u- [" e+ MSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
2 j3 V0 H& o7 a8 n4 G) p9 y) ~9 g, [! C+ c" @0 @% T
Spring系列第15篇:代理详解(Java动态代理&cglib代理)?' Q0 y; Y1 d" d# T! j- ~, T
+ Y5 `* O7 i$ w$ l k2 NSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
% U+ U: B" b$ K2 B% g' C% F1 N3 g! X" o1 c
Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
" |4 o- Q( h9 x0 {! z" n3 ?0 T- u5 L% D% e% ^
Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
( h; r0 |% K2 k) d! Q. k
; q# @+ O- _9 Q. ]8 h# vSpring系列第18篇:@import详解(bean批量注册)2 X) N4 f# l: M6 e2 W) t$ ]
k- i: x8 s8 G% g3 M* ySpring系列第20篇:@Conditional通过条件来控制bean的注册 c" ]5 u+ \% R" Q- w, \- n1 v
0 m$ z: r! p: }& ~& b) w: FSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier). U1 w" B; W( L @7 T
@9 f O2 n0 f6 I( OSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解" w0 k! o4 W" m) f5 O
5 j% S3 [! r; P! t: K6 HSpring系列第23篇:Bean生命周期详解
4 ] h, M3 y% C, T x; m. J o1 v- `$ ?
Spring系列第24篇:父子容器详解
, [! I1 ^8 B+ C/ c* M& U1 F- e* P; B1 K* e2 Y( C
更多好文章: \" n" S% ?9 H* B' d
6 c* T9 S% \$ g! a2 \7 j0 O1 \Java高并发系列(共34篇)9 Q) U0 r4 g6 E. n" @ o4 F
0 c" Y6 J- e' O* \
MySql高手系列(共27篇)& f/ O1 W( u. v- |) j
* G) ?0 c( K7 L& g9 L
Maven高手系列(共10篇)! P3 ^; J3 K0 ?" m
" c( y, S# @/ W1 h- h
Mybatis系列(共12篇)* P, |+ d& ?3 M- U
" c) Y# M1 j- @0 j) o' G
聊聊db和缓存一致性常见的实现方式! i' W# f( p' A# L7 L% N7 W5 I
6 Q$ P0 b4 `+ s
接口幂等性这么重要,它是什么?怎么实现?( H" Y: V% @/ q) ~4 }. k
, T# Z8 Y( P" ]8 h" U5 G1 |+ `泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
) P2 @$ x. H# Q+ @& K% y( W————————————————
1 M& J9 s3 M4 [; w+ B版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。7 v+ D0 D6 O4 X. V* F& H
原文链接:https://blog.csdn.net/likun557/article/details/105648757% i; h8 ]. M7 {$ m
! J; d ~* `3 U4 o( s' |; m6 Z( s4 h1 Z, `
|
zan
|