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