QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5410|回复: 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!, e8 T+ V6 F/ q$ @4 I8 U8 L. h
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    % U' _1 J( z0 x8 A1 I2 g9 T' b7 j% N' D7 U
    面试官:Spring中的@Value用过么,介绍一下# A) x5 j9 ^7 @3 o
    & @( k  v. C% m! q5 D, Z
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    % E2 [+ U2 i( o2 |/ e
    ( e) u" i; N9 n4 ?2 z: X- d8 j( d面试官:那就是说@Value的数据来源于配置文件了?
    ( J# y" g! v4 T6 c- E
    + ^, j/ J( }1 C* `9 P- n我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置6 ~% z9 I* l0 S% v+ E+ ]! ~* ^! c
    2 ?0 h2 u  [: ^2 a- [
    面试官:@Value数据来源还有其他方式么?1 T2 Y0 h5 X1 e6 }( W8 ]: e- ?
    3 [3 z, Z- x+ c" j( S! L$ A
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。& x7 M3 m# v& t/ N" Z( J
    & F0 o5 ?+ _8 V+ x) |
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    : u. m$ j1 N, Y  n/ K
    , W/ U/ m* o, h0 y6 e我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧* i4 j0 w# A5 u- y8 b7 b

    / |& o$ s4 g3 \$ t1 l; C" k7 a' F面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?1 a1 Z  m5 ~1 t# X  T" C7 U0 d

    . \- g# a0 j1 q我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    8 m* i/ k' B. G4 E8 C% G- S0 I
    ( C. k1 P# C7 S( m  L面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?. O" e' ?, D; o6 s6 }2 `( k

    : w) G/ w3 M8 o9 S- k$ h我:嗯。。。这个之前看过一点,不过没有看懂
    5 H3 o! q9 n7 O4 b, L/ P" Y0 D
    . }2 f  B* H/ F. P面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    8 t' |* A; Q+ M" ^2 A
    4 c5 d" S! a$ S: O! c4 [6 c! Y我:3万吧2 I3 {4 n: f  Q  {4 S* y8 f# D3 W

    % \7 n; F2 E% a! v' j. b+ s面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?- O9 V( K' F: s- c
    - P4 v6 D" C! p$ t
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    ! F& G0 I$ U, K' j9 B
    ( H  q: J( f9 U4 u& l8 k" `面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    7 n8 _% V$ K" d5 D8 h! b9 q2 i
      a6 z; n" b) ^  R  |* f8 [我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。+ \: G" j( z' p

    9 `% T0 l3 ]. z  H1 o% |# H这次面试问题如下3 J  C1 }/ j; T( P5 L. K* Q. c
    5 S5 H. W) C0 K( W7 c% f2 U
    @Value的用法/ ]0 ]" Z. k6 Y. [; \6 s+ d' A

    - G& S9 h3 f+ |@Value数据来源& Y. z$ l1 O. }/ b' c

    & }" ]3 t' g8 k) K$ p, _. \# w@Value动态刷新的问题: p1 [6 Y' C! z1 i
    * _+ _+ h; `( X' L) c: [
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。' B% O# m9 k$ [0 L

    ) k1 y' T5 F: U; x, T/ y0 p  L; J@Value的用法. Q$ h0 f+ P7 v: ?; u
    3 v3 r# A" ?/ n
    系统中需要连接db,连接db有很多配置信息。( L6 S& d' Q7 ~2 A3 Z) ]9 z, j% C

    . G: p: g: f. h8 a  [系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    . v+ d: u3 f# Y+ P' N6 w4 [
    & ~0 G* i; Q) b- q# E还有其他的一些配置信息。
    , @  H; ]3 W0 {( S$ {" I' B, a  e
    9 r; R5 ]7 ~5 U* y5 v, U我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    3 z, x* a& s: T. X. I' o7 `- B0 }- o, n, Y/ s! l8 k+ p: _
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。0 U$ w9 V! a- [/ ~. K6 J4 n) \

    ; {! P4 c! [" L! |通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    + R. M  _- N/ C' |. U$ t( I2 A5 N+ @6 e* ~1 e
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    & q1 Z/ p) K8 O
    9 @5 _9 ~! X. M! t) m+ q/ h@Value使用步骤
    ) m4 h% |+ P+ D  K% r& f0 g" F. b# J2 H. T- ]( W9 P8 M3 N1 X
    步骤一:使用@PropertySource注解引入配置文件
    2 L  t5 [* C6 K) F" H* A' m! P! p% e& Y
    将@PropertySource放在类上面,如下
    % v: _0 z( K% {1 e8 ~7 P, Z' ~9 {0 B4 G* l( p
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    % ^0 T5 w6 W9 c! Y( s@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。4 L* L. l9 g( L2 S! p- O- }$ W
    " H) E; Z0 V! M5 T. _0 _
    如:8 d0 B, q7 M* K: Z! F$ }
      N0 d1 D) ~5 ~" ]7 f
    @Component2 f4 M1 Z& V+ o' K
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    3 d. n* u( `/ _public class DbConfig {, v8 ~- p4 ~  G3 i0 {1 K
    }
    ) m! X' C2 z) d: E& }2 L步骤二:使用@Value注解引用配置文件的值
    " P) G" r1 _% F. _2 m! s
    & X4 y, a+ h4 M. K- ]6 P, R通过@Value引用上面配置文件中的值:" o* S7 c% m4 v; O  J: ^( Y

    & f: f, a( R6 v1 ^. ?$ r语法9 I# F- T2 z% F1 L% v+ c
    5 T0 x3 T# B: u- w4 ^1 u
    @Value("${配置文件中的key:默认值}")8 \' z8 z7 q" v- c9 V5 x
    @Value("${配置文件中的key}")' p' ]2 |* ]# }
    如:
    6 ?$ ?9 ^! V7 j3 q" s3 B1 `4 }- c0 D8 L8 I+ {/ Q
    @Value("${password:123}"). W$ j9 D" G. J5 x
    上面如果password不存在,将123作为值2 i: o) Z. ?; ~* r/ H+ W( j( z
    ) J0 [0 X1 W* h; w6 C
    @Value("${password}")& d' H3 e1 t+ r$ k
    上面如果password不存在,值为${password}7 e+ A# @& E8 T$ U; W$ R# @
    & W) X% N. Q+ J* `; @
    假如配置文件如下4 Z+ x% P5 {& N- d
    2 u- k9 g" T& m* u6 r) x
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-87 j% T5 C/ `$ g8 O6 h
    jdbc.username=javacode
    % ?0 O4 G: f( Z6 f, G7 F8 I1 V/ c4 Vjdbc.password=javacode9 c; g, B6 a; q
    使用方式如下:( ?. \, p5 F/ c" `# o, F9 n0 F

    6 G+ {) @: V6 A6 I: e2 @@Value("${jdbc.url}")6 I. S/ d9 o) ^- U* i& k" T8 Z
    private String url;
    5 S8 z3 J& \1 G% P$ g/ }1 `, {! G" ?1 c! w" e- c
    @Value("${jdbc.username}")
    ' E, f# ?) J! F$ dprivate String username;# m9 C. q4 G3 h3 E
    6 l* G5 Y8 |5 _  o$ \6 r6 H
    @Value("${jdbc.password}")
    + O: d" `8 @$ m2 Iprivate String password;" B+ T& ]6 k( ~- {# J# E/ B
    下面来看案例% a, _! M: H8 K$ P

    : x1 I( I+ P+ J1 ]  w案例
    . `! \6 A& a7 q1 ?$ c9 `! W
      L: g6 I% F0 {; C8 O* v" W来个配置文件db.properties
    ; q. D2 Q& U8 K# i1 E; i- `' P6 m& |
    & B( X1 S9 Z7 B6 w. Ljdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    % F2 p$ [! {' F8 O& j9 `jdbc.username=javacode
    ( G- q  `  R0 _& f" [) [% kjdbc.password=javacode
    # f; U4 S8 u3 H. n2 _来个配置类,使用@PropertySource引入上面的配置文件+ d1 V( H; P4 ^0 P+ h0 d. j5 |

    ; J$ R$ ?7 c4 U! U8 P6 Qpackage com.javacode2018.lesson002.demo18.test1;1 |, t4 ?5 W0 x

    0 V0 ~) w8 t0 Oimport org.springframework.beans.factory.annotation.Configurable;
    & ]3 ]( N6 p$ a$ ~* m. ?( yimport org.springframework.context.annotation.ComponentScan;/ E% {8 d* L3 N# i
    import org.springframework.context.annotation.PropertySource;  y3 E& Q" n( G5 r6 h% F' e

    " L1 c* r7 O0 D0 m' x@Configurable
    7 t5 a* V/ \; g5 L@ComponentScan
    ! ]+ h' e* e' N@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & x# ]: P" I: a2 F# a7 S: xpublic class MainConfig1 {
    4 N2 _9 V# o4 G5 m, L" K) A8 m8 S4 G}- a/ m: b" @% k
    来个类,使用@Value来使用配置文件中的信息
    ; `& b7 G9 X5 C3 d7 i! I; ~
    $ J4 f& \) q0 i: z! m) {package com.javacode2018.lesson002.demo18.test1;
    6 {$ ?  Q7 u0 |2 R
    + t/ x7 n' P  b! d+ t1 `( h( X+ K7 Q  rimport org.springframework.beans.factory.annotation.Value;9 }! O7 G/ n  b$ m( \
    import org.springframework.stereotype.Component;
    # b2 q; t) _" ]; L( p6 j4 j8 s1 p0 P) m* j$ B. V$ K7 ?6 {
    @Component; v0 @6 y& `( a( X1 v2 A+ T( e
    public class DbConfig {
    6 y2 q; Z9 z3 D0 m6 J" m0 [+ L, }
        @Value("${jdbc.url}")5 K5 `% _7 @: f, k
        private String url;
    . w% C* r1 ~5 B: w5 `9 i, n' z9 [8 n0 x% _5 H" x
        @Value("${jdbc.username}")
    : G% M/ u0 n0 l. O+ N8 k3 ]. D    private String username;# G0 {% T# Q& l  W0 x  Z

    : R: I# d4 h# Z    @Value("${jdbc.password}")
    2 ~' v. A$ t7 E. [0 Q0 [    private String password;
    $ ~) l! n9 \% Q  R2 U- [- o# D: \6 c0 Z. J2 T( B* S" G
        public String getUrl() {
    " k2 x9 d; K+ _  |        return url;- \5 Y1 M! I; F5 |: t1 e- C" ~
        }5 B9 `# |) O9 `; j! I

    / Q- o( c9 ^( g    public void setUrl(String url) {
    # s* |0 O, Y% v' \        this.url = url;% k1 F! s0 |+ S$ T2 @
        }
    % I8 c; \0 I, L7 \
    1 F2 O! h5 h+ D    public String getUsername() {+ h6 w' I  C* |# e2 `3 r
            return username;: o- p& J0 m2 p
        }
    " Q( K7 u  V2 w# M$ F* d+ f2 T4 O0 S' M/ {6 l* P) s7 U; ^# Q
        public void setUsername(String username) {& Q; j6 g' `6 P3 t/ o% h
            this.username = username;
    6 g- @, a' O  G( {; N9 _) d    }* U' ?- L$ h2 J2 }/ r: E0 j, z" V
    " d: L) x1 P) H- @
        public String getPassword() {
    3 V& y6 v, b! m0 F5 v        return password;1 v5 I3 h/ c) j' t! i" J6 m
        }
    ' P4 b5 x0 I1 |+ z+ E  w
      e. v6 a% v; I& J    public void setPassword(String password) {
    4 `& z9 a8 K4 e- {- Z        this.password = password;/ g' f+ [# M* l
        }! N+ D# Z2 E! {3 `

    / l1 a  v: x' }    @Override
    # C3 y: I( b$ D    public String toString() {
    ' }& l8 |* W% B! O! J6 Q6 T0 K; p8 A        return "DbConfig{" +1 ^0 L8 q! o% W
                    "url='" + url + '\'' +3 ]& ~# U4 g- v! S! l8 {
                    ", username='" + username + '\'' +: S9 J! I* s+ A. s6 a
                    ", password='" + password + '\'' +
    7 d* W0 \. k8 H                '}';2 m/ w* y% F. V6 v
        }, O, q( M7 C/ w1 M
    }" i1 m6 l5 u3 I: H0 v5 z7 z: b
    上面重点在于注解@Value注解,注意@Value注解中的6 e+ C! X3 y$ Z6 ?& l$ x
    6 ?- d4 e' k3 v$ Q" u
    来个测试用例  N: U/ u, ^* w: [5 _2 y
    . Q# J1 R/ m3 |( |3 F! K
    package com.javacode2018.lesson002.demo18;
    1 t) `  C# g5 c; m
    7 }0 t3 R& }6 O$ Q  K+ cimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    , d* `8 x: Y' A8 d( c  C, K) ^import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ! V( e0 d5 @& b& oimport org.junit.Test;# X  D; ]) T3 `. `6 J
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    9 d: s' Y8 d$ u0 B4 F  b$ h( e3 C* ^$ d% m+ C0 K% W+ C7 v
    public class ValueTest {8 _- F9 [4 u% g6 u4 d  J6 n/ o% m

    " v6 X6 g. a" _8 z* |) Y    @Test* K7 k6 \  w: n2 g1 k* R. l
        public void test1() {8 F3 u" n& j8 c- a! O0 w
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    " T! Z) j6 O6 O; d) _% O0 k6 P  S        context.register(MainConfig1.class);6 v  `+ f( u9 q5 L1 J2 I7 x6 \
            context.refresh();+ R6 y8 [* V8 f9 H  o

    * a* k, V" }4 R- r/ Y) P- z) ^        DbConfig dbConfig = context.getBean(DbConfig.class);
    $ ~5 P. F6 F! P3 I; ?        System.out.println(dbConfig);5 C! ~- i) u. `* {2 U, ?- i- x8 ]0 D
        }
    & b6 q( N; u2 C1 n}
    0 [+ k8 z% I8 m4 Y) u- ?运行输出. [3 B1 f8 v# J

    8 |/ a$ E  a, {- u8 I1 P: s- bDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    2 p: P  p* g7 T8 R* X) H4 K0 R上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    0 p8 R1 m: U4 w. F: a9 G7 i: c# A! w4 k# C: f
    @Value数据来源
    % @* ]9 Z& _7 c" L0 C* D0 H2 a2 P( A$ T. Y3 H6 q/ y% K4 R
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    4 q; ~7 |( t( @; T* z, Y0 m! L, a* x9 c1 Y# u
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    % e( \% s5 K! \' q: P3 j4 o! F% i6 r. T7 ~3 @
    spring中有个类
    # S. ]2 R/ f  U9 l. `6 x7 \4 [  v7 F
    org.springframework.core.env.PropertySource& B9 E% t8 d+ w' B1 C
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息$ n. ^( g3 j3 \3 m0 q' C- n

      n9 }2 \' u. r4 \8 k+ X内部有个方法:- U( O% _. k4 ]; E

    6 d  q( S# s5 ]' U$ c/ upublic abstract Object getProperty(String name);& k2 U4 o+ }' \1 R; k
    通过name获取对应的配置信息。
    + d& W- c% j) e# I  C0 W
    - X2 _, h$ ]1 U+ O% C1 a0 |系统有个比较重要的接口4 ?7 \% S4 x% L. Z

    , W$ b/ \: }8 [" N5 \org.springframework.core.env.Environment4 S' @; I% r. Y1 ?2 N
    用来表示环境配置信息,这个接口有几个方法比较重要8 w- S) T; d- v8 m) {* Q
    ! A. F, U7 j9 ~; S' c  ~* ]
    String resolvePlaceholders(String text);
    % e( t) A, A9 [! |MutablePropertySources getPropertySources();
    + S! ^, r( }3 [; L/ i8 iresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。  h* {% L& r8 ]+ w% _+ R
    ! G0 j' s$ y' D: R; u
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    ! u' A  Z4 T; z* i4 f5 f$ l1 K6 e# u5 d& F
    public class MutablePropertySources implements PropertySources {
    ' G8 Q  C) S5 k, {% p: E( v7 v( C& l. y, N" Y7 f! e
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    6 c2 U6 a6 c' n* H/ L& Q
    - D% C3 l* Y9 m+ ?}
    # Z  l! q3 W  W- O+ w* n内部包含一个propertySourceList列表。
    - a' V2 J$ b" T/ ]  l
    * Q4 l+ M3 S! K& n+ A( G5 U  vspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    9 r' L! Q* `- g
    $ A( e/ j7 i; }! y大家可以捋一下,最终解析@Value的过程:) }8 p2 [2 Z1 B/ [! D: [

    * `8 w8 Q, E& f9 E1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析' c2 ^. T0 j( T$ E
    2. Environment内部会访问MutablePropertySources来解析
    , \1 d+ I4 r2 ]9 M# |( [3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    # c9 X9 m, ^2 I通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    , n1 O# f& w7 E1 ?
    - ]  s0 p0 d' Q* o) o+ ?: @下面我们就按照这个思路来一个。
    $ R; n: i, _) ?3 n! s$ i2 P, C! p7 }# H) k7 V6 I
    来个邮件配置信息类,内部使用@Value注入邮件配置信息+ S* U- i4 _$ l) {/ K* f0 h
    - S5 N, T6 X7 v+ A/ {! m" \
    package com.javacode2018.lesson002.demo18.test2;4 n! D2 n% d) [4 T2 Z2 a

    $ p* a8 {2 Q/ x4 T( V5 x" |& S* simport org.springframework.beans.factory.annotation.Value;4 `3 k/ v9 L# H4 F' S, `* f9 n
    import org.springframework.stereotype.Component;
    9 D0 Y" N5 L6 l1 `- G: c, w1 E$ l9 H9 d4 W
    /**0 k+ _# g' F9 S- @8 z2 W, ^: }9 l1 l
    * 邮件配置信息4 t. o# _. r* r% N# D3 T: F
    */
    4 l5 j  [& {# o' Y9 Z@Component$ f1 M* \7 B& P
    public class MailConfig {( {1 c5 L5 Z) y" n# Z/ b* o; k
    # x7 a+ J; ~, J1 f8 o
        @Value("${mail.host}")8 d9 r. i- M0 z1 [7 j# l2 U$ ^
        private String host;
    ! ?- j! C* n  l( k* P6 K: C2 r# q2 j2 c2 i# ^
        @Value("${mail.username}")
    ) K! [( o: Y  ]$ f    private String username;
    # }' l, }7 u/ a2 l$ P* K5 ^/ \
    ' o- G& |& \; W2 j' }) J    @Value("${mail.password}")
    ' W8 |$ c% ]" q4 i    private String password;1 Z$ \1 {' `& k1 Y8 S, E9 R0 X- Z
    + ?) [: o- d8 E- E- k
        public String getHost() {9 z0 l* \4 }0 I8 P& a
            return host;
    / E& K1 V# @1 B& r+ Q+ i/ \    }
    5 J; @- z$ K! \& v, ?, L8 T/ Q: O5 W" w$ N/ d; o
        public void setHost(String host) {
    ! S! q! t; A  Z. L. X        this.host = host;
    " o5 ~9 k. m- a    }
    ( p+ X9 A3 R  m) J0 o6 p6 C0 r" I' u
        public String getUsername() {
    6 _% A7 w$ @. }: m! F6 k, C        return username;
    % S4 E6 J2 I4 A! x$ E    }
      W( a9 C) [4 N# W6 ]4 X- P9 r, ?+ |1 ?# o2 z
        public void setUsername(String username) {
    4 @# u* R7 g) o3 D        this.username = username;5 x7 t! U8 [3 C- S; H  y
        }
    : K% D6 u' m2 d) J' P
    & u* [! Q% j4 z" o" ^5 k    public String getPassword() {
    $ O; l1 v! o: g! u! O0 B) k        return password;- `2 D: H$ x/ _  D& S9 ]3 o
        }& e6 A7 D$ E# Z8 z7 w

    3 O5 J/ Y% k. {3 Z- _6 F1 x8 S    public void setPassword(String password) {, _4 O8 a6 v  ?
            this.password = password;
    ' v* H9 r; W( y2 ]$ H8 f$ a' n3 B- c& ?    }
      j4 g/ ^1 }2 U5 f: Q
    ! _7 u: S' g" i2 w; |- e" d- t* E    @Override4 \& b1 q: @% Z; l) q2 F
        public String toString() {3 I- D3 M& n' K& P! ], s
            return "MailConfig{" +3 F* ^( i+ |$ O9 V
                    "host='" + host + '\'' +
    0 [. h: I0 p. y3 o" Q$ {                ", username='" + username + '\'' +
    5 G3 ]" K5 t. i, ?7 r2 x- q$ L                ", password='" + password + '\'' +
    3 ^( H* o, W/ i1 D, ?                '}';1 H& m4 R* W- _  a
        }
    9 D  \& _9 q: M: g}
    - ?2 Y) a; k3 \, R) b" I* a再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中$ U& X2 R. {6 R) s( v

    7 \; P' C8 B3 tpackage com.javacode2018.lesson002.demo18.test2;
    . H* y1 \) k( P- Q2 `. }- `0 S3 U. D8 N6 k8 K4 k0 o$ A
    import java.util.HashMap;% V! h4 V3 g- _* T& s
    import java.util.Map;8 w  e' E" Y) Z4 V0 u& |) x- u

    1 a" @. I2 [- U1 v7 j8 u4 [$ C# vpublic class DbUtil {! p0 O- _4 S0 v
        /**" J" W# T. O6 d5 K) Q5 q1 p( B
         * 模拟从db中获取邮件配置信息
    " R/ [9 U1 d, v% s3 D6 q' b+ m4 A( [     *) Z8 N: M" r) u7 U4 Q! R6 Q
         * @return' ]% e- J5 Y  |' U8 W
         */# L5 a1 d' P/ z2 F6 C/ C) d3 l
        public static Map<String, Object> getMailInfoFromDb() {, O4 E% E- x: [1 v9 G8 y
            Map<String, Object> result = new HashMap<>();2 X6 t# M6 V1 `/ [+ ~% C0 `
            result.put("mail.host", "smtp.qq.com");9 O0 @8 f' e& Q5 L9 h9 \5 W7 f5 B
            result.put("mail.username", "路人");
    # [4 o( Q6 _: S: n: K) ]        result.put("mail.password", "123");
    2 e8 n# ^& `* q$ \% a. k* a        return result;( D, J6 }- z& P7 N' t
        }% ^1 ]) Z1 L7 E2 k, ^2 D
    }
    ; m0 j( m( r& `8 B+ a4 |5 ?来个spring配置类
    7 W/ a+ W3 H+ C8 U. L9 [9 \4 m- g" z
    package com.javacode2018.lesson002.demo18.test2;# ?. g& z  J% n& W8 W+ `4 I$ Z6 c

    0 ]! T8 X) R0 |9 w/ p6 N% Mimport org.springframework.context.annotation.ComponentScan;' y0 \/ b, E8 x" i8 Q
    import org.springframework.context.annotation.Configuration;5 J- d9 }4 h3 {+ `) ~- v& r, Y" c

    9 Z; n% B. u; t: _@Configuration
    : ^& |! F8 {* i8 t; l& c@ComponentScan; p8 a; K+ t# y0 w6 b
    public class MainConfig2 {
    % k, i$ I% |3 X4 n; [}1 j3 H7 d% H/ ^1 q
    下面是重点代码, @* g4 F( W/ n

    . Q- ~8 {/ u" k$ B@Test* r% r, B/ t" p  M  Z" H: l0 R5 b3 N
    public void test2() {
    , {7 \7 T: D  T) @/ y0 b    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();) i* X" H  L- T" i* O
    ; d# c, h$ j9 K; V; @" _0 ?
        /*下面这段是关键 start*/
    ( e/ G+ K/ v1 o$ H    //模拟从db中获取配置信息
    + a3 J) \% V0 L. Y    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();& B. S* Q9 T$ n- `/ Z" q
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ' f3 V# B! K3 |; x& m( ~    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);4 }, c7 t/ f# t# m
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高' @- n6 B: l, g4 b" A% Z' ~2 i
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ; P2 d- D; ]1 b) Q, |# E    /*上面这段是关键 end*/
    " z" t& r7 ~8 _
    9 {  {# J; @5 ]2 q; s    context.register(MainConfig2.class);9 \% }+ t  M7 |! ^1 f
        context.refresh();6 c; W0 {+ @$ V9 V2 ^. u
        MailConfig mailConfig = context.getBean(MailConfig.class);
    + ^! q5 A, C" \/ Q6 a    System.out.println(mailConfig);3 {  K" j3 Q9 N
    }
    & E' }) y" b4 u& J8 w注释比较详细,就不详细解释了。2 n# _2 T% Y: y  n0 G4 N

    # p+ _+ W3 o8 J) ]+ c4 [/ x直接运行,看效果
    3 A, c% l4 ]: o+ D6 h( Z
    2 {8 l+ _6 a& {1 WMailConfig{host='smtp.qq.com', username='路人', password='123'}+ T. b3 x* W$ |5 B0 `
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    . ~% b  I# f5 g' l$ L. P+ j; i6 k- Y, R8 H8 Z, Z0 u
    上面重点是下面这段代码,大家需要理解  U; b7 ~& F, O
    & c; v" X8 g- I6 V& l; b
    /*下面这段是关键 start*/
    + t- G1 F4 {/ g8 g8 ^//模拟从db中获取配置信息
    6 Y3 Y6 k4 }/ K/ E- y- wMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    $ B1 f" B  C+ d. R; P, T" H# @//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    2 F: I; s3 O  c8 i* t  fMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( P% j: l* v3 }+ ^6 c. T0 ]& I- y//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    3 t+ @& M) l# d7 h6 u! @0 {9 Y# Econtext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    9 S4 \+ H: i/ F  U/*上面这段是关键 end*/5 T3 M: g7 F# g# z* V7 ^& |
    咱们继续看下一个问题4 y0 E! x: E' Y: l0 I, Z! m! I( b# K

    8 A- x9 b- X) P4 ~. q0 y6 F0 q5 M如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    $ K5 m% e/ o6 w# n' {6 O$ x4 L' U- L* ?# t( u, O, h/ H. D. G' B
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。2 F2 L- H9 ]$ u! k

    5 z  V2 [( u* |5 \" O8 A实现@Value动态刷新
    ( g# e3 t' \, M! s* H- ^! G
    ' _2 T/ s5 B$ m先了解一个知识点2 U' b( C+ V3 U# i6 v

    2 K+ f+ q9 H# c1 a. r. U这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。3 [/ t) @1 }' q5 E) l  G
    & C( T- a4 ]6 X) \; D; s: j* |
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解6 v( J& Z9 {6 J2 u

    6 U: O6 Q3 }/ S' \! T# jbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:2 C: R3 E# u0 \: s  w$ j! G6 W
      }* h7 a: b1 P; T, f
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    1 S% f3 x, Z2 V这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    * f( P* r& x0 ^( M6 i9 Z% Q( O! X. {3 D" G0 w0 L
    public enum ScopedProxyMode {6 w0 r! e* h$ M5 \1 C7 J
        DEFAULT,
    0 P3 v+ J! }0 ]9 }5 i9 ]    NO,
    + }) S) t6 E$ x3 z4 u; Q, F    INTERFACES,
    - q- n5 V. Y6 _! i" I    TARGET_CLASS;' H' d$ o3 C% I/ F) [; }& W( K
    }0 [1 b& ^3 |: e7 f
    前面3个,不讲了,直接讲最后一个值是干什么的。
    ( {1 e: a- y" s) \. W
    1 e% V2 A% a: r0 d% @; h3 ^当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    3 V3 j% i0 D5 Q0 g0 b8 t
    ' n$ h- |" s% G( g( n6 L( R. l理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    2 e: Q, w8 ^8 C+ l0 i
    % [% p5 r" R' R' }* A3 @$ l自定义一个bean作用域的注解8 j) u2 b' \$ h7 L$ j2 X
    # Q# L4 e4 L: u1 Y
    package com.javacode2018.lesson002.demo18.test3;
      a  P, F4 [! G& {" Z+ F7 T
    ; T, _4 ^; k( u  {/ b/ simport org.springframework.context.annotation.Scope;+ x  M7 ~9 N6 {% c6 B2 S- g+ S
    import org.springframework.context.annotation.ScopedProxyMode;+ ~- L( U8 b4 A. x

    6 E6 N$ I1 [& ^7 ^import java.lang.annotation.*;7 S8 ?! U" W& V! O: G' \

    , H3 J& O9 w' }6 Q@Target({ElementType.TYPE, ElementType.METHOD})
    8 [3 h4 n0 ^, q@Retention(RetentionPolicy.RUNTIME)7 Q, ~9 O' v, q# J0 c) ?' B; g0 {
    @Documented
    4 S, U' P4 D% r/ e( {- v@Scope(BeanMyScope.SCOPE_MY) //@1
      f. s( X1 C6 ppublic @interface MyScope {5 c; s4 Y1 i% d' [% u) t
        /**
    1 ~, {7 Y3 Z, r9 n- a     * @see Scope#proxyMode()
    0 `0 Y3 \+ S, u/ \+ Y, D     *// x  a' \* b7 g% f
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
      t' ]% z; q3 D9 i* R) S}
    # U, X+ c; C- Q# ~" X6 [7 j1 G@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。' R$ H+ L) l  g9 ]4 x" X  Z/ Q; ?

    9 `9 P& t0 s/ E2 g# d@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS; {7 f& p$ X8 @

    : O6 P" E  a8 ^9 a- L' J+ A@MyScope注解对应的Scope实现如下& v8 G0 a' q# P8 {) K' H) o/ x% s
    ; P8 s3 U" A; g$ v  v. Y% [7 \8 W
    package com.javacode2018.lesson002.demo18.test3;0 P2 b5 V3 n2 H& R& n

    ' R: p/ L' G1 Q; M! ?! x7 `1 J( \import org.springframework.beans.factory.ObjectFactory;; D% W7 L* m. Y6 t2 G8 K5 k3 B
    import org.springframework.beans.factory.config.Scope;
    8 D0 C  ^5 X2 V0 M% x4 }( m! Qimport org.springframework.lang.Nullable;' X* g* v" Z+ `; K; l+ D
    * j* A( V% z/ Z
    /**4 ?; `( H' |: A% \# z5 G0 E
    * @see MyScope 作用域的实现
    ( G7 `  D" [# s; \- R */
    2 o' d3 |7 S' P0 U. rpublic class BeanMyScope implements Scope {0 ]/ [. h8 z" h+ Z4 _
    + C+ S; |1 f- r: i2 g' w
        public static final String SCOPE_MY = "my"; //@1
    6 W3 `7 N9 y% K. y" U+ i; ~, e( K6 o  J. O& ~9 K: ^( L/ f
        @Override
    9 _% d3 \& }+ ]) E: u5 Y    public Object get(String name, ObjectFactory<?> objectFactory) {
    " S. T" U3 J1 z- }6 V+ a' b        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2: P6 m, _6 u6 j$ M+ }
            return objectFactory.getObject(); //@36 r9 A% T) n. R1 s
        }1 @* A% D  W" s
    ' M5 Q5 x, m& T0 z! _
        @Nullable! a( p+ }0 M6 R
        @Override
    - Q% ~0 Q' x7 @  |* t/ u: z* ]    public Object remove(String name) {
    0 d- H  J3 ^; \+ N        return null;
    - P# r, d/ r; x7 x$ X, ?    }
    ! |( J4 ~% _5 P& i, n
    $ m  O# n( s/ @    @Override* k4 G$ t. M! d( I8 {
        public void registerDestructionCallback(String name, Runnable callback) {
    . D: M2 n, Q2 Y) l* I( `3 c- d$ F: m. A' Q$ c5 g7 g! F; Q
        }( z; s3 Y& e1 ~; o! w; I* x
    ; k8 k8 D' `7 n% o
        @Nullable
    ) X1 m6 m$ P  d. f" x6 i    @Override) r3 ~) w) x) n. ]) b
        public Object resolveContextualObject(String key) {
    9 K$ N! x- P* A* q6 |0 q4 p        return null;
    5 M. N0 I# t) ~# O4 C    }8 |# R4 X; H, n# W- h# n- t2 C

    . n$ {7 U4 P/ ]    @Nullable
    ! U3 }) U$ ]# _8 i+ @    @Override1 a; A. [3 S9 D4 {
        public String getConversationId() {
      X7 k" A& Z$ M) @+ \2 V        return null;
    & e  D) S, S( ]* [9 h: B  o  g    }. N/ X4 a* l1 ]8 b" R1 o, z2 X3 M
    }( g5 R+ b! s  R- u' J4 s* K
    @1:定义了一个常量,作为作用域的值; _5 `; l; c- r2 Y1 q# c! n
    + t, o/ r8 g0 v( B
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果0 [8 q7 s) y3 q! Y# g

    ( W- o: b! c7 T& \3 S$ d& b( `- f@3:通过objectFactory.getObject()获取bean实例返回。
    ! I6 [; \: p0 I* t
    - d# i2 v8 h% @% I! Q下面来创建个类,作用域为上面自定义的作用域
    " G$ M- W# n' |
      e5 B3 p7 X6 o0 r. l8 ^9 }package com.javacode2018.lesson002.demo18.test3;
    * r1 a+ {" n! V0 |! N
    ; L0 s$ K2 N  n& {1 m3 u; |) \import org.springframework.stereotype.Component;
    : Q2 f) h  a( G' [3 q+ Q4 x$ L/ L& e6 o$ x/ \
    import java.util.UUID;3 }3 G( ?0 V4 A' U# b: \4 o
    - Q$ k8 l: v6 T2 g. f# \7 {9 J( e
    @Component9 c. {1 F. V3 ]
    @MyScope //@1 + {1 N. R# T! j4 b
    public class User {
    3 V- {9 ]6 [# ^7 S
      m0 `* ^& x. ^6 p    private String username;
    % |4 [. E$ {9 C3 f( w, q; Z: G7 H. \) N6 k" b
        public User() { " _( A9 l# N& B$ U
            System.out.println("---------创建User对象" + this); //@2( }1 J& @* P% q2 N) p: X" }
            this.username = UUID.randomUUID().toString(); //@3
    . A/ m' z1 h8 h$ @) d/ W! d    }4 m4 F+ h8 {! d

    , W+ \6 H" r8 N" k: u$ r; M/ q    public String getUsername() {
    4 B) j$ P+ v* s- J- r) `! W        return username;1 v) f9 j3 M+ c- f1 C
        }
    3 ~) ]6 @8 w, U7 \% o# E  I! B( {* `; \+ G
        public void setUsername(String username) {5 d( N5 R& |1 K9 r8 K
            this.username = username;( @& Z7 H* {6 k% l1 N! H- |3 |- V
        }( c9 |% |- ]1 `4 C2 ^5 k# E

    + X' m4 ~$ h5 m8 R. v: w}
    3 u0 z) t0 M$ V+ U2 w7 C  J. G" @@1:使用了自定义的作用域@MyScope
    / b5 V2 q6 P: w& [& e
    ; r( e- K0 W0 F$ d@2:构造函数中输出一行日志3 K4 c  }/ Z9 e8 R1 m9 [( ]/ t0 _& v

    : _4 R- ~6 \+ G@3:给username赋值,通过uuid随机生成了一个5 X. H$ l; T5 k3 x; T8 S+ F( C' ?5 p
    ( H! F6 @* }- z; {/ q, H
    来个spring配置类,加载上面@Compontent标注的组件$ o# v$ C1 A% D* m" i+ {7 _# x" Y

    * V0 o# C$ \2 a# C2 x$ lpackage com.javacode2018.lesson002.demo18.test3;- s5 x2 f0 ]. n# K) m

    4 r; ~. q# e" c& cimport org.springframework.context.annotation.ComponentScan;
    4 f% K4 [  P1 U/ @import org.springframework.context.annotation.Configuration;
    " T- v( X6 o8 a- N6 P
    : s/ x, `6 }: C5 Q@ComponentScan9 K! c8 Y/ o! s  B( [: l+ W
    @Configuration) s( i. X) t% P, |% i% j
    public class MainConfig3 {
    / \+ m4 B; U) i9 e}
    , M! k4 T2 {+ p  D9 Y" t下面重点来了,测试用例
    % }( ?. `, A- ]* B8 w* k$ p2 ~2 P- m, k  w
    @Test
    , s2 E/ v4 u' F  H0 npublic void test3() throws InterruptedException {7 c% q+ z: Q2 C6 K
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();! T% N. F, M" v1 b% ^
        //将自定义作用域注册到spring容器中
    1 U. d  Z) _2 G( f4 b    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1. ?' A7 T8 q1 k9 v1 Y
        context.register(MainConfig3.class);3 F9 u9 v$ B$ d, h- B' c
        context.refresh();
    7 n& a7 K2 h* `( p9 n7 {' n" K7 r+ W2 a/ E" `
        System.out.println("从容器中获取User对象");4 h& N( H. n  [9 n1 ?! \7 C
        User user = context.getBean(User.class); //@2$ r' ~6 E( a/ z1 e& z
        System.out.println("user对象的class为:" + user.getClass()); //@3
    6 q% p1 P! x' f0 _' p2 C0 F4 _9 }6 {# J2 \9 W; D0 X7 |
        System.out.println("多次调用user的getUsername感受一下效果\n");
    * ^* ~1 C8 ~5 e% a; x+ Q. K    for (int i = 1; i <= 3; i++) {. Z% i0 Y1 M# q. I
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));$ u) A1 w  n1 E8 D
            System.out.println(user.getUsername());
    4 H/ z* x0 u) ~. e8 {        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));7 H# B3 m9 ^  N0 L4 }: a
        }* X. E: h6 |4 ]: s' k7 F
    }
      R( ?" K7 r4 c$ U@1:将自定义作用域注册到spring容器中
    # N3 A. ^' p, p3 [! G6 G
    . V+ c. H/ K+ p@2:从容器中获取User对应的bean4 {4 O% D! I% I' v7 c2 m0 a" d" ^
    ( I% V8 P% r8 `1 l
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    9 T" K2 }1 t0 p5 D7 {( e9 J' E4 V% @" G; N
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。4 }2 u7 ]1 j( c
    $ F( T" M$ D) L
    见证奇迹的时候到了,运行输出, D4 N/ T, O  i* i1 }1 @8 F) \4 q
    3 B% _. m4 I: W) a
    从容器中获取User对象" Q+ v  `9 _7 R# Y/ B8 Y1 D4 a
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331270 f6 o( ~) I: [( f# A
    多次调用user的getUsername感受一下效果
    7 A" E- \- j' _2 k3 p9 X7 N) c  j+ ]$ e8 T4 L7 a
    ********
    ! h- a* J6 L* M6 S) S% z5 [第1次开始调用getUsername
    1 P4 e6 Q' V  E7 G; xBeanMyScope >>>>>>>>> get:scopedTarget.user
    2 a+ S; N. U1 F; V3 C3 w---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4; J" B5 _$ N0 K
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    # r& j% Y" f. S第1次调用getUsername结束4 J( M4 S9 [$ E
    ********
    + \' |0 u  l) S1 r, B+ m; \  q* \' T- T$ m) ?) a$ t5 N5 u% O0 f
    ********: Q0 b; o- J$ [- l4 Y% A
    第2次开始调用getUsername
    2 W0 M5 E& h3 N) I  i( ]; i% Q  VBeanMyScope >>>>>>>>> get:scopedTarget.user/ I+ T; e% N4 l4 J5 r: L
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b! U% H7 Q0 P/ `+ I+ v% v
    01d67154-95f6-44bb-93ab-05a34abdf51f  Y, M- l' Y; A4 |  v' U
    第2次调用getUsername结束: F9 Y. P9 S2 I  W7 U& v& U
    ********
    - U9 W& T3 ^# Y. _0 v5 i; y
    8 h9 a/ o1 A4 a5 ^********
    " M0 b0 l9 y3 T: K6 m! k! \$ W) ]第3次开始调用getUsername5 {  f  d3 C7 j" R/ [2 T! g
    BeanMyScope >>>>>>>>> get:scopedTarget.user: B5 ?% K- K$ O  {/ E
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d157 R& J+ \0 J) {. O* x
    76d0e86f-8331-4303-aac7-4acce0b258b8
    / c+ L5 T9 r9 [: r5 \第3次调用getUsername结束1 D' h, ?0 y: `( l" a+ n, s( \% _
    ********. a2 o, v7 t2 A  y/ M
    从输出的前2行可以看出:
    $ _6 H7 S! b; N- u3 b" |, W3 t( n' T
    8 |' ^# F9 E0 b7 e调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象$ Q; W% ]# B* P5 r5 s5 Z% H

    3 P# h& G5 K# D& D0 v第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    8 {* t* }/ B9 R9 T6 G7 @+ I2 A* S" I" _: t
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。* l2 ~' I, K! r( }7 ~7 H& i; k" V

    ; u5 {. |$ n+ k# j# e: h, i; C8 X通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。9 R* q: x( f* W' O
    8 ]3 g) w1 }, y% j' _1 H! h
    动态刷新@Value具体实现8 i. \0 D: i2 Q; F9 o. `6 F

    & l9 j# Z  T, ?2 D" O1 _那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    / B& ]0 s6 k; N/ d  G) g% w, x0 Q& n9 I
    先来自定义一个Scope:RefreshScope
    $ @. O9 R' X0 t7 I+ @: o7 v; Y; k5 A' W+ V# H- J- ~5 u
    package com.javacode2018.lesson002.demo18.test4;6 c+ V7 c, V8 }: G4 `* D0 F
    6 b3 z/ ^$ Q1 J, p' r
    import org.springframework.context.annotation.Scope;
    7 |/ h5 V9 W' c% w# Yimport org.springframework.context.annotation.ScopedProxyMode;. b$ e# j; J( P& z7 R- w* [0 j
    + W3 f' }# |8 O4 f7 G* K
    import java.lang.annotation.*;
    7 [+ H$ k4 w- g& n1 E0 e6 I$ a; Y0 h" Y/ |2 }2 h
    @Target({ElementType.TYPE, ElementType.METHOD})* X9 f1 p# ~% R, P' }1 w
    @Retention(RetentionPolicy.RUNTIME)
      U2 E" _* b2 h; G# n@Scope(BeanRefreshScope.SCOPE_REFRESH)
    2 x7 S, K3 R; ~) a& @4 L@Documented
    ) R3 U% d- b) Q9 z, [" K) X# b: ^public @interface RefreshScope {/ `- M+ r# ?! ?
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@12 |# e2 \7 O9 `$ w3 {9 x
    }) k0 R( i; A1 c: |' _8 D
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    1 s+ X3 u# `. Z' z7 F5 x: k8 K+ _$ h0 [: r. K
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    ; h: C* v  x0 N* P' Z* S9 V) B& t3 |/ j
    这个自定义Scope对应的解析类
    0 n/ R5 z/ b' y9 R" F' N6 s  n- S6 i1 }$ r: b6 m5 W4 r5 V
    下面类中有几个无关的方法去掉了,可以忽略3 h  w" f4 e  Z4 r

    9 s. v- E* \2 f6 Z% z, W, J5 Spackage com.javacode2018.lesson002.demo18.test4;4 X" b% V( w% c

    4 x; M# w" _) \! H
    $ E3 L! o! z* Y4 Oimport org.springframework.beans.factory.ObjectFactory;
    4 K3 E4 t* {& O/ Vimport org.springframework.beans.factory.config.Scope;
    ( C; K, G3 P+ V: W7 x+ e' timport org.springframework.lang.Nullable;
    " ?: l; I$ g7 f) t' N% s5 h. v" @3 Q. E) S4 `. r8 k
    import java.util.concurrent.ConcurrentHashMap;
      @, m9 s! {0 {3 t/ z" e/ |3 O, x
    & T/ {# p/ q; q$ e0 ]# @0 f4 r* Lpublic class BeanRefreshScope implements Scope {6 _1 s2 J3 ?" z! G+ D7 [1 n. `
    0 s6 W* S' K5 q1 I' ?- W' u* g
        public static final String SCOPE_REFRESH = "refresh";
    ( p  ~  v3 F4 e( u8 C/ B+ \; L9 f$ Y2 b9 a: O. a4 _! }* o
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();) X" C4 {1 A' Z2 x0 \

    " [- H0 k. B/ O9 K# m  r9 V1 d    //来个map用来缓存bean
    & ]! C/ X  U( R+ R    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@19 {: n  v( V% o! Q. N6 N

    ; n4 t0 _5 ?/ y' N. [! ^* L    private BeanRefreshScope() {
    + }  F+ {1 Q( A& F# o5 F    }- l* ]' k2 K# ]& M8 a

    , n1 d, W5 k9 g/ t1 ~    public static BeanRefreshScope getInstance() {( A: I  p5 d6 v
            return INSTANCE;$ Z0 ]: m" H: U1 \# }
        }* U+ ?% v8 b0 G# y9 d

    6 E2 R0 l% H, E4 |% X/ @    /**# E/ J5 d" t% s; b" q
         * 清理当前. b- B' T0 ?" K+ `7 L0 A
         */
    4 H3 w% g! ^, X0 ~! j/ J    public static void clean() {- j% s9 a1 m( _9 e' e
            INSTANCE.beanMap.clear();7 h0 K; ^0 q3 _
        }+ l" m7 y5 ^1 C9 }+ Q' ]

    $ {, T# S( @7 b! ]    @Override: U& d( h0 _, k& \
        public Object get(String name, ObjectFactory<?> objectFactory) {9 E' W3 M# y4 G& P/ V* d( q
            Object bean = beanMap.get(name);, o/ U! i  v7 z0 z- z; A4 L
            if (bean == null) {
    7 T* s# o4 z. J) x" B! o8 K            bean = objectFactory.getObject();6 ^1 _4 a: c! F; B) m
                beanMap.put(name, bean);7 @6 b1 q) X. B8 _4 s; J, P
            }
    ; L8 s( ~" W( m, }9 e+ p$ r1 ~6 A        return bean;0 K/ B( z: H* n+ Z+ c, g
        }3 m" o# x8 W+ @1 P. P! l
    + c! W( f5 K; J
    }- J$ c' t& H- W- u4 v- A
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中: U: s4 v9 a. `

    & M, m: B' o; d7 D4 h5 U上面的clean方法用来清理beanMap中当前已缓存的所有bean0 C2 D- V: u" T$ V! z4 |8 n! z; v
    % O  a7 ~6 u/ V, u, t: w
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope+ k# [% H6 _5 H$ C* |& P  c+ c- r
    ! h9 z4 d( e; `1 G5 G
    package com.javacode2018.lesson002.demo18.test4;
    * a- B( E9 R5 z; O" l) s+ i6 f7 y! B" V" Z
    import org.springframework.beans.factory.annotation.Value;
    * {* F- I- v8 n( C5 X0 w; {% Yimport org.springframework.stereotype.Component;
    8 x% r, k9 J5 i$ w" [
    5 G4 J& I* q2 |- H5 \  J/**. z  `6 _3 T- Z0 d8 x6 c3 [1 j
    * 邮件配置信息
    7 ]* D7 N' w# _; N* V */
    ' C5 `  B. y# d/ [1 @@Component
    + i- B3 c/ g" L7 x. a0 ^, u1 ^& ?+ a@RefreshScope //@1
    ; i- u% A% P. @: p% ]( C' L6 tpublic class MailConfig {+ Z3 S3 |% f0 y4 e
    ) D( T: D" {, Q5 `- h& V% C" }
        @Value("${mail.username}") //@20 A" Y& I( F2 v* e& k  t& I
        private String username;. u: W: R# \/ \3 z  w. {: C3 O
    # s" U! p& r5 B* T- W
        public String getUsername() {
    # q4 b4 Q* {2 B& X        return username;
    & {9 Y+ [* F8 c# T' c1 f    }" I: A1 ]! C) E9 a- j( e6 J$ b

    2 A9 n8 @& |& F4 a% L" u# V3 t    public void setUsername(String username) {
    / U8 o  ]& L( d0 o        this.username = username;
    3 e$ x2 |& e& B8 `; E    }
    " |% A3 i. e: k& T5 h+ C3 G% i7 g& V7 u+ ]  l3 p7 h+ D7 H- b
        @Override' M/ u2 E" ~& S, J* _
        public String toString() {; I8 A3 b4 ~9 f* _  C+ K
            return "MailConfig{" +
    , `. I9 M0 `  m% g; m! |% T5 k                "username='" + username + '\'' +8 r3 `$ G9 Q9 e5 C7 g9 K
                    '}';7 P7 X' J- W( Q& s2 J6 f
        }
    0 n3 C6 _7 Z+ U" y0 h}3 h/ W$ J: K1 V: t1 ~! u. V
    @1:使用了自定义的作用域@RefreshScope7 E' W# ]" H- T7 V) j  a# s" _( H1 n
    7 q& u, ^' K- S. F. O# M# L1 d* w7 ^
    @2:通过@Value注入mail.username对一个的值  m* _8 c  a; K3 T+ E: g
    ! X/ P6 h1 _$ A: m- Z" e. s3 z1 x
    重写了toString方法,一会测试时候可以看效果。
    ) K: j- E4 |1 T& T1 u  k5 ^' d' u7 o/ D5 K* c0 G0 j' T
    再来个普通的bean,内部会注入MailConfig
    & E/ m3 E7 r6 T! l- `. N% r* k" M3 z5 j
    package com.javacode2018.lesson002.demo18.test4;/ f) ^8 }7 Q# ^/ ]# H- B

    2 K  T2 J. F8 j2 X# [import org.springframework.beans.factory.annotation.Autowired;
      o& Q! t0 g4 D, K( Q/ P$ k! Jimport org.springframework.stereotype.Component;
    , o, X1 Y7 h: V$ Q+ X1 [, X
    9 Z; f, r6 r5 Z" y5 u; v( I@Component
    : C0 w2 T  [4 q' wpublic class MailService {  z# ~- _7 z/ D" y
        @Autowired
    % W) x' k  T3 j+ e& Z/ L. o    private MailConfig mailConfig;
    $ R% s9 N+ ]' t, J6 M6 D2 ?- P8 _4 P1 Y
        @Override$ X) F( e2 p; J6 H
        public String toString() {4 n, q0 B& p4 I' I4 z
            return "MailService{" +# n9 @9 F/ S. d, X& d) \
                    "mailConfig=" + mailConfig +
    ( L$ }; ~2 g" x( x7 C7 K                '}';& B) e- z9 r( [( S) A
        }
    / u( S: m" {& e! c' O1 h}: P: L  ~2 `* u1 [- s2 }( {
    代码比较简单,重写了toString方法,一会测试时候可以看效果。& Y. f" t: {0 m6 ^1 j( M

    2 o% y( k% j3 I/ E4 G& m- k$ n来个类,用来从db中获取邮件配置信息+ q' B5 G% U, D9 ~

    2 }* N; G4 p- I: W, epackage com.javacode2018.lesson002.demo18.test4;, Q0 W) n! D- R& i% y  Q8 p

    6 q8 \. V6 {2 Y. Z7 E9 S4 aimport java.util.HashMap;* g7 X7 s  ~0 M  p6 Z
    import java.util.Map;% {( Q- I6 p& B' d# j4 E; M+ W
    import java.util.UUID;
    7 }& t& S' W- z8 N; Q* y/ x) V) f8 ]4 `
    public class DbUtil {
    2 t' }* E/ ?* \9 `% m    /**$ A( S3 m) u$ H$ N; g
         * 模拟从db中获取邮件配置信息
    $ r( ~$ g  |" ~/ H: P# C% l$ `# j& l$ }     *( m5 f" y' M6 Q% t2 v* l4 |
         * @return- L4 o$ V; O/ \4 b( ~/ _
         */
    + I8 Z% {3 Q6 l0 A) Y- X    public static Map<String, Object> getMailInfoFromDb() {, p' k- o" @5 B$ d& t! e. W
            Map<String, Object> result = new HashMap<>();7 Q& v5 P+ @! L3 U5 @0 ?
            result.put("mail.username", UUID.randomUUID().toString());
    7 M/ l: t% d6 j2 N/ y        return result;
    + o% R# V5 G8 Y; U- D0 o    }
    : q) h/ E9 m5 e: N0 k}* P1 T' Y& s2 V7 \6 P7 O  r% \; X
    来个spring配置类,扫描加载上面的组件
    7 S/ M- l/ o& S6 i
    ( \7 ^5 L- B/ P% q- I* M6 ~# G0 rpackage com.javacode2018.lesson002.demo18.test4;
    $ D$ F5 j" k) {1 z% t" t: L, A# u9 Z) i8 d
    import org.springframework.context.annotation.ComponentScan;
    4 a( o* ^  j5 eimport org.springframework.context.annotation.Configuration;
    1 D% D8 W. G) T, w$ \
    3 f0 l( t1 @. _7 l# ~, x& J% y@Configuration- H- ^% M, ^0 ?7 @4 D' A
    @ComponentScan
    , n* p8 P" r4 ~2 U4 f3 Ppublic class MainConfig4 {
    7 M8 ?: c4 I- s( X) ~}
    % j3 Z, i$ T* ^* |6 C来个工具类$ Q) y- }6 `2 Z! y! k  c

    - |0 M5 s; h, c9 P% y7 T内部有2个方法,如下:% {$ e/ d( R/ @: R* g9 Z$ r4 b

    : C* m- S" I* Q5 o. \package com.javacode2018.lesson002.demo18.test4;2 f/ S5 k# ?5 ~; h% ^9 X: J. p7 M
    3 i( a; J4 A7 U! q
    import org.springframework.context.support.AbstractApplicationContext;% Z: r0 z' q9 K' Z( \% S' t( @
    import org.springframework.core.env.MapPropertySource;
    2 p8 Q# @3 W2 l: H) g5 k
      j; p3 e" i1 v* p7 Y9 b  }import java.util.Map;3 ]. m" q3 y/ `, x' z
    5 {1 {! Z9 y% X6 ]& k. ]/ t9 e
    public class RefreshConfigUtil {6 S: `( f6 A0 x, K. D! D
        /**2 W+ k% h3 C' `5 @3 C, Q$ e! r
         * 模拟改变数据库中都配置信息+ Q( r# z' T" l
         */- u& b" f( T6 G3 q7 n( Y; N* X
        public static void updateDbConfig(AbstractApplicationContext context) {- `: A. v- x2 N# [* `
            //更新context中的mailPropertySource配置信息
    + ]3 F1 g, ^( Z2 H6 Y        refreshMailPropertySource(context);
    4 A1 J6 V) P( a* o* t/ I8 K# e8 ?" S: \4 e8 j) w5 I* @
            //清空BeanRefreshScope中所有bean的缓存
    ( V4 s  P2 L; Q+ E3 L3 ]! k0 \        BeanRefreshScope.getInstance().clean();" w+ B, k# Z+ ?% P1 M2 r
        }* h6 P7 V/ c. J; X5 A0 S
    & B: h5 i8 Y! N0 Z
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    . {- c) K8 z2 x% ]        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    $ g4 f+ }% e+ R$ a9 L2 W6 u8 `' u( q' U        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 P8 P. b4 D, @
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);9 {0 L# ~; n1 {8 M2 ^/ ~
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    * c4 R) X/ {2 K3 m# E1 F0 c% b    }& B3 D! ~4 E! H! V

    $ f# Y8 h" y& N+ B# P. Q: K}
    1 k: k. `$ {* n; c* i' f. I% FupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    6 a) V; Q- }  D7 I+ `% c9 q
    % ^4 M* S% o- l& D0 Z- w+ EBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。6 @2 v- Q; l" _# I0 U$ ^
    % A* p' ~; }6 S( n3 I8 d% W
    来个测试用例
    . j3 o5 t  _- h$ p( q8 K4 U0 P0 N% o4 [9 P! g7 F5 z6 Q
    @Test
    ! p$ ?( H+ |) `- y. o9 _public void test4() throws InterruptedException {
    ) C5 [+ }& q, S    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 M# E( t' A5 i$ c& f5 ^    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    , A* r) y! W" v$ s' f    context.register(MainConfig4.class);/ h8 U; F7 D: {! a; \
        //刷新mail的配置到Environment
    4 q' }# g& F/ m4 |    RefreshConfigUtil.refreshMailPropertySource(context);
    ( M; w1 M3 T5 z/ J# B    context.refresh();. }( \- M& T; C# c

    8 H: f8 ~& e$ e; |    MailService mailService = context.getBean(MailService.class);
    9 ~* F* c" N  k' g0 p7 \$ A    System.out.println("配置未更新的情况下,输出3次");
    2 J4 T* M, H3 @- u# ~8 ]9 P    for (int i = 0; i < 3; i++) { //@1
    1 n2 }. t, _5 f( M( T" k! i        System.out.println(mailService);* {" R" I  |; m2 g* y
            TimeUnit.MILLISECONDS.sleep(200);
    , d7 ], z+ M# v4 s, Y* v    }2 H9 t$ Z# @9 P* U

    * r" W' e7 u- |/ C    System.out.println("模拟3次更新配置效果");
    7 o: h. z6 X* X( u' j5 a- n    for (int i = 0; i < 3; i++) { //@29 V6 ]& ~6 ]0 F8 v. [' x
            RefreshConfigUtil.updateDbConfig(context); //@3% Y9 N' f' ?2 B
            System.out.println(mailService);) D; E- X, W( v
            TimeUnit.MILLISECONDS.sleep(200);9 c% h) G( ^/ H* }: s
        }. O% J' l" J* n# J
    }
    * ?% B2 Z) k7 j5 E! K+ M2 ^# ]5 V@1:循环3次,输出mailService的信息
    * b; q+ L% D" L
    4 {% u9 b, H0 m$ {2 |# `7 w@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    & W4 |; W: W2 h! g1 E9 Z! ~$ O" T( W  Q: P0 R  `1 b: X& o
    见证奇迹的时刻,来看效果
    . F) [$ m) K: c  Z% A! L; ?1 f+ `% z& Q( E& L/ i1 v4 l7 e) ~# ?. `
    配置未更新的情况下,输出3次" u: X/ ^+ M2 q: \# Z- G: @: p
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}! Q4 @! [' q, _2 s
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ [( v& c/ i1 ?9 J+ y) J7 h$ c3 l
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    4 S: q$ q  F9 v模拟3次更新配置效果+ G: e* I- h) N7 k
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}- O( q* K) M9 I1 f
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}. w4 C% Q" g% r# o1 y9 n& C0 l5 C
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    . h0 E3 m5 t7 R1 o& d上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    3 t# _; p8 b# X; E# S. n+ ], P! Z6 U. c! e- y/ s
    小结  V& ?5 o- L, z. H/ ^( p, D; o% `
    " T6 s' [* {% x3 N' V3 P( D2 e
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    ' h; f. ~$ T- D5 s0 l$ K0 ]! N; K+ f% e- D5 B. F
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    ' j6 B8 A. L2 Y" `8 _) W4 o/ q/ F- Z- x( ?, ]/ d4 K) c& R
    总结2 H7 t2 c0 }, H  S0 y

    3 k. x! W; }# w/ V7 p' D" a本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    ; ?5 c* t/ a$ K2 U) N- l, R) n& |2 D  G" _$ @* r
    案例源码
    / T% r+ B0 G( l% i6 u
    # Q' I* E" z4 r2 C# n6 m& [https://gitee.com/javacode2018/spring-series& T/ U8 a" b- V! _
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。5 v8 T, ]: K" {$ l

      i4 C6 z% X" B0 P! K! F) ~Spring系列
    ; I/ o( Y3 f& M1 Y0 ~- R
    & @4 u; l" s- lSpring系列第1篇:为何要学spring?
    % e4 W3 @  E; {- W, B) D
    8 J2 a( c: l* A# cSpring系列第2篇:控制反转(IoC)与依赖注入(DI)2 U- r( P; J( ?+ Q/ C+ \

    0 J: z+ k  b3 B; jSpring系列第3篇:Spring容器基本使用及原理
    - v0 l0 S# c" \* z- P0 D. s2 X- Z& E5 s) [
    Spring系列第4篇:xml中bean定义详解(-)
    % M1 U4 k8 W; p" L! O: n2 c- X9 o4 B& T% s
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    9 |  Q+ I+ @2 d
    0 k' \3 ~; W5 A; l) k3 gSpring系列第6篇:玩转bean scope,避免跳坑里!
    0 o! W# i. `5 |/ `* E
    ( h  @: r3 s, C2 ^Spring系列第7篇:依赖注入之手动注入
    2 Z6 Q7 h! j* I! P4 T! |" L* c% Q4 F( s+ X7 n' e
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    - a* ^' v/ k8 |+ ~6 H) C3 ^/ }+ k, N$ G5 C$ B( }/ [1 B8 c
    Spring系列第9篇:depend-on到底是干什么的?
    ' s+ y+ ^8 f% ^7 P
    7 m* E; U0 Y4 S; Y3 J/ x; E4 iSpring系列第10篇:primary可以解决什么问题?# e5 j' b5 r$ m: n7 l: W) ?

    9 J/ S5 v5 C6 ?6 K! L& t, ISpring系列第11篇:bean中的autowire-candidate又是干什么的?2 `4 A, B5 P+ n- B1 ^

    ( q9 f) Q+ b* t7 f" T( s& ESpring系列第12篇:lazy-init:bean延迟初始化* L% ?9 d; I- j7 ]5 c

    4 c( T. V  v  r4 y9 `5 y" fSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    $ x7 ]" R  p% g2 [, B% H  N% b5 N0 P0 a+ J$ B3 f
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    1 G0 b" w1 z! e  ^
    ; v! K, i% U" JSpring系列第15篇:代理详解(Java动态代理&cglib代理)?7 e9 a- i  C- a  O6 T3 Q& ~

    4 x! L/ H5 h7 L( L, ~/ sSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识). @8 N  b  t9 z7 h( W1 R
    : S0 I4 O: _: k# C: F
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)1 b4 H! h/ E7 S; ^- u
    2 q% V9 ?5 K$ z; m/ V
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)  T9 N8 ?1 {' ~0 r" l# q
    7 @7 k; s$ h( R9 T
    Spring系列第18篇:@import详解(bean批量注册)
    ' ~- w0 Y: P4 q- p9 Y9 p4 C
    4 }# k6 \1 l( Z+ y; _7 a% {" ySpring系列第20篇:@Conditional通过条件来控制bean的注册
    - b/ Y+ ]* h; j" k
    : b& u$ C7 t* h7 ~* [3 E0 y0 D8 oSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    : F& r8 V( \1 G% E8 r6 R" e5 t! c. Y) P% u
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    * z" z0 |) M* ?/ L6 f0 k. b/ I4 W: [( ^, S2 k7 p% F3 `
    Spring系列第23篇:Bean生命周期详解  `; i" B& `+ X" u. [% c

    $ e5 C7 Q4 {* j% s3 lSpring系列第24篇:父子容器详解' d8 N& s! c, M- i  x( c

    6 A6 I+ N" o5 }; ~: m6 }更多好文章! b  o% v$ S$ `/ h8 C
    + ]! m7 x  H6 f- X
    Java高并发系列(共34篇)* e5 b7 u8 E) i! J& a3 P! d. _6 m
    # G- u0 Q, q7 t7 u$ r
    MySql高手系列(共27篇)9 _: G. M% t0 h) U. ~
      |7 ?  ?1 W5 C: ~/ F  \0 K
    Maven高手系列(共10篇)
    ( x( A: T8 F6 e
    & N- O6 f6 ^/ p' N. S+ v. a3 ^Mybatis系列(共12篇)
    : M$ t* H+ k* J' a7 r4 L0 G" a  P. B% @1 v; S6 k% |
    聊聊db和缓存一致性常见的实现方式, a3 Z& A* D+ n4 Y2 [( z
    6 _9 j4 {* g* g+ l! r
    接口幂等性这么重要,它是什么?怎么实现?
    ) U. f& B: I; O) P
    9 h1 I( e0 K" d* W, m; u6 l泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!# y6 o. _& E' C4 T- t8 Q
    ————————————————; P: ^. H# v; u. y( q
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    + G: \& b0 k5 Q0 o# \原文链接:https://blog.csdn.net/likun557/article/details/1056487570 B) j. W) P; {8 `* Z

    # s9 j+ `. h* e) |% I  h# G. C1 A" V& W3 c! P* D# D" 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-4-22 22:19 , Processed in 0.490206 second(s), 51 queries .

    回顶部