QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5538|回复: 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!
    ! u2 ^; {  v/ ]" f* D疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    ' `" U/ S; E5 v9 q1 X$ V' f* W1 U7 b/ T9 r
    面试官:Spring中的@Value用过么,介绍一下* |; f) w; W+ ]" ^6 m
    9 t3 [& ]. C5 N# y
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    % c# Z7 @$ G5 I: I) T  [" D/ N5 ^( n; c7 \5 d8 C
    面试官:那就是说@Value的数据来源于配置文件了?
      R' I# e- s" l$ v0 x1 g3 n3 V3 a3 |3 [
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置; g& g! c: h# _) ^$ V0 X

    3 z3 n, u" D- A; L& X# y面试官:@Value数据来源还有其他方式么?
    ' t2 }% k8 n0 Y1 C( v) K0 Q
    & G$ B9 z" j1 Q6 Z  e( ~我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    0 |$ \% A* R4 w/ m- c+ c  a* c& o- b/ v5 [
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?1 \" k# D. j) w
    ' ]6 g) F! J7 q
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    9 w5 ~. S* c' v
    5 @0 O% i6 C  p3 k7 w4 x4 k9 G' V7 l面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?& z- Q$ `- q& K/ k5 S6 ?

    3 F  N9 {- n) P我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能2 ]" i' s3 [/ x* ^

    - G& M, m' J9 m9 w面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    8 o" T4 K7 F# M5 _( t1 F% B' d
    " i4 i  U5 |) q  r" `, Y7 n4 b7 w+ F) J0 A我:嗯。。。这个之前看过一点,不过没有看懂
    ( x6 G* n- ]* X0 O0 }3 z6 m; L9 q5 _* w* i$ o2 Y7 |4 }; `
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?8 }% S4 A1 _$ ]  h6 e$ d

    2 F& C$ t) Q  ~# G3 _: I0 m* ~我:3万吧+ U0 u! }" a, v4 S; j

    ( ]' V* f% x2 f/ R6 @0 N" A面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?  X6 K' R$ w  B
    8 z$ r+ p3 [" c6 A" ?# J" a
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万% P  j3 ]; {# G% V. {5 P# o
    " ?% ]: y' N; v* Z3 P4 a2 C9 s+ h
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!- T7 n. X, a5 g
    3 l' p  D) Q1 K* P
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。" g& |! J8 B, H) x5 G7 [% c" A6 h) I

    # C) ^0 w% `  a5 P这次面试问题如下
    4 y+ c% K0 ~/ u) Z
    3 X; W: p2 W0 f+ G% X7 [9 @@Value的用法' i8 s1 r' F  a4 c; M' e

    3 z; S, B- b9 K& r+ q2 ]@Value数据来源
    8 Q5 i# `1 a% }; y; @2 V5 s6 X  i0 e, O7 \) k' U( L8 ~
    @Value动态刷新的问题
    * L4 g9 f0 W5 t  @! n' J/ J5 f! P" V: b
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    ) ~# {( |% C$ H+ I% c8 v, I: [! q" `$ _
    @Value的用法
    7 N1 L7 k' m3 y9 c6 s( x2 n" Q/ K" k4 _: M2 H4 N( f5 m. t
    系统中需要连接db,连接db有很多配置信息。
    * v: e& B& B; w/ y8 n) m1 I
    3 J  T3 y: K+ \& t; Y# n* Y系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    6 a; K& ]7 I9 V, _% J, w
    ' q& a% \% @1 K. a还有其他的一些配置信息。$ I7 E8 U/ s/ q7 [. L$ d5 C3 `
    1 w  u: p( t- o" U
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。: m3 e2 @! M, _8 g5 C

    . b: v- i+ h# J: s那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    # ~8 d3 j- d- J2 r7 f7 y5 H9 t/ b0 n! M8 j' T* [
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    5 Z) k0 q. l5 {$ h
    6 L8 V  U! J) [* K* R; i8 ^通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    # G2 d6 z" \0 F) Q6 Y) n% C" ~+ [( e
    @Value使用步骤
    $ ^2 |+ {7 Y, y9 f+ ~. p4 j3 k; s
    步骤一:使用@PropertySource注解引入配置文件: \- }3 Q  o; S; i9 h" w& o- _

    ! x. |0 E- R2 m6 J将@PropertySource放在类上面,如下
    & Y9 l7 c3 v% o* ?7 h+ a
    / R5 o$ s$ z5 w2 L9 _; k6 v@PropertySource({"配置文件路径1","配置文件路径2"...})
    7 J4 H/ v( P6 Z6 \1 _@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    9 @4 b7 M$ \8 C8 ^6 Y
    " Z3 S. @8 ?+ O3 t2 }3 P5 f7 V! Z' l如:$ V, p6 a3 O( M7 X
    $ b4 S* P) L1 e
    @Component
      _9 @7 T& [$ S5 B. D@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & }. ^. b. G# Q& j1 V8 Y/ @public class DbConfig {
      T( q) e0 a% ?" V- [4 o. \; T}
    7 X1 g/ E' D: c& n1 F步骤二:使用@Value注解引用配置文件的值  a5 p" L. L1 W6 E) f9 S* T' J
    0 ^4 t2 O3 @0 H- O! O* j
    通过@Value引用上面配置文件中的值:
    + ?3 t4 J" j4 V# k& Q# ]
    0 l0 l" ^3 [: j语法" ^8 L6 I$ B' I! y2 K. P2 {
    6 t, v8 s" S5 B
    @Value("${配置文件中的key:默认值}")
    3 L2 j5 e! v3 p5 S5 l, P8 _@Value("${配置文件中的key}")( N/ K* g  V3 H
    如:
    ! T+ @7 q6 X- K$ z" \( w6 a! i
    1 C* c8 ]. q' q# a@Value("${password:123}")7 D. T4 U* t1 Q- r! S* f5 {, @
    上面如果password不存在,将123作为值
    0 _$ x# O: J# u0 V. ~3 k  k% r* \# V9 @, G2 x. k( Y+ l$ U% Q
    @Value("${password}")/ S% I& F( O3 e0 j4 T
    上面如果password不存在,值为${password}" o2 v' }$ I: s" g/ E

    & @1 ]6 n% s8 K2 H8 f假如配置文件如下8 g- s9 Y5 |: V, }3 \5 w' ~
    + L, f: F) c6 R) A
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    8 D5 Q0 W$ Y/ |$ E5 sjdbc.username=javacode
    " N  T2 r% Q) p2 Pjdbc.password=javacode
    & T0 G0 J! a! N8 e8 W使用方式如下:
    5 T* c8 N7 [" X  Y& s% J
    : A# _& U. w; j2 v3 l@Value("${jdbc.url}")
    ( d3 g5 F5 m8 ?/ K9 qprivate String url;4 W' M* T5 a- _
    , q& Q' V! P$ m3 d; Q
    @Value("${jdbc.username}")
    ' n) C" F! n6 zprivate String username;
    & o& h% U* u2 t# y
    * D! P7 z! ]3 N6 P@Value("${jdbc.password}")* v) Y: a8 m, f" @% M6 r
    private String password;5 [7 I/ g2 t7 H+ l4 F" o
    下面来看案例
    ' J: s/ \4 [+ ?7 I8 y  M  T: B  ^0 M3 s" B# Y! X; X
    案例
    - S; a) c* Z4 z9 h9 S- z" O7 C5 d7 g& q' F; w- {* r) n
    来个配置文件db.properties# H3 [- ]( }- y9 r4 Z- @

      J3 `) N1 @7 ]; V, `. f% e# ijdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8" _/ J+ |  Z2 B
    jdbc.username=javacode
    1 q) ]& }5 w* q& L! pjdbc.password=javacode
    2 U+ h" @7 J+ A) x+ T. P来个配置类,使用@PropertySource引入上面的配置文件/ r% c0 ?1 o* J& N+ c# j

    0 H; z, ]$ O3 O4 e4 [* e+ Rpackage com.javacode2018.lesson002.demo18.test1;) c7 z6 \5 Y" |& A: a2 J2 V9 s" z# G

    ; ~8 |# }# f: E) q. T* H& z* C) ]. o% ~import org.springframework.beans.factory.annotation.Configurable;
    4 a+ ^9 x" s; \% J8 n! simport org.springframework.context.annotation.ComponentScan;/ z( l& f7 t; u# s& C, @
    import org.springframework.context.annotation.PropertySource;# Q0 A, h$ ?; {5 f

    : T8 h/ d- U, [) k@Configurable7 c- H& N9 T5 s) D# m; q9 W
    @ComponentScan
    # n' }8 K1 k2 ]% U! Z$ B, d  ]; n@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})8 V6 l* @# d5 H% D
    public class MainConfig1 {& _6 k& d2 U, H( a% |3 W9 m8 b* |# ^
    }
    3 l5 ^6 K9 l+ f. o' y% ?& r3 ?来个类,使用@Value来使用配置文件中的信息# h: }$ M! m' A- y
    & d( ?8 B9 A+ ?5 |  I) y# ?5 H
    package com.javacode2018.lesson002.demo18.test1;; e7 F7 U  f, _  {
    ; ?) t2 p% K7 }
    import org.springframework.beans.factory.annotation.Value;7 v) p6 |3 K- M0 K+ g
    import org.springframework.stereotype.Component;  D- M) u3 k  [, U, `  ~  N
    # {- U. c7 k6 M
    @Component
    5 h/ G& W* K% s/ Q' b5 Fpublic class DbConfig {
    4 B. B* _7 V6 A: j6 z/ v- k& X5 W' ]$ e
        @Value("${jdbc.url}")
    $ t" N: T( E: @" E: ^& U    private String url;8 C6 R% @+ h) V

    8 K* I. [+ w- m" k' i    @Value("${jdbc.username}")+ e) N1 G% {7 r+ }8 d8 d
        private String username;
    - a, f5 |" ~$ q! k& |0 y' x
    + Y. T/ N/ W0 C- R    @Value("${jdbc.password}")+ }! w5 y" f$ r" k" w
        private String password;& d- v" b% D! O$ P1 V

    + z0 {5 q: ?" {) d" v; O$ x    public String getUrl() {/ `3 W4 C  E: m1 h
            return url;
    . Z( w. _# ]! F    }- l5 _9 m7 K" P2 ?& @* X5 @) p/ x& |

    : n2 ^1 a: n  H3 m- G* w    public void setUrl(String url) {5 m1 z' F& L8 h1 }& d
            this.url = url;) M& x  }9 z: @6 ^* o4 e; v! r
        }/ }* @  Y& P" s
    ' G  a3 V* t# S) G3 d, ]& S+ _
        public String getUsername() {1 a- _9 E% f. w8 |$ s' O
            return username;0 d  H, l" h( Y6 D
        }
    8 K: v' ?9 P" `; B8 w1 r
    / @# q+ n( I* G3 b    public void setUsername(String username) {
    $ [" T6 b+ F; ^/ i0 h! g; n        this.username = username;, v8 S2 c3 M7 a9 Y
        }! n" F, E4 B+ G; ~& g4 s4 A8 T

    4 W! n" S: l; R0 b    public String getPassword() {8 {, x" K- Z9 Y. V. l" W
            return password;
    / f2 k" V/ c) k4 K/ U3 h    }2 u: N3 i% R$ V7 A% [0 v0 |2 B$ g9 B9 e
    + u( R: n/ S! j! c" s
        public void setPassword(String password) {
    " L0 h4 \, ]3 s9 h        this.password = password;1 j' F; q! V9 ?4 m0 n2 k
        }
    * ?; i1 W- E6 R/ y2 T& ^6 A: B# ?: I+ }" \. O  P
        @Override
    6 ~; U. Z/ t1 e% t( h; Q    public String toString() {
    - S3 z) N* |* t+ M% H0 U% v; B        return "DbConfig{" +) D) {* ?, B/ N, }1 r1 \& S& o5 E9 X
                    "url='" + url + '\'' +4 t2 h' X% e' f% F- S: ^6 ], f
                    ", username='" + username + '\'' +& P+ v6 t! f, M5 g
                    ", password='" + password + '\'' +
    ; g) H( U  Z3 F. k$ g# o0 f                '}';
    , k7 U; A+ }. e  r, L    }4 ~0 J: V8 t5 Y8 z# ^, E
    }
    , e) m3 e: p7 a0 q; l: x3 U7 c上面重点在于注解@Value注解,注意@Value注解中的
    1 h  q2 V6 Q; E  {9 Q- B+ q9 X- V
    9 x; m- {4 X9 H9 \  C, t来个测试用例
    0 \6 f$ W! v# U+ Y6 B' ~8 H6 U
    ; d4 z7 F3 m. Y" S, S, _package com.javacode2018.lesson002.demo18;% @" B8 y' l/ K" K) ?

    " t6 E2 g) m6 I- f# m  \import com.javacode2018.lesson002.demo18.test1.DbConfig;( K( o, s5 l" [% x
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;1 R/ i, L, i& g: e0 K0 x" |9 ^  Z& x3 y
    import org.junit.Test;9 y" o9 b' M4 }' d, e' Z: x$ I
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ' W) P# c! q, f" L8 ~& V( A0 X& P; d% u" r6 Q' T7 X
    public class ValueTest {0 Q% O; m7 E* n" l, [: o
    9 H: v2 Q; ^( d0 g7 p- E1 W5 [+ e
        @Test# @9 E0 a- {$ ~. x+ a) h( n# Y
        public void test1() {8 ]  J# {0 c% s( M2 u5 E4 @
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. e# }9 B4 ^: T/ _8 j2 Q
            context.register(MainConfig1.class);3 f6 O" t6 @" B) O" ~
            context.refresh();
    # t4 A  u5 s' a/ t  ?7 {% r3 q" s, h( M7 s+ ]/ L
            DbConfig dbConfig = context.getBean(DbConfig.class);
    " s9 ?) D( e, g- U        System.out.println(dbConfig);
    9 U9 y: j  [! d0 e7 ~    }" Z: @" k( G  w  z7 |
    }
    ! s/ I9 P- t) e3 N  I" S: P运行输出
      ^2 L" `. p! d# k+ H
    - [' _* U2 W& E7 x' ]9 L6 k9 A- b0 FDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    " e' m$ k' i' R; O5 Z7 {1 ~  B上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。" B* v5 x% @& y

    + l7 x+ _; H8 d/ z# o1 n@Value数据来源
    - f+ t7 O! B  W" n: y2 H8 ]  e8 F( K! N+ K5 o: h5 n
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。9 E1 s4 ?- F$ V" r6 U, M$ G
    2 a& ?+ W5 @9 \3 l$ c/ W2 {1 T/ U
    我们需要先了解一下@Value中数据来源于spring的什么地方。# l6 V2 w0 G) Z( o$ B

    7 [+ o3 q. V6 a3 w! n/ ~1 Gspring中有个类
    4 q* {7 u) i) _4 D% q2 ~3 p5 Z/ X* P8 E, Q* z" q
    org.springframework.core.env.PropertySource
    . p. W/ x# B3 O( }- e' h8 o可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息" Q* K1 z. {* y/ K, s8 ~  A
    * O$ l4 [0 m: i  x+ N
    内部有个方法:  u9 h1 `% ?5 \  X0 t. J% ^

    9 c% G8 {$ S/ R, W  ~* Q; ppublic abstract Object getProperty(String name);9 P. U! f+ v% i1 d! |3 d. v
    通过name获取对应的配置信息。
      y" }4 @8 ^" o! o8 A, e/ M
    ' y0 g4 s: k3 D系统有个比较重要的接口5 O1 M, j- [7 w& _$ e% P( r
    $ P8 ?& N/ A/ g/ U- t! j0 Y
    org.springframework.core.env.Environment
    ; p/ V9 M* d6 V5 l; O/ _用来表示环境配置信息,这个接口有几个方法比较重要
    & ]* M7 Q8 G0 i! h: {
    / ]: ^( Y' ^2 ^# ?( T& r- IString resolvePlaceholders(String text);
    / i) \" A. R" d& VMutablePropertySources getPropertySources();
    3 t$ V0 f  }0 J' v! [8 GresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。. E* S. ]( F# h' w) x1 L: s
    8 W7 g6 W* u. V0 @. V% _# p
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    8 P8 v9 K! N2 h/ ]3 f/ a# v- ?' R3 V( ~% Q6 E$ [
    public class MutablePropertySources implements PropertySources {
    " r* a. ?( M# W1 j4 p1 T; m0 [0 w) b( \* Z# d1 }
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    9 M% u9 \# P# |7 D+ U- R9 ?( `% C/ M
    }
    : \; s1 d  r, W* u/ ^$ a内部包含一个propertySourceList列表。
    0 P6 B4 s3 Z! p: K+ H8 _% c9 m: A7 H6 i% P3 y
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。: P7 c+ B5 w' q8 f4 H1 z8 x( l# m1 l

    + g8 r" t! s2 X- J' A# Z大家可以捋一下,最终解析@Value的过程:
    0 @  Z! G8 Y9 r/ N8 @: B. S$ k6 F/ ~3 H0 G( L
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析' }/ b2 S1 Z5 h" b" e
    2. Environment内部会访问MutablePropertySources来解析
    ( f8 K6 d  [* _9 Q3 o$ U7 Y, d- L3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    5 ]1 w% m- ?& \( M, `7 {通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    7 ?' b9 H% K. Q( T. [) q5 F: r: C  Y" B7 [4 r
    下面我们就按照这个思路来一个。
    & u" r% ]! [, d4 S- D( F: B* O, r
    ' Q# \( R% k) h来个邮件配置信息类,内部使用@Value注入邮件配置信息5 G. k6 A! R- j2 R) h

    * K2 S" I( H3 X1 }/ T: \9 k; Z8 Hpackage com.javacode2018.lesson002.demo18.test2;& j9 z2 v( O0 F. Z( e, x

    2 X2 j+ ]6 U6 b, C4 [0 @. rimport org.springframework.beans.factory.annotation.Value;
    4 }. y4 I0 h- \& {import org.springframework.stereotype.Component;
    / Y0 [  w' D) u! R& Y. E! i+ a1 Y
    /**
    ) D" H! P7 f! H+ y1 M" |3 q& I * 邮件配置信息. H. e/ Y% t* l8 ]5 X2 n
    */
    , N. o) r+ U9 M' B) c! T" T@Component
    , U/ O' q9 T  E2 Jpublic class MailConfig {
    - h' t: Z1 p8 Q; e* B; S- M# x* \" f* t; ?( ?2 y: w
        @Value("${mail.host}")
    . G' Z1 H0 M& C  l    private String host;
    9 T* w; m1 j3 h) F, l( ^# e& U" C) o: L
        @Value("${mail.username}")" k5 K, D' S: r7 w$ |  ]: X
        private String username;2 C. Z7 q- R9 H" X' L( ]. v1 ~
    # R/ O* L$ J+ g/ r
        @Value("${mail.password}")4 v  S& W: j0 g' N/ {4 b
        private String password;  H$ B2 [. K5 K- D" V+ z7 M

    ' ~0 ]% R8 ^  u0 D    public String getHost() {9 q) g0 r6 M' S
            return host;7 Q4 H$ i$ [. ^$ z. U. G& C
        }
    ! o9 L& K! z; g. q+ u* y& l' v, A& v$ N: y
        public void setHost(String host) {
    ) c2 a5 s" E3 T( y: p& n" C% ]+ g        this.host = host;
    " \" {$ `* H9 h2 \) Y- @7 I    }
    7 r* K$ F. i; R- x. t$ q  T. v8 _6 y7 C! L2 t% i: G
        public String getUsername() {) A4 c1 e+ `1 n; L" A$ q  R* ]
            return username;9 X7 }5 G* I9 ^$ D& T
        }
    ! R6 Q* a1 W( g7 x# c7 c7 g1 h) y1 ?' d- ~5 L+ I+ m
        public void setUsername(String username) {: X7 `- n+ B- r( z" x: l) e  @/ U5 m
            this.username = username;' ~) E$ N. z7 m; a
        }$ S1 |7 O, e9 t0 }1 v3 |, a
    - `/ C2 r8 P' y" m# r) @, A
        public String getPassword() {
    * P% e7 m: Y- L0 G5 T) j        return password;, v' a7 h1 I+ Z8 c& m
        }4 @7 |" [  z' T2 t3 d- o! f; E
    $ f8 j+ a! C0 ?; |
        public void setPassword(String password) {
    5 A5 t: B- Q' E: L8 J  M        this.password = password;
    0 ]& l+ L% w9 w, T    }
    / G" _3 }0 K3 P$ H4 a) v
      m# Q0 g# |- Z: b9 J0 h( h    @Override
    , `7 o' ?3 d  f: L: V    public String toString() {
    " i( x3 q1 Z/ g; u        return "MailConfig{" +! I- [5 J2 G) I6 j8 T
                    "host='" + host + '\'' +: T7 Q" y3 |* u& X3 o5 X
                    ", username='" + username + '\'' +4 K- J. E" R# u0 q
                    ", password='" + password + '\'' +, W1 R5 P/ T& v0 T
                    '}';
    3 {0 K4 B9 ?% a" B" f" K    }& i% F! u" T: l* u  G+ M( x6 C
    }! g; {) w0 E1 x
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    7 l% R% a8 q2 |2 r4 R2 }' [9 w/ k
    package com.javacode2018.lesson002.demo18.test2;
    : u: y) e  p3 q7 a( Q3 r1 u- X9 ^* ]5 w) @! D
    import java.util.HashMap;
    ( q( W, x' d. n! z6 R& w3 U5 Y" Cimport java.util.Map;
    / W0 [& g% x  E0 T4 l2 H
    4 x7 c! [4 R/ a& M8 j/ |public class DbUtil {
    ! B5 q. U9 |  c! Y8 R0 W1 Y! K    /**
    + e" m1 u5 b. H- S; Q: X# A, ?     * 模拟从db中获取邮件配置信息" O9 T( r4 N) D" T) ~, \
         *3 G6 O' @3 S$ l# }
         * @return, }1 m: H& r3 I6 N: \) b
         */2 ]* [2 ^  y( n* c( s" _2 Y
        public static Map<String, Object> getMailInfoFromDb() {$ v% j  v1 P4 ]8 c8 Z  l
            Map<String, Object> result = new HashMap<>();
    ( o$ E3 M! R( k8 f        result.put("mail.host", "smtp.qq.com");
    $ l# j1 \3 Z) U7 J( j) l3 Q        result.put("mail.username", "路人");- w3 z& N  {( t, w& T
            result.put("mail.password", "123");% N7 k, z, t. v9 c8 F* V, M
            return result;9 M- O+ V9 T) f. p7 m( B# Q, S
        }
    ! ^+ r9 E9 J% J1 Z}# l7 h  N% A0 G
    来个spring配置类
    ; g/ x8 X0 u9 z. @# H
    $ w; ~. u6 j/ b/ Y: @, @8 q4 dpackage com.javacode2018.lesson002.demo18.test2;
    ) h7 Z# H9 Q9 o
    + }! R" H: Q! [! P% g  F8 A0 Himport org.springframework.context.annotation.ComponentScan;
    1 A: O8 e( j3 f* f3 e1 y/ ^import org.springframework.context.annotation.Configuration;$ F. q' r, ]; ]" I" i

    8 c7 m0 P; R$ k9 x4 s@Configuration
    : D5 ?3 b# D5 j; X1 t8 u7 e@ComponentScan* ?4 e3 h* t/ u+ P1 N
    public class MainConfig2 {
    $ a6 x% I3 m! B5 f! B}, S+ P+ w0 v8 ?4 ^! J1 F
    下面是重点代码
    ) J3 E8 V" w2 g. }0 i3 v4 r+ h. ^) H8 C' E
    @Test
    % z/ I5 I: R5 R" b0 G( J+ b. P, upublic void test2() {7 w: |5 C; B2 b. G/ k) Q  n
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ) ]' y% ?1 R( W7 T6 e/ c! P# T7 t% A6 O3 O, p9 m
        /*下面这段是关键 start*/1 B2 R9 ^" m1 q/ z8 T
        //模拟从db中获取配置信息
    9 A9 @$ N7 Q' Z$ N5 [    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    0 v  Z2 S6 I/ V4 F$ I    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)$ |+ t3 @# S" [5 N8 v- d( u2 o8 I
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);7 ]4 D; ^+ Y6 m8 @' g
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高9 T0 R5 r6 c! A: `: r8 U# [
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);2 ^# [1 G3 c* |+ t, |. ?
        /*上面这段是关键 end*/
    ! p1 U! f6 t! Z/ {5 {6 `
    + I+ w$ o! s( M5 w& s; h    context.register(MainConfig2.class);. o1 z8 F% m9 u# M$ j) }
        context.refresh();, o, n7 I( h) G3 \; R  a
        MailConfig mailConfig = context.getBean(MailConfig.class);
    ! ]. n5 Z$ ^; s% g! ~/ N    System.out.println(mailConfig);" p! T. U  F  p$ a0 z/ y
    }
    7 {+ e" M, P5 R  i7 Z5 f注释比较详细,就不详细解释了。8 k2 k4 d8 {* ^" q) Y

    $ ^) P; T. J& ~$ q直接运行,看效果2 [& V3 F; z8 y7 }) ]  j+ Q
    7 z! ?9 E) u5 F  [# X" |# |7 l7 t1 c
    MailConfig{host='smtp.qq.com', username='路人', password='123'}1 W8 I2 D) f8 o
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。, j1 X" X& N0 u
    4 r. m9 o9 y' I7 M* n& w3 n" \0 i* K
    上面重点是下面这段代码,大家需要理解* @0 H" s9 E' e1 G5 y. J' O  u

    ( C& i5 h! |! H3 K; \. b( d- x/ U! D/*下面这段是关键 start*/
    " W' v/ G7 x3 ?! w6 S0 j//模拟从db中获取配置信息. L/ Z' S2 x$ z+ H' H! Y
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    : ?  R* P) W% u. N8 c- ^+ ?//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ( ], @- a/ o7 _4 O1 V" VMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);) C. Z5 }$ B5 B5 v7 R. K( T
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高; ]* d, m! a8 ^5 _" n
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);+ r2 D2 \5 r, X  }
    /*上面这段是关键 end*/; h6 V4 v( Q5 `( w, E! b) @
    咱们继续看下一个问题
    8 x0 ?% P8 ^) d0 c9 L' z/ [8 G- O7 m
    . j  h0 [/ e: }3 z* ~; D如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    6 _' |  i) a7 n4 K6 n8 P4 x" @( ~. K  F! Y8 y0 E
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    3 u% z6 R% d1 E8 O' \4 }8 d6 c/ S- p
    实现@Value动态刷新2 H# ^" n$ J+ s5 P, p6 S

    # y# @- S7 u8 P8 `* f( H5 Z& K+ z先了解一个知识点  k, p6 y# g$ f7 O9 D. e

    ' B+ b! F6 W8 G8 C4 [, o* d这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    5 J* m( e6 U) R3 F# f' j3 Z* _# y- {3 S
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解9 I) i8 p* X' s0 W: H; }" I8 a6 r( }4 G
    . P8 q$ i4 M' u2 v
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:3 |# l" d, {$ R. @/ F6 B, B2 _, g

    1 Y2 h$ _$ @, ~8 \. YScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;7 [/ P  h8 Z  k5 r
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中# D  U/ F$ ?  S, G' b6 ~( |
    * B% g4 ^8 }/ y% D% L: L' f
    public enum ScopedProxyMode {
    ( q4 R5 b* }' g8 r4 w# {* ^( K' B    DEFAULT,' b' W3 }5 c, P& o7 H" n
        NO,
    " P6 S! e; S4 @* ]1 s    INTERFACES,4 v4 @$ ?, s3 }" O3 l& l# k
        TARGET_CLASS;* J: e0 S2 \3 L/ I' Q" S  q! F' A
    }
    ! }0 v: A$ H. ^% p& w前面3个,不讲了,直接讲最后一个值是干什么的。
    - e- m% N0 o) U- P4 M. m0 t6 ?7 v9 A( X' C3 m/ B1 O2 J/ T* K7 J
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    9 [( F. f$ p  w  s
    * N& P, {5 l( ?6 L) M理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    8 U+ D! r( x" @1 k  J/ y8 e
    9 U: A- \3 [8 }+ [/ k自定义一个bean作用域的注解
    * Q# a  T  s) U# r
    " g+ B8 L  m; H4 ^( B0 Bpackage com.javacode2018.lesson002.demo18.test3;
    3 Q; X, s6 v' |6 \1 h4 C* n; @/ u( j, U; C) H
    import org.springframework.context.annotation.Scope;' O* _. h4 R8 J/ }* Q  ]
    import org.springframework.context.annotation.ScopedProxyMode;
    + p1 M% h1 k# |7 F1 j7 {; P8 C5 g6 I  Y( P' z! K& B; w
    import java.lang.annotation.*;7 w, L6 |: J- z
    6 K5 D5 @+ J- A, [- I" `
    @Target({ElementType.TYPE, ElementType.METHOD})/ q" c+ e4 [1 U7 M
    @Retention(RetentionPolicy.RUNTIME)
    . T. o% R6 N* j+ j@Documented
    8 \- A3 i! U- ~% f/ g* _8 K8 k@Scope(BeanMyScope.SCOPE_MY) //@1
    ' i( \7 c. p: {$ xpublic @interface MyScope {$ ]/ L7 b3 S' K* \* r' B
        /**
    $ v" `2 Y: Z4 a7 k' c( [     * @see Scope#proxyMode()
    8 l3 B# k% o' u$ ?+ I. m' @: E, Q" H     */0 A0 T3 v" i, f4 `. y
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    6 @) X5 ]4 E; M8 [1 O}  @' O$ f1 X" K* X) O
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。2 {. ~5 ]3 M  e6 z" \

    3 `8 e" t( R4 T5 T9 X@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS5 ?" K2 ?4 P" C: ^- D7 |! U
    ) P  l" l1 p1 x5 ~3 m& i
    @MyScope注解对应的Scope实现如下
    # x  \+ i: O! m" Z0 q, C1 e& k  B9 w0 E
    package com.javacode2018.lesson002.demo18.test3;
    # \4 o( \; X4 M: U" j6 _0 R7 A6 n/ g/ l4 O2 d, A
    import org.springframework.beans.factory.ObjectFactory;
    1 D4 d4 o# z9 u; t3 ]& `9 fimport org.springframework.beans.factory.config.Scope;
    ( H/ g- V$ h( _  S, L! e1 Aimport org.springframework.lang.Nullable;, I- z" n1 q9 M% I6 ]) a# k
    $ T. O& U% k8 @7 b) V; z# T
    /**1 T& \; ^$ ?  y& ^" q( Q
    * @see MyScope 作用域的实现
    * n. L# t! V) }) ?2 U$ f! Z2 J2 x */6 ^9 ^$ J6 e, h0 R0 p* r* o' B) E/ c
    public class BeanMyScope implements Scope {
    & }+ x9 `% Z% j8 }+ M
    / J7 M1 e* E8 h; g    public static final String SCOPE_MY = "my"; //@1
    9 f9 U$ u: `* a! T1 J4 A! f) Z7 I" r! m7 w/ e, a
        @Override
    + [1 b! K* g2 E; p" U    public Object get(String name, ObjectFactory<?> objectFactory) { # z1 Q  w) N  [. q
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2* F: B( o& Q% s1 L3 h! R# l
            return objectFactory.getObject(); //@3$ M* o8 G% v4 s- \
        }- c6 X3 R) z0 }, l: Z5 q

    2 x( o- V% z5 Z    @Nullable
    # m1 o, V$ R, H5 {: V: u, t    @Override' N  s. W1 V2 c
        public Object remove(String name) {
    7 e5 B" Q  W( q6 O# @9 D# l  w        return null;
    2 i6 E; R. Z, ]    }
    2 V( o. E3 X' w# ?1 b1 B4 r+ `* j  R6 H  o. w0 w/ w6 ^
        @Override
    5 _! h2 `6 b: k; r( Z    public void registerDestructionCallback(String name, Runnable callback) {
      p; P. W! @" a1 j, s$ q% I. U
    $ `+ P/ @# i: x! A* F    }
    3 o2 ]& y  Y# U5 D% [8 w& r1 s  S' O7 {3 c4 U+ e3 t, `
        @Nullable/ o" U8 B  d( A4 O
        @Override
    , l$ f/ q* M8 q. x! j    public Object resolveContextualObject(String key) {. q3 G1 L" n$ J$ ]: @: v! ^9 @
            return null;
    : e0 W- ^. t0 x; ]  e- {1 e    }
    1 m1 H- d* _& e+ ?7 y# m) x$ l( v3 y
        @Nullable! P1 _% o  h' T( Z, v! ?  H
        @Override
    * i: R/ U; a* U$ Z- c9 `    public String getConversationId() {
    ( a$ v: |1 J) _4 \9 [( ^2 m        return null;
    0 P7 D0 Z; A6 o% @    }/ z, }4 [; K; Q7 M+ l
    }
    4 K* L3 Z" O- @% b+ [" `@1:定义了一个常量,作为作用域的值+ c9 k3 n. C+ z7 @; J3 _8 q  G
    ' ^! n  @( C* i' l# K$ g& E
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    4 J# T7 O* f  X3 m0 U* f
    / Y0 l8 G) V0 W& U5 s" ?@3:通过objectFactory.getObject()获取bean实例返回。
    ) L3 G+ I0 }* }: z! X) ^+ u0 R/ o3 B" a
    下面来创建个类,作用域为上面自定义的作用域$ x# r7 ~9 z4 o0 j4 b

    ' T5 U  n# m5 A* f# G0 |package com.javacode2018.lesson002.demo18.test3;2 j  a# O; ~# a' m* x

    6 O3 x4 O9 \6 i5 t) r; x; p) s3 Limport org.springframework.stereotype.Component;
    3 D  \4 X6 f& X$ e9 ^0 _/ ^& |5 `' \; i# |" S0 I( p7 Y
    import java.util.UUID;
    ' c" n. s) F5 R$ B' _# D
    3 @; K7 \$ w3 K2 J3 x. k5 p@Component
    7 G; @5 ^2 p5 M, N' w$ E! l@MyScope //@1 5 G4 l( ?1 [# y8 o  t9 @; |& Y/ F
    public class User {
    . y7 e- K7 f1 d" n$ m+ C" r
    / x2 F( t9 c3 C  C( b; E  e    private String username;
    , V8 S5 R2 E# c$ X& |2 Q5 g8 M8 p/ y3 M+ W7 e+ a7 u/ ^+ P& v& ]
        public User() {
    8 Z5 l% T7 u4 V5 z  r$ A        System.out.println("---------创建User对象" + this); //@2# x, V, N# G$ J* T% D% g4 n
            this.username = UUID.randomUUID().toString(); //@3! l: ?9 [( W& K6 a. \1 P
        }& n, w) g7 H& A- O1 S
    - ]/ Q, F1 w# m7 b% Z6 f6 U
        public String getUsername() {
    + l$ {% B# @" R, k9 L+ ~2 ]' Q        return username;- ]% D3 e  Q3 x5 I+ C' J5 C2 D  |
        }
    ' o9 `; N* |( D$ M9 E. H* T& s2 D5 t( ^: @* n
        public void setUsername(String username) {
    0 \1 z. }, d) q7 _' A        this.username = username;6 C; ?1 x3 h  Q6 Q
        }( T* k5 v0 m2 q2 a2 h/ K6 k& J* n

    - v! y4 r" s" U% c}
    7 L- S' z2 P* Y@1:使用了自定义的作用域@MyScope  \- y3 n# G* t9 W% P

    : ~) |) P- f5 z3 }8 t& c$ R. ^@2:构造函数中输出一行日志# E. e& K' U* H  M9 u8 _, O3 I) [4 j

    9 j7 y* u2 C' i" C@3:给username赋值,通过uuid随机生成了一个1 H8 `+ ~! @, I) w: ]1 d
    % `9 S1 R+ x; t. F0 l$ ~& v& t5 n
    来个spring配置类,加载上面@Compontent标注的组件- A5 s! o: i; j% o; l" P: `3 J

    ' ~' c; z" N( b$ ~* [package com.javacode2018.lesson002.demo18.test3;7 B+ f2 B+ z) Y7 w7 H1 B( H) ?; R
    : s/ h3 i  ]( t) v
    import org.springframework.context.annotation.ComponentScan;
      G2 ^2 o, Q% t/ L# Jimport org.springframework.context.annotation.Configuration;" D' [6 E! S* J" Q

    # L) s: l0 o: N& Y2 ~6 y) {@ComponentScan
    5 @/ h4 w# o, M# w; y" ~@Configuration9 ~8 ]4 {7 ^  G9 F/ t
    public class MainConfig3 {; C" o+ u$ D" s+ H5 W& S
    }
    1 {* i! e* A! [; ^. s下面重点来了,测试用例
    + c7 `! `; z$ x' L% J; J6 D3 x; b$ d
    - m  W8 h9 |! G, R@Test: q5 a  I, h+ Q5 D& x( w
    public void test3() throws InterruptedException {
    6 z3 L2 K& T  `    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; i; w+ f) E3 X( s9 D& {) @" g* H2 l    //将自定义作用域注册到spring容器中3 j( J" V4 P, D4 e1 X, J
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    4 t4 L8 D2 x: d& s  I    context.register(MainConfig3.class);3 A! j# N! |) U  i* w
        context.refresh();
    7 x& @; f% C, `0 L& O9 s0 M  [5 c8 L4 n5 p9 a
        System.out.println("从容器中获取User对象");
    4 T( k0 i# i1 U# L4 v    User user = context.getBean(User.class); //@29 Y8 G8 x4 F5 X& [" g, f4 U! A: v
        System.out.println("user对象的class为:" + user.getClass()); //@37 X" }4 K- r: Q/ z2 L7 C% `

    : t" H+ G) B4 T4 i# z$ o4 f    System.out.println("多次调用user的getUsername感受一下效果\n");  ], h4 x" Z5 \4 m! ?' l( ~# s
        for (int i = 1; i <= 3; i++) {
    6 e$ }! ~4 T- _        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    + W) H" y/ g  `' m! [/ U0 @        System.out.println(user.getUsername());
    5 M! V1 b1 z7 [! P2 |" m! D' M        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));4 g* D3 z4 t/ a
        }
    0 g' x0 }, @4 `5 G}9 {: a9 B( A( Y4 B# C% ?
    @1:将自定义作用域注册到spring容器中' H4 F; q& r3 r# a8 A* F* r
      ~" V. T- ~! h
    @2:从容器中获取User对应的bean/ @. G7 u; S& J
    5 }4 F/ B: z0 h2 Z6 H/ B
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    " I) Y/ a7 @8 F, {' O3 ~4 i3 g' e6 E4 U  y9 m( ~
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
      m& W# Z% ?4 [4 O& @  I9 z% H2 n2 y+ s) V$ j* _& y" B3 U' G
    见证奇迹的时候到了,运行输出9 }. E( n9 F1 n' t" ^2 b) S

    0 y, M! \% _' q% a$ [从容器中获取User对象
    , ^6 a" T$ v* y" Z" G* Ouser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    * J: }6 ^! Q" v& e! w5 T8 e多次调用user的getUsername感受一下效果: y5 n. [0 ~. q, g3 A4 \

    4 J3 L; f5 I* s3 d% H: k7 I********. i2 J3 l) |& v4 L+ P9 M9 T
    第1次开始调用getUsername; G8 X$ o0 h# ?0 h+ l
    BeanMyScope >>>>>>>>> get:scopedTarget.user% r, ]: L7 ^( s+ u1 b
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f45 Q$ b$ j4 S/ ?
    7b41aa80-7569-4072-9d40-ec9bfb92f438
      _& D0 G; o" j9 S第1次调用getUsername结束
    ) t& P+ [( E1 V2 v3 l9 Q. l********
    ; T) I, _7 N( |% M9 f, u( M( ^& N6 X6 M, e
    ********
    , I* a, b4 K  D7 x5 \; I2 W* X第2次开始调用getUsername
    & o4 A( F, _% h1 c: uBeanMyScope >>>>>>>>> get:scopedTarget.user0 y9 @# V5 M( ?
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    ; P' S7 i  p: ?2 W9 v01d67154-95f6-44bb-93ab-05a34abdf51f. _* h- \: |+ H: S
    第2次调用getUsername结束
    # w+ e- _2 U4 G+ @( |********
    ; l3 }4 M/ I# j; y. e6 ?7 l$ V+ x! E- W+ c2 s2 E
    ********3 {  ]; c( Y; r# Z
    第3次开始调用getUsername0 b. Y4 d# k- {% w* g/ @
    BeanMyScope >>>>>>>>> get:scopedTarget.user3 G, t0 P9 h3 w  x. y
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    $ q2 \: j7 \. `& U# X' E& S76d0e86f-8331-4303-aac7-4acce0b258b8
    2 [- |$ q3 f! \- Y$ P% j. u4 S0 B$ W第3次调用getUsername结束
    9 [; D( x  \/ H" N# `. @********7 m# x" g# t+ I7 h& B1 [4 `
    从输出的前2行可以看出:
    3 O, a* d8 a9 U  \' w/ O9 Y; i0 ^' r4 t! b: D
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象! P4 o6 z% M1 Q$ {  z; P

    * C, ^! l2 g' `/ W' I+ E% P第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。  z4 g! R( \6 q9 [3 }6 ~4 X

    & l. [9 E* k% c& ]2 v' q后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    0 W" X, i$ m0 U! D$ K* ^4 u' |( A0 B7 Q& V/ U. g
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    0 o: i4 v! N  W: }& @5 j8 J  I% |! H' Z. c7 V+ {: L( U& r
    动态刷新@Value具体实现
    7 f5 u" }  j2 a
    1 Q0 M; L' |  U: F6 i( Z9 X那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。2 Q1 x' ~3 V0 w

    7 W6 B" `4 W0 t先来自定义一个Scope:RefreshScope
    ; S) `+ s2 u$ |0 U$ U- t9 v
    % N7 p% @" c3 t. dpackage com.javacode2018.lesson002.demo18.test4;
    & l' K# E1 D- E; y" P! ]7 d' i
    * Z$ c$ \- a- R! Zimport org.springframework.context.annotation.Scope;
    5 i( K" J6 ]  V' Timport org.springframework.context.annotation.ScopedProxyMode;
    - q9 L( N, y) K; K: ^* S- Q  r1 Z2 r8 v  g1 G1 O$ L" N, p. }
    import java.lang.annotation.*;) i8 E8 R, \0 }

    ' \0 O5 r- v; g' w) j7 s& ?, @; X/ |@Target({ElementType.TYPE, ElementType.METHOD}). D# M. G" T, i* F, `7 k
    @Retention(RetentionPolicy.RUNTIME)0 U4 U5 s5 I) E, w/ {
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    - d9 ^5 t1 a' z+ L: D@Documented+ u6 t/ s0 E9 P, v% S
    public @interface RefreshScope {$ r$ l( F/ i$ z) z
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    + ^* i  @4 x! W5 P- X0 }( @2 f}' p9 N7 D; r% ?, Y6 w( o" G
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置( K) h6 _4 Q% S, i! |4 i
    0 c  C  ^1 M) q. f
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    - _" Z- M' W. R. @$ Z4 u( K+ ]: o, z# m4 u, i& \
    这个自定义Scope对应的解析类
    ' M+ i! K% S6 t0 J/ y  ^' t0 @- Q/ I7 y' e2 f7 _
    下面类中有几个无关的方法去掉了,可以忽略
    - G% p! ], y3 d' K4 X, a. u; ^
    6 l8 ^; h) E' u3 e5 u; j$ lpackage com.javacode2018.lesson002.demo18.test4;
    / q4 U' r3 v( ^6 Z3 f' t3 @. _, O: ^: E7 P  I- ], U
    , C' }& E2 k6 m8 K. ?& |
    import org.springframework.beans.factory.ObjectFactory;
    4 Y4 s0 x0 Q$ y& q( k9 Yimport org.springframework.beans.factory.config.Scope;) X4 _+ e# T" K. d7 j" B. n' `
    import org.springframework.lang.Nullable;
    3 I) E( S4 P# B" i8 S+ W! t1 a- k3 h1 ^1 z' H8 b
    import java.util.concurrent.ConcurrentHashMap;9 x; G# J6 c. F2 o1 B
    1 p0 f* }0 s4 ]5 p: ?- C3 g! [+ m
    public class BeanRefreshScope implements Scope {
    4 n! O( s' ~6 d! g
    % b# W$ i$ ]2 h. z    public static final String SCOPE_REFRESH = "refresh";
    5 ?  Y6 r+ k: D$ b- e, ^( c& E6 o" A: |2 [
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();; K! y7 r; j8 T0 Q  W3 o0 f
    8 Q! R# P1 b( Z" J& ^& E2 Q- F2 H( h
        //来个map用来缓存bean) I8 h7 @; F0 r; c1 ^. P8 j# ~2 n
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1% W; Q$ F% B- D* W
    ' U) C, B8 x/ ~# F( x" Z' F
        private BeanRefreshScope() {# v6 u- V% Q2 F# W  }6 J
        }
    ; {) Z  ^2 Z" k+ h6 |; Y9 A: r& T
    1 ?6 Y4 C) j7 A    public static BeanRefreshScope getInstance() {
    0 [3 j6 j5 k2 b1 s# {        return INSTANCE;5 K. ?/ _& A- b; ]% s5 k# [# b
        }3 f( B" s8 o3 O& G- G) t

    3 O# c: L1 g/ {, W3 H/ Z    /**
    . n; Z( c$ i- v, y9 v     * 清理当前
    2 w4 u, u/ [( s     */
    9 M; u' i/ X- u7 p    public static void clean() {$ Y7 R9 m* @& g
            INSTANCE.beanMap.clear();
    ) h$ c/ E4 C0 P( q    }% x: F$ c0 d, z$ e- r
    . l  `! h2 J! R3 g0 U5 k! {
        @Override) J- M9 A/ y0 @3 x1 s& Z
        public Object get(String name, ObjectFactory<?> objectFactory) {# B, a' S! d  y: B
            Object bean = beanMap.get(name);
    9 g/ }0 j! ?' h) w# x1 p. P        if (bean == null) {6 p4 P! I9 N# U, w) g" Y
                bean = objectFactory.getObject();+ q+ x5 U, n9 ^3 e0 R$ M$ q: w
                beanMap.put(name, bean);
    + T( ~* R: |  ^0 `        }
    4 J) Z! ?3 a7 C* x' I# A: E$ l        return bean;
    0 I& l: H# n. G$ V. n9 F3 N    }
    0 `9 ?  V1 [- Z4 v6 @# z& f# T3 F2 y
    }7 n4 ^: |; D9 N9 Q- W+ K
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中- q5 r5 }5 ^  o, u- ~  x& Z

    ! a4 R+ c6 G8 N' G1 I上面的clean方法用来清理beanMap中当前已缓存的所有bean
    6 W( m9 r; S# G6 P3 {( ^- A1 v" G! \- X; b* q
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    % Q" J6 y9 K" H3 |4 W! j, I) z
    : a6 ^3 u4 @/ [: K6 O# mpackage com.javacode2018.lesson002.demo18.test4;
    9 B; p2 d: v' e& t, h: @+ E1 H" O3 `7 k/ F7 x
    import org.springframework.beans.factory.annotation.Value;
    . b, N4 z& N# z) Uimport org.springframework.stereotype.Component;
    ; W3 U; w' Q) N* h6 H6 B6 P9 S3 M5 i. ?# t7 A9 a
    /**
    ' X. M" Z. t8 J * 邮件配置信息
    9 g. S/ y+ T+ b9 ]- i. N4 M */
    ) ]1 D. J) f3 [@Component+ d* X/ L; y3 i8 |5 Q
    @RefreshScope //@1
    & N2 J# A. T4 Y1 z# o% Mpublic class MailConfig {) c1 w3 T; I' _5 f6 @0 C

    & w5 o# E5 W7 V    @Value("${mail.username}") //@20 c; I7 J; ]2 U( ^, K: g
        private String username;& \9 K. ]! M8 {* S2 k- m# [
    & e. L5 o) k% |( G, o' ^
        public String getUsername() {
    2 f) K. |" z% w1 h) F9 l. K+ Z        return username;# j- i8 i$ i/ q/ w4 h
        }
    & F6 ]0 x  T  e/ \
    ! Z; l( q( U* u7 L; v9 R    public void setUsername(String username) {
      Y# G+ t; R  l: r- r& a0 C        this.username = username;
    # F  j* E# N9 F& r+ A' I    }
    0 e% P5 m( o% k% o, k; Q$ e% O' ?: c+ W$ f' y4 [# U
        @Override; c, Q  R; S' p
        public String toString() {: C3 D6 B- U: p2 b' s8 u
            return "MailConfig{" +7 X0 k* e% |& a) o& O9 v
                    "username='" + username + '\'' +
    . m+ p  |! w/ w- _/ g$ o1 x                '}';. _2 Q: M& \7 q. m/ P0 [
        }
    $ z2 ^$ v& R5 H" D# |1 z! f}% A* K* W9 j0 M9 l- B7 z
    @1:使用了自定义的作用域@RefreshScope& V& V/ |# F0 k# y, ?# E* L4 E
    ' P4 c7 H: v$ \3 w0 Q, n
    @2:通过@Value注入mail.username对一个的值
    . A; ]! P; s6 G* [0 S. z( F$ ]; K/ u; [' L2 G" b
    重写了toString方法,一会测试时候可以看效果。0 l9 c5 D& I, t0 E* b
    3 l+ t) V# T& f: t  f9 V  r/ |% n
    再来个普通的bean,内部会注入MailConfig
    - f& K! E$ A6 S$ K( t. H2 ]# R) i+ y
    package com.javacode2018.lesson002.demo18.test4;0 X. Y3 }8 N- ~

    , R+ o# ]: m# L$ z  V9 ]" Vimport org.springframework.beans.factory.annotation.Autowired;
    ; h9 s& g, Y* D) ~; i7 g4 x' `4 O7 Simport org.springframework.stereotype.Component;
    : U' J7 A4 B& l
    0 ~# u) j8 ?6 i( o0 k@Component6 |; C! s9 M: G3 r
    public class MailService {
    5 Z7 F- Y% f5 q$ Q    @Autowired# ?2 l; ^9 b9 p6 v; o
        private MailConfig mailConfig;
    4 a4 g' F( I" T5 ?1 a( x' k
    ( ^$ h+ x$ [. g+ d0 s    @Override3 @6 V) [1 Q, @) E0 L
        public String toString() {0 O. S% f: Y- ?. {' h
            return "MailService{" +/ j4 m2 \) o0 F# D4 R( b: Q
                    "mailConfig=" + mailConfig +/ y: V9 a1 Y, _9 L4 {
                    '}';
    9 ?' N7 n' g0 ]: P5 N    }
    / O+ r% _7 d4 u" V7 l}$ H" R6 T& l! S+ k4 n! H
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    ! g4 V7 |, k- w, K6 r$ w1 N. Y0 p2 `/ \4 q+ W" }
    来个类,用来从db中获取邮件配置信息6 I$ a) L% ]# c! T

    9 o0 v; j8 t" k3 O6 w, spackage com.javacode2018.lesson002.demo18.test4;, N) D# a7 u1 d% _, ?" K! I: d! R

    # w( _) E( ~8 Y5 r* n* O. k2 _import java.util.HashMap;
    $ a6 A$ I! F% C% Nimport java.util.Map;
    4 v4 v0 i. |& n6 Ximport java.util.UUID;3 c' o% J6 Z/ c/ j* t" B9 J

    ( q7 q$ F9 [/ z& }6 opublic class DbUtil {& \9 c/ J" y* C; @, t- q
        /**
    5 J! i) I* O2 b: W8 H& S) Y6 ^     * 模拟从db中获取邮件配置信息8 \  P: x6 ?' e. y% J8 \
         *  @+ S( P- `& ]1 H0 v8 A
         * @return( p' w" i  j0 E6 n5 d) _4 U
         */1 Z" {4 ~0 k; y: v7 u
        public static Map<String, Object> getMailInfoFromDb() {) r7 Q' [5 K1 ^# M6 W. b
            Map<String, Object> result = new HashMap<>();4 K; K- t' I2 g% e. g' n6 z7 F
            result.put("mail.username", UUID.randomUUID().toString());+ h, y4 B/ h9 n5 e2 E$ U
            return result;
    7 f. b" ^' {& r# {3 y7 p7 o7 |- J    }
    ' T: _/ V* x" b0 t9 a}9 @5 z* ^( K( |0 L
    来个spring配置类,扫描加载上面的组件5 m. V2 f, h" r& s. A/ `

    . m6 P5 v* d3 \6 l  k( Zpackage com.javacode2018.lesson002.demo18.test4;
    " F  o) M: f6 a3 f& \( g# m/ N7 g$ R
    import org.springframework.context.annotation.ComponentScan;
    ' O9 B. I4 h: A8 T( H# c6 y$ Q* ?import org.springframework.context.annotation.Configuration;
    ! Z+ O' r5 q" t1 N" t: |$ ^# ]; M1 P" w3 D! j" X
    @Configuration5 |3 M5 C& a" Q: p8 E* ~" V
    @ComponentScan' O+ Q9 Y" g9 c  T
    public class MainConfig4 {, I% `: h7 H4 H) ~0 R. L
    }. W( [. m# `; x6 K2 H: }, V
    来个工具类3 S# c. ]" Z* [( p6 P; Z# @

    5 W, w; ]; I& W. e) ^; l, E内部有2个方法,如下:) f1 _$ |1 a4 b  a  r% I9 m) D5 v

      }' \) C6 a2 B: M4 B. r8 G- _. {package com.javacode2018.lesson002.demo18.test4;+ L1 H4 P+ b: y
    ' J7 z# B; @; b0 G
    import org.springframework.context.support.AbstractApplicationContext;
    , b& ?) o# f9 u# X( V; simport org.springframework.core.env.MapPropertySource;6 n3 G. {$ H4 D3 z5 U$ Y% g
    6 C) R) ~5 c. m  u8 |' Y
    import java.util.Map;' |4 d. A; U- T0 ]; L. k

      J) Z: S! F& z6 `! Z& npublic class RefreshConfigUtil {
    # \7 w' U/ D3 Z* @    /**
    1 ]5 ]# D* S  j, v3 q) r     * 模拟改变数据库中都配置信息
    , W/ m; }$ c" |9 [2 Q# }     */
    + V- j7 ?3 N, }9 a    public static void updateDbConfig(AbstractApplicationContext context) {8 [8 M) }* b9 ]; k
            //更新context中的mailPropertySource配置信息
    * W8 J. D7 i" l; ?" z' D        refreshMailPropertySource(context);9 K* ~0 }& k9 B0 U* g

    6 |' R% f) A" f: Z6 f        //清空BeanRefreshScope中所有bean的缓存
    ' ]2 v9 b) Z" n" g        BeanRefreshScope.getInstance().clean();
    0 O- p* R$ ]3 C% j    }( r, L: F- W+ g& {

    : d# P0 l9 V- G$ q! u; h    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    . n0 @; Y4 W9 j8 v& X; d) d        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();  Y, |# a3 ^  D' u, A. y
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)% c3 ~9 [4 U& d+ M
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);0 l/ U/ n" |6 C$ b4 ]
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    / g$ F1 a! v, I+ D/ S5 T  ]    }
    : ]/ Z4 s& [8 X+ `  s% T5 ^6 t) M" ?
    7 G9 V! A. b* f4 Z2 J}
    5 T- h3 G0 o  k' L9 O# C$ Q' wupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息% W! k/ ?6 z) T, o
    5 ^/ h1 D* Y  p% D
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。3 x0 V1 t. I: Z6 P4 \

    6 T4 m. Z& H7 u2 p5 X来个测试用例& T" G; B- v3 m$ C

    5 |9 z5 B  b2 O# [( T8 B& c0 v@Test
    ! e: }7 C. Z- W+ e0 {public void test4() throws InterruptedException {
    8 f8 v) H9 S5 }0 {. p, N    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    1 i' B" r9 d7 {0 \' y    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    2 T) g& t3 @; c5 T0 f, U+ g% C8 x    context.register(MainConfig4.class);
    ! h+ ^! }' v7 ~    //刷新mail的配置到Environment
    , n+ f/ x$ v8 Q    RefreshConfigUtil.refreshMailPropertySource(context);8 T" d, N3 H) X2 X* m
        context.refresh();3 @' w6 K: J4 Y
    4 H3 e$ F3 A  \
        MailService mailService = context.getBean(MailService.class);
    2 i5 Z1 `" V' G8 F. t" G    System.out.println("配置未更新的情况下,输出3次");
    ' Q: x: K  T. Q- x  Y    for (int i = 0; i < 3; i++) { //@1
    $ w8 Q6 P/ x( c        System.out.println(mailService);
    5 l: y$ m  @$ W5 F        TimeUnit.MILLISECONDS.sleep(200);
    " p/ V7 E& |3 e8 V4 {, x% W! z    }
    9 g# j+ ]* A- Q$ ?8 U# K" t* e1 z9 t1 B
        System.out.println("模拟3次更新配置效果");& \/ q# p' D4 R; z7 H( T8 _% ]6 r
        for (int i = 0; i < 3; i++) { //@2
    + V' F7 e2 X5 r& ~# [7 F8 l        RefreshConfigUtil.updateDbConfig(context); //@3, V( X/ L" j5 Z( H8 z: d& k
            System.out.println(mailService);$ j: h: F' V% e3 d( J
            TimeUnit.MILLISECONDS.sleep(200);: n, ?+ y  B7 ?1 y( E/ {
        }
    % A4 ~- F+ }0 ~" K}6 b: L  e/ x  g3 H
    @1:循环3次,输出mailService的信息
    3 O1 E2 L2 b7 a6 E9 E; d  o8 I; j, c$ p5 n1 Q
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息9 k: q0 F: q9 m7 h# L9 {# _
    ' {" l+ Y4 j4 n( U+ [
    见证奇迹的时刻,来看效果' t: ^; J- W( G% n6 Q
    * Q8 q! {4 o" n, u( s4 |
    配置未更新的情况下,输出3次3 g+ w( Y1 y4 q+ y
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    4 ]+ _! I+ ^" F: o) C5 UMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}) N! t$ a% A2 i* q- ^
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}5 L' s2 [" b1 J. Q8 Y8 G3 W, ~3 K5 r
    模拟3次更新配置效果( f/ W) q3 Q, W$ j8 j
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}7 R% L' `0 a9 y8 d
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}4 g" E# ^4 a+ u# d$ ~
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    1 y; C/ S8 O6 D7 Z' E上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。0 ~& H, M! L& w! C; U! L

    + g) n8 A5 l& L8 a% g% I. i小结
    " Y8 N" y# V4 D! J5 p0 Z0 g4 P, [0 o  o# B' ^+ Y
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。0 \1 a, F9 K+ B# t! Z

    ( q5 A$ n) W+ p2 A3 S有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。& z0 {. A( E/ ^+ _. i0 h9 y
      W6 m  X+ T: ^/ U! Z
    总结) g* {# k8 P% o: O

    3 z2 x/ w; V6 z' c1 v; s% y: b本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
      c* O: }; s/ G6 z: K0 X( _$ _+ m
    9 s! |( C8 y) k+ y/ [9 \( k, ]案例源码
    / E% q6 S1 B7 F. @3 F9 d! X. Y% A# Q2 k5 F0 a
    https://gitee.com/javacode2018/spring-series
    # _$ T+ Y  L4 c路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。7 v$ ?" t# f  l
    1 c5 S5 W; ?- }9 [  R2 H' b8 b
    Spring系列
    ) y. P; T. }7 y+ _1 w% g& c: v
    0 _2 u; [7 B, h6 C8 G+ RSpring系列第1篇:为何要学spring?  K  A0 \3 ?& g& X' V
    , K* g9 R8 P  \/ v" U) S( I4 C
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
      o$ _- ^' P5 ^5 E7 a4 b6 s" H- h4 G# N$ t7 i; A
    Spring系列第3篇:Spring容器基本使用及原理) M) z- ]8 y* k4 w
    3 \. M2 L: f/ |) V; E
    Spring系列第4篇:xml中bean定义详解(-)
    # d5 J. b4 u. f" B
    5 {. U, A8 H; v# a* q; G3 F# d1 GSpring系列第5篇:创建bean实例这些方式你们都知道?
    7 n6 W2 m2 I& R2 D' [
    & [0 Y% u/ c" Y# Q4 HSpring系列第6篇:玩转bean scope,避免跳坑里!
    * O5 P3 A2 J) \: p, O7 n1 ]& l. J& b7 A( {& V2 f  \
    Spring系列第7篇:依赖注入之手动注入
    : C- y# }" e  A3 r
    ) C: u, y3 w9 E+ ~8 ]' N- @Spring系列第8篇:自动注入(autowire)详解,高手在于坚持; R' D( ~0 p  `- ?+ |/ S& r1 B8 U0 H

    / d( U9 w4 W0 O" M  TSpring系列第9篇:depend-on到底是干什么的?
    4 ^- X7 i9 j! x9 ]; n2 W# l( \/ N7 ?( {/ F  R' M' l
    Spring系列第10篇:primary可以解决什么问题?9 v7 ]* w' {# f) _0 S& d$ _  m
    + ?& ?) a  `+ c" @
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?8 c6 I( e, t# t1 w

      y8 R, T, Y  l1 R8 f* e! VSpring系列第12篇:lazy-init:bean延迟初始化
    5 g' j' R+ d2 d2 L6 n5 @! e- D: E% S- h3 a% i% X7 _- }
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    % W" I. _# C2 T. P" P) r  @4 d/ k8 S  A6 s+ v
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?6 }7 [- S! K% W1 Y8 Q* g9 j

    5 a  I( r2 r% R( \5 KSpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    $ f8 Y0 D, ~$ m1 i7 g+ q6 K. K/ Y
    7 u& x) Y0 R$ D, U' {Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    / M1 \# _: d& z  o/ a, r# {/ T# C8 p# ~; V' [% m1 S0 W( U+ }
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)5 O# K' F& J1 E9 x( T2 b

    0 P, l% F7 ~+ wSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)( F; O$ ], u$ j+ k/ [; S
    . O$ ?9 I. E9 S/ k# c/ m: ~! |5 D
    Spring系列第18篇:@import详解(bean批量注册)1 z9 n: O, \: G5 n" \. [
    , ^- g+ T8 W6 u
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    7 Y: [( E8 T+ X1 _' J! k. ?; ]6 _
      Z8 P) L5 U4 ^' t0 v# e" zSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    9 q6 w" E; b5 c8 O
    " Z- ~- v# ]7 A, o. G$ @8 rSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解8 h7 [, @* ~+ V8 ^% ?

    / ]/ F5 I3 e& m2 T0 y. hSpring系列第23篇:Bean生命周期详解
    ! e; g. Q, Z0 L! p* J9 B8 r0 d4 z1 s% w4 g/ d
    Spring系列第24篇:父子容器详解- \0 B7 U: h5 S+ \  \  i! ?

    ; {  u* _; p# n% p0 J# m更多好文章5 C6 h, [: q# m9 w4 }) a3 p' g2 A
    ( A3 L9 W$ x$ Y. S: P# k9 [
    Java高并发系列(共34篇)1 K7 q# u: T4 h. @+ h8 i

    6 i- R6 X9 h' ?) J: Y9 x6 XMySql高手系列(共27篇)1 }$ \0 E" N7 ~2 J) t
    $ M. c8 F9 r. k% i3 P
    Maven高手系列(共10篇)
      `  l. a, T. t9 v) I/ T* |- y* ^. l9 ]' y5 n
    Mybatis系列(共12篇)
    2 U( f. y4 _3 G+ P, I( |7 }* ^. |3 }" B( J- [. y3 l
    聊聊db和缓存一致性常见的实现方式
    ' E" y7 @& k3 j& e+ g$ A# C" L
    ' H! Z6 j0 r) V4 a3 J! g1 t接口幂等性这么重要,它是什么?怎么实现?9 w* }' O  y9 r* a9 ]3 N4 M
    ( a1 Y+ i  W  w! ^2 o, y: W- o' W: @
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!! D* t! I+ A" a! I) J0 p; z
    ————————————————3 c# k: p! K  j; Z* s8 Z: A# d
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ; [5 G5 h: `" k1 Q; m原文链接:https://blog.csdn.net/likun557/article/details/1056487574 y2 u. Z3 e% `# Q
    8 `" V% `. u) p  Y- V8 }) Y0 T/ }

      Y1 s) P4 t; R7 R
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-8-4 19:24 , Processed in 0.476689 second(s), 51 queries .

    回顶部