QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5452|回复: 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!
    7 N# G- J6 ]: N* e疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!5 V- _6 l2 K! U- y) T' @

    + U4 w/ m9 d$ m9 e8 s4 p' X/ X面试官:Spring中的@Value用过么,介绍一下* I# K& i4 `) \3 h9 [8 s

    $ ?1 \' R- R9 Q3 M/ L" f7 B( T我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中& e" t: R5 P% o/ m' e" ~

    " n& o8 P- n  S$ {+ \面试官:那就是说@Value的数据来源于配置文件了?( V8 o9 p* }4 n' K
    1 ]* ]+ s3 P6 @4 P' R
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置4 q7 S. K& ^  z; N" p" ]

    2 n3 v3 h1 V1 T% s面试官:@Value数据来源还有其他方式么?
    . V( B# _3 m9 H# S4 c' K5 t! J/ M* t
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    9 B5 @& @  ~+ x' q; N, O' g
    + ^) m+ M0 A* D. c- ^5 _面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?3 {# y- i) O' s

    ) o) Q* s& {, w  Z. w我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧2 p6 U! E( j1 o' w9 x( ?! ]
    4 O( a6 y& O  L+ y# b" c
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?# u+ {+ R6 Y. _3 L

    ) B: a0 y+ o/ t, I我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能1 F, h  \2 I6 I! H
    2 u3 u! x3 W; ]2 G2 Z% f5 s
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    $ a) d% H4 l; J* j. K
    & \8 N$ U3 I) k1 k我:嗯。。。这个之前看过一点,不过没有看懂
    3 G2 A9 v9 o  J& e. B3 O1 s" @
    ' G, s; b9 ?: {* t) y面试官:没关系,你可以回去了再研究一下;你期望工资多少?7 Q! g. O3 k; n

    * y* f' t% l+ o我:3万吧5 P( D0 m1 L; |, }1 \, k  j& q0 g
    ! \2 l- @0 O8 i3 ?" Y+ u' L, S
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?# [  {  }/ g9 o: h0 `  p5 }3 C" f
    ; T! P& I3 g$ `9 ~- q. J
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    4 r% a7 H; f; \% K& C
    2 @$ i, F' r$ _/ A" n) `面试官:那谢谢你,今天面试就到这里,出门右拐,不送!: C# w- Y! V. a3 W) H
    " \2 Z( e9 U! c
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    $ b$ I9 N: Z2 x' f
    3 \+ A8 [$ [' E, w5 e- v" _) |1 s2 @3 v这次面试问题如下
    , S4 C" Y% c" b" V0 `$ M. x4 D" `% p5 j# U; ?
    @Value的用法
    ( x- U! |6 c9 S" y* J/ p- h3 C
    7 ]3 O) W8 u4 d3 \0 L$ u9 R1 O@Value数据来源
    " Y: K6 \5 c5 K* m5 o' e% m/ G' X; e' [. b3 [  a  T# g. C5 u1 H6 X
    @Value动态刷新的问题, i6 w* j# y$ ~. A2 t' {

    5 b$ n) A/ F  u9 X, K" g下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。$ d! t1 X# o0 h) f0 Y
    ( j8 m/ [" }6 t& L
    @Value的用法1 U: m3 b6 K' ^5 J

    * d/ O' f+ q+ O8 l2 ~9 g系统中需要连接db,连接db有很多配置信息。' T6 q5 u. f3 L4 e; m2 `
    # E! W" V4 D1 I; e$ s4 W* Z$ Y
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。$ z$ }  P; Z% B/ R( w

    $ U1 T  I, N  F6 A( O! c还有其他的一些配置信息。
    8 y1 j: M& U0 v! F
    * x3 w, Y3 V1 J' |我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。& A. F/ i- t8 K0 _
    . j7 v3 ?. E) \- p1 Q
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。2 L% W7 o# f( Q. ?, l% s/ W
    0 t+ @. b# ?$ a6 d" z5 _
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ' d2 R! [3 w; c7 ~4 A
    3 G" v9 a# a0 h& r通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    ) P$ _9 p( E6 Q! z/ i2 i& V7 @5 ]/ u. q0 |8 e
    @Value使用步骤
    % [+ L9 i- w0 |& q; ?2 p. }) I) h- G9 i7 A( h
    步骤一:使用@PropertySource注解引入配置文件
    $ ~; N* V% C+ N. ~4 v
    0 T4 E% m; O# D; N5 _+ K将@PropertySource放在类上面,如下
    4 }5 O/ N% x, n% K; c7 i
    ) A+ q( q+ `) ~2 ]' {@PropertySource({"配置文件路径1","配置文件路径2"...})
    ! h  @* r+ E8 u0 z  p# L@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    ) f, n' N6 |0 M: N; o9 V8 {- j/ Q0 K; G
    如:
    ! k8 Z1 W5 Z6 r6 x& n$ b3 C1 Q
    0 p( c8 g. Y9 d- m@Component
    2 \! q4 u8 \4 ~8 ?4 c; D  \@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})) @1 A* o0 i7 C: N
    public class DbConfig {
    / a- d+ G& }; ?! C}
    0 L: o2 v' |$ W( j步骤二:使用@Value注解引用配置文件的值2 O( Y3 M2 |; C) V

    ! d& U9 T( W! `  s6 t5 C通过@Value引用上面配置文件中的值:
    ' Y; C9 F' y' A; H0 i( K1 |) e8 @3 V3 R
    & O3 @. l" x  d0 F, n+ T* w语法* r3 \! S1 p/ c. M9 n+ F* W. Q

    / B: t5 v2 I& `) [@Value("${配置文件中的key:默认值}")
    ! S- {' e) ^' F4 F+ {: Q@Value("${配置文件中的key}")' w0 O2 f( R1 L* [
    如:
    1 \" ]; m5 b& c# a
    6 O1 F) g3 a! p% ]@Value("${password:123}"). r4 @% q; u5 S2 f1 X' d4 k( d3 `( i
    上面如果password不存在,将123作为值- \) s3 @  X0 B1 {0 {  X) H4 z

    5 \( g: ~+ v) h0 p6 M8 o/ x; M@Value("${password}")# ~, @+ a! D3 i+ W6 S7 r6 c
    上面如果password不存在,值为${password}
    - M1 K; k2 P2 j* S$ b
    ! B* q' r6 T& u6 x假如配置文件如下( d& z6 {3 ]6 i% F1 D

    6 ?- ^9 U1 P6 L& A. ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    0 S4 j% Z6 t! O1 W$ gjdbc.username=javacode4 L* X7 m5 b0 \% U% |8 p
    jdbc.password=javacode
    / h6 r3 O6 u5 n# T% k% a1 x使用方式如下:
    5 R3 i( w$ }  a7 N1 S$ `
    # X( w9 l& t: \@Value("${jdbc.url}")
    6 l. }4 W5 y( z- e/ ?1 W/ pprivate String url;
    $ d7 h$ x2 K/ ?1 q) T
    " j, P- ?* p* ]3 [8 h$ m$ w@Value("${jdbc.username}")9 P  m0 u, p& [# a0 q0 y" V* Z1 |7 k# m8 o
    private String username;
    * l* _! ?, b$ D/ `0 E
    " j: t" M+ ^/ \$ N1 M- a! H. {& M/ B@Value("${jdbc.password}")
    * @. b% _, T) f# I) a" ?5 ^% \3 l' Gprivate String password;
    / D) L% d& M6 ~9 J/ [0 A6 w% o下面来看案例- ?- X8 v  w' U- b. S
    , S/ ]3 S9 \  T! l. A
    案例" ^: v4 P3 g7 ^" I3 E/ P

    ' l0 `4 d6 J- B, I- `: l来个配置文件db.properties. B: N0 ?" N# ?: ?: ]8 a4 |

    ) u, a+ c9 S" u8 ~jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-80 T/ z# Q: w' s
    jdbc.username=javacode, n# S; ^  T$ \7 U
    jdbc.password=javacode9 |& J1 W6 h4 q$ _& R# s4 f
    来个配置类,使用@PropertySource引入上面的配置文件: q. e5 r1 o$ j4 P1 N7 H6 m

    - V" i9 C# g$ [2 I! T. ?# s6 X$ upackage com.javacode2018.lesson002.demo18.test1;. B0 b: T4 Z  d- Z
    4 F4 z$ N1 b  J( z
    import org.springframework.beans.factory.annotation.Configurable;; e* Y! o8 Z" y# s: L7 U
    import org.springframework.context.annotation.ComponentScan;
    ( l  @* s+ t8 }0 A- limport org.springframework.context.annotation.PropertySource;
      L! V/ z+ S. o- g' z$ O5 e  C
    9 N* f$ P" s* {, J5 J@Configurable
    % z# c0 L' k- |4 b8 Z; ~. g@ComponentScan
    4 H$ C! A* J3 b$ `@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})" v: x& l0 W( j8 S, v' f
    public class MainConfig1 {+ |. k& V. p# c
    }" }# c2 z* d5 G3 L9 m* s
    来个类,使用@Value来使用配置文件中的信息
    $ J1 I& J4 V+ p/ K2 ]3 c; ?* Q
    7 n; C3 A! y5 _; }" ?/ W9 zpackage com.javacode2018.lesson002.demo18.test1;0 {  }% j' H$ v+ e9 D' K! u+ |5 Q
    - B% z5 E5 w/ o1 O8 A2 G8 y2 w2 B
    import org.springframework.beans.factory.annotation.Value;) L. y" f! j1 ]) I- I
    import org.springframework.stereotype.Component;
    ) q2 D0 J! A. X, m( u( D
    & a& ~* h3 f) b! ]; w+ f; w4 ]@Component
    . e, c* f% y( m! l1 Upublic class DbConfig {4 m! t" h$ I- u6 o" F3 i% y
    * s7 F0 V+ v$ Q/ ]
        @Value("${jdbc.url}")
    ; B. d, O% N* W1 K$ A) @8 C- N    private String url;1 ^, z1 F3 N- r3 k! i; T5 W* H. `9 h

    2 [7 s7 L: @. T4 c- t- g* I    @Value("${jdbc.username}")
    " ?) f. h9 n& ~9 I1 X  u$ M    private String username;
    # O, w; g$ Y3 L
    , D- D, p; I( _& }9 O* ?    @Value("${jdbc.password}")
    . J; J) y- B% L; L( Q8 ^4 G, y4 W    private String password;
    5 P( T4 t+ |3 K3 C, a& c: S9 r/ _& v+ x  K9 F
        public String getUrl() {
    ' ?) I1 R% T' \. t1 i        return url;' d. a# h$ D8 c8 q
        }
    9 m  m' z4 R, N/ t( |: b& s' ^6 d2 x8 C7 [" M. q7 Z# z
        public void setUrl(String url) {
    1 ^7 I+ o# x( O, h6 z        this.url = url;
      n# x$ w; y/ V6 R    }
    ; t! X+ @) d+ e; V  W0 @! S- G' J
    ) }- L. z2 C& e: {' f" w" Z' m# F( U    public String getUsername() {
    9 q2 `6 L+ }$ |3 H& b7 f! u        return username;
    # I* X/ q' g, ?3 m9 F, G0 B    }
    8 d4 E4 {- L' \& L/ c9 y9 d' J
    * T, e2 n. l) n! E. v; u9 Y3 @    public void setUsername(String username) {' o9 Z* p! t% d! N9 d
            this.username = username;
    : U# U' P, y3 T6 s' l! Z  O    }6 C5 Q+ l7 l0 s% z# d

    * [9 ^1 b1 b7 ]8 |' S  H    public String getPassword() {0 O7 r# A3 i" D% ~. E
            return password;
    0 s1 E; {  w& N" _6 @    }* i/ G4 b# N! c. d' O: f& O

    : r7 O+ P% C! g# @    public void setPassword(String password) {4 A3 p# \/ a0 }7 f
            this.password = password;
    - R9 L+ g. W% H. K* e% I    }2 F# p( I1 s; \; C. ~3 k" D
    % K* f& |, ], R+ _6 p
        @Override
    $ L2 h2 j/ B6 |. k* A* e# l    public String toString() {" }8 _* P. s5 ?6 Z2 G+ x3 ^. W
            return "DbConfig{" +
    3 z$ X. f; I1 @% m                "url='" + url + '\'' +4 h% t/ Q  v: l- {
                    ", username='" + username + '\'' +- H! R# f2 V. m* g# W% J) k
                    ", password='" + password + '\'' +- V4 n0 ^% f* u6 ?3 \' R( r
                    '}';$ N+ v, \3 b7 g0 K! T
        }! B3 z2 o8 {1 P0 g% a5 f- i/ ^) V
    }
    5 H' v5 }6 }* Z# [' t上面重点在于注解@Value注解,注意@Value注解中的/ A1 c, u% c5 G1 z! j

    7 Q% W9 N  N- }$ S0 }  l% c来个测试用例" ?& t$ z# e* q$ q$ R. X7 R

    : n' I  B+ C; |0 F' fpackage com.javacode2018.lesson002.demo18;
    0 M* x$ K4 l5 p9 z# u' U% ]1 w- x
    % P# G3 Y; ^+ h- F: }; ^import com.javacode2018.lesson002.demo18.test1.DbConfig;
    ) @) q: V. i2 J2 ^import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    5 i& H: ?. b+ g: P* u4 |import org.junit.Test;& |0 E, @4 i/ g) e# b
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    % n8 M) I  g( ^9 W1 L& x3 n! g9 G, @. _) {6 g* w# U' u4 r, c# I
    public class ValueTest {: G8 H! q0 N( X5 G$ d
      a0 X  Q* h# ^2 d4 `  X- K: v- a# Q' u
        @Test
    ; R; S2 B8 E- g    public void test1() {
    8 M/ `' z' h- b6 j) G9 N, F% q: L        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();, F' n. L: [7 Z; I
            context.register(MainConfig1.class);& a7 j1 ~& d* G% n" L9 K6 M
            context.refresh();
    $ Q+ u/ f+ b. y7 F0 `$ u
    3 i" g1 u' \; D& V        DbConfig dbConfig = context.getBean(DbConfig.class);
    # E; [7 O5 h+ L& ]* b        System.out.println(dbConfig);
    3 q. ?; r+ p  M& N    }
    & r" V. _; H! v8 D3 T4 |}, |# {: S/ F, [# S4 h
    运行输出
    % e- ]! h- ?) m. g( B& g0 D% g# ?
    # V) ^# J7 V4 w3 P- `+ cDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    4 D4 h3 G. n+ l; \1 S0 f: `4 G上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    # z# T) C! ?5 a0 ]7 S+ r$ N. l% d/ @$ i0 B7 T
    @Value数据来源3 Z/ f( i2 ?# p; \% C8 S' S
    ( R5 F, _  I$ l' J5 S# J
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。+ X- K9 }& J! M4 N+ o- q2 p+ `

    , t9 {' D* R# T# z0 C/ z  o我们需要先了解一下@Value中数据来源于spring的什么地方。
    + M" |* e4 |$ r  i2 Z; L4 J2 Z: U# `0 K( z3 n' Q6 ]
    spring中有个类
    & q2 Z1 U: ?8 j" n/ b
    " }1 \3 U% a: C( ~# o0 Xorg.springframework.core.env.PropertySource6 ?6 ~3 k) ^' Y& U: ?9 s! C* k( I0 }
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息+ P; s/ m1 X! p6 {; h
    : l! t/ }" P% e5 z) t/ S8 r
    内部有个方法:
    1 C" p9 g+ I: H" u, \1 D/ K$ v; }
    0 @( v% q8 q! C* V- ppublic abstract Object getProperty(String name);
    " v% C0 J! w* g4 e通过name获取对应的配置信息。6 k2 ?5 [. {+ g+ E
    9 H5 _, R6 L2 ~7 Y: m$ N* z3 {
    系统有个比较重要的接口9 ?- [% u$ n) n4 E" W! s- ~

    9 v( i, a4 ]6 \- Y- Dorg.springframework.core.env.Environment; [7 J+ ]8 p: d5 l% {2 i- D
    用来表示环境配置信息,这个接口有几个方法比较重要
    , C6 a3 l: b% W1 p* j9 X
    6 w, y) `! U4 d: }3 UString resolvePlaceholders(String text);$ T* ]; x7 q3 w* {  q+ Z, R- }
    MutablePropertySources getPropertySources();
    ; V9 \1 d/ c0 N& m, l1 b# |! [resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。* `  D# ?$ _5 j+ @0 D; n- [
    + m  w' d' d5 ?' q& I; C
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    4 k/ p) R, y' d9 k- b* U
    8 Q& X; Q3 C1 p8 Y3 j5 x( y, ]public class MutablePropertySources implements PropertySources {) a1 U" `! |. H7 T
    : l! j2 L/ g3 G# n# o
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    % h" X* t, V( s0 B) L0 I$ O9 @0 A, T9 y
    }
    ' [+ f1 p$ R- \: p: {0 K3 Y内部包含一个propertySourceList列表。9 P, m9 J) {3 B  U+ I

    % g& r, l2 r' a4 B5 ~, j9 ^spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    % h: E% h5 B; }+ O$ r" m  j. ]% M. p9 h. a7 b* e
    大家可以捋一下,最终解析@Value的过程:
    . U" y+ c: I- d. D! P
    5 v" y8 t; t+ |1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    ' s$ u# s! k5 x: ^, D0 m2. Environment内部会访问MutablePropertySources来解析
    . S$ X) Q6 o6 i7 ], [9 \  v3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    : X' s9 y+ ?  [7 y! _% m通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。; _% H& T+ p( H" L

    % ^1 y- G+ ~: t- L4 g0 ?1 d下面我们就按照这个思路来一个。, K2 E# y$ M! p8 n5 {& }
    4 z7 g) F8 L) V0 |7 k' Y9 |$ L3 z
    来个邮件配置信息类,内部使用@Value注入邮件配置信息! b  n2 O; ?7 r  Y

    7 I! u& e7 r+ \) r5 I5 J9 t' apackage com.javacode2018.lesson002.demo18.test2;8 I# Y# z& e: _) g: }% o9 e% L+ d' _
    # L& o* ~: O4 {8 Y9 n: P
    import org.springframework.beans.factory.annotation.Value;
    : b/ x% Z* G9 ?+ i0 c* a% Zimport org.springframework.stereotype.Component;
    % X4 u) ~6 N( Q
    , G. G3 K+ E% s6 D* c' `/**
    % R0 u1 {- Z4 n * 邮件配置信息
    . ?8 W: k' r- N( u3 h: e */
    3 v& T) H: y( e@Component
    1 v7 F( j, y- D! bpublic class MailConfig {
    1 h) y, W+ J4 q6 t5 S7 Q  ^0 z$ O6 D3 o- N& G" l
        @Value("${mail.host}")
    % t- d2 Z3 C" B) d    private String host;
    1 c. E  @4 A: t# d4 a4 |; P6 m, l% W+ [: k2 K
        @Value("${mail.username}")
    ' P9 J2 y+ ?! I. t! u7 a* x    private String username;
    ! h: X4 f# f. Z) c. i' n4 n9 a0 _- n- p# P; f2 S8 V" j( U
        @Value("${mail.password}")
    . U" }8 n7 V' v5 M9 U8 S! s5 R1 t* N2 P    private String password;
    2 f5 J2 ^: X. \* A! a& ]
      w8 w: i" g8 }3 M& d8 G8 f+ p    public String getHost() {' @+ [$ J: ?7 E6 A; t, P; k
            return host;6 s0 G# G1 M+ E
        }
    - h; ]& b' }+ G* G6 V/ m, N. U# N, L4 l  `) ?
        public void setHost(String host) {
    : E: v8 Y/ B+ C        this.host = host;
    8 K! m, J& F# s+ W- D    }
    9 V. R# v1 K  j+ C
    " o) D4 [& z; @- b' H% n    public String getUsername() {% g/ z$ e! {. v; i  {+ a  H9 m
            return username;( b' N4 u% q5 x1 v4 z9 R* i
        }
    : p6 K% H- p9 w8 [8 _- ?2 a0 T. K3 Q: n1 ~+ m% i0 d
        public void setUsername(String username) {. T8 [; `; I& w
            this.username = username;
    ; w$ ^4 |5 V  k! Z2 b    }
    1 U9 q, C# w6 u% D6 ^. z- e4 j0 ]0 a5 U6 x1 i
        public String getPassword() {
    % U* W7 n% q3 i' G" i6 @        return password;
    , o# z8 s5 C* o% k; Z! }    }
    3 S2 s8 G0 [0 S" f( e  r* A4 J0 [9 ~# E( w
        public void setPassword(String password) {
    ; n! F& K. D7 d0 s        this.password = password;. B0 h  e( p6 `2 b! U6 |% E- n2 l
        }/ j  ~' B  N! k6 [1 t* N

      @0 i& B6 ]# s% [# J    @Override
    , J% ~' w5 k  {* }# i    public String toString() {$ H% m. E+ Y  r8 ?+ u, W% U* h
            return "MailConfig{" +
    , K' O3 b5 d  a7 M                "host='" + host + '\'' +
    4 {7 r; }& q6 p                ", username='" + username + '\'' +
    4 A& @* Z! Q5 @                ", password='" + password + '\'' +8 H% M6 k/ u$ g. W1 Z
                    '}';" f1 Y+ o3 v- }- ~
        }4 {! O1 ^4 _" G! V
    }
    $ J+ u( }# Q, I$ D2 h) q0 }再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中% f! C4 }4 R) F. w) `! |9 D, q

      g& U3 Z$ S- x6 Z: y$ Vpackage com.javacode2018.lesson002.demo18.test2;
    # [7 ?  q6 g: }: Z  m- A9 K
    1 B8 N6 Q/ {# S0 z( kimport java.util.HashMap;
    + U2 F6 M  T  K# ^& e2 L( C6 ~import java.util.Map;
    ; p, U. \6 C, W; M4 S' _# E% x; p1 n
    * k! }& ], ^% h* I& [2 cpublic class DbUtil {' \  x+ X5 D; s: s
        /**5 Q6 i, [; B4 \% k" a- W
         * 模拟从db中获取邮件配置信息/ h& F8 d0 C  k3 B' I4 w. k
         *
    " I. m  U( z, K/ z. I  k2 w( Q, o0 `     * @return0 |; c9 Y6 _% \0 u
         */% ]+ o5 V# K/ W' L
        public static Map<String, Object> getMailInfoFromDb() {
    5 m' H. j0 g# I        Map<String, Object> result = new HashMap<>();. p+ d' B; o, o5 l! K. d3 D+ p
            result.put("mail.host", "smtp.qq.com");
    2 L2 y, ^) D3 i( \        result.put("mail.username", "路人");) W+ B2 J6 D. q/ Z3 P% a
            result.put("mail.password", "123");5 d# v6 U1 k! T& H& g. M
            return result;; k& `8 Z, a1 F2 t& y" s
        }, v2 r8 c" P: F% B* g
    }( M# `. K. }- l7 n2 Z9 ]$ j
    来个spring配置类% a5 y* u) c8 G1 e/ W/ q

    2 X# d) o( _- K1 V; l- |package com.javacode2018.lesson002.demo18.test2;5 ^  ~/ q, `% @8 `. a
    4 h7 C" b: p* y% K
    import org.springframework.context.annotation.ComponentScan;
    " r4 p7 Z% t7 H( w6 bimport org.springframework.context.annotation.Configuration;9 e3 E6 ], @( s' o7 x! H/ F0 Y* i2 M/ X
      {  N* x# \9 |  c9 I' Q" K
    @Configuration
    0 A) y& }/ r* [@ComponentScan
    6 H: q. z  ~$ G; \* b" _public class MainConfig2 {% G) b! P; u0 G9 O
    }
    2 D6 i5 k; A0 g/ S* {$ ?4 a( c' T; e下面是重点代码) i( l; m7 A$ b* Q3 c

    2 u$ m% f3 ~" c+ F/ D: ]@Test: d0 y9 t8 A2 ?* ]" D
    public void test2() {! ?, O, ^; C" w4 }/ g* e
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    - f2 r5 o5 |+ T; g& A1 Z* R% q$ u: I2 B; ]8 ^  o
        /*下面这段是关键 start*/3 X/ {) j7 i$ o" w8 C
        //模拟从db中获取配置信息
    # p3 g" Q; e( c+ N+ Y/ a    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 \- e" Z7 H; q0 R4 y
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). S: S3 `2 J, T7 N0 b$ x$ Z
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    4 A7 J+ q& `( _0 H3 _% s9 x    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    2 x# S% m; v) R* g    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);0 P) ?6 Y  p% |5 r
        /*上面这段是关键 end*/' I1 W5 r6 b/ N& L! F* g
    ) H" l* V+ Y2 b
        context.register(MainConfig2.class);
    ; x4 `. J3 x% Z  `8 v  D# M    context.refresh();' {3 V  v# L% [' v6 i3 c' B3 I) m
        MailConfig mailConfig = context.getBean(MailConfig.class);# ?4 D9 g+ B7 W& |
        System.out.println(mailConfig);
    6 t$ V8 h, J8 T/ z}
    0 x; T* a+ E, `! V- h注释比较详细,就不详细解释了。
    . H. |" d: @# i! J' E/ O4 U. v4 R, d; v; ~
    直接运行,看效果: U' f. b+ @$ E, l5 R4 Z* A8 E

    - A% b" d  W5 b2 b' h* Y' M+ `MailConfig{host='smtp.qq.com', username='路人', password='123'}
    8 E! y' G/ [; g( \7 I有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    . m6 K" A# t2 P# a
    $ f$ J+ c( ]6 u* T上面重点是下面这段代码,大家需要理解- a# n  R) M9 d' |7 O8 }
    9 w- {9 T8 N: @0 L5 j4 t3 x( q, V
    /*下面这段是关键 start*/% L$ K* N  ]/ L% [
    //模拟从db中获取配置信息0 e, T1 e2 O+ \" l& K8 g) c
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    - K2 J/ y: e+ Y* A/ D* l5 h9 z" K6 i" U//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    5 G! n/ z8 P: J9 ZMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( J4 U  L8 W0 M- o" {8 M//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    , A$ c+ {' J& y; z$ C( U) G* ?context.getEnvironment().getPropertySources().addFirst(mailPropertySource);- v+ ]$ H0 z  J; ]9 g& _5 a- a
    /*上面这段是关键 end*/5 @/ f2 d/ g' G% Q
    咱们继续看下一个问题
    6 E, d- U" }# D2 l
    1 O# }$ ^  |! i& K( a如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    . U- l) {6 @7 k. M
    : D& q, S2 r7 O@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。3 _; ]/ @) o  r6 q0 r( _$ p3 l

    : ?# e- I) U0 J/ U( c实现@Value动态刷新, B. `8 ~( Z6 u! l" e. T
    % p) G( \0 Y! z1 T" z! Q
    先了解一个知识点/ L& f- Z1 F  E" z/ r( n

    8 k! L+ n( G: [3 ^- y这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。% k# J3 n5 H) D1 i, |: x

    3 L, K& f( c$ Y这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解3 g6 G# P* W* F% t0 X% k- O& R) \

    % B' A( d/ {# w: Pbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:; }: N! Y% C# n7 M' G
    , F. k- w7 e; W/ I3 Q4 e1 R% o
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    " D2 ^3 |  {' [6 c3 o这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    . a9 n/ ?  H+ K8 Q* l4 y5 S6 t) G! h) A+ S
    public enum ScopedProxyMode {
    7 h1 y) ~+ G) _: |6 J/ d& _6 a    DEFAULT,& g3 Z0 n0 Y. g
        NO,: a  |& H3 O  Y, X5 W
        INTERFACES,
    9 g" v6 P/ b. |; O( m1 J( J' u4 t    TARGET_CLASS;2 w4 V' |# [4 z/ h6 n& u# W
    }- U' }! B& ^% ?% `% a' ?
    前面3个,不讲了,直接讲最后一个值是干什么的。; L) ]+ w9 u6 |" |1 T2 d" g. z
      P. v  ?, i$ ^  }
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    $ C9 Y/ i) n# _; j5 Q, d& \8 f) y0 w  d) q* b$ k- I0 L4 v
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    6 Z) N6 L: ~4 J& a, y2 r/ v/ S/ \" |+ N5 e$ L
    自定义一个bean作用域的注解) R! i3 s7 x4 i/ Q  Q6 u7 c; j# h/ }

    " ~; }5 r* C5 npackage com.javacode2018.lesson002.demo18.test3;
    % e/ X( S; ~9 y7 u7 s# ^* a/ K5 j
    - P  I  ~( S% \import org.springframework.context.annotation.Scope;
    2 f/ f: p. U. Dimport org.springframework.context.annotation.ScopedProxyMode;; ]' e0 T3 V  A, D
    " c& ]& j/ {" ^( W& L7 D7 ~! o
    import java.lang.annotation.*;
    % o: h- t2 |" ]
    + r; J1 x) l" Q. D2 D: {1 k% s  H@Target({ElementType.TYPE, ElementType.METHOD}); D: g* O, q  C2 y) m9 {8 l& Y
    @Retention(RetentionPolicy.RUNTIME)
    ! r$ T# k; R& m" \@Documented! w8 ?/ O' D) ]  Q5 x7 m' t/ k- w
    @Scope(BeanMyScope.SCOPE_MY) //@18 c2 w1 h0 u. j$ ^! {& E  ~6 [: D
    public @interface MyScope {
    . \) A! @% ^+ h4 a! a5 {    /**( N1 u  l! X: S+ v6 w' I4 J5 ?) g; y
         * @see Scope#proxyMode()
    0 }7 c" i- {% p" ]     */1 x2 E5 Q3 d" V
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@22 b% F2 U' u" A6 v" k6 G3 [
    }9 J5 M8 O7 P+ d- m* k% e
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ! |  u  \7 I3 h. C- l: Z" Q0 z# I$ v0 _$ t% G' {" V
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ; e5 V2 H/ ?6 l) f0 z) W
    $ E! _% h6 N- T1 p1 N; j$ Z@MyScope注解对应的Scope实现如下
    9 V0 |1 Y7 D( @7 j, J4 @) C- i) P( Z+ F) a, \8 z' Y1 v
    package com.javacode2018.lesson002.demo18.test3;4 H0 |$ ~1 a/ Z1 B4 ~" \  }/ q

    3 {; X% t* {. ~5 c$ Kimport org.springframework.beans.factory.ObjectFactory;# f( Q, F- l" T0 B
    import org.springframework.beans.factory.config.Scope;
    / L' Q8 B% f( g- R! ^1 G5 c* }import org.springframework.lang.Nullable;' j; J0 i$ _. d% A( M% {

    : s* ~( q; X( i. b7 C( E6 ^" o/**1 A' `6 O8 b" E! L& @6 A
    * @see MyScope 作用域的实现! q5 n5 P; x# W$ j  J* W) g5 @
    */
    ) k% A* s; t& O! p& Q9 r. M, Hpublic class BeanMyScope implements Scope {
    & I2 l, |" h. U& U  }
    * F7 M1 V) @, r' V& o2 Z- M    public static final String SCOPE_MY = "my"; //@1
    & m" J* x+ K. n2 Z" \- ~, x% f8 [9 R8 R" H. }5 V" N* @
        @Override, Y# g+ s4 o" O3 M( E
        public Object get(String name, ObjectFactory<?> objectFactory) {
    ! R' Q7 P# L5 H: n        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    ' ~# M! v; |8 r; b" F        return objectFactory.getObject(); //@3
    0 ]* |- C) ^. J) c$ |    }
    1 `/ E( n. }- g' B# c
    2 k; ]; J9 L' W/ [+ K) S- u# y# I" p    @Nullable& F- h3 p4 S% E  b. v. [) U( M
        @Override- }. l1 ~: N+ \+ F! E, z% y, s
        public Object remove(String name) {9 y1 r9 r* W" V
            return null;9 }  Z0 D) g4 }
        }
    - C9 v: w  R( {+ \1 S) u' u- A
    $ h1 r% f0 ^2 J4 v- H' K& X1 w6 m# Q  q+ Q    @Override
    / m* K, m# T7 T9 ]    public void registerDestructionCallback(String name, Runnable callback) {
    . E6 Q0 W9 U" _  z/ M% W" ~" P
    7 c8 f, d- B7 F2 i2 i8 B4 J, R1 n    }
    5 g$ D! u: }$ ?- K$ w: P  k+ y+ }! |( b- T; ~1 d
        @Nullable
    2 h$ J! }% `' ^, m7 Y$ b* U    @Override. v7 x! s7 K  B$ z' J
        public Object resolveContextualObject(String key) {; J% N: u6 X1 h* L) d2 ]% \
            return null;) {0 F+ B" V/ Z3 V
        }7 m+ r; o; d( `6 C
    ! ~7 f1 p+ M' Z. f
        @Nullable, i( e& E  ?, F2 A' Q
        @Override
    " Z5 C1 B0 w6 O2 @    public String getConversationId() {7 @( U, `5 d& p, d
            return null;
    ' o1 K$ t+ H  L: U8 t: W) Y8 Y    }
    " x- Y; n1 u+ a6 K; Z5 G4 M}7 m* d+ d3 F' f4 s9 S8 \
    @1:定义了一个常量,作为作用域的值
    , l+ T0 T- j- m  r8 F5 p' E5 H
    9 N# P9 T. W1 P; y( X+ K6 A0 L@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果' }% J; k. a9 z( M. {, C
    , d; N# q9 n; L* A3 ^5 M
    @3:通过objectFactory.getObject()获取bean实例返回。1 _4 U" M- b' U+ {* i" s% B
    . z9 d9 V( K; R* a
    下面来创建个类,作用域为上面自定义的作用域
    4 L7 _2 W- k) ?- H; L$ ]2 M( E5 D  E. C1 W% p( I& A/ R/ I* T2 ?
    package com.javacode2018.lesson002.demo18.test3;
    : i/ y1 R$ w  q& ]8 P0 m1 B
    3 p' m8 `" @0 X$ kimport org.springframework.stereotype.Component;, K! f$ n$ [( x9 e
    3 w% H7 S( e) B2 B, Z' B
    import java.util.UUID;
    " I1 R& s7 v- W& C$ B( {! {% q$ b6 t3 P' H, s
    @Component
    # @2 e5 }0 u, r* l( P' K6 s@MyScope //@1 * S( x- Z8 B7 W8 f; L! S3 |; d* `' \
    public class User {' r# T, \5 K/ Z$ I

      P* D6 i% e2 f  F    private String username;) a1 k# U, S' \% v, Y
    0 S# O6 M  G3 M0 [, H# e# h$ E
        public User() { 5 |/ |" L+ v& l" H( n+ c) t: n
            System.out.println("---------创建User对象" + this); //@2$ o" B6 O. |% u2 e
            this.username = UUID.randomUUID().toString(); //@3
    & K( s' Q% g% i) A- R6 T* k0 H4 t    }
    $ t0 R: r. \( H7 _: p5 S5 w1 y& e; g8 o
        public String getUsername() {3 c- h$ g( K2 I3 F* I+ x
            return username;
    8 F/ L  t  r; |: T  v    }# x# a# s* i& Y+ Q
    * y: X7 d+ ?" L: }5 ?$ l% X! l* p
        public void setUsername(String username) {( f' N  v2 T" N& c: i' F8 W
            this.username = username;* \2 Y. {; @3 b% J; q  }' h
        }
    ( F+ c- L1 ]1 x3 `# k- X+ F" c+ K' X" x2 V: J0 K4 n, j
    }+ P' q6 t* R/ H, n' _( t! `
    @1:使用了自定义的作用域@MyScope- c2 E7 q; ~6 T7 w+ d  n
    " }/ k, ~$ [. _8 Z+ E6 N
    @2:构造函数中输出一行日志& U1 F" G" e; F( n5 `0 n

    8 z1 k3 W/ i2 C; Z@3:给username赋值,通过uuid随机生成了一个
    . p; ]4 x) v) d1 ]
    ) Y7 y8 Y% Z0 U$ G" ^来个spring配置类,加载上面@Compontent标注的组件
    ) v, W1 k' i$ P- \, J2 c/ v/ ?2 p9 a, c# d6 j
    package com.javacode2018.lesson002.demo18.test3;8 e1 u9 d4 ^" Q% k: A) ^
    / c0 ~: A$ R( g9 v8 _/ A1 Z
    import org.springframework.context.annotation.ComponentScan;3 x8 i# W, F+ M5 ~! f" y! H
    import org.springframework.context.annotation.Configuration;6 |! H5 }  w- P: U. q
    + M/ {' L, t0 ?, f& i
    @ComponentScan, S. B" R+ x9 p3 `
    @Configuration
    3 P, ?$ n) U2 O$ j. f5 g! |! Npublic class MainConfig3 {, X# r' u1 V6 D! y6 Y8 l
    }
    + S4 }6 W9 h; ~. }/ Q下面重点来了,测试用例9 [6 `9 w! Y" h& [9 H

    5 N& Q: m$ ~0 I1 C" t' Z@Test
    . Y) Z+ d4 {% npublic void test3() throws InterruptedException {4 [) J0 Y( N1 w7 H' o7 ~7 n
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 X2 Y7 _  R0 X! p% ~6 N- n    //将自定义作用域注册到spring容器中
    ! O  S! P% z: j1 L, F" t+ g; g8 d+ Z    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@19 c3 z/ ?9 o7 M1 N: Y1 e5 C
        context.register(MainConfig3.class);  t8 o* \5 x6 \9 S# q. H+ l
        context.refresh();; O4 f, n1 ^$ x4 W8 f
    0 }/ }6 {! |; [; J7 ^' v2 H  Q) U
        System.out.println("从容器中获取User对象");
    ; P% }2 P* J* i8 p6 C$ o5 d# c    User user = context.getBean(User.class); //@2
    + e4 B  T- N2 r1 D; s    System.out.println("user对象的class为:" + user.getClass()); //@3
    ! {2 L' R8 \1 \( z6 S2 @) m0 a; X
    ! |; g. S, O5 Z! A; m7 {* f6 }% ~: g    System.out.println("多次调用user的getUsername感受一下效果\n");
    / w# L7 P- C0 @3 G    for (int i = 1; i <= 3; i++) {
    7 w" q$ a1 c- W7 ?5 B        System.out.println(String.format("********\n第%d次开始调用getUsername", i));, E% ?! O0 a- d- a
            System.out.println(user.getUsername());
    3 u) I) _9 G$ A4 I, V        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    % Q1 h- L3 D9 F( P3 ~    }3 p: Z  Y5 H7 ^. b$ B0 c
    }2 |' L  \+ U4 e# ]4 w! D. y& R4 J
    @1:将自定义作用域注册到spring容器中- Z) U+ `0 C! n
    ' s. ?7 I8 k  b) `  E
    @2:从容器中获取User对应的bean
    9 R  d* ^1 r- ?# {7 F' I1 f% Y- a  l# A' ?6 o. D/ u
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    5 w+ l' [, A0 ^! t: o
    5 j4 t& j; w3 @, I: N代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。# K6 }: G' r% s2 w. `) Q

    " L& Q  H* q' f$ X. s' e见证奇迹的时候到了,运行输出" |0 k# `& L! ~. p$ N; f9 M
    % g2 u/ q( _  s, C1 A5 F( D
    从容器中获取User对象
    ! }+ O' a9 J+ k0 F, z8 Y7 Vuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331279 l+ e2 ?* _5 F! c0 c. Y
    多次调用user的getUsername感受一下效果: ?  a/ w% s, N& d( R
    % }( A) A  g% S9 |
    ********
    ) p+ a9 e0 G: ]- m4 p3 W) y6 m第1次开始调用getUsername  [7 r4 N# @) K* a! |
    BeanMyScope >>>>>>>>> get:scopedTarget.user; _& g7 D) S2 D; N5 e& d. X
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    $ G  x2 e2 S3 U  P5 T  U# b7b41aa80-7569-4072-9d40-ec9bfb92f4386 F* e) K2 `/ K& A
    第1次调用getUsername结束
    0 N, x% Z0 p  P********! t4 K8 v1 j: `1 E0 L' l8 X2 l$ H

    ( V, C3 S7 \; o********3 y! t. K1 N  K: Q) J3 e1 Z! J
    第2次开始调用getUsername
    ( h! Z, S+ q# F. g! a8 O+ s, @BeanMyScope >>>>>>>>> get:scopedTarget.user4 l8 j% Y, S# [
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b) w  y- Q) i9 T$ W% t& r
    01d67154-95f6-44bb-93ab-05a34abdf51f# a  Z2 l3 j& R0 `" H. ]$ A+ A
    第2次调用getUsername结束
    0 o' i& o- U3 R3 s/ m********
    . ?. V- C; B; c# d4 K: A' f' M, u5 h* w
    ********
    * A! L' R1 m" t/ B第3次开始调用getUsername
    - B' U/ B7 |) o6 F& s6 jBeanMyScope >>>>>>>>> get:scopedTarget.user- J% C. M3 m, t6 P  H
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    5 {) ^  h# v& ]$ r+ k8 Q  y- Z76d0e86f-8331-4303-aac7-4acce0b258b8
    8 @/ H+ [9 \5 Z/ o- d+ a! \第3次调用getUsername结束
    / E& M7 s5 I" w9 g4 j0 p********
    ; g8 _4 H4 s& W从输出的前2行可以看出:9 t' m/ P% x( E9 J% L! f$ R
    ; f+ E' S3 h4 _2 M2 T3 r
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    % q& G) \3 b8 }4 ?5 r, m/ \# x/ b6 H, l8 O) `7 ~9 ~
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    8 k4 {: x! R4 j" M% d3 l2 Q3 l2 ]( H: o( k
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    $ S  o+ X- F( \+ M: [3 d3 H$ s* f$ c2 v. _* F5 B% v: I
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。+ y0 x: {3 {) d/ _6 E! o( M! V
    0 U" b# h, g6 Y2 o3 P4 B! u& r
    动态刷新@Value具体实现; A- s1 A( f  A6 w

    ' U, I! P. O% i) m那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。4 b4 W) v9 f2 ~9 a) W+ n7 W

    & l) E& N* G( g3 q8 k: u% c! x# P先来自定义一个Scope:RefreshScope
    - Q, g* p+ w8 z- v- n/ i3 X) [/ e& i+ u( g8 [0 M% {
    package com.javacode2018.lesson002.demo18.test4;
    6 W2 F& N, N0 f$ O& d* o$ ^( [% ^# T7 z1 `
    import org.springframework.context.annotation.Scope;$ C) @0 P. ~1 `+ W# c" c
    import org.springframework.context.annotation.ScopedProxyMode;( O; J$ Z& h4 Z6 w% e4 V5 [8 ^( C
    1 |6 d# W" a5 l* T1 [  g7 H. x1 D
    import java.lang.annotation.*;
    ' C: t9 C& j6 F% U) t
    ' l9 X6 O% H$ ~9 L# A4 i+ _@Target({ElementType.TYPE, ElementType.METHOD})
    # ?  U! r5 H; q4 B* B@Retention(RetentionPolicy.RUNTIME)) {9 S( q- w8 e' m5 J) T
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    4 W( a! R, @! J* }: O$ N/ U% X$ p@Documented) `2 i5 C/ v" {5 ^- }* I. ^
    public @interface RefreshScope {$ e7 g/ h# m' p9 x9 Q$ H3 D
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1+ E* C4 [9 `  h3 n' b
    }
    4 A' [# t2 l  ^要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    2 ~  Y9 O! E$ }" x' o8 E
    6 X4 l  P4 R* r+ @2 C' ~@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    - Q) i4 k; }) C7 X0 w# ]& \
    ( S( f% w  v- Q  f- i$ M+ v& B这个自定义Scope对应的解析类$ G# A) z( r0 \& s& ^) a3 o

    , E# Y  a) L% B' B* I  `% L! h下面类中有几个无关的方法去掉了,可以忽略0 M* L+ Z$ b# K7 k; G

    : o2 P  g7 }! x3 K/ A8 ?  lpackage com.javacode2018.lesson002.demo18.test4;
    ! K# v* ?) _& x5 L, ~/ v' l7 L( V: T+ V8 }% K
    ; h: T  u3 a$ }6 v
    import org.springframework.beans.factory.ObjectFactory;
    * @# Y( ^$ c7 w& c# Y1 ]/ U+ oimport org.springframework.beans.factory.config.Scope;3 O8 M. R  B2 |9 M7 f6 ?: E+ S
    import org.springframework.lang.Nullable;
    5 t7 W1 Y: f6 s8 l9 g& Z3 F
    / g- v) s' v& K, f" Simport java.util.concurrent.ConcurrentHashMap;' }+ l6 [3 w8 Z: v* K
    5 y( M- Q% H5 Q$ V7 k& w: k# _
    public class BeanRefreshScope implements Scope {
    ( D2 ^4 Z8 d4 n7 m: c; K6 x: x' N0 b/ b3 h! X/ R0 N9 }% }
        public static final String SCOPE_REFRESH = "refresh";
    " u; Y; V8 v1 i+ ]$ V- h# [5 _
    * e, i% Y% Q- X: a; S2 N3 p    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    , O  `) r3 E( |- c0 N9 X( R6 o1 C4 B7 E. N: s# ?! I% V
        //来个map用来缓存bean
      B- _! M6 }/ w; I" @% l    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1$ S8 y( n- k: a' o

    9 e3 B4 o! h( q/ v    private BeanRefreshScope() {
    0 d1 ~4 Y9 Z+ h; N& X* s. R; f$ W! r    }
    8 b# t- P% ]7 A" H4 V
    4 F/ P/ @3 b. v6 }8 D; X    public static BeanRefreshScope getInstance() {
    . A$ m- F, H8 H1 L3 P8 U7 |        return INSTANCE;$ t  v8 k2 j! h) h% Q$ R# `
        }
    0 W: z- }& M$ O$ ]2 a3 o. V& w% S/ K% P% W
        /**8 }% H; v$ L8 }0 W5 X8 C3 e
         * 清理当前
    # K, ?& C& [+ P     */
    + U% B  ?7 Z+ }, f- u    public static void clean() {4 [# }* v6 k) N2 r2 H
            INSTANCE.beanMap.clear();
    5 n! G  Y5 L0 d' o% L# i5 D5 M    }5 Y( p+ l8 d9 a# }" X
    ' b0 h0 S' u# i: m& l0 ]
        @Override. `/ h, q/ _2 |
        public Object get(String name, ObjectFactory<?> objectFactory) {7 d  T( B/ O2 p. z/ ]
            Object bean = beanMap.get(name);6 f5 i$ [* X; z' n) B
            if (bean == null) {
      g0 I" I+ j  W  J- ^: x            bean = objectFactory.getObject();) n; g" d% |8 b8 W4 j5 q
                beanMap.put(name, bean);3 x5 N- I0 N$ ~4 G6 J0 T$ ~9 C3 z
            }
    ( U5 ^% B$ `4 q# h- t' Y        return bean;! \4 R; ]! R' R# e7 o
        }, A1 I6 d3 Y( N! j

    * `2 C. l- K/ y* Q: l/ n/ C" w}; ?  J: V9 G; v! y
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中* w' Y  x! Q) E1 [

    ) N+ d9 ~3 |3 U3 f5 C上面的clean方法用来清理beanMap中当前已缓存的所有bean% W3 y4 |$ p6 V8 ~0 x# B
    / j1 X7 j- v; z
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    5 B0 ~4 U1 O. c
    : f- j: V$ O7 _& o' G' j3 |package com.javacode2018.lesson002.demo18.test4;! }; Q4 Z2 E# t: z2 V! o

    8 s: ?- d) ^) O, G$ ^' O8 iimport org.springframework.beans.factory.annotation.Value;
    8 F% u% Y6 d+ W! b- P, simport org.springframework.stereotype.Component;
    " |! h7 m; {  ]% L2 b( L, q5 R% L; J* b( p& W
    /**2 a  I: }& ?$ R9 P
    * 邮件配置信息
    ) s) t# F, C/ r/ e2 X- m *// @: a7 p/ d: m1 o6 B9 T0 }- Z
    @Component& ^5 y* q! A$ D
    @RefreshScope //@1
    , U0 n: P: X  Lpublic class MailConfig {% O. l6 l0 d9 X9 _1 f% Z

    0 A5 V2 E! ]; y( V    @Value("${mail.username}") //@2
    1 Z: u, [5 `* \    private String username;! R3 Y0 R( Q/ H% V  P6 N, M% Y  N) i
    % N2 Y1 J$ |! q" {: D! ?( g3 H
        public String getUsername() {% C, q4 I, [' K* {. \$ H+ d- ?" j
            return username;! k$ {: q- ^( Y$ t7 l/ ~  b/ k
        }7 n. C+ G! D$ J8 ^  ?

    7 d  Q; {( Y$ E: N# Q    public void setUsername(String username) {9 X7 _; b# W! e, y: K* A) h
            this.username = username;! |8 l4 u3 y) S2 E# j
        }
    2 ?- I; _) w+ @. Q+ F4 E6 Q" F4 }$ ?% m
        @Override) b( \* Y* H! i' J1 ?7 C
        public String toString() {9 O  l4 Q, k) X! r9 V1 _; O
            return "MailConfig{" +
    ; q& d& P+ |  P, y+ b                "username='" + username + '\'' +
    9 O6 r( z! Z7 j9 S& |0 \                '}';
    5 j: t! q8 f" u0 _8 o    }
    , j" R3 d/ y$ c2 @7 t}
    " L+ u9 s) o  B1 t. }% s@1:使用了自定义的作用域@RefreshScope
    2 Z2 @1 I$ r4 ?5 ^. \/ }
    # {( h6 o) D9 C+ y' K@2:通过@Value注入mail.username对一个的值
    - z" S/ t( }* S. [$ }4 T' L
    , G; i! g0 z7 k* F$ I5 g# N( h重写了toString方法,一会测试时候可以看效果。
    - ?+ ~4 ~& T0 |1 e- @0 c4 d. \* S% t. X5 I! o. w- w; n
    再来个普通的bean,内部会注入MailConfig7 K; d* b# i( M2 [+ w
    & J  O- `  b0 Q3 ]
    package com.javacode2018.lesson002.demo18.test4;
    % ]6 T) H. d' L% r& v& X: a8 n! w' ?7 w" K
    import org.springframework.beans.factory.annotation.Autowired;
    2 P" E5 A8 h: I" E- Rimport org.springframework.stereotype.Component;, a( t, ]/ ]+ C! R9 [
    9 x  M) m3 w) c% o. ~2 t9 E2 W  a7 W
    @Component( k: @& }* Z2 A- ^& X
    public class MailService {
    & Q7 B2 q$ C& F; G  |    @Autowired- Y4 M6 I6 x% i( B- a+ S' Y
        private MailConfig mailConfig;1 Q, y3 Q) S; }* p# z1 }; U" F* M' i

    1 \( g6 \( ?" ?- U% K0 `& s    @Override
    8 H; l- K- P5 v) T    public String toString() {2 @, T7 q9 h% j* k+ x5 V, ~8 l
            return "MailService{" +6 J5 y: G2 |0 w/ ~: @5 K& x2 O
                    "mailConfig=" + mailConfig +
    # E6 b# a+ A' u( G0 y% @                '}';$ Y7 }0 z8 Y# {0 p7 I! b1 ^1 r4 f$ l
        }
    # x$ @" j) @, h/ o* h  G8 \: U8 f! @}
    3 ^1 G9 d  d* ~- R代码比较简单,重写了toString方法,一会测试时候可以看效果。
    ( a& S# Y# W1 T  L
    8 E6 E4 W( E; Y来个类,用来从db中获取邮件配置信息" A* S$ n4 C. w9 H9 f, ~0 v

    1 w3 G$ l9 e) C8 H8 {. Z, c6 [package com.javacode2018.lesson002.demo18.test4;
    6 M' |& \3 A" X1 ^1 u+ v
    ) y9 S, B, t0 W2 ^$ M8 b4 f8 m0 timport java.util.HashMap;
    % E6 |8 S& f) O+ G- a* N3 Iimport java.util.Map;# R2 Q$ ]/ D9 I) e; O5 Y$ \1 C4 h
    import java.util.UUID;
    ' Z7 p0 K! O, s" q- [! o6 {* L, g( f# o) Y9 P0 P1 N9 ]
    public class DbUtil {9 T: i, @& m% Z% \. n, z
        /**
    8 @1 B. L0 u& r6 P" B8 j     * 模拟从db中获取邮件配置信息- |, ^5 S* Y1 p* ?  E# B
         *
    1 @& X8 c6 e3 I% ~     * @return
    3 j1 L' u3 \5 b; f% J2 p     */4 X  w: z/ L; B; t  d9 j
        public static Map<String, Object> getMailInfoFromDb() {2 j2 |1 F1 l8 [& w( {& ~/ F
            Map<String, Object> result = new HashMap<>();1 k# p. b* a% k9 {- u% U
            result.put("mail.username", UUID.randomUUID().toString());* |' C; I4 O8 k$ u" D
            return result;! u: A6 K6 \6 J" x! u7 F7 V; p
        }
    9 S! w! f' }. b. y# F6 T2 a3 L}
    ! s  }) o, m  y$ T1 B! v来个spring配置类,扫描加载上面的组件
    0 f$ F) M: N. I: j' u/ s$ G" E" i' g2 h5 O! a- u# G
    package com.javacode2018.lesson002.demo18.test4;
    5 a( j) I0 ?2 e' v% K0 B& B  i5 l4 N. k: D4 V
    import org.springframework.context.annotation.ComponentScan;
    # ^: V( b& y, c# y& G. ^import org.springframework.context.annotation.Configuration;
    0 o) n; K- T* u, [& m3 S" b
    " Y, m8 {+ T* l7 o@Configuration
    6 D; j" ?: ?7 M' r. W@ComponentScan
    + P& s* {3 M* }( x  Kpublic class MainConfig4 {! |$ R1 `0 O/ {0 p& i! Q+ _# V
    }" y5 E* c9 g  v( l& w7 P' X
    来个工具类
    ; }2 I( r4 v" q  G" ~8 U8 I' c0 F+ J6 [9 I. U( l0 o
    内部有2个方法,如下:! \) u. G; D' f4 J* b
    + i, V' h# X5 p
    package com.javacode2018.lesson002.demo18.test4;
    . S, [4 l( C2 F; v: \
    4 M& o0 l5 F) V9 Nimport org.springframework.context.support.AbstractApplicationContext;
    4 j' Y% F0 N; ]0 Qimport org.springframework.core.env.MapPropertySource;
    % r( a9 [9 Y4 G
    7 v/ @" Y5 H. }* g0 G/ R/ Mimport java.util.Map;
    0 r* d: K7 b/ Q3 y! S  n9 H* b+ G, E$ p( A" z& D
    public class RefreshConfigUtil {, r- j* g$ [+ z8 i8 J" O+ P
        /**
    ( G# m# ~1 ]2 N     * 模拟改变数据库中都配置信息( n# L/ a4 a. i/ ]' k
         */: V( \' t& C+ k7 [7 @" j( q
        public static void updateDbConfig(AbstractApplicationContext context) {
    ; v" \& q: O1 c3 H  G        //更新context中的mailPropertySource配置信息1 C6 U7 K. x: Z' m- G7 z/ h- y
            refreshMailPropertySource(context);
    - E. o+ ~, L5 q8 U; L$ X9 x6 G! j3 n
            //清空BeanRefreshScope中所有bean的缓存
    5 h9 F0 c1 n& K        BeanRefreshScope.getInstance().clean();
    - e. D0 [3 ^# C* X' s. I! |/ i    }
    & `$ S  J5 I/ a( Z
    1 p$ l, H5 ?) v0 V    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    * m  C' s; a6 \        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    # Q( N% W% x) ?        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& l# P) d- N5 @' y6 I
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);; P7 S3 ?5 c3 _& U
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    : K+ V- p& m% X8 d$ ?& n5 a9 z    }
    # y1 O2 c7 {2 u  t. n% A3 G
      f1 V' N& h5 g$ j}
    4 b! R% Y% K" ~. a- GupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    2 ]6 L7 O. o0 u6 W0 [3 r- l
    7 I" I/ b, @3 w9 V/ H" @. ]1 P5 a+ oBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    8 Z+ D; p, Z8 C: j8 V# N0 n/ y
    & {2 |8 }) _; a4 I% u  U1 d来个测试用例6 b, Y+ |7 G3 Q

    6 D. q- i2 H- U" [@Test
    $ ?; p' L9 C5 i1 hpublic void test4() throws InterruptedException {+ P, ]( ]" u2 M
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    3 `/ j, g$ d1 V( \    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    # [5 c/ v$ W0 H5 Z$ M( Y+ B    context.register(MainConfig4.class);( E# G& _3 d4 K+ s
        //刷新mail的配置到Environment4 P. F( j  j7 m+ U5 v- r/ v5 g
        RefreshConfigUtil.refreshMailPropertySource(context);
    ( R9 _( B0 {8 c9 ?" p    context.refresh();
    / x9 Z  [4 ?% Y  v# ]
    1 x! Y* W) O& b7 D, k    MailService mailService = context.getBean(MailService.class);. ?, y" _; e3 Y+ S9 Y0 L: t5 p# a
        System.out.println("配置未更新的情况下,输出3次");
    0 f& p' G0 V1 v    for (int i = 0; i < 3; i++) { //@1
    ' C; V: n$ t* c2 K7 f3 F/ z        System.out.println(mailService);/ W* p# Q& b: e: A" `# l8 F; g
            TimeUnit.MILLISECONDS.sleep(200);
    7 c0 v! U. t4 ]    }
    8 T& g+ C- x+ r4 U  h
    1 |4 q2 \1 `+ K" }# o& E$ c    System.out.println("模拟3次更新配置效果");
    ) h! L( h7 P2 l% b; U, `! d, k0 A: _    for (int i = 0; i < 3; i++) { //@2! F5 M3 X9 Y. X# d6 o
            RefreshConfigUtil.updateDbConfig(context); //@38 x6 k) y, b, r9 d
            System.out.println(mailService);  B; O- u3 E2 W# ~6 y, r1 i
            TimeUnit.MILLISECONDS.sleep(200);5 P0 h5 c- G: G3 ~/ F* B3 A2 a* _; g2 Q& t
        }
    $ L3 g5 g8 B- u}
    . h5 D: c; X! h) K) _@1:循环3次,输出mailService的信息
    6 i( i) t6 N: `0 R/ D+ g) Q1 w1 v$ X
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息9 Z2 s0 e+ a% T( o2 f

    * m" `7 v. i7 u. A/ I& j见证奇迹的时刻,来看效果" M6 e7 B+ @. q) c

    " V2 u  I" x( }) ?# o7 c0 l配置未更新的情况下,输出3次
    3 U2 b4 B. K2 l# P* g* _% tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}' d1 }! r  w( B+ T3 o
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}4 l' }. K+ `; _  q, g, b
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    . c- @+ d) r$ ~+ v2 |6 M( m+ H模拟3次更新配置效果
    4 A  J7 G) t& P/ JMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    4 Q' K3 ?3 r* \2 |/ M) b! UMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}8 @0 R& ?$ o; M# H' X
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}1 s, K( ]0 |+ C
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。4 Q' F4 k/ d9 V" i  L
    , {, V3 T+ z- Q$ o2 {; D  O
    小结
    . Y% a3 N* I2 R7 T2 ~+ K' w
    ! f0 I7 }- T: |动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。- J" r2 \! V$ Q7 ~# `: a

    - {# V3 m3 @3 d( G/ ?2 y& x% T( X有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    ) s9 {6 O0 e! |3 |  f5 \1 J* d6 }/ q) B( J& P7 U0 G" c
    总结
    $ K: ]% u# F. N* }2 [4 z4 w$ Y4 T1 W  X5 a& n- w
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!' q  F- |. F* T$ W4 C
    8 Q3 z) I2 k) G" l' v% h
    案例源码
    / n) O- c% |/ r7 ~# g
    / k- [- N: r) N8 z2 x9 e; qhttps://gitee.com/javacode2018/spring-series
    ) q6 F7 ~5 R- V! G; }+ L路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    7 c% G1 i6 ^1 _4 h' P* D. h& E* q) j1 f- B" _/ W
    Spring系列: H. Q  I+ A9 e& u# M1 v8 g" z2 Z
    2 g0 G8 L% F4 e, Y$ A" a" w/ [9 f; s
    Spring系列第1篇:为何要学spring?
    2 w0 ^# N) i. t: D' K( h/ E# w6 z; n# P  n9 r
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    ) g" h3 p' S9 j* ^% R$ ~8 S
    9 L  r$ [9 ~9 A; w' QSpring系列第3篇:Spring容器基本使用及原理
    % u% Q- h1 m' A: i3 ~$ R
    ! v% H# r% m1 N# T2 p& P$ _; w5 eSpring系列第4篇:xml中bean定义详解(-)& Q* D5 V8 q; }6 X' e

    : {1 X" j% A6 l3 E1 ^) \Spring系列第5篇:创建bean实例这些方式你们都知道?
    ; q  K9 x: o9 n5 |2 C- m
    * A' e4 s. s9 J1 h+ PSpring系列第6篇:玩转bean scope,避免跳坑里!
    " A7 r: c8 G7 {' B6 H% o: Y6 x% z; ]
    Spring系列第7篇:依赖注入之手动注入
    # ~! I0 p+ G1 L5 _1 w# ?" y; H2 o( X0 ^5 S) U- D( j0 d' }" U
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持/ f# T! j" y7 y& d9 }( D  l5 L3 h: Y

    8 N* J# @9 L" O  ?6 h: Y3 A/ OSpring系列第9篇:depend-on到底是干什么的?
      \7 d. @& T2 a9 ^  L
    ; p6 q: j2 D; m1 F" |) f( s! OSpring系列第10篇:primary可以解决什么问题?$ y/ I+ w' e2 H+ T7 ?
    $ w5 }5 k7 t$ d  _
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?) L4 ^8 ?- `2 G5 |
    5 }7 ~6 s5 I+ z7 ?
    Spring系列第12篇:lazy-init:bean延迟初始化" h3 o, O/ G- i8 D  P
    " r9 L6 y; w9 ~7 P9 _
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)7 I1 l/ z# {3 B5 `# X* h

      \- x+ J7 b* z' t" s. f! SSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    ' K( K# X5 \: ]; j8 h& C' H5 M5 m" F/ c/ ^: W" c  s
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    $ x5 h6 i' w8 \3 \; o
    2 Z/ {, `/ d: s& p* F) aSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ) r, |# S/ l9 P8 O7 z
    7 R7 D* I4 M6 b6 U* @6 S5 z2 ?1 JSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    3 u0 ^% |* \9 w
    : Y4 K8 F& x& d" X  K% O* `Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    3 H0 Z7 {% f- X- E6 l2 U% {5 s
    % w9 L. [$ ?3 Q) ySpring系列第18篇:@import详解(bean批量注册)+ E) E/ h1 g) _+ X3 g/ ~% {9 s

    6 _  U  U1 m' n- j3 ^$ M0 B4 CSpring系列第20篇:@Conditional通过条件来控制bean的注册
    $ q1 _8 j# `9 O# Z9 _2 j( t5 i: C8 p8 M
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier). [  C* s$ O  Q3 M& d
    0 l& l- h; A' G; r* _" D  h1 W
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解# m& D: R, ], R: X1 g5 g  c$ c5 G
    ; x( w2 c' O: ]
    Spring系列第23篇:Bean生命周期详解
    ) o$ e( Y3 r& V5 ~! i& T
    : _: M6 ]) @( q+ K0 `" F- T6 ySpring系列第24篇:父子容器详解+ S8 M$ n4 w4 n9 v  m. G2 Z3 }
    0 n8 b% m+ B+ H; y6 ^4 M
    更多好文章! r- Z. u5 t& H* I. Z% P$ H
    % e1 l3 K0 e( {/ t
    Java高并发系列(共34篇)2 G0 {( x# e  Z
    : D1 ~3 e# N; [, |" Q
    MySql高手系列(共27篇)4 s* P: g1 R2 ^) o$ N
    / r' @% G8 k, X. }
    Maven高手系列(共10篇)$ }, P& b" d" i- U) D+ T* `
    % c) A4 }$ L% g( H$ A7 v
    Mybatis系列(共12篇)
    7 |% F7 P2 W- j9 E$ a& X; Z
    3 ~. ]7 b1 j4 k  u聊聊db和缓存一致性常见的实现方式( e" k( `) A5 S6 {

    ; h& ]  z: s( K" L接口幂等性这么重要,它是什么?怎么实现?
    ' t; ]5 ?- \  Y9 A3 T
    ) I2 h) b$ C6 v, ?5 q泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!- y3 Z7 J' G! S& }6 j
    ————————————————
    $ L) o4 Z0 g8 V" @版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    1 a; C+ |1 r& Y4 G" K原文链接:https://blog.csdn.net/likun557/article/details/105648757
    ( c. G' K) G* p5 T  e# c6 J% e' n
    2 j# s+ {5 d: `' x4 y0 h6 b8 g, A+ i, J
    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, 2026-6-15 22:34 , Processed in 0.437336 second(s), 51 queries .

    回顶部