QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5129|回复: 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!8 s' I, n/ F" n( C  w8 E
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!) q$ b2 T* m+ s( S  R! a3 J& x- I
    6 S$ d9 c: P' Z8 K$ [9 V' T
    面试官:Spring中的@Value用过么,介绍一下
    1 k5 N; n  q1 e: s- X
    ; G3 c7 j/ E  I, n$ e, i/ I我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中1 V8 }/ v1 ?2 h3 w: S1 \5 F8 j
    6 R. ]. X+ L$ _! M2 S4 P  R5 r2 V. s
    面试官:那就是说@Value的数据来源于配置文件了?: p. n7 K7 C( `

    & {7 N8 J  F, `9 a/ y我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    # j$ B! _$ |$ }, |! }2 E4 O
    - Y7 U/ N6 S- L: u, t面试官:@Value数据来源还有其他方式么?
    ; q8 q" f  h5 v1 y0 h) F. p( v# t" W$ O4 X
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    2 N& N0 T, {+ m" V8 N4 b7 X% Z# W$ f+ ~# \% K. l, C9 ?
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    + F. n9 [6 U+ H8 R  ]2 t, s* N" _* L' p- x
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧2 @; Z( w& x% T- j

    6 u$ y- ?5 o9 P% i- e( D2 I面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    / {' j% {! R5 M, {& q7 k) a2 G. S+ X6 H$ }  T4 o
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    - J! C0 [& i# k6 t) ^  w5 y$ D& ~% ?& i; q
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    5 c- z3 \. ^' ]) z, u: s- \0 L
    ( A& l! I6 L% Y6 S# Q, x( v. O我:嗯。。。这个之前看过一点,不过没有看懂, l! B! e/ K8 F  t+ `  ^

    ( C2 D6 w7 j1 T7 X% D- h) I! n# A8 @面试官:没关系,你可以回去了再研究一下;你期望工资多少?6 @# y! h- O3 z7 m" C+ Z2 C

    7 g( N  n, V7 h我:3万吧
    7 Z; q& k$ m5 `, P8 R' T1 E* @+ O2 z( G+ l
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?3 G' \9 u" L; E2 Z& k

    ) w5 [/ `$ \7 }# N我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万' _/ Q* q  V% g( y* a% f( d
    - ^1 I$ ^/ I. Z5 k
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    0 X6 E% V' M2 Q( D8 p# |* S8 c( @
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。' B7 s- ]/ b; F
    + E, Z/ b% I5 k9 t
    这次面试问题如下( S( N& g- q$ K# H2 Y$ y3 ~
    - r+ n5 t: |! ^* k1 F2 s
    @Value的用法: q# O0 @/ J6 r7 P
    $ x! r; }* |0 j1 ^% t' C
    @Value数据来源) D; k! g: @! \2 p
    . i3 m- B1 \& ]0 `; L
    @Value动态刷新的问题, d8 {  C) r* F* {

    / K: S2 L/ p+ [  z; }' b; C) j下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    - n6 c: U8 C% C( o/ X# l  t1 M, @. B6 A) M0 A/ a: a/ \7 j
    @Value的用法" t- B+ b, s6 z; ^' f5 z& w

    ' M. S: l' {, W  s+ {系统中需要连接db,连接db有很多配置信息。0 r1 S, @1 h5 M; d8 J
    3 Z  ~5 N& A" g; O6 F: }6 X
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。- S7 F* a* r  X4 E1 a! ]
    4 N7 ^$ B* Z7 I: h. l, {
    还有其他的一些配置信息。+ a! P+ b( A+ T! M* r

    / X- |- {  u) b0 a4 @我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    0 \5 u( q9 X+ C4 S5 Q- b( @/ z/ ~. m
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。( I$ F: Z" x" p0 ~' `) w
    ' N, q" y4 [% W  K# q
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    & T, _5 A, L* [" b
    ) W; t% P# X% t通过@Value("${配置文件中的key}")来引用指定的key对应的value。0 W5 r; d) s; `9 g2 \2 E
    2 [6 U7 \! u! r7 Y! ]" a: }+ `
    @Value使用步骤
    $ K: i$ t  P! t6 b7 ]: ~7 n
    ; M: C6 ?7 U+ t& z3 U步骤一:使用@PropertySource注解引入配置文件0 d  C& U) \: P) h$ @: @3 r% K

    0 Y% w; e: F/ O* }将@PropertySource放在类上面,如下( @3 [4 c5 F  p5 d

    . s/ B' |: z4 @@PropertySource({"配置文件路径1","配置文件路径2"...})8 f4 ~3 p2 k0 B1 E. h( F/ m
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
      d2 g5 W2 m. Z* R4 T9 z/ b& p$ u* s3 E& b. L- X) H
    如:
    / g; V; {, E" C4 G8 \
    + i0 h8 H' v! M@Component
    7 V3 K$ R# P+ G0 }% k) K@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}): Z/ O* G1 k, e1 @  t
    public class DbConfig {
    8 F) T1 _! A: ?* s" O/ I6 U5 g6 Q}
    " F# h) K) Y9 H( G- b" x3 p步骤二:使用@Value注解引用配置文件的值' Y' L, B6 K0 b4 n: r- T! u" X- Y. C

    % N  v& Q4 g8 O9 z7 |! T通过@Value引用上面配置文件中的值:
    ; Q$ n3 S! ]1 e% g# ^  z" R) H) [
    语法2 b4 c- _+ j$ G( C  L

    - W4 h$ r; e9 F7 r  w; ?% @5 g4 x@Value("${配置文件中的key:默认值}")
    ! D( P: z/ h/ c: F0 ~0 ^@Value("${配置文件中的key}"), y5 r/ e* t* o+ C& F4 v' B
    如:
    & h3 k8 v3 q* G5 ~. L1 Y1 b& \% ~
    @Value("${password:123}")
    0 S; @. X  I9 m( A2 Z+ ~4 b上面如果password不存在,将123作为值
    6 x+ p' o3 z6 _8 v4 v* ]6 o
    - s1 Y/ |. L1 G( e$ X$ E@Value("${password}")
    0 K0 ]" F1 C0 w6 f% y5 {" I上面如果password不存在,值为${password}, _4 \0 r0 Y$ M- z

    ! x4 f9 w' n3 |) [( G  c. l假如配置文件如下! g* r+ m7 {5 O0 L

    4 w: D$ B9 Q; c1 r- m- fjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
      N3 J0 o2 O$ w5 W" R# H& v6 |jdbc.username=javacode2 O6 A; B3 c2 U3 b
    jdbc.password=javacode) \; [; u: ~* c9 ~/ n* P
    使用方式如下:
    $ [9 M: i9 i$ n) A! D$ {# V: s2 Q, H  j- ]3 \2 O
    @Value("${jdbc.url}")" v8 }8 }, K5 w: M' W0 n1 |
    private String url;
    $ j3 M8 o0 a  |0 {! _7 `4 u5 V
    ) `9 ~% ^& s2 ]6 t@Value("${jdbc.username}")
    3 ]7 F$ ~) o' E8 {: H- ~private String username;/ m' M6 |  I9 I
    ! G0 N% F% c1 A: p8 L3 ~
    @Value("${jdbc.password}")
    ! J- A7 t$ b1 r) ?& e$ @  {private String password;
    " ^6 R1 R3 D* c% b. C2 h0 o6 [下面来看案例: y, Z& |5 l2 J  i1 v: \

    " C' V" v9 X/ B7 x5 X8 J9 j案例
    & U9 I! m( j: g" h* r1 F7 v
    % w2 c  o2 I! j8 B- h1 R0 A/ c2 V6 Z来个配置文件db.properties  r6 n* X$ K1 }9 p6 `7 C

    ' g  `" ~$ w; j' |' U/ e' p& }! f6 o! djdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8% T8 |. _* V6 i" k$ [
    jdbc.username=javacode
    6 X1 S8 n: n* Ejdbc.password=javacode
    6 Q& H0 @$ l% r3 ]( \8 W& \来个配置类,使用@PropertySource引入上面的配置文件% B' W3 j+ m5 x9 d; o  n
    + V: G/ n- z- F  x: T6 {
    package com.javacode2018.lesson002.demo18.test1;! U2 `' K' K$ u* ^3 X- ]" q3 s" c+ J

    , J7 ]3 X$ U, k. ]7 [$ J+ Yimport org.springframework.beans.factory.annotation.Configurable;
    9 H9 @# _; y6 }; J7 Cimport org.springframework.context.annotation.ComponentScan;
    2 K6 t1 ^  f5 {4 `import org.springframework.context.annotation.PropertySource;
    ( Z  K- K* ~- F3 ]/ b
    - L( t/ _4 q$ m6 v@Configurable& n: J) ^4 i9 p; ]! _+ x
    @ComponentScan: @4 z$ C4 s7 i9 M% a' v
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})5 a1 T* V" p5 F% R" v
    public class MainConfig1 {
    ) z! a# u# J: n9 l$ P}# B+ t! e6 M: C1 S, a* T& s# c
    来个类,使用@Value来使用配置文件中的信息# J0 d0 Y. e+ |7 B# m9 v

    0 s, p0 E6 l  B4 m3 ~3 Z* I$ T% A, Kpackage com.javacode2018.lesson002.demo18.test1;
    8 q7 k& ^7 s# p0 D8 K, E5 a6 X9 o5 O- c3 e* `1 J
    import org.springframework.beans.factory.annotation.Value;
    , g) y  c' g& ?4 v' a7 dimport org.springframework.stereotype.Component;
    4 k, {7 R$ b  Y! @. L# A  a+ X, r" \8 ^: x4 D
    @Component
    : ~! c( l% W7 Wpublic class DbConfig {
    % h4 N, E5 d: n! P* d" f1 s* x
    2 h$ M" K! r' X& d7 j9 X* W$ v- ?    @Value("${jdbc.url}")- \0 V+ F- w# G% E9 e" b+ V
        private String url;3 o6 \0 H+ P9 h9 d* e3 C
    . ]5 r! P+ F8 z9 W9 p! w
        @Value("${jdbc.username}")1 b% Q; \) B2 V- I/ f
        private String username;, x# o: ^# j; l

    ) h5 R# @" v; Q3 ?0 Y, m4 O9 U( s, ?' B    @Value("${jdbc.password}")& y$ W0 k: A2 {
        private String password;: a6 |) G2 m" X5 W
    2 f/ j" ?3 x# ^, E3 o
        public String getUrl() {
    , {5 M- l: `' N$ P& I8 @, ?        return url;
    ' _7 P! y7 M6 |    }
    $ g; P! U0 b. P( {& B& d4 t
    3 m: S) j5 G6 P7 s: \! W    public void setUrl(String url) {3 m$ d6 `# z+ ]# D7 k
            this.url = url;
    . O) S, R, h4 i    }
    3 o) k0 K0 f. P& ]1 Z) Y
    ' J0 y* \$ `! P# c7 y    public String getUsername() {
      p4 s2 D4 w& I6 w! N6 D5 z- J, Z        return username;2 }0 d! d( [* d5 D9 u. \* @! k
        }' {; P: k2 G- l
    5 S1 [# v, W5 D4 ~+ S
        public void setUsername(String username) {. \7 u: v+ H" H" ^& C: g1 b
            this.username = username;6 R, u! a( t$ @% j4 j4 ?& w% n- ?
        }% z. P' A* K" g2 `
    # V6 p3 a7 O: _/ n  h1 b
        public String getPassword() {' L8 y$ L- [. u
            return password;
    * ?& c" `/ C1 q( U' L* u    }
      |4 v2 O2 B# u2 a. k$ A  [& y" j' }- d2 b
        public void setPassword(String password) {
    . c7 Y- d2 \6 J, ^" [6 f        this.password = password;
    5 j% }/ L9 S" J, Y/ V$ v    }
    9 {/ A) T3 y0 L+ h% \7 X
    , W/ S! ^& R1 I: Z* k    @Override
    / z, ~: G) B& D0 c% W  I# _# @( [6 g) r    public String toString() {
    8 O! X: Z$ I! L+ c9 m) @        return "DbConfig{" +" X/ X5 T- d7 g3 I5 [# R9 u* x/ k9 m
                    "url='" + url + '\'' +- u: ?; L* I* |$ D, i
                    ", username='" + username + '\'' +- K, M, R& O2 c8 Y/ s' Z
                    ", password='" + password + '\'' +
    - A# I# H8 |- l1 e1 Z                '}';6 K" @( R. Z3 d
        }( x- }" r8 m) j7 [* H: i
    }
    , }& x! v0 i: ~- @" h* k上面重点在于注解@Value注解,注意@Value注解中的
    9 ?5 t: C' T' h7 _- S2 o
    " Q, k$ L# ?1 A: @  k  u# {' Q来个测试用例. P( L* S% C* o# x4 ]
    % ]* A6 R8 F! |% s) U
    package com.javacode2018.lesson002.demo18;
    . \' V; v  z; }) s5 X8 K9 ^/ p- `  X2 m0 `- |4 H' j' q
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    ) r: w- f6 u1 T# ~/ mimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    4 R  d) j$ s6 R: J- z6 h9 `$ y# Pimport org.junit.Test;
    ) ?& U5 k$ n0 B/ O6 Kimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ( A/ t$ U: q0 W+ d0 z5 ~
    # }  x) Y3 b6 u' U$ K  S/ m* F; ]- I. \public class ValueTest {* @. x, \% ^6 ~( C/ C' w6 l% x
    ! G* T, B0 x# c, z5 v
        @Test8 \% O! A3 z1 W5 s% C
        public void test1() {
    . X% a# P" s0 [+ T* T        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    1 q' \8 M/ q! ]7 f" e        context.register(MainConfig1.class);
    $ \) A) Y; H/ m; c( f! V0 Q        context.refresh();+ U0 c) ~: y! Q2 l7 e4 m* Z( X/ @
    & x# J0 J5 ^: b5 M) u
            DbConfig dbConfig = context.getBean(DbConfig.class);0 @( t, L: F. {# k- e
            System.out.println(dbConfig);
    3 F3 x) k) T0 w  H3 @    }
    / T2 _8 a, P$ @! l& o: H}
    2 ~9 M  `. A/ W+ a8 s+ S0 g7 t运行输出
    ( f& F) g. W6 `  ^- e5 v  x" c" S! V2 y! u$ i
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    / w2 v5 k) m/ S上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。* y. d; g; y  a" r3 C& H

    , q! A4 @* ?: B0 W. a# x& C@Value数据来源
    , P# o) d; G, d" M8 \& K% R2 A7 B4 ~1 C: I3 w2 z
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。: c: M3 O' C( v. @  Y
    % ]! r2 D, N4 j' u; w' d
    我们需要先了解一下@Value中数据来源于spring的什么地方。6 |" q. f) l1 g  v# q4 H' `9 G* l; y

    2 u$ j4 I( l, D7 R  Wspring中有个类7 a5 ?; {+ _/ Y% S& i
    - `. g# ~- v3 j! F% {; x+ X
    org.springframework.core.env.PropertySource8 A0 b6 X; y  R  H
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息& |1 J+ @$ R" O' J& M& K; c
      Q& ^/ m- H& m: S
    内部有个方法:
    7 O" j6 g' U- x3 H/ r9 ?( n6 D/ T
    public abstract Object getProperty(String name);; c$ G/ M6 `5 _9 m4 W
    通过name获取对应的配置信息。" }$ l; Z1 t; p9 O4 ^2 b" J2 A3 J

    9 o, t8 A% u, g系统有个比较重要的接口
    * g/ g" o6 x  t- {% ~
    9 s8 i% t+ V8 Rorg.springframework.core.env.Environment
    8 n' k" u( ]# @# X3 l% L, q, ?) n4 T用来表示环境配置信息,这个接口有几个方法比较重要; j8 a; i( W4 I8 F- F1 f* y6 z0 Y- \

    0 P* w6 X4 Y3 C( AString resolvePlaceholders(String text);7 [, E! F7 P; x, `% v
    MutablePropertySources getPropertySources();
    + v! z6 u; Q8 i$ y- N# V0 k4 aresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。; B# Z- p7 Z, A) q& f, @
    / ~$ v! J4 A* r
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    : V( y& I7 z2 e- l
    6 L# U9 J* ^* u: n- T5 J  hpublic class MutablePropertySources implements PropertySources {
    % V6 }# h& T, i9 T$ M7 t0 ?% F# k& d1 t, H
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    1 P- R) k; a% V( d9 I
    & x% N* |/ r; W$ l% g; O, k}( Q+ w: M. g7 j' R$ a4 L$ ?
    内部包含一个propertySourceList列表。1 b$ i- p6 Y& q8 E, J1 q
    0 Y9 w; u! s, d  l1 N- X
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。/ A  \. Q" l0 ~0 ]; _

    6 o$ p6 N$ C. J1 K9 Z9 A大家可以捋一下,最终解析@Value的过程:
      o  R' h: @- ^( j: r- O, ^4 W2 l6 X" {- d7 c* V% T( m) s
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析8 d0 Y( L1 k& t
    2. Environment内部会访问MutablePropertySources来解析
    * D( \; ]  `' n  |! N+ `3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值! X' K! z6 ~. ^5 v
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。' D/ L  S; f) X/ A6 o
    5 s9 w: d1 y/ o0 l
    下面我们就按照这个思路来一个。
    / x' F0 M+ j! k3 o* l+ h1 |9 W! G" |! ?+ z/ q; U; O
    来个邮件配置信息类,内部使用@Value注入邮件配置信息  n' C) H6 T, @

    % u: z( ^* w( G& }1 F- epackage com.javacode2018.lesson002.demo18.test2;3 T, a! s3 {4 i2 ]/ y
    ! L/ e! j# y4 l1 U' w) p5 @/ V) {* y
    import org.springframework.beans.factory.annotation.Value;
    6 F/ R  T6 k  l2 g/ uimport org.springframework.stereotype.Component;9 D9 ?' R! f1 v! G: O4 V! T4 g

    # R8 S/ s( z# c0 n5 P/**9 V7 N( o, a: _
    * 邮件配置信息
    / \" b: h- ^7 ?% o$ c6 V& X) U5 V */
    ) t% P) d$ n7 J% A/ C1 _1 H  T@Component
    4 ~. v, H6 p7 a+ ?public class MailConfig {
    . H: X1 i  G0 w5 x) o3 s5 N( C5 {8 s' ^+ k
        @Value("${mail.host}")
    " b7 N1 P' g  H  l, u) k" H6 {    private String host;* b" c! J7 H3 U

    - ?* N( [. y' @) g( L! G$ @    @Value("${mail.username}")2 p: P( x8 X! y, W0 M
        private String username;
    7 x# W6 E1 b7 t* g3 p3 }! L" o1 J8 p/ Y* L: u2 r
        @Value("${mail.password}")# H5 |" |1 f; z3 q, l, s( B
        private String password;
    0 f/ i- n5 z! f/ U: e* N
    5 E) h3 K- R0 }& {    public String getHost() {; q! U' y: {, b1 Y6 D
            return host;' x; W1 d" G& m# K# a' ~
        }
    ! ]+ G. [! W; j+ F# w
    ( D$ R' F0 Z. u5 m    public void setHost(String host) {  Z" z4 g0 @0 {. a6 g# p2 Q2 g
            this.host = host;
    6 @6 O+ A8 s# h4 g$ K1 `    }
    . C3 I4 |" ~8 N* @; N) a/ G; G% a. o# u' m/ J
        public String getUsername() {6 ]2 r! d+ d' i
            return username;1 a7 W' E4 d/ L# \6 z. e
        }2 |3 H2 w' z" A/ r( k

    & d0 g3 j+ A' X4 j# t* r( h6 i0 ?    public void setUsername(String username) {$ U8 [6 x/ K0 l! @, _! Y3 u
            this.username = username;
    $ V) y1 L/ n7 M* X0 @    }
    6 Q: U! L8 [5 m; A& @. w
    " \$ j) s! Z  i, h    public String getPassword() {
    $ k! P$ l$ s' K) M! B        return password;
    " b0 C% _$ ]+ x7 Y3 u& I" e" W; W6 M+ N( _    }
    + K, H5 Z* A1 Z+ t7 e/ D  f
    0 h( h& Q" c; ~( q# T+ \    public void setPassword(String password) {) k; p3 o8 k  S, ^
            this.password = password;
    / m% f$ l4 @* S/ X: o% o6 z    }* n! W% p7 I  v* w

    + X$ T- \% A9 C9 \. A    @Override
    , w  v7 h9 X% z1 |! X( c. w  v1 \    public String toString() {4 Y. W' |3 z( t2 g: K% s$ M
            return "MailConfig{" +3 |3 {' [8 N0 b- J, X1 @7 p
                    "host='" + host + '\'' +, ]# ?) A; A4 N, U
                    ", username='" + username + '\'' +. a- @. A9 v/ j3 F+ g4 u
                    ", password='" + password + '\'' +) \2 W) Y: ?  h# c" W
                    '}';
    . o9 D5 F; K+ \# P! u8 s" c; B) m    }
    & X/ N! i) V4 s% e6 N}
    $ q5 F" h$ e/ z8 s7 P- x再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中# [( Y3 n2 z- j) f

    - a& G3 o% v7 ?- e) l! _1 Y; u; y6 Upackage com.javacode2018.lesson002.demo18.test2;
    & C8 D# ^7 m  d8 K7 s
    : c3 w- g4 x: q% x; Q, gimport java.util.HashMap;
      F, K6 U9 y3 i3 w0 Ximport java.util.Map;& M" \& T4 o( i- G+ Z. W- l' ^* T
    % q/ c8 u, @! h1 f
    public class DbUtil {
    + G9 I4 ^/ _# ~. \# e" |& p    /**& h! J! O2 B4 C8 p
         * 模拟从db中获取邮件配置信息
    * _$ a0 }2 I; j* |2 v     *
    4 y: r$ X1 i8 X  f3 I4 `* `     * @return
    / j( M3 b3 W% F# e     */3 F  c, V8 w0 q& J
        public static Map<String, Object> getMailInfoFromDb() {
    ) ?+ L: k! a, a$ d7 E        Map<String, Object> result = new HashMap<>();( z# |5 K/ i( C9 e, y7 F% |; Q1 p6 ]- M
            result.put("mail.host", "smtp.qq.com");, q' ~0 p) t1 ]! O" U
            result.put("mail.username", "路人");
    9 d, @# a  j, n/ O5 p: Z3 u        result.put("mail.password", "123");
    ' R; X% m2 |2 D# x        return result;; v5 S; q4 w+ ~, k: }. X
        }
    8 k  F. E# T, q/ Z9 G( _/ C}( o% z# T2 [# o" }2 k
    来个spring配置类8 r2 v* u& e, i$ S  D% R' P* c3 q

    ' d. M+ @9 {7 j" g' u4 S# upackage com.javacode2018.lesson002.demo18.test2;
    5 M- e7 p- a6 ]  [) G; v3 {8 F' o+ q
    import org.springframework.context.annotation.ComponentScan;0 ?3 y8 x& w6 g
    import org.springframework.context.annotation.Configuration;
    % d4 G3 I4 j7 L0 n( q  ^% d; ^. ~. `" U/ f# J( |9 C
    @Configuration, n9 ?; @5 `' O% B; Y; G  a
    @ComponentScan* b# U( ~0 E2 @( k- P
    public class MainConfig2 {
    $ a4 S- b; d6 E4 E* O3 o: y$ I8 c}
    " U8 {% x! f; i) C下面是重点代码
    . P1 v' @+ A8 @& ^/ N# I
    " O; a% {/ Q- L3 b! ~@Test' z3 z# z% R8 I* p0 L
    public void test2() {
      i- I0 B+ t3 o# l% n2 g    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();! B1 f% `. ^9 s! M5 n, g

    , C$ }# O3 |8 v. P    /*下面这段是关键 start*/, Y, C) t( I' g% D6 k9 x0 K
        //模拟从db中获取配置信息
    1 B) G2 O* z" L. J( ^    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();" Q2 z0 v* h  J% b1 g. i5 a' ?
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 B+ `! q- i. _9 Z
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ! X4 R2 V( Q9 Z( w, [    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高; H2 D% c+ c6 N! H- Z. B% x- Z
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    2 e: J8 v: X- P( k4 a    /*上面这段是关键 end*/
    9 R/ B/ w# J8 |
    & i0 [! n* Q9 M' w4 {$ E. A! r+ z    context.register(MainConfig2.class);: m) ^5 T. h' w- y, W
        context.refresh();8 z0 d) C/ r- o+ }
        MailConfig mailConfig = context.getBean(MailConfig.class);
      A' B0 ?0 ?; ?# z    System.out.println(mailConfig);, T$ J& ?- ]6 o# C9 ~0 Q% b
    }- _$ Q) j* a; P/ @  v4 q
    注释比较详细,就不详细解释了。
    4 e! m7 X- v4 j: e' L# {8 \
    ! }- r7 N  f) _* m' }- ?直接运行,看效果
    : V" p" M5 Z! m( y, K7 J2 `& p2 H# s/ D$ }+ o) z  x; O
    MailConfig{host='smtp.qq.com', username='路人', password='123'}  @' x" u# V: }8 v2 Z9 G
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。4 R+ n7 f0 X" ]
    6 ], n1 @" l7 }% M+ t
    上面重点是下面这段代码,大家需要理解
    2 Y. }" I7 d& ^  z
    * v; H2 y' _* a& w/*下面这段是关键 start*/: X9 A; \% ~* K% J6 I9 O
    //模拟从db中获取配置信息
    # I6 q7 M8 G! G6 |3 m" AMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ) \4 U9 l! Y" k7 F+ }/ o! D4 d//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    7 I' w6 _8 V- \% n% F$ x* uMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);; D' T$ X# ?3 l; d7 x$ O; }
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高8 O5 H( j5 B9 i5 C* Y/ ~2 W5 l
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    + k2 X1 |5 d! V9 L# p9 J1 e/*上面这段是关键 end*/
    ( b& C, Y( M) C* @* m% u/ i咱们继续看下一个问题
    7 [% B7 g/ C- Z: a; y9 C$ a
    ! W, J9 |0 N+ o" E- |* U% I如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。$ P/ X1 f' y' n% b0 i  }

    ( q# {! D/ x( c- Y@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。2 e- N- t! `( d  a  t

    6 m8 P/ P1 J" ]3 U, T* u实现@Value动态刷新
    & t9 C5 g  T( R% s% I: D3 H- Z0 H0 I' Y! P2 b) P1 \4 f! m
    先了解一个知识点
    6 {1 W2 _0 N, U& |: v! e; o2 G2 W( y/ q( M
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。; P; J4 _1 \; P2 R! L0 w, z  X

    ( E* t2 p! b8 Y这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解4 ~9 h3 \5 U; T; X$ U2 o

    ( b+ W: ]' H/ o  q% ?bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    $ P7 j: h. N! P- _/ @  m6 C9 I0 {9 L! q+ @. }
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    4 t$ ~( c4 f2 M: m$ o* N这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    6 Z* Q4 p$ o% h% j$ Y, ]4 V
    7 N9 U# ^1 i$ [# Epublic enum ScopedProxyMode {) ?$ `, Z. g6 ]1 }7 b7 r1 w5 }
        DEFAULT,
    3 i* w; b3 R7 f    NO,
    / Y0 x  y/ {) L, E0 c8 T" I    INTERFACES,
    8 B2 V% E! `7 M7 ~    TARGET_CLASS;
      Y! p. R- t9 N- ]8 b% J}- |& N( ?3 n% U+ A: t5 H, R
    前面3个,不讲了,直接讲最后一个值是干什么的。
    ; m6 Y! y) |$ T5 \! w3 @: t: T  q; Y' I" t+ W: ]6 h5 ^( V% I
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。) a, ~8 H8 @+ U8 y! G- O! Y
    4 n6 _4 ?3 T4 [- R$ t0 p* O/ v. {
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。+ `/ O8 }+ ~* K, i( r' N8 t# u4 b
    * a# |; V& N* Z8 O
    自定义一个bean作用域的注解  J* {& S5 {: x8 u1 K% U& {& \
    ! m, E. O- ?1 G$ ?* H8 S
    package com.javacode2018.lesson002.demo18.test3;# {' M" ]% F/ G( N* D) G
      R4 t$ ^% j& e* I0 ]
    import org.springframework.context.annotation.Scope;
    0 j0 a* q' r  F0 x( i+ P9 timport org.springframework.context.annotation.ScopedProxyMode;
    $ y( D* R0 L. H0 P- Q, e
    : |1 g* @& n' Uimport java.lang.annotation.*;" s3 ]  m4 P6 v+ s+ \6 t1 e
    ' p  Z5 L4 ^( v% w5 ?) \# Z  X
    @Target({ElementType.TYPE, ElementType.METHOD})4 L, b% J, n1 g. O# P
    @Retention(RetentionPolicy.RUNTIME)/ T$ p4 a+ d3 v( L
    @Documented9 e/ `# |! P( R7 j; v  T3 J; }
    @Scope(BeanMyScope.SCOPE_MY) //@1
    ) B) g! x7 K/ u0 x" cpublic @interface MyScope {
    ( t' t7 U. O8 O! h    /**
    . j+ U4 a7 z  I9 [+ G. H. ]     * @see Scope#proxyMode(); |$ u# b( G0 K8 v
         */
    : I0 [6 \7 A# I7 E    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    4 y5 w, m7 p8 d% C9 \}3 o( s/ O1 I" v, N4 u/ m0 s/ [# e
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    5 D; ~- P0 G9 F7 {
    2 d7 V6 s1 H" }# e1 {, r@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS+ I3 C+ O* W) j' ?. ^
    4 M/ ~* V2 q8 C9 q9 j. ]) S
    @MyScope注解对应的Scope实现如下
    $ _, z) r9 z: u% j9 e! u( X( r) u7 r6 i6 }
    package com.javacode2018.lesson002.demo18.test3;3 h; m2 X; ^! N; q! O

    1 U: m( N0 w- [( Q7 Pimport org.springframework.beans.factory.ObjectFactory;
    0 k% y- y3 r% V/ Timport org.springframework.beans.factory.config.Scope;
    5 N$ D6 C5 P: y* Dimport org.springframework.lang.Nullable;, P9 S. [$ U6 X  ~5 x/ Y

    ) s* t4 Y! j, M6 p5 f/**' L, g# j5 N1 j2 N3 J
    * @see MyScope 作用域的实现( H, B" V' m& u
    */
      ^- u+ J. U7 \' T; D, dpublic class BeanMyScope implements Scope {
    * Y2 L" ~, f" o2 z: ~# N$ C5 W
    ' D0 q  C% e$ c) W8 u4 l    public static final String SCOPE_MY = "my"; //@11 b! D) N* c. H. p+ E

    ' m: E0 l* q9 d. r0 z4 q' Y4 R0 }) n    @Override  E7 F) X; }' k  S. d/ |
        public Object get(String name, ObjectFactory<?> objectFactory) {
    - b1 y7 z0 {6 j: D9 W+ J        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2- I) \  R0 V2 w2 y0 |- U
            return objectFactory.getObject(); //@3( G; ^5 }7 a  }* Y7 ~5 `
        }& M6 _' X6 m6 T: M9 F9 r/ J

    2 |3 X5 t; B0 b0 r/ _: t4 t    @Nullable
    , [4 [# Q1 i) a8 I/ F    @Override; ?; s, R" z, K9 [
        public Object remove(String name) {8 s0 c: z" U' x1 M* O! g9 \/ Y2 \# \6 l
            return null;0 x. N) T  ?' e& u1 F7 E' X9 m
        }9 [3 ]3 l3 k  s& x

    8 N; \) v+ b* c$ j    @Override
    : Q3 |8 s- a; O. C6 Q    public void registerDestructionCallback(String name, Runnable callback) {
    : O& t" x" n6 h- W  B% |5 p1 ?& ?" U# o# G- E1 r
        }- C- E+ m. D: f' j

    7 q* D; q8 P4 p5 U, f    @Nullable$ [% ~9 ~$ Q) b! M' L2 m
        @Override% z: x8 I7 r, A
        public Object resolveContextualObject(String key) {
    * A1 A+ @" `+ s  _7 U& v' W        return null;0 P0 b2 s- q7 p' F/ A/ ~: _: }# q' n
        }
    5 }" {7 Z3 s8 m% B' E5 B% U; K& x/ B6 t/ r
        @Nullable4 |  M# X* v8 [. o8 C) V  H! N/ P. V
        @Override2 g# ]$ ]! j) @  r0 ~8 A) f
        public String getConversationId() {
    / q  Z  ~9 J0 j. \        return null;
    / a: m8 u6 `% \! G0 L: n    }
    7 `9 Y# X* F$ p% e}
    3 z3 Y5 n: l# r% D* s" m7 R@1:定义了一个常量,作为作用域的值
    ( u8 K6 P( P2 \% ?0 A% A% h* F( n% M7 N6 L
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果( g; P  W' p; m% e# {
    6 Z9 {% Q0 c, ~! H) M
    @3:通过objectFactory.getObject()获取bean实例返回。
    1 T' b6 o+ d& @' o
    ! D% _7 b7 u6 C2 h; `, A下面来创建个类,作用域为上面自定义的作用域; V# V( Z, p2 B1 f. `' }
    9 d) S8 j. D7 G* ^+ V
    package com.javacode2018.lesson002.demo18.test3;  T& m) ?" t6 ?5 |2 W! j; y

    8 A; L6 R# ]. y1 Vimport org.springframework.stereotype.Component;
    2 `' @- r7 h! c1 v9 T6 ]4 m, ]& V# k) {/ |; _3 ^$ z
    import java.util.UUID;
    6 b5 l( Z7 h7 Y" ~. V, x$ X
    4 D! m! v7 W" }5 _# X4 c@Component
    & p' @7 p! [' x! J. `9 b& C2 L@MyScope //@1
    - a7 g- M' L/ A' x/ ipublic class User {
    , A/ `( L! o1 {8 _3 x$ J7 G* g8 O" }- L6 i
        private String username;
    & D! x  M' |4 r
    1 z1 z4 o2 {7 G/ Z. i5 [1 M# o$ D    public User() {   P- e5 X- Q& Q! r( H
            System.out.println("---------创建User对象" + this); //@2
    , [0 \6 e* r% ^( `! u        this.username = UUID.randomUUID().toString(); //@3
    ) B8 H, T4 m$ }    }6 ]4 w% ^! D+ O2 r, e
    % e/ p8 f; S' _$ W
        public String getUsername() {
    . L. i% F% r) ~. }' p- F  [! r        return username;
      Z) T( K" ]  ?" y, o; K; M    }- ~  B2 {0 V% t  A" f; l1 o' X
    0 V7 d5 `; _' Z
        public void setUsername(String username) {' m' C* ?3 I6 ?1 R
            this.username = username;
      P2 e5 @0 j6 k; ^    }4 z  k# S  ^0 v  ~2 s( i
    ! m- @+ ^# C7 c; ~  x. u0 O& }* x
    }2 y2 P. F* C. |
    @1:使用了自定义的作用域@MyScope5 L' o2 f' W( B9 D

    ) N$ g7 y- @7 U@2:构造函数中输出一行日志6 A; k/ z+ j6 N( E% F  G7 `* h
    2 U- i3 K0 S1 F& Q& E9 ?8 f
    @3:给username赋值,通过uuid随机生成了一个* o9 j" s# J! B

    . g# ]: A/ f. v: j$ L  B# w0 S) g$ k来个spring配置类,加载上面@Compontent标注的组件
    " o  e( N6 k8 X% O  ^! e, }# G$ h& j( f. E( M5 L
    package com.javacode2018.lesson002.demo18.test3;2 Z6 }( @- V) B% r% B

    ) I# T: N' j, j5 N$ mimport org.springframework.context.annotation.ComponentScan;
    2 N; v% L% `1 k3 F- O* S8 aimport org.springframework.context.annotation.Configuration;% w! U$ M# o, S% d# q
    1 n: o4 }  u$ X8 Q% u. W7 t. U- h. a
    @ComponentScan# B; m" i, T/ y% f) W
    @Configuration
    1 ?3 q/ h9 X: z9 o* G2 Zpublic class MainConfig3 {: b7 V- }7 |- r1 x, D5 F
    }
      r& g1 }& M5 H) N; L0 v& A下面重点来了,测试用例# x+ m2 Z" Z+ P  i# f2 M
    5 B8 q  w7 _- i/ b- D9 a) ?
    @Test
    : W) n! b$ A' g% Z. g$ Z9 o, mpublic void test3() throws InterruptedException {
    % z; s( r) E2 J2 g    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ! \0 e7 {7 N0 v- Z& K    //将自定义作用域注册到spring容器中
    ( o+ e8 g3 n6 F% [; X1 m1 f9 ?    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1/ q) I* L0 e7 j& ~* k2 Y1 n3 ^
        context.register(MainConfig3.class);
    # Z- m7 S2 g; j    context.refresh();' u0 ~& r. L* E8 K- B% c, z

    7 M  f5 X9 H- ]1 e* V    System.out.println("从容器中获取User对象");
    . h. |( [9 V- |8 S' W- |  k    User user = context.getBean(User.class); //@2
    % j6 `7 N; P1 j+ ^; t# j# @# c    System.out.println("user对象的class为:" + user.getClass()); //@3
    1 B" z( l0 S9 A
    * _) [) l* a' Z, F0 I2 `9 ]$ k    System.out.println("多次调用user的getUsername感受一下效果\n");
    + d* e) F2 {$ D    for (int i = 1; i <= 3; i++) {* z; l4 |- g& n1 n- l
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    4 F5 W8 Y* j+ }3 j0 V) \        System.out.println(user.getUsername());
    % ~1 P5 A$ N! Y8 W- i8 w        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
      d# l& a8 ]. p, u    }- B# G- A0 p& Q( d$ {
    }
    ) a; z( ~- o3 L$ Y' a0 g/ J- g@1:将自定义作用域注册到spring容器中; l! o$ K, c8 ]) T, f
    . n+ n8 |+ _. z8 I$ Z7 a8 [
    @2:从容器中获取User对应的bean
    , n9 s8 Y- M. s8 X7 \( W# }; o: o- {8 O, A
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    $ a; P% g6 x. E$ V9 n
    ' \/ l: u1 @9 G0 V* Y4 H9 \5 S代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    + O$ d, Q" A# `& a% G& U+ U# _5 q- z; S
    见证奇迹的时候到了,运行输出% O$ B; \# }4 U$ l: c5 w' W
    / U# j+ c9 x7 c' v9 k
    从容器中获取User对象
    - j2 H, ?% o! z, U% N* ?' Iuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331278 s7 s) f7 @+ K9 U6 N" V' Z  n
    多次调用user的getUsername感受一下效果
    2 H6 }8 Z' W, ?+ i8 A7 F2 F' V& q# [, C$ C( B$ j/ P
    ********
    , I3 P+ h4 G6 z. w第1次开始调用getUsername" N: f! c! k4 T+ `( d
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    : v: \0 E  H* T; b$ q6 L# I---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
      R0 `& t+ H: {; a; j7b41aa80-7569-4072-9d40-ec9bfb92f438
    5 m! U3 |+ A- J5 J5 p: R) U第1次调用getUsername结束
    % L' ~+ x$ P3 t  w********, h% m* [6 a" n; f5 Z0 a

    * e. |3 r8 G  F( y+ r$ q' h8 ]7 r, _. d9 [********
    0 E& }2 d) R# H* y6 j) O4 P第2次开始调用getUsername
    % J9 b8 {. ~2 z6 U' TBeanMyScope >>>>>>>>> get:scopedTarget.user
    7 K# X. L% Q7 t! v& _---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b! J3 c+ d" D5 ]: |2 B9 [
    01d67154-95f6-44bb-93ab-05a34abdf51f
    - F& e5 s! v. o- L第2次调用getUsername结束
    - J+ H3 H& _. [: c, F********, G5 T$ ^+ P7 S; N  f. h) o% |9 P
    2 p, r  r: N% {7 I0 Y' @, `4 J
    ********
    ! a7 A* {8 k5 {8 k+ B第3次开始调用getUsername
    ( b6 c+ ]- X" H9 ?BeanMyScope >>>>>>>>> get:scopedTarget.user& ~5 r9 z1 }5 I
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15, F8 b" H5 I- E
    76d0e86f-8331-4303-aac7-4acce0b258b8
    7 P, @  Q- ^$ V4 T9 p/ Q8 F第3次调用getUsername结束9 t& k( G& N7 s/ W% C% q! V; |
    ********
    , P7 v* J, H/ h, e! @& _从输出的前2行可以看出:
    ; l' F3 B9 n7 O$ p# K' [3 v/ ]/ }8 y( N6 c9 F
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    # {4 G- s$ F4 q7 z8 Y9 q, N" X4 i
    ! ~: w# y. d6 v第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。' K+ v  C: O/ B8 r% t
    $ l) j4 A1 L7 H" s& y
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。6 ?6 Z! C5 k6 R( k

    - h- t' ^# P4 O' |; }通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    2 u  `! T# _0 S( q9 A, c, U; U& `4 e8 j+ D8 r: u
    动态刷新@Value具体实现* e  y6 H+ \( V) N2 G/ ^% ~
    7 x- a& y$ c) J& u
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。; Z! B- a( w5 ^
    4 A- ?, C  T4 L8 K
    先来自定义一个Scope:RefreshScope  m0 Q; X3 D8 [, I

    ' x( v, ]* t4 X3 ^7 xpackage com.javacode2018.lesson002.demo18.test4;
    ' z' I: J- N6 l4 V9 ?7 q: }/ ?! m! j/ M9 e
    import org.springframework.context.annotation.Scope;9 ~2 g0 G  p. A  |" ?1 d7 J6 t, }
    import org.springframework.context.annotation.ScopedProxyMode;
    % ?. p! h5 g- i! M7 n  x- n* R
    9 D' q' I. Y  p7 Zimport java.lang.annotation.*;  ~) `1 t0 Z6 ?5 I: D  E% o% B

    3 \" r2 w" c( Y& K5 z@Target({ElementType.TYPE, ElementType.METHOD})
    ( y9 `5 N3 A- d# S. q@Retention(RetentionPolicy.RUNTIME)  e; I9 e5 b/ ^1 {4 n: ?7 C% Z
    @Scope(BeanRefreshScope.SCOPE_REFRESH)7 m1 s" S3 z# l( `
    @Documented( f8 o: y; b" c: V5 C
    public @interface RefreshScope {
    2 Z1 m7 D+ N# d, C! n6 p    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ) V. d+ K+ |# [, I/ K& r0 |}9 a+ X, v& ?9 h8 ~) q. N5 R5 s+ I2 Q, W
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置3 x+ a$ z- E6 Z) G& T
    / j' n- p% _2 [6 [
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS* j) V6 K- W9 T& G5 w
    . J  z$ U* v' g5 B5 M8 m! V
    这个自定义Scope对应的解析类
    9 K# J/ a' @, \, ~: ~" K- c  y' B9 m0 C/ \
    下面类中有几个无关的方法去掉了,可以忽略
    ) u: _; ?/ W6 \6 V- C! U, j
    3 l& y5 ]7 P% m3 z% m4 Lpackage com.javacode2018.lesson002.demo18.test4;6 |. `# l# ~7 C( Q

    + X. x; U. [' a1 {2 X6 ^2 Q* ~8 |, e+ s$ d; ^
    import org.springframework.beans.factory.ObjectFactory;
    $ {. H" Z( H2 Mimport org.springframework.beans.factory.config.Scope;
    3 k  k! D% \3 L# o( D6 T& Rimport org.springframework.lang.Nullable;
    0 H: v! d9 Z9 m' E- V
    1 }% h7 Z+ S' I. [! {6 b/ Timport java.util.concurrent.ConcurrentHashMap;+ |4 P0 t$ y; x0 C9 O
    2 d. b3 o9 L! R7 B2 d9 y
    public class BeanRefreshScope implements Scope {
    : X- c+ A& L' g, q; `5 {( W; m1 o9 ^' ]/ `/ m
        public static final String SCOPE_REFRESH = "refresh";
    - O* ~' O8 ]8 k; E
    . A5 U% \  W$ G% E, _    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    ) b. ]2 P1 z* K! B- F
    * i8 M. N0 C. Q% X/ c9 j( K  n    //来个map用来缓存bean. }& d9 f9 ]; I0 t, g0 l
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    ) e# r& v% g. ]/ P5 J8 j
    : k* D8 i% @4 T9 t( @: ~/ P    private BeanRefreshScope() {
    * E' j: ]9 L3 G  P! i; ^    }8 y9 ^7 c, M* C

    5 C1 ~2 F$ B5 k7 s- Q+ q* ]    public static BeanRefreshScope getInstance() {
    / {! _: Q, \; K* {        return INSTANCE;( n4 c! ?6 H& x
        }' w, I: y- h0 {4 D8 g# n+ m! w% ?

    ; F" g3 i) D* _& I% H9 O. N( h    /**" ~5 e- {  R8 H  r
         * 清理当前1 P9 ~2 K! @. n7 B. P0 B7 r
         */
      c9 }( F$ }9 a, c$ N    public static void clean() {
    ; o: U) }5 }1 F4 f" n& f" Z8 t        INSTANCE.beanMap.clear();" }3 h; L, }6 Q" S* B
        }3 r, m# L, G4 V4 c8 F
    $ s5 q. W2 B: x9 [' ^9 B6 `; U
        @Override
    6 v0 d; U4 X1 K    public Object get(String name, ObjectFactory<?> objectFactory) {. E4 k: K  `' v  t# o  @
            Object bean = beanMap.get(name);
    5 F  X( O9 q# i) X0 p        if (bean == null) {
    # `3 C& y$ @7 K$ d            bean = objectFactory.getObject();
    ( L, Z; u- F2 j3 L& O            beanMap.put(name, bean);/ I$ _& A+ m* B( N2 o
            }
    ( k# p6 m) Q  M) v        return bean;7 n- ~% e: x6 h& H5 |+ }8 }0 X1 q
        }3 n; H+ L: A& c. c
    - ?* `0 v' ?/ K& `) t- y& c+ |
    }  M: ?  f$ p) B* j' J6 ^4 D* h
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中" ?6 A* }6 }8 s# ?/ B4 u
    # u7 R& y, I0 ?7 O1 c- ]2 X* `
    上面的clean方法用来清理beanMap中当前已缓存的所有bean$ P# w# X8 K+ b' V; f

    8 j- v1 ~/ ?7 X3 I来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope& q2 g4 m# s6 H, s
    * ^, X0 f, _" O
    package com.javacode2018.lesson002.demo18.test4;: ]6 b2 Q8 M* U6 N
    9 ?- q/ }$ H/ G& ~- m
    import org.springframework.beans.factory.annotation.Value;$ T3 U6 C* I8 C" ^/ t' b
    import org.springframework.stereotype.Component;; m' M, a# M8 U( U% d( R
    * [" c( a9 s# v2 ?0 r  }  ?$ @
    /**, n. f5 Z0 \; }
    * 邮件配置信息
    . b. P1 n8 Q0 a# r */
      T4 o% I1 ^5 O0 K( o) O. M6 Q7 J; Y@Component* P) h. e/ V' h; ~5 p
    @RefreshScope //@1
    " L/ `6 V+ ^8 F& z5 bpublic class MailConfig {
    * ~1 o: }+ y/ M3 I8 |
    # M$ \8 S0 x, G- O% B- l+ X    @Value("${mail.username}") //@28 B; T9 R/ X# |* m1 q5 t8 s
        private String username;
    ( q: ]8 \  H7 k# |4 h8 @
    . Y1 ^% K5 f& Z4 ^    public String getUsername() {7 q% s; G8 b; T4 Q1 M
            return username;
    1 W: P1 B4 Y* d    }
    4 u( N8 \& Q4 r0 o6 H4 [- o1 m/ X/ x5 |
        public void setUsername(String username) {
    , A* E* U& t6 d( B/ Q2 ]        this.username = username;
    # f. N  O: r- q$ v+ s8 i/ m9 K, d$ x    }3 T- \: j% r, c" Y* T
    ! n3 P, H  i* @* r( m4 h! }( c
        @Override( E0 _9 o/ k: t4 h/ p
        public String toString() {. E& s/ d. Y: q
            return "MailConfig{" +
    + K( a6 }& F# F4 Z                "username='" + username + '\'' +
    4 o' b. j0 N% E, ~. v7 @" H5 Q: }3 c                '}';
    5 f4 d5 Q% C: Z8 v! h    }$ O' W+ U$ _( p
    }6 X0 D5 G) \: F% \5 c
    @1:使用了自定义的作用域@RefreshScope
    . J  c: Y  o8 G+ b. @- }/ m4 P2 E7 ^. p6 u4 h
    @2:通过@Value注入mail.username对一个的值
    ) T/ ^0 @: L- J$ \4 `$ b$ _" |7 `2 R
    重写了toString方法,一会测试时候可以看效果。
    ) M# R) A$ Q* R/ i
    , Z& F: _" X+ R再来个普通的bean,内部会注入MailConfig% V  Q9 y8 `/ {0 F3 M# A
    # k2 W8 N3 m* I  _( s3 x4 }! l
    package com.javacode2018.lesson002.demo18.test4;
    8 S4 Z1 J) a! O% l# D2 V8 S, v  |3 g
    import org.springframework.beans.factory.annotation.Autowired;
    ( i3 S! e0 k! Vimport org.springframework.stereotype.Component;9 z6 L4 L+ v: p4 R

    5 _2 C  @6 w' n7 u: B; `! i2 p@Component
    ( ~: T. a- \$ z4 H, E! ^/ r, vpublic class MailService {" W& H8 E3 U9 h% h( T; P
        @Autowired
    9 s! ]% t* \( s4 \8 O    private MailConfig mailConfig;/ _/ F5 A% l" G+ G9 X, w, F7 \
    . G+ k3 V2 m8 B5 n0 |
        @Override
      f. C5 k; o7 d% o) u% X    public String toString() {
    $ _& K/ V% A4 n# K" B* a* v        return "MailService{" +
    % x1 N" }8 k, b                "mailConfig=" + mailConfig +
    . b) M9 c, X8 r) k  q: s# B                '}';
    * u4 E4 a; H" o' m    }8 K" X! L* Q4 h  X
    }* P# |2 q. e. H- n0 \
    代码比较简单,重写了toString方法,一会测试时候可以看效果。- }$ n; F! {/ o# w% E/ W' n

    * x+ s1 j+ F( E3 S7 F' U! W3 K! }来个类,用来从db中获取邮件配置信息9 Z5 [& A- x9 I* c$ _

    5 ?3 g' w9 R- h, }+ Y4 w7 S+ Dpackage com.javacode2018.lesson002.demo18.test4;) v7 p3 v  Z* e& V9 }
    9 \# Y6 o% |! Y+ r  O
    import java.util.HashMap;) q# E, C: _0 ~8 N8 q& R& x
    import java.util.Map;
    ' I# `- J# c& I" l: ?import java.util.UUID;
    & b; d  r' b' q1 H, c. W: m, p9 `, G( ^+ C( @3 A  p2 Q
    public class DbUtil {
    ; h. g' U6 }* v, v& Z2 v    /**
    9 o- G8 i, m( I' L     * 模拟从db中获取邮件配置信息
    7 K' {+ @% I( {0 U1 F     *4 @; W) d* w: |0 p4 S7 y
         * @return
    5 _  u9 @7 n3 ~  D$ `, m) K     */
    8 n& j5 H- u3 c* C, K3 i  m    public static Map<String, Object> getMailInfoFromDb() {/ s) H7 r/ j. t$ Q
            Map<String, Object> result = new HashMap<>();
    8 v* K# E/ P4 d) ]) Q* h0 B) v9 H        result.put("mail.username", UUID.randomUUID().toString());
    5 W8 i! @# f/ U% A" I% x, Z        return result;
    5 M" Z4 ]8 e5 n% ]- |3 G. d    }6 Z# U& i( k9 Y' v& W
    }/ Y! O0 z: z7 D$ P% P
    来个spring配置类,扫描加载上面的组件
    # @; h% S/ z0 g9 R1 h- o% s% ^) s( k! H: Q0 h
    package com.javacode2018.lesson002.demo18.test4;) {0 a; f* J. K4 \0 M3 V

    + p1 I1 d. @! Aimport org.springframework.context.annotation.ComponentScan;
    $ K( P/ F, p9 `" m( U& qimport org.springframework.context.annotation.Configuration;/ B8 h: }2 _: \9 x! z
    8 F) l3 |: N3 X, Q
    @Configuration; L; Z) d! w& B; B( V5 a* h
    @ComponentScan
    $ W" O# S4 I+ ypublic class MainConfig4 {" o& R7 k/ e' ~" z/ l
    }+ X9 K7 _& u, D% P) ^
    来个工具类+ ^0 Z0 ?& b! m8 J
    3 R% B0 f6 L) G7 ~' ~3 `
    内部有2个方法,如下:
    1 M9 e+ B& k3 u) j- n3 C/ z; U) e7 W) C- O6 {, o8 c8 X3 m
    package com.javacode2018.lesson002.demo18.test4;
      l5 `- d# j' U/ k' D, i3 [( F* @; g" K5 L! r6 {1 Z
    import org.springframework.context.support.AbstractApplicationContext;
    , L  a2 o# R: O7 _- Iimport org.springframework.core.env.MapPropertySource;8 ?3 Z$ F) Y, Z

    - x: ^  {! `( T/ ?& @3 B$ |import java.util.Map;
    . \/ H5 N$ u3 m% G
    & _9 ]3 T  {- t7 n9 mpublic class RefreshConfigUtil {
    - Q$ @) X7 z1 P  O. U8 X    /**7 j' h$ @; b" P' ^8 M- ]
         * 模拟改变数据库中都配置信息
    ; X  E7 W. Q9 h9 F     */6 v/ V; r0 z; a; J7 [8 f; s' }/ r* h5 d
        public static void updateDbConfig(AbstractApplicationContext context) {
    - w& R/ O; h1 ]& H        //更新context中的mailPropertySource配置信息
    * B, J: A1 A$ C# I        refreshMailPropertySource(context);8 s5 h) ]6 z* T

    3 k! [, h- G+ w" L        //清空BeanRefreshScope中所有bean的缓存2 g! \) K0 }1 e' V. ^  V# i8 S
            BeanRefreshScope.getInstance().clean();
    5 Q5 c+ Q6 y* N& ~4 s6 a    }
    # |1 d+ r4 Q- s" \3 Y
    & J* X# r% P: w8 o! o6 E$ y    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    ! f/ W; q( [$ a6 c5 G/ e0 U1 ^        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ( @2 p6 a7 X2 B* Y& `1 {        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)' N& v/ ]' j$ o" ^9 Y2 U
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);7 R# g) P9 @) @7 a7 G
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    . Z" J: B% ^/ d- y    }. h4 }, j5 [7 h+ p# n) w

    ) C; \* X  ]! D5 r8 u0 e}* b3 r0 P0 e4 H( K1 ]
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ) @3 }8 w0 \8 e0 ^) J0 H% H; Z, D9 D; r4 ]0 z+ f
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。( Y+ H  o& R% @1 [% C7 W& T

    $ L' }* m) G+ |8 x* f2 a3 S来个测试用例$ X' l4 B5 ~9 g# U9 x9 g# ^

    + x% I# ^' c- \3 B- N+ h@Test
    . d# T% G  k% p5 [public void test4() throws InterruptedException {' j; S# {7 V. Q4 x
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    6 `# g/ @. ^) q4 K2 \! B    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());4 e( Y# O2 F+ M6 I2 J
        context.register(MainConfig4.class);
    ( S3 M9 ~+ w* F. w    //刷新mail的配置到Environment
    8 e0 w% X6 K7 H7 i( v    RefreshConfigUtil.refreshMailPropertySource(context);
    6 m3 m/ @( n, r3 u    context.refresh();: p/ z8 T3 G. i" f% g

    8 t2 {9 @' |/ f" G    MailService mailService = context.getBean(MailService.class);" n/ Y: V' i' G& V
        System.out.println("配置未更新的情况下,输出3次");2 j0 S. t! w+ X3 X" O. `
        for (int i = 0; i < 3; i++) { //@1; e4 D/ L; T+ V
            System.out.println(mailService);
    + `* i& A/ Q" i5 y5 h4 E        TimeUnit.MILLISECONDS.sleep(200);
    ; j& g) x, \) w; {    }
    ' m9 G# m: h7 I6 J: t: q/ T4 y  g7 }. r! m5 ?* K
        System.out.println("模拟3次更新配置效果");
    - E8 t6 N. m- \6 Z- \9 m    for (int i = 0; i < 3; i++) { //@2
    7 o5 y6 y  \! M+ b5 O        RefreshConfigUtil.updateDbConfig(context); //@30 M% n7 r/ v% \$ }
            System.out.println(mailService);
    8 q/ k6 `0 F3 ?$ g        TimeUnit.MILLISECONDS.sleep(200);
    9 @3 y  R! ?1 ~* o9 t' P* \    }
    " Z* Z. ]1 N+ F4 _}
    6 s- |6 @5 _) L# k0 ]@1:循环3次,输出mailService的信息* q7 J# @( g; I; u; g

    $ F% V) @1 b$ }9 \8 w$ s@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息( M* i# j8 a& r) G
    0 A" W9 y" [( C& b
    见证奇迹的时刻,来看效果
    1 Q. }: ~/ Y! G3 \* ~, Y; O! n4 W  `. ?% s
    配置未更新的情况下,输出3次
    # X) ?& P: c9 O/ {3 B! W; {MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    3 b0 G9 ?4 X( q3 MMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 N$ D- h( W) m+ bMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    . _" B  c) x; E. q模拟3次更新配置效果! ~+ v& u6 a) ~/ B8 s9 s% e4 r3 I
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}# ~. {: h/ d2 d  F8 f* u
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    2 R. i6 w0 ^- Z* U9 sMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    ( ^! @* Z4 L$ r0 i$ ]' Z; q+ D8 [上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。- p: g3 J5 Y, T' _& e5 O
    7 w9 C9 G2 Q5 W' }8 [) @- L
    小结* g; N  A% G; E" @4 ?
    , [) c* w" q' B* ~3 |+ L
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。6 _* S6 n+ b  [& `
    % a- N9 W  ^# N0 L7 R* l2 L; P' c
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。: U/ T" V  c0 w* t& X
    ( X9 I. E. K) y& D( K
    总结
    . {* N$ n2 s* E: |. a2 ~1 \6 [4 A+ U
    5 _" ^9 O; C" J* \+ [% U! W5 E本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!  n9 ]! j' D! A- f3 B: J) r
    + M+ b9 Y# [) h' e& ~, k3 ~
    案例源码" x' E. M  j/ Y2 U( _9 s

    * w6 K/ D3 W( E* E, H9 uhttps://gitee.com/javacode2018/spring-series% P7 F* i: x4 e3 j' W
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。2 `/ r& h3 J& f) G. }3 u# O: q

    * `- v# ]" k8 i6 F. RSpring系列
    ( \5 q; t& l7 D9 d7 P" f
    ( S5 ]! w% t( w. p2 NSpring系列第1篇:为何要学spring?! @2 s: l) t" e$ ?. Y

    8 z! v5 W9 l* N! uSpring系列第2篇:控制反转(IoC)与依赖注入(DI)7 h, A5 b# U: r# j

    ( M% T- Z2 y8 H/ j9 S& ~' HSpring系列第3篇:Spring容器基本使用及原理
    ( H* S! n6 M3 p$ }- y$ Q" C. c  d4 a: f- f( R4 ]: i
    Spring系列第4篇:xml中bean定义详解(-)
    % L% ?% o& I8 P
    ) ~7 r% _# r' w5 vSpring系列第5篇:创建bean实例这些方式你们都知道?
    " W) u9 `: ^& ]7 l2 h1 F
    $ {# S% d; |5 n" R7 w( }Spring系列第6篇:玩转bean scope,避免跳坑里!
    1 V# J- \6 b7 g, ~4 y" D& k$ E: }- K1 a3 e' t/ [5 K
    Spring系列第7篇:依赖注入之手动注入* H. v1 S! W; V4 v
    5 Q& }& f' v8 @+ c* j5 K5 I
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    ( F: g3 |3 _: y; H2 n5 F' W9 R" X' B. [, n9 s% u# K( m& e
    Spring系列第9篇:depend-on到底是干什么的?0 T4 C( n* m9 @4 ~+ H$ h

    9 J6 I1 G! q: rSpring系列第10篇:primary可以解决什么问题?% L* X3 v8 H) n

    + h+ B" r6 A. O0 D: hSpring系列第11篇:bean中的autowire-candidate又是干什么的?
    " h/ ]2 R, f/ E9 o- s- H" \0 e$ O" K5 C  _/ F# W
    Spring系列第12篇:lazy-init:bean延迟初始化
    ( X2 m  T4 [- t9 {) c
    8 D( M) Z# q: \& m- x. GSpring系列第13篇:使用继承简化bean配置(abstract & parent)$ n) Q3 k/ r% e
    : J: F+ c8 [& ~% B1 G
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    0 K( F+ j& i9 T1 k! k( L$ O: ^1 z/ ?/ O; p. _
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?+ m# f& U9 H0 q8 s  E' ^+ O% l
    0 `. i" B8 k2 H" a3 }# ]! ]
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)0 a; y' n9 D5 G  C3 {6 E

    * o" P: M/ _6 b+ v. I* G6 ASpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    & F  p+ v# x* I: q5 e% p$ f6 t/ O+ V9 m% Y' Y
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)" O2 Z$ x4 Z1 M: Z4 n
    & ?; v+ P0 x' u. }! p
    Spring系列第18篇:@import详解(bean批量注册)
    " H  ?, ~$ s$ G0 x1 a- l1 K! A! k
    Spring系列第20篇:@Conditional通过条件来控制bean的注册' {, z. i. h, @4 M3 T/ N8 C

    % B8 r4 W( O2 s3 u1 ]Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    & g% I( q3 m9 |  f8 H9 H& E) B8 J% i3 H3 W3 V8 M
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解( b0 O6 j+ ]# x2 E! z, a/ L
    . ?, G" |' J4 z" ^% e+ V3 u6 E
    Spring系列第23篇:Bean生命周期详解
    8 r/ X2 u" D6 }- S, ]$ d
      y% L2 Z5 g7 X. |6 W6 LSpring系列第24篇:父子容器详解
    8 H" F( I: {0 `9 W1 ~9 p) t& ^
    8 }3 r6 A: \1 \" h0 w( y更多好文章
    : v2 l) `0 J8 _2 q. M6 \/ x9 @
    5 w* K9 y# f7 w" w* u  ~Java高并发系列(共34篇)8 N0 L0 r* c! m' ~7 C
    . \% t. h* ?6 P/ ~( G  z+ B
    MySql高手系列(共27篇)
    % N7 N8 e# _6 V) s: [2 }0 s
    ! V3 s/ ~3 r: V# EMaven高手系列(共10篇)( E' m7 a  r0 G$ p6 s! L7 H, n
    . w9 Q# u% s, f  R, J6 y. B
    Mybatis系列(共12篇)9 H; m) G$ ^" ^3 M8 }6 m" h8 c  z" G+ l

      S5 G6 {5 h8 q' D* s: k4 T3 p聊聊db和缓存一致性常见的实现方式+ V  p! c2 S+ e! a# ^: G

    ; b3 a$ M" t% V接口幂等性这么重要,它是什么?怎么实现?6 j. J+ ]8 _6 W6 P, `
    7 h" ], ?- g5 U& S
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!# x6 ~/ A, v5 O
    ————————————————  ?1 n% Z4 b% _1 G3 c8 i
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    1 ]0 W# Q6 x+ G& _原文链接:https://blog.csdn.net/likun557/article/details/1056487577 O; ]  k6 O# R
    ) d8 i: C- x5 }. \$ Y+ M
    * j. V0 I& u# R0 Q' K' s$ t
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2025-8-5 01:41 , Processed in 0.560766 second(s), 50 queries .

    回顶部