QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5623|回复: 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!1 o1 s3 S7 u6 C% N) _( R
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    7 C  ^' K6 K; n9 B: ~
    ( z! r9 z1 H: w: R  [9 r面试官:Spring中的@Value用过么,介绍一下  j' f  b$ Z' B
    ! N% ~  q. {' E' B1 g
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    * u% e2 E& {4 ?' W; |3 s
    2 V  S* s1 }1 p$ N% Q: l2 ]3 S- S面试官:那就是说@Value的数据来源于配置文件了?
    " N6 {" u* J' n2 C
    ) ^/ p4 g! r5 p3 m5 l; A: B4 u我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    # o; |- v- ?% Z; ~0 {& j; \+ \: l. ~2 G
    面试官:@Value数据来源还有其他方式么?) \: S4 [& m; s2 c
    5 }. q' Z2 T! P+ C# q% _* n9 n
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。" u7 L. E/ }# w

    ( ]' ?5 ^* U  s( K6 p3 ^; F面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?6 Q! A, \& f0 e3 l+ ?$ }
    / `% O% H! [- V9 Q( \+ s5 }
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    ! |3 [( G: o: k, ^6 U. t8 o7 S1 L8 z& l. c+ H
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
      P+ z+ L, R5 ?. k0 F: E: c4 p: v+ o8 ]' C6 \- D
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能% O% E0 q9 c8 J4 p7 d6 j3 b1 C
    & _! u' s1 ?8 H5 d/ W, c. ^
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    9 U! a5 {0 b( s* o
    8 V4 a0 G9 m& I+ R+ m) O我:嗯。。。这个之前看过一点,不过没有看懂
    ( d* R# R+ ^9 R" l
    5 K$ T6 h- S  R. v' Q2 j面试官:没关系,你可以回去了再研究一下;你期望工资多少?* R7 u- ]. \/ d8 ^
    6 I; c+ b0 U9 S, s) Z2 j
    我:3万吧
    . f. f1 o1 h2 ]4 f1 D) H
    2 D. p9 z. R7 Q, V. C面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?! W  Y3 D4 b, d2 c
    & u& D3 \4 O0 a
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万7 W7 p( A! B4 L

      H2 G7 G0 {5 r+ b7 Z面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    . n- }6 L4 P- H6 U5 j9 o! b8 Y; f+ x8 j8 g' w
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。  k. Z; d6 q: M4 v1 C$ j6 I
    5 ~+ r  g4 C1 H  @$ Y
    这次面试问题如下5 e/ F! o- J3 ?! u- \
    . Q6 g, @* }9 O7 L" o1 d8 e1 `
    @Value的用法! U" A9 C2 `/ f3 X

    / t" |# Q% A* ]! V' u: L; \@Value数据来源
    9 k, \! q  p8 ]5 p8 M" y! |4 A0 U" Y5 Y0 A
    @Value动态刷新的问题
    / p* K1 t8 I6 w) ~/ r7 |, k3 w9 r) \9 j1 A$ V
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    5 K$ \+ K2 X! V9 f3 h& \; ?8 l" h7 S1 Q7 ~; x
    @Value的用法
    , r9 [0 {" g% z, u- }8 w" h7 u# ]6 E: V% H. A
    系统中需要连接db,连接db有很多配置信息。
    0 Q( l; i6 h1 `9 \+ `$ J
    * u  a/ G1 P6 A4 G: N系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。* G. v  f& d& c: L7 x

    7 A3 M4 y) [$ d% C还有其他的一些配置信息。8 V2 Y0 x) y& R7 U

    - b. B' ~1 Z- d- \/ ~我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    - L+ S  t- k' a1 O8 E- [
    1 ~. \& a4 Y6 i+ e3 y( R那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    - A3 D9 s7 r3 J7 a' [
    # M/ ?( J$ _& @+ r' f通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ) I7 p0 P4 ]  l- L. s6 z
    : p* v" F% ]5 m+ }4 d: m通过@Value("${配置文件中的key}")来引用指定的key对应的value。. f$ e0 [* \# Y5 V6 L" n. }

    . g6 L: [+ k( t$ H/ x7 \@Value使用步骤6 v3 G% e! w# @/ @6 J! j

    / }4 r, L4 K+ B; v2 m* J0 m步骤一:使用@PropertySource注解引入配置文件
    # N$ X9 `: p& I! e% n- r& J: g- Y; x* B- E2 {; n. |! s
    将@PropertySource放在类上面,如下2 Y" Z" U# H0 y2 L' _" S  e+ o  m
    / ~; W) Z5 u5 _$ t3 M' f% T  w
    @PropertySource({"配置文件路径1","配置文件路径2"...})5 k- S, X- R: o4 `; C$ w5 V" C, ]
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。& t( P4 ~: U* |: L7 L# i
    # V& {) l/ Y, z. U: B% J( ~
    如:$ i. J7 }" j* e5 |, V

    ; Q- a$ n# |2 {( @3 r+ |4 ^% A6 `@Component
    - J9 `# b$ L3 |) ]@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & ~) B5 y+ ?/ P' B& e, ^public class DbConfig {
    $ k7 @! v' _" ]4 `}8 V6 y8 x, c* p( Q- h) S0 q; M
    步骤二:使用@Value注解引用配置文件的值* H& ~: C/ j( q) S. }

    ! e3 ~4 X- ]3 ?9 c. ]: b通过@Value引用上面配置文件中的值:
    2 Z; A2 _* p. k6 Q8 i
    7 h0 a6 J+ @' f0 l语法
    - Q+ G% V: p5 ~. v- e: R0 }- E. A6 k4 ^) K8 w  ^4 X" f5 Y/ _
    @Value("${配置文件中的key:默认值}"); p! R: ^" ?4 p: L6 E1 I
    @Value("${配置文件中的key}")/ k/ L) v3 F4 V6 A
    如:
    - H) V- @4 E! ~/ U& u. \0 {
    " B1 H3 a; |: m4 r  k@Value("${password:123}"): |5 r5 N* Z; F
    上面如果password不存在,将123作为值' Z5 }7 ~- T  K3 }

    2 A5 @. c; x4 F3 p5 G@Value("${password}")
    * w2 I3 {) [' U) |1 D% N" W  A上面如果password不存在,值为${password}
    ) g. K2 R( w2 @. [5 I. d" {8 D' O
    假如配置文件如下& d0 F( s2 p! C8 W
    9 @( h+ F& F- c: q1 ?) k& y
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8: n5 W1 F: N. x1 O  p6 O2 P8 |( H
    jdbc.username=javacode$ {: R( Q! ~$ T) r$ [0 ]" Q, u
    jdbc.password=javacode
    8 N7 k1 L. f. w. a3 J2 E使用方式如下:% J" u+ w- T/ {  l2 d( {, k" b3 L9 j
    ' F7 W3 L- a8 c: K* H. w4 p
    @Value("${jdbc.url}")' ?8 |' G8 H4 t+ P
    private String url;
    ' t2 X+ g1 O* v9 ~  s3 e& @9 D: I* O) R6 ~
    @Value("${jdbc.username}")
    + Z7 ]3 m8 n" k4 q/ S0 i5 L) pprivate String username;# u1 m7 Z) q# D9 e" s. A

    . g# C0 ?+ K  k6 h! U$ f5 _: x@Value("${jdbc.password}")
    6 V$ Y# v9 t3 v4 z. _: [) ?private String password;$ y- `; M$ t: w, [' l: c
    下面来看案例
    ! t$ a7 p7 n- @1 M3 K; B, P8 Q: w# |1 `
    案例) l6 @$ ?3 w# z, V
    6 s0 q' p4 X5 E9 m$ A
    来个配置文件db.properties: c2 t: ]0 P7 |2 j, N
    ; E  D7 Y7 D: w$ g$ b+ b1 R" T" k$ a
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8* `/ e- E) f9 d( k7 d# v. k/ p
    jdbc.username=javacode1 T: I/ N6 C: m7 _/ z; c
    jdbc.password=javacode
    6 m7 ?3 S/ F3 C1 u4 K; y3 i8 `. @来个配置类,使用@PropertySource引入上面的配置文件
    $ ]: l; W% r" `& T8 G% i3 |! z
    5 q( v3 m0 Z  Y! i4 U* ~5 ^package com.javacode2018.lesson002.demo18.test1;2 P$ o; ]) Q* W" s+ [
    9 [0 E: g! h5 ^! E4 A  b! w+ e
    import org.springframework.beans.factory.annotation.Configurable;$ A# U5 n  F$ I6 z2 Z) h/ I  `
    import org.springframework.context.annotation.ComponentScan;
    / g. h0 V6 B4 J- nimport org.springframework.context.annotation.PropertySource;4 j, Q5 s: Y  ^9 R% Z2 M0 P
    ' Z' j+ T" \) |; l0 \
    @Configurable
    * w+ K- B8 L0 b/ v6 C, X. {( |' ~@ComponentScan/ r7 @7 h/ y$ e# Y
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    - |  f' a1 ?2 ipublic class MainConfig1 {$ K" y# J) y( o8 ?
    }
      t  x, f; w3 }. q2 L% w来个类,使用@Value来使用配置文件中的信息
    ; W9 X7 h; X! u! F, h0 Q7 c( p2 u1 P. P. g
    package com.javacode2018.lesson002.demo18.test1;
    6 p8 ~. _2 L* H$ b6 \+ W
    & Z& F4 J' K& `import org.springframework.beans.factory.annotation.Value;) O0 m# ]/ d! G' a
    import org.springframework.stereotype.Component;
    1 j" z/ W. z( V' L. \. ^( B+ J& w' }# R. L  |0 f+ _
    @Component
    ! }( V. L+ @" W! A: e, q' \public class DbConfig {
      h. k) C1 X" g0 p
    : h8 M& k' \5 c1 a+ y    @Value("${jdbc.url}")& b& {; A0 y+ D+ |
        private String url;
    , A! U5 h& D5 H0 p! h* B
    2 h4 K1 x- \7 r( @    @Value("${jdbc.username}")6 J* T6 R+ R2 k2 S5 T: }
        private String username;$ B/ _& A1 H; F- L4 J/ F7 B
    0 U9 e" |0 z. ^* x+ L7 d. i' E: a
        @Value("${jdbc.password}")- ^# f% }& W% Q, \! p
        private String password;% o: P1 d- U6 N4 I( ]' a2 c: }8 w

    - T% Z- |9 X: f    public String getUrl() {) x6 J/ [) \  a. {4 x/ L
            return url;$ ~1 Q+ @- K  s
        }
    2 Q; R3 m; A2 K9 o$ q
    5 p% c+ v' ]: K$ l    public void setUrl(String url) {
    & \$ H8 O1 a7 b' @        this.url = url;) W) n7 }9 z0 t, P4 {* |  u
        }% j( h( ^7 {# q1 o. \5 N: x

    ( H6 z9 K5 W! H* k$ Z4 l# A0 X    public String getUsername() {
    / u! K0 O; s. |4 S6 y! ^4 ?1 P        return username;
    5 c  p4 V+ q# k    }
    # g. _3 i8 W9 ~1 Y1 g3 a" j3 F9 V# v% J  R, r
        public void setUsername(String username) {
      b. w! r! U  s5 s        this.username = username;9 [/ e/ d7 j* C; ~( t1 t( B* @
        }
    0 `: c6 P0 n$ y% z8 p
    0 ~$ ^0 h; M# S    public String getPassword() {
    # @& w0 v1 D& d% _        return password;
    ) S0 [6 H# n& |1 _    }2 h1 E/ j3 r4 K7 @, O* s5 }

    # `# L" d4 V* h# Y8 ]    public void setPassword(String password) {) j5 T" K+ ?0 A6 s2 m; k
            this.password = password;3 `; t5 X& t6 {
        }
    4 \7 C/ [8 }2 M7 _- R* [  l: h2 F( y- n: {, |2 M
        @Override
    7 U( z. r; r' p" [' `# }1 d    public String toString() {7 Y9 w, [7 w6 D
            return "DbConfig{" +
    , x& F1 d  D! B                "url='" + url + '\'' +; t) J' h. r* D* f4 Z0 \/ `
                    ", username='" + username + '\'' +1 \; ?% Q# Q4 r6 L3 j" m, ?
                    ", password='" + password + '\'' +# p/ B5 T" m5 t$ ~& S8 @- c
                    '}';% |6 b4 B$ ]2 m* n3 [
        }" }: Q9 U' @  @7 V4 \% L, G
    }
    / |$ `, f- y$ R' b8 s( S上面重点在于注解@Value注解,注意@Value注解中的
    : P% _' I& W9 q
    . W: m3 C4 _" c6 g2 b来个测试用例
    4 s7 q0 `; J" U; `* O. V' _8 P: v; M8 U/ s; j" x3 Y
    package com.javacode2018.lesson002.demo18;5 d6 G" e. D/ K- }  {  w3 K/ K

    3 y: \& K7 Z: D8 w+ cimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    ' k# l! Z6 u  n3 }- n+ J( |import com.javacode2018.lesson002.demo18.test1.MainConfig1;, W7 b& k% L* K" M" |
    import org.junit.Test;
    $ w) o2 a' ~! wimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    * U6 l" r0 V9 C3 ?
    . ]# _( |7 K4 Y( U( J1 I' Y9 |public class ValueTest {$ c7 r8 g* a( S) u5 P5 |! }: [
    2 W! X/ H* z0 K/ o& M
        @Test  S# f3 p% g. r8 m4 |6 G, o
        public void test1() {
    3 I2 n  x) h% E" `3 S1 V        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();1 I+ u# A0 J, R% R
            context.register(MainConfig1.class);7 X4 l8 h! X7 m9 k- \
            context.refresh();7 c5 @+ v" Z" ~- i
      u! {4 r, S: u" ^' _+ ^- s, I% `
            DbConfig dbConfig = context.getBean(DbConfig.class);
    4 o( X, F3 q/ ]4 O/ `- f7 z$ J        System.out.println(dbConfig);5 h9 D0 s0 ?. f9 ~/ G" N9 j
        }) `8 ~# s5 t6 v. F3 ?
    }
    3 f. M8 }  H0 R- Q6 I! T) G运行输出
    4 t. I2 J" B3 G) I# T; s3 y, |* `1 I2 L- @" J, T1 H4 x4 R0 N2 g( X
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    ' y, g1 J  w( G. K- {上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    " ~. ], F- E! L" W: r6 Y
    0 d  G, T, e( l& ~@Value数据来源
    # U/ u) O! s. A  K2 G$ H1 Q, V) t2 `8 z5 j
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    / U& _) R0 q! E+ K3 v! t8 X" z% m- ~: Z# n9 Q' d7 I7 o  S
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    0 t! m% ^/ W8 S) Q  l3 ~# d7 H
    % \: p" S% w1 U# _# K) cspring中有个类4 o. r* o9 t9 _

    9 l* X, S1 J( \5 {; Yorg.springframework.core.env.PropertySource
    : G% N6 q9 S% I! c6 Z" M+ L8 O9 L可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息5 m8 W$ [! x# m' P

      J% I+ C) M* i0 |/ K! d/ O% N内部有个方法:+ \5 z2 V2 t/ U  i9 D# w* t
    . G( S( _( L, H) f# k
    public abstract Object getProperty(String name);) I$ W6 P1 }1 ]! J( k2 D
    通过name获取对应的配置信息。) }9 J1 a: |4 A. Z3 I/ G
    4 z% d" b" @: z0 a& F
    系统有个比较重要的接口, I0 h* G" p# P# v$ g; Y
    + G( L. Z5 X9 @  U3 ?0 _$ l- V
    org.springframework.core.env.Environment2 ?8 E# K& q, T3 B# W: }
    用来表示环境配置信息,这个接口有几个方法比较重要8 g. Z( w/ J, v3 Z( v+ I. o! r) O
    % G/ K% l2 V5 ?( x
    String resolvePlaceholders(String text);
      G$ F; W( q7 e, I0 \4 L+ Z7 FMutablePropertySources getPropertySources();. b( V& A7 R6 G* y9 V  U3 o6 {* x* P
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。5 J% ]# P% \& q5 h* ~! \
    & C7 b( w# Y! N+ W# t
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    + n- V& h8 j0 B! X1 K1 S8 `6 Z# g- }9 |: n: L( o
    public class MutablePropertySources implements PropertySources {3 ~7 V; |0 T$ v; H0 B
    6 r* n; o. l' D
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ! u! O1 V% t6 J( |0 @7 q: L  |8 v+ q9 i! [! R# ?- ]
    }
    # J1 j. q1 Q. Q; l7 y7 y# }2 f内部包含一个propertySourceList列表。, @: M# r" W0 s: `6 h1 ?% X) H" F
    ' Z+ o: J2 C1 q- w/ h7 h1 v
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    / K; N# h$ I. [  l* e
    # a3 Q. d- Z. E  Z+ P大家可以捋一下,最终解析@Value的过程:
    / f% g4 i/ a0 Q8 ?+ t# H. k1 N( U# `8 v: N5 c
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    " J0 K+ A# a8 p- T% O+ B5 O3 |; F$ ?0 \2. Environment内部会访问MutablePropertySources来解析
    5 h0 s% B- E: ^/ k& k3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    ) ?2 y' F7 T* L# Q: n$ W通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。. R0 n/ |0 D, f. N; N
    * s& v0 I+ ?) j
    下面我们就按照这个思路来一个。9 o# A( n2 Y5 K

    6 H+ e) q, _5 K; c' p来个邮件配置信息类,内部使用@Value注入邮件配置信息
    7 C' B, N7 ^$ W  C- }' S
    & q3 I7 m- O8 i/ o5 ]5 Zpackage com.javacode2018.lesson002.demo18.test2;" r: x6 e6 D* P: K! H9 E
    ( d0 P$ ?6 N$ ]3 g  n
    import org.springframework.beans.factory.annotation.Value;
    0 B7 g5 y- a7 h; I+ w+ @import org.springframework.stereotype.Component;; \, ?, [; k, Z6 c0 ^/ Q  I6 W& b
    ; f' c% D7 h8 |& S5 I
    /**
    , N! V7 ~" R5 i" ?* [ * 邮件配置信息, e4 |2 Y- m2 x$ x6 O: A' v
    */- N+ E6 l/ I" W( z
    @Component2 L2 W0 P( S! E
    public class MailConfig {+ P  {- L9 y! }! m* O
    % O' I2 \" Z: g. b& l* @: ^7 Q
        @Value("${mail.host}")
    ) M( x1 D( m+ q! I    private String host;
    1 D' A7 Z$ }4 ?; M) J
    , U3 b! Q' b9 e2 ]  t    @Value("${mail.username}")' l4 D) F- h3 |9 Y0 V( H( K, V1 g
        private String username;
    5 F! K( ~+ y: O: @* O! t6 G  U5 @. C5 c0 m+ n
        @Value("${mail.password}")
    - p8 W  h1 q/ n, |4 @& `    private String password;6 r* [* }& z( K4 z! M

    + {& E: V/ W7 k( U* ^    public String getHost() {
    0 T4 s4 @5 S; J, }        return host;
    9 l! u$ ]& X7 Q! ~4 n, u$ m    }
    # `2 Q4 [4 W4 e: a/ P& o1 @" J9 \& [. i- Z# d( t: ]0 K0 l1 U
        public void setHost(String host) {0 ^- `8 M% F9 a2 h( j: C# h
            this.host = host;& f( n/ u/ C0 ?7 M; J4 v$ q
        }
    ( v6 p+ Y2 T( D5 {6 f
    * J$ ~9 n  T4 L5 D8 b' k0 [1 [    public String getUsername() {
    6 }1 @( {" J0 {  S% C) S' I5 |3 j        return username;8 {# p# t7 p7 E' T# l
        }
    % r% z0 g: |/ _) U+ @  c% z8 O2 }* X
        public void setUsername(String username) {: f7 p4 ^( O$ a
            this.username = username;/ U/ Y4 D' k# C# A' }6 ?$ m& c# s
        }! R4 n' J4 X$ i

    1 s0 A9 n0 X9 ?4 M9 v    public String getPassword() {" ~; M/ r( j3 n* T: [# T: X
            return password;( o) X) ]- W7 ^) Q1 O
        }# n/ n6 a. y; C( p$ ~1 j
    8 E  p0 j$ @* s. f8 D
        public void setPassword(String password) {
    $ K2 M  C& K  c* A4 x5 p        this.password = password;
    % u+ _: y7 A4 Q+ A    }
    ' d* u1 s4 e; X1 f/ [# W5 {4 b' h6 h" `5 y/ u; k
        @Override# H' j: N, h* ?8 ?
        public String toString() {; A3 W4 \; R$ b
            return "MailConfig{" +4 n# I: B) X" ~- S6 Q3 ]
                    "host='" + host + '\'' +
    ) o+ S1 l9 ^0 D' G+ t8 z. e                ", username='" + username + '\'' +
    4 u% g0 H1 @0 u9 D. ]5 v                ", password='" + password + '\'' +! S& u, R9 @9 O
                    '}';# I! Y/ z, `1 ^+ i1 a  r2 j
        }% o" b2 M; r+ K/ V2 k
    }* P- D3 L  q1 h3 `* U
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中" S+ J, Q) Y$ D
    " y: Z* K% S" ], J3 U, ~2 k
    package com.javacode2018.lesson002.demo18.test2;
    1 x) R3 G3 Y& j1 e3 N3 m: e7 C+ y
    ) v: q2 L7 M. A( c& \: Aimport java.util.HashMap;% U4 R! c$ o' }$ ^3 h; e% d( Q
    import java.util.Map;+ N1 n/ g- S- b: m6 y
    ! o: X7 R  {! W
    public class DbUtil {
    ; {# l* @1 N$ o0 f8 t    /**- c. L- M. d& A* G( [) r
         * 模拟从db中获取邮件配置信息
    ' `! L  y. `% g: L, e4 C/ @2 e2 \     *! |( q# c2 s( n4 V9 c4 w% k' [+ p
         * @return
    . L/ V( N. d6 G* J7 {7 m, n7 @     */( j) u1 |; Q( [6 D
        public static Map<String, Object> getMailInfoFromDb() {
    2 ^# @  `0 N2 i  B+ {$ o        Map<String, Object> result = new HashMap<>();1 c% {, P4 ?8 O1 e3 D! f
            result.put("mail.host", "smtp.qq.com");
    ; f7 L* j+ A  a        result.put("mail.username", "路人");- m0 c  d9 C: `- _! Z' @; G
            result.put("mail.password", "123");! ?; r, k! o1 K8 v3 A5 f- a4 Z
            return result;9 Y+ s; n6 R5 |# F, V6 T5 }  `
        }" t+ F) {" I& ]: ~
    }# g  G- }- i5 F
    来个spring配置类
    # _4 N( S, t" I, t8 z" A7 G, q6 g3 a7 I5 b2 k9 `1 ?* H: x
    package com.javacode2018.lesson002.demo18.test2;
    5 v, t9 ~4 c+ q# T, D1 n
    / j- m$ q; J3 R+ T  U. M& u, k" ~; Yimport org.springframework.context.annotation.ComponentScan;9 r; D# u1 p+ U7 c8 o0 `) E% ~
    import org.springframework.context.annotation.Configuration;9 }$ A3 G( J- \6 R/ l* c

    # W+ k, I& X, q5 V/ E2 _1 H( G% g@Configuration! w, I9 V9 O( N1 s
    @ComponentScan
    9 W1 j: T" t% C$ opublic class MainConfig2 {
    ' {1 h" d* k* Q}
    & m( D. d9 p  h! a1 t2 l下面是重点代码9 w0 |/ J4 e2 ^/ N% w2 y

    1 p( ]2 y$ ^8 S: g3 u@Test
    9 \9 S% C4 N% k& s/ dpublic void test2() {! q" q1 @9 K! Z% H- H) M
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    " Q* g/ T( P9 g% H$ y9 ^4 {3 l0 k; X4 M; U, n
        /*下面这段是关键 start*/
    6 j! k! u, r# X: o/ Q& S5 ?0 h    //模拟从db中获取配置信息
    - N! d; m# @2 @/ E! U    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 [4 o: q/ f. |# c' z  j5 K
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    1 Y* {$ {. d# i; N5 _) v    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);$ N9 T! l1 C1 M- V- z
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    # a7 m6 x1 {4 Q+ W! i* u6 X  _! m4 R6 W    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    2 f- }' k. I9 Q, q    /*上面这段是关键 end*/8 w8 G" j$ p( [7 h- W2 C- c

    2 a. q3 C3 O& g  y    context.register(MainConfig2.class);
    # f1 y1 a: S, d1 G# F, Z    context.refresh();, a" J) o8 M: [5 y8 U
        MailConfig mailConfig = context.getBean(MailConfig.class);( `" q4 v5 H8 G& c- u
        System.out.println(mailConfig);, L- }- N& Z6 L5 }' q; O; }
    }
    ' y' A' ~) d9 A6 l注释比较详细,就不详细解释了。
      o3 Y# x3 y2 n* N) O6 \( y9 g9 L$ \
    直接运行,看效果# a: D/ _0 Q4 g

    ) j; v4 L3 `* L4 N! I/ D- c3 A' LMailConfig{host='smtp.qq.com', username='路人', password='123'}
    % J' n8 r. v; ~$ ?% s6 O" ?1 T5 ^, Y有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。6 N1 R: P* N2 ], e" P
    # p% p3 i4 u7 w8 i0 T4 J+ i1 T
    上面重点是下面这段代码,大家需要理解
    1 p' t* E7 @3 N9 K* O
    # p7 u, K: E! [+ m; W  c/*下面这段是关键 start*/, N0 ~6 H" ?( K
    //模拟从db中获取配置信息& J4 C+ q1 f7 I8 `$ h, Y9 @! {, B2 k
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    # D( j2 o$ s1 H; j: u7 l//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)  |9 {5 r8 V4 ]* l+ E" Z$ C
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);+ P3 m8 y* r7 x' K2 K
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    0 j3 ]' l: C3 tcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    6 _/ C# y3 g5 |7 K1 l9 h/*上面这段是关键 end*/; e! ^: z" T: R7 X7 L2 X
    咱们继续看下一个问题: p# i  Z' o# ~- }$ G

    4 [+ E% S7 h) f( u2 }) L- f: J! U如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    7 \; _/ G% Y  r% Z  b. l
    % p; b. O( B1 v" X8 j7 N* q4 T" F@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    / @. U' O" X2 T
    , k( Q% {1 _5 l7 g3 I实现@Value动态刷新
    ! p0 g6 d2 w8 D# @6 u' Q9 @, {& X/ Z! v, R0 i! I+ R: E
    先了解一个知识点
    5 i, O% s. k2 F% k! b, T
    8 N; B8 F8 ]/ V5 m- s: m' C+ |这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    % T: Q* R5 v* P7 j
    $ I; @6 ~9 [5 p" O6 M4 C' e, K这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    / o" n* u1 T* {0 m; `8 u* K7 z- x8 A: Y8 H( h
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:0 ~4 [+ P% b) P+ q+ d( e

    : s4 T6 e  o& _4 z! I; gScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;  |. a$ J4 Q- q" b& c
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中9 c. t# T+ c& q* b  v  c

    5 l% Z" ?" J" P" kpublic enum ScopedProxyMode {4 l8 r) ?. x( k; U! y
        DEFAULT,
    % N1 Z' B7 p. [! Q2 j, Y+ v0 ]    NO,: j7 A% `+ X; @5 M3 c5 B
        INTERFACES,6 G) G3 u3 r# {; ?
        TARGET_CLASS;* |, f; S2 |5 h
    }) D" u4 w; g0 }8 p7 c3 @7 I' W
    前面3个,不讲了,直接讲最后一个值是干什么的。
    , `) ^' \" q2 y) Z' U" t2 P5 e6 n1 {0 u  t
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。6 @7 _, E, \* Q3 B: W, s$ X

    2 G1 ]; X* A4 j' j4 i理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。9 y9 @, K( y  p- P; [+ F! d# Z
      M5 M1 }: b; V' p5 B
    自定义一个bean作用域的注解
    5 K+ H/ _5 a# Q# z" [+ E3 o7 i* b+ g9 F
    package com.javacode2018.lesson002.demo18.test3;% B( r+ d8 y; \' L

    - Y1 Z! Z; e; N0 Cimport org.springframework.context.annotation.Scope;  |* J- o9 l4 f- Z" B$ H
    import org.springframework.context.annotation.ScopedProxyMode;
    2 w! q% }8 i" |" o/ _- }+ c1 B
    # S0 H+ \0 [# \- e& ~. Qimport java.lang.annotation.*;3 u2 b6 J3 g( l# X( W2 J" q
    ' r% J. F, d3 y9 n
    @Target({ElementType.TYPE, ElementType.METHOD})- K- d' [' e+ [9 _! N0 I) |
    @Retention(RetentionPolicy.RUNTIME)
      o8 }  I3 G1 }! m+ U9 Y# @$ k8 ^. O@Documented
    . c: t0 h, M# u@Scope(BeanMyScope.SCOPE_MY) //@15 X3 P/ f4 Z( a' s6 a+ m: P
    public @interface MyScope {
    - ]0 B3 e6 P9 O0 t$ |, ]    /**
    * L( I- r' h' w6 H" J- A     * @see Scope#proxyMode()# _# a: M9 h0 n3 q3 |& t3 t2 w6 H
         */
    ( u2 k- e1 J2 R& I" K& ^    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    & U8 H2 ~( Y! `: M% E}' a8 G7 K" L0 G3 j
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    + P/ x  S* K  k0 {$ p* f# Z# m5 C2 ]/ O5 \1 S/ B1 m& j5 p$ N
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    % p5 B! S* `4 W# O
    + k( A& |! v5 r- x- K) q@MyScope注解对应的Scope实现如下
    % c6 F8 [( o" y0 o! y' I
    ; z* B+ T; b0 _0 I. S/ \package com.javacode2018.lesson002.demo18.test3;
    ; p2 k! a7 U4 z) M0 z& m& ]) S
    7 ]  ]$ _  ^) @9 Iimport org.springframework.beans.factory.ObjectFactory;) ^" r- K: I& ^8 U
    import org.springframework.beans.factory.config.Scope;
    ) c- H& R; q5 pimport org.springframework.lang.Nullable;, c) N4 S! o% C$ d% X
    & A  ?" r9 I- }0 C- C3 x. g
    /**
    # }* g! x9 n2 h5 U1 f: `: r * @see MyScope 作用域的实现
    % O3 W6 X0 Q9 |) A2 _) B */
    / q0 K$ n& s( C3 kpublic class BeanMyScope implements Scope {! \9 P" @* C5 T0 g' \- ]9 U, R

    . A7 z2 L3 g+ H3 n( I- M    public static final String SCOPE_MY = "my"; //@1
    4 K% T( U" X2 Q  a, k( ]9 u* m" v) N- |
        @Override
    . U' d, F* n: m+ @+ Z    public Object get(String name, ObjectFactory<?> objectFactory) { . O1 k5 V6 _$ g- J+ E
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2% K' K5 p9 N- q1 f% _2 {; v) q
            return objectFactory.getObject(); //@3
    & v5 c+ J& n, U& B    }( G9 H( O) i1 q, J4 }0 C  f

    , {3 U/ z4 u* f+ ?( p8 K+ r! @    @Nullable
    & c, K! Y3 C) V; {0 j: M    @Override
    2 J& v2 e5 Z0 `, x+ Z' r# j    public Object remove(String name) {
    ; F- W# S3 q3 I) t" R        return null;4 c( g) P9 {  C  x( L8 T7 Q
        }( S) m4 B. C# n, Y. r
    ; y0 X) A: l; c: R( R. r; q
        @Override1 x( Y6 B' F& B4 M8 Z6 C. t8 p( @) g
        public void registerDestructionCallback(String name, Runnable callback) {
    & b6 [8 m: r$ C3 X) h. D+ R8 l( u2 l6 U* S
        }
      j: J9 J5 @+ m8 B6 J, B' ~  l+ H% a$ v9 B& o4 Q" p% _+ `
        @Nullable% r4 P( x8 d0 j( z* Z- O- ~: b' \
        @Override  p/ X1 N( I. j' P5 k  u$ z
        public Object resolveContextualObject(String key) {
    % Y, Q( G/ k9 U        return null;& k" D& l0 H" a' \  y' o
        }
    : |# _: ]/ I# c  y( u$ O9 Q4 W9 `5 G, L$ Q) |  e7 g! d3 F
        @Nullable
    + |& O4 G8 `& q' b9 g5 X8 S    @Override
    , o" E5 L' x& v# s: O    public String getConversationId() {5 q6 I# P9 y4 a+ s9 X& D
            return null;$ S0 {3 D4 k  E: Z  e0 R
        }0 w4 z2 |2 c: Y; ?* I7 v4 i
    }
    - a$ w1 K# l" O) T@1:定义了一个常量,作为作用域的值. V/ l7 L0 Z4 G$ q- m& {
    8 A* U: ~, E2 V/ W" N. J) V  \
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
      T& ]  L: l$ P5 ^
    & J  f+ Y. d# I9 ]7 p1 w& A@3:通过objectFactory.getObject()获取bean实例返回。
    9 A! G$ e% A4 f* }
    1 P& U& U/ f8 _  n# ]下面来创建个类,作用域为上面自定义的作用域
    " ]8 [3 h2 n8 i" E+ v5 n9 ^7 Z
    $ K; F2 m6 P* ~3 g% o5 `package com.javacode2018.lesson002.demo18.test3;$ j9 G$ j. N; _' \, o

    ( s& S2 r! w5 i' f# cimport org.springframework.stereotype.Component;% P+ f$ c* b+ k; Z9 Y+ G. F9 \6 u

    - _5 g  ?7 n% m& x2 b6 A$ @& ?import java.util.UUID;
    6 A! Z4 ~$ I+ {  Z  T5 s& u! r2 ^& F) s. j/ p# E
    @Component3 U2 R4 D1 b  S
    @MyScope //@1 ( l- C; C. I0 @2 f  t* y0 x' I
    public class User {; S1 h; J& F3 J4 h4 |9 b
    6 J6 x; V$ P- Z- |7 N& n2 w
        private String username;
    / c3 e- _# D2 s$ U3 w" M. v5 O3 g+ m8 V. u6 x3 s& K
        public User() {
    8 ^7 N8 X* }* o4 ^. t, J        System.out.println("---------创建User对象" + this); //@29 m: ^! n3 ~# f. h% v, F1 P
            this.username = UUID.randomUUID().toString(); //@37 j7 `. g4 b: N4 y: f5 Y
        }  j9 V' ]* Y+ i
    9 m* R; }1 P" t1 m4 g8 d; N4 M7 h
        public String getUsername() {
    " @  e) s0 o: q        return username;, c  }. i5 ]2 z5 r3 n7 V
        }0 V: a" a1 W, M. i
    ( j4 f" x6 m9 I( f
        public void setUsername(String username) {
    6 E4 y! b" O8 h( u( Q        this.username = username;5 O4 Y* G6 K* I9 |) y
        }/ X( R/ x+ ], Q7 V

    4 |% n) e9 W8 Q3 R3 ?( w}
    ( I% B9 ?" e+ o* t" [8 ?@1:使用了自定义的作用域@MyScope% P5 C6 |- z. J- d+ e. N) J
    ) ]! B6 i3 c* ^% p# R3 i% k8 Q
    @2:构造函数中输出一行日志
    , p6 p2 P0 q, Y0 R1 N, g. S; H  _
    1 y4 q3 _2 F6 m* f@3:给username赋值,通过uuid随机生成了一个/ V3 H* \- c2 z
    3 _( `. u3 a; h3 T4 h, r! J9 W5 U
    来个spring配置类,加载上面@Compontent标注的组件
    7 A8 I3 ]% k0 e9 x8 L
    # h1 K/ W5 T2 vpackage com.javacode2018.lesson002.demo18.test3;
    0 i$ T: F& e1 T7 Y0 y7 f/ y9 W) @* U" p% N7 C( J
    import org.springframework.context.annotation.ComponentScan;: l! E% D% H% C- p3 q  c
    import org.springframework.context.annotation.Configuration;! J2 x. ?) U3 ^8 p* t

    " O4 e8 M: }; I0 X# t5 X: }@ComponentScan
    ! l6 v! S, u0 A9 Z5 D) M@Configuration8 ?3 z4 K3 J1 U% {! k0 }* F. D
    public class MainConfig3 {- I( {! f+ B6 `& H
    }2 I" L- A0 g' \' \5 V
    下面重点来了,测试用例% d5 l# Q) H+ C# `6 ?
    5 q/ O  H9 [- \: \2 }- a1 N
    @Test
    3 @. }4 ?2 m/ N1 a0 S: bpublic void test3() throws InterruptedException {
    . d1 @+ P0 c* u( ~    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();  O* B" G! Q! v; _
        //将自定义作用域注册到spring容器中
    3 B$ R1 ]4 ^; O" m0 c, w. l4 K    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    ! F+ q6 W. |2 ]; H2 Y3 {    context.register(MainConfig3.class);# C2 n) [3 m" `" q8 x
        context.refresh();
    % a8 u) n) J" E4 ~0 p  B1 [& c# k
    ) R* I  [2 n3 j" a    System.out.println("从容器中获取User对象");
    ; D8 o* N! W3 n( q: h. N# X3 ^    User user = context.getBean(User.class); //@2% e/ @' L2 h$ Y2 o# o% @3 y
        System.out.println("user对象的class为:" + user.getClass()); //@3: D. e6 k$ u' _- E9 j( @6 }! e

    / d! C& B" }: U( _% ]5 l    System.out.println("多次调用user的getUsername感受一下效果\n");
    " J! j, J* J% y6 u! \- x+ Q    for (int i = 1; i <= 3; i++) {7 B; B+ p7 v; x1 I
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    9 d2 K2 p* B5 j: b" @5 \        System.out.println(user.getUsername());
    # y4 r9 U9 b1 a3 o* K& M7 ]0 r        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));7 }& M. A' I1 i0 o- X1 m
        }
      F3 g' w9 z+ b" A% f5 K) a}
    # `- C  ]6 o( Y- z! j) z@1:将自定义作用域注册到spring容器中4 d, y8 Y8 ~& j# t: n
    6 I8 U3 D/ p+ W9 Y7 [+ l6 C
    @2:从容器中获取User对应的bean
    # H% T+ \6 l& U8 |# Q' h& ~  q; W0 a8 z# x. V7 M% v: R+ o& L
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ( y% c7 D" }; K4 @5 b1 Y0 N& _6 J# X6 C9 O. B) q2 C! p' O
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    # ^3 N% ^* `& E* ]
    2 q& K& A- N9 E2 j/ A/ O见证奇迹的时候到了,运行输出
    , }/ o0 m- @: i2 x6 p1 k% p5 c! P. M# j" X* @! |7 r! l5 s, o
    从容器中获取User对象' `0 K4 ^5 S; G6 ], d! F
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127( v5 {( Z  M' w- q9 Z" T8 a5 A- I
    多次调用user的getUsername感受一下效果9 M+ L# t4 h9 X" U
    ( ]+ l* E" K( T5 M2 h# ^+ P5 r
    ********
    6 J  ~8 J! ~& R: e第1次开始调用getUsername
    * q9 x: r5 T) z$ _BeanMyScope >>>>>>>>> get:scopedTarget.user$ i, x/ G6 q/ H' P8 ~( w
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    3 ~8 V% \1 G6 |3 m4 T7 l/ d7b41aa80-7569-4072-9d40-ec9bfb92f4387 W7 m4 V& r6 Q3 L7 V* f$ @9 m. E
    第1次调用getUsername结束6 ?5 V0 Q% h( e- T
    ********' h% p/ g. T: }* H
    - c; b* x+ k) G% N( B" g
    ********1 h& X2 P- X  ~4 E, L; L  h
    第2次开始调用getUsername
    & ~+ P1 J9 t3 Q" FBeanMyScope >>>>>>>>> get:scopedTarget.user. u7 j, u, |1 U' f# I# h9 m/ P4 W
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    : w: \( f7 u+ f4 e/ |01d67154-95f6-44bb-93ab-05a34abdf51f
    ! R9 h5 G  B6 }# q4 @) A第2次调用getUsername结束/ ?/ A1 E- d, `6 h* ]
    ********) S/ w" K6 X. d+ f/ Z' V
    / O, R/ h% @7 t( {2 |5 X
    ********$ S9 w6 p) h9 t5 X$ x7 o
    第3次开始调用getUsername5 K9 c, y& t8 ~) o: b# _
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    ( q7 I, N  \- Y% z8 u" d8 O---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d159 F5 l& i! E( p, `0 t
    76d0e86f-8331-4303-aac7-4acce0b258b80 f. {" m2 w1 ^: p' t9 j0 Y1 ~0 |
    第3次调用getUsername结束' u/ o7 e& j. k
    ********
    # M  r$ L' Y# F% Q7 W从输出的前2行可以看出:' k" v; a: \: ^0 A7 T

    5 Q) w+ {+ y4 n' D' g调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象+ L- h/ W) R* [. M& y$ o
    & d; G5 t( g4 Y3 x' b6 n
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
      C( r$ [: g) ~& D, N* r4 h7 _0 B( ^1 Z  y
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。7 c' Y/ o2 h1 E$ V7 p& h( t

    & {) \) }0 q) }: _7 s/ h8 m! s通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。+ J9 L* a' v/ ?3 E: P

    & a, W, D9 s6 q/ ^# o动态刷新@Value具体实现
    9 Y: \' }7 s% v/ E6 E  F' O' p. {( M
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。' F( `) h5 G9 I, i$ z' M1 H
    6 u& E4 ~! ~( M# Y
    先来自定义一个Scope:RefreshScope
    3 ?* P2 s( R) P. P9 c. c! E. t
      V  N- K( m4 u$ o7 ?package com.javacode2018.lesson002.demo18.test4;
    ) ?7 ~& h- R6 ]0 H$ \" D3 G; y0 ^' y8 |" R4 w
    import org.springframework.context.annotation.Scope;/ H- i  \$ T% i, ?
    import org.springframework.context.annotation.ScopedProxyMode;5 z( z  }( h4 c2 k+ a1 n- n
    $ t7 n4 {, d. W) l! `, N8 Q( l
    import java.lang.annotation.*;
    9 B: a9 G" m0 _; n  H6 `) ]' H( S+ D% v3 y6 f9 s% J: {
    @Target({ElementType.TYPE, ElementType.METHOD}); Q  L# M% F7 D
    @Retention(RetentionPolicy.RUNTIME)
    8 B) F1 k# s1 z  z( r0 J0 f, R@Scope(BeanRefreshScope.SCOPE_REFRESH)
    ) c2 p% y) I+ m& Q3 }@Documented/ @/ a! k" h" w; j: g; D' {
    public @interface RefreshScope {0 e! `* L5 G  h' m( a+ ]. y
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
      g: `! k- i8 s+ s2 E, Y# {}6 R8 m( ~$ E7 ?# o3 A+ X
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
      u% S4 }" ~5 Q2 K9 m
    / J* ?# C2 X" W! }@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS$ @. [1 d' c! X- L, _3 }3 B

    : v% ^$ ?# B8 B7 K% q这个自定义Scope对应的解析类) I; i! S6 C& E1 Q& T6 O4 {
    " Y4 X; h, s; R9 S+ e% j
    下面类中有几个无关的方法去掉了,可以忽略
    . l: s7 c1 k& Y3 _' I# U. d% P  p/ r$ T) [, y) _$ X5 N6 x
    package com.javacode2018.lesson002.demo18.test4;
    4 w7 s) x, K8 S8 B# T# g0 P7 U; h# }/ P( }
    , D; ]5 x  y& M9 {* L+ L
    import org.springframework.beans.factory.ObjectFactory;
    * l+ Z" Q% o. h+ l- n3 I( R3 V" q" i' N7 Qimport org.springframework.beans.factory.config.Scope;' }' K4 l/ |% C' t
    import org.springframework.lang.Nullable;
    8 j. L9 a3 P1 u" J  u
    + y) m; V" U" N. Y8 X) vimport java.util.concurrent.ConcurrentHashMap;& I2 g6 R1 h' O1 T3 N+ V" v
    & i/ T  F& O" U0 ~
    public class BeanRefreshScope implements Scope {; [( z7 K, e" h6 \% B

    $ l% s9 K5 A' V, e    public static final String SCOPE_REFRESH = "refresh";& r* s) O9 {- C2 `7 R& O; Q& d
    : k3 \! c8 ~: y/ k: B" ^2 n. }
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    - q3 p9 n  R1 \, z
    1 R4 y. g& m8 ~1 D/ S. L5 M    //来个map用来缓存bean
    ! m3 }" l. k3 s7 o! f    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@16 Z, L( g) C1 V# V5 K6 {
    1 ]8 P4 _. f% N" `
        private BeanRefreshScope() {, V# X4 |% X' B6 U/ u9 `
        }
    & |8 m% t% w3 E1 j# f3 B8 K
    ( @3 c, T; Q6 Z2 u" a4 N1 v    public static BeanRefreshScope getInstance() {
      E5 x. K) o: S3 S3 ~4 |" r        return INSTANCE;3 z9 v3 ?+ I1 g  e6 z  |
        }7 k" \' }/ l+ M3 s1 h- v
    . B& B  ~7 i! X) ~
        /**
      a- a# ?/ {. x2 B2 L) }     * 清理当前
    ! N- e2 B. B4 j& {. o; q     */
    . ^% s+ O$ A% w    public static void clean() {
    5 S/ H/ g6 M/ m$ ]        INSTANCE.beanMap.clear();7 w$ L0 n' _- L" R
        }
    ) O" n2 o7 i$ l! K' A, I8 r( J' o* S! Y
        @Override& i& z8 M6 `$ ]- W. [2 t
        public Object get(String name, ObjectFactory<?> objectFactory) {
    5 w  ], _6 E" S5 z# W        Object bean = beanMap.get(name);( _9 \9 p6 b% u- o% Z2 C1 J
            if (bean == null) {( Z2 }5 h3 F% _# n2 W# A- G
                bean = objectFactory.getObject();
    " S0 q& _9 d) U7 w! N            beanMap.put(name, bean);. n  r1 G8 b: _9 ?" Y+ ^/ a! Q
            }
    ! I+ Q0 g: S: R        return bean;1 t5 `9 @! J+ Z* _
        }
    * _+ p, |9 v3 M7 l- {( J5 _' @9 ]% ^# R2 Z" m) K3 x
    }
    2 |+ G5 Z# ?5 B6 s, G( @5 A上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    # t. c" E* t- z, }  I  Z! p7 Y" ]: `& `3 [; t: w+ j
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    # g* Z& E5 j( v. Y$ z6 h
    5 W; G7 ]6 R8 G- _+ V来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    1 p5 O6 o2 B* O) ^# Y
    ) r  P# ~2 i1 Z+ H0 V. P! S0 rpackage com.javacode2018.lesson002.demo18.test4;
    , p9 P8 ^/ V, ~) ]0 P$ ]5 s
    6 n+ C5 P4 d( O- G7 Aimport org.springframework.beans.factory.annotation.Value;% u! D9 ]7 G# x
    import org.springframework.stereotype.Component;! Z& \6 `# {" L2 t( Z
    + T8 M; h3 U+ t. m9 \* e, m
    /**
    8 P! R6 j2 V: I% N: l* M* W * 邮件配置信息% {' Z& q# p4 Y6 K
    */
    8 A0 \/ @# u4 m5 H6 C@Component
    ( S" q4 i0 J; l3 q/ Z' [. @" h" d@RefreshScope //@1) H1 d4 V1 h0 b- x9 G% U
    public class MailConfig {
    : o& R3 a* T4 Z- }: D+ l
    ) m3 o% ?0 W! b% e: J    @Value("${mail.username}") //@2" z: J9 a, y' ]3 B
        private String username;
    # M& }" i9 r6 ~# n2 b7 a8 s2 t4 A/ ]# s0 s/ c: Q
        public String getUsername() {- g$ t/ a' D  @- }! p3 r' m" o
            return username;3 A; p+ d' z9 R0 x3 A7 ]5 ^
        }
    - `, }# X% o$ l3 O. f7 g) C
    + \" O4 \9 _( m1 Y$ k! S) u0 x    public void setUsername(String username) {7 @, X& u1 M2 l
            this.username = username;
    % a: ~& N0 B2 x    }- I' n: D5 w4 j  G6 i

    / @: ?" ?: M" W' _1 z5 G    @Override/ A: P+ E5 j8 j0 T
        public String toString() {
    * X; |9 X3 j6 M/ O        return "MailConfig{" +; k2 K+ K3 z3 E0 W. W
                    "username='" + username + '\'' +
    1 u1 i7 }" k- K( T- `- q( ^                '}';
    # T' p1 W# B* X+ b; B    }1 R' t! f( }/ ]
    }
    , n0 g5 O6 v1 t/ m; N9 U. f4 Q@1:使用了自定义的作用域@RefreshScope
    2 M- d# a$ B! a, S6 f& _( w: a2 d1 n4 [' Y
    @2:通过@Value注入mail.username对一个的值
    8 }, W) m+ V9 ^9 K( d1 R* @
    9 `: m7 e. N: m4 E% `重写了toString方法,一会测试时候可以看效果。
    2 _$ `( }2 j+ }6 s
    + g1 q5 ]4 U4 y& C/ B9 `# o6 W再来个普通的bean,内部会注入MailConfig
    ) c5 \( N( T1 ]" Y
    1 \0 D7 ]; a: T$ H& [) v5 w1 Opackage com.javacode2018.lesson002.demo18.test4;% Y5 R( r9 q8 [6 j. a- M
    1 K, b) ~0 e' c+ k
    import org.springframework.beans.factory.annotation.Autowired;4 E- C% R: o# b
    import org.springframework.stereotype.Component;$ I9 r7 K8 x* e( o9 p8 q
    % M  w& V3 s: c& G3 T( Z
    @Component
    4 \! @1 I" N# Fpublic class MailService {
    + h* i' k' r$ b+ ?0 Q    @Autowired
    & m" L, z* C1 d$ P" h! e" V- o) ?    private MailConfig mailConfig;
    7 s% S7 ?1 o: P* f0 t* ^4 d: B6 ~+ W" D5 a- _; k
        @Override
    . c' s0 x7 x- v' D: D/ R    public String toString() {3 ?" a: b0 I9 s! ]+ @- n
            return "MailService{" +7 _) ^/ N+ u5 p4 c& ?& j" m
                    "mailConfig=" + mailConfig +
    ) {; i) }# m" K4 r0 ?                '}';. C* M" h, @1 H% ?
        }
    ( a; `9 I' d8 ]5 Y! x}8 ^$ r* s- b% H* N
    代码比较简单,重写了toString方法,一会测试时候可以看效果。; W# w5 d0 k1 a

    ; S) K  u' Z4 r6 X* l2 {来个类,用来从db中获取邮件配置信息6 s1 r" t) O" d' z2 T* Z+ a; M

    % |9 i& T. J$ Lpackage com.javacode2018.lesson002.demo18.test4;
    0 y* J$ i% Q# W3 j  ~( V  [  l0 L* N' m
    import java.util.HashMap;# T2 q$ d2 b5 N: g# c! k# t
    import java.util.Map;
    8 G2 r# ~3 X) o- T) b0 {import java.util.UUID;# Y3 b/ L1 o# ^" \' Z

    6 o& O* I5 K$ T5 Wpublic class DbUtil {
    / K8 z# U8 W1 O5 ?+ P- ]3 o0 V* q    /**
    6 v- @8 {  C2 x- z( _" Q     * 模拟从db中获取邮件配置信息
    2 l# q7 N" |: T$ s* G5 [0 ]/ Y( @$ n     *
    . B! m1 i+ K$ ]& h6 ?     * @return! a" P2 x- |/ V! [# o$ X  C, |
         */
    . Z& j, ^% M* o5 g6 r4 |- O    public static Map<String, Object> getMailInfoFromDb() {0 O6 I# o% g3 H* X$ z) }
            Map<String, Object> result = new HashMap<>();! q+ T, D0 U' b( @7 w  i6 ]2 Z& F
            result.put("mail.username", UUID.randomUUID().toString());; j9 D+ H# ?! d: i% C, K6 i
            return result;
    * v. f) D$ J. o( T, C% u3 K0 j9 J6 W    }. J- \/ ?7 U7 J! o/ S
    }
    2 q0 T+ U0 \5 S3 b4 F# X来个spring配置类,扫描加载上面的组件/ K4 x( K- y6 ?! _! ], O$ ]

    # C) [+ [6 I1 Ppackage com.javacode2018.lesson002.demo18.test4;% j  D" h! R3 e9 Q  a0 g
    4 e  v) b! C7 x
    import org.springframework.context.annotation.ComponentScan;3 T! R9 F6 h3 b1 `  G3 l
    import org.springframework.context.annotation.Configuration;! j- _; W) Q1 _1 y& d# I, W+ k5 N
    0 _! i, N# c1 ^9 U
    @Configuration& J/ o" \- V) D& g6 b% z8 @: a
    @ComponentScan7 N) f3 F) E& J  j; y+ V- P8 z) E
    public class MainConfig4 {& p4 x5 \& c& E- t  h; |
    }
    7 w! T& n, F4 g. V  h5 |) B来个工具类5 ?. C) R# X9 a" A. B

    ' m; `4 F4 P! l  U! H0 ~4 |3 w内部有2个方法,如下:8 ^* @7 |; q9 v# w7 @8 u: z
    " u1 ]4 E' C; J# z5 b1 Q$ U
    package com.javacode2018.lesson002.demo18.test4;5 D! [4 Z5 G1 e  w% ~% n! S) u, j
    * K9 X/ N: N; z' |" m1 }
    import org.springframework.context.support.AbstractApplicationContext;
    . B$ J/ ?. s( r3 dimport org.springframework.core.env.MapPropertySource;
    " K) W6 Y; @' ?  w6 U: Y$ S6 b5 L% W- y. o3 d5 l  Z
    import java.util.Map;
      |& }% [  X: {( C1 o0 I) k4 O
    $ @' ?" s. _7 V" Ypublic class RefreshConfigUtil {7 E) z  r* ^6 S' M3 F7 n2 L
        /**6 D4 J7 u: v3 P9 G! }/ Q& f
         * 模拟改变数据库中都配置信息
    * x. ~- W* V0 z* |+ L. T2 K. X     */
    3 _' l  w) X8 H* s. s; j6 ^    public static void updateDbConfig(AbstractApplicationContext context) {
    6 b7 l- [9 a* ]- \3 k% J, t; ~        //更新context中的mailPropertySource配置信息
    3 G' D. m5 n4 L$ J% z        refreshMailPropertySource(context);3 m' m: t, V* f9 _9 J

    1 T# t! H) w/ |2 T4 h        //清空BeanRefreshScope中所有bean的缓存- i/ M0 W* l; A
            BeanRefreshScope.getInstance().clean();0 X2 |% [+ ]5 `4 K- H& }
        }
    9 h' X. I8 q( f- l, b. U3 T( p2 h2 a3 E# G" x
        public static void refreshMailPropertySource(AbstractApplicationContext context) {3 z2 Y2 K- z3 e- g/ ^$ A
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    - N* i6 S% d( o0 X6 `# M        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)3 |4 x! \8 ~1 H9 G& Z# C1 w( Y
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* P0 x+ _/ ?' G! C# a
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! v% B; k/ }" h    }
    ' C: k" D. t. s% H( _5 d6 S
    # H" t* R. n. C1 N. W+ {8 _}3 ]$ y/ c" q2 n5 J5 Z8 T' J" \+ N5 k
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ) l) G3 V3 J3 T) Y$ z( n, B7 u+ H1 x- j8 p8 e
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。0 u6 i8 i7 ^. t! J4 u+ h0 i
    ' w; H; Z: G2 n7 e! g8 R: R
    来个测试用例% z, l- @& \/ Z1 Z; Q; @

    4 A! u5 G$ v9 A1 Q4 b& m@Test
    / v% S/ G- ?' ?/ E  d% Lpublic void test4() throws InterruptedException {
    0 P4 ^4 c. z6 j3 R- e. N    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    . ?' [) X2 o" m/ f6 p    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    2 Q8 L4 j& n5 Q* L% {4 |# m( v* W    context.register(MainConfig4.class);
    ! i) e6 G, t. B6 S" k    //刷新mail的配置到Environment6 M/ f% U0 d$ x5 t/ y) m+ H
        RefreshConfigUtil.refreshMailPropertySource(context);8 \" x  O2 H8 H2 x
        context.refresh();  n8 U! c8 O/ i
    ! R! c& \/ r( m- B1 I4 m
        MailService mailService = context.getBean(MailService.class);* q; h+ Y* n; R
        System.out.println("配置未更新的情况下,输出3次");
    3 s1 I/ _& ?5 i0 K4 L8 _' Y    for (int i = 0; i < 3; i++) { //@1: d8 R5 d5 p: I' U
            System.out.println(mailService);
    / u1 \# F4 Z" F        TimeUnit.MILLISECONDS.sleep(200);
    ; b' U  G  ]- q$ n+ p: E    }
    8 T" A5 C( o, r" L2 l2 ?; i3 g$ a$ _4 ?: {$ a/ t
        System.out.println("模拟3次更新配置效果");
    & }" B( l* t* E$ e0 m4 a    for (int i = 0; i < 3; i++) { //@2* J6 w3 L% a+ U
            RefreshConfigUtil.updateDbConfig(context); //@3- ~5 d! z9 i1 [$ B6 X( r, w
            System.out.println(mailService);: K! M! I) N7 Y4 a3 o0 k: v( c
            TimeUnit.MILLISECONDS.sleep(200);, q2 [. R7 Z* ~: Z! I
        }
    * V" g) j) o- b) I- Z! o}$ ~' u* @, m) i# a
    @1:循环3次,输出mailService的信息. H* u7 ~/ Y1 W$ E  H9 j
    4 F1 D3 g8 e! t6 r
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    ; J1 D1 y: @. v. M3 c) q8 `! z3 d$ `: b, _
    见证奇迹的时刻,来看效果
    " ^. q  a, h9 K6 X, c# q: ~8 a- r. v, z
    配置未更新的情况下,输出3次
    1 q! q& f) U- w( o9 ~. ]2 fMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}) H& G! b# y. s3 N, b& q4 x
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 z, z. Q8 f! z) kMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    " r* w7 K) Y9 M4 E& x$ m模拟3次更新配置效果
    4 D7 t5 j/ N5 B/ P3 |5 gMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    " I" f% d) G) F8 bMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}( K5 K' Y- d/ w5 ]# `% y
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    # x% k3 H& }7 P, n) V7 `上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
      T2 U$ U" U) r7 y; V' p6 i* X. {3 Y1 I' f
    小结! x" N4 D8 x" P$ K& }3 G
    # ]; P* r' d4 E, S; H9 _! o
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    $ z. z5 ]& s7 O$ S; @3 ]6 |, d! t: m9 j
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。) R0 ]' r' o* q5 ?
    . ^7 y% y6 D/ R6 q
    总结1 Y6 z, w+ ?$ E

    / r: P. H6 s  @: N# ^0 B: p本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!9 T  x8 w0 ]  S( I

    ' Q3 P6 n  n1 e5 K案例源码  G5 d" B$ ?- y( z; o

    2 Q* e# b, h+ W0 J4 s0 m/ R  c1 ]https://gitee.com/javacode2018/spring-series8 u! j4 I% ?1 ]- E7 I$ D9 W
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    * e; U5 `( l: @/ N" |/ H& Z! P  M  h+ k! Z" A/ @2 g: Y3 G
    Spring系列
    + F  s# f- ?8 ?7 a! X- _/ b' S+ ]* a$ C, a. Z1 B' z" _( D6 d2 y
    Spring系列第1篇:为何要学spring?
    - L% g! G% }- |* {
      X# i' O7 ]/ [4 r0 L4 Y: DSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    1 ^' E. V+ U, ^3 r: q0 U7 Z* B
    + A8 f" {- C* rSpring系列第3篇:Spring容器基本使用及原理
    4 z2 q  ?( M# D+ {% H& ^4 b' a! d( I7 m; l8 J, d
    Spring系列第4篇:xml中bean定义详解(-)5 ]# i; H( M3 m( |7 C3 P: T3 C

    $ D% L8 p3 t; o2 q$ pSpring系列第5篇:创建bean实例这些方式你们都知道?
    9 R* n& m- w3 P4 X* e, C8 x: }. ?8 y! K  e* i' Y7 k1 r. k
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    7 C$ f+ l0 C+ p
    ' x3 ~2 P& E! c& I0 ]( SSpring系列第7篇:依赖注入之手动注入) u& A# g4 j- d. U1 @
    ( u6 E9 R$ Z% T/ G. E7 m2 y
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持9 M) r  C  \  }# o1 c

    6 A" ~9 I9 C+ Y; v1 cSpring系列第9篇:depend-on到底是干什么的?  i) C. i  c  K9 H1 f; y

    ( I7 }+ `8 I, N1 n, ZSpring系列第10篇:primary可以解决什么问题?
    7 F. ?% ~' R$ C, C3 e2 a2 Q- F/ Z2 u) B) ~4 Y
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?& X# \6 V$ f+ Q% _/ n

    1 h' L" |- j' d/ |* `Spring系列第12篇:lazy-init:bean延迟初始化
    ) U8 \( Q9 D9 x/ W0 p: Y6 h
    ; _. o' o' g7 OSpring系列第13篇:使用继承简化bean配置(abstract & parent)3 Z7 I/ Q% D4 G
    + ~+ Q+ }6 C+ s/ B9 P' [8 B
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?/ C2 Y6 F. F0 ]; f6 x2 F

    5 B  H5 T5 ~1 a: E7 ]+ d# F4 p& q3 MSpring系列第15篇:代理详解(Java动态代理&cglib代理)?$ \8 Q, E; e8 o4 B3 ~2 s

    - n7 p9 {6 Z( O+ l* k$ YSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)' D# ^# e* t( K, f" R7 ]- S0 I
    , G$ V3 J6 O% `/ w+ x0 q# |
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)0 I- W7 Y9 W( W

    ; U$ v; \. i7 C; l% e7 B6 D, aSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)" a$ S8 q, C% r' j
    0 Z! X: s$ A/ t( d8 N8 {( g
    Spring系列第18篇:@import详解(bean批量注册)6 `: n$ W! G5 h  @- {0 w
    * H0 i  @7 _) B5 v4 _# E% m9 W
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    ; y' n& C! l+ `* O4 R
    9 {4 z1 X  C3 }: B9 c5 SSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)5 o8 q( c  L- n0 ~
    3 K3 Y* q$ h. X
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解6 [% H$ |' I1 n/ _6 ~9 }% @% F
    & H/ y% K1 L/ M0 k
    Spring系列第23篇:Bean生命周期详解
    5 }& U, v9 G; r5 H- }9 v
    ( R" b# B" n# ^# d0 S0 J/ OSpring系列第24篇:父子容器详解4 a" \- b8 t) r$ N4 @
    & c* B; v" E$ I! m
    更多好文章! F) t1 p/ Y% }- c* i" V4 _. C

    8 d+ E" V. h  T* ]& @  J4 uJava高并发系列(共34篇)- Y+ U$ @" ~8 o0 x, ?# E$ ~0 O

    0 S, M' Z$ m$ c! c3 ~! CMySql高手系列(共27篇)
    8 i  M, S' m% T/ x6 ]9 Q2 ~% t0 f8 `) u, Q# y0 \
    Maven高手系列(共10篇)
    5 t0 {  _( j  Z8 ~; U- L& j2 G& K) q5 t$ O" y
    Mybatis系列(共12篇)
    2 p% J2 y  O6 T0 k6 X6 P: T6 m5 }, _
    聊聊db和缓存一致性常见的实现方式
    : o- H  m3 E4 u1 a
    6 K7 v5 C* ?, V7 I" Z: N接口幂等性这么重要,它是什么?怎么实现?
    & I+ K" c0 V; g; g3 C' G# j
    . w0 \) Y, W6 S6 H  r. x1 j: B泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!  H, t8 o0 O3 ~9 N8 p- n& T7 T3 t
    ————————————————
    2 L8 Z9 v' N. _0 N8 `3 I3 B1 [+ y% v/ n版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    : \- s$ h" K5 k. ~4 U3 t) q0 o9 f8 ~原文链接:https://blog.csdn.net/likun557/article/details/1056487576 W) ]$ W& O4 b5 O0 m* l" L

    ! b/ W; N  H1 `4 A/ ]( ^/ H' s6 P8 ~5 V
    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-9-24 18:58 , Processed in 2.685477 second(s), 50 queries .

    回顶部