QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5532|回复: 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!3 H% X' ~& Z6 W; S: O; o5 T! t& t
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!; D/ y: @, ?( z) ]
    , e* x$ C: Z: {/ h" w
    面试官:Spring中的@Value用过么,介绍一下
    0 G% m0 s: u$ E; l6 u8 n( X: y! s4 S& E/ X' {" s
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中; j$ Z8 C& s# z% [6 d
    3 D! [) q# ^! j
    面试官:那就是说@Value的数据来源于配置文件了?) Z6 ^  T( A; w

    - T" ?/ _* l; K0 Z# s我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置) R0 V1 @: i0 `6 O9 l: }

    ) `. W* {" c2 U8 P6 w2 T+ |  E2 e面试官:@Value数据来源还有其他方式么?
    . T/ n9 ]# ^7 d. f; l3 b
    7 m( C) B6 R0 ^1 \8 o我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    ; Y' ~) K% q, }  C8 c0 ?* |
    9 `1 ^$ [0 ^5 r2 A& ~' w! m1 ?面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    " t, _8 P( B  b0 P2 ]- {( L
    $ J# s+ v) N: Q- k* p8 W+ R我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧$ _! G- ?/ a# i7 f+ m8 j- J8 c

    - x3 O) |# y2 ~# y- r  ~/ a7 R面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?1 |! q  m$ z0 I  |- e7 a
    9 [$ _; U; {$ f8 _0 m+ z
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能! A) n4 s+ B3 S0 `
    3 W! g' T3 d: u$ ^1 h$ T
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?0 F0 g% Z: f3 L: U+ v

    / r7 E( Y+ ^4 u% N我:嗯。。。这个之前看过一点,不过没有看懂
    1 h& w3 R$ B* q- B4 M" _) l; E4 p; j6 L# D7 o2 h
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?% ~& t" d; u: x; O  B

    0 v+ t  x6 s4 }, F0 z3 Y3 K我:3万吧
    ) d0 f: ]# \7 {7 ^
    / Z0 C$ L1 m) U面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?# h; E3 {$ b/ V9 Q, D/ _
    $ \$ g% a: S7 p) o3 S0 E. E
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    - P& N& D9 d" L6 p) R6 ^, P9 w$ L9 e, h7 p
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!2 I9 S; \6 T  u
    ) a+ {# P* }$ |+ W
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    $ O* p1 c$ U4 }1 u8 F
    # `  B4 M: X6 ?9 N/ y这次面试问题如下
    0 O6 l' g! Z* j/ Y8 O, i  ], Z9 T% I' {1 ?
    @Value的用法" M2 t- g/ v* G. r1 J. z7 E6 g0 D

    . R9 A6 x7 T, O+ j9 G  R# F@Value数据来源
    ) U( D! ^$ O+ ]* w8 x% w) y+ r  d
    4 B3 y; @9 d) y/ @$ l1 B( o' r@Value动态刷新的问题! R$ }" w  E' e3 r/ ~

    6 c9 B" Y* Q* a4 `! D3 h& D1 k下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    " e% q( @6 q  Y& N. J" _6 Z0 S: T1 i: `' ~& \
    @Value的用法; L4 W' }  E% |

    8 a4 u0 V' T& }& t系统中需要连接db,连接db有很多配置信息。# u% D: j. y3 n2 M/ I! V

    4 M$ C- `: K$ T0 ]% |: ?! Q系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。) H$ [0 W; E1 Y0 s

    ) w* c. g- v8 b还有其他的一些配置信息。
    / D& ~/ E1 ?" Z1 C1 M1 f
    " R3 h" v* w. i; m, {& S, p# V我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    ! o4 [- E6 t5 m9 A6 {
    , H# y: j* `( |那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。: N& K$ A' d4 ^

    / b. j. }, s+ R+ _0 q$ n通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    . Y5 U  ~* W  T0 m3 b% ~+ P* f' p
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    - [' t% D6 ]0 \9 @* J- s) O
    : [$ w- ~' v' C+ f9 D5 j  Z9 k@Value使用步骤$ m/ q+ _1 M& _! h$ x
    ' a9 K0 s! s1 v0 q2 q
    步骤一:使用@PropertySource注解引入配置文件# i4 A1 d$ F# {$ g+ R3 A: k; x
    % K+ r2 x) {) ~' B+ B$ t* [8 X
    将@PropertySource放在类上面,如下
    , B" v9 Q( R2 I# a' p
    3 ?6 T3 a: I4 B, u" ^7 e@PropertySource({"配置文件路径1","配置文件路径2"...})
    + L% ~9 I5 z/ s4 |6 {; q: D@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    0 m& }4 K" l! K
    1 L9 G5 n: s* c" D6 l: X) X如:
    3 b- I. N$ |$ k. @% N1 w, M* K) Q, {" t0 ~5 V2 W
    @Component
    6 K2 e) v1 Y( A9 L' {- f2 W@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})$ E4 S# |# |: ]! ^& [& S4 B/ n- _
    public class DbConfig {
    5 H& @' L. f' }- h! R1 W& z! g}9 _/ G1 a9 M! I  s3 X) N
    步骤二:使用@Value注解引用配置文件的值. {/ _. {$ t: e9 i" ]: O9 F

    7 j: {0 E; v5 H通过@Value引用上面配置文件中的值:# I2 F9 w# M0 b

    " x! P" X  \- v- T* @. b0 l! ]语法5 A0 X" M8 C; M7 t( ~" R

    8 g+ [" z; I4 h& }" M9 D@Value("${配置文件中的key:默认值}")
    - z4 S0 ]3 C$ _2 [  B/ k. T@Value("${配置文件中的key}")
    4 ~! _: d5 X6 V如:
    5 e0 V  h% m3 d4 W* E
    ( p1 e. Q( K) o  C- g, V- ]@Value("${password:123}")
    ! Z0 U/ S7 v7 l- v" k$ w9 r上面如果password不存在,将123作为值9 {6 P: R1 L1 r- h; g

    3 F; U; E& E0 R  d" s! R) S@Value("${password}")) R' x" X& U9 o' l; g* J  U$ E
    上面如果password不存在,值为${password}
    # @" d: ^3 h* ]3 h3 Z+ h+ c
    0 w3 y+ ^1 J! l; _. R4 P  [假如配置文件如下
    + o$ W) o9 L! Q
    : e2 J' J: a4 T' H# wjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8% F8 z/ s! `  ]# y. p
    jdbc.username=javacode
    7 d, y; v0 x9 N$ Vjdbc.password=javacode
    ! z: `% V8 G9 v9 X使用方式如下:
    1 i9 [# w- E7 }
    " J/ A9 A9 ?( l. `  I" u* g@Value("${jdbc.url}")7 t/ D. ?$ A: [* P. T
    private String url;
    * c' }- t6 l! h. y! g5 }+ ]: l9 H8 @0 x/ b( ^. _; i. A+ z; R& X
    @Value("${jdbc.username}")2 ?9 H" S: s# C/ u; ]6 g
    private String username;4 M4 y6 h2 B; u% Y! y' [7 z
    " H+ C$ H) h% f9 I7 ?- }3 z( o
    @Value("${jdbc.password}")
    . |% Y4 y/ m8 ?private String password;
    3 h; t& w' ]  U' ?2 l* S2 l& M下面来看案例$ z  s4 T$ p8 k& a/ z
    8 d6 k) ~& Q( Q
    案例
    : \0 S- U; T8 \% Y8 z5 I! s
    0 R3 s$ i2 n1 B& B' W* u! O/ _2 F  w来个配置文件db.properties7 k: m; Z& K' m, N4 j) Z; j9 u2 q
    " J, O7 E+ v3 m( H% |0 u
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    . N  g4 \+ J6 ?7 A9 Vjdbc.username=javacode7 [# a# n* |5 {, b
    jdbc.password=javacode, o! a% B) I- n+ P! n/ E
    来个配置类,使用@PropertySource引入上面的配置文件
    - z: q2 q5 r8 t+ f9 {$ p+ f' J/ c; F: G0 _/ w  O
    package com.javacode2018.lesson002.demo18.test1;
    ( K1 ^0 i# h9 G' Q0 j% @- z8 q+ {) r/ e" u
    import org.springframework.beans.factory.annotation.Configurable;; b6 I; ?  N: K: _
    import org.springframework.context.annotation.ComponentScan;3 z2 Y! @9 ?* \; v  K
    import org.springframework.context.annotation.PropertySource;
    & I8 q' v! x& r# |; p0 k* Z' j
    8 H. j% z2 m5 J  N. o$ \@Configurable) I( m- }. x/ t& I: K8 P1 x% o
    @ComponentScan
    * p! M8 U5 o3 @, A( Z5 L2 Q1 i: l@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    * I( W; I+ E: v( A; F' bpublic class MainConfig1 {% U% q" C, t/ X9 G+ R* c
    }$ Y( Z- U- R1 y7 p+ c
    来个类,使用@Value来使用配置文件中的信息
    2 W- K- E0 s" I! g" T4 L/ U" P, k9 R+ v7 U% |+ N8 h1 u4 P$ x
    package com.javacode2018.lesson002.demo18.test1;
    . C$ E; j$ r0 M) x7 f5 b( H) t; N! `6 H/ \, T2 z1 m
    import org.springframework.beans.factory.annotation.Value;
    - z3 |, j+ @) kimport org.springframework.stereotype.Component;! A$ _! Y$ S/ j4 j% f
    ) e8 }4 V" p" n1 _
    @Component3 C% u, @0 C5 D8 e! A, h+ }& k
    public class DbConfig {
    $ M: C# f, i) ~& F: m4 t3 S/ ^4 |5 i
      f8 Y5 T) b' j, s2 C; W: y# H    @Value("${jdbc.url}")
    % q" _; m: t2 L, n) f5 W    private String url;
    0 [* b- F0 m3 L4 C" Q) l# u7 }
    & ]" {, j7 s0 u    @Value("${jdbc.username}")7 h6 P# t9 P* C* q
        private String username;
    / n# N3 w* C' Y6 m/ x8 w- T6 a- a8 n- q: u( m
        @Value("${jdbc.password}")
    ( ~0 Z6 D3 P% d  S& [, B    private String password;7 I( |# U: }( D" q4 J; K" h
    ; D: j8 ?7 F$ N: `' y3 U" s
        public String getUrl() {5 |5 M/ z' }& a7 w# ]$ e- l
            return url;- K" r( L: k: C# g! s+ n
        }
    ' a, [+ E: F  p" l7 v- k& P8 ?, b- Q! E& E8 d. N$ Z8 n2 v7 W8 ~% Z
        public void setUrl(String url) {
    4 X* S+ b" g, Y( E9 ?8 `4 G$ A) t5 _; d        this.url = url;
    7 O5 D% p; a, T$ r- b5 o    }
    - ]% _: @7 D' h6 L1 m+ p- x+ _/ k
    * D9 g! j# h0 t# c9 e    public String getUsername() {
    0 j! a) f* ^# K; ^9 m; F: f        return username;3 E  @( Y( U  B- N! r
        }
    : N8 y+ G+ `% ?- [3 Y% a) T: d" ^" p! i+ y0 P
        public void setUsername(String username) {) ?0 ~/ {' S( ?7 m; C9 C
            this.username = username;
    " c/ w1 x% K8 K    }
    0 B, V2 n: T- \+ x- h( A" ?$ V9 t) |+ q. c. u) g6 h  r& K+ ]3 B
        public String getPassword() {
    + K' N. s% z7 h" z3 w        return password;" h0 K5 _; r' F
        }1 l& q( `) [* o

    & M2 [9 ^- e- P' Q( v" {* P    public void setPassword(String password) {# A5 r' W, X9 W8 |; _
            this.password = password;- f* I, t5 r: T# M7 o5 u  {
        }
    / v/ _3 W7 t7 g7 y+ \/ q- C% t% `0 a& u6 r
        @Override
    / z3 C' w! M& Z. d' {    public String toString() {
    % |0 I& @( ~# A% q; \: ~% h        return "DbConfig{" +# n6 G* G  ^  [$ `0 q( Z
                    "url='" + url + '\'' +. l. f0 n  K: R2 [
                    ", username='" + username + '\'' +4 N" }( J- u# g+ _
                    ", password='" + password + '\'' +# N0 X+ Y, V% y# r
                    '}';
    ( r; t8 Z+ u: a8 d    }8 T0 Q+ T) B0 y# s: Q3 _3 r# q: U7 z
    }
    / S" E! H. z5 \% _: f  G上面重点在于注解@Value注解,注意@Value注解中的
    / C' @9 Q% y! N4 Y& R
    4 L" O9 @" r/ K$ r2 l1 Y来个测试用例2 l5 P; P! n+ X+ c9 W' g) t

    ' k- p# f+ \1 U  hpackage com.javacode2018.lesson002.demo18;; e; H, H( ]7 S3 f

    " a) R% D, ^  d0 r; k- Aimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    ! k" h2 F; H4 X: `import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ; Y' u$ z" v6 s' [, x- t0 h" aimport org.junit.Test;
    , Y6 C# ~! ?; {4 Yimport org.springframework.context.annotation.AnnotationConfigApplicationContext;9 D! y5 g! U4 Y
    6 `% ]) Z/ n9 s) R2 i
    public class ValueTest {9 t$ {# O5 c$ z8 M, v4 X

    " n! G. N/ q" g1 f3 U- t    @Test
    ( l2 C/ P, F* X2 u, n/ `- X    public void test1() {7 z6 _( |7 H. O, m8 g  N
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      n3 n: k# P% {% h: z6 F        context.register(MainConfig1.class);8 S/ e5 `5 r' k7 }. k: Y  }# M
            context.refresh();1 u! M& Q2 O* U

    : O+ ?2 \2 T9 Q" S/ {        DbConfig dbConfig = context.getBean(DbConfig.class);  D( v5 M0 q, S* N& I7 d
            System.out.println(dbConfig);% ~* y- R5 B6 k
        }
    9 K8 G( K7 V- }+ e) Y$ y& s}2 w$ \) `  N* h9 T  n! ?
    运行输出
    $ X8 f2 c: ?" Y% e! ]
    2 T/ x! e2 h% O  NDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}8 P9 Y  k2 q6 v: R
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。& U9 Y7 j9 F: t/ C: e+ ~
    ; F5 d* @9 v  {& D. ?9 D
    @Value数据来源
    . K0 X, O- s- N8 |( g! Q
    # ?( g0 H' X* G5 b3 V通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    % f! Q! @( X2 T6 g; s4 D, T1 q5 B; E& E4 h) S9 M6 @: z# G
    我们需要先了解一下@Value中数据来源于spring的什么地方。7 f- `, }1 C, i% y! u  a* C
    8 j& Y% |: }- z! [* B7 L
    spring中有个类
    & i7 K' {. h/ C( U$ @& \5 q; t. Q/ `+ M6 }- e* N; v
    org.springframework.core.env.PropertySource
    - H% L% v' M3 }; \( z& {可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息" e- g7 J' T, b4 G5 B8 Y+ s

    3 u8 x9 u' x8 e7 _: ~! v- a内部有个方法:
    & L! u/ _% R$ U: {$ J" G  `. W- B8 G" r, f
    public abstract Object getProperty(String name);$ T9 Q3 `( W* ]3 E
    通过name获取对应的配置信息。
    1 L2 w2 y' [) ^: t* f+ H. v) e6 j4 L, ~2 g9 @
    系统有个比较重要的接口
    ' X2 R) o( _' {# X' b+ A
    2 I$ R; t# M3 p/ r3 k1 c6 iorg.springframework.core.env.Environment5 }( q; G# E6 N( y9 J( c0 l+ @( G& n
    用来表示环境配置信息,这个接口有几个方法比较重要
    ) d/ S6 e2 W4 k  `
    + r1 i4 }# s- k+ o- c8 r: wString resolvePlaceholders(String text);, J' `* u( e1 @$ k% E
    MutablePropertySources getPropertySources();
    ! x5 h# s; m: q# i) a1 `/ y4 ?resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    ( ~  L& D' c8 L& t$ ~0 n
    0 Q$ t0 R& `, z) Q0 {9 zgetPropertySources返回MutablePropertySources对象,来看一下这个类( b9 c5 |$ b* i: r, T2 R8 l

    " d) S. |6 b, B" \0 Y" _" _5 Kpublic class MutablePropertySources implements PropertySources {8 c! y3 Y5 S+ Y* ?, |

    2 r- ~  X% s; t+ i* ]4 |, U    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    2 S: h  K6 ?8 }1 @3 D0 D# Y6 \. |7 T; `$ r0 q7 ~
    }
    " A; D$ }2 `( }' b' S& P. w内部包含一个propertySourceList列表。
    ( O4 a0 g3 V$ |9 o% t* N# B5 W2 c' H% W
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    ( ^" V0 w& U( x+ ?( |! v0 {- E+ c$ X# u! g0 W) l0 {8 q
    大家可以捋一下,最终解析@Value的过程:
    $ `, \) K/ ]2 V$ C8 t
    : `9 Y6 ~- h/ I7 W& B3 K: ]' u1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    9 t5 U) v2 K7 Q* I6 @2. Environment内部会访问MutablePropertySources来解析+ t; w8 T, |* O/ \% X5 i
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    2 \1 _8 t- O$ V/ ]  t8 L  S; W通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    ; Z8 t8 x+ a  e7 g2 m% q( \- _# c0 F
    下面我们就按照这个思路来一个。
    6 B# {; d' y& K: X2 a& j
    5 y8 G: Q6 a9 ^" G) U: b6 f来个邮件配置信息类,内部使用@Value注入邮件配置信息
    1 N& G& u9 @4 ?4 B1 x) A
    9 b9 p$ g$ E9 F. E, Bpackage com.javacode2018.lesson002.demo18.test2;
    % Z6 {9 `# C! \0 V, q  [2 O1 ?* b! w" ~( T* J. b, V
    import org.springframework.beans.factory.annotation.Value;
    0 @0 _. G( X) M7 h0 c2 s4 k5 Q2 J0 Kimport org.springframework.stereotype.Component;! p: \- W# {& k: M* q
    ( W6 R8 J. c7 \. A$ r, O
    /**
    $ k( Y$ U! p2 B3 {2 Y! F2 `* t * 邮件配置信息
    ( D$ _1 W1 @0 H6 \$ h *// Y7 J& @) Q# }# `+ |7 s
    @Component
    8 t8 }4 n& L1 Z# p; {( u7 \0 Npublic class MailConfig {2 L6 b. g  |" p1 r$ Q) n/ h  d4 G
    , K( m# z! Q3 ]5 c- F
        @Value("${mail.host}")' [* ~6 s$ P( l6 s6 N4 B
        private String host;
    - w! \/ x  Y) T  U+ Y: M2 I0 D, _' Z. A( W
        @Value("${mail.username}")
    + x5 @: y3 t- P8 l& u    private String username;2 L) g' M8 `4 y
    7 M. e9 c7 v: C1 O
        @Value("${mail.password}")
    * ~9 U  G& {8 ~    private String password;1 N5 G3 Z6 m/ Y! e, v& @) b

    ; ^4 R: s" V. p3 R6 N# N    public String getHost() {
    & U$ i* m* v9 J6 x2 c, Q        return host;; X1 f, a3 x9 R( b$ h: j6 a
        }
    ; ^% O# K9 s( i6 v9 Z0 A! p2 t7 D. u' y
        public void setHost(String host) {
    # ~' s- G0 P% X0 s        this.host = host;
    ! |' V; z# D8 M& U8 Z' v    }/ W2 F4 k4 T1 ~

    : ^- b) ~2 [3 h' q) }$ t    public String getUsername() {  l4 G3 W5 K" l& c$ E3 j
            return username;
    - V8 V3 {" {5 k6 G    }
    * v: G# x4 Z- M/ H* c/ o) _& Q$ e) H5 }
        public void setUsername(String username) {/ h/ a% i; D( I8 k4 Z6 Q6 h4 h
            this.username = username;
    / A$ C) B8 G7 K  C1 t5 R6 l    }: {3 _5 J  {# ]* P/ k/ U! M

    6 {. r7 G3 q2 H( U+ m0 @    public String getPassword() {# i  ?6 l4 [+ g$ Q: i6 k2 W9 s
            return password;* W: T8 t* w( H7 X3 O( ~* j% y
        }
    7 O: h: L: E$ b8 L. }+ k) ?3 {, K* D: s( o) p& P
        public void setPassword(String password) {$ R6 i' v. a$ M; D0 w0 F
            this.password = password;
    ' O. S! e4 Q, B2 X    }% U2 ~8 E4 K: x& |5 i
    & r9 Q: L5 V$ F- T+ \
        @Override# S% x8 W$ d: q
        public String toString() {$ a: V( u8 L4 W' o9 ?2 u! L! K
            return "MailConfig{" +
    , c5 R5 [9 v% K1 o6 l                "host='" + host + '\'' +
    ( a3 ]; Z' k% t( v- ?                ", username='" + username + '\'' +
    $ {0 l+ ]! T' X( X0 O                ", password='" + password + '\'' +, w$ M8 r2 B7 N8 P+ G4 D4 y
                    '}';0 E  h4 E% L% C. t1 N
        }6 _) e$ V) I. f$ C, y5 C" Y
    }
      b7 b3 C8 U8 F4 O( q' J2 F% {8 j( ~再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中; m% B3 \2 z% ]
    7 I2 L: V: y2 C1 a8 G8 n, U
    package com.javacode2018.lesson002.demo18.test2;! `- t) b. l! v* \9 R
    ; b, b. F- E* @  z% \
    import java.util.HashMap;1 E) Z( o+ ^' Q* |1 R
    import java.util.Map;
    1 {% d1 J0 A5 c; v4 I! m' B" G
    , D+ ^7 b8 n& Z1 H" ]2 ?. D' n( Jpublic class DbUtil {
    5 `0 s5 K# c5 ^/ E    /**5 H8 Y! ~+ p6 {3 B
         * 模拟从db中获取邮件配置信息
    ! G  w- U; S: m3 T3 x     *
    , C  }4 h- t9 H* P     * @return
    3 L2 r8 `7 o0 x% ~+ O3 E+ ?8 U     */) K1 H4 s& D& g
        public static Map<String, Object> getMailInfoFromDb() {
    ! Z/ q* ?% R5 i5 t        Map<String, Object> result = new HashMap<>();
    5 @; W! }7 G" o! T' M9 {% G        result.put("mail.host", "smtp.qq.com");- Y  a: k! P  e/ Q% a
            result.put("mail.username", "路人");
    : _, h) c  }4 @        result.put("mail.password", "123");$ g) y8 @1 f* {0 y+ {/ @3 e
            return result;/ j4 ^/ `$ i. U8 m$ r$ h# w9 X
        }# m% i: e7 f; }
    }
    ( q0 C% K3 E+ k* H# x来个spring配置类
    0 {4 m/ J/ O- d% o. S2 ?8 ?6 X3 O9 M: f
    package com.javacode2018.lesson002.demo18.test2;
    / `2 s1 y" R% q! P; G* q& M' B4 `4 r5 X/ F
    import org.springframework.context.annotation.ComponentScan;
    - ^- Q4 ~# x# J. `/ Z8 L$ timport org.springframework.context.annotation.Configuration;( X2 Q3 [1 N. `  q; n

    1 ?3 \* ], T1 j& H. S@Configuration
    8 F& x0 f, E! g1 ^# Y3 m@ComponentScan
    + Z( A1 E7 N, V# i/ W7 d- p$ ipublic class MainConfig2 {) \9 D; ?4 C9 k4 l) i
    }  Q, N+ h6 |" `) t% ~& [! s+ H& X
    下面是重点代码) L- G! \) C$ Z) v" B3 x  [
    9 \8 i' V" _( T! v7 M+ l
    @Test
    - E0 h+ \9 C6 \; s) Vpublic void test2() {8 V2 M* L. Q6 a7 D, d/ ?
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();1 ~5 m, \1 _% Z( @5 w& k* o

    6 ^$ A  `/ l+ s- X4 E( j$ z8 T    /*下面这段是关键 start*/  m( C2 F" K( q7 |% o7 u
        //模拟从db中获取配置信息
    $ }3 {1 G( n' `/ c) u    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    , A( a% U" G, s, Q* W6 U    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    . J; y6 c: E- f' w' Y3 |    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);; i! v9 e! F0 t0 v
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    , D/ M2 n8 d1 s$ k# y    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    + w- h7 `1 Z  B; z( c    /*上面这段是关键 end*/
    + u# C( v5 v7 y, ^% p7 w
    7 k0 r4 [- }& {$ B1 l) c    context.register(MainConfig2.class);
    2 O1 W5 P8 p8 b" c    context.refresh();
    / l' o, `& b* k9 u) E    MailConfig mailConfig = context.getBean(MailConfig.class);
    $ K2 E% ~) K  V. r: [( F; z: z    System.out.println(mailConfig);; G1 W7 h. l* c, `
    }! e% U" T7 o7 |, K
    注释比较详细,就不详细解释了。
    6 k& l; b9 M0 E# c  \% O! j
    , T6 C7 S( s" b' Y+ d- s2 g4 N  l直接运行,看效果
    0 k  ^( U3 S2 F& Q$ E4 |- T* b6 a3 o0 l, Q# [8 P
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    9 r1 W6 `, m( B& Y1 ?9 n0 ]1 p有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。) y4 X' c9 P& J) Z' H. B

    + c% z% ?0 O% ^2 O上面重点是下面这段代码,大家需要理解# q- _- L! d5 Q1 g) Y
    2 E) ]+ M% h, ]$ }
    /*下面这段是关键 start*/
    . _9 a) B$ t0 O' o& `( a//模拟从db中获取配置信息
    3 k% I$ F. n# M- L+ s+ g: |4 iMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();( d: L9 d8 J: r: H/ a- H; ?
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    3 J! X) c$ ?2 }/ t9 _9 e9 `MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* N* I0 Z5 M3 L+ c2 a9 M: M
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ; m5 L+ L! ]4 x# R, {: Dcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    6 G- R* P* z8 I/*上面这段是关键 end*/
    $ s. o" Y2 j7 G; M咱们继续看下一个问题
    7 Z5 H" g+ B! q1 N( o. b
    , J) k: R2 Q3 d& P( o如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
      t+ r5 X9 R$ n  [1 a( u/ z7 l; b3 Z2 w4 G2 d  {
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。# S' }' i6 v2 L4 E

    7 \  H* z8 @, m: @实现@Value动态刷新
    " v9 Q( D" U7 o& }- D7 O
    5 i; C4 {. {3 V9 h先了解一个知识点
    2 a$ |( r: q! ~6 g. @$ S5 V! _5 x' k7 r
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。  Q5 t7 J! v7 N3 J( _, Q! c$ r
    6 z) p& z! o% w
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    % N; ~' e$ T5 u7 d2 d* r1 u( E% p) u
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    ; L7 Y/ y# M$ Y3 V! g, B: z4 a
    ( f& }/ f, L5 C3 e2 v  @ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;+ I' C* |1 W/ y+ W6 O. U$ M& H, c
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    % Q/ k( ?# a5 e" U: V0 ^: }: S1 v6 g; U! l
    public enum ScopedProxyMode {
    1 W6 \# \' _1 W6 Y$ F1 ~  {    DEFAULT,
    6 j" N$ W1 ?+ p    NO,, \& d6 \5 u  D- l/ J3 o
        INTERFACES,
    ' h3 t0 D4 M4 A, X" b) o* l    TARGET_CLASS;
    , J5 Q- g# s% {* q5 u% M  X' B* W}
    9 P/ j: F: A& }- H* E) |3 A前面3个,不讲了,直接讲最后一个值是干什么的。: V7 C7 K/ P) S! d. a6 Q& `# d6 }

    / D' u9 s) o0 i& D" E当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。! D' i  {4 n1 S1 O* Y/ x1 M% k/ S

    % o8 b( J: H. K  k) I' B7 v. J  }理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。  a0 d7 ]! k2 d% s6 q0 m
    - P* z& E2 D9 c9 M. Q$ |
    自定义一个bean作用域的注解, D2 o0 I) w9 H$ }

    ! q$ c1 ?" J( d/ @( wpackage com.javacode2018.lesson002.demo18.test3;
    , x6 \$ \* X4 ?6 }: ^: u3 Q. h' G6 V& ?( O* o
    import org.springframework.context.annotation.Scope;+ D- S7 b5 t3 R3 G
    import org.springframework.context.annotation.ScopedProxyMode;
    1 R; T( c( y  B+ Z2 a: G5 |1 p' Z' {4 h( I
    import java.lang.annotation.*;
    4 P: v. o# o5 F% i7 P! V2 P( X0 m& o  c9 m" P, G
    @Target({ElementType.TYPE, ElementType.METHOD})
    ( u3 ~' {# R& c7 p/ ^@Retention(RetentionPolicy.RUNTIME)- o) X+ R9 K, c. I8 N
    @Documented. e6 L8 q, n& ^/ m0 S1 n7 S
    @Scope(BeanMyScope.SCOPE_MY) //@1
    , a$ W% N1 ~: K0 {5 s& Fpublic @interface MyScope {
    ! K* H/ B) }% r7 e; G* d+ o    /**
    + ^& C( R; u3 o2 n) M& g     * @see Scope#proxyMode()
    ; C7 ?! j- Y& [% o. j/ a! p' ~2 m( E     */
    ( y9 ~# [% ?% e3 i) k    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    $ z  B& O5 g2 J% g+ v. J}8 w4 r( W0 S9 k: q. H* |- f! F
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    6 F4 _. r5 m! J4 h0 B
    3 Z# \0 u5 s' d+ ~@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS5 D  H% T; g! x% I
    ! L, U  Q; f# q7 M& m- ]  Z) a
    @MyScope注解对应的Scope实现如下' Y" |# \9 o8 T  |1 h( s' J

    # Q8 ]% _# g: {7 Ipackage com.javacode2018.lesson002.demo18.test3;/ m+ @1 G) z1 T; J! I

    5 T: T( z+ t. G/ \& qimport org.springframework.beans.factory.ObjectFactory;
    5 q7 y8 V* v; x5 h, Jimport org.springframework.beans.factory.config.Scope;
    & M+ V7 L; S' v" Himport org.springframework.lang.Nullable;8 d6 A4 Q3 k4 r1 w! L1 R% [
    : B* q" g; H" `. }3 i5 E
    /**: H& I5 l9 d9 J9 {) f- Z1 E
    * @see MyScope 作用域的实现
    . W8 o  [4 t8 a& C2 |0 z/ _ */
      @. L; Z3 w2 I5 V9 Ypublic class BeanMyScope implements Scope {
    + Q# I2 h8 J/ D& V5 s+ M- d8 u+ T- M0 I1 N* D
        public static final String SCOPE_MY = "my"; //@1
    ( d4 D8 l& M- i; s
    1 {7 |3 P( _, g: @/ o    @Override4 W" a! W. m  e0 a, Z# ^
        public Object get(String name, ObjectFactory<?> objectFactory) { * h3 i' D* V! ?/ i0 S6 F
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2# h5 p# f7 Q) L! X
            return objectFactory.getObject(); //@39 r; t& A9 g# o" g8 H5 ^& }. q7 }
        }
    + m; s; I- o+ _" t: |7 l8 ~: O! \0 E/ }, p2 T% r2 j
        @Nullable9 I# Z4 o0 V: y* A
        @Override
    ! E2 Y$ ?- w+ |( [8 o: {; w% v+ S7 s+ k    public Object remove(String name) {3 a- o0 E, y+ v, ?& j
            return null;
    ' |& {5 f* ?' C! ~    }. P  V' s7 j: H3 \

      Z* {7 c* e2 t7 W) @8 G    @Override
    * ?- w, K6 x5 g    public void registerDestructionCallback(String name, Runnable callback) {8 `4 U1 k, y! N. I8 f* n( i; q

    : U. t! `0 k4 I7 S    }
    + p0 f1 I; r$ p7 L
    8 j1 i+ Q/ v* L/ C: x    @Nullable
    % u+ v+ c+ W: B. q    @Override% {" h( E% N1 F7 H( r& \; U! J. f
        public Object resolveContextualObject(String key) {" S, K. U$ d6 f8 j: E) Z
            return null;
      L! }; X( S4 G- d6 ~& P! C2 L    }3 x8 x. W& F( O  O7 K
    7 q. b9 }# J% b. a
        @Nullable  z+ w- U. A. _8 p
        @Override
    . y- B' n3 S1 I% Y    public String getConversationId() {
    ) j+ |. M# N5 S' o+ L- |        return null;
    $ [( _: b" S6 b5 n1 E    }2 F3 V2 f  J1 p2 Y. l# w3 a( k
    }9 z9 Z0 Y4 i7 g
    @1:定义了一个常量,作为作用域的值
    ; |! `; u: J- G" n* l$ ]
    4 ~+ J* o/ a$ w! H@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    " q7 }- F* _) U: T/ y+ Q
    : n- ?$ C8 w. j1 S) X9 I6 @# I@3:通过objectFactory.getObject()获取bean实例返回。# ~- h! Y4 }/ Z* J& h# [3 W- Q' A# O
    2 i% {+ K' Q. B5 m3 e
    下面来创建个类,作用域为上面自定义的作用域; [9 N) q4 C: Z- w" o1 {) ]# _

    * G: b1 b. d9 ~6 P8 @, i+ Opackage com.javacode2018.lesson002.demo18.test3;' {2 j- r8 m! {2 Z* ]0 J

    / D  X, p; ?2 S1 b, v, ~% v8 iimport org.springframework.stereotype.Component;" w# {* I1 B9 [7 |6 L. g  e  q. B
      x& X; S4 h" o8 s: p6 l- l1 O0 u
    import java.util.UUID;
    # q# c/ i3 s2 u0 J  _; E7 N: t% L. Z- w7 g
    @Component2 u! u+ e; A/ @* V9 @& `4 O
    @MyScope //@1
    1 Z, h8 o) R  T4 Z9 G# `- M9 rpublic class User {
    4 w4 F7 d3 ^% Y- f' [) l# u7 X3 u8 x1 H& ?+ G/ s3 N
        private String username;6 B- J; G1 y1 M) k  X. t4 L/ ?4 E
    6 |% P5 S9 a" H
        public User() { . [, s9 y& P2 W! T/ K# }  X
            System.out.println("---------创建User对象" + this); //@2
    ; |4 T5 h6 B: s& ^: i: _        this.username = UUID.randomUUID().toString(); //@3
    # Z# S1 Q2 U9 J7 W- Y4 Y' `$ @    }
    , ^' C7 E% O* e' T  B) c% W! p) J# r; b- ^1 y) x! a
        public String getUsername() {* W' H" o" @$ h  V% X
            return username;
    . r/ ~+ s- ?( l, Z3 |    }
    ; I" _$ \" k- |* j  A0 c( ]* x' x
    2 }5 D2 u- U3 V    public void setUsername(String username) {
    7 J9 m- t% ?! ~4 M( }        this.username = username;
    8 T5 T" i* l2 W! R/ ?+ h+ j    }7 t# d( F9 o) P/ z/ |% O8 X" f
    - f2 X; |% [1 D- f8 t5 r
    }
    $ ]2 \* h3 g, R6 p: `@1:使用了自定义的作用域@MyScope. Z2 @0 B$ A8 D/ ?. j

    5 Z$ a2 C) X  q@2:构造函数中输出一行日志. z# t: f$ k6 J! F7 F! H
    " a& h4 R" g6 r' j' {
    @3:给username赋值,通过uuid随机生成了一个
    9 y; i6 p; G8 }/ T' d1 p8 b) b. v1 a+ P- M/ X, }, w6 C
    来个spring配置类,加载上面@Compontent标注的组件. h$ \8 [5 j% z# j

    ! A. Q5 y. B, N. hpackage com.javacode2018.lesson002.demo18.test3;
    " \, h; ~) G- q/ Q+ ^- x7 |7 x0 |+ R3 Z( w) W. G+ w' v
    import org.springframework.context.annotation.ComponentScan;. ?) W2 F! `& n- p- j6 J
    import org.springframework.context.annotation.Configuration;* H, T5 c$ y! R9 H% E
    6 N& R1 F- b+ f9 {! v" b$ [
    @ComponentScan
    % C' y! V+ x) Z2 D5 e# H@Configuration
    1 m- M5 s* S& w2 z# k/ \public class MainConfig3 {
    5 G  ]. d8 k! W; m* J& I, ?" q}) N! V' G4 U. R$ Z4 E! T
    下面重点来了,测试用例5 Z0 H) s" ~: T+ S. W8 C

    # A# Z: V; Y6 j@Test# ^( A* f+ `; y
    public void test3() throws InterruptedException {
    4 y5 ^3 ]% W6 p    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' U1 j/ M- Q- F! O  ^9 m, }4 ]
        //将自定义作用域注册到spring容器中+ j2 Q" Q: {. _7 v. q
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    + n( i( g7 p* I7 ]( m4 u5 c& Z9 N    context.register(MainConfig3.class);8 S! {! O9 ~( p1 d( G
        context.refresh();
    $ p  S* n! b% {# R& F0 A
    2 ^& e1 J+ r" w4 ~/ M, G    System.out.println("从容器中获取User对象");5 H! L+ G9 ^+ R4 y! |
        User user = context.getBean(User.class); //@2) e( r# \0 [, C0 y
        System.out.println("user对象的class为:" + user.getClass()); //@3. [6 b( Y! n4 R3 V8 S6 b& {
    + M" G- [- J- j
        System.out.println("多次调用user的getUsername感受一下效果\n");
    2 P1 m2 H- p* N) h( e) A    for (int i = 1; i <= 3; i++) {
    8 e3 T9 k% u8 E        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    3 y4 K; f' m- x1 V2 j        System.out.println(user.getUsername());9 E5 C: _/ `. R. a
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    + b  I$ C" J" q# Y$ q. Q    }4 P6 C! o- i* ~& j1 _% \  {. g& `
    }
    6 B7 s: A$ H8 s@1:将自定义作用域注册到spring容器中
    8 z( ~5 M: M$ {8 Z* l; l7 x. I6 |* n1 L: Q' l7 Q9 H
    @2:从容器中获取User对应的bean
    $ N- f5 Y' n7 Z" h- k6 ?# W
    : I" I# E+ W& j- {5 J. P( ~$ Q@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ( x4 K. t9 A* X$ _( p
    / x; D" x8 E' z( p' Z* k; b5 A代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。1 G* g4 E; `, }' d
    " d. O2 ^2 b9 ~0 j6 H
    见证奇迹的时候到了,运行输出6 m  m4 x* \( o. N- c

    1 A; X! [3 `" Z* R! x& E& u" E6 k从容器中获取User对象# U6 O% Q5 I9 i7 x/ B
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    . ^5 R8 w. k5 O/ Y多次调用user的getUsername感受一下效果
    / i5 s( R0 |5 J! o1 P
    + ?% F2 Q/ `& z: t3 F) J' c********5 @3 ^! z8 \- J  Z
    第1次开始调用getUsername
    ' b! g* i: b0 V& WBeanMyScope >>>>>>>>> get:scopedTarget.user
    & J* v  ?: @) N0 B# _---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    & c5 \6 O# [7 o" l8 _4 `7b41aa80-7569-4072-9d40-ec9bfb92f438. \4 W/ o, W6 B
    第1次调用getUsername结束
    3 B, O7 U! h4 C. k, {* e5 e: L********
    8 n! L9 e% e, E5 B
    - v( d' z8 n4 P2 E5 K********
    + B0 b  N4 R, q6 ]' b: G第2次开始调用getUsername' P& i. l' K- e
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    $ ?  X; g+ D1 `2 q2 y---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    $ X- y( v) T1 I01d67154-95f6-44bb-93ab-05a34abdf51f
    ! M2 n/ J3 L* j1 T4 x" ~5 J$ J: [第2次调用getUsername结束
    # s* W* s. h* k: B1 `********
    * p* j( N; e3 I: Q. S8 d+ l4 }# |2 v. G
    ********
      }+ W+ N- ]7 F7 n& p4 p第3次开始调用getUsername2 `7 A. F8 U$ j% R9 {) f/ c" Z  Z+ ?
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    # R8 `0 x6 F1 i2 a! W$ {) }0 r- [---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    , M. q" ]$ n. e. Q1 _0 u76d0e86f-8331-4303-aac7-4acce0b258b84 @! {) ^, M' X# a' j; g
    第3次调用getUsername结束
    . [3 n0 i1 v& C& }********
    4 w. V$ Z2 Q7 N( G# K从输出的前2行可以看出:/ X. g( ~7 `& G4 Q- B

    " t' Y* v+ |3 I3 s" d  u; w" x调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象- g( Q2 ]/ R; ~- y+ B1 u" h+ q
    & `. h. g, ?8 p3 a
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    ; }8 M# _7 W6 v: e9 O; J2 Q
    , D' z6 t, S7 O! C. C: I后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    : D! ?0 H  _# V6 J' ?" @6 j/ P/ W; l% f0 s# \, W( l' J
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。( |9 B) \1 k$ B8 X* s; J" R: I
    9 _/ m' d% B7 x. s3 A
    动态刷新@Value具体实现
    ; ]( T+ }. M1 ~; |" |2 R
    . H( X% `1 X5 s6 p, }" u. {那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。9 E5 G' A0 Q0 t( M$ w3 e

    - }. i8 d: n6 F' i" M4 }先来自定义一个Scope:RefreshScope
    % H: k* P" y, D5 ]. {  ]) @! s7 T
    & R6 `" S% C& l' opackage com.javacode2018.lesson002.demo18.test4;6 O5 _* C1 }- U! D) f0 V( s6 C
    9 S  D# H% ]  P. k
    import org.springframework.context.annotation.Scope;
    % J, V+ o" y) Dimport org.springframework.context.annotation.ScopedProxyMode;
    9 Y$ @9 w4 Q4 f  V+ G, S0 T8 W  I
      \- F1 c3 g0 _3 s" ]7 ]import java.lang.annotation.*;
    3 X, J; _9 V$ S5 u! u) x4 e5 g0 i: X3 D5 X% h+ b7 t
    @Target({ElementType.TYPE, ElementType.METHOD})
    ' h# H! W. w3 V6 }% A@Retention(RetentionPolicy.RUNTIME), t* S; V' E- O9 j7 M; a
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    - T1 w# F: u1 Y. @# f@Documented
    : b5 e/ M0 c# [$ qpublic @interface RefreshScope {4 r" ^2 Q" q6 q8 Z* |: A; s
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@15 l6 `$ a* _5 Q
    }7 n1 v8 e- v$ h5 l2 w5 n8 |
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置0 T7 ~! o/ q0 l! B# @, l
    ) j9 ]( g$ O6 Z- D$ |, d
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    : W' ?' \# u0 a/ |; I/ N  i8 v" h
    这个自定义Scope对应的解析类
    : `9 d% X2 o5 `- _
    0 |9 M. k* i7 Z- \7 a6 _, Y5 F下面类中有几个无关的方法去掉了,可以忽略/ K! y8 U6 g% M

    7 l' Z3 o2 q, v$ `& ]- a# }package com.javacode2018.lesson002.demo18.test4;/ N2 Q+ P5 g3 L  N4 ^. ]( M. v! y" c0 L
    ; ~3 Y4 b; ]% S: @1 R( M7 i! U# l% N

    0 ^8 w! y+ L% k' _8 Vimport org.springframework.beans.factory.ObjectFactory;
      t) a0 W+ _) Z$ Wimport org.springframework.beans.factory.config.Scope;6 m4 B( N# |+ U0 G  n3 e# @
    import org.springframework.lang.Nullable;
    2 K( C) F, [2 b7 w0 Q. d3 P% l5 @, g
    import java.util.concurrent.ConcurrentHashMap;
    ; H+ g5 x& Z+ z5 ?# f# F6 a; q# J$ c
    & O4 T/ f, t  M' z( G; t7 cpublic class BeanRefreshScope implements Scope {
    ( Z4 a0 \/ I% k7 x" t+ W. a9 F
    & Y3 f7 q8 T: K' h' ~# K6 t% [    public static final String SCOPE_REFRESH = "refresh";: a7 W. o  \3 n2 @8 |2 P/ @; `) S

    % |5 ?; ?4 p" E) R2 u    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();& D1 R+ A, N( D- \4 Y# E
    / i; L0 ?1 i4 q, E3 o3 W
        //来个map用来缓存bean
    3 x  y& d" _( p    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    - d) U6 R8 x* ?+ @! d' L7 T
    2 K8 u$ b# I* [0 t& F2 V, w    private BeanRefreshScope() {4 d# i* ?' U9 t6 E  V' T
        }
    6 w) r; e! q  G( o3 k" f' K" ]8 B& y
    * L) @2 p8 G  z. S: `6 u    public static BeanRefreshScope getInstance() {
    ( a  v( B" R$ t- e9 s3 ]        return INSTANCE;
    ) }& V  K! U8 n) }) p0 C! x    }
    9 l) {$ X- b" L0 B5 O0 g- I
    7 B  @) B  k! u( u* P  Y8 _    /**5 Q! t; N$ T. ]7 J4 x
         * 清理当前0 h! ]+ e" @. U# i+ `. ^- ~% m- q# ~7 V
         */
    6 K( [) s1 F! R% {2 j$ l    public static void clean() {
    . M" q" O7 C( U        INSTANCE.beanMap.clear();
    8 W& I" n$ Z8 g% X/ Z8 Q    }4 {& I" w0 e: C; Q% c8 a! K2 p3 H
    4 u* p) b- x. X7 f
        @Override. |7 r6 t1 Y: K% N0 }& t/ j+ d
        public Object get(String name, ObjectFactory<?> objectFactory) {
    ( ~3 _  E  M2 n; S6 D        Object bean = beanMap.get(name);
    ! ?+ G# Y) A- ?; M        if (bean == null) {2 _/ s. U' y: {
                bean = objectFactory.getObject();0 H& A5 j) s0 T0 F4 j! s- f
                beanMap.put(name, bean);
    # u0 I. j2 @2 p8 S! f1 _2 o        }
    . s; r* J4 c7 z9 E* x8 q* ]        return bean;
    ( N' `2 L+ i  m    }: ~4 c& T/ L! @4 o* ], ?
    8 C) `: u' p& X4 f7 A, y
    }
    - X2 l. l6 b, q上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中$ Z9 k  N" T2 m: S2 o# q: W9 P

    6 q' ]5 S) h0 P上面的clean方法用来清理beanMap中当前已缓存的所有bean# Z; N( F% P+ k+ L
    " l2 j0 J- P) l/ ^3 A; b* r
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    : G7 H1 A' N" q8 S0 I, d- f. E' F) S6 S" u3 @" l) T+ `- W
    package com.javacode2018.lesson002.demo18.test4;
    4 f4 z4 y5 Y& a9 ~( q' z* w! ?" h/ D0 {8 z$ J  N: w8 i5 K9 D
    import org.springframework.beans.factory.annotation.Value;
    ' ?, s& m/ F7 A- V/ y* o, |0 _import org.springframework.stereotype.Component;; g& u+ }* j. M. j# l
    2 i* y. {0 s' Z& Z: W- j
    /**
    ( g: D2 G$ P2 w * 邮件配置信息8 e4 L' U" b0 w$ \- [2 d
    */2 K' Y) U- j( c0 d* R3 Y0 B& N* U
    @Component
      [$ G1 m8 ?9 v0 }: _) T/ K@RefreshScope //@13 }" \# \: f; H0 s7 k/ @& E% V
    public class MailConfig {
    # c/ k% Z+ b# G' M1 S! J" E' n. \/ O% T% e% J4 h" U
        @Value("${mail.username}") //@2: ?& C8 z" p; W. L* r! j0 B
        private String username;+ s6 D( O2 r% r1 t( z8 o

    % S# d) ]5 Q9 J4 P    public String getUsername() {
    8 G: t: x3 l9 H4 f8 s- P2 A  ~        return username;# S. E. N/ v7 G% }
        }1 N, r% Q+ R( Z6 @

    8 N$ v4 Y6 n7 q; m# l    public void setUsername(String username) {: y! E4 w* M0 ~: H4 D& u
            this.username = username;- P. T: h! n5 p: V2 U5 a! X
        }
    " k5 |* ~6 f( A& ^: X8 F+ G7 K" x! i0 N& N; @" P  R7 J
        @Override
    9 ^+ z" o, Y2 J    public String toString() {
    2 D# E! k8 K5 X, H5 H        return "MailConfig{" +
    & ^! u& l1 G( W' V; U                "username='" + username + '\'' +. [& b5 N1 m* u  H3 O
                    '}';
    # d: H3 Z' [( Y' s7 F0 m    }; p5 ?7 _8 q, }1 i
    }* K4 E: c6 m+ ?! g
    @1:使用了自定义的作用域@RefreshScope: O3 q9 R# ^+ l/ w$ K" ?7 e0 {
    ! ?: z2 V/ o9 i- Y' [6 p. l3 ?
    @2:通过@Value注入mail.username对一个的值
    9 I9 p) |5 q! \- l$ A7 B1 [/ c' L  a# p+ ^7 h
    重写了toString方法,一会测试时候可以看效果。; n' w7 X( e7 l, E& A/ a
    # f2 L' _+ ?5 l5 G# B0 m% _, n* u
    再来个普通的bean,内部会注入MailConfig
    ' T% l  B$ d/ E
    : w. L9 r/ C. U3 S4 `# ipackage com.javacode2018.lesson002.demo18.test4;( D' [0 y5 e/ e, [

    4 V* b. m: d8 M1 {$ eimport org.springframework.beans.factory.annotation.Autowired;- ~6 t" L1 ~( l5 b2 P: V. s+ k
    import org.springframework.stereotype.Component;2 _5 R# D4 h& ]9 Z- G2 }* i
    7 V* K5 W- j. u5 f, z6 k
    @Component
    5 C  S& z& s" E# ~" T6 h  d  h" ?( ypublic class MailService {
    # b; L2 V% I- O- m+ N, A    @Autowired
    + \* Z3 G, t$ t1 L    private MailConfig mailConfig;* y$ z' P! [: j
    9 H+ Q% Q4 g7 t" p/ R1 k
        @Override  g3 p( k& I4 ~# J: B
        public String toString() {
    % n! Z$ ?) L: a7 c. N5 x  x        return "MailService{" ++ R- @4 v4 G( G
                    "mailConfig=" + mailConfig +2 t* R5 \! P1 d- w; ?9 U4 \7 B. ~' D3 S
                    '}';
    0 W/ {* l4 I' {    }
    5 t3 q) N  V" @5 O1 T4 |. |% v- k}2 e# }9 W) @9 e& O9 Y7 G& s9 b# H) Y0 C
    代码比较简单,重写了toString方法,一会测试时候可以看效果。& E; E. a. V0 ?% l8 b

    1 g+ p" F0 W7 U4 |来个类,用来从db中获取邮件配置信息% k- V' ^& C4 o3 I4 M5 ]$ O  z
    ) t; Z  ^& h) N1 t7 j6 e  v
    package com.javacode2018.lesson002.demo18.test4;2 y5 a6 p" o9 }7 x

    6 A# S% x  W+ A# \7 w& ?import java.util.HashMap;
    3 G1 P( `" Z4 ^- Pimport java.util.Map;
    # b" P% s# j3 T% Zimport java.util.UUID;
    # t( }5 I! u. {2 P/ `9 @, |$ j/ o& x
    public class DbUtil {6 i+ e3 ~) b- j! o1 a; ?1 o
        /**# m% a6 T3 N7 p2 y
         * 模拟从db中获取邮件配置信息2 x5 O4 \1 k5 I
         *  I+ F& K- n+ E. P3 o; N
         * @return
    * O6 A* C/ C7 {$ e5 R* w+ v     */
    2 J; Z' i) \) M  F# a! G* T    public static Map<String, Object> getMailInfoFromDb() {* u, ^: I# S4 |6 }# P0 m6 ^  X
            Map<String, Object> result = new HashMap<>();
    . b$ a/ {) E' ~. Q5 v8 J. ]        result.put("mail.username", UUID.randomUUID().toString());( w- f) Z& k' `$ g, z8 Z
            return result;
    % X/ _% i( ?: ?1 B/ ^/ u1 L( o8 w    }
    7 a5 Y% ~# j( [' e1 T}
    8 P& y7 J$ \9 Q( u# X4 F来个spring配置类,扫描加载上面的组件
    ! z1 I0 B& O8 q& `# y
    " J) z5 M7 X3 ~9 ppackage com.javacode2018.lesson002.demo18.test4;
    ( e: H% A% `* t* W9 K8 {
    . j  W* ]- W% @( U! Q3 O5 |8 Wimport org.springframework.context.annotation.ComponentScan;
    * a: Y$ ]! m- j) A$ ?import org.springframework.context.annotation.Configuration;
    7 e& \  {) }2 P) [. \2 I* s  x+ A8 L3 V# s: C
    @Configuration
    , r, d2 [( t  Y: _@ComponentScan
    8 }& I/ g, \. r2 ^public class MainConfig4 {
    6 `: F+ T+ e$ }* v* c}* n9 q/ ?* s3 z5 D- d8 x
    来个工具类- y# L( s. e, ~% `% S

    : V$ s6 F# z& ~7 I* r内部有2个方法,如下:2 T) T: E2 {& e

    0 W, M9 ~$ @6 @5 @package com.javacode2018.lesson002.demo18.test4;2 F' k5 i8 S, b; T* `3 Q5 f$ g4 G! O9 y
    1 L7 ]( R3 z+ _" O0 R6 b) y0 o
    import org.springframework.context.support.AbstractApplicationContext;$ T( {& w: [% X; r' Q# f/ f
    import org.springframework.core.env.MapPropertySource;
    ; V/ Z/ Q8 l$ ~( |. a  R- a1 n+ O* {, ]8 w! B8 P
    import java.util.Map;& R' J; G: F# z4 m7 y) H
    ' J( B, U7 x9 _# J
    public class RefreshConfigUtil {, [$ J* H0 m$ ]2 |
        /**
    8 N3 Z, p/ {1 t8 i9 _6 l( C# U     * 模拟改变数据库中都配置信息, _, W% ^1 h" y7 t9 W
         */
    ! U# _+ R. W' L' z    public static void updateDbConfig(AbstractApplicationContext context) {
    9 P3 r9 ~: _  v/ F9 o  C$ t# x# g        //更新context中的mailPropertySource配置信息
    # @. ]/ m5 Z. Y( F. t1 n        refreshMailPropertySource(context);
    . h# g. G9 P$ B2 H* i+ X' {; j- B3 X/ u2 Z; h3 x
            //清空BeanRefreshScope中所有bean的缓存& V$ a" E$ A3 m2 v0 r. X* Y- [
            BeanRefreshScope.getInstance().clean();
    / K1 B, G9 o# {$ r    }+ M1 a) D) \; C+ v7 O

    / L8 s, C6 D7 Y4 c6 W  M4 ~. d" x: f    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    + a9 u3 s4 q) v" ]- i9 }. L        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ! y- Z* A6 D; j9 \        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)7 q! U, t7 q1 H2 m9 h. u# [
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);! x8 T# y' @7 _+ c5 _
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
      F# O# u! m; A! V0 h6 U6 W    }
    " {8 M9 }& g$ w( ?6 W4 `  Z! `4 f) v* ?; r& z: N; ^
    }
    2 O! B! ^1 K/ |& qupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息1 f0 s; m3 f& N4 f

    - P' ]1 R1 n/ xBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。! k$ w1 J' Q& ^" `/ j

    1 T- C2 G+ X9 U/ u" x( D4 _来个测试用例% L, U  j! v$ @1 n

    ' B& T6 i3 L  U@Test7 \3 f. u5 s5 n. f; O# b5 D5 V: g1 O! S
    public void test4() throws InterruptedException {  H+ p" V# N2 D
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();& U1 ~) N  n9 a( u* S+ Z
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    % ]/ O3 U$ g/ R' e    context.register(MainConfig4.class);% V; J! v% e" T3 y1 K* m% n
        //刷新mail的配置到Environment1 g& G+ d# R; h! n5 `5 W# r  c
        RefreshConfigUtil.refreshMailPropertySource(context);
    5 T/ H, M& f" @7 T* Z    context.refresh();: l, j- O5 A7 _
    * E; x+ ^) ^7 K* Y7 B0 ~
        MailService mailService = context.getBean(MailService.class);: j" t' ]5 O) e  r( _
        System.out.println("配置未更新的情况下,输出3次");# J* y  Q3 q! h9 _+ T' s4 M1 o* r) |
        for (int i = 0; i < 3; i++) { //@1
    7 L- B) R2 d+ r' f7 {  L        System.out.println(mailService);, ^5 T5 K9 b' k) r; U
            TimeUnit.MILLISECONDS.sleep(200);
    5 P# m( A/ v* F; A    }8 Q( P  R/ O0 }! S$ w

    & S6 C7 x- J5 c1 c6 E% I1 [6 ~    System.out.println("模拟3次更新配置效果");* R" h; u9 C% q. p5 }" A
        for (int i = 0; i < 3; i++) { //@2
    7 F6 S1 q/ Y2 b) m        RefreshConfigUtil.updateDbConfig(context); //@33 s" T  N; R  \& |
            System.out.println(mailService);
    , e9 ~3 R5 f* E; D4 |        TimeUnit.MILLISECONDS.sleep(200);
    : k# b, t: x' M+ Y    }9 _3 j# _: c1 K
    }. V) _- {  N; G8 y6 Q- F, F
    @1:循环3次,输出mailService的信息2 N2 @; ?) s& t$ e
    0 l) U/ k8 j2 i) |1 i  R  A( e
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    2 L6 \# Q/ H7 _( r1 Z5 k% N# k1 r. N' v4 O- u: `
    见证奇迹的时刻,来看效果( `5 K, _3 J# Z% Z; k' o) z# e
    3 Y& M# l7 m5 g" d
    配置未更新的情况下,输出3次
    ( G. a% L1 R% WMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ' V. z2 U9 e0 I8 ^MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}( H6 t1 @$ r: t4 w
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}0 g5 g: k. V) A1 v/ ~3 s. W
    模拟3次更新配置效果
    3 ~6 L: \6 J6 o: C8 }. LMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    + H; ^% {7 H" f- sMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}! e+ v9 K  o" V- }* \
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    # X$ P5 g7 `: F6 m% p* p上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。1 f( z1 z# R4 |4 @' S
    + x, o8 P# f7 |5 I5 x8 C6 R1 i! _
    小结( K. |$ C0 V  t6 ]# d

    ) p' A6 [$ M9 ^( P& F动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    , i  V) Y. M/ \( D$ c( q, b6 K/ m* h8 L
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    " v1 d( H" F4 {7 H) Q! `2 b! C+ S0 r/ u: T8 T( z" P
    总结1 W/ M9 s6 E/ P

    4 q  M. H! [: K' m. `1 s8 l5 l本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    6 x6 k; m; w5 u: Q( i8 ]" }$ @, D9 _2 ]
    案例源码
    - S% J8 S, O# @! b  X- a- C' t* n0 S: K  Z3 f  W, b
    https://gitee.com/javacode2018/spring-series" x( F7 d. f" i0 P, b
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    3 @& T. @# e+ h0 C( S& {
    1 F( y0 V* r$ T+ XSpring系列
    . ^$ w/ I6 s) V! F0 B, W2 H& d9 I" F; L$ S# q4 h
    Spring系列第1篇:为何要学spring?7 t& ?4 F  O& O! t
    2 b6 T( L* l; o( G$ _
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)6 H7 L1 Y9 W/ O8 d% V

    6 }  e2 g% w) C5 v- KSpring系列第3篇:Spring容器基本使用及原理
    7 @7 p1 \7 P7 [# K5 j# n- O/ e/ \0 U! Y
    Spring系列第4篇:xml中bean定义详解(-)
    + e0 ^3 P+ m) k- O7 k5 a& N: q- O1 Z; O. ~/ `6 X
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    . {5 c( M/ N; [3 r+ e& J; k0 s: T4 h  t
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    ( P% [, O) Z$ g( A& F8 \
    , R- V9 R* G! k) v6 }Spring系列第7篇:依赖注入之手动注入
    * e7 l5 P* {1 z7 e5 W) E( _( C% B" U/ M6 k, z* f: L8 b* ^
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持# k" b- ~9 ~' B& N/ ?
    & c/ J" o1 Q0 F$ t
    Spring系列第9篇:depend-on到底是干什么的?
    ! V6 I" B. |1 n
    - C+ [2 J" y% R0 E6 rSpring系列第10篇:primary可以解决什么问题?" U. h; O) h. {" f* ^' P. ^( h$ R

    ! I6 M- S2 n* l7 zSpring系列第11篇:bean中的autowire-candidate又是干什么的?2 n; e8 m' G4 o9 c# z; M

    . j; k- z" u+ O' I$ P  tSpring系列第12篇:lazy-init:bean延迟初始化
    ) l/ |5 `! K0 O! r9 H
      p" p3 e; c) a5 MSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    + g! ^2 ]- g3 h: ^3 ]
    % {; w6 J' q, ?/ x& s9 \" wSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    , U, O' y3 m1 s8 C% G# v& }" p0 J1 Z, L; d/ }
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    % V& s& R; X& `+ C9 @! Y4 D% q
    ! ^( P  z0 [1 |. `# {/ U+ Y; X0 T5 MSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    5 L. T2 m5 o2 Q% E; ^
    9 V; `4 c& v' f: @# a* QSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册); X# O, Y( `" z2 n, ~

    - n% L% B. i+ Q; p0 J3 ]; jSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    - ]  n" g/ S, I9 x$ @0 i( L
    + i2 B5 ^0 }7 f# U! ISpring系列第18篇:@import详解(bean批量注册)2 v' K% J, @" Z/ a
      {+ m9 k7 {+ B+ O7 J; R! @  t
    Spring系列第20篇:@Conditional通过条件来控制bean的注册" [4 k9 d' o) a- W

      p( i0 H  W7 l7 t( ?! r1 qSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    , n) A4 k1 e+ y, x- \# @" G
    ' {$ I! h9 M; v0 kSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解& `8 \( `! i; [+ {8 M& J4 R1 a
    3 R9 v7 I: p* g; N' [- i. Z/ @$ V
    Spring系列第23篇:Bean生命周期详解0 I1 \; _" r- ~8 d9 Q4 t: R

    0 Q" s# [& g' [( C1 XSpring系列第24篇:父子容器详解
    ! @- t$ ?0 w$ _/ n% C6 P: V  i$ ?/ F4 b7 N+ U9 s( }: h
    更多好文章4 |5 r- C3 L9 N7 H% E2 x8 x8 T

    ; w2 ^# k7 u. o- ~% v' CJava高并发系列(共34篇)/ O' u  c/ S9 r
    ; _( Q" j  X9 w% E5 {
    MySql高手系列(共27篇)* F* G! P% D# u: x
    . C1 F+ J; n2 I8 F2 A  w( _6 m
    Maven高手系列(共10篇)
    5 A# b7 o6 C. w. m4 A: [; L  ~. e4 k/ K8 T' d. D1 J
    Mybatis系列(共12篇)& h7 b+ w9 R- T$ d

    - `6 L; r$ a0 s% K& x. |. U# P: D聊聊db和缓存一致性常见的实现方式- F9 Q3 U! d! F# C+ m7 a+ b

    9 x" T5 c1 e6 h2 D* u7 C接口幂等性这么重要,它是什么?怎么实现?
    6 U" L5 ?8 s3 |# J" F& Z; m) p# g" t
    2 t9 j; u1 G$ m9 Y; o  q0 b泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!2 v3 Z, R& J# _
    ————————————————5 o, ^" w# K- N/ g! S0 c
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。' @  G& a0 K( _: J% R. G+ c
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    . _# v: W/ w3 m% G7 B! j) Z5 t: o1 Q  I9 l- T& u. K* d
    / K- u, p+ W3 J3 l% F( I5 i! m& R
    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-8-4 04:16 , Processed in 0.360484 second(s), 51 queries .

    回顶部