QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5446|回复: 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 F/ ?0 L( o( x疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!- l7 i$ [7 Q. C6 Q2 l! l, o- s
      |' l2 |. X9 V4 `
    面试官:Spring中的@Value用过么,介绍一下' S2 I& ^2 N; @4 u  L6 O. t
    1 k8 w! ?! C0 v( N5 Q: c# q( e, @
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中3 u: M* r0 W5 K. c: r( t

    5 I1 W1 U1 O) J5 M( V$ H4 M面试官:那就是说@Value的数据来源于配置文件了?* R& g4 S3 r( `

    : z: s/ V# Z7 p; W, E' W/ t- H" }我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置& w% \" W* x' y! i
    1 x0 c6 U/ S& _# E2 q* x4 x" w
    面试官:@Value数据来源还有其他方式么?! Z; Z+ x$ ?* @1 h
    ) q) A: ^/ C; @+ T
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    5 _, j! _0 }, _; |; ~0 K; c
    ' f* P# ?0 Z0 R* `( C面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    . N0 l$ ^" T1 C' j$ l
    ! Q+ S% R  u: j, ?1 L1 V我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    8 D3 J$ y8 ~4 l4 S) p* L6 T: K
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    5 s+ e5 l* X  a0 G, [; O
    % z% d0 L$ d9 v# y; n% N: b' Q5 U; b我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    & q0 b1 R4 R0 H1 l9 s. e0 Y5 w+ ^- l( P9 L' q2 O* g
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?% x, |, b# j4 I* @9 d- n; x

    " |8 b; ~$ x1 T我:嗯。。。这个之前看过一点,不过没有看懂2 h' Q7 a4 R5 x" C0 @

    5 Z- v7 N, e6 n8 m7 R) E( ]4 @面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    # a& ]! l' e7 W9 ^. |; \2 F/ P( k1 g% M' ?4 W( m
    我:3万吧
    + w9 x' t# p2 I( T' h0 A7 Y5 `8 Z- w8 n8 i8 }+ J, X- j
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    8 u+ v. J+ B" p; n$ F9 b+ b7 k4 W
    9 h* I% N+ H" |) k我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    3 I) a8 A9 G% O& Y! t5 ]2 P5 V1 {$ ?3 \  _
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
      o" p: B5 A, M( j1 q1 \# @, t, f( ^4 q3 I5 K' U$ A
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    & d. b9 F3 ~- A3 j% Q3 K
    $ y+ d8 f% |9 n4 d* T/ u这次面试问题如下
    0 m, X4 i- q: i& X8 P( `6 A$ S: y- g, e, f# u
    @Value的用法
    0 [: ]: @5 {8 m. D
    0 \0 |! M4 H, u( _7 b@Value数据来源
    : d! N# {7 r1 E: L# `
    3 g1 v) J9 Z6 v8 d5 R6 C@Value动态刷新的问题3 N: v* ?# H  G

    & X8 K6 |: [4 K7 U# y, f- g下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    9 M2 c5 I- N5 }$ d/ i2 Y) H4 [  x3 N6 w! h' {
    @Value的用法
    0 X3 E5 [2 f+ A% U5 q1 b2 C
    / j2 Z" O9 x1 b系统中需要连接db,连接db有很多配置信息。
    + |8 K: O' [7 h7 a8 F
    2 ^$ ~( s3 j; \7 M- ^/ K系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。7 d; c6 w0 t5 @$ l4 F: A
    / T4 L. y; T  x1 E7 r
    还有其他的一些配置信息。
    : U1 L& o% _' C+ t) F1 V: H/ ~% F5 I: Z
    5 y3 j" W# J5 x0 ]: q% P我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    5 C. z- t& {3 e4 W2 w9 v# e2 y' x2 x; D# W0 s4 p# G" b( N7 g5 z. w
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
      I3 x; d1 p# ^; A
    5 |7 x, ~" X4 `- ~# V; z7 b* U通常我们会将配置信息以key=value的形式存储在properties配置文件中。# B8 G& \4 P  X+ m+ F
    0 y5 F9 N: O7 J( T6 B# \5 R
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    7 M. F( z* q1 D, ]8 l2 r" R  J5 Q0 |/ l, o. \! q4 m
    @Value使用步骤, s6 ?; S! s% g3 w/ V

      N  u+ W  _& \# ^) k' N) m$ |步骤一:使用@PropertySource注解引入配置文件
    4 ?- L& D8 a' A5 Y
    8 H9 t5 u7 R: \& B" K1 y将@PropertySource放在类上面,如下$ O5 h* u( T, \! U5 i) Z5 i
    4 Y% V, C. V0 O8 H- d* H
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    9 |& E' ^8 A  z+ A4 U9 J$ h@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。/ x5 u+ Z$ V1 q; W7 Q, v
    7 |. Q, G" y5 T  I# m( [" v( Z  w
    如:
    6 R. V- `. s$ v9 F' @1 J* F. K7 W8 e: A: H( _; d
    @Component
    " D! ?5 i, M5 u' p" G, }% t@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    : D3 s8 N$ j' [# M5 Vpublic class DbConfig {
    9 O9 e/ C; o! V! y. a3 i; E}* p+ X8 b8 H: ~* d
    步骤二:使用@Value注解引用配置文件的值) _6 d8 i  b1 |, z9 F9 d3 L3 \

    ( z" u% x' o' ?1 n* J通过@Value引用上面配置文件中的值:
    - O! m4 @; c4 ~* y: ]  Q+ X  u1 [) c
    语法* Q1 w- H1 b" e( B5 M

    0 i# G, M8 U& R& R# S  W& f@Value("${配置文件中的key:默认值}"), f1 ~$ u" ]0 _/ J
    @Value("${配置文件中的key}")
    % |7 Q* d. B# F$ Z# W- {; o4 @如:& V2 S) @, ]% L8 V7 }
    9 H5 T) C: k" o8 b$ a0 T
    @Value("${password:123}")
      _3 N3 ?2 A: p4 z; L4 ?% g% y上面如果password不存在,将123作为值2 J6 g9 x9 P6 C  N/ d  B

    / K/ u* X$ q3 ^, e% o5 {@Value("${password}")2 z" a7 C7 i5 v2 _
    上面如果password不存在,值为${password}8 k3 A( H. ?* z$ _
    : C2 r( o6 A: v2 Y+ B6 \: \6 G
    假如配置文件如下
    / d9 ^! x7 s. h4 r: H7 w  g+ G2 p0 {$ m
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ( G. `& Y/ s- q/ Ejdbc.username=javacode
    # ?4 @5 e4 }: R* Q' w- Qjdbc.password=javacode  k4 w( |/ z" a. K! N0 k5 N. H
    使用方式如下:
      B. @/ A# C( r# }7 T! j' }( h& V$ w
    @Value("${jdbc.url}")- v0 _5 O$ W# J  }3 w+ ^
    private String url;% K0 `4 G+ y$ ~4 Z% X

    1 L$ Y1 _2 {3 y; \@Value("${jdbc.username}")$ {+ w) W7 X7 O! k' x
    private String username;( j' I% J* U; L+ x

    ' J- \8 _% v+ ^( T, x( G& Z: }4 _@Value("${jdbc.password}")5 _) D2 J$ s  g7 E* O3 q/ E' W4 D* F
    private String password;5 i2 u  [( ?% S! l7 |) W- S
    下面来看案例' O8 e! \" H6 r* `$ @
    ; r2 H+ l3 H8 S) w7 g7 N+ E8 q
    案例# f8 O1 A+ s( P4 k, f

    : Y& t5 n& f3 d0 W! w4 y: }来个配置文件db.properties
    : ?2 L; h% m9 c( e+ o0 s  B+ q
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8: k8 ]) X9 C5 a  ~; J# B8 n
    jdbc.username=javacode0 d* P) `" e  q8 ^. ~' M" U& h+ {1 J
    jdbc.password=javacode2 i3 ~6 T, P5 _  [6 _
    来个配置类,使用@PropertySource引入上面的配置文件" Y+ x' c, A+ o6 ]! u
    2 c0 k3 }* S; z$ q. p! y6 m4 `- z
    package com.javacode2018.lesson002.demo18.test1;
    ! F( t* S7 h/ E7 M. L6 O2 @
    ) j$ g4 X4 ~1 z# ?+ d4 z+ S7 q& Timport org.springframework.beans.factory.annotation.Configurable;
    / E" y# G) U: A9 \# `9 Simport org.springframework.context.annotation.ComponentScan;
    % l7 ]  j' d% W4 Vimport org.springframework.context.annotation.PropertySource;% {/ |5 Y1 h$ }3 r( i# X
    $ f" i# l1 s8 F& h5 D/ t
    @Configurable
    & M) Y7 {# d- C6 u+ c" J7 ~1 g' h@ComponentScan
    6 h! f* l: q9 ?+ R  y@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    : x; ?3 v: n4 `3 M$ ppublic class MainConfig1 {- b. M0 C% x5 D4 P  ?. b
    }
    5 K+ Z7 l! u6 R  v来个类,使用@Value来使用配置文件中的信息
    % B% K- h- o1 ^! ^) K# H0 n, J' z5 |0 u" {
    package com.javacode2018.lesson002.demo18.test1;- j4 r9 j$ A; ~/ t2 |/ W
    9 I5 n4 i% D8 x( e6 }0 o0 m1 W
    import org.springframework.beans.factory.annotation.Value;
    4 f' P# g" Y, T$ Bimport org.springframework.stereotype.Component;9 V9 j/ M3 C' w. Y7 S

    " ^3 [" h# V; c7 y9 O@Component
    , n6 ?0 h$ u, A! b) \8 ~. bpublic class DbConfig {/ L1 B) x% @! R! r0 O

    * A% f2 U' H; ?, M9 R    @Value("${jdbc.url}")5 J$ J# h3 ?& ]7 ^) C
        private String url;6 v# Y2 q9 G% p' [: ]  J

    + v& ~, @* p( j5 g    @Value("${jdbc.username}")% S* b+ U$ y# l6 L
        private String username;
    ; [( h8 l* K& Z6 s/ P0 E. b$ S9 @0 a/ m& M8 K$ u: Y: l1 \2 H
        @Value("${jdbc.password}")
    0 Q' b7 q. c: s# Y% I+ O    private String password;! |3 c5 g# s9 f$ l- C8 ^1 }1 `% z
    ( @2 Z  p7 z; u
        public String getUrl() {
    ( }2 [. o4 T. w; l4 ~        return url;
    1 R" Y1 _& ?9 K. T    }, k/ n. _# a* E  Q  |9 r: k
    ! ?2 k2 l7 x7 M+ R2 Q" G) o' x
        public void setUrl(String url) {
    9 @  J! P' f6 S5 z' \/ n2 t  T        this.url = url;# i" O8 q, b" A. v! |- y$ p* A
        }8 b/ O! d3 q# M1 l+ l
    0 W/ I% H; Y/ N; @
        public String getUsername() {. W+ H+ p) ]$ }3 o3 F/ w
            return username;5 ~+ W2 R) c1 J+ r. X: }: a
        }
    + J% n# d% x2 g: V# B. v3 I4 I$ c
        public void setUsername(String username) {" F4 h! j% Q2 q; n; H3 P* s: E
            this.username = username;
    # o% a$ V# w/ L4 O    }/ Z* z6 h3 y7 u4 W3 W7 X

    * O( X" B$ T( V    public String getPassword() {: A, B8 G# d: |% a
            return password;/ @# F2 l! s- N) K3 b& D6 f7 R
        }4 E) u  ], w, `- b
    3 m6 S" `5 Z) D4 k) e0 i3 z$ c
        public void setPassword(String password) {5 d# N& {/ d  U0 p: q) g
            this.password = password;
    3 Z+ q4 y9 A5 w5 E3 p. H  N    }
    0 O& j9 G5 G# Q) F0 Y7 V1 o8 K! z; g0 t, r
        @Override% P8 I' h( A: w- ]5 p9 R
        public String toString() {/ `* Y9 |( F$ b7 P; M3 O
            return "DbConfig{" ++ x( Y; z2 S1 M* `! H
                    "url='" + url + '\'' +
    7 M* g! G4 ?- o7 B                ", username='" + username + '\'' +
    1 U/ S) q# U: O; b8 @# S2 p                ", password='" + password + '\'' +; ?' ?  U% e' K" o& I6 m$ |
                    '}';
    2 Z5 y3 g) p; e. W  ]/ P2 e+ V    }) i" v4 ~: h6 g. e' P" x" P
    }
    - m! z' y% c9 H; H; c. N上面重点在于注解@Value注解,注意@Value注解中的/ R; y2 w' n# o4 m

    9 c" u5 u& M7 e9 e+ T来个测试用例( M$ G* K: G& e( P& p: E
    & g. e+ L7 E( F6 h& k$ k
    package com.javacode2018.lesson002.demo18;
    8 q3 m2 W% N' @
    7 h$ Q3 T- `$ i% U# aimport com.javacode2018.lesson002.demo18.test1.DbConfig;& Z4 X( e: Q* |7 I: e, |: f% J
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;; U6 J1 I- l* ?  [! F. ~
    import org.junit.Test;* D3 q. w1 y6 c
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    $ G2 e) X9 E' @2 D+ [  [; j9 X( T
    * B  X: N" K% W1 o# A9 Q7 Kpublic class ValueTest {3 K5 Y7 S5 }& A+ f$ e1 Y# x) Z

    % ^, P# ]2 b- A    @Test
    2 `/ E3 `8 r/ e$ w* Z% v0 Z    public void test1() {
    0 M) i# x+ L( c5 S        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();+ r0 Y/ u  C' ^! o/ Q: N
            context.register(MainConfig1.class);
    7 H( v+ \! b. w. z- t5 n        context.refresh();
    ( s$ v5 n" C# |
    , d& a# y1 z/ h1 P1 I1 d5 @        DbConfig dbConfig = context.getBean(DbConfig.class);& g0 }6 M' F' {! ]6 I
            System.out.println(dbConfig);! H8 E8 |: J/ W) M. U6 Y
        }  @# ?2 o0 m7 J1 t  A9 F
    }
    ) v1 S& V  d+ q2 R8 [: p) A运行输出
    9 U; u8 p/ w9 p! O. L# B. U+ q) }* z, {
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}3 V8 i5 g+ D- l- l* o, h9 Z  k
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
      ]; [- Q' _! _7 W5 h; M# E7 H6 L" `4 i" P
    @Value数据来源
    # z6 M" @8 B; m: _5 o
    7 g( g0 H; t2 r6 \通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。& w/ U  [, q! w- N

    $ d2 W+ U" I5 I" L我们需要先了解一下@Value中数据来源于spring的什么地方。7 m  O: v/ s/ Q! W, E8 J

      e# U9 o3 [( \spring中有个类# K2 H) ?# b8 Y5 ]
    : [& G8 Q8 O2 E% A. b6 F' a
    org.springframework.core.env.PropertySource
    4 b2 {! m& w; n* A) p: O% M可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息2 H  ~4 W7 ^" r: O( w

    ' B* |8 ]) n& j6 u& W: f: I内部有个方法:6 k' Y) w3 B9 M3 n
    2 ]8 I3 l, |- `0 x7 {* p2 c/ g
    public abstract Object getProperty(String name);/ p* S- [" a+ _# V( g
    通过name获取对应的配置信息。: |8 O1 u" M* I" J7 ]; h

    + |4 I8 E  A, N3 j$ d6 E8 l+ N系统有个比较重要的接口
    ' U/ ~  n  G0 q3 S
    * p( J# _3 K: N) M! Torg.springframework.core.env.Environment( v- r9 q+ G" S4 [8 E% ?
    用来表示环境配置信息,这个接口有几个方法比较重要9 @1 Y: X* q! F- C
    / `; x1 G2 r* B
    String resolvePlaceholders(String text);) c$ w- F, i/ u$ H. ?
    MutablePropertySources getPropertySources();0 G& L3 M- }9 K! O% C4 g' ^  B
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    ( w4 C9 ^' X0 ?. y) B) {8 R& {" b& \& r6 E* k( [/ G
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    2 M9 {+ Z# Z' L; F9 s0 o% L6 a/ E
    public class MutablePropertySources implements PropertySources {& g2 u  V/ [0 v! B9 U. d

    , H) E  L& W8 b: G! M" v4 c+ P1 q) ~    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ' r( O7 h- ~1 m1 V8 A1 Q1 z5 P+ c- u" p, v" E4 |( w
    }$ \. V% f8 x- \! b4 }
    内部包含一个propertySourceList列表。
    : n' h: e8 |* X4 w' b& K* r7 o2 ^& s5 z8 f2 g8 k
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。9 }# u, @# g* d  j7 |  H

    9 e0 O7 F) ^' i大家可以捋一下,最终解析@Value的过程:1 g' x* Q7 @$ H1 ~, P/ a, F3 Z
    % S8 [, O4 ]2 a0 U! Q( g7 L0 G
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
      `9 ]3 F4 p- S( s& g, a2. Environment内部会访问MutablePropertySources来解析
    / \1 P& t6 y& w* }' E+ a! E3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值' ^' {6 W4 }. a# Z
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    - h! |5 r4 l% y
    ; s+ Q  A$ r% v: o8 f/ ]2 Q下面我们就按照这个思路来一个。
    / p- t' ~" I, f* n7 Z3 m( h9 U" m" i8 I  _; n+ t
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
      C/ I# @( Y! c1 L' }( ^$ f7 m- s
    package com.javacode2018.lesson002.demo18.test2;
    , [! }; `1 W8 \8 g" F/ d) _" D; @" F3 r  d7 j
    import org.springframework.beans.factory.annotation.Value;
    ; D4 S6 {. r& z  Zimport org.springframework.stereotype.Component;: \! O0 f2 X0 \0 U% z
    ! ?! h: Z' a, Y2 j
    /**8 p+ A; u# \; D5 e4 u/ C8 Q7 D% v
    * 邮件配置信息. I6 S0 j* U3 X& ^8 k
    */
    7 c3 c0 X/ E, @@Component
    / j4 D, h% Z( q' |2 M, P* upublic class MailConfig {
    ! s( x9 O+ D8 v; A! Z9 E! \% n3 Q; S" o$ I' S2 a
        @Value("${mail.host}")
      n5 `: |# Z- k+ y& g; r) _    private String host;
    - p0 k- i$ H; m( ^* h. d/ q" _/ M9 w- t8 W
        @Value("${mail.username}")
    ! R" C& M+ e8 m" Q* n' |2 r    private String username;% r4 B/ h" a# O' ~1 J
    ) `2 P9 ^2 O/ z# H5 w" P; P6 `1 C
        @Value("${mail.password}")) `0 ~- R; p1 Z6 u0 W' a
        private String password;" G! J8 k5 m6 M

    7 ]4 v3 f. c7 \3 H    public String getHost() {  }( a2 ~  ]/ a  e0 @$ c
            return host;: D# `$ c6 k: S( z$ m3 e
        }
    7 y* B; m; p3 X  A5 O! ?& X9 |/ j4 x# U8 F
        public void setHost(String host) {
      u* h5 d& r1 ~% k, t9 P        this.host = host;* _- e: M. `6 ?" f8 K$ |/ Z6 N7 \' n
        }
    7 F. u- M+ s6 c4 Z% A9 x- h+ M" J) E( {3 G
        public String getUsername() {
    # O: k6 U  \0 s% C7 J- f        return username;
    . h2 g' \+ _) v/ |    }
    & C, f" H" g0 w) G  \& Q" F) B- t/ ^& q. ?  h7 G; j* {" G  b
        public void setUsername(String username) {
    " t* ^4 j  f  k; J        this.username = username;+ U2 e) n+ ?6 F5 h4 [) D' k! s& _
        }, B* ]) t- |+ @, l9 C( F/ o  O# E

    ' V7 D$ y% S9 K0 N3 {9 N4 s    public String getPassword() {  I' H: ~% C7 e7 v6 `
            return password;5 B& B# w& }, U! l! B4 t. Z
        }' m& U) N2 G+ x/ E* ?  B& G
    6 ^& s, J: [; y( k/ v. O
        public void setPassword(String password) {
    7 O$ B  @$ E8 i- J% Z! Z/ f6 x        this.password = password;- A; q) t" e. A$ w7 W7 _
        }
    $ a2 d% B$ m+ c7 `# ]" D7 _9 V' Z( v
        @Override. d$ z8 P1 Y) M
        public String toString() {8 l6 v# W( R' a1 G4 }+ K& a
            return "MailConfig{" +
    . Q. m% j9 e7 C' g- ?( b6 r                "host='" + host + '\'' +  z1 Q: W# S6 }5 e' I" S( _$ k
                    ", username='" + username + '\'' +
    0 T* U' e, w9 {& a# u# Z. m$ U9 C                ", password='" + password + '\'' +
    5 s; Z4 c- M: y# P4 k/ X                '}';
    + @4 x. j1 t( X5 k/ n  P! M. p; z    }  {1 t' x8 k3 }" }! }' j0 l
    }
    , g; a% k% ?/ H% C4 t4 ]0 t% ?再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中5 r2 y: |. M8 l. q1 A& k
    9 M/ K. c1 g7 H, x$ _8 j5 r& r
    package com.javacode2018.lesson002.demo18.test2;
    ( D9 i2 {0 L4 ~. c) X" w2 l) {
    # ?" }' R& n- G, z: i$ `import java.util.HashMap;
    ' j. g3 e: n* qimport java.util.Map;
    & q. a+ o" i4 b* n3 F' ^! y$ I% U: A2 [% G8 L% v: J# Y8 E
    public class DbUtil {* m8 M9 Z: K/ X3 M" c  z
        /**% z- @; T! e5 }3 T
         * 模拟从db中获取邮件配置信息
    $ B2 y+ _* p9 Y, S- f5 h& @     *
    8 l5 B! G4 X8 c     * @return3 D- b. \1 f+ o1 |. p8 `
         */
    3 p. o7 u. T8 N( ]7 Z* T# H    public static Map<String, Object> getMailInfoFromDb() {
    : ~" S! ~" ]* f9 W$ k        Map<String, Object> result = new HashMap<>();% K" i7 c3 w. u3 g2 N, n/ F
            result.put("mail.host", "smtp.qq.com");
    ( Z) ^5 E4 F/ B9 e5 k9 z        result.put("mail.username", "路人");
    ( D& Q! b3 s- k- Y# S        result.put("mail.password", "123");* J1 d$ y1 p! j' R" x
            return result;
    ) f# o& M" o+ k& X    }% P$ u* K' O& d5 ]& n
    }' q8 r) ~. F9 p; P  t# u( z7 r
    来个spring配置类
    5 }0 @* n, I+ g3 N5 f
    # J) }- a9 v. h: E, Vpackage com.javacode2018.lesson002.demo18.test2;
    8 ^; x9 Q! |3 K1 q1 P" `  n5 [' P
    9 T# B5 u1 f! Y" O4 O9 A+ Z& limport org.springframework.context.annotation.ComponentScan;
    8 B+ w/ \" E& I; dimport org.springframework.context.annotation.Configuration;0 c* Z* ?0 e/ T/ e/ S$ D
    2 U) ?9 |5 e2 U
    @Configuration
    7 _( z2 a8 z2 w1 X@ComponentScan
    $ q( v: l4 K  {public class MainConfig2 {4 s$ @; q: D8 F) U  j2 l
    }
    ! G/ q& K0 A8 S9 B; s  G下面是重点代码
    8 B, G/ b4 i- a% g: ^% D2 l' |6 H: h5 y/ W2 t+ x5 U
    @Test
    7 Z* ?4 X% T; }+ T% Dpublic void test2() {" i. G/ G7 D/ D; K1 Q7 H
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. |& W: i: G# L- _) I
    1 n0 ?4 d* s1 p- N* R! o
        /*下面这段是关键 start*/5 R7 M2 P+ Z1 _$ N$ I1 i4 O
        //模拟从db中获取配置信息  r) L. _! u* `" d' F. K8 J" F
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();- q* n9 P7 Y: F! D3 G. @+ b8 {
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 q/ D3 P& h& ~" K
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( `! G2 @8 S" M; ]- r    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    / v1 w: P: t# I2 }8 J    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);, Y$ Q) M2 d! d! K$ L  W- y- _
        /*上面这段是关键 end*/# V" i; Q) G1 S2 O& @" K, Z
    % q. m% H) [; I6 ^" U# y
        context.register(MainConfig2.class);8 J  X- D; E- n0 {$ U/ v
        context.refresh();9 g6 u6 @2 @( Q& D% B# Q
        MailConfig mailConfig = context.getBean(MailConfig.class);0 n0 g5 _( t& L8 C" u0 ~
        System.out.println(mailConfig);$ s" d( C6 ]6 L5 o/ K
    }
    ) f1 X0 N4 ]3 V+ C! Z注释比较详细,就不详细解释了。5 M4 e( ?: l7 s4 H
    + q% ?% _1 z4 k1 L
    直接运行,看效果
    ' N' j6 x0 x0 q; Z2 A6 L+ [* @/ K
    6 q" ~' T7 D3 }7 tMailConfig{host='smtp.qq.com', username='路人', password='123'}' m2 o! s6 e5 f. w
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    1 ], }7 {3 l( n, N3 Y1 P, V0 U( o% y8 M1 z& |+ p4 u! N6 \
    上面重点是下面这段代码,大家需要理解: v& p+ q$ `' w, O5 [
    1 F! k0 V& K& p) p% ]9 x
    /*下面这段是关键 start*/3 W2 S8 o  B1 E9 p  E2 P
    //模拟从db中获取配置信息
    & F+ i9 j! H2 z. P# QMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();0 `  a4 y) n) y& ~- Q# ~. B) N& x9 z
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). `! _, F) i7 o; s
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    , H! a7 a( j8 h0 G* h//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高4 h+ I- q" J3 w
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
      g+ Z- K3 H. A1 [- [) ~3 ?, T0 `/ s1 P: w/*上面这段是关键 end*/
    & r* i; a5 I  W) V' R咱们继续看下一个问题" m4 X& _( M% v; c# R: _

    # }8 Q- G& U* k6 c, v+ }% i如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    9 k/ |$ g  q4 R
    ; J8 [" s( I; q1 W@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。+ v6 k5 [% n8 l7 [

    " t( ~8 ?. Y. c实现@Value动态刷新( Z/ q" x* I  v' ]# G/ D- h3 Z

    ! ?. S: l) D  x5 o& i9 G% o先了解一个知识点- Q5 k& g8 Y; Z+ Z! |( K1 u
    ' X- {: b. k7 q, S6 K4 U7 r
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    7 R& h' l3 K% c9 U
    ; p0 T+ W* {% C! e: W1 |' ]4 H* k; x这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    # h' X9 a7 q$ {2 v
    $ Z3 |4 l) y5 X3 dbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:3 C' t6 K2 f* M1 ~5 x  e

      u; a6 U' k( q+ oScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    . r  }. c5 }! f) p8 s1 f这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中6 c' C2 @, {- ]

    / F. Q, w  @* P1 Zpublic enum ScopedProxyMode {
    & p! ?3 X- g* j; S    DEFAULT,& f  o/ p1 [1 F+ c* q3 _& J, B4 |8 x
        NO,' B6 l9 V* ~, g  ~' F
        INTERFACES,
    7 M3 P  Q$ a! l% Q    TARGET_CLASS;' Z' E* H( q; z- W. ?
    }5 `1 V* E0 l. ?- D3 g$ _
    前面3个,不讲了,直接讲最后一个值是干什么的。# ^$ }3 ?$ k9 a8 W' V4 _) \
    4 p9 `7 L- I9 T: K. E/ y4 h1 ^
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    . u8 o) W- Z' n9 p4 m% ^
      R2 `+ C( r& B4 t& k. F+ |7 G理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    8 s1 }5 _7 Q: ?0 ~" j$ b, d, e/ E( `- y$ l& _2 S& |
    自定义一个bean作用域的注解% L& ]6 V3 {2 K, o+ g6 ]
    1 C3 l" a' j, T( ^+ i7 E
    package com.javacode2018.lesson002.demo18.test3;
    $ V$ B' H2 ^# L$ G( V5 j
    - ?. v7 j5 D0 V+ B% f1 ~  |" cimport org.springframework.context.annotation.Scope;
      M% p, t% G, q$ K7 i) d. nimport org.springframework.context.annotation.ScopedProxyMode;
    $ k- t* m& c: r$ t: f( j& s; j4 o" _' R' h
    import java.lang.annotation.*;" v8 u' y, l' v/ Y4 P$ B' n  i8 ?
    6 y. c  [- N8 @. i
    @Target({ElementType.TYPE, ElementType.METHOD})
    0 y) p3 D# Y9 r: k4 r' P' ]@Retention(RetentionPolicy.RUNTIME)
    - \/ q" C( m* c( t- O@Documented: I6 `  X/ O/ @  I, p' H2 m1 a
    @Scope(BeanMyScope.SCOPE_MY) //@1
    , e% g7 p0 s7 a, Apublic @interface MyScope {5 O% e* s0 r) l9 g8 m
        /**
    $ u& S# D- T& n* g% K# l& ?( G     * @see Scope#proxyMode()
    : P/ T9 ]9 K/ _1 C0 L     */3 s# S& H) w8 f+ ^$ N6 \
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@26 n8 D* Y4 l( h/ `3 I& c
    }# o* U+ |7 Q' @5 O2 F
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。, w7 I( h* X* ]
      T( \2 Z. h1 J+ j% f+ _; G
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    1 u; i# x* j% ], }
    . T& E) w6 c! ]; Y! c5 Q$ c1 F# [@MyScope注解对应的Scope实现如下( A* k1 Y+ m) V9 v/ K

    5 I4 X% R2 I& hpackage com.javacode2018.lesson002.demo18.test3;
      h9 l& e1 t; q# M: p
    3 k9 P* r! D4 Uimport org.springframework.beans.factory.ObjectFactory;
    3 r/ x  ~2 R/ kimport org.springframework.beans.factory.config.Scope;
    " G4 F6 y( \" c* Qimport org.springframework.lang.Nullable;/ N7 \# E+ H& H* \. R+ f

    ( R) y8 Y4 X" Q6 R7 ]3 h/**
    6 N0 n" L+ S" I+ m, f  k * @see MyScope 作用域的实现
    ( i+ A* T2 H) k- Q */8 o1 C4 S- f' f
    public class BeanMyScope implements Scope {# {4 H/ J- H7 n4 v' q: o

    2 {5 Q+ P% Z0 W+ Q    public static final String SCOPE_MY = "my"; //@1& p6 [$ u$ e2 E; R- {$ L
    ( v" X# p  `( j# l0 _+ Q; i
        @Override
    7 Q3 O& a5 L5 H1 a2 [    public Object get(String name, ObjectFactory<?> objectFactory) {
    % m% \* o! y/ h- n        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2) o! x& d9 h3 }. |5 V( M4 W
            return objectFactory.getObject(); //@3
    ) S- Y2 ~' ^  C% }( F) {. _    }
    " q) y2 ^( ]$ P4 ?4 l* w- s& n1 V" w' W5 j2 w
        @Nullable2 A( X2 {4 ?( @5 l: T
        @Override: h+ j) y$ [$ c# I. |- \
        public Object remove(String name) {/ @: L2 D  |. d
            return null;
      V. L" X% O1 o( j    }
    / K* M% q1 T3 a/ T) `" i' ?+ Z! y! P2 I( D! _+ T, i$ l
        @Override7 @1 i* B2 T# w+ f8 o
        public void registerDestructionCallback(String name, Runnable callback) {
    ! X, l! Z/ {1 h9 W
    4 ^* q. x% D* G. `: h9 c- f8 u7 D+ _    }0 \/ }' V4 X2 s2 X5 k, U

    - m2 E% S: K* d2 C7 O8 v* g, \    @Nullable
    ! @: I) r7 o" `    @Override( o% r4 P" n4 V5 @3 N1 j" \1 Z
        public Object resolveContextualObject(String key) {1 I3 o% r3 \8 o% e) Q. J) k2 z
            return null;
    ' S7 ?0 U; W# @2 a- }2 r0 D    }
    ) R( V, i! s& T) Q0 k! }- K* `! t
    ) {5 ], e% x8 {' R4 b" Y    @Nullable
    $ o3 N0 z0 ]) v6 @. C# N1 o! h    @Override
    & i. A+ w5 ^6 F/ `    public String getConversationId() {
    ! p, X* Q+ j* r  v        return null;
    7 A. H; o8 X" j# E* \# l2 ^    }
    9 R% g! A9 T1 I) `7 T' ?$ `# N}9 f- w3 g1 M: A( A6 j- c8 {( C
    @1:定义了一个常量,作为作用域的值
    + I; q& A$ J/ L# a& f( g# ~" }5 }
    ' k+ W& |8 u' ]) H2 a6 l% e. J, x9 U@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    % B# z2 k# r( X2 x' c; p3 Z4 o$ |3 \4 q' _8 U
    @3:通过objectFactory.getObject()获取bean实例返回。
    - D* @: U, F9 [, {' ]% |& C  ~/ l8 H& ^0 |- ~$ C
    下面来创建个类,作用域为上面自定义的作用域1 p, c" L8 @  w$ R5 I3 z5 i

    6 v5 c+ J) T- @$ @package com.javacode2018.lesson002.demo18.test3;! N9 @$ j) F6 ^' R# t7 b
    4 \" H& H6 r, G" r& Q2 |/ m/ c# x
    import org.springframework.stereotype.Component;' _% h, i! s7 _0 d" h5 l) }

    ! g9 ~& @3 _$ F+ Jimport java.util.UUID;
    7 o) K% h8 u6 Q1 C4 y) A" x
    ( q, r# N) X0 {* E5 u! f@Component
    4 Q1 T  g% s: @6 @  w1 G@MyScope //@1   N% H9 k* Q- \9 \) P
    public class User {- o! m! U8 F, [' z0 M8 S' t9 `' s1 D

    7 s' M, S% q" D; U& G/ h8 O4 R    private String username;9 C8 T/ x. A$ f3 F3 [0 j9 P3 q- h7 [
    & Y8 N- L, ?/ P* Z$ s
        public User() {
    3 G9 v/ C4 s; N! b2 W- e" Y        System.out.println("---------创建User对象" + this); //@22 o; i- q4 b# J
            this.username = UUID.randomUUID().toString(); //@3; y) W7 K: N- J: w, K
        }* m- I; U6 M. n& d

    * c# H3 ?/ [2 |3 z/ s0 v    public String getUsername() {
    1 X3 A; H( X: ]- r5 e- c        return username;
      b6 Z0 S- B- T  {0 z7 `( n6 S    }
    " r! V! \" Y8 M! u+ z; P  }2 h, j3 H" C
        public void setUsername(String username) {
    : `1 ?6 }* Y/ [" n# N        this.username = username;
    8 p& {6 D/ f9 R! q" q, u* y    }
    7 i( q- ~* }* K, s; }! A# V% @
    0 ~% r$ b7 a) m' i: I1 y9 ?+ F3 R( W}
    ) q4 ?4 @: Y$ }1 Q- N/ o@1:使用了自定义的作用域@MyScope' N# f. P; J( j7 v& m
    # S. G4 j# R! L- N% q$ s. l6 m
    @2:构造函数中输出一行日志2 Z1 r+ ^. t% G
    ' `0 S1 w, s$ q3 f" N
    @3:给username赋值,通过uuid随机生成了一个
    / P$ p" O+ L$ ]0 J- o: {5 \' \  k( p5 I5 q. A/ \: U* i
    来个spring配置类,加载上面@Compontent标注的组件/ f; c$ j  q3 S% M4 e4 M$ q

    # ]  H+ E& D. h$ H- n' x% P6 bpackage com.javacode2018.lesson002.demo18.test3;
    . L: U4 B+ k# N5 q* q2 N, @  G: U  L: |& [" y
    import org.springframework.context.annotation.ComponentScan;! ~4 y  a. j7 j2 X/ w0 E
    import org.springframework.context.annotation.Configuration;
    : Z3 r/ i/ Y) c. z7 l
    % u5 R, s! _% ~/ X5 }" H# C* d@ComponentScan
    / B9 u7 C% y- Y, S' b" p, U& i@Configuration5 H, U* b5 s/ G
    public class MainConfig3 {" h) p+ u% e( X* ~# J6 R4 p
    }
    3 h# m/ T$ i0 a$ {1 p下面重点来了,测试用例  ~" n+ t3 I2 I* V$ V
    - z+ a3 `4 t3 ^1 C& f% [
    @Test
    " F8 h9 f! i2 l( z/ {public void test3() throws InterruptedException {. `: [9 b5 z' b/ U7 k# ~
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    + ]: X0 N/ P% E3 |/ s9 d    //将自定义作用域注册到spring容器中  W  h) R7 B" t% c# z4 v+ L5 c
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@17 t" L+ F! F. z7 [/ g
        context.register(MainConfig3.class);
    : N" A% e' L) p# o, h$ J    context.refresh();
    * E& l3 R& c$ E( x$ i7 q# a4 N0 H5 W1 {( h0 T( G$ A0 \
        System.out.println("从容器中获取User对象");% Z+ x. o4 k- O0 {+ I
        User user = context.getBean(User.class); //@2# c% Q7 R9 l9 V9 U: u  M
        System.out.println("user对象的class为:" + user.getClass()); //@3
    + e( {3 G' t) B! h, ]  u' C3 u. w% h- ?6 j& m
        System.out.println("多次调用user的getUsername感受一下效果\n");
    * C- t, r9 B+ q  b. i" q- j    for (int i = 1; i <= 3; i++) {, Y' U; T6 V0 f9 I
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));4 c7 ^3 |& a4 J; Y: v! m
            System.out.println(user.getUsername());& P0 Q- ^" N: X8 _5 V5 L  C
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));" e- U. ~* N0 Y' F( m, d. |
        }4 G7 E6 ?6 c6 ]1 c$ m
    }
    ! S# F* m! a; K8 [/ Y@1:将自定义作用域注册到spring容器中7 K1 M/ H7 i5 z  q$ j3 e, z

    ; P* B! F0 O& \% P  R9 E1 s@2:从容器中获取User对应的bean3 q+ J: P) U- n& R& y  V
    " y$ D, m  o# g* {
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的: J% s6 m' c* {" E% o- q6 o) Q

    ; G6 _# x' ]& |9 u; K. _" C代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。9 |& f0 C3 ~4 t, |1 C

    9 _& S8 p/ q; ~# Q+ l见证奇迹的时候到了,运行输出, p2 F5 g# d4 W' C! J3 z3 X

    / |. Z0 u3 X' q/ I8 z+ z从容器中获取User对象
    0 G4 \8 |4 y2 o; Iuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    0 F4 z  k; G0 ^$ }5 F多次调用user的getUsername感受一下效果
    - u0 t1 R. y, k8 {! n2 [
    & t$ L* `" Q$ P" b) p% H5 w0 |********
    , `# A% N4 X# Q( f9 ~第1次开始调用getUsername8 c  c' g4 Z/ X" N2 |( ^
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    . o* o9 V+ e6 h" V& c% L% ^---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    8 {5 ?- o1 R1 t1 R7b41aa80-7569-4072-9d40-ec9bfb92f4384 l$ S( @1 V% i/ t- F! {6 \  F
    第1次调用getUsername结束1 w- O+ L: L& g- @
    ********
    1 `( q2 x$ m9 M! d$ L. h% ?3 e  ?& z3 H- }
    ********: @8 w! f1 H6 ?3 o1 _3 m
    第2次开始调用getUsername( S$ @* v4 z2 C7 n8 {7 j
    BeanMyScope >>>>>>>>> get:scopedTarget.user/ Z9 q+ k# D7 o
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    7 F# u" K, s( W& W01d67154-95f6-44bb-93ab-05a34abdf51f% _  W2 X5 Z; s9 \
    第2次调用getUsername结束; g2 S8 t. S2 g( i6 x4 m* U
    ********0 [% i% |  G8 q: [* |. C- H; h

    * O7 U# i5 ?, y8 D$ \" a4 ^& J********! P9 P& H* p2 Y1 g7 K) f0 |, h3 E
    第3次开始调用getUsername* [3 w# U# B8 I. h/ ~7 b
    BeanMyScope >>>>>>>>> get:scopedTarget.user, W6 W& Y+ L- x0 i  ?" n" C
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    1 O$ ^* j" @% N+ l/ @5 ^" y' v7 G76d0e86f-8331-4303-aac7-4acce0b258b8
    , {! E% X- Q# v2 l第3次调用getUsername结束
    ' q: \: [5 R/ |! V; ^********8 ^9 _; K* H4 Z" D
    从输出的前2行可以看出:! g8 Q) D$ R2 t  G8 e9 ~

    8 h+ a: D- {$ f; e  i$ `% K调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象% w5 n" L& N# q/ Z
    ' r) v5 U/ |  \2 _7 e8 K* D
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    , M  q; K' [% q) R$ }( F) l; C
    ( ]* O9 I7 Y2 x  e4 w1 G后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    : w, O/ Z% ^: B- M$ |$ t$ u# o7 u
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    7 X% P6 D' O/ s- ^# Z" C) K% _
    7 v) Y- G3 d3 h/ J! N3 o! q动态刷新@Value具体实现2 A/ E. D/ G; [6 D6 Z* ^! q
    / y5 }  |  L* Y1 W/ |, p) W2 D
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。/ _( r% j$ k9 |+ p" J

    " l" [$ K4 e( E' q' s3 C先来自定义一个Scope:RefreshScope
    + |5 ?6 _" J# I2 N7 ?5 ^0 C* g& [" W
    ( Y& F, C1 Y2 m9 P! C: ]/ ]$ ^0 {package com.javacode2018.lesson002.demo18.test4;' J; H/ W" g- s* J- p

      B+ L9 E8 |$ D( `- Dimport org.springframework.context.annotation.Scope;
    0 C  S+ }9 u0 W8 L% X8 I* Wimport org.springframework.context.annotation.ScopedProxyMode;8 i$ N% @/ k" t
    7 f8 Q5 D$ Y9 k/ M, F
    import java.lang.annotation.*;' L; b9 i- s1 |/ }1 o4 G4 z
    ; ~2 H  y- y& Z" h
    @Target({ElementType.TYPE, ElementType.METHOD}), w1 d4 B* U, h9 I/ f; u
    @Retention(RetentionPolicy.RUNTIME)& U# `% u; A0 {5 D( p2 }
    @Scope(BeanRefreshScope.SCOPE_REFRESH)7 i! N; g# A; o9 E5 Y& S
    @Documented
    2 H- w- N% ^2 T1 f& Lpublic @interface RefreshScope {: T5 T) k3 \' S1 I4 G. Z
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1( j+ h1 B1 c1 I$ X
    }0 i7 q& ~5 C$ M9 _% b9 x
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置& g8 b  w/ r& P% m: b/ V
    5 y* h6 q  V3 L: [8 h
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    : y! U8 R: F8 S4 e) M  R5 q% i. k9 @4 M. V: z
    这个自定义Scope对应的解析类/ Y( N! Z/ O8 P4 f. w* X; l

    . r# k6 M% z5 v# Y下面类中有几个无关的方法去掉了,可以忽略
    / ^' F5 K: F; f6 S* n6 i' [  n
    0 o6 Z" ]' u0 p0 R: w( A0 q) Wpackage com.javacode2018.lesson002.demo18.test4;, t" f* H1 @- u/ u+ F8 w6 y
    6 s* K0 ]! ^3 A9 C. B, x) l

    ; o( E2 b0 B8 jimport org.springframework.beans.factory.ObjectFactory;( w$ h' {0 D* A, f1 \4 t! H
    import org.springframework.beans.factory.config.Scope;' }- J4 b. }1 U8 ?( V( ]
    import org.springframework.lang.Nullable;
    4 U  T, h$ Y: L* O& L, T# [% f; Q2 l5 v# |4 a5 w4 F: G+ a- u
    import java.util.concurrent.ConcurrentHashMap;
    * A( M2 Z* N4 {0 z0 ~3 s0 l8 q$ J
    public class BeanRefreshScope implements Scope {
    7 c' w% U3 M6 n) m
    " C7 H0 E! F4 z4 `    public static final String SCOPE_REFRESH = "refresh";+ @5 Q- O, Z& A# ~  ^# M6 ^) K) f

    * S3 P! T3 }7 `! d3 C4 }    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    2 ~0 f  s! \/ Y2 n: S
    - x% ~; A# y8 f- k) c+ H% O) k    //来个map用来缓存bean
    0 @- x* U( T3 ~4 A1 O: m    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@11 G; k1 I/ i3 l4 l
    8 c) ~! _8 B6 O0 u) S& d
        private BeanRefreshScope() {5 T- ?5 ^4 [1 Q" X2 u1 w
        }
    9 `& a4 T; \- I/ B+ L6 L% W
    - u, S2 }0 q% ?6 Y    public static BeanRefreshScope getInstance() {+ f& s1 h! E: z) D
            return INSTANCE;
    % e8 N1 i1 W$ Q/ V# [/ ]! t+ m    }
    $ c  _) L* Y# l  s* R9 E  C3 G1 v# c* o: k+ \+ O' ]
        /**# h9 [3 e$ l1 A$ c+ x
         * 清理当前$ V+ |( ~8 J6 _1 K: G, n/ G
         */; W* s% B5 u1 q
        public static void clean() {6 D% q  P, x+ U' b0 @+ X7 K
            INSTANCE.beanMap.clear();- T6 g( T1 `! j  T8 v
        }
    # [2 P/ H1 i8 r3 e: B2 X
    2 s2 d% b( Y2 i    @Override
    , `' g* m% K: R/ f# `  X7 y7 s    public Object get(String name, ObjectFactory<?> objectFactory) {, `6 e) w# v4 y5 `. r" i) w
            Object bean = beanMap.get(name);
    0 h; o" t; P% |# o; n. [        if (bean == null) {7 U0 c% h$ _* c$ L) d. ~5 H  G8 ^( X
                bean = objectFactory.getObject();
    + `5 Z0 q# O& a' }8 T            beanMap.put(name, bean);; a0 x3 d$ W3 \5 A1 c1 B
            }6 S& W2 T% y8 r* W' X) A4 b2 k
            return bean;% @. g0 {0 y4 I5 |2 n7 |# K5 x& O
        }
    $ J: v% d% J; |5 z. a. I( I$ ]
    1 v; Z: T, ~/ _}
    8 A4 K" w' o  p1 ?1 k0 {( u上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中' Z5 c/ h' \/ Z
    9 n6 b3 _' N( g. E
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    & p0 q4 u" Q! B# e  l" c+ Q  F3 J) Y& t$ q% c0 x3 j: n
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope7 l' A# v3 k4 R3 \) ]
    ( h5 y" n7 h2 S7 R
    package com.javacode2018.lesson002.demo18.test4;
      |9 y/ D9 }' z* r4 g
    ; g+ G7 n( D) S; \) b  P/ }1 qimport org.springframework.beans.factory.annotation.Value;
    ( N, a9 e8 z- z* |! @import org.springframework.stereotype.Component;
    + l3 U5 l, K0 o" q7 V0 ?' h9 ]0 m2 [5 J9 ?7 a0 p1 Y: H
    /**( C( E. ], R  y% ~- k' n# m( ?
    * 邮件配置信息
      B; b& u4 G6 Y8 J; Z9 j */
    - u' L7 j+ n. h: b4 J1 n@Component% n; Q8 e) y) z+ n& m. B3 h% K, h
    @RefreshScope //@1
    3 V/ A3 o& H( y" h$ f  B* u  ~( F' jpublic class MailConfig {' w  Z, L. o1 H

    8 t3 q( N% P/ i9 r( d( Z  X0 P2 H    @Value("${mail.username}") //@26 ~. \/ f  X0 R8 ?% e4 R  N  V% c
        private String username;
    ! J7 }4 K7 Q: e3 @
    . D9 T1 @& Q! ^3 r) B    public String getUsername() {
    " t9 c$ o# A  C8 p+ z/ ?        return username;7 I! A; U$ P* K! s2 s3 C
        }! V1 L. p6 ?% _) r: a8 J0 E
    7 a9 k. ^5 O3 o/ o9 e' v1 V' L
        public void setUsername(String username) {
    1 D  G- ?; r, U. j0 s( l$ t5 c        this.username = username;$ a; y' W4 m" c+ d
        }4 v5 E: D0 A% T5 N+ ?  t( D( n

    5 A$ Q4 m' Y2 J! o% p    @Override8 F6 C! l) {% `5 e! O" h; i
        public String toString() {
    8 E# K. L. z" R: d* ], b) U9 P        return "MailConfig{" +, [' [% o) V' r5 |& q/ f- U
                    "username='" + username + '\'' +: B2 J" z; \+ f6 x
                    '}';
    + V8 n/ F! `4 I) _    }9 Q  Q0 U' F' \( E2 ~9 L+ k
    }1 ]( c( u; @2 i1 g* k
    @1:使用了自定义的作用域@RefreshScope4 w! ]" m  K; T
    ; K$ W6 m: _1 s$ W3 u$ |- B
    @2:通过@Value注入mail.username对一个的值& j. P" n+ q- Q/ t- v( s6 i: O9 S8 [
    : p# K4 j  U& y+ B, c1 N% v- y5 j
    重写了toString方法,一会测试时候可以看效果。; E4 R' J" I7 z' l2 L

    $ I" x+ W/ E  ^! S. P8 R再来个普通的bean,内部会注入MailConfig
    ( l* I& p* p6 v( a6 @7 v2 R8 f: I$ _5 z5 G
    package com.javacode2018.lesson002.demo18.test4;# o1 B. Q5 ]% I0 |. J

    / O' e! O0 U9 e( I. Q1 y; Dimport org.springframework.beans.factory.annotation.Autowired;$ @0 [: O2 ]% T1 w; v/ Q
    import org.springframework.stereotype.Component;  u3 T2 C& }( Y

    0 z$ N, e" @" ^: |! T@Component
    5 V8 l( p3 g8 ^& f6 x  [public class MailService {
    ) u3 ^7 S0 ~, v: h# j1 j: R    @Autowired- _' @! L+ Y1 K. _
        private MailConfig mailConfig;
    1 H$ T6 s7 b8 n  q- ^/ n- ?0 b: h3 c$ L) [% b- ]# a
        @Override
    & h; K7 U+ ~/ z6 |$ ^$ F8 L    public String toString() {
    3 T/ T5 O; }4 j) `/ T. C        return "MailService{" +
    - x- r+ K" f, `+ v1 k+ i                "mailConfig=" + mailConfig +, p+ W$ H! ~. E1 x& t3 c7 \
                    '}';' N7 O, L$ a1 u3 g
        }: g, }0 e) ]5 _& O
    }
      ~+ T3 R9 K% o5 {, l' q代码比较简单,重写了toString方法,一会测试时候可以看效果。' E( w5 r! T3 O; ^* y* ~
    4 r3 Y+ t+ s9 ~1 f% Z* w0 X
    来个类,用来从db中获取邮件配置信息
    1 d; X% O& ?* w9 w- Q! }% X! P2 M% T5 s1 h" v5 P  k3 X8 ]
    package com.javacode2018.lesson002.demo18.test4;
    ' N9 f1 }, _9 c  n6 l$ V: r# V5 P5 u+ e/ I6 u. ?
    import java.util.HashMap;( V0 Q( _+ X- W1 g4 e
    import java.util.Map;
    ( Y4 K0 I2 P) Y* M. M& O, _' eimport java.util.UUID;: y8 ~/ X% B: t' j  |
    # R8 N  ~! m8 x$ ]5 a2 l3 e
    public class DbUtil {: }+ A. D1 B1 ]1 c9 W, m3 w
        /**
    ; }! J6 A6 ?4 k" T     * 模拟从db中获取邮件配置信息
    + g2 O8 C+ d- P# \) S2 O) R* A( D7 P; s     *
    & G: [& l( P1 F* @+ m9 Z     * @return
    2 x! c# {. A; C- W     */
    8 L8 q  z- p; W3 G; y- Z2 U    public static Map<String, Object> getMailInfoFromDb() {
    1 r  h, g+ `$ y5 f        Map<String, Object> result = new HashMap<>();) R" h: L0 \( }# j' w1 k( j) g0 P
            result.put("mail.username", UUID.randomUUID().toString());9 ]6 a$ ^1 |8 s
            return result;2 H, Z2 z7 m8 i7 Y) V2 O7 ]9 ~
        }: O1 }2 _: D: X! @
    }# i4 {; c8 \9 i% i( f
    来个spring配置类,扫描加载上面的组件5 ?1 T- B9 p# R( J. g8 i+ k1 T. Q6 E

    9 ]/ |, e/ Z  F2 @package com.javacode2018.lesson002.demo18.test4;
    3 g4 ^3 n4 y8 A& G+ s) D" F1 c3 L# Y: K( F5 Q
    import org.springframework.context.annotation.ComponentScan;! J* U  s' \( ?, V* O3 U: d" F
    import org.springframework.context.annotation.Configuration;- O+ y8 J: Q, f) O

    9 l1 g- V- y. l, p, d* H2 N@Configuration
    $ I: r! n- e$ v# k4 X@ComponentScan
    3 W8 t4 E: @/ G# h& m) g* e1 ~public class MainConfig4 {
    # @8 Z8 X  M* C}9 H6 Z' m$ n. T9 t5 p
    来个工具类
    . d8 f, x4 T9 ]" [5 U, y7 f0 K: q+ Q& R7 Y. x( l
    内部有2个方法,如下:# V: X2 M- m; l* {! O+ R, `
    & a' E  K% o* D: {9 h% S
    package com.javacode2018.lesson002.demo18.test4;
    1 ]$ v& z2 r4 ~1 e: R7 S0 l5 T$ _5 s& H) K# {6 s0 k* {  s
    import org.springframework.context.support.AbstractApplicationContext;
    ! D& P3 F/ u8 _5 t8 k+ qimport org.springframework.core.env.MapPropertySource;
    ) n7 {$ r; Y$ s, e- E. ]0 D
    1 ~  X3 J$ g  W, x5 ^import java.util.Map;/ P  [# @6 f7 w! n; }

    4 D( i* B# a. h, S2 q& g6 H+ npublic class RefreshConfigUtil {
    % f# G; {- @7 \( |# s: |% S    /**
    4 _( e( Z7 z  W3 g/ n     * 模拟改变数据库中都配置信息2 ?' `' h: j/ b4 p
         */
    - h4 H2 B, `8 ]6 Q    public static void updateDbConfig(AbstractApplicationContext context) {. _3 u  d7 [( O' |
            //更新context中的mailPropertySource配置信息
    4 f- S$ c6 p- @( X- r4 \5 v6 [        refreshMailPropertySource(context);  g- r& J1 a1 {2 }- n* I

    9 l  K  A+ P8 }! g" ^        //清空BeanRefreshScope中所有bean的缓存
    ) ?0 b- h- G: y* V- E2 W3 P; V        BeanRefreshScope.getInstance().clean();" ^9 p1 P9 U+ l
        }
    # z2 Y6 @% @2 A4 y& T# d" ~5 z+ j6 @' [5 o# _0 B( k& D
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    9 o& p! f2 K" |0 R9 o% y9 L+ T        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    / M" }- E9 G  o- H: I1 z) a        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    " I$ @1 l+ y0 `5 ~+ g2 S        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);! G9 g5 U# L( p3 V
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);: Q$ J+ S. `. U1 q  r& j+ ]
        }* a: I; |4 k  \( j8 G7 T/ T9 _
    0 n) }9 f" z0 x5 P( _
    }
    + Y; i) w' n% @, K$ I* u0 mupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ; J: F8 \1 U# X! v$ x; c! s9 g1 J. R. {6 ?
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。. Q) T) g3 [% f8 J
    8 b( ^( U" a6 R; b$ `- Y: p
    来个测试用例0 I0 {( r6 `1 }0 f) q

    * n6 N: y* p9 r( T@Test
    2 L$ d% A1 J5 C; Z( d4 ?& E8 Hpublic void test4() throws InterruptedException {" C2 G! v7 b: c6 G, c
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    5 s8 x" G3 |4 r% t0 N    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());0 e# w' C9 }4 p2 c
        context.register(MainConfig4.class);
    * X5 H+ Q; B9 q9 p    //刷新mail的配置到Environment6 M3 z$ h( m  X2 U) f& J- m
        RefreshConfigUtil.refreshMailPropertySource(context);1 h) t$ Q, T# @- m2 {9 `
        context.refresh();$ O3 Y) z2 |3 M9 V4 D- E; U; {

    1 R& [2 `: ?$ O9 T    MailService mailService = context.getBean(MailService.class);
    3 s5 U; m3 M: F* U6 `7 ^    System.out.println("配置未更新的情况下,输出3次");, B1 T3 E9 W# T0 }
        for (int i = 0; i < 3; i++) { //@1
    , u) S# d+ i; k7 [2 f6 \        System.out.println(mailService);
    2 m4 J+ S/ K7 Z+ L* i/ l" S* `        TimeUnit.MILLISECONDS.sleep(200);' b: e% ?" j: g  M7 a8 |8 ~5 M, O
        }- I+ V" y. h: {5 V+ O

    , F9 ~/ s% r0 }( }2 V    System.out.println("模拟3次更新配置效果");
    " w( }* {4 g% o1 D$ g! [    for (int i = 0; i < 3; i++) { //@2
    ) E; Z3 W1 L9 K: T0 R# |        RefreshConfigUtil.updateDbConfig(context); //@39 }3 f6 D* @" q
            System.out.println(mailService);
    ( t( _! K% X+ X9 p$ |9 F, `4 m        TimeUnit.MILLISECONDS.sleep(200);6 K& h0 x( n  I
        }
    ! Q5 ^( P+ e& o4 x3 F}
    & [+ Q& t1 G: E  `@1:循环3次,输出mailService的信息
    $ n* [  }3 Q$ S1 y+ [  f3 p. i, H+ s1 B" G5 |
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息- }; S- h3 u  D, p$ W

    , A( |2 F% P: C- P/ d7 V见证奇迹的时刻,来看效果9 Q8 q: {* m. ?( d: o& O

    # z% D. `* |9 J) k7 O6 u配置未更新的情况下,输出3次
    6 f% U0 r7 K: h% J2 O; Z6 }MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    / |- ?1 K7 r) w5 r* w( VMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}% b4 C! I+ T/ P. u5 E
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 w3 [9 d* A. S模拟3次更新配置效果
    " `9 b. V- z! n* k' g) t! }) jMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    " e& x4 {" I. ]) C! @MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    6 P. s9 ~+ u& a! U; u- Y( hMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}" E+ }' L! D7 r7 b, [5 O
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。- J; d, F* J6 M9 Z  d4 b

    2 }% Z* E5 k, w7 k1 r小结. |* `/ e- i4 D0 w: L! Y1 e: {

    . M: ~: A( T$ r动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    ; B  r2 T; Q4 e& a" j! J! u
    6 Q. l& i( D! g4 Z! l/ w有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    * y# E7 H# q/ y: x: s; [0 ?9 P9 b2 ^+ a" }; X2 A1 x
    总结
    / K' T% L- K& X  S3 N& U* x" P0 {  Q+ r- d% q% J
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    $ ?! r& q+ h9 \  i8 u: M6 @; H
    ! v" p6 P) w& E. x4 P/ F7 {% C. I案例源码% ?$ g1 J. z  H. G9 b  Y5 W0 x+ e

    2 p5 T3 ]2 t7 n2 U8 n1 Hhttps://gitee.com/javacode2018/spring-series) f: r( w$ C- z0 e* `* R* C
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。2 t  k5 A- |6 W7 Y

    ! g* V) v  x/ W. dSpring系列
    $ `7 r. k7 j( w: r) v
    , F7 c4 ?- F# i0 WSpring系列第1篇:为何要学spring?
    3 Q2 t0 H: C& L& Z
    * a0 |6 h2 U# @; [0 I$ mSpring系列第2篇:控制反转(IoC)与依赖注入(DI)* l& x" _: d( f2 J& _
    , }( E: ]# I$ H$ J$ x& O6 z
    Spring系列第3篇:Spring容器基本使用及原理! K( P  b6 W+ C/ F" R' [

    7 W/ }$ Z: D1 x6 U! R+ cSpring系列第4篇:xml中bean定义详解(-)
    # N+ |; B! t; g4 ]- }
    ) ?- u- D: ]+ o) gSpring系列第5篇:创建bean实例这些方式你们都知道?
    - W$ |* Q3 `4 v. e4 V0 }9 e- v+ f3 {) p' k8 i/ t8 h2 K# K4 D
    Spring系列第6篇:玩转bean scope,避免跳坑里!1 o! C  }" U# H. h

    : i5 D: l/ @' _Spring系列第7篇:依赖注入之手动注入
    , b7 o" S( O7 _: G! D& G" f0 Y% d/ B" Z! X2 u8 {, q, j, q: C$ g
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    ; D) u% b2 N; m5 \5 `2 B) \
    ) j+ T6 `* M; @7 W, sSpring系列第9篇:depend-on到底是干什么的?2 w6 f' \! t6 I+ S5 U

    " u8 F. Y% z8 k% h& |Spring系列第10篇:primary可以解决什么问题?2 Z) k3 G2 j6 M6 C3 F, M! `+ r. c

    $ V! j2 U' S! u6 F$ wSpring系列第11篇:bean中的autowire-candidate又是干什么的?
    - @* ]# J2 D# z" \9 H
    ) H) z; v- a2 uSpring系列第12篇:lazy-init:bean延迟初始化
    ! b6 j1 w. L9 l$ }# v& R
    * y; ?0 |( d! E- F+ r7 u; CSpring系列第13篇:使用继承简化bean配置(abstract & parent)
      @, P$ n. [# M+ e0 e0 b! W6 i5 |4 Y4 T: v1 d' |7 ]
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    % Y! j  P# T8 c$ @: ^0 `
      x2 g' _( h: r" P% J; M  |Spring系列第15篇:代理详解(Java动态代理&cglib代理)?9 s7 t7 Y& P+ G- A8 q  y
    9 q& Y' D- h+ p" ^; G) J: [9 b& k
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识): a8 P1 U2 H- [7 W* G: I; R
    ! M: O5 y- G0 b, r5 H6 r5 K* G
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    2 {: q. c1 t" }8 S7 x: g
    6 R4 y& I2 q; O) d2 BSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)5 Q# |, R, \0 Z% k- }$ c

    " H, ?' J$ ~- D) ]0 Q* }Spring系列第18篇:@import详解(bean批量注册)$ L8 h9 O7 b4 J4 G  [# ~

    1 ]% h) ]7 L) w) o/ v8 ESpring系列第20篇:@Conditional通过条件来控制bean的注册, O* d6 P/ K1 v( k

    8 C2 J4 M, A$ j3 g2 }; |+ i2 ASpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    $ u! o- X4 [" Y' A9 `7 D
    ! c7 w) a& N& W, ], _# QSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解8 R' M4 ]3 z. `# T+ |/ D6 z

    / s+ m5 y; [2 bSpring系列第23篇:Bean生命周期详解
    5 _; l; c8 Q( {$ y- i4 q0 D  K. E7 c9 V% g
    Spring系列第24篇:父子容器详解
    ; V# S7 T- o1 }  \+ _5 D& b3 {1 D. T3 T% j5 L2 n* t5 h
    更多好文章
    , P# k* S; x0 }9 }. ^8 }
    3 {+ B& @% q, i: kJava高并发系列(共34篇)) n8 P5 ^+ w+ H7 Z( z# P
    8 g! C4 F, ?4 `& V. m! @
    MySql高手系列(共27篇)+ M% b5 z. D4 ?  ^$ F7 x# s

    5 U6 n! g7 B, ?5 ^" PMaven高手系列(共10篇)' V+ Z! G7 X% Z+ `! f0 J  k
    2 H* F  D$ l9 u$ Z: p1 k
    Mybatis系列(共12篇)
    ) q9 J/ K3 t/ F0 }0 T7 `
    9 n9 z. C: ~2 c1 o. w% [3 H聊聊db和缓存一致性常见的实现方式
    $ Z/ k; M! I# ?8 C& X0 y
    / \4 b/ V( x  M, x接口幂等性这么重要,它是什么?怎么实现?
    / E, R3 S6 E- p3 v4 Q" f1 Z6 U6 @  @( Y! W& u
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!9 t( P* [- X1 L( l2 G: r4 b; U
    ————————————————! H; |( z7 A) J: C. S( k* O* A
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    " [" Y/ ~# S% L! e  D. W/ l/ n原文链接:https://blog.csdn.net/likun557/article/details/105648757
    6 f% [) w5 p% ~" l- r1 x; @1 r3 l$ D  Q9 s& w' d
    2 R- O, l  f& N" r/ J$ |
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-6-12 22:42 , Processed in 0.463690 second(s), 51 queries .

    回顶部