QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5107|回复: 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!
    , r, E, @- Q" Z' t! |. M2 p5 ~疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    5 `. [! R+ a/ V6 K9 g
    " O! t2 m: e$ Q/ J  w9 j' B3 O* q面试官:Spring中的@Value用过么,介绍一下
    * e4 V1 t( P2 _4 Q; n1 f* U* y1 y% r8 h4 l1 D
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中  a5 \: o3 g, k; J! d, z" K

    ' _" I- x! W4 D4 u) I; P面试官:那就是说@Value的数据来源于配置文件了?
    ' I. v6 l' _( o( z% _
    # J& q  o. f- s; |# d! c2 c# Y5 C& O我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置, \% r# Z1 n: `0 w6 A0 @- m' V

    & f' \* z: B; o4 f4 k; P面试官:@Value数据来源还有其他方式么?& y& t. r0 V  H- g7 W

    3 W; u1 X1 s$ j" {我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    . |8 h- P2 ], u8 v9 i+ {" o) n. m$ A: t% \) [$ u
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?0 I  ?9 z" K" j" g

    ! q6 M  A. m3 q  b我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    3 \! K, f8 }# ]# D9 X1 N5 e$ f& b
    6 \! K3 B7 z9 V$ s) I6 v面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    * j% K+ A" A0 Y; e/ C/ n2 y8 z
    & a' @, o5 Y0 E* ?. }* l' N我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能7 F2 l/ t" X4 p" w

    7 i$ ]( T) `: m4 ~  g; Y% j" Q面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    & _- f# O8 F% f! o) u' B: P6 \/ S* [$ x$ e. w8 w( I% n
    我:嗯。。。这个之前看过一点,不过没有看懂& b0 R2 `0 u+ s  H
    $ ]. ^. ]$ A9 L" @) @3 G. A
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    9 K( a; q6 l) q- I: _& B) p& h; j0 |& H& H/ B3 c" o2 X0 Y6 q
    我:3万吧
    $ i) h% {9 Z4 |+ Y
    5 w; `# U: `" `0 h面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?% P, `" }! S. C
    , S3 }, K' d; p. ~9 o; M0 }
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    + H0 L+ d5 K  f/ p( ?0 ?. J- a8 h+ z) ^" g0 j8 V
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!8 e) S+ \# Q) b

    ) r- r5 j! {/ M! `9 o# h9 M" n我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。9 L- j) Y3 a4 I" T# \+ {' y, {
    8 ]" [7 ^" \% S, w
    这次面试问题如下
    4 Z4 {2 U  G# S0 t9 r, u
    5 \. L2 |& ]: k: [- n# d. a@Value的用法# u4 X+ u. c: O9 t8 i
    / N( S: y8 G% c' V0 A" @& i, y( ?2 J
    @Value数据来源6 k/ ^' e& N; p& p$ ]+ r2 ~

    6 W( r, Z( }  c* a8 ]; A@Value动态刷新的问题
    4 Y8 U8 j4 c* j0 M$ |; _4 s( f; C) C0 _
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    - T& [+ H" Y% G, T) I
    . q' `) k5 q5 I- d! A7 @1 B! c@Value的用法
    ; Z8 C# O# D% Q: s$ E  W4 j% J6 k2 ^# g& T1 Z
    系统中需要连接db,连接db有很多配置信息。
    $ F% ?+ T" X( r# T
    " L8 ^* O$ {8 t, Z: a系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。" l% y$ c! Y4 R
    & M1 z; H6 f) M/ k  }
    还有其他的一些配置信息。) T: a/ k6 M& {
    ( R, W( l6 r. @* b
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    ( `+ `' J$ J" {: A
    2 X4 R7 R3 y. X$ c7 ]那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。) B! @) Y$ R' h, Q9 o

    7 u5 n# G3 V: x/ `通常我们会将配置信息以key=value的形式存储在properties配置文件中。% E& O: F1 T, x! x4 E: ]8 ?
    4 s; G% `8 y& a
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。1 c7 H# O) [/ m' N+ {( r! N- Q5 w* h# j
    / b' Q: c+ p  ?5 m$ T& H
    @Value使用步骤
    5 A; O& U' T/ b" e
    0 Z% q% @& A1 h( `步骤一:使用@PropertySource注解引入配置文件8 [6 ^" }( X/ g+ Z! p+ p

    ; s2 w/ J  H% L1 b  b9 s将@PropertySource放在类上面,如下
    ) R- T9 {) v8 m7 I& `+ S6 P) }9 B3 h3 g6 X
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    ( ?1 p; N; P$ w5 I@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。$ Q7 V7 ^- [0 E* ~# g8 K. z, ~
    % m- y% D% e3 p! V0 Y5 y( `
    如:; c. D1 M6 U8 l8 X
    5 u. \  J4 [. i7 D1 v+ v+ M# s1 Z
    @Component
    / c, l* v+ k3 Q5 i& T, b; C@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    0 L6 S) O. L/ E; S- _public class DbConfig {" z& V& X& g: A( S7 V/ C: S/ s
    }- Y+ ~4 \! r# W: w9 S
    步骤二:使用@Value注解引用配置文件的值
    ) y* s2 m2 X7 w2 \& O5 O
    3 G4 a. W  p1 i: a/ x, l* o$ d通过@Value引用上面配置文件中的值:: L( L# r; `. n8 }1 L7 e* H

    ' t, M' O1 Z0 d. }9 g2 F( N# z语法
    ( A5 ], L' Y. ?" P- c4 ]* c5 t0 I- r* |2 [
    @Value("${配置文件中的key:默认值}")
    6 h7 {. Y, S' T8 o* c/ R  b# u@Value("${配置文件中的key}")2 k" Z: A. H- R) E% i1 m; R$ m% H5 M
    如:
    - i1 @% l) P6 c3 b
    # o1 s6 d; R* V@Value("${password:123}")3 ~( @( t3 s4 S, d
    上面如果password不存在,将123作为值' I9 Q; y4 ~  K$ z. o7 @# _
    8 p- v3 l$ x2 k: F" x5 {4 q0 E/ @0 k
    @Value("${password}"). k" f$ E% I2 D$ t7 ?+ f$ h7 A
    上面如果password不存在,值为${password}9 i! B0 W9 E! c1 d: s  _% d0 u

    ' }/ G0 V' A4 N; S4 b& I" C4 ?假如配置文件如下
    ! P# Z- O( b, ~- q& ^; L; a8 a$ v. E; Y1 ~4 H
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8$ O; _' |/ N6 U* J
    jdbc.username=javacode
    / @, m* t* R' o4 j: Q/ o8 T! a! Sjdbc.password=javacode7 b+ _# j% B/ ]- X' R7 r
    使用方式如下:
    & w' p1 p3 ?: v) H1 [" y( d8 X0 j( b" }2 N+ j
    @Value("${jdbc.url}")# h; A( n4 l) ]' C6 A: q/ q2 X" M
    private String url;) K9 B$ q# `6 v6 {8 k7 s# {" j, W

    + [' M1 V+ d1 r$ S* D6 q@Value("${jdbc.username}")  n% T7 f8 W, h, v! t6 `
    private String username;$ S/ y" H  L1 L  i& u

    % {" D- ]8 \3 {8 J% x; y@Value("${jdbc.password}")
    . V3 ^0 _' }! A. e8 y1 bprivate String password;6 e& e  @' ^8 }, B4 C( P/ Z& Q
    下面来看案例# O5 x3 v% }* j2 E& y

    & `! S+ z) |! T! p+ b! q案例
    + s& C/ r9 u) ?- F8 d1 C5 U# \/ }6 p/ G1 s- a5 ~
    来个配置文件db.properties# z& \& ^. b( ^7 H

    & U# ^0 w/ C1 F1 B- ]( [  Kjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    1 N$ O% N! D' h0 jjdbc.username=javacode* _3 i+ R5 [* @' }: J4 P# c
    jdbc.password=javacode( {; g+ Z0 k6 k9 Z
    来个配置类,使用@PropertySource引入上面的配置文件3 \9 F% ~9 J- z4 g9 A) V
    5 Q4 A: A8 |9 B/ C: B+ u1 i
    package com.javacode2018.lesson002.demo18.test1;
    . i7 M# ^0 D8 y4 [  p& h5 T0 e1 f+ y3 l' ?4 `
    import org.springframework.beans.factory.annotation.Configurable;
    / N2 o( V+ y3 }- |( R7 [2 d. ]import org.springframework.context.annotation.ComponentScan;! t2 h$ z5 {+ Q+ Q$ v! \
    import org.springframework.context.annotation.PropertySource;
    ) \$ m! f3 k5 ?& B' s/ M  n: Q' Y* }8 g5 |6 r5 y2 ~4 C7 t
    @Configurable
    / ^/ c2 ^$ ^5 x@ComponentScan
    ( [" V' r8 I0 N9 n4 @0 d@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})' V! h% `9 E5 Y, y
    public class MainConfig1 {, \/ ^/ Y. K: `8 Y5 v+ }8 a6 y  P/ P
    }! [% |- }: r- Z5 _
    来个类,使用@Value来使用配置文件中的信息3 G& A+ |0 e  l+ z) M' c

    / u4 Z9 m/ l3 |; `# r1 cpackage com.javacode2018.lesson002.demo18.test1;
    0 ?, G* q/ O! _0 x( |. p; V* {8 y- ?5 _0 r, s0 h: H) ~
    import org.springframework.beans.factory.annotation.Value;
    ' W% A" b! B7 u, B/ n- _import org.springframework.stereotype.Component;
    1 f+ j" Z8 p3 R3 z+ @# Y- k' u7 l  M
    @Component
    # q" v7 I0 B, n! q# ^public class DbConfig {; _+ r$ q; B- @9 H, j# Q
    % |3 C% V+ a$ T: u" u
        @Value("${jdbc.url}")( {5 L# x; u2 o! p' w8 C5 F
        private String url;9 |7 j, ?! S, z$ l
    , B* v/ F" Y/ F& n
        @Value("${jdbc.username}")/ p# B* G+ _9 h) I
        private String username;8 Q% C  B: @5 `
      `+ R; S3 Z3 m7 d- b$ t% F- x9 q0 Y
        @Value("${jdbc.password}")
    7 H$ \) d' I3 S    private String password;
    - M; f* t  ?& D' |* ~& v0 Y' |$ Z9 c( N8 B+ B. y
        public String getUrl() {( H/ t3 B6 [' K: _# K8 x
            return url;( u! F( E9 G# g* h# @
        }
    & l6 ?6 W$ ^0 O: C% U8 K  Z* w* p0 C7 t3 Z
        public void setUrl(String url) {
    / o1 p1 y3 z7 p: t9 x. ?, k% R" R        this.url = url;; G5 _/ Z0 g$ [; f
        }; }; G3 X4 _2 v  G2 F; }+ P

    ! J) ]1 _3 i! {; Z$ B$ v    public String getUsername() {0 s7 z7 r9 H: J3 `" _$ J
            return username;
    3 o$ Y' V% q; e  l  O1 Q: ]    }; X/ V0 K; P% X) @: c
    $ H1 |9 q0 p& _4 }% E  h: |, j
        public void setUsername(String username) {! T- p: o- a: P0 k" O
            this.username = username;6 O9 z; j$ s3 |: {% F3 y
        }2 C, @/ r7 r# G5 z* s1 M

    3 Z/ G: _% {9 X' J* \" u% T! H    public String getPassword() {
    ( R  ]% Q* @! {4 `1 s' h        return password;
    " i  U; v9 j- |& h  O4 w- D4 U    }
      S9 n/ O- y6 K8 ~. d: ]5 y
    # x: d9 P8 z  @' m8 z' u# x    public void setPassword(String password) {- a8 g: ?6 Z9 L
            this.password = password;2 C  R0 q7 U9 f
        }
    ) f; u0 ^( S( W4 H
    8 ?$ y1 R% w+ F. c- W0 n% U: I; p    @Override, y# r$ V" ^" `' J2 G, V% A- b' t
        public String toString() {
    * ^: N+ T8 Y5 I        return "DbConfig{" +
    2 s* Z; T( e7 W+ Y                "url='" + url + '\'' +' J- G; {/ D' E/ h; B! T" a
                    ", username='" + username + '\'' +5 r. K8 X" i& }" H* K+ T
                    ", password='" + password + '\'' +9 D- j3 R4 z2 I; O
                    '}';
    # K! x2 O. Z- H! l    }
    " s0 A% S0 b: M- }) l2 @* |' F2 D6 b}- i: e! L# j1 U0 b4 X* D3 R
    上面重点在于注解@Value注解,注意@Value注解中的
    ( s, V5 T  B9 d  T! v( A4 N
    . c" M# }1 G+ p5 _来个测试用例( p: o  z- T7 O- z5 y" p
    # P; g% V, S2 J3 ?+ C5 i
    package com.javacode2018.lesson002.demo18;
    7 ^! R' f! {6 M9 `1 p4 N0 z/ F7 ]8 d# A! Y0 P  R1 f' L
    import com.javacode2018.lesson002.demo18.test1.DbConfig;. M' \( X9 V( _1 ~( X. u0 n. O
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;' H" O  P4 A' {' I/ \/ L
    import org.junit.Test;( Y1 G" A& ^* F
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    + C# D* t5 Z5 I% V2 S$ O& S6 C( i0 v, W& t$ ]
    public class ValueTest {" `+ |( ?/ a3 \* w, @

    . U0 z- ^9 L* F( `: ?# w' e    @Test5 l& o+ b$ o) C: L  s# D% p1 {
        public void test1() {
    # p! D* E- O+ w. w+ K0 S) Y5 J( F% P        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    - v# ^) N, x( j& r( i$ S5 V# z        context.register(MainConfig1.class);
    6 c: E  H- h) M        context.refresh();
    + i, m+ @3 R; i6 u  S; Q; C' N* r- z4 N
            DbConfig dbConfig = context.getBean(DbConfig.class);
      w( T8 u$ o2 i. ~( ]8 z3 L        System.out.println(dbConfig);7 Q3 K  q" _5 N
        }# r  r( i/ t. `6 F
    }  ^; {4 X  b. F% _2 H
    运行输出
    6 ^4 C; [" x* r1 g1 [+ X! G+ R6 T/ r/ ?% B# g8 h0 G/ q
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    / K! o% R- B8 ~0 ?$ _: A, @上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    5 }: U8 ~" E5 s! I4 S2 u" g" S# H, y. l( @0 S8 D, a# e# K
    @Value数据来源
    ' [. K- h; l; u& H, n& h) i: J2 x. u. L$ R+ J
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。" N* m1 r* P9 |  B

    # ~$ b% z4 s5 `我们需要先了解一下@Value中数据来源于spring的什么地方。
    4 u' l2 R1 n" }% I6 Z
    , H! O$ j! R$ a; j5 _spring中有个类. @7 w: Y# O0 ^# z1 r2 G; |
    ' i6 b5 D+ G& ^9 J6 ]
    org.springframework.core.env.PropertySource; A' S& z* b2 f
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    ! a( l2 k1 M5 `2 @
    " l7 K6 z( s1 ~9 x内部有个方法:
    ) o- D3 s# K) {# c2 |. k7 J$ E9 i2 F" l; P0 b
    public abstract Object getProperty(String name);7 T8 G7 O" r5 u3 |# T# L; [
    通过name获取对应的配置信息。8 {5 A7 u/ I# V  y3 {$ h  ?

    6 Y" }% R7 ]2 E系统有个比较重要的接口
    3 [$ Q8 M( w! d8 ?3 H9 g- l
    * P" w' p3 v/ q* P, r! @org.springframework.core.env.Environment5 ]$ m% D5 G& c, ~% V
    用来表示环境配置信息,这个接口有几个方法比较重要2 v- {' y7 a. }  k5 h2 \. P

    ; D' Z( R( [2 x+ PString resolvePlaceholders(String text);/ Z; T4 u3 A, `3 k9 o' P8 `
    MutablePropertySources getPropertySources();4 W. A; [) e- h' z& V/ i. S- H0 V
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。3 l, p0 n" e! y9 b+ Q6 ?3 ~

    5 Y" y. G' F% zgetPropertySources返回MutablePropertySources对象,来看一下这个类
    7 _7 w) u) E, ]0 w7 \; N7 G% i9 B4 C' T
    public class MutablePropertySources implements PropertySources {
    3 ^5 ?+ m& Z" I4 p- U* x* f* r! _6 _: `' ~! }
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    0 Y1 s; n6 q2 q0 }# p# X: f' {' ?+ k5 b
    }
    1 F. z+ |1 m$ }, {# ?内部包含一个propertySourceList列表。
    ) P% R0 O# T4 W" R! o( g4 f2 N
    - V0 Y. o/ ^! p; b& H7 cspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    ; x! c% ^) a$ Z
      {8 A: C. x0 O5 j/ {* h& I大家可以捋一下,最终解析@Value的过程:# i4 ~7 F: \$ Z
      v$ z/ v" N0 j( h2 Z; j
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析# ]4 ^; f! {( H. j2 w3 }! N8 ^
    2. Environment内部会访问MutablePropertySources来解析: G  M; G9 q* u- K% O* E, u' |
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值. w% A) `6 i( I$ U' c: i  c
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。- d% k; u8 d" j2 {
    4 y& P0 ^. j8 F& Y2 B
    下面我们就按照这个思路来一个。9 M4 n% U/ r+ o% J1 U4 @

    $ s3 n- L$ L: i( t: O6 p/ a来个邮件配置信息类,内部使用@Value注入邮件配置信息
    3 \$ v; \% A% T
    2 o- ]1 `( \. p; V! f0 \0 i* mpackage com.javacode2018.lesson002.demo18.test2;
    4 i& s$ x: r1 p7 K0 f4 j, v) u" }( |" q
    import org.springframework.beans.factory.annotation.Value;
    $ \5 u1 e8 C+ b2 C$ Eimport org.springframework.stereotype.Component;
    2 z8 T6 T  ?* P" W: o+ e* y8 [. q6 p5 [. e0 W( Y
    /**
    ( \2 d& F/ R% c9 Z * 邮件配置信息! @# w/ c1 Z2 B2 C6 [# V
    */
    1 [" K3 r( M' Z+ C@Component
    1 b7 p! U' W9 Zpublic class MailConfig {3 [, x! E3 g% P- ]2 B

    ; q. ?( `* X% b/ W    @Value("${mail.host}")) ]; Q7 d. R7 d; q8 k5 {1 T" k
        private String host;# [  |3 M) e0 D8 [
    9 ?! M- o9 U) o5 t
        @Value("${mail.username}")
    : F; }& |7 D7 j8 a7 ~1 X0 i    private String username;+ r" |' S* `0 z9 l: \& u

    + ^  L, h( _# l    @Value("${mail.password}")% z7 ]& V; E' L) N( r8 E
        private String password;
    * N! `1 j3 f9 Y9 a$ J3 X+ r$ u  @- N* A( B3 x* P0 ?
        public String getHost() {6 B+ q0 z. }- x% d" o- E
            return host;2 q/ u, S' x; I
        }2 q( \3 T) L/ _8 d+ r$ V
    + I- u* c! c4 d: M
        public void setHost(String host) {
    ( ~) h6 X7 Q- ^9 m0 M- `        this.host = host;
    9 A/ T& }# Y. g. @. C4 i    }4 Z( x' L' \2 C4 \* S; U

    2 q& ~+ v/ s% g6 \0 l, p4 l1 v    public String getUsername() {
    $ r; Y7 z! S  R) ?8 s: U1 k9 Q" |        return username;
    - V) T1 U( d! G  R    }
    & I* x6 R8 s* H. c) C( Q% \3 n' h$ v" Y0 t% J7 ~
        public void setUsername(String username) {/ g! B2 K; f4 `% {
            this.username = username;
    3 p) D8 K: F& o6 t8 u6 M5 M* ]% F    }6 Z5 P4 ^7 Z/ }1 q$ z  Y  k& q

    ( J) C4 z- Y9 P7 n    public String getPassword() {
    + T4 |8 F6 p+ o4 i4 o) a        return password;
    : U1 J4 w4 O2 M! @    }% H; d  X- z3 C
    % s- ^7 ?$ M: n# q
        public void setPassword(String password) {
    3 |% P) T) V0 z0 v5 H8 w! `- A( V3 p        this.password = password;( @: H$ f' C) r# X4 O+ {
        }
    ) z5 ?+ W4 ^' z
    8 q, H' e& D7 U$ [8 ]    @Override
    1 M( e+ d0 o, r/ _$ }8 _7 R    public String toString() {
    3 y# E* g6 r) D5 ^: O4 n1 a0 ~        return "MailConfig{" +
    ; i0 h5 _4 z$ H' N                "host='" + host + '\'' +. Y# O4 a! w) ~3 r( Z" \
                    ", username='" + username + '\'' +
    0 G# f: E- Q' v1 T5 L/ R# B4 K+ e4 _                ", password='" + password + '\'' +
    0 X: \, w9 p4 d+ @  i. B, I                '}';8 g# ^* U( K( f( H9 L# E; x
        }0 `; @8 Y4 K) e
    }
    # F  w: _% I. Y$ u6 L/ ?再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    7 J* ~  n* e$ `6 G' W' ^, }
    1 s/ P6 s) J8 d3 fpackage com.javacode2018.lesson002.demo18.test2;, ~$ C; }2 w! t9 ^( {3 F# l' f, J4 H

    7 d( n0 l$ d) E7 b! Aimport java.util.HashMap;0 U  c! R" `' B1 K7 S
    import java.util.Map;3 e* z* ^7 _* z3 a3 ]) }/ c6 F
    ; o$ w6 Z) Z2 s8 d
    public class DbUtil {- n" x# B! J" ~, S& q3 t
        /**' b( [# g% w, H# D
         * 模拟从db中获取邮件配置信息
    $ ^" E$ E  s: j& f, [# z     *' D- P. r% `% J" Z% f3 M
         * @return
    + e. N" C: B! i5 D, z/ s     */+ m$ u7 J) o, n3 l; U/ F
        public static Map<String, Object> getMailInfoFromDb() {
    + |9 u: L* t' ?4 i( |+ }/ Q        Map<String, Object> result = new HashMap<>();5 w7 w% N' J1 k- o* H4 }( c
            result.put("mail.host", "smtp.qq.com");
    - M0 ?! w0 y  H- Z: j        result.put("mail.username", "路人");' v' y" x4 M8 G2 o/ Q( W
            result.put("mail.password", "123");: C# V& A, Z* Q/ v9 t0 r9 P
            return result;
    " m  `, W7 u9 V' m& u    }
    5 ?" v: M* U9 D2 Y/ b5 W}
    " k* S4 Y' k' M来个spring配置类9 W% J1 J& F& q; b

    - J. D# y6 P+ D4 @, t( [( U1 {package com.javacode2018.lesson002.demo18.test2;! F! b6 ~, ]3 w3 o& A6 u* k

    & F4 ], x$ b* |4 aimport org.springframework.context.annotation.ComponentScan;
    - @6 V. B5 ^0 m$ m/ u5 dimport org.springframework.context.annotation.Configuration;
    ) Z8 E% _( a$ J; m) c; z+ i, A6 T1 i! [
    @Configuration1 T6 P8 |8 G: s5 m
    @ComponentScan4 ^; W  M3 v0 ]/ N6 k+ M
    public class MainConfig2 {
    8 m" B2 b1 _4 m! F( S/ a}
    ' ~2 s$ J- w. o' m下面是重点代码
    2 j$ a9 |+ @3 E1 v/ d/ q  p+ O) O. T0 h* R& I8 w) d( {7 f1 s
    @Test
    ) t# z  F* E6 s) A; N8 }public void test2() {2 F! }, J1 `) Y0 l# d: a1 N" Z# }
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();6 M; g- c8 W! r0 h: C2 C1 ]

    , [1 ~1 }# t% S, X    /*下面这段是关键 start*/; k; F" I( \$ {0 A% b; ~
        //模拟从db中获取配置信息
    9 \# D7 z0 a' }2 C+ _& J    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    - ?6 k; m! g1 \5 X, ~. C    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)3 P: L& S) l: |% f. l2 o
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    : Y% q8 q  u! ]# b* O0 P    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
      w  d, t5 w9 W. ^7 c    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    2 n5 g: J# ?! m1 e& t    /*上面这段是关键 end*/
    1 ~' C( |  f" _* X# z5 }% _. G& j; H/ r
        context.register(MainConfig2.class);
    ; K0 K  e$ c( ~0 }' j1 H    context.refresh();. m1 m1 z4 n! L6 I
        MailConfig mailConfig = context.getBean(MailConfig.class);( L) \$ b- E& R4 ~
        System.out.println(mailConfig);
    6 E6 b, m1 h/ s$ Y9 u) A}
    5 b5 O% h/ `; |注释比较详细,就不详细解释了。, T+ X9 l( Y; p  _3 N9 Z- m
    & w( a* \9 i5 s' Y1 M
    直接运行,看效果
    5 a! @. j8 I# a: x( P- ~% w8 B* x) Q$ m' d& _- c  ~; Z
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    4 U4 ~% C# X: A! E; K有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。6 O# `- t# X3 P( Z7 z0 Z

    ( L7 O3 J5 K6 E0 G8 s$ i上面重点是下面这段代码,大家需要理解
    * Z) M& N. j, K' V7 r
    % X3 i! a  o0 o9 \' r* ]/*下面这段是关键 start*/, X. z8 |# F/ o5 M
    //模拟从db中获取配置信息
    9 x# D9 D* e$ P% p. u4 rMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    3 B8 \/ y8 _0 J//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ' N' X1 ?- ^# `. W8 VMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ; U. x1 N8 j7 _//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ( l+ p. }3 K* o- O: f3 D/ r9 Qcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    , B( q% @  ]; i% t6 a6 E/*上面这段是关键 end*/5 c) ]" f8 `, v. ^8 i7 b- H; O; r
    咱们继续看下一个问题
      R: S6 X" ]- _( y, i  ]; B  A* t& L6 u  \
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。$ Z+ z  c+ Q0 L7 _2 G

    / j0 z& Z8 P; _@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。; p; M/ {; }& S
    $ \! \& p0 B) O) b6 |7 }7 y
    实现@Value动态刷新! u( [' }- O! \

    . a! C  E3 D  e; p% Z5 p& q先了解一个知识点% ]" w+ D; g9 g3 Y- G+ r

    , n& @; g3 A4 U1 d7 V! U这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    7 ?7 ?3 v# _9 {4 x. S6 W
    . b" V, g( ~; |7 [2 y这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解" p- M6 F% ~1 G8 A" n! U: S3 V
    # B( ^1 b# B# j% p
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    , v- E9 ?. N4 o2 e4 F$ T6 W% a8 ~9 E- G( C0 L; K
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    ' s- V  {( C2 J- _2 [* p( c. m这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    ! D; ?5 Z- o& Z
      d1 |- B( A( P8 i) I. C6 N+ l: p4 ^public enum ScopedProxyMode {; A1 m4 B# I9 |- w% M$ N
        DEFAULT," }  C. i) e( [
        NO,
    9 L3 x( |( d& R& _    INTERFACES,1 M0 G- f9 M5 {4 L2 `
        TARGET_CLASS;
    5 z% M# D3 A: N; C& a( e; S}- p& T; P! ]8 s: U4 `
    前面3个,不讲了,直接讲最后一个值是干什么的。  {% j5 X! g9 P: u& j

    ; S+ Y0 T$ V7 A- ?2 t当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    8 ^! x9 ~  `7 V+ p. ^; u$ F7 X# ~5 k
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    " @; Z5 R& O# S' G' H+ u0 A: ]8 C0 G" J& U; |6 `
    自定义一个bean作用域的注解
    ' P9 c  W4 ^, e) L  {, X4 P% S  y0 Y1 ]- h7 p6 T3 L
    package com.javacode2018.lesson002.demo18.test3;* ]7 b( t& M6 P' C! w$ M
    4 N4 |: K* v% r9 k
    import org.springframework.context.annotation.Scope;
    + M: r# G8 `+ N1 j( himport org.springframework.context.annotation.ScopedProxyMode;7 {( x) |8 q" I1 n" ^( Q. V

    3 U7 t3 m5 ]1 f2 Z; ~/ bimport java.lang.annotation.*;
    ; F% K$ O. i  h) d& E7 w  c# [6 E
    @Target({ElementType.TYPE, ElementType.METHOD})
    1 B9 _' \8 j6 x6 B1 G@Retention(RetentionPolicy.RUNTIME)3 b) w% W" `7 `* l, M7 U
    @Documented, k! H9 S6 g- }% {$ {
    @Scope(BeanMyScope.SCOPE_MY) //@1# J  L# C3 y% F) y
    public @interface MyScope {% d3 S/ J3 n8 j* X% P0 v6 n, W; _
        /**. |( v6 f/ n4 F* S* P2 L
         * @see Scope#proxyMode()
    5 K3 H$ _9 n4 K3 f  Y     */- _& @: I$ a) \2 ]2 V4 V8 G
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@25 r1 k" r9 D8 G. ]  @- Z* n
    }3 b" D5 z) I9 e! L+ c2 u
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。( m& E- K1 D) W6 C0 ~6 |
    # ?4 P$ k8 P6 i" U! y3 Q  j
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS* b+ p+ |/ E) k4 s2 F& b

    , u/ x* t7 ?/ z( O7 J. e$ W@MyScope注解对应的Scope实现如下
    ) Y3 N% v. c: u- \/ t) K4 @# |: i1 I% T" O% d# I3 [/ D6 c
    package com.javacode2018.lesson002.demo18.test3;
    7 R4 p, Z& g: ^+ w0 a
    ) L) Y" H5 n: X5 g0 ?$ M% u. wimport org.springframework.beans.factory.ObjectFactory;
    " C( ?3 K5 t) a8 B7 G/ p" Dimport org.springframework.beans.factory.config.Scope;, s$ P' j; n# m
    import org.springframework.lang.Nullable;( A, s# v1 S( J6 i6 `: B# A

    ! T. `6 e: f' p& }; `; @/**/ {( J: j4 s1 _* o& f' x& j; B# d
    * @see MyScope 作用域的实现
    # ~  |( N( x. p' k2 T7 t */
    $ [5 }! N; O8 \; spublic class BeanMyScope implements Scope {, D' j- X# L: \! y/ L- `; ]: ]* t! b

    ) A# M( e# n. ?! j4 ~; _5 F5 C+ A    public static final String SCOPE_MY = "my"; //@13 {+ K) F; J9 w- t
    2 ^0 }5 N1 |& B( |7 O
        @Override1 D" q. I3 g* s6 x) n! L7 u
        public Object get(String name, ObjectFactory<?> objectFactory) { 8 L! ?1 U( @' b0 {( ~& x
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@23 e5 H: D1 s7 Q2 m
            return objectFactory.getObject(); //@3
    ( K* ?, U$ b1 o4 ]% D) u$ F    }
    # |, x* h! y5 \! ]: w0 ?  H0 H0 S- {( _0 L
        @Nullable2 R( h# g1 S+ f
        @Override% P5 Q1 h2 ^3 z; z
        public Object remove(String name) {+ k: d5 v  M5 `/ M9 u
            return null;$ }9 Q2 X5 a/ C6 M: l3 F1 g% w
        }
    0 A# }7 H1 o' c2 h! Q" C9 _
      u2 T8 u- M. e9 w- y    @Override: t3 C8 @# X) J# y
        public void registerDestructionCallback(String name, Runnable callback) {
    2 }! L" i" L0 U" ~! U" r" A( j( k# ]- J3 M9 D
        }
    " x/ w2 ~8 v- d3 N% q9 f  y4 I+ e% y4 T- x: P8 ]% X
        @Nullable
    8 K" r& t4 W# ], A1 p* {    @Override, i7 I% P/ d3 b) w' L1 p4 i$ l
        public Object resolveContextualObject(String key) {
    , O: D5 [- S; m        return null;6 s# S' p) Z/ o9 S/ J, ^+ t# e( N# I
        }
    " b8 ~7 _; `8 W! U, G. W4 j) U- C
    " d5 U# L. I3 @' E6 j8 s8 z; i" x    @Nullable
    9 ^  t; c+ A* L; s, C: j    @Override) Y- O7 j- V9 V2 ~) z) `6 T
        public String getConversationId() {
    8 j7 @* Z" P1 i2 T        return null;
    7 e% L7 t. X: `4 t, P: {- c# o    }; v5 B- P! |/ Q
    }+ x+ b0 L! T/ i9 A2 H
    @1:定义了一个常量,作为作用域的值
    . d. X' X' j/ h' ~6 B4 y7 y
    : d$ O* a1 w+ M) ^@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果/ c. O4 @9 b9 s; v
    ; Y  T5 H. k, A2 a  s
    @3:通过objectFactory.getObject()获取bean实例返回。
    0 u! T/ k# F7 f% i. L5 b( k; z9 S
    ( P% ~1 C, r2 z! G# h8 t0 }/ F下面来创建个类,作用域为上面自定义的作用域
    * d! y" C( K! A. A& [0 s. g4 g, ~& C) j4 I: V4 M2 Y+ v
    package com.javacode2018.lesson002.demo18.test3;
    7 ]1 f* {. N' e& x0 S5 y7 ?  r5 G5 L  V  L1 u! u
    import org.springframework.stereotype.Component;
    ( h* `6 y0 v: Y# y& A' g$ F* G5 r8 }
    ) K2 W4 K6 q; \, d; n( simport java.util.UUID;- l. p: `/ \. M& R) T
    0 z( j* B4 q( V9 E. _
    @Component3 T6 ~+ T4 f7 M
    @MyScope //@1 3 \8 n2 g! `* E' `) h3 E/ _! d
    public class User {
    0 F1 p* I( m' w0 N2 q& e& y  s) ^- o" ~3 Q/ r
        private String username;
    1 @3 c5 W% \# s$ k9 k" R4 f0 M/ H5 p- m" b2 m
        public User() {
    ! F5 `2 E7 q; y" T3 \        System.out.println("---------创建User对象" + this); //@2/ ^$ Q. P( I" n( Y: T/ m* _
            this.username = UUID.randomUUID().toString(); //@3+ x1 N; F: U% L* M( \6 j& n
        }  s; `) `2 ~( C) W( E

    * z2 g. M% \; u    public String getUsername() {
    9 ]; K  x* i8 g( ~; y4 ~        return username;) z7 ~' a0 G: w
        }2 Q6 e/ ~( ^5 Z. [

    ; [3 o# t* W: `$ D/ |    public void setUsername(String username) {0 F; m3 [  v% f$ }3 L  O
            this.username = username;: A$ }: ?& I! Y5 }
        }' ]+ |( E7 ?* [1 k' d/ B
    8 C, ~% M% X/ y* @( L/ e
    }
    8 T; c( T0 t: J@1:使用了自定义的作用域@MyScope8 [0 r- i9 y9 T3 L, X
    ! h* H8 C& X2 ?* `
    @2:构造函数中输出一行日志
    7 n7 Y* f$ o" S1 n% p$ j) z
    1 D- _6 J( f, W4 e@3:给username赋值,通过uuid随机生成了一个+ Q7 I4 s: S  @& b. d3 B4 [
    % k6 Z4 s  w/ \( [, g9 F
    来个spring配置类,加载上面@Compontent标注的组件
    2 r7 E& J) A0 W  R+ I
    5 @; [. S8 i8 ?  s8 A2 ~+ `! z0 cpackage com.javacode2018.lesson002.demo18.test3;
    / Q  F: c/ W7 G
    - p/ |" Q, n: `9 k+ _6 cimport org.springframework.context.annotation.ComponentScan;* l, z6 E# ^: O3 ~  \6 B- q
    import org.springframework.context.annotation.Configuration;& U3 C9 Z+ A5 e! c5 J/ F6 q7 L, w
    - H  o+ v' O+ t& Z
    @ComponentScan/ r/ p, ?: U* G: l; D% }
    @Configuration
      v; E: E2 V4 Y: ~8 ?; |public class MainConfig3 {
    ' M# ?3 ]9 [* q) v}
    " a0 j8 {4 k: q  b' c' Y. d; o1 F下面重点来了,测试用例
    1 [" y0 Y1 n8 e* j4 G$ F$ L, h" v3 i. P- M; S: B, [
    @Test
    6 T9 A, U5 l7 ?, d1 ipublic void test3() throws InterruptedException {
    & Z- b+ j4 F3 h" e7 [/ C    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();/ C( ~6 a" C% g% K$ O! W, U. L
        //将自定义作用域注册到spring容器中" O8 N% c/ Y7 M) B
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1& S9 i& H( \. Q2 W% H0 {
        context.register(MainConfig3.class);7 ?" G/ z% p6 \
        context.refresh();
      l) ^2 a  |* o! K5 y" ]/ l; Q; M- D+ l$ i/ D' P% ^9 C
        System.out.println("从容器中获取User对象");
    " T( B+ L$ o# n0 c    User user = context.getBean(User.class); //@2
    % U9 j& ?& x2 E    System.out.println("user对象的class为:" + user.getClass()); //@3
    - R" c" C( ?' M3 f4 K% [1 n7 r; e9 ~+ K$ L+ \/ p  K2 D& y. N
        System.out.println("多次调用user的getUsername感受一下效果\n");
    3 l3 a6 T8 b# A& _) ]. |8 L    for (int i = 1; i <= 3; i++) {
    4 h: r+ }+ v9 Z0 D5 ^7 t        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    . E" Y! Q/ I6 R3 x$ i        System.out.println(user.getUsername());- o9 u/ O/ h# Y0 b' r9 e
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    5 c0 N; w5 x; x    }
    % U/ ^- J/ C$ U2 z; b8 z}
    - @1 }; w8 G/ J9 s9 c- O% D0 D@1:将自定义作用域注册到spring容器中
    + Q5 v# w" _) @
    3 B# E. T: S' ]' L6 s  x; z@2:从容器中获取User对应的bean6 }4 K- D$ u2 K9 ^  W, O7 u

    ' T0 q& M8 C) i5 p0 o/ ^7 t  d' ~@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的& _3 O! K2 H" Z% A. E) D

    4 S$ \' D9 v" @- N. N' z代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    2 Z4 O; Z! _7 S( v1 {1 z# @" l' l6 K; @6 T
    见证奇迹的时候到了,运行输出% P8 D, p/ d& a4 |# w- B
    / w: a: s9 U% a2 q
    从容器中获取User对象
    9 A! m. `1 r/ ~1 ]0 buser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    $ m& S6 X/ Z1 @2 J0 V# [多次调用user的getUsername感受一下效果
    : m  h2 U1 }6 N( e; s5 J% H% ^, }9 \( j9 M" t
    ********
    4 x" ^+ z) b. \第1次开始调用getUsername% N9 \( ~/ i5 J( Y' K3 `
    BeanMyScope >>>>>>>>> get:scopedTarget.user/ N+ o; s+ W  i& J* \8 E
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    / h9 A& S$ I' n$ p* w& S& C* c7b41aa80-7569-4072-9d40-ec9bfb92f438
    , T* ]+ |( {" L! J第1次调用getUsername结束
    2 e* E& z6 O/ J8 W$ R9 w3 a- I) r********" l% e2 o, J5 f! \  Q. v2 j
    . {' L7 z+ v- I# e6 V
    ********+ o% l/ i! `4 c* [$ a) f' k8 c
    第2次开始调用getUsername
    1 N: I( V  S, NBeanMyScope >>>>>>>>> get:scopedTarget.user
    " ~' b% s* q+ Y* j( E) f% Q. S---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    8 h9 ~2 h4 y) r( A01d67154-95f6-44bb-93ab-05a34abdf51f7 q: O0 |( N( Z7 U
    第2次调用getUsername结束; m& ^5 ]! z8 O5 z. ^% L  m
    ********
    1 y9 n+ C+ ]- o1 j( }0 @* I) f7 |% d' N* w  U. r' ?' o2 h
    ********
    8 m/ T* e! ?* w) A第3次开始调用getUsername
    - [) b# F) f: uBeanMyScope >>>>>>>>> get:scopedTarget.user
    9 ^7 U( Z) N) f; T---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    ' f& ]) W  J8 I7 j" }: P9 X$ [76d0e86f-8331-4303-aac7-4acce0b258b8
    # I5 D) E& |( w) s9 Z' m第3次调用getUsername结束
      X, D4 g9 {: N  g$ U********& a, o8 J7 l6 Z! P# r' L1 H  c+ r
    从输出的前2行可以看出:3 J7 |' ?* h& E9 ]/ z+ O2 S) O# t0 x

    / |- z- l  e0 @" @8 o7 E, s调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象: m) n4 m$ y) c" _6 `. m/ N2 \* c0 `. n

    " Y5 i( b% `+ D( O1 u第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。( V2 W' {3 T: D5 N: [) f# W
    + e& a7 m2 n# M) f) |7 @$ ^' i
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。5 H+ h$ g: c0 u* x0 n0 t! E2 H" _

    ' t$ l3 x" R2 z) F1 n$ x/ b通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。9 @  q7 N1 O6 O: d+ ?
    + X1 I+ P/ Q9 l5 t' k6 c3 r
    动态刷新@Value具体实现3 A6 u' |1 d) G/ ?

    , {. ]3 {8 ?+ y6 h2 K那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    1 q. Z0 D7 u5 u9 X. t! I. t# L$ O) x  ]
    先来自定义一个Scope:RefreshScope
    , s+ [5 o2 x' v$ S( V" O1 X8 K, k* L% V& K4 o* K$ m! Z
    package com.javacode2018.lesson002.demo18.test4;% J) b; S8 t; T" s$ ]' o5 [' P8 K4 I
    # o  E6 P3 v+ J( m& X' C6 w/ N
    import org.springframework.context.annotation.Scope;9 |/ G2 N4 W) h/ D& k1 s
    import org.springframework.context.annotation.ScopedProxyMode;
    4 }5 j' m* n3 n) G$ \! j* I# Y' v. F( O! ]
    import java.lang.annotation.*;
    9 S1 Z  f- Q4 d3 x: {( p8 p4 _
    % U/ c$ E" I' V* s4 X@Target({ElementType.TYPE, ElementType.METHOD})
    $ k* W& N$ w7 {/ @# N7 Y& {@Retention(RetentionPolicy.RUNTIME)# `, g7 [* C- Z! k; ]$ q5 t$ X" x
    @Scope(BeanRefreshScope.SCOPE_REFRESH); d7 x! e0 A5 K+ F
    @Documented' p1 |6 E3 d+ m4 g
    public @interface RefreshScope {' E! s; R* [+ _: t4 o# x+ @* E
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@17 A* [8 o. ~- k1 ?1 b( s4 z$ O
    }
    % Z. L7 ?7 i3 a要求标注@RefreshScope注解的类支持动态刷新@Value的配置! w# ~1 ~7 S! I

    , N# }6 x, f$ _+ i8 }! n@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    + G, \# a7 K' _3 K3 |9 \8 Z# j3 x6 ]' ~3 Y7 |( E; P, R8 L' Q' I8 l
    这个自定义Scope对应的解析类
    3 O# z& M6 y( U: i# u5 q% h7 w0 \- A7 U* _6 z7 a5 k; n" A5 s: r8 `6 g
    下面类中有几个无关的方法去掉了,可以忽略0 A! W/ f" ?  z6 R1 j% O
    1 D; T# b7 i9 c7 {
    package com.javacode2018.lesson002.demo18.test4;
    - N: j. Z+ q* o  r) \
    ) Y/ A% j" l( Y& `2 n
    3 [' ~3 A6 m3 H) Y' h! Q) jimport org.springframework.beans.factory.ObjectFactory;
    ) y: K3 M  U; h7 g6 U- w) Himport org.springframework.beans.factory.config.Scope;
    0 E5 y0 M/ O# n; d) V5 Iimport org.springframework.lang.Nullable;
    # {) y% `3 c3 V) g2 W, t+ M2 ^
    ! Q1 d# y3 i$ o7 @4 {# W) `import java.util.concurrent.ConcurrentHashMap;
    5 u5 X) s( z* ?" h/ D. `: ]3 O8 O9 S1 D
    public class BeanRefreshScope implements Scope {/ \1 k0 U1 Z2 f$ I& B( A7 L

    ! G" ?3 @, w4 c' x7 h. y    public static final String SCOPE_REFRESH = "refresh";( {5 q: _, P' M+ g+ Z
    5 n# W  u7 [9 C
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();$ w- U  U2 F- i5 a! |- r- P

    8 q5 l( W; \( H) d  z    //来个map用来缓存bean' Q3 R: K/ Z. ]
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1* k  k4 s4 ^0 U, O

    " e; X: T9 M7 t    private BeanRefreshScope() {
    + w4 N/ O5 B1 \* `    }  F) n0 R% A5 e9 F; e' X

    ) s7 u" e. T# M1 ]5 U    public static BeanRefreshScope getInstance() {0 v1 `; b2 F& C( Z. A% E6 q5 j
            return INSTANCE;2 A3 c# \$ Z1 a, R. j$ L
        }
    : x" X, B  U& |# Z! r
    2 a5 u# j( I# y. M) u0 d$ E* n& w    /**
    3 G% j& Z! u% e1 ]) i; b     * 清理当前% E) b% \# M: ]
         */
    9 M* n5 e# t. P* ]    public static void clean() {  ~4 D; R+ y+ }0 R
            INSTANCE.beanMap.clear();+ q9 C. G$ C2 @, h
        }
    6 O, D) E9 S5 S( K8 _; Z# b' F! H6 L
        @Override. D0 }1 t. K4 X
        public Object get(String name, ObjectFactory<?> objectFactory) {& d4 g. o7 y7 [& A7 h# b
            Object bean = beanMap.get(name);
    8 Y. v# t% [0 C: i        if (bean == null) {4 G: J$ Y, E/ D/ Z+ F
                bean = objectFactory.getObject();8 |7 k- C# b2 S4 a+ v) G
                beanMap.put(name, bean);
    ; G1 J3 h" t5 k# U- J* ~: U; L        }2 K+ g2 G8 J; `
            return bean;
      O0 c* V4 I9 r) S9 I  y, f  u    }7 g6 v5 b1 @1 j. O9 o) U% `' `
      {3 i1 X, G3 r8 h. O% `
    }
    1 |4 V7 R, c) x) P3 Y9 t5 l上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    ' J  \. q9 J& F! q+ L
    + M! o. c! D. D0 C上面的clean方法用来清理beanMap中当前已缓存的所有bean7 Y8 ?3 f4 `; z" f

    - h1 H2 K* j: B! X( J8 i来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope' W+ Y# x% x4 m8 n$ Y/ \

    " }9 I% g; U* U& p* n8 {package com.javacode2018.lesson002.demo18.test4;
    , h* {; N/ H$ W" g. r. s% Q& [; [6 [) t* ~5 q. o% B; x. {3 U
    import org.springframework.beans.factory.annotation.Value;- _; T7 B# v/ E8 ^
    import org.springframework.stereotype.Component;4 F  k' m) a/ K  z6 B+ P; M

    0 n# k( E) S& M' d% X/**- R) Z2 _% c: `1 t
    * 邮件配置信息  i- K3 q( k5 F+ Z" ~
    */
      k2 j1 \4 `- `. e8 a& a6 V@Component
    ! `: h6 P9 F0 \) R; I, f  I@RefreshScope //@1
    % T, X$ h& H4 w! g9 e8 g- @( i( npublic class MailConfig {
    * z3 i1 r) `0 b2 x% }: H3 o7 ?' F$ R- H3 G8 d% c$ j
        @Value("${mail.username}") //@2" ~9 Q0 ]5 c5 C; }3 n/ ^$ B
        private String username;3 x& P' {( z5 {* R/ a9 s% A: n
    : w- r3 e# d! U. s' X3 ~4 k
        public String getUsername() {
    6 Q! _" I7 E9 r# ?! t        return username;
    3 z5 {/ u# b7 ]  R' ~( F    }) g- c  T/ {1 A+ {' `
    & G; l/ Y! y# j
        public void setUsername(String username) {
    6 {& E1 K; B3 f1 s/ Y( Y1 ~0 c        this.username = username;
    0 x, v2 o+ j& E: k7 o: t* O. l    }$ i) n' V1 S4 d/ z  A4 w) }

    + d7 _! e* N: \: [# [    @Override
    2 ]8 d2 E! a5 ]* n    public String toString() {
    & P: }* f+ t8 u        return "MailConfig{" +* k0 M! u' u, e1 V
                    "username='" + username + '\'' +- n- P. D5 Q; f& M
                    '}';7 _) l$ g- e! b0 q
        }1 v7 h; A7 }3 w$ g; T
    }
    8 s* o, u+ y5 T) Z5 S@1:使用了自定义的作用域@RefreshScope
    9 j* l) S) u& [+ ^6 |* j( M; h0 d3 h  I! a" e' d3 [. p
    @2:通过@Value注入mail.username对一个的值  U9 z$ {6 [) d
    ' M3 S% B/ U3 f( s7 ~
    重写了toString方法,一会测试时候可以看效果。' G- N, j& P0 h3 p

    5 K9 n- [$ P" N2 [. F4 C再来个普通的bean,内部会注入MailConfig2 d  H- _$ F7 k0 c

    7 z$ K" Z& U% Bpackage com.javacode2018.lesson002.demo18.test4;3 b  J5 U9 i" D& ]6 u1 t/ s" ]

    3 ~; x9 r* y: h9 j7 e" c' limport org.springframework.beans.factory.annotation.Autowired;
    5 h! u4 q; i0 I! _) Z7 S* dimport org.springframework.stereotype.Component;
    . z# W- c; |5 ?4 G% @. k/ v
    5 L( d: {, h- [  o0 v4 v4 N7 B+ _@Component% c  F0 I  t) D
    public class MailService {2 |8 N7 |" Y7 ^! B
        @Autowired5 s6 j; `+ ^' n
        private MailConfig mailConfig;
      b3 n/ R, o7 g/ ^* g$ y- c3 {# E
      o: L) r7 k/ d9 W- v* p    @Override
    / |6 T) d" G) }% b: A! k    public String toString() {
    + s$ @5 N: Y, S# s  m: G% l        return "MailService{" +! x( o( V: m( H# y  F- B8 l
                    "mailConfig=" + mailConfig +
    & U, W7 A+ N% I* R8 V                '}';3 c! k5 H8 o7 @6 [, j
        }. ^, E/ h- t: h" F+ |
    }, D1 c+ P5 n( f
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    % f7 j- h. @( d+ C3 F$ z
    + x4 j1 l6 e9 F- l/ A来个类,用来从db中获取邮件配置信息2 V8 Z/ L. K& |8 o4 F' U. f; m( a
    + D: i+ ]  H  Y
    package com.javacode2018.lesson002.demo18.test4;4 l  K; Y8 u' s4 z0 }) n; w

    ; S  x- j  s- V9 c! mimport java.util.HashMap;3 x- L; n# _* A. k. p8 a
    import java.util.Map;
    ' i3 `! _0 `0 rimport java.util.UUID;: {6 @1 ]/ `! l* R3 q# T3 j8 ~

    # ^9 I2 a" Q* A0 ]5 w+ k* {( @public class DbUtil {
    ) y' }' ~8 J' r    /**
    + w$ @3 N% _. M& @, o& p- k5 g9 u& [     * 模拟从db中获取邮件配置信息; I! K7 Z! ^8 Z
         *
    - R3 s: e; {. B- I1 v     * @return
    5 F+ M# J8 B- k9 ^1 Q: \3 [     */0 i& {% |2 Z9 Z4 x4 D
        public static Map<String, Object> getMailInfoFromDb() {
    6 B! A; e' c+ p        Map<String, Object> result = new HashMap<>();
    + H1 X4 r0 w* @- b% C        result.put("mail.username", UUID.randomUUID().toString());  M7 ]; N9 U$ {% P  r/ h
            return result;5 M! J8 O/ J  C0 F, h; J. t) i
        }
    9 A" d7 E: z" e2 E) z, Q}3 N- n5 D; I. ?, [
    来个spring配置类,扫描加载上面的组件9 |- r, w- z0 w* k9 T; V
    & s8 Y" ]' [* B" J7 X
    package com.javacode2018.lesson002.demo18.test4;( @5 e' g5 W( L
    " m+ W/ q8 Q0 f" W( L
    import org.springframework.context.annotation.ComponentScan;
    $ Q& o% l8 z. Y1 e8 [- m" Aimport org.springframework.context.annotation.Configuration;( z! G6 S% t& x

    * ?  R8 }' b( q, `@Configuration
    1 F' y- z+ }$ m: ?1 G/ D! Q@ComponentScan- J+ k0 a9 S% h- ~% q  }, X
    public class MainConfig4 {1 G# [% P7 d7 q
    }# B* [* U. _6 G7 R, V' n
    来个工具类$ x* [5 U4 g" B- p, H+ i1 h- e
    & M2 E5 ~! ]% P. ?# s6 d
    内部有2个方法,如下:
    , z1 U$ u5 c0 C( L; B5 Z; T( n$ H0 e2 d$ L+ N
    package com.javacode2018.lesson002.demo18.test4;* c. p6 V5 a* H; ~- P

    5 J7 `  O2 I) b- [6 i! }import org.springframework.context.support.AbstractApplicationContext;1 R: v5 g5 j: _* c1 G4 W
    import org.springframework.core.env.MapPropertySource;5 \3 o* r) Y5 G( {8 U! t

    . o: k/ L- {* X4 h' F+ Z6 b& V2 oimport java.util.Map;
    6 q* x  ^0 _6 @: z5 z" e& G" Q# i$ v: ~3 ?9 a, ~  O* p) Z
    public class RefreshConfigUtil {
    " j$ Z: f0 K: L( S3 L    /**0 r4 I8 F2 i: U
         * 模拟改变数据库中都配置信息
    7 u2 x- ?1 z8 N     */3 m6 V2 c! U7 M% n1 H
        public static void updateDbConfig(AbstractApplicationContext context) {( y8 Y0 Z3 b, r3 b6 x
            //更新context中的mailPropertySource配置信息
    1 d# F5 @) b8 q+ g6 W        refreshMailPropertySource(context);6 {/ l/ H0 @. x
    , e* u* z/ u( S9 v- w/ b
            //清空BeanRefreshScope中所有bean的缓存, \4 I  g, a% [
            BeanRefreshScope.getInstance().clean();& c+ m9 O9 a. j0 D" D' B
        }
    9 W# M6 y2 ~9 |& f$ X) k/ O0 l& y, l1 }8 E  S
        public static void refreshMailPropertySource(AbstractApplicationContext context) {" N3 v8 n* m7 |% N# q& [5 M
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    / I1 e! L6 e# }        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    0 r8 m. D0 J7 c" B/ e) g4 B        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ) o4 V/ Y5 l, h# H4 p) ]        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);+ l! V: x* t) L3 j! r5 J4 H5 \
        }
    + g( \) }$ s7 F7 x" q# Y% B& d5 Z; D: Y* Z, J1 N, r
    }
    6 ~( F4 a: i& S8 e& RupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ! E! J" w9 n/ o; p# v( N* a
      d' V7 {9 M* J- H& y% j& p1 }BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。' V  V. B! r# \$ V: Z3 t3 l
    : I% R) E, Q5 }$ z  l0 G0 N4 f
    来个测试用例) h3 D! C# M5 `) b2 C# s7 t

    ! ~! J0 y. ]8 I# l) G@Test4 D2 {' C! [) L; D5 F+ |0 j
    public void test4() throws InterruptedException {% `( x5 H( g# d; K
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();: R% M) M. L9 \, P4 N  W8 p( v8 a4 p
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());- W2 S( `' L0 t9 M# l% E* y, k
        context.register(MainConfig4.class);+ i5 w2 E, {1 X8 T) W% t9 R
        //刷新mail的配置到Environment- ]# \% f- Q4 R& P7 ^' E
        RefreshConfigUtil.refreshMailPropertySource(context);
    ) v) ]& j! @1 i1 S4 ^9 z2 [6 ^    context.refresh();: [  `- e( b5 {  P2 \8 C% i) T
    ' p$ k" Y" k0 J9 r; {1 R, S
        MailService mailService = context.getBean(MailService.class);6 _8 }( N, u* K: K
        System.out.println("配置未更新的情况下,输出3次");# S( c( _7 ?) W/ u7 i7 L
        for (int i = 0; i < 3; i++) { //@15 n& {& f; s8 F0 g% j: B( I+ T
            System.out.println(mailService);
    0 a6 w! L/ {: s        TimeUnit.MILLISECONDS.sleep(200);
    : K0 S2 K: y$ ]* [9 c2 X5 H    }
    8 D, Q+ b: o% R6 @9 a( w# e6 a9 v0 X5 Q. Q
        System.out.println("模拟3次更新配置效果");5 M& i4 n7 j1 \4 t- ^4 L  A" l
        for (int i = 0; i < 3; i++) { //@2: T8 U& E3 f9 \# s- g# h7 l/ ?
            RefreshConfigUtil.updateDbConfig(context); //@37 c4 |# B/ `3 a7 B
            System.out.println(mailService);
    ) m& k8 m2 p4 A9 w% t        TimeUnit.MILLISECONDS.sleep(200);% ], v; H4 w# y5 w  V
        }
      Q- f% `4 t" G5 k; F+ E8 R}
    : r6 s1 f; ?8 C) |@1:循环3次,输出mailService的信息+ u1 k6 b5 V4 [. V: }
    ! x0 M& K" k- }% z9 ]/ c
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    4 I5 {0 g3 O5 O! r, O6 A7 }& \- O3 |0 v/ |% ?
    见证奇迹的时刻,来看效果
    3 g9 y1 m6 t2 Y$ ?1 D
    5 X4 v  {1 F, p配置未更新的情况下,输出3次* c2 M" t( R8 M3 {, Z9 Z
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    - B( q+ ?# g' t: uMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}& ~3 `/ e) w3 R
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ s8 w7 L) U% W& i
    模拟3次更新配置效果
    . V1 A; A, `4 B0 B- o, e6 nMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    ) y( P! d: q( u" J8 E& tMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    9 w. y7 `3 @, @9 a+ O0 Q$ v! bMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}; y2 k( I" g( z5 p3 g7 y
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。" U+ @! y) H* F6 U' O' L

    ) O1 L2 z* n$ J小结
    - I& u0 L: B2 c1 S; @3 B+ B2 N
    3 S4 M  k, d; Z: h动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    ) V& f0 P$ L* b: `9 e1 J' @+ h
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    . p. `+ b  T7 |% t' L
    8 `7 `% q1 {  c% a/ J: ~+ o, Q总结
    - g7 S& U1 J1 g) ^- S9 i0 [$ _' |( e
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    % `' H6 u/ f& S! U" t8 d# r
    7 l' b# s8 l  j1 K/ m  K1 x* V案例源码
      Q5 u6 w8 l. n& Q, P
    1 ~$ R% f5 {0 s- @$ Fhttps://gitee.com/javacode2018/spring-series5 A  k  r( f% t, v5 _' v, N8 j
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。3 O5 [. `/ \: c) G( Y/ Q

    1 ^9 y$ Q* I! a4 bSpring系列! u4 ~/ b; e8 K/ Q5 g! H
    + r0 s; ^$ j" i  [( v5 R
    Spring系列第1篇:为何要学spring?
    7 u& ]. \$ N* ?* g: }0 P9 j: w3 x
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)4 {" ]7 i) r/ f
    1 o' a: H- E& ?0 w: |* W/ ^
    Spring系列第3篇:Spring容器基本使用及原理: y9 U- Z: P; G9 |' g
    ! C2 S' ]9 W6 [# Y3 Z
    Spring系列第4篇:xml中bean定义详解(-)
    2 a2 t" E# j2 \1 d! C- v/ ^6 P  a# Y0 Y: Y
    Spring系列第5篇:创建bean实例这些方式你们都知道?: n) m/ g. K, f4 I, S5 A/ x% p

    + ]3 Y( k7 i! Z8 f" \% RSpring系列第6篇:玩转bean scope,避免跳坑里!
    . {6 H! D% Y' r1 ]( f7 T1 C2 h/ ]" x1 l" `9 [
    Spring系列第7篇:依赖注入之手动注入
    ( \& O' h6 p& g  F) u) o% o, v( L  `) y& R
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持4 b6 b2 X' l6 j& |, a

    5 a3 m; P; S' n* ^Spring系列第9篇:depend-on到底是干什么的?5 K# w9 c3 q# T! b

    5 q+ z) q  \3 z) ~9 f) b1 S7 GSpring系列第10篇:primary可以解决什么问题?, `( j4 q; F! y$ R. @1 w, U2 T
    5 |& N' X4 l9 [5 i
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    # H. o! l( ]9 d- j/ j1 j# g$ M
    3 W' _1 q+ h. K. f( g  ]- kSpring系列第12篇:lazy-init:bean延迟初始化
    . Q, g4 J  e0 ]* k, S! x8 Q* F
    & b. N- b0 Q  _, hSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    % _0 U, l; y7 \( q6 R# e: M- O" \0 f% I$ l3 H
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?! h( a" N3 Y2 l' G

    * n% Y; \/ v2 ?7 \5 v# D! G- `Spring系列第15篇:代理详解(Java动态代理&cglib代理)?6 M1 Z! h0 r4 T9 s; @/ h. x* U" u$ D: l

    * W! b! n8 S0 C0 k2 L9 t! k1 }Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    - r- f' s5 a: K5 R/ {5 V
    # E& m+ U& E% s2 ]$ [; j  a! W3 xSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    5 k- U7 s% l0 `: P( R3 G1 [* v7 ^$ |6 j& ^0 p) X* G4 Q1 E, b
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    ' T8 h, I+ ~+ z. J& l: j2 @
    $ ~) e) s7 r  i1 JSpring系列第18篇:@import详解(bean批量注册)& s% X( ^) k1 X! K+ [
    2 p- d0 o, ^/ ?1 T$ @
    Spring系列第20篇:@Conditional通过条件来控制bean的注册; m. a$ S; T  w8 h( b7 z
    ' s. v! B4 K: @5 o" n+ @- n
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    7 W3 R: ^" g1 p" k" e0 {2 C# b9 M7 ~9 h; K  k0 R6 E
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    : j9 A: B1 c# y) Z' ?' ?- j
    * J0 z5 H( x% Q0 ~" _Spring系列第23篇:Bean生命周期详解
    5 T* P( }9 ?6 l* ~5 ?. f5 g. `3 y. t+ m
    Spring系列第24篇:父子容器详解: r/ O: ^" E6 l: j9 `$ J* ^6 z
    8 q# {: O- X- X8 D) K5 G4 e. H
    更多好文章
    1 p$ c% ?+ b, z( k5 H
    ) r, l" o) x  Z! xJava高并发系列(共34篇)
    / A8 m3 Y! f; ]- L' @+ c; @2 i9 _. B% }
    MySql高手系列(共27篇)& u" C! @# h' T& Q) G

    5 F+ d; n9 b! l8 {Maven高手系列(共10篇)
    2 P2 C: }! X) N) p9 i$ i
    * l9 L4 ?6 z% \% [Mybatis系列(共12篇)
    / q9 q# @- ~& a, S3 z" k
    # x( ?( I* K% ^; n& ~7 |! E聊聊db和缓存一致性常见的实现方式
    . ^; Q! `2 d$ `5 E8 [1 p
    0 _( a' L' D# x  e! Z2 M  r& H接口幂等性这么重要,它是什么?怎么实现?' O- b! C* G" \9 \5 T6 B

      p" l' s  z$ {, e: ], G泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ' _) R# W6 b: [* b0 k: j. V6 ]. Q————————————————( r. k/ ?; Y) x, }) `9 y
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。) j  n, R5 v2 {* b; b1 S
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    , H( Z6 Q3 U$ g1 b. A1 G5 r9 W' U  I

    ( I+ p% w& n$ l* ~
    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-7-21 22:57 , Processed in 1.492202 second(s), 50 queries .

    回顶部