QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5206|回复: 0
打印 上一主题 下一主题

太狠了,疫情期间面试,一个问题砍了我5000!

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2020-5-23 11:01 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    太狠了,疫情期间面试,一个问题砍了我5000!
    # ~! b5 a$ k9 M& K0 [疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!5 B% C( Z* \7 Z
    ) t' L2 J1 V( o# |! p' Y
    面试官:Spring中的@Value用过么,介绍一下
    ! Z3 [% Z, f8 ?# L
    6 [8 G, d" T3 A我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ; ^; I4 a& A! Y! A" [4 K/ W+ l1 F. p" D
    面试官:那就是说@Value的数据来源于配置文件了?9 V$ b  v3 V0 ]7 o/ s% b

    2 s6 U  Y" m% O6 m0 D4 B: J我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ; m: {$ u) n) f* @* d( M
    8 a; u7 D1 w0 x% v$ t6 b; L面试官:@Value数据来源还有其他方式么?
    7 u% z& c5 q5 B$ Q( c
    + b1 ?$ U* U$ x. d  u8 Y3 q  S我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    , W  V* F# X$ R( X1 L8 P0 p3 M0 @- K& l0 n* i% D9 t
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    # ]9 m) Q/ n7 e3 n: v
    - j8 t" R# \- @/ n# Y我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧2 o1 @% C- V8 n

    ( d% s& a$ P  Y8 w9 V  I# `面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?$ K2 G( w% ?, S# O9 S
    - ?& F" {9 n4 p7 I# f- ^
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能. l& |4 ?: U: U) J
    + I( Q' x0 V8 U1 \4 e3 V+ d( P
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    & d6 V. |% I! q6 V; h0 d& y% R! E  |4 Z1 t+ I' A
    我:嗯。。。这个之前看过一点,不过没有看懂1 d( p# I4 b$ m/ b0 r2 V4 a
    5 m& S7 o# V8 K6 G- j; I
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    7 g: g4 d. Y! d! J  `2 h1 D/ V6 e
    9 d( I2 w, x2 t我:3万吧
    % A, R# g8 C; q" H" N9 X% h7 w6 }$ Q
    6 b% s; |! A2 C面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?& T: i: l8 b3 b
    9 H9 D4 h0 V$ Q% ~) @' v8 i
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    ) x6 ^( g( S; s* X+ c" L+ C
    1 w0 b. o4 a+ l- T1 T面试官:那谢谢你,今天面试就到这里,出门右拐,不送!: d; r4 ^! p- c: b/ ~

    # n8 X9 q; w, e* @" D- ~# [我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    . I% d1 S+ }* T; \) h, \+ y4 k$ ]: {6 U( T$ m
    这次面试问题如下2 t1 j- ~& f8 v' D0 O+ e0 X

    6 E5 d$ O2 S* Y! [% e4 P- H@Value的用法4 e+ d0 M4 U5 i* E# ?: o
    ! M( l7 Y* W- R/ l
    @Value数据来源% ~6 u! m* c+ ]6 }9 U; \7 u$ @
    2 t4 d% [3 F- b. g0 b$ ~
    @Value动态刷新的问题2 X0 O! o2 z* N: P' H; r1 u+ x

    " R8 {( I. w$ x! S' K下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。: Q4 n6 S5 M5 T, s% V7 r
    7 w! S: @% G% I( \
    @Value的用法
    6 d$ W  j% g! z+ e" f6 Y
    % z- }! ]. W- u5 r系统中需要连接db,连接db有很多配置信息。& X9 q7 K& t- a. A1 V, G3 a* C

    5 ]$ J! R- \) @( B& u系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    ) o: [9 y, t" y5 Q# I( k4 m) e1 f& p* A$ n8 A
    还有其他的一些配置信息。; z4 w9 [- o$ C% X; v
    % x( f) S( C% d8 R  }1 Y1 c9 K# V
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。1 x$ n6 ^8 e' P% ?! A
    ! D! s6 W* u) N) y, P) N' X
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    2 }, [6 F" N; b# g6 J4 Q5 H1 f5 b" L9 H8 h( `
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    4 m' |1 `: v& G7 t4 h4 {8 Q. |; T9 c0 E# s
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
      \: f2 j2 \2 Q7 A4 J! j+ ?" z3 B4 I3 d
    @Value使用步骤; h3 R+ o, T* T0 _: F- f

    7 |  b! E& y: P+ z9 g- H2 J0 j! k步骤一:使用@PropertySource注解引入配置文件
    . f! K6 x/ a0 O0 u. G! d. ?6 r( }5 K: k0 i1 q% D
    将@PropertySource放在类上面,如下
    # n. Q4 s% C  r+ I/ X6 Q8 a$ m$ Z2 l) o, }' z% `) R, M
    @PropertySource({"配置文件路径1","配置文件路径2"...})4 \; a+ b8 ^0 m2 {6 w' i$ h
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。, w2 s* W( m7 ~4 ?2 G3 ~

    + K. S# k! c7 H2 r* {4 R. C. ^# u. l如:
    : \- T+ t' q* P
    3 g3 j4 v0 U& A$ U1 f@Component: t0 }+ d& m. t! j4 J# Q+ I
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    0 f8 G8 i# `" e: K6 h4 k0 u& Upublic class DbConfig {
    5 {" e" G( N$ V2 B% t& Z. l}9 O, `6 |) _/ k5 T% Y( H
    步骤二:使用@Value注解引用配置文件的值" z1 E' _% o5 V6 r: h0 b, c- N

      ]2 u; |  J  N4 |" _. x通过@Value引用上面配置文件中的值:4 r- J% i( ?) }% h& B" y" w

    2 [* x1 C* l7 p4 Z7 E( J; I语法
    ( u0 `3 o+ \) Z$ X6 e( ~* ?4 h- J/ b% N$ }, W1 g9 q
    @Value("${配置文件中的key:默认值}")' n' G4 I# b( g
    @Value("${配置文件中的key}")! @; S7 g1 g9 R' R4 ^
    如:3 @+ i0 ?4 P0 r0 B  K7 a' c1 q

    : H$ `3 n2 j( _3 f! }7 k1 M* M% ?% a@Value("${password:123}")# L3 u, b3 a6 g8 i3 B4 @
    上面如果password不存在,将123作为值$ u' U- Y( I7 U) _, i

    , ~  ]0 k. x: F9 S% R' D' C@Value("${password}")6 K* {% X9 N. W
    上面如果password不存在,值为${password}
    - O) ]% s* C3 P! [6 x
    2 S# q( k' M7 O) e假如配置文件如下
    6 d/ S# ?5 i$ K9 A% i/ w7 {) H" {1 ^5 x0 m
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8: r) a# c- q" e. O
    jdbc.username=javacode" E- x* q* l: k
    jdbc.password=javacode
    7 q; \7 p) K- r: H使用方式如下:
    . x* A6 R' q1 F# o" i/ v& L) l7 Y8 \- g
    @Value("${jdbc.url}"); U! [: ^2 |& g4 j- {9 \  {8 Z/ g7 ~
    private String url;# I3 e7 E) I1 K
    , k% {- u$ g( c, i7 [
    @Value("${jdbc.username}")
    & m6 e& V& n! A- a/ }5 P1 Aprivate String username;9 w8 L: M/ ?0 P% }& P9 H2 B

      C$ h. V( a; I' G, i) u; K1 t$ {@Value("${jdbc.password}")
    * a6 H' J2 o4 z. I( `: j5 P6 F! yprivate String password;/ E: N3 g! l+ r
    下面来看案例( X. U) _, `  Y/ B# t7 g

    % f7 n- D0 M6 H, }案例- b+ b0 v& ?% |! w5 G, K" `3 @7 C

    / g- @: @% b/ |来个配置文件db.properties: P: z8 D  v* ^  j9 `) h
    4 ]0 R# ~- _- _7 e
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8) l- Y7 V2 f6 C! g
    jdbc.username=javacode
    ' p3 ~: _2 n8 [0 U" k) j. g) a7 bjdbc.password=javacode
    4 p. _  m9 z* t来个配置类,使用@PropertySource引入上面的配置文件
    5 {2 y# M& P9 Y+ u1 U! b9 Y0 }
    ! f5 i: G) T) a3 xpackage com.javacode2018.lesson002.demo18.test1;
    % ^, N3 J5 D9 f4 N
    . E& M% H8 F) ]; fimport org.springframework.beans.factory.annotation.Configurable;9 F$ \' c' j. E1 m9 W2 B- s
    import org.springframework.context.annotation.ComponentScan;- d8 A5 O6 ?- O3 e5 H
    import org.springframework.context.annotation.PropertySource;
    * E7 G+ D% A4 M% R
    , P2 f1 @/ o4 d" n+ p; Z@Configurable9 S9 `+ E& t/ A9 ?9 K8 v2 |/ `
    @ComponentScan
    8 Q& L( `6 b" x@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})- p, k* T+ F* w
    public class MainConfig1 {' c. {, H$ ?. a2 v* ~
    }
    - x% W6 S" o( q$ Y6 _  E. Z来个类,使用@Value来使用配置文件中的信息3 ~4 L9 w& F9 N8 z: Y

    * X2 J0 m0 X" b. qpackage com.javacode2018.lesson002.demo18.test1;: L6 q# ]3 [& K/ g
    4 l. _' q! t" I8 r% N* @4 Z0 M: C
    import org.springframework.beans.factory.annotation.Value;
    5 U' }  L' I7 y! F& Z  a9 F- ximport org.springframework.stereotype.Component;
    9 M; w: i- e1 n" U; F- J# t, [) q. \) T' M- ]) p1 H
    @Component# ]1 p- ~; j3 n2 z8 |. K
    public class DbConfig {" D4 b2 ~2 [$ d

    4 J  y, \5 |# Z8 L/ R: T2 e& o    @Value("${jdbc.url}")% M* {1 X5 y7 _& ]8 `! ^# W3 H6 h$ o6 G# W
        private String url;/ c. J3 n7 q# [3 q: K
    ; d) z+ l7 s* G# \% n, |- l
        @Value("${jdbc.username}")
    ; K: c) J/ V& @+ \5 P    private String username;. }" e0 P/ I4 |. m4 Z/ g( W

    & B( ^) e1 o( T5 I6 {    @Value("${jdbc.password}")* L& B/ v& o2 A* M  u( h2 B7 q/ ~
        private String password;* j4 O  ?6 g" D" e0 F# L, x9 l4 C5 k
    8 o' j8 ^& g' S4 v( f+ U
        public String getUrl() {5 B$ d& D5 K8 {+ J: p& ~
            return url;
    8 p. C4 ~3 A1 u  y- `8 p* }- k' Q% `3 f    }6 U+ O7 ~/ m( |6 {

    ( h2 U7 b6 L* U: z. I) X    public void setUrl(String url) {$ V( ^# i9 ^+ U- o( z
            this.url = url;
    + M4 K3 \9 s9 Q: g% ?# a    }+ Y$ r+ W) [' i% Y8 |! q) V. _
    3 e) o/ M: r; u8 ~& O3 y
        public String getUsername() {3 |9 u6 P$ l5 j2 ]
            return username;! z: N# F! Y8 _: p
        }
      K- R! ?1 @+ y: z+ o/ F
    3 }& P  d" {" {( P" n    public void setUsername(String username) {
    0 h, ~8 j) ]  v9 v( ]        this.username = username;  ?3 V: b3 K) t# A. G/ t
        }* A( i+ M! u% I' c6 Q1 d4 w
    1 V+ S9 ?2 P0 F+ [3 Z( Q
        public String getPassword() {
    ; S! H( ^# B# @3 Y9 l! i  Y) J        return password;
    . {, ]0 R, z$ j1 c; {* v1 g    }8 l  N% A3 g3 k7 K- d
      Z* {( ^$ I4 G' W" h' W
        public void setPassword(String password) {2 Q8 o3 `# a" {. B6 [1 Q" x
            this.password = password;! O, y3 e% h% O4 p) b& d7 ^
        }
    ' J( d; C" y4 ?7 p, S- W
    0 E5 v( F2 q; r    @Override
    2 L/ Y+ N& U: u. Z    public String toString() {% n  C- d; k+ m/ d
            return "DbConfig{" +
    6 B* G4 Z9 Z. ]+ o6 i. \9 q8 ~+ f                "url='" + url + '\'' +
    ) ]& X# z. }2 O; ]; O" J" S: [2 M                ", username='" + username + '\'' +/ ^) w' N2 S* S. ^2 n" _, E
                    ", password='" + password + '\'' +
    7 l/ e& ]% _# b. I4 p2 {                '}';9 M" F) ^" F2 W4 q9 ]  x( B* \7 P
        }
    5 {# u  E! w! p( h1 O. X8 `! ^}
    - n( V# {* O$ v8 K7 u  Z上面重点在于注解@Value注解,注意@Value注解中的
    & |5 S, d  k; Q; g; Q% {) l
    ) y, y5 w, v" h2 w" o来个测试用例
    : n5 W" z5 i% n
    ; K; p3 S* m$ zpackage com.javacode2018.lesson002.demo18;
    * g, e/ s( f+ g, _4 {6 I
    1 ]7 C! w/ @# w" X% ximport com.javacode2018.lesson002.demo18.test1.DbConfig;
    4 O2 J- U. g3 }4 v4 Yimport com.javacode2018.lesson002.demo18.test1.MainConfig1;+ ^- u* c: E( X- p
    import org.junit.Test;" L% X8 Y/ S' v" {$ {/ ]
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    7 }7 s9 z& y) |) Q# `2 v% }# M: t/ R5 |: `+ @8 m! ]1 ^* V1 J
    public class ValueTest {
    + R9 V+ A  a  F) V& @! ^7 X
    4 y! O* g, D- ^4 h( g. N$ p    @Test
    4 C5 J2 f; r) L9 O3 @& J4 [1 W2 G/ _    public void test1() {4 [. P, h  V' ~
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    8 F* b8 N2 m# Q        context.register(MainConfig1.class);
    6 k9 _: F) R/ K! }& d+ @        context.refresh();
    $ w& o$ s+ d) L% M/ U
    ! a1 u7 u3 b9 X, F        DbConfig dbConfig = context.getBean(DbConfig.class);! D3 _  R2 ?5 q2 J
            System.out.println(dbConfig);
    * q! w$ k0 i( f, t& }    }$ \* M9 C1 O9 u& E
    }
    # i5 S5 K* U+ F: _# `% B: v2 Y( o5 z( w运行输出
    2 w& M  o  h+ Q" j  n2 s7 i( _# ]# Q9 D+ t2 P1 ]& I5 O3 u7 g. q4 Z
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}2 S* z6 e, m2 @* R( x  C' W- v5 F
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。$ n' O5 x: N4 a& @) m/ A; @: G
    , c& V/ u4 S6 ]9 b8 n, e% \
    @Value数据来源
    1 r# {) G# E5 G
    7 _( W# C0 R( v1 G7 R通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    3 n" Q5 W9 o* s! Q3 n+ x5 v0 @' {" d( @. b# k* |! S& P, K
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    " X& `$ L, ]1 T4 g# m- G. B: o; E: U' U% t
    spring中有个类2 T4 S: a( V" P! ^* ^) q! q
    " d! b4 D# m# w1 h
    org.springframework.core.env.PropertySource- D' C4 h6 [8 a: p, K' \
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    0 w9 E% _3 I( e) U( P7 `, H2 v3 p& [4 K; Z( L7 z+ t
    内部有个方法:
    + l  D+ i# b  j" u; o" ]& `( w" k2 d1 F, k3 ?; J5 M
    public abstract Object getProperty(String name);! p1 B3 X, X8 ]; j
    通过name获取对应的配置信息。8 i1 `5 }6 M- T9 e+ k
    5 E# J. g3 F5 r6 |6 f
    系统有个比较重要的接口
    * k/ L. r7 L5 e2 \6 x+ o
    - [+ U! ^, A2 Z/ Q, G% ]/ c7 `org.springframework.core.env.Environment
    2 |. m' ^- R) Y0 m+ l6 k7 D7 f2 g( @用来表示环境配置信息,这个接口有几个方法比较重要- d% y6 G& v' N/ M; d2 v+ `7 ~

    7 r' u' S' b* |1 P$ g& T! AString resolvePlaceholders(String text);
    5 ]" |9 Y8 K" ^  G' O0 lMutablePropertySources getPropertySources();
    3 n) A5 V& ~& sresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。. E9 ~! p6 h8 n( H$ P8 c
    # M. h+ i$ o) V
    getPropertySources返回MutablePropertySources对象,来看一下这个类% b) h/ P& q. G, x* R
    % h- ?- G7 u" Z8 W% {
    public class MutablePropertySources implements PropertySources {
    8 z1 j8 l6 p4 o5 Z' L5 A4 @8 j6 w" @, U
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    % @0 V0 T1 T2 {6 n/ N, `. n- h
    4 e% F% D. ?+ j$ w( Z; t. s}
    : k3 ]. w, z* j# q' |- A( k8 C内部包含一个propertySourceList列表。
    % I' {- I" k( L3 i% _; [) l: R% x
    4 l$ [/ V# a- P3 Pspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。6 F6 @  l/ K5 S( n9 |5 @4 g+ p
    3 K9 ]  l' M* G8 O! C
    大家可以捋一下,最终解析@Value的过程:
    , K6 g0 J: I0 k
    " e9 e$ e: w- j( Q# j6 t  j1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析7 D6 N6 p; W: W. f
    2. Environment内部会访问MutablePropertySources来解析
    + {4 Q3 ~: k8 J  f2 P; W4 E7 Q& A1 W3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    ( R6 [7 O: m- b' {通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。, \) x8 ?& ]4 m! h+ k& C6 v
    0 Y# {' g. M6 m
    下面我们就按照这个思路来一个。" o& O" g* z7 `  {9 L
    9 @3 ^' ?- {5 Z$ p* ^  B& _! r3 h/ q
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    ) V7 ^2 B, U# h7 T: L% S* p8 j" ^1 `
    ! h6 U! c2 }- {package com.javacode2018.lesson002.demo18.test2;
      w% s6 n  c: b
    / }- B  {* G7 J4 M* Vimport org.springframework.beans.factory.annotation.Value;
    4 z5 W% u% S* J+ ^" @+ \4 x6 cimport org.springframework.stereotype.Component;
    ) W" Y" Z5 v( M, _) S7 ^
    ) M& f" M% F& a3 }8 R% @* K" l3 [: M/**/ f* t% N" l# n  M$ v
    * 邮件配置信息
    8 t1 v/ T% f+ @ */8 Z# D0 l8 K+ p  x
    @Component* o/ g0 K* m3 ?- d' v& G$ B" l
    public class MailConfig {% ^7 |0 a& H; `
    7 S) Z, E, y  s2 d+ ?
        @Value("${mail.host}")7 U  `" A8 ~! ]* K
        private String host;" ]1 B' x* p9 d8 Z  S' A

    + T! i1 i' Q5 a" @. ?; K8 P    @Value("${mail.username}")
    : Y! [) d* }2 f    private String username;8 _8 T0 ]: a$ E( ]& H

    7 Q9 S4 `! E7 X; e( s0 w! S    @Value("${mail.password}")& K1 e) o: l1 {
        private String password;5 M. m+ U9 d) B& v" ~5 j+ ]
    3 x+ N3 W% H) ?; \1 r: ~% e
        public String getHost() {
    0 `2 q% t4 \0 P4 H3 z4 \        return host;+ X* ~  O/ o$ R/ _
        }
    $ _* l. f  a; B, ~# S! E4 ]+ C. H: u# a) i; D3 A1 n# i3 g
        public void setHost(String host) {
    & d, s2 A" f7 h& K' @" Z1 W        this.host = host;9 R1 k7 ?2 D. u7 @1 z" R% W: Y
        }
    * Z1 n! d6 w6 k* D9 k  |. P& w6 v# e' {& U; ^
        public String getUsername() {/ E) x4 Z# R$ z2 j* S& [% z
            return username;
    / d, L3 i) x; [4 c    }
    5 y, e; x0 f6 d9 i, S7 O5 k
    0 `6 s1 l; o4 h    public void setUsername(String username) {
    , R( |7 {  P+ g4 r2 X8 m        this.username = username;- n6 v2 a+ H" L( [' U: `: G, J
        }* {1 z2 k8 P, X4 \$ v
    ' X9 k  \3 e5 j. y3 {/ V
        public String getPassword() {
      O5 p( N- y' N/ d) X- {0 Q  V        return password;+ U6 ^& u3 }0 S" H0 |
        }3 o2 K. w. u1 e" s% f: f

    5 s8 m$ S* A4 y" Q+ _" H: u) w6 T    public void setPassword(String password) {
    ) n( z: b* j( U: b/ F0 `" l        this.password = password;  E1 {* s- |. H/ N1 S, v) G) W$ _
        }& e+ V# p6 w' W/ q9 v

    0 `1 e- T7 a; Q/ r$ ^7 M7 e- M    @Override3 u: R* c( g  i1 e8 A$ w
        public String toString() {
    2 y1 t( G' P6 s7 z7 d6 m. t        return "MailConfig{" +3 u9 o9 M7 z1 f3 q" `- |
                    "host='" + host + '\'' +
    ( ?+ a& H! a& A                ", username='" + username + '\'' +1 d8 M+ ]8 X$ l8 Q
                    ", password='" + password + '\'' +- n7 A, Y/ ?" n. z( S+ s
                    '}';. J; |# N- N) g
        }
    4 ?1 T6 ^/ q3 d' H}# H1 P4 X+ q8 M: |* F
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    $ y  w& P. u( L% z9 C
    ! ]4 H8 S3 v7 ]1 u5 ^& ?3 ~/ opackage com.javacode2018.lesson002.demo18.test2;
    3 U5 x2 q/ j" u- f+ j% w3 ?1 a
    : G* d* a/ H, G$ n5 Vimport java.util.HashMap;3 ]) }6 A4 h, |5 Z( f% K
    import java.util.Map;. R  N* |. K& v" k# \% |. S6 k6 S
    # R9 b4 V  P8 X8 v/ x
    public class DbUtil {+ ~7 m0 F/ Y5 C' W6 |5 |
        /**3 L* q8 _/ ~, l- r! B  \
         * 模拟从db中获取邮件配置信息
    6 y  n/ A: C5 s     *8 E. K. M4 J2 }5 Y( G- `* y7 A
         * @return! F0 ~4 `( j5 q/ v0 T; V% l
         */
    6 }# U& }8 W6 p6 W    public static Map<String, Object> getMailInfoFromDb() {1 A. P+ F; j* }
            Map<String, Object> result = new HashMap<>();
    # C5 w$ `8 p/ G4 P2 {, ?        result.put("mail.host", "smtp.qq.com");
      x, \+ U9 `  n" X! }% z2 j/ k/ ~        result.put("mail.username", "路人");6 w- W6 i7 _4 q& C; A$ v* I3 U
            result.put("mail.password", "123");2 h/ h9 q; `& z3 V
            return result;
    " ?1 S" F6 s( y9 P    }
    3 z: c6 l; F( k6 |0 V. }}6 ^3 I2 N$ Y" M- L* o" p. t! \: s% n
    来个spring配置类/ f& O7 c' J4 ?; N

    6 J, k: L% t, t3 r3 X5 x2 G3 ?package com.javacode2018.lesson002.demo18.test2;
    ! x& t) I8 a9 A/ }* C! V
    $ J0 C" f8 A4 C; i# |import org.springframework.context.annotation.ComponentScan;
    6 [! y8 D; D; Cimport org.springframework.context.annotation.Configuration;
    0 e3 E0 N7 N; _. q2 I9 f, B1 S8 V: e: g7 ~( p* x
    @Configuration
    $ C. e& t5 ]7 j@ComponentScan
    4 o5 {" L5 A' U  s# Upublic class MainConfig2 {- c" m( _  e( {2 P
    }: u5 k  E* k) d/ W3 F
    下面是重点代码
    : t8 f+ v8 {7 v' G: }8 p% @) C  I. [/ `3 k7 X6 w1 R0 L
    @Test
    2 @" X/ c/ h% a0 ?! upublic void test2() {
    5 u4 l7 q5 M4 M% t5 l( h    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; t# |( G- X5 p+ r
    / g, r0 L% r' s, t    /*下面这段是关键 start*/
    1 e3 h3 p8 r  g# Z+ y9 t& `/ _4 W    //模拟从db中获取配置信息
    " n" N! U; Q1 w& _9 v8 p8 c: ?9 n7 f    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();, B7 v2 y+ o( u
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    7 C+ w) x  \7 }  K0 ^" l2 _/ C4 x    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);$ c( j4 n. ], O" a% f; ~# k
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    " p; E" v9 C$ M    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);) f1 o9 J; }3 _
        /*上面这段是关键 end*/
    6 L4 B! }4 M& F2 L
    2 E& s% V8 X3 n; D$ T7 Q    context.register(MainConfig2.class);
    2 N$ s' ?* h1 R# S' B$ [* _* h    context.refresh();  P4 w& w  d1 _$ N
        MailConfig mailConfig = context.getBean(MailConfig.class);
    " k$ t2 W( p4 X9 C0 A    System.out.println(mailConfig);, f, |) P# T" \/ I
    }
    9 Q- c- T  J0 @& {注释比较详细,就不详细解释了。
    # O; H( M) I5 {9 P7 L4 |( v" `
    : t/ S6 W4 d! x/ T* G直接运行,看效果8 D8 B) S& f* n; h5 L, o$ k& f1 K
    , B  H' y; f0 l3 {1 d9 x  }
    MailConfig{host='smtp.qq.com', username='路人', password='123'}: j9 H( _* {! t% n: l; x
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    4 J% H. v7 P0 @9 R, s5 Z8 L$ o: \9 {% o
    上面重点是下面这段代码,大家需要理解/ r% ?9 L7 z9 j3 j3 q
    ( D# h- r% W, X1 m) ^- J* F
    /*下面这段是关键 start*/9 m& p4 b3 Y# `- S: U
    //模拟从db中获取配置信息
    7 z8 [3 F8 v, b7 P6 yMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();+ p3 a( ~- m' @' q# H2 p6 `8 _
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    1 h- h6 L( @! A4 nMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    1 ^+ A$ q! \. o  c+ m6 M//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高. ^$ Q# N: i( i/ l" a6 k
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);- d1 {$ g% x/ C- \% w, V: _3 w
    /*上面这段是关键 end*/
    1 t5 J" A! Z0 T+ \3 h- Q8 l+ f: R咱们继续看下一个问题
    9 o# b8 E% Q8 L- k
    $ \' `. F6 F7 s% `& z- o/ e如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。" |: a8 q0 |8 ~2 p
    & F: [) ~% [) x. R8 \; s
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ' V# @6 ?* a" m0 S) M5 s- i  L& S" K. x
    实现@Value动态刷新
      e  a; k9 |" C/ a- ?
    6 C3 y8 \2 Y! v! S先了解一个知识点1 b' L, X5 f# H9 M: {

    6 U2 K" l% U8 ^1 {这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    5 g$ ^& W5 w9 q5 _  T
    ! ^, R: [9 f1 v  _# d4 a这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解) L( z, @( i1 I

    ! g, C' z  L2 Y- J  Xbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    3 P9 g8 h+ |' V6 o) i, F: G/ m: x- A2 y
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    ) d9 i; i3 c# j5 D% w/ ]% U2 v* W这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中, U0 ^+ t, b. t- w$ L6 d& k

    - s# ]6 F: }5 Q7 ]! B: P. ?public enum ScopedProxyMode {% A( m7 H( T, e. w
        DEFAULT,' @" D3 R3 U0 o, s& P
        NO,
    ; N+ {4 y3 R9 ]7 t# z    INTERFACES,& z0 T( `8 A0 k+ ]
        TARGET_CLASS;( X4 J7 h! [# F5 f; h7 \( W
    }
    ) m% D7 L& ^/ R  x1 s: S% L. w前面3个,不讲了,直接讲最后一个值是干什么的。
    * Q: |4 ^6 r# S7 @2 g  c, n
    5 c+ H1 x, c% Q. {! @当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    8 m$ n  O6 \7 q' d
    5 N# N* K" X! w* N- S6 d理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    : t! V6 q& ^: U: E
    ; m+ J0 W7 `0 R6 {自定义一个bean作用域的注解# V! R! \8 x/ y3 G. |' `, k

    # k* u, ~  s2 N3 `" s0 rpackage com.javacode2018.lesson002.demo18.test3;% `3 z5 t4 P& Z' P- A7 |; T& C- v

    " a5 E. M2 i" f5 ^+ Q4 r$ `) Wimport org.springframework.context.annotation.Scope;& e* Z: ]% K4 H, z
    import org.springframework.context.annotation.ScopedProxyMode;
    " p5 T5 k1 O* H2 t- {& e# o! s6 a/ k* L6 l
    import java.lang.annotation.*;
    ( b" v# y) @! v! b/ H+ y' a) H5 r5 o8 B) K: Z  I9 E6 K
    @Target({ElementType.TYPE, ElementType.METHOD})3 F2 r8 q& o; ?/ j( [& F5 e- M) }' W
    @Retention(RetentionPolicy.RUNTIME)
    ! c" f- G9 S3 U; P9 k@Documented
    , ]3 a$ c, Q. |2 @7 K6 z/ T* K@Scope(BeanMyScope.SCOPE_MY) //@1
    & V; D' I% _; f1 p8 {* j5 Ppublic @interface MyScope {' F2 m3 H; b9 P- _- T' i
        /**
    ; t- p+ v- u0 o7 ?     * @see Scope#proxyMode()" m, I* o9 {2 P! V7 Z) l4 q
         */, O0 v; P# l) w- c/ @
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2- }$ {0 N" k" Z+ |' v
    }
    : J8 r8 {+ U- l8 ?# q- C/ U@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    7 F" B( c# ^/ I. F8 }% B
    ; [8 c. Q" @6 Y/ C. R; C% e  s9 Q: a@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS2 w5 H8 a$ q1 C2 q
    / X) L; f& [; K4 ~' g
    @MyScope注解对应的Scope实现如下% E! w/ m$ ^* s! A, z
    4 o1 c7 q% r2 E0 Z2 a
    package com.javacode2018.lesson002.demo18.test3;
    * u+ J' X2 h, C0 t3 x5 ?& U% O. V' n. S. s. ~, x+ f
    import org.springframework.beans.factory.ObjectFactory;
    ' I/ a- {2 I8 U# u$ R, Nimport org.springframework.beans.factory.config.Scope;: b: }. T( B9 e" L% W
    import org.springframework.lang.Nullable;" `8 t( N! k; u  h- w# i8 x
    7 h/ N6 n7 `  I, |6 v
    /**
    1 _) O4 t9 F) k' x9 \6 [9 A * @see MyScope 作用域的实现, V0 T2 {2 m& f3 A: |3 ]/ p
    */: q2 y) V' z3 ^8 k: k4 k' V
    public class BeanMyScope implements Scope {3 ^* _4 F: {7 X' z8 z% J' F
    0 t% y/ P' ?& s- I
        public static final String SCOPE_MY = "my"; //@1
    8 ]; _6 r2 C+ |4 x; E  t
    1 C6 D" X8 T7 s) E- o    @Override  U, q& M) x  d) D* S5 {4 {
        public Object get(String name, ObjectFactory<?> objectFactory) {
    2 E8 m8 x: `3 h1 i) L+ t        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    4 y2 E* q, a; I8 W* u        return objectFactory.getObject(); //@37 T: I: z+ x, a- T1 _
        }
    & q  I' I) Q! o) A0 R; ~& q3 s3 Z& ^2 ^. h8 o7 i9 e, b# k2 W
        @Nullable- ]1 n6 ^8 J2 `, E
        @Override4 k  c& `9 x# j& k) D/ G* ^! n
        public Object remove(String name) {% m8 B+ h5 I# k6 n9 R
            return null;  F+ N9 @; y. _, [
        }+ g, J% d/ J1 B, m; s& x
    & \4 D6 t; S2 i+ A$ Z: D, S3 j
        @Override
    9 U$ d2 O; W* ?6 X7 q    public void registerDestructionCallback(String name, Runnable callback) {
    4 v2 I$ H6 y$ U2 v* v- A& F( y5 ^# U0 k
        }9 ^5 V! V+ s% `8 r" _8 W% l
    4 w5 O8 }8 k7 @, p9 c3 }
        @Nullable% C! S& B  p$ N! y  ]% t
        @Override8 _6 K7 @! s/ Z% q! n
        public Object resolveContextualObject(String key) {
    / G8 p! n- t2 |- L( p. g3 Q. y! u        return null;' u: I& N( }2 A+ b, j
        }
    3 I  e- o/ p* Y0 K  u8 A5 H/ N& u
    * D6 l. r' n+ ?; M$ f8 d    @Nullable
    / w6 Y) l# D0 l    @Override3 s- p$ }" e! @
        public String getConversationId() {: b5 l( B6 r4 F: ^; K
            return null;# P6 K# L  h+ T# t1 w9 ^
        }. t4 {- L9 X- F/ R: k; f
    }
    & j; I/ [/ ^! ?  W0 G8 J; K@1:定义了一个常量,作为作用域的值9 Y* n& ~. Q0 N" C' m
    ; s5 ^  a1 o* I( w
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    . b2 E: n+ X6 r, w+ Q7 A( A5 \$ S3 M9 ~- _& g' H
    @3:通过objectFactory.getObject()获取bean实例返回。+ n$ L- b, T9 C0 T

    ( p8 a3 r6 `7 f0 ?4 {下面来创建个类,作用域为上面自定义的作用域: \4 o* P  \1 u

    8 o5 B1 g# S0 S5 L: qpackage com.javacode2018.lesson002.demo18.test3;
    5 h6 `7 A4 Y9 X  z$ [
    & P: q! s' O% r+ Vimport org.springframework.stereotype.Component;
    ! a8 E+ o  |& V/ y
    # Z! u# h- H! E- m" Yimport java.util.UUID;
    / [8 A  _3 z6 Z' g! J
    ) \7 n, W) N6 N/ J  H8 ^@Component9 Y# s7 ^) H" S4 g4 l" u. Z! M
    @MyScope //@1
    2 j- a/ ~% z$ X( C1 a3 X: X6 apublic class User {- |/ X7 t# o, z. N

    7 ^' b7 H) C% _! y3 w; B    private String username;
    1 s2 x# k$ A' }6 i: m+ Z4 A0 `; w: k) w* i
        public User() { 5 u2 c; C& l  V2 Q: M+ W
            System.out.println("---------创建User对象" + this); //@2
    9 G; q0 `8 k; D; u        this.username = UUID.randomUUID().toString(); //@3
    ) t) X( @. v. }3 H8 W% |    }
    6 r! c4 |* A3 n6 |4 I7 y, M7 R: T+ \: c$ N9 Q2 w$ h! H
        public String getUsername() {" C/ ~+ D* W. H: F1 R
            return username;
    4 K" }' k/ Z" g" h$ A8 A$ z    }( \4 @9 I: a/ f/ j7 |; j
    / a. e$ v& {) m9 G' y' u9 P- X
        public void setUsername(String username) {+ U# x1 x- ]5 M& v8 `- o/ @
            this.username = username;
    " d/ h* {. G* X8 f, `1 V; x    }8 M( k  J4 {  j" H7 x8 _  ~. k

    0 i+ Q4 D) r" G; {! C: R3 \% }}  d; c0 v8 k% z; b- F* u
    @1:使用了自定义的作用域@MyScope2 _+ g. r# R! U* b1 `4 L
    , g! ~( w& \- G2 M
    @2:构造函数中输出一行日志
    " Q3 ^+ R1 _) b" \6 k+ k( ?0 s9 t; \; z/ A- H
    @3:给username赋值,通过uuid随机生成了一个7 H0 {. E2 ~9 q3 a

    * T! g6 b' b* I$ n- O& S, S来个spring配置类,加载上面@Compontent标注的组件. w! q) G9 {2 n9 r6 ?# l# ^

    * w1 v4 C, s: H8 R% Zpackage com.javacode2018.lesson002.demo18.test3;3 H# T# U2 ^" g1 e
    ) t' q5 ~9 T6 b# T8 V
    import org.springframework.context.annotation.ComponentScan;2 j; Y5 R/ a0 i1 D+ `9 S/ W3 H) ?
    import org.springframework.context.annotation.Configuration;  B$ }5 t: v: n4 H( |

    8 n5 d$ @7 e0 V@ComponentScan
    + R; ^# F3 Y( y% O5 t3 q6 W+ Y@Configuration' M) h2 F/ N& ^, j  \
    public class MainConfig3 {
    . M" _6 c* \2 {# R}
    7 }% i& b! K* u$ ?+ D& W% D6 s下面重点来了,测试用例# C7 E8 F$ ~! O. [" Q0 q; A1 X
    - Z2 K, O7 U) n& X7 H2 ?+ h% W
    @Test! z9 L1 n9 F+ J" g
    public void test3() throws InterruptedException {% R* Q& \$ G( w& @; A
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      Y2 K8 _9 I( P1 G1 c8 a8 ~; N    //将自定义作用域注册到spring容器中/ }9 C3 i7 I6 A5 a, C: [
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1# q. P0 ~% Z9 @2 V! t
        context.register(MainConfig3.class);
    5 V% W4 E3 D, \+ ]0 B  L1 f    context.refresh();/ q+ b, _3 M# x9 t; G% z

    ; q- X# a2 z& W$ q) m7 u4 G    System.out.println("从容器中获取User对象");, m& H' h* r+ S8 @
        User user = context.getBean(User.class); //@2
    , o3 R" i# W, F& S    System.out.println("user对象的class为:" + user.getClass()); //@3: P$ U) e7 c& I" i2 ?' m9 @

    7 I# y  r& C: T) ?/ C    System.out.println("多次调用user的getUsername感受一下效果\n");1 V3 l( `( D( I1 g% e/ X
        for (int i = 1; i <= 3; i++) {
    7 P- H' t9 H) k. K2 a5 o* |1 u9 ^        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    , ]% }: x, b+ J+ q/ j        System.out.println(user.getUsername());
    3 j, ~0 c0 U' G/ f$ C7 d        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));. r/ i, X3 V# i) i
        }
    - o* U' j% s( c3 V}7 D! ^4 R4 p! x5 p$ _) {  E
    @1:将自定义作用域注册到spring容器中
    0 B( t5 K3 f* N9 Q
    1 |2 s- l' @3 L@2:从容器中获取User对应的bean
    - m* i) c# z6 G' O  K- y
    . k. L6 P& R/ R5 h@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的. [- U4 F8 _& V3 d3 Q  d5 @
    + x+ J2 R. O5 U; `6 {
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    & R1 }- }7 }0 A  [: w' Q& h( k! w  t& x0 u/ T5 M5 h0 X) _3 i
    见证奇迹的时候到了,运行输出
    3 k% z" y0 k# @
    ) G! u' Y1 W( w5 ]3 e3 t4 I$ P) e从容器中获取User对象
    $ n! I' g% I! a6 ~( p( t6 Uuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    ; @" J2 e1 B. G, X3 Y! U4 X多次调用user的getUsername感受一下效果
    ! b! a2 O: n6 W5 K8 G5 v- s& q) T. ]& f& `2 ]. o$ L5 G( @8 k3 t
    ********( ]1 R, O  I* J
    第1次开始调用getUsername
    8 p' c+ Z, X* [% j3 \8 t9 hBeanMyScope >>>>>>>>> get:scopedTarget.user! o/ N2 Z6 w  l4 u# t7 f7 W
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f44 Y3 r7 N/ O, n  Q0 g8 F& w, h
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    1 t  ^: g! H1 C9 ^+ @第1次调用getUsername结束1 q. S" M. d0 q5 ?' d: t
    ********
    0 t- ?9 V' J) l! d
    7 j* Q) b1 I4 F  g0 t********6 d5 F; r' `' T( D/ s: Q6 N
    第2次开始调用getUsername
    $ @0 p. w: n. o- E7 cBeanMyScope >>>>>>>>> get:scopedTarget.user/ ^+ j, x# ~* }( b7 S
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b1 s) H) n8 O4 T* ?) A/ I
    01d67154-95f6-44bb-93ab-05a34abdf51f
    9 a: c9 {" q4 O; K: U1 F4 m第2次调用getUsername结束& ~* U* {" ?9 q& |% J7 x) q
    ********/ p/ M* W0 c& @6 q8 b0 C% }

    & p% d; [5 ?" k3 A: w9 ?' q********
    " M; O- H5 s. ^% y: u第3次开始调用getUsername* N! Z: i1 |9 ~7 B7 K1 [
    BeanMyScope >>>>>>>>> get:scopedTarget.user9 F" s  r8 E7 s# n4 F
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15. Z# q' J1 ^* {  ?( P
    76d0e86f-8331-4303-aac7-4acce0b258b8
    0 i, N7 B: |8 f2 b1 d  O0 [! }) Z- j第3次调用getUsername结束
    / Y/ x# t8 y2 p5 V8 T1 Y; Z% C********- t4 _- W$ n9 D; O4 [
    从输出的前2行可以看出:# w8 ^3 z( n. h5 u

    ; n0 O# p/ p& C" ?5 ^  H  ]调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象! I- o' n/ }1 }, b6 V/ X
    4 n( @* `) E. Z# y7 X
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。& G4 l& O  B/ l& f

    - e* f) E( |8 U# d; t2 c; J后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    5 P' k1 ?: |7 _+ u& K
    : v0 ?: n) V" w' t! A通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    7 `. @; A" L6 S  x6 ^4 _" }' m) N6 }* ?7 U% S
    动态刷新@Value具体实现
    ! m7 Q3 o) `# z8 i% `" r* Q/ p7 E6 f  a% h
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。; F. @7 G7 Y; J2 t! s$ \2 S% d

    + Y; i# Z- G* G先来自定义一个Scope:RefreshScope
    : M. P0 `) w* q4 I: [4 n
    4 I# \( Y) D0 ]( dpackage com.javacode2018.lesson002.demo18.test4;
    / b: A7 I; w7 B6 i  M. T+ O6 L  }& f& D5 {# @; ^% A
    import org.springframework.context.annotation.Scope;2 D! P- W- u& r( a  @1 b
    import org.springframework.context.annotation.ScopedProxyMode;
    6 W& b. ^7 i# f1 ^4 ]+ r% y
    6 n. |5 E3 ?0 mimport java.lang.annotation.*;# ~( u( s$ Y- W$ [
    . z. Q- X5 d0 u  K3 e
    @Target({ElementType.TYPE, ElementType.METHOD})1 V2 L" h' a/ j6 T$ b! K! G5 `/ S
    @Retention(RetentionPolicy.RUNTIME)
    : f1 X# I7 m, s; Z@Scope(BeanRefreshScope.SCOPE_REFRESH)
    0 N* X2 V/ b  h@Documented( x/ x1 O8 Z. G1 p7 ?: T1 S
    public @interface RefreshScope {
    / |0 k% R7 S4 @- u& X. }& M1 o    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    " ^6 I9 n' [5 F% m. o6 D2 e1 ?$ Z}
    ( [5 C4 }) ~; H7 Q8 b+ \要求标注@RefreshScope注解的类支持动态刷新@Value的配置1 m/ @8 x7 L. F: q  F

    2 [- I. x& [' y6 F' ^0 D- H$ e6 [@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    . Y7 y! i: u8 {6 |- @+ o: e5 z
    - j. v: q* n6 |/ w* d/ l这个自定义Scope对应的解析类- q" n/ g  s% v4 r' y( w
    # H" ~: g- R6 T+ j
    下面类中有几个无关的方法去掉了,可以忽略
    ) ^; v: G: s$ R7 [/ ~* S1 T; a8 q3 a! `( T2 J
    package com.javacode2018.lesson002.demo18.test4;
    6 T" P  l7 K6 J1 B+ a3 X# `( v; |- t* K' p$ R

    6 f( L# w; T5 Y( \% Fimport org.springframework.beans.factory.ObjectFactory;
    * k0 F9 m- E3 Uimport org.springframework.beans.factory.config.Scope;* B6 S, Q, P! }% Z
    import org.springframework.lang.Nullable;* b9 X  v/ o4 {' b: g, x2 O# l

    * M# o: `/ ], X8 E" ~import java.util.concurrent.ConcurrentHashMap;
    , ]; h$ u0 l5 S- ]
    / n) T6 ?* V  T* u+ Z  jpublic class BeanRefreshScope implements Scope {
    : T% @+ u3 N+ L( K" N
    - A5 s/ N* W# |) X    public static final String SCOPE_REFRESH = "refresh";
    7 V2 V& n6 H& w9 i
    / l! b$ G, I3 _- e2 h; Q    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();) y! g) M3 b" r5 f4 |5 q8 B+ @
    0 D) V8 C& I- a
        //来个map用来缓存bean
    - m: b6 L6 d% t$ p) f* U    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1( z  r6 P2 O. t" N5 j6 r

    1 n2 K4 Z# x# b  s- J( y9 @$ P9 n    private BeanRefreshScope() {
      g2 Y! ]! n: q( F+ t    }
    . v2 x1 q8 p6 ]; @$ Z
    7 N: I4 Q( w- W0 e3 G6 ]# m! u' |) F    public static BeanRefreshScope getInstance() {7 \# c1 {$ p. u6 J; q, F' e  D
            return INSTANCE;
    1 [7 v( M- g0 p8 p! ^2 B9 `3 y    }! G* q7 V' D5 g! j% k  F; \/ o
    4 L; T. n. }. y
        /**" a. |5 l% ^* G0 p% |1 d0 `% U
         * 清理当前2 a- }$ }, x4 [" b+ \
         */9 l2 ?5 ?7 G4 u9 q; }% f+ x
        public static void clean() {
    0 x7 W2 h' {  q' Y0 K  D        INSTANCE.beanMap.clear();
    * N, N2 E4 x& e: Y    }
    7 _6 w- ^- C+ _! I- j: {% O  W# ], U# t- @' l6 Z
        @Override
    ; t% o! ]: m2 U  O+ l& `4 a    public Object get(String name, ObjectFactory<?> objectFactory) {
    : [' w, P8 F6 d" G3 g        Object bean = beanMap.get(name);: t$ ^1 a/ _: e3 G6 V
            if (bean == null) {
    9 H6 M# Y5 |: m( F; @; c( C. g            bean = objectFactory.getObject();3 a; K3 h+ B- }5 r6 J4 V
                beanMap.put(name, bean);
    ) T8 {/ G7 \7 ]$ j, w& v; J& L1 B) b        }! L9 r1 H. W+ B6 _
            return bean;
    # i  Q$ s: M- X5 Q: t' u1 W# w# J    }
    ' f0 ]- [2 Y4 h4 {  {. e! t$ k+ [& k" }9 A  I$ N
    }
    % [1 z+ A" E1 B8 ?* \上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    6 s2 M7 ^% H! b1 i. d' ]4 h- o" Y) u* L  k" A3 w
    上面的clean方法用来清理beanMap中当前已缓存的所有bean1 J7 D9 U$ b. b: m8 d$ i

    6 F$ G" o4 \# `' r来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope8 W% m% l$ W7 g3 i& U
    0 U: [( \3 h. t; D& F7 U9 _
    package com.javacode2018.lesson002.demo18.test4;
    . A: l- x) b* R1 H1 H5 @, C
    ) w' C# k- a7 f  Zimport org.springframework.beans.factory.annotation.Value;3 w3 r' L% a2 Y, y# A
    import org.springframework.stereotype.Component;
    2 P) f0 d; f8 R2 B5 _" m, p2 e& g- w3 B- n' M
    /**
    9 d9 C, W" w* t! y * 邮件配置信息5 b  h/ S; Y  k% `6 n
    */
    2 U& u; a! C/ q@Component
    ' B( ]9 ~  X$ b7 _* h@RefreshScope //@13 t4 V. G: a! x8 s' E* k
    public class MailConfig {, G' K& T& Z# l% n. P* P5 k+ _/ t
    + N+ \5 h. d, i; J
        @Value("${mail.username}") //@2/ q5 F% k5 b% L7 o2 O8 S# R* I
        private String username;" T# n1 U5 |7 W' M) W& R7 p

    6 O$ \5 M6 H+ ?    public String getUsername() {
    4 c/ t5 l2 S4 Q% p3 E) {6 w        return username;! x; _; V4 l+ H' l: N$ ]8 a& J
        }
    2 \9 ^7 H) j, X4 T
    . w5 V4 g: W# Y! d    public void setUsername(String username) {
    % M% s" `& X/ B+ F2 I& Y6 `        this.username = username;
    , r! v9 Q. P& k0 W, \6 ^8 u    }
    ! {: Z& V* l: w+ ]4 ?+ R! t5 y
    6 U3 O. M/ Z' L    @Override- Z1 r; R! Q! K+ W
        public String toString() {
    + \' Z1 ~$ E& T8 U5 B        return "MailConfig{" +
    . j! L( h$ T' d; F; ?& ~& ^                "username='" + username + '\'' +
    4 i1 J0 X( r  k, I; f                '}';
      `+ U1 T7 O  k! {( m    }
    , N* K6 J* d' t- w* c}: r. B9 v% O8 s0 K1 R/ R
    @1:使用了自定义的作用域@RefreshScope
    . g6 K4 x6 h& w' Q1 I" D
    8 ?- ]: x# f( @; q$ H@2:通过@Value注入mail.username对一个的值% `/ m- X& [. `1 H8 T

    2 {6 W, s  e  m5 i6 p/ a% M重写了toString方法,一会测试时候可以看效果。- u; T& a( t8 y

    : |( H9 i: ]8 p7 u% A: f2 V) f2 [# R2 b再来个普通的bean,内部会注入MailConfig
    % L4 W0 C; |) y# j+ }4 K* Z( }( u; Y% v; q- S
    package com.javacode2018.lesson002.demo18.test4;% D0 L) Q8 `; L# Z8 x

    0 r$ j) ^9 E5 U' S1 I* S& iimport org.springframework.beans.factory.annotation.Autowired;
    / G! {& o, Y! i, @( ^* limport org.springframework.stereotype.Component;1 }7 f8 B" S. E! l  Y
    0 @2 T4 v5 |& Z4 L$ I5 E% x
    @Component+ I+ b; ~& r& n
    public class MailService {: h- C0 F& Q& T' I2 h
        @Autowired
    7 v) K$ c! p" Z& z, }    private MailConfig mailConfig;0 F  w# f# X9 {; }4 Y8 P4 e

    ; i; S- Z% N" \9 g    @Override/ h/ S! y& h: f7 f' P% C
        public String toString() {
    ' O/ Z% U/ A* V: {2 N        return "MailService{" +3 n' t8 V0 P5 x! u& V* b5 L
                    "mailConfig=" + mailConfig +, U6 x# ^8 w* N3 U& X! ~6 e* L
                    '}';
    5 O5 j  u) d7 |* n0 V5 O+ T    }" J' k1 A+ b5 ?# m" p
    }
    ) W0 [* q4 I- @1 s) @代码比较简单,重写了toString方法,一会测试时候可以看效果。9 A: F" ]: l7 [

    , Q$ E! C) d( t4 y0 ?6 V- w来个类,用来从db中获取邮件配置信息
    - ]: j( W! e. a4 r+ N) h) d% H; q6 z& F+ R0 y& z" y+ h' D2 V
    package com.javacode2018.lesson002.demo18.test4;4 z- n& P$ W/ p" s

    " ^0 h: I. z- y# H4 G( Pimport java.util.HashMap;7 E. S: }5 c2 \+ \( z. `
    import java.util.Map;7 Q2 H/ t$ p: U/ S
    import java.util.UUID;
    0 e1 a. X& ~% T! x
    , y  G  |* X- g% M& ?* V* vpublic class DbUtil {& `' D) g8 p2 s2 u* q
        /**
    ' g# _1 @4 D# B3 I% O8 n$ K     * 模拟从db中获取邮件配置信息( H) Y: l! c% G' u
         *
    & k2 y$ _5 i! A     * @return
    8 x, d! A: H1 @7 Y7 m; ~, b     */
    : t5 P% s; h0 J' X- \" ^5 K    public static Map<String, Object> getMailInfoFromDb() {
    8 C% J3 `( h; f        Map<String, Object> result = new HashMap<>();
    ( k" S4 S7 s: L/ v        result.put("mail.username", UUID.randomUUID().toString());
      ~# O/ Z$ ]- H3 [$ B        return result;: z" ?) {. `7 I- Q) |, O/ D2 y
        }) _9 Z8 Q4 |5 S% [& {3 A4 d3 N
    }
    ) W! k3 V$ R* ~) k, ?来个spring配置类,扫描加载上面的组件
    " R0 z& t1 F( L! Y- \$ Z. u4 V) }/ N
    * J: V, ]- ]5 }# bpackage com.javacode2018.lesson002.demo18.test4;
    ; [: K: P, z5 u% l2 Y3 \# k* N; i. N  o% L' u0 ~. a+ G
    import org.springframework.context.annotation.ComponentScan;
    / X; ^8 h5 k( A! a5 x7 G! fimport org.springframework.context.annotation.Configuration;
    7 a, G4 i2 c8 T4 T& G% B# L5 _2 E4 ]5 J
    @Configuration& q3 A; @0 P/ N" Y9 z% m, j
    @ComponentScan
    $ a  F3 h* N3 t/ }public class MainConfig4 {8 u! `  ]: ~1 H) ~  ?) `. O; z
    }% F9 A0 ]+ K6 L* G- Q& h1 N
    来个工具类- v- w! l- P: ~* w' {0 s5 y
    $ O( t1 c5 y2 n  |
    内部有2个方法,如下:
    & Z1 M: U' [# L8 v; a4 ^6 N4 m
    & e( S1 Z/ y' f; dpackage com.javacode2018.lesson002.demo18.test4;; P9 g/ i1 y$ E; u7 D6 _

    ' [, C2 n) s4 Simport org.springframework.context.support.AbstractApplicationContext;
    0 z- Y7 R$ m% P9 x& U* e; l& L6 Iimport org.springframework.core.env.MapPropertySource;" N. Q1 n+ t1 }4 i& \# x) `/ A; t+ b
    9 |6 p1 x" o, a) z
    import java.util.Map;) A3 \" f* V3 `8 d! u
    6 R! R" ~6 p% f9 w' ]
    public class RefreshConfigUtil {3 V+ \( y! O5 r% b- I
        /**4 j& ~& {3 _" K& Y' B* e
         * 模拟改变数据库中都配置信息
    6 ]' w0 n; H( E; C# {; G     */6 q( u9 r( z, l7 |" v, }8 \5 @' v/ b# W# V
        public static void updateDbConfig(AbstractApplicationContext context) {# ~* g5 c3 G( `& Y! \. M
            //更新context中的mailPropertySource配置信息
    . R, c8 r/ {, Z0 I0 C- H5 Z; {        refreshMailPropertySource(context);5 k; F* g# O  E% y
    4 x7 ]1 z4 G& U7 O% n3 a& s5 i
            //清空BeanRefreshScope中所有bean的缓存
    ) Z6 I1 o+ Y$ r1 o        BeanRefreshScope.getInstance().clean();! c8 ^9 u1 p( E% z; D
        }
    5 v) E2 e* B; \8 g3 ]! V$ ^: p3 `
        public static void refreshMailPropertySource(AbstractApplicationContext context) {2 G8 x7 o8 W* {7 Z
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    / t8 z$ K% {. d& t        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)' S: M" ~9 ?: x. j" \
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    3 i% {8 k. z4 P7 Q5 g        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);3 z4 B8 T8 |) f* s) k
        }
    ' w4 A2 y$ t0 U6 E% Z7 f  a3 N/ N( i* e& r$ _6 w& E! i4 j
    }
    3 E* p, Z- v* a! n- f# GupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息( C  Y& Y- V& `, r' @6 X/ n

    7 Y5 c" |2 r- p* e, X$ }+ a7 FBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。. `& b/ P( h& B
    / ~% ]4 h" M: s2 j  f" v- Q
    来个测试用例! i$ K* H  Z  j/ w0 h7 c
    3 S6 x* L7 f0 F# q1 ?: S& p
    @Test8 z" `% {# P- g; F$ X+ x; ?
    public void test4() throws InterruptedException {
    5 h% z3 c/ ?% \) e* P2 U    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();6 o8 ^4 T; T: \* @6 Q
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());: S! T: T4 z/ M- @% U$ d; k
        context.register(MainConfig4.class);! s! j9 N* h, q7 W
        //刷新mail的配置到Environment
    ! G, @0 |, _: ]    RefreshConfigUtil.refreshMailPropertySource(context);1 K6 P. i9 K7 U# k& ^, ?
        context.refresh();
    # s! k7 c% x/ u, `* N" h) `5 z/ w7 A1 j* Y
        MailService mailService = context.getBean(MailService.class);
    4 C  D. S  L4 w# {+ @1 n$ }    System.out.println("配置未更新的情况下,输出3次");
    3 O9 J! a! |  T9 I0 \1 F$ ~    for (int i = 0; i < 3; i++) { //@11 x' ]' B0 g3 f) n& J9 |2 \
            System.out.println(mailService);& a0 |( M% T& H$ m
            TimeUnit.MILLISECONDS.sleep(200);8 ]8 I- ^, D7 k8 i8 E7 Q# `
        }
      D) o3 [, Q4 |5 H% k
    , _7 j3 D  T- O: M! l8 K) L    System.out.println("模拟3次更新配置效果");$ g& k5 y& M% E) p% b
        for (int i = 0; i < 3; i++) { //@25 f5 M) {& v# g9 i
            RefreshConfigUtil.updateDbConfig(context); //@3$ L8 j# P  w5 B* O1 _5 f
            System.out.println(mailService);
    + Y- ]- b" }8 e* G3 n% Y+ A        TimeUnit.MILLISECONDS.sleep(200);* O7 _' ?: q! Q! P2 w5 g: J4 X7 ~
        }
    4 p  M. `- j# E9 u# ^0 ~}- F1 D5 L4 H: `9 D' v
    @1:循环3次,输出mailService的信息# l$ x# m" i$ w0 W5 k" _
    5 k8 ~5 [6 B4 e* J1 H3 B0 Q3 l
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    / ~) x) c/ Y0 T) [; r# C1 b; Y: p
    5 `' o; ?9 |# B( g见证奇迹的时刻,来看效果/ C& q( T- B, |$ ~1 L, s* Y

    ! D. e, t' J6 }配置未更新的情况下,输出3次0 c& f$ ^1 V- t3 q1 w8 B0 q3 T: s
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}4 \8 F, r+ a) b  q! f- [0 _2 a) c
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}" f  _- ?# K9 x& {* b9 B7 o/ d
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 ~( X3 j5 k% l) O/ g模拟3次更新配置效果
    5 I  ?3 d; U. ?4 Y) r. D* XMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}! U8 D! g& L% ~0 r) u- X
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}8 C+ A4 [. c0 i# h; J
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}8 W7 U4 x6 K  B) n# F( t0 j
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    , D1 r, K7 y3 G5 F0 k. l5 D& G* i: Y$ t8 ^4 b& T
    小结
    , [9 i2 }9 |8 Z, |5 |- l0 }
    5 @+ a' }) ^2 p0 n2 f& P+ ^动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    / n; Y0 O3 c* s6 S' N3 p; v" ^8 n" P$ T) B7 @% K/ D
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。. O  }( e; A' Q8 P
    1 a6 j/ g% p4 O, _: Y
    总结
    ' X, f! d# d0 K* D7 S' f, P4 v9 U/ w  G& x) i
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    4 `. _1 n1 ~* s& i) y9 K, O- f1 N+ R8 ?2 ?
    案例源码% b  {6 D" E4 m! M5 _# @

    & N, z8 y3 a1 m: S& Ohttps://gitee.com/javacode2018/spring-series0 `( ^% D; x% z6 t8 K$ _( Q3 n
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。7 m: e% J; ^" y  M$ q0 a
    . R9 ^0 N9 p2 e; @0 l: \7 }) u5 }
    Spring系列1 B3 b7 R7 A& A$ T& J

    ' ~$ h! u! G) {) E1 M/ n. j8 cSpring系列第1篇:为何要学spring?
    ! h: `: u* K+ H- t8 _' r) u# M2 J
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    ( H5 H* ^& t/ C' X( h4 b
    + U0 ?- g9 W4 GSpring系列第3篇:Spring容器基本使用及原理
    : x5 X+ e4 m% X, f9 `. y  Y5 t
    3 V7 t, ^4 o. G+ S, j6 S0 \Spring系列第4篇:xml中bean定义详解(-)/ P5 l0 k2 Q+ G& t; x( q0 H# m

      N0 I7 X" [* ?' f1 w" iSpring系列第5篇:创建bean实例这些方式你们都知道?
    ( i/ m& I0 ]" c4 y) n
    9 X3 r- [+ b% M2 g/ x) E+ PSpring系列第6篇:玩转bean scope,避免跳坑里!7 x& x2 X' G& c
    & V- A) J$ W, ?, }8 c: w# m
    Spring系列第7篇:依赖注入之手动注入+ y' z! A, `) V  t- ^3 L7 f5 s. v
    ' D. E) s3 P$ r2 q, F
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    + ^+ G7 @8 y6 ]/ p2 K
    * q1 G  \4 ?' m( h. Y. ]5 q1 `# g$ ASpring系列第9篇:depend-on到底是干什么的?
    " I) i- I" Y) n9 ~! Y6 m* X# j0 x, W; E, p0 n, E) f
    Spring系列第10篇:primary可以解决什么问题?( v3 E" H2 Z! O; i8 n. e
    ! u" Y# X0 ?, O" L
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    1 C' {& G0 j( u5 {  ?3 G* O( z
    + p' B1 C0 u8 A5 zSpring系列第12篇:lazy-init:bean延迟初始化
    + \& E5 T; O3 D* ~* T
      X# {6 L, I) I. X2 zSpring系列第13篇:使用继承简化bean配置(abstract & parent)) H$ |* q. a. I" V! E

    ! e* e7 E6 G8 Z# U- }1 d9 {3 M5 ySpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?8 I5 M; g" B2 P- }& r' r

    & M8 J8 ]  K$ M1 o, n; ?3 \Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    & B$ f/ ]9 X1 n- O3 D7 \4 S" p  T5 u& H! h6 e$ }/ |. A' l
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    : ^% W" ]$ f! I& ^$ f9 {
    9 o4 s3 z$ C1 M/ p) O3 n* SSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ( M/ K8 a7 @7 i" s, p# x* a: q
    9 j) f- D1 T$ C% R  X1 gSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    - Z- `" I% B' K/ u! x4 t: D' x3 v* [. N* i8 q
    Spring系列第18篇:@import详解(bean批量注册)
      |- e1 c! }! _  Y$ y5 H, J5 L5 ^: r, n: w1 x4 o% g  ^2 D6 W
    Spring系列第20篇:@Conditional通过条件来控制bean的注册+ u3 S2 Y+ K3 }1 N! i  k

    - J" J4 O. b# S8 u: h! P2 S0 KSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    3 h& E: z- i3 ^% B
    9 [/ a* u0 q2 m, ^* A2 OSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解* E4 b# z2 o$ X$ x

      B/ A8 k3 D  W8 c# \% vSpring系列第23篇:Bean生命周期详解' ]8 [4 d/ B" h$ S/ d$ v

    ; @6 \, y& x- ?' Q( nSpring系列第24篇:父子容器详解
    * k/ i9 x, z3 s5 X' [/ n* L* ?; X
    更多好文章2 G+ v4 ?7 v" G) ~* y5 K% K
    9 Y8 D! c9 X  N8 w
    Java高并发系列(共34篇), k7 x! j# M  i$ A; G

    ; [5 c$ {# z0 W; F& GMySql高手系列(共27篇)& i4 J6 O/ q5 P; E, D. c( ~
      }% j# g7 r  \9 ]; r  y* w  U6 S
    Maven高手系列(共10篇)2 ?$ G7 u0 o9 ?: A6 ~2 c
    # s9 Z0 u( K2 h6 y8 f' u' \7 c  L
    Mybatis系列(共12篇)
    & S: ?# Q4 c& B" ^* z  }6 U$ D; Q
    , ]7 u8 P3 v. u聊聊db和缓存一致性常见的实现方式7 O' Q  D4 O; b8 `4 g6 p
    0 R5 V& G5 Y, ?) W& I  P0 Y8 a
    接口幂等性这么重要,它是什么?怎么实现?' R9 S6 \# M7 [- M6 b$ w* H7 i- J8 B2 G
    ; z' T- r& ]6 q( c2 E
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!' p0 g4 G+ W% Z- K
    ————————————————
    , |) ]9 q8 [3 U( J  z版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    4 ]. n* Y+ b( W( f. u  X3 N原文链接:https://blog.csdn.net/likun557/article/details/105648757! T; l8 O$ }3 _1 H' |
    ; e4 G* n/ P; |4 ]/ ]

    & K4 a2 B/ r8 y
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2025-10-3 14:43 , Processed in 0.461933 second(s), 52 queries .

    回顶部