QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5442|回复: 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!0 M0 Y* i3 l8 k& ?9 j- U* X; H
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!' {( e0 c2 B2 X8 o5 h1 {6 L9 `
    / i0 v% U0 a/ Y8 t2 m5 [
    面试官:Spring中的@Value用过么,介绍一下; @. X- E1 @+ \9 q0 `
    2 P, j+ x4 |. X
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中- k  H+ X: r, P2 C

    2 P- q: Z: J0 O- F面试官:那就是说@Value的数据来源于配置文件了?
    + o  @6 K2 x& [6 p/ f. f/ R( M2 j8 J
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    * q8 m6 d/ p5 T- J; ^1 C9 C- N3 v6 j8 k
    面试官:@Value数据来源还有其他方式么?
    ' Z* e3 z6 s8 B1 T& c: ?
    5 f  V) o( {; h* [( d我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。: G, H; t) g& _, t( o3 C

    0 G4 _: f2 s& X4 [面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    $ c6 \0 p2 o  |& x8 x# q
    7 O% P) B& n' E" b. ]6 Y" d7 R, [( o我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧2 y) e  |7 r/ J1 O2 s7 `/ E! E
    % E5 V0 ~7 W' M5 A7 s% [: R
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?; o6 R$ {7 X4 K' j9 R3 e

    1 b# j+ E7 Q8 @8 V4 p3 H+ H* c! ?我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能  h4 u! k7 B+ M% _6 o8 F
    ; ^3 I- d5 T1 U6 [9 [
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?5 m: I! `  {' z2 E+ T6 C

    & d1 j* T2 W" l2 c8 ]我:嗯。。。这个之前看过一点,不过没有看懂$ O0 p" P; C9 j9 `8 P
      N$ E; N4 b: `' |; \9 _
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?; u& S1 c' v3 a( U( ^8 h: N" O

      h6 o. p0 h  U/ D2 ?% I$ u, O, I1 @我:3万吧1 l+ v- t8 ~  U! f% ?( Q: P

    2 U) E0 t  @0 J( r/ D* E面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    9 {7 o, V2 p* I6 v( A3 y% D# ~; j9 C5 _2 j
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万3 Z/ A& U% C( H7 m
    ; M/ j1 ]  ]% ]" h
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!& h0 P- k" V& ?
    ) a& m9 C# |- w" y- B9 d
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。6 q3 W( ?' P6 ^+ V

    ) C2 ^; n9 `4 V1 H3 @这次面试问题如下
    3 d, G+ G  e2 q7 ^. i# n7 u
    . y8 }2 |+ g" c. k@Value的用法
    3 \8 [$ x0 M& @% i
    ' l; V6 U8 j# C@Value数据来源
    5 i! Y  a7 d! u8 s1 j. r. B6 b+ C0 w
    @Value动态刷新的问题! V" d8 x# M0 |% {2 y. ?
    $ @1 y5 p$ h- y: w6 D3 D5 q6 }+ d3 O
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。; X- r& W8 S/ ~# [+ d
    : r' `; {$ w1 v) C4 l
    @Value的用法
    - z- N  x9 j9 r1 A' L0 F6 Q) Q! E0 L, W) y& ~
    系统中需要连接db,连接db有很多配置信息。* \) ]; b5 p  P$ N$ G7 }

    5 A, g+ z) y0 X4 f) ?3 j0 W/ i系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。* K3 q- Q/ }$ |% L4 e

    9 E  p' w& U, r' [7 G; s+ Y# T0 @还有其他的一些配置信息。
    ) C( m! z7 z" Q) k3 A4 W
    " k5 v5 a0 w4 E7 M' q4 A( S$ C, ?我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。) `( q! G& m; y3 ]

    1 _' C/ m/ r7 _, \那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。8 b/ F3 N( q9 U

    5 s, V  C! F, @通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    : g  ~  k* J5 l$ q& ^; l: o3 r8 {3 W, T
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。( r! |4 m# e) s$ Y6 v
    ; s: j  C5 N' U5 v; w2 n! @) U. T
    @Value使用步骤+ R/ C; p  {9 W# |* H
    8 h; c9 ]6 b' e5 C* ~0 z" D
    步骤一:使用@PropertySource注解引入配置文件, f4 K2 \8 {4 Q# n) F

    4 p) O( |, i3 x! B' h$ P& f将@PropertySource放在类上面,如下
    ) G6 f) k, F! \" C5 |7 q3 m9 K. r4 |( {4 y
    @PropertySource({"配置文件路径1","配置文件路径2"...})+ B% }7 J6 ?$ g: P
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
      }  J1 d0 x( U- r$ q9 @7 \( l9 b8 g6 x, k  h( `# J
    如:* ]- [( V% z+ l0 G, }( p

    - B5 N& l4 I3 R@Component
    & v' y* y' n. w+ j@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})& J& y4 x3 M1 _
    public class DbConfig {/ B' L! b% a7 q; }
    }
    & ~7 B4 f7 O6 l7 x, e2 N( q5 j步骤二:使用@Value注解引用配置文件的值1 h1 w# g8 ~0 `) ^7 e, J4 {2 i

    ' Y2 D* e' ^, g- S( H. T通过@Value引用上面配置文件中的值:& V8 o' p# M" D$ k( S% D8 ~

    , c* T2 Z8 q- o! A语法" m) r6 I- ^4 `0 n$ v6 [0 t

    0 ~5 N4 C6 p& v0 H* k  w@Value("${配置文件中的key:默认值}")  r; A) ?% `* `+ K5 p6 P
    @Value("${配置文件中的key}")
    . ]' K1 ^1 ?  i: w) n8 i如:- P# x) o- ~8 g4 m" e

    , \  ^  O* W! t2 U/ q@Value("${password:123}")
    ' l! ?( X) [7 b, c5 m上面如果password不存在,将123作为值6 Q: l& K9 y+ d+ q) `  x% z
    * b$ [/ V3 ?) B, J
    @Value("${password}")
    ; i3 M; r/ j( z9 ?上面如果password不存在,值为${password}" d7 l& d4 R! W7 L! j

    ( O9 i' H3 S( Z3 Q假如配置文件如下0 ^, h  E6 o$ D, p

    9 j. I' S2 Z- \, ?' f' vjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8- M2 I! f- r. X3 b4 R, u8 A9 n
    jdbc.username=javacode2 f7 O+ X5 q! T* [. \+ A/ ?
    jdbc.password=javacode
    2 k  J/ t: ?; b2 k  u; R使用方式如下:4 q- x/ l/ E( B4 _7 Z0 J$ \
    , b; `! N1 B* z6 B/ ^# N! _
    @Value("${jdbc.url}")
    ( R1 c0 {) v' k2 v, Qprivate String url;
    5 x- M- ~2 T3 k( I/ _) i, Z9 Y, e  m& B; |
    @Value("${jdbc.username}")8 B. q8 c4 @9 T+ s' N+ E$ [
    private String username;
    4 Y+ M4 P3 H) }1 x* Q) ]
    8 a. {& |* Q# _: M8 i. U' a0 C@Value("${jdbc.password}")
    / V# R- D0 I; M6 \. X. w! ~private String password;+ A  J2 K0 _: ?( B/ I
    下面来看案例' i  z& W+ `1 L8 U
    , \' o' B" B, b  T9 h6 ^
    案例3 |. [% b6 C9 v0 L: Z. _, I
    $ {6 l& f: u- @) g6 n. _
    来个配置文件db.properties
    ; b' l; {1 P/ g# J
    / x$ A9 ^  k2 u" Y0 Ojdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    " z2 a4 }  s: ?5 Z6 jjdbc.username=javacode
    8 H1 |& u4 q* P1 {$ Sjdbc.password=javacode
    1 V6 D! M, O% {% k来个配置类,使用@PropertySource引入上面的配置文件
    8 v& I, K0 P; o( g: L; u; k; T
    7 F: F1 R2 S, q& p5 p3 Cpackage com.javacode2018.lesson002.demo18.test1;
    3 y5 s8 i8 i; X' V& W' S7 s3 o/ u% Q! P6 {5 r$ C  B
    import org.springframework.beans.factory.annotation.Configurable;4 {4 E* |" s$ V) G5 S
    import org.springframework.context.annotation.ComponentScan;: N8 c9 u, @8 U
    import org.springframework.context.annotation.PropertySource;
    2 x3 ]5 O0 V& {/ L) m$ z
    # w1 V2 k3 R4 {+ j3 r* Z@Configurable* L' N* x' \& L8 s
    @ComponentScan0 w% [" I" R6 b# L$ k1 m; C* i
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    . Z/ U0 J% G+ q. L6 b7 P; }public class MainConfig1 {
    : `( M2 y5 q- O' d2 x}7 _. Z" S& Y# o- s
    来个类,使用@Value来使用配置文件中的信息( }& L' u' K$ Y7 J( p& g

    # Y4 x1 g( v" E! D$ A* Bpackage com.javacode2018.lesson002.demo18.test1;. p7 C8 u# J2 {8 a$ K" Z( |. g
    # p! h5 N' h! t! m- W% ~6 C
    import org.springframework.beans.factory.annotation.Value;
    4 a- R( D: p+ K9 d; eimport org.springframework.stereotype.Component;8 s2 J9 P  L8 b% _9 Q1 @
    ( s  h3 `- j4 O
    @Component
    ! j9 q4 K2 o* J; rpublic class DbConfig {2 v. V1 [' a9 x# L  [1 o
    5 w  r6 q9 `9 U- _+ a: p
        @Value("${jdbc.url}")
    0 l1 t" c: n2 ^8 p; }& O$ f" d    private String url;2 S- W, @' `% E
    ! c1 i! w3 B( O! I( L3 q
        @Value("${jdbc.username}")0 J# b5 I  h( M5 E. \
        private String username;
    6 k+ C$ g6 Y, S6 \9 d1 J
    ' D& _5 e5 T9 `7 `! j$ U. S    @Value("${jdbc.password}")5 p% f  B) u8 H; K* V1 m$ l
        private String password;
    ! Z/ ?% G, L1 b, F* Q! K; T$ W
    + a8 N" t5 M  \; T; h7 s! m! V% |" m    public String getUrl() {# E2 {7 _) M9 f, V( n) z6 _
            return url;
    ( n7 Q2 `$ f6 t$ j: ~    }1 C7 d7 V: }5 M1 Y2 k1 x6 b

      q  {9 b8 K, w( d: j    public void setUrl(String url) {
    0 P( V! q. V9 R; D        this.url = url;  q# a  d) T5 H  b, _& |
        }' E: c' j+ r8 R- T: v5 M3 U" J

    9 H, O, W: \/ P4 g7 t) }    public String getUsername() {  I( y# \; F$ |; A- a
            return username;
    & T2 J6 z# m+ m" B% D3 }    }
    6 B; o" L; U* I! C4 r& c3 r5 s  n; |; ^
        public void setUsername(String username) {# x( `9 o) o2 T4 p/ u
            this.username = username;, N. y2 E8 y5 [8 I+ x( Z0 a
        }) v. @. t8 L: \1 F
    4 r$ u* |) W- c4 d' U% s
        public String getPassword() {( b6 V: [1 W! }8 S6 o! ^: w# i
            return password;$ D: I- c  @7 }% w: v+ j" p
        }1 k1 s6 W3 t4 `" N% K! ]
    # W  u& o. S4 \* q7 Q
        public void setPassword(String password) {1 I& U% P8 A7 {2 b% j5 d: F
            this.password = password;* A& p+ p9 v. z4 N1 ~! X5 U; B8 n
        }
    ; L: n7 Q) E, Y6 t9 v2 T3 [: z1 j6 R; R  x# n4 n7 ^9 B1 c$ K- f
        @Override# |3 o2 V; b$ R2 n* c1 t  [' L* N
        public String toString() {$ J2 r  d; k! X- r
            return "DbConfig{" +# \' [6 l& s7 q. R: J3 Q4 \
                    "url='" + url + '\'' +
    / T, ~' p9 q; I0 |8 m% b, `5 g                ", username='" + username + '\'' +' p9 T: |: [; @0 N: b
                    ", password='" + password + '\'' +
    0 m; ^! {2 O" Q/ j                '}';( n& z7 u9 m9 h) k% Q( S
        }6 U' V. ]& C+ b  b1 W% M/ u
    }
    ' b4 }' q5 H7 Q上面重点在于注解@Value注解,注意@Value注解中的" C3 R# K9 K* Z$ |6 q
    8 j/ d/ {3 x* D: u& e/ N9 I
    来个测试用例  O; W, Y$ W. A7 F3 M7 x$ x, o( d" s- m

    # o8 V/ h4 P- a. Z+ O) r3 y8 fpackage com.javacode2018.lesson002.demo18;
    % x( w, c8 G$ z! Z. h4 L0 f' f& ?) R; o  A4 P+ j" q$ I
    import com.javacode2018.lesson002.demo18.test1.DbConfig;/ N# u0 h1 B+ |2 ~$ N" T
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;: {. W% h2 ^5 s7 \4 V$ \: ?6 j
    import org.junit.Test;
    ) l/ I- o4 J# b1 P5 D5 _0 u# ~import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ' r0 c9 s* Z, R9 a+ z2 ~4 I2 R: ^6 _5 o$ |0 ~8 U& Q9 T. b
    public class ValueTest {  r) R0 C* K1 Q- l; i
    $ A5 ~1 V1 T/ D: B
        @Test9 f: _4 i4 L  U
        public void test1() {' |0 j4 Z" X3 d3 j1 ?7 ~
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();0 A+ {$ n- [' `: d
            context.register(MainConfig1.class);) y1 i0 m- n& m' q7 \
            context.refresh();
    5 i# f; f* e1 T4 [  o
    . O0 v, ~$ |5 g9 o! K2 I% K8 `        DbConfig dbConfig = context.getBean(DbConfig.class);
    , \) p0 X5 I( c        System.out.println(dbConfig);
    % h$ p$ M0 e; K$ g- f& H8 y- d; X8 @    }
    * g* Y3 J# Q/ f& q8 G( ?}
    " g+ A8 @, a8 ~2 r0 x* a( j运行输出4 j- k5 t; ^* m3 E6 u
    1 W, z) b/ R* H& i. o1 D
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    , p* |) r; i. O) K3 V0 [8 s上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。! j: S/ _# e9 O5 {. C, |

    ) N) ]8 N. L1 Z, ]% N@Value数据来源8 I( R" \/ i) Z' ~" p* q

    , @7 [, `2 Q. Z6 l2 L3 {  q- P通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。1 W0 g  @. ]' ^( ]

    6 K5 O/ T1 Z8 b6 O' f我们需要先了解一下@Value中数据来源于spring的什么地方。
    2 \6 X3 D% G$ g: }% X8 f4 g& R1 e' |6 f) @+ o
    spring中有个类
    ! d7 U2 W6 m- ~, F+ `4 x4 w. Y$ S; ~% K+ n: w
    org.springframework.core.env.PropertySource
    3 ~% b9 W& z% U( N' ]可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息/ r+ z; e; a& U6 o; C. p

    % u$ S5 P. l( I& @( j: x; S" Z* X内部有个方法:
    * n4 i. N3 r  z& O1 P! ?# g
    % t" F% ]- s; ]1 wpublic abstract Object getProperty(String name);
    7 V* R$ L$ d8 w7 E& [% `. K( G通过name获取对应的配置信息。2 n  w/ e, I6 k2 ?; I, Z- |
    ! Q' ^8 P5 [+ O7 D0 G7 i6 ^
    系统有个比较重要的接口% p' V( E/ b0 _0 s* x

    5 y) q7 L3 V" U% N! f' y" ]org.springframework.core.env.Environment
    4 }* t% K7 ]. V% r$ o: S用来表示环境配置信息,这个接口有几个方法比较重要6 u# o+ u7 U2 g- g/ D7 _

    , h7 m: m/ J1 i7 n6 C( yString resolvePlaceholders(String text);
    / s3 ?' |% n! |$ c# v- a9 cMutablePropertySources getPropertySources();& Q( [3 x5 A* x) N
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    : A% q9 Q4 J2 ^5 Q: V. k& ^' a2 ]7 s9 q3 ^: P
    getPropertySources返回MutablePropertySources对象,来看一下这个类3 y3 I' Q  B# e9 K, ]

    7 E. s3 M/ t& c- u" ipublic class MutablePropertySources implements PropertySources {
    ' T6 k1 P: I6 j: z  s# m
    8 v; X0 c9 @% y% p3 Y7 C& c    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();* }; Z8 J9 J2 d& ~
    ' J$ S0 s6 n9 |+ p; w/ O
    }
    9 b1 O9 v- A% a, `0 c内部包含一个propertySourceList列表。$ b, e0 L" O) e5 Q( [/ }  V
    / {  I  m  `" U4 i" l
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。) o! n; e0 [9 v- t5 Q& ~5 J
    & k) _) L0 i3 Y$ d0 Q  e( F: G
    大家可以捋一下,最终解析@Value的过程:* Y: J2 B+ k6 o* m# ^* y$ K. x

    7 J( G3 G9 U$ d0 v/ L- u/ t) d1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析8 Q# I% P* H0 S
    2. Environment内部会访问MutablePropertySources来解析6 E& }* Q" b* ?. M7 K
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值' A8 O6 A* @& g2 N
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    ' l: S5 I: V# a: g6 f
    " D0 |1 \' P0 ~( P- m. Z下面我们就按照这个思路来一个。
    9 M3 E* K4 V! U0 ?6 r  @3 j& h/ }& N2 s2 c
    来个邮件配置信息类,内部使用@Value注入邮件配置信息, R5 I" ]3 V& r1 v4 {$ Q9 C5 _3 X

    4 o' O- M- a4 g7 t( H  a0 A' Ipackage com.javacode2018.lesson002.demo18.test2;, A0 [8 K8 |4 ?3 t; M
    ( w+ H- s+ U% }5 V
    import org.springframework.beans.factory.annotation.Value;/ F- b; {: @7 C2 b- |9 p
    import org.springframework.stereotype.Component;
    % d5 G7 B7 i+ n2 ~4 s& `1 U. R, _* i
    /**
    + X+ E1 d* F2 D * 邮件配置信息
    : V* ]8 }, B. p */; F4 j! g4 A2 e! ^
    @Component
    3 m  R$ q; y# I* S. m6 kpublic class MailConfig {
    * C3 B4 e2 P7 m/ a0 W8 ?. f# I8 `. D5 v$ C* C
        @Value("${mail.host}")2 q$ c4 g+ ?' U; O: q6 Y( l9 I
        private String host;
    , R4 Q0 s( \$ r* F( Z$ \% k4 Y9 J7 Q. c+ M$ L5 \, V- I
        @Value("${mail.username}")3 e. ]- J, l# F3 W* R% q
        private String username;
    . M5 Z' A. C% p- |( |$ |
    2 E. w* V9 x7 M6 |4 S% o    @Value("${mail.password}")5 S% H1 R) w6 ^
        private String password;
    ) \* f! s* o! l+ y' K9 t% Y" r. }
    2 l6 J" X; I/ F" v' z  P    public String getHost() {1 k  o! R3 ^2 j4 B$ Y4 W( r$ L
            return host;
    & j2 o# M5 B9 t( d  Q) ^( D* ]! y    }3 R6 m# J  w5 f# q5 @' L

    ' j. F4 q# k- Z, e    public void setHost(String host) {
    4 b3 S  j( ]9 M- ~; Z4 [        this.host = host;( }6 t2 ^* d  [' P" y( R, g& g  P) O
        }
    % G- l# C9 S' d( B: X" |: u. ?9 p5 n/ u! y0 ^# X) {  F" M
        public String getUsername() {
      L* a( ]- B, b& j& d% w/ `        return username;
    ' C0 G9 j* S4 H    }
    9 U- g: \. [% [4 e3 y  j
    7 ?" w0 {6 ~2 z1 C) r2 Q& J    public void setUsername(String username) {
    1 c8 I& @" n3 \( O+ I" }        this.username = username;% c* I9 c( O" u2 X
        }
    5 H: }+ J/ L8 h: F( i) q$ B$ `! \7 z" e  V
        public String getPassword() {
    , E- W/ J1 m3 r- ~& ]        return password;
    3 Y" Y$ M  e# Z& |    }
    0 L# m+ D5 n+ y1 f5 T/ c" r8 T' a8 P+ L' o! Q
        public void setPassword(String password) {
    5 ?( x, B* f1 M3 u; r/ i  V        this.password = password;
    # {& T$ t/ X( e) w) ]    }. B" A4 _$ y- W0 n
    / k% d0 A5 u$ J& h6 G- l
        @Override2 `+ h8 Q* @/ V* |7 X# m
        public String toString() {9 `; c; J2 j: o3 v/ H
            return "MailConfig{" +, l5 I1 ^, m; ^% b" O- [, B
                    "host='" + host + '\'' +
    6 ]- d6 n  @: m/ l# x! {" p: @                ", username='" + username + '\'' +
      y# H5 r) F+ h; K$ d% @  ^4 U9 t7 ]                ", password='" + password + '\'' +
    3 `( y% R2 s* X" _3 o                '}';
    . H  u, O9 x/ I% b4 b# j6 z    }
    / J" z4 {3 t6 ]4 ^}/ a$ ^6 J  D; i2 v2 h
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中5 ~! f- w% R/ _6 W( _% B0 q

    0 K: ~; @! p; ^* wpackage com.javacode2018.lesson002.demo18.test2;0 Y' \' @. \" y7 c, `+ O
    # i; q- o$ X( H% h4 J! u8 v
    import java.util.HashMap;
    . g. i- B4 t, ?, m2 aimport java.util.Map;9 V! E3 r! O9 S* i( q. V+ P" G2 X
    # t  X5 {! }. J$ R' a
    public class DbUtil {
    ) b9 _( b' x  y$ \) z- c$ Z    /**
    3 X8 j- v  n3 G1 `     * 模拟从db中获取邮件配置信息' ]$ A* U0 [# q% _0 A4 i
         *3 h8 [0 \% e# p) F' I
         * @return2 v6 E6 K- g0 `: v3 b
         */: D2 [2 F! P; z2 D3 j
        public static Map<String, Object> getMailInfoFromDb() {
    5 [8 t' x- Q8 s2 _        Map<String, Object> result = new HashMap<>();
    : O. B+ N  P" K# Y/ K; S/ V7 [6 c        result.put("mail.host", "smtp.qq.com");0 |$ e: j1 N; e/ A% d0 K9 p
            result.put("mail.username", "路人");/ g/ f3 V/ P  }1 V
            result.put("mail.password", "123");
    - c6 h. m* W% f. {! O% o        return result;, z, R  S+ C" g: T
        }+ t' w, h* G6 U/ X/ y# F" S
    }  u) s. G  A. s# M$ Y& J! Y" k# {5 T; }
    来个spring配置类( a" E4 m+ h( ^0 j/ d. S

    1 r7 B4 s: K7 M4 }8 rpackage com.javacode2018.lesson002.demo18.test2;
    5 Y; D, i" |3 `; N3 L* t" [8 D* O9 ]5 k
    import org.springframework.context.annotation.ComponentScan;2 u) p1 }$ s# z( @/ x
    import org.springframework.context.annotation.Configuration;8 J2 M. J! L9 E% M. V$ ~- T
    5 R# c* g7 K- X0 h- R& D' c5 g
    @Configuration9 p/ W/ Q$ s2 n" Z. Z) c
    @ComponentScan
    4 A2 b, x* x. k# G" I. _public class MainConfig2 {3 y' O& G# W0 w5 M* {0 S0 r
    }% m: R# c% g5 C- p: I
    下面是重点代码+ ?6 n) ?2 N' m8 `1 _* r
    6 `0 G) G1 W* D# A4 y$ d/ d* T7 Q9 |% P
    @Test
    6 m6 P3 Z; |1 o4 S6 Xpublic void test2() {/ {$ T9 Z9 B* q  g. D$ h
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    # R. u! R, J' g3 u5 c8 ]) L6 ~4 y- j$ J7 ~' Q9 ~+ i: M- U
        /*下面这段是关键 start*/
    - C, p# S5 @+ b/ h; e    //模拟从db中获取配置信息, ]/ P* g2 `( v7 [2 {
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();. l; p' R7 Z& P
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)6 q* Y6 u- w4 n7 j) K+ F0 V
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    - @" [& }5 _9 j% O" |$ ]    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高# O' T( I$ @+ w6 |- M
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);) ]( E2 d# m) h
        /*上面这段是关键 end*/: J0 r4 u4 K" l2 e

    " Z0 W0 g! y9 ~3 h- ]    context.register(MainConfig2.class);
    + Z3 \& n0 j  w  M0 o' X# t    context.refresh();6 B2 a/ y& y: V1 s
        MailConfig mailConfig = context.getBean(MailConfig.class);" K# d& ~5 x( x% f2 x( i  _: y
        System.out.println(mailConfig);7 ~* ?( L8 h: n; Y6 @+ B8 {( R9 H' m
    }# F# q& \" K' G* p) Z* n
    注释比较详细,就不详细解释了。# j$ m1 d& X! Z) O% J2 N
    + w0 w5 ^" X" k! U$ h7 T! b
    直接运行,看效果
    . N# X2 R9 k  H$ w0 m% a+ r* d' O7 p" x
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    6 G/ S" a$ p+ h) R" `有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。- }, M$ a# G  `6 S  z

    8 D0 Q5 h$ E% h. y& L& |( S/ \上面重点是下面这段代码,大家需要理解" t) _  |# {9 m5 i! x2 |* O, J& \
    & b& S3 m3 {) g# y
    /*下面这段是关键 start*/. ]& q& G- S" J+ D
    //模拟从db中获取配置信息
    2 D$ W! ]8 D) y! AMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();4 Y7 J# e1 V8 ~9 `
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& L1 d# c3 g8 W6 x, h' p" l9 V
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);% h+ y3 u0 k0 M2 R/ w8 ~
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    8 @; M8 K: y5 r% Ccontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
      H' M# b1 ?7 [6 J/*上面这段是关键 end*/1 b- R+ T& x) Q
    咱们继续看下一个问题' l6 h! K( \; l5 o+ H
    3 _8 i- Y$ t0 F9 H
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    ; ^7 E2 L+ b! o4 V  w) T
    # T+ D0 T1 H( P8 _9 g@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。. S) k' c& ?: z# V$ o  p$ Q

    & l. M% {8 A$ @. M( p- A8 @( s实现@Value动态刷新9 N% U" f/ p8 _4 Q  Z
    2 K! Z* k3 |9 @1 e2 j- Y
    先了解一个知识点- F( A; v' t/ ^' r% o9 r

    4 ]+ B0 E9 A" F# H3 `+ \6 A这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。4 n, J( l  V1 z. J

    $ V& d3 F; x  Y3 u, [6 B1 F这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解4 F6 P  E1 D# a7 y, r, n) _

    9 K/ X' a8 T8 w8 u9 T- _bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    % @) g) R' o/ e# @" {% ^3 C; L/ J  |' ^
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;, ^+ z, R7 N$ }
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    ; u0 J. V4 h6 t
      Q) C2 N, ^  ~6 q4 opublic enum ScopedProxyMode {' V4 ~1 Y2 G) O5 z6 q2 J  q) o
        DEFAULT,2 H. A3 @) b, {4 o4 S7 \
        NO,9 Y. b0 H) [, K8 T% B0 e+ A' e
        INTERFACES," M4 G/ d$ [! i
        TARGET_CLASS;
    * h! ^# x5 c4 t}
    ( h0 Y6 l8 ^* z8 `3 x! @前面3个,不讲了,直接讲最后一个值是干什么的。
    9 W. U/ I% @0 }+ c0 E$ Q& P) y5 g7 }5 }. v; I4 v
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。& m  B2 l) \0 u- T; G

    ; u: `- P. [; m( `2 w4 _, L7 P理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    1 g: m. L0 O0 [( B; T9 n0 r6 j5 j% ], ^6 ^
    自定义一个bean作用域的注解
    ( }; u. b5 B3 a4 q: X
    , Q$ `9 a6 b( @$ [3 j% i: C& Ppackage com.javacode2018.lesson002.demo18.test3;
    ) V9 f6 w& H1 n0 ]
    ) B2 g9 w/ T& Z* Ximport org.springframework.context.annotation.Scope;
    + M1 I  P  O7 i5 z3 Z/ k) c, rimport org.springframework.context.annotation.ScopedProxyMode;4 q2 i' j. s7 W
    * q6 K1 h0 k1 X4 l# L
    import java.lang.annotation.*;
    # ~7 V0 j8 A: M& X- a' r: i2 z) ?7 d* k$ p% m# T% S6 _% J4 T
    @Target({ElementType.TYPE, ElementType.METHOD})
    ) {3 i, I  ^3 O# M3 e. [7 ^@Retention(RetentionPolicy.RUNTIME)7 [; {. J& m, X1 h9 y7 z9 o: b
    @Documented- p% ^9 K: x7 w: z! I
    @Scope(BeanMyScope.SCOPE_MY) //@10 r, a. C# Y* ?
    public @interface MyScope {  @5 O5 Q8 ]  B! ~$ p. Y
        /**! f- W& y0 R$ F, |1 T7 A$ V
         * @see Scope#proxyMode()) Y7 j% ^8 W. r/ H- S/ o
         */  k: B  n- V2 H! W& M- E; ~
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    / U, x  p' S% u- X}
      x5 H' |" Y& l' P6 {& e$ H; a, W@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    4 J. l* U& P7 t' P- D2 r5 Y& q; i! g- u& N. y( {
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    6 i! x4 w$ y* \# P3 x( w
    * {, r0 w* [0 F9 ~@MyScope注解对应的Scope实现如下
    0 r) g- Y: K& E. t# y6 \
    ; V( H# r$ W, U" I# r6 o6 tpackage com.javacode2018.lesson002.demo18.test3;( o* O% J* Y  p3 G7 S" r6 C! O
    : {! x, A% l2 g4 ^: R$ Y
    import org.springframework.beans.factory.ObjectFactory;2 \  o5 G, Z6 m5 t- Z
    import org.springframework.beans.factory.config.Scope;  r) T! T! |- b& V
    import org.springframework.lang.Nullable;2 O8 q" Z0 }* |9 B) T
    / o+ N, _) A5 `5 A$ {0 y9 w
    /**, N) L" k) r+ Y# Z
    * @see MyScope 作用域的实现
    6 p1 x9 Q9 y# H2 a1 P2 g# u *// e" D# T4 p5 s8 m  _
    public class BeanMyScope implements Scope {
    % ^7 R' q, x+ d
    * D+ o5 |6 i" I/ s* V0 l    public static final String SCOPE_MY = "my"; //@15 {) _3 Y1 {! W: J6 N
    + e, D2 E' v  G' P8 J: I" ]9 T6 A
        @Override
    1 K, l' c5 P: x/ U    public Object get(String name, ObjectFactory<?> objectFactory) { 0 t5 A2 m9 P; \8 O% S6 P
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@20 N! l/ f( o4 t0 m
            return objectFactory.getObject(); //@3
    , _, V+ t! L. p0 g/ U$ o2 i    }
    : s$ b" L( K, j) {0 m& X6 i
    , N; S$ f: q# I! x: ]    @Nullable
    ; ?' k# h2 Q$ u& |9 G    @Override7 X2 Y4 ~' w! \0 J, Y. G7 c" J
        public Object remove(String name) {% J' D. c+ S2 C
            return null;# k& T& q/ Y# @- _
        }
    4 ~# l+ x5 {1 c- U' W# y7 Z
    # A/ ^% [  R4 _0 W* |& l# b( q* X    @Override
    - K- U' s, [5 U* }( |/ q5 a. l4 s    public void registerDestructionCallback(String name, Runnable callback) {) X2 d5 K9 A9 B( X

    7 P5 j9 z" C$ W& ?! c    }
    ' p4 }: J" f7 X2 I5 w# i  c$ A# q* v( h- |) W7 l3 t4 V" @5 @
        @Nullable+ a, N: X" P$ ~% ]) T: x
        @Override
    6 S- ?" _9 H: Y    public Object resolveContextualObject(String key) {
    ( F' Y5 O1 W/ n' I! G        return null;
    " \% [. L* o& E2 }6 @    }
    ! h0 \+ ~; n7 f4 H7 @" u* t6 n! e9 R$ w! H8 M# _  I
        @Nullable
    + F6 S8 w; b4 p, q9 E# E    @Override
    % z8 @: E- ?9 Z8 A+ b3 C    public String getConversationId() {
    & b' P! e4 x& l4 W! h4 \& F, x        return null;5 y* U$ O' U9 A/ t4 q. R
        }
    + ?0 |0 ?- t. @7 s}( L1 B* C* s% R. Z6 Q$ e
    @1:定义了一个常量,作为作用域的值  d* |% X) @$ u* ]6 A( F3 Q+ Y
    ) P+ V5 E; V$ _" m# S9 [9 o
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果: n- R: f; p( `
    . E- j5 c2 x, \# }  y
    @3:通过objectFactory.getObject()获取bean实例返回。
    ; ~' p* H* w- w
    ( e7 q( X1 v9 @下面来创建个类,作用域为上面自定义的作用域2 p4 B4 s. D+ Y6 k

    4 k" A3 i6 T+ C/ j* Bpackage com.javacode2018.lesson002.demo18.test3;
    ; y* Z4 I9 t; J: {/ M) l' j% ^  ]1 K
    import org.springframework.stereotype.Component;3 F9 Z" U6 ^" {7 k, ^7 Z

    7 e7 P( T7 z( u3 ?' ?+ Mimport java.util.UUID;+ a# v9 g  O. _8 I8 t6 J
    & M7 Q. `& v, d- {0 ^
    @Component
    2 y" U' N! y+ f# c# L@MyScope //@1
    + p( D7 t) J9 n* y& s' U5 w+ Q$ qpublic class User {+ e& _& F& A) a! L+ d% ^
    . _  \3 m9 j7 ?  C. e7 j$ }
        private String username;, d. h0 `0 D. I9 }8 n& O! P9 H

    : b, U1 e% G- ]; S7 f: {- ^# @% ~    public User() { ; w5 a) O: u! B/ S2 i
            System.out.println("---------创建User对象" + this); //@2
    ' h- j3 g( K) V* w9 D        this.username = UUID.randomUUID().toString(); //@36 K3 F7 M# J& Z$ i
        }
    0 A) z8 F6 H! H) E! ]$ r, `
    + V2 n- f/ ^/ w, {, ]0 C    public String getUsername() {7 J. T1 i1 E' L& m2 ~0 @
            return username;0 @5 I9 @' F* x& c7 o7 n
        }! Q' w& s. ?0 W# v7 j
    ' h4 J1 H* H8 I) L' X! H
        public void setUsername(String username) {$ ^0 X% _. T# y9 y8 N
            this.username = username;/ C& x: r3 u  Y  b/ U# ~$ I  K
        }$ a2 y' H; j6 v8 J5 d
    4 m3 C: p) N) G7 b& Z
    }
    / e9 t5 ^6 r$ I% u- M0 d* S@1:使用了自定义的作用域@MyScope
    3 ^: y6 u+ g4 X" ~0 l/ V) N8 d& n+ D2 @1 Y- `
    @2:构造函数中输出一行日志
    & x* y& ^  b' K0 g
    ( H$ {7 S- f* k% ?@3:给username赋值,通过uuid随机生成了一个  T  J7 `+ s/ f" a
    6 k! Y0 J) l2 Z  x( C
    来个spring配置类,加载上面@Compontent标注的组件( b0 [7 W7 I9 Z
    7 \6 e+ K1 k% U, e) i8 {
    package com.javacode2018.lesson002.demo18.test3;
    : t: f& c0 o3 S6 \: s! \, [  C
    4 `+ I; E: s* s) m4 q$ N$ wimport org.springframework.context.annotation.ComponentScan;
    0 T+ ?* C3 n6 C8 U) D  V, Ximport org.springframework.context.annotation.Configuration;
    / p+ N) g" m8 o% r6 z
    * {+ h! I, g5 K@ComponentScan
    ' F7 B& S) u/ Q8 m! T/ x3 e  a@Configuration
    ) u5 O7 F- v- {* {- _' qpublic class MainConfig3 {* _6 t/ F* e5 R% f
    }
    6 z/ K! t/ X  c8 h3 v* ?" e9 E0 i7 n* N下面重点来了,测试用例7 a7 q: l- @8 [& W3 w1 T% i

    & g( M; Q) j# u% p9 P- i0 p) y@Test! F' }3 E  d( f" J; ?" W
    public void test3() throws InterruptedException {/ H) f+ E  O7 ?! i! G2 R
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    0 X. @$ [1 N' J* v2 w    //将自定义作用域注册到spring容器中
    + g4 G6 e! i7 ]8 r    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    : a$ n0 n6 a- B    context.register(MainConfig3.class);
    / G6 p( t7 ~( C. x3 w$ U    context.refresh();
    " N9 k. `% O# {6 N6 Q& l4 Q0 k5 Q; w, q
        System.out.println("从容器中获取User对象");
    6 {2 O6 ?3 P' S* j9 J) v7 s2 n    User user = context.getBean(User.class); //@2
    6 t/ K  N2 y; `0 @3 f" q1 m    System.out.println("user对象的class为:" + user.getClass()); //@3& _+ v% T; b' B9 p
    & r& R0 O5 O# Z8 Z
        System.out.println("多次调用user的getUsername感受一下效果\n");$ j' X6 _4 _5 C; \; d
        for (int i = 1; i <= 3; i++) {% ^7 N5 E) ]: R7 Q
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    6 D4 X! Q8 I& H% G8 M2 a        System.out.println(user.getUsername());$ R0 E) B9 B% Z
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    4 |+ }. V. t  n' d+ a; w  R    }
    - s- N! X, f2 y+ Y, r}
    " L/ j; d- |9 v/ Z" ^7 I@1:将自定义作用域注册到spring容器中
    6 Q8 H3 f- C! d- O; @% J$ k' K$ [6 l0 T- `$ a, q; b$ t
    @2:从容器中获取User对应的bean6 K0 F# B  t$ i

    6 j7 f3 Q: z+ `4 b! o9 M@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    4 A! ?. X4 T$ V6 a  q1 D5 i
    9 [, k" e9 j  [' U3 j代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。7 H2 ?) j# o& @1 L; H( S& f
    ! ^) t8 W1 E- X/ M# |2 ~# f, q
    见证奇迹的时候到了,运行输出
    / |" I8 ^/ U9 a) t
    8 W, S# s  _( M, H! g! W从容器中获取User对象
    " ]& v7 i  X. h# Z4 [1 [user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331278 x3 C* A0 i. l: w
    多次调用user的getUsername感受一下效果
    7 l* Z# T3 Z: k. X9 ~( R
      e$ {% _0 Z% a2 H- K* }********
    / v* Q  ]2 O1 n2 H9 K' Q6 s第1次开始调用getUsername
    2 G6 z* [4 \( `5 ?BeanMyScope >>>>>>>>> get:scopedTarget.user3 `+ g; ^9 |. u$ H- c5 O
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
      C) }( r5 E# Z7b41aa80-7569-4072-9d40-ec9bfb92f438* E, ~. V' |+ \7 D
    第1次调用getUsername结束
    : K9 Y) s. G6 }* C3 _) o. z" h********
    ; {6 d5 v! n" m/ A4 a& A, x8 j1 @) m
    ********* @3 B; w# J) u. |! m
    第2次开始调用getUsername& ]) ^  L8 i& O: n. ~3 H3 l( m
    BeanMyScope >>>>>>>>> get:scopedTarget.user, _* r/ g, d) m) h4 q1 T# ]* @
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b& E# C+ e& f9 S$ I  d  Z+ K
    01d67154-95f6-44bb-93ab-05a34abdf51f
    % f: Z8 J; D  n- J" y第2次调用getUsername结束
    6 w& n0 Z6 D9 D! `) }+ W% T********( m; W: q/ m1 h

    # `  r& E- i6 |, ~; v0 V********! v  z5 N! z, e- C; h
    第3次开始调用getUsername
    7 b6 Q& \& g. J# C+ w$ y* XBeanMyScope >>>>>>>>> get:scopedTarget.user
    * \% d5 M  j1 b5 C---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15) r0 R. Y' X& o" Q7 Q! s  F
    76d0e86f-8331-4303-aac7-4acce0b258b8% N. a1 J$ Q, n: Y
    第3次调用getUsername结束- a6 W; T' P! s  g' _
    ********
    ) D) K% k# N3 W( a5 b' n7 P从输出的前2行可以看出:
    2 r9 n* j7 L) B. z' g) ]7 U: \' T9 |: `3 ?
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象" i# ]: F# m& F! q! H
    ' t" U& J/ ^& f; N
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    + L! C! X: R; j. L2 t* j0 k/ ~! P9 q* |
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。+ Z9 N5 c1 I! }. O" g

    % `2 i  q2 b/ h; f/ y通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。+ x4 @$ D8 K! x6 G/ n. j

    . t0 E! j: ^' N+ O6 D( q6 k4 C动态刷新@Value具体实现' y5 @* ]$ Y, I+ |' |2 T
    : R; i6 W$ L4 J2 [8 A
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。5 E4 G0 L( t4 a1 B! h& O! p

    , C3 n9 P3 ^% y! Y4 d先来自定义一个Scope:RefreshScope
    . R! _, \3 v  d2 X9 i, l6 g! U% H3 S$ a2 `; {
    package com.javacode2018.lesson002.demo18.test4;
    + N6 Y8 a  B% Q$ Q- u- K/ W! E0 D- D0 @* t+ s0 E, c2 \
    import org.springframework.context.annotation.Scope;& _) F5 d  f4 V4 g: G' J
    import org.springframework.context.annotation.ScopedProxyMode;0 U/ F8 e" O# X9 l; o( `  Q
    2 K" s! W: s- c: `. u7 [9 X
    import java.lang.annotation.*;3 T: x0 E- U: W, ]4 p$ r. \

    . u$ o' K- z. ]9 I3 q3 |+ x8 ~" k@Target({ElementType.TYPE, ElementType.METHOD}): Q+ \2 o% ?$ Y& J
    @Retention(RetentionPolicy.RUNTIME). w8 U, |" l7 G' ]
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    # q6 `- v8 T, F2 p6 _6 Q4 }@Documented; d/ f2 p4 T: U" }0 ?7 F# }8 p# G
    public @interface RefreshScope {
    + x; t2 E. y. ]' n    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    : ?0 x, M' h! \}
    * q, r. M' d8 _8 {5 Q& O. B, T要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    6 E! W- O* S0 f, j5 V8 z  [4 w: u/ g4 F( R
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    - \: \5 O2 t5 I& [' E0 x
    9 E* ^" p9 v; T6 S2 p  E0 U/ k2 r这个自定义Scope对应的解析类
    , {* B/ w/ P; k
    $ q6 O5 b0 F. n下面类中有几个无关的方法去掉了,可以忽略. B$ g9 V: _& N; J# M  o* I5 `1 k* P

    ) O: H$ J/ x0 n& ypackage com.javacode2018.lesson002.demo18.test4;
    1 U" r& a4 c' L+ n: b1 {. ~: S1 c( L/ V2 r

    0 X" n+ j  M8 x) {6 H; ^$ w; _import org.springframework.beans.factory.ObjectFactory;
    ' t' X, P' x1 D; M& kimport org.springframework.beans.factory.config.Scope;
    9 }5 e% M& p3 D6 A3 U; O+ cimport org.springframework.lang.Nullable;
    0 f4 U6 Y9 r4 c1 c7 ^3 I" c' b6 b& O6 l
    import java.util.concurrent.ConcurrentHashMap;
    * y; z% h. e* i( V$ h' H' I
    % B: O" ?$ E, x9 }public class BeanRefreshScope implements Scope {
    ; }3 I2 W6 k: e. r( \2 L$ r/ L4 N
    : E) n5 n$ x$ d0 ]( z/ Z3 q6 ]" m    public static final String SCOPE_REFRESH = "refresh";  u8 q! E% F) z
    * A% v' s8 W8 S' p0 g. L; A+ L
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();  \" I: E9 a6 W( R: w% U6 f

    " g2 u" I: @% L/ X" g" c; S    //来个map用来缓存bean9 n) X, K) M; G8 P* W* s: ]3 R6 Q
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@13 @' e$ u. z, D) l4 P) f+ F; ?* O: O
    ) f& i+ A( p8 ^9 I/ X" X2 W
        private BeanRefreshScope() {" y2 m$ F2 q) }; t  a
        }# l$ m* e" }1 \# l" o
    5 u9 z+ Z, m' V8 V+ I6 \2 N
        public static BeanRefreshScope getInstance() {; e( }4 W7 [% f8 v1 I
            return INSTANCE;$ z7 u  P! p5 ?, y' n, w
        }
    : ^( X% x& M! x  t% L
    3 h* ~3 J  r# \) P, t8 b( ?; G8 z    /**
    + X2 u' O0 E! P     * 清理当前% ]; W7 e5 O' M2 F: T6 H0 O, M. R8 ?
         */
    8 T& [$ g+ s3 c7 Q& F    public static void clean() {
    3 w, T$ l8 Y; I! Q        INSTANCE.beanMap.clear();
    , m2 R: |$ K) P; a/ z    }
    : |+ A* H# A/ P& `
    " g; x! z0 A4 Z4 {$ m; e2 }    @Override
    / r! N  ^8 m2 g: E* Q    public Object get(String name, ObjectFactory<?> objectFactory) {
    ) Z! m* Z- K0 k0 A# h- G/ U        Object bean = beanMap.get(name);5 d0 N  h, v5 U. a: S
            if (bean == null) {
    7 J6 @* l1 h0 s6 t            bean = objectFactory.getObject();! u  f) ]& i3 A: ?, s* |( q# N
                beanMap.put(name, bean);
    2 Z" m. t% O. s) Y        }; g9 r+ C  @1 [4 O. {/ e
            return bean;
    6 Y; H" q+ w- S1 T% n; H    }- y0 u* e! v3 s% Z1 Z

    ; X! l; A/ }, a. j}
    " n3 K( t) [5 v/ A上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中7 }# g7 k" N/ |- X5 N5 |

    4 e" B% v  \& C" K7 w. d0 c9 P7 m( \上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ! c5 Q' |  n# P" \! ~
    - o$ }9 H+ W9 b4 k来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope  w  }5 b& h5 j. y# q

    9 T1 f& c: B' E; Q* ]( apackage com.javacode2018.lesson002.demo18.test4;
    & O$ {- T8 ^' H8 I) `$ ~/ v5 f5 M6 L9 a8 E% h
    import org.springframework.beans.factory.annotation.Value;6 \* |" Z5 f) F8 z9 V
    import org.springframework.stereotype.Component;
    2 C4 \9 B0 a3 Y+ h% b; A
    ; s' P# t4 W2 n5 w: k! M# e' v/**
    : I& e" d; h/ _+ N& E$ ] * 邮件配置信息6 Z2 o& L* V$ G0 v$ p) m
    */, g% w5 J8 j4 b( e
    @Component
    9 k$ l  F, m. B8 m+ K7 r% A" {@RefreshScope //@1$ N* _) ?$ Y$ y
    public class MailConfig {
    8 o' _: J$ _/ v0 G$ a) b4 O9 N7 M, V8 \" b+ o, u
        @Value("${mail.username}") //@2( \! }  O& |3 R) h3 w' K7 {  F
        private String username;
    * z: E' |7 N2 p' E  Z8 Q0 @1 l
    3 A8 f6 h" _% T. x# x9 [( E    public String getUsername() {* r+ E' Z$ a8 p% C
            return username;
    * X3 P9 s7 t  Z+ Y5 r% |, V    }
    $ Q% z$ F) ?3 }  f0 b4 `3 S
    $ t( g: G1 U% I- o    public void setUsername(String username) {
    1 F5 v2 I6 M8 q7 L3 G        this.username = username;# m$ K) N) @: ]* b7 E. w0 h! ^* Z; I7 B
        }+ v1 J1 s/ v! J7 x
    % K1 @# v4 V: R4 i4 `: u2 K- E- p
        @Override5 T* V! b; y" Z
        public String toString() {
    5 P: V8 }7 L8 Q2 e# b" n        return "MailConfig{" +
    ( u- G" R8 t8 X7 U( d                "username='" + username + '\'' +/ n- M: p4 P0 A* U2 @
                    '}';, H! G; p: V: E
        }* j8 X% ^4 f# P' o" y
    }
    2 N/ ?. O# ]/ z% l& Y  }@1:使用了自定义的作用域@RefreshScope4 |; w  R2 p2 J4 ]; ]% ~

    7 V3 L) M4 m2 n( R@2:通过@Value注入mail.username对一个的值
    . `0 v; f( B' H0 P3 T- o! n/ O2 l
    - _& {  S1 ^  o% n9 a重写了toString方法,一会测试时候可以看效果。
    4 k3 S* J1 u- P1 w6 q; U. f4 k) h% ~  o8 V3 @. c; C
    再来个普通的bean,内部会注入MailConfig( r! T3 _% k' U8 p; ]6 T+ K

    5 \# J) t7 T% u; p- V2 G. _package com.javacode2018.lesson002.demo18.test4;# A; s3 p9 i/ Y$ J1 e+ e
    ( [7 G  p( z% J, `7 `
    import org.springframework.beans.factory.annotation.Autowired;
    * K/ `5 E. y2 _1 I' t! |import org.springframework.stereotype.Component;1 ]: F# L3 z) k" e

    ' z; @4 K/ V" i9 q/ S+ v@Component9 D. b3 B& {' `. U+ w- M
    public class MailService {
    # v/ p) U4 d9 @! P& X    @Autowired
      J4 n; U8 O. k- u    private MailConfig mailConfig;: _5 {1 k& Q  c# E# C$ b

    - U/ w3 z- k4 K0 a  \    @Override
    8 U) L- a' l: e. ^% _0 ~; ?$ t6 W0 x& @    public String toString() {5 X- Z+ \) G! V, ^) z7 f) o
            return "MailService{" +
    6 O8 J; a! T, k                "mailConfig=" + mailConfig +% _" e3 [9 h- t
                    '}';) ^2 G% m/ o& J) f
        }8 g* S2 y$ U  J# @$ l9 Z8 Z
    }
    1 G9 x8 N( s% x6 T, M代码比较简单,重写了toString方法,一会测试时候可以看效果。
    . e/ M2 M; K9 U1 y
    * G) D0 ]" ^* j7 K( k1 B" }来个类,用来从db中获取邮件配置信息! `8 q0 F% v/ k0 x. `* @) }

    7 q3 A% D9 P5 y9 L+ s" n( opackage com.javacode2018.lesson002.demo18.test4;  |9 t  C# _0 H7 i2 h! o3 w/ ^3 V

    - X( @) m/ ?' G2 G) L# C. d0 yimport java.util.HashMap;% `- t: P# }) k, k% F
    import java.util.Map;
    * c9 k4 T: ~4 M& A& }$ Rimport java.util.UUID;7 ]6 O% V" b' e7 y- a  s4 X

    . M( U  G- c7 y' M' Opublic class DbUtil {; B. L+ O9 P7 G" a* y
        /**
    7 h7 L! s, Y* J5 Y& z     * 模拟从db中获取邮件配置信息/ _2 S, H8 c. b5 `
         *6 Q+ N5 A" {- N  c3 f$ a
         * @return2 v  Y, _) j- O6 X
         */7 I" V, Z' `. E
        public static Map<String, Object> getMailInfoFromDb() {' r0 w4 s2 X8 k
            Map<String, Object> result = new HashMap<>();) O1 L. L& O  Y6 c" m. H
            result.put("mail.username", UUID.randomUUID().toString());
    / k# Y  i4 C0 T        return result;7 F' m1 l4 H  |- W9 \
        }. `" k/ I8 A0 R) ^
    }
    + _6 w& C' [6 r% |来个spring配置类,扫描加载上面的组件
    ( j4 T" n2 Y. L$ K# `' N  M; ^9 z1 ~3 [! [
    package com.javacode2018.lesson002.demo18.test4;3 W; ?4 V8 P5 C
    1 k, a# K" ]/ [7 M3 S
    import org.springframework.context.annotation.ComponentScan;8 a" c% b) |4 r, g& [" @/ m
    import org.springframework.context.annotation.Configuration;
    ' Y( A- U- E: U" a/ X. m+ r  A$ Q3 a
    @Configuration8 f% Z; D- u  V5 {. B  F9 e
    @ComponentScan5 d; y% Z6 f  P/ d
    public class MainConfig4 {
    - C9 [" Z; c% d" v3 h- U}
    # h# g, e; o2 N+ J% u" n4 ^& o* X来个工具类
    + O% S1 A/ r& J3 K- \0 S- Z' s4 t8 @, ]8 p( ?. |
    内部有2个方法,如下:& Z8 L+ z4 I4 o

    0 d$ i4 ^2 B" f$ }package com.javacode2018.lesson002.demo18.test4;
    * p. t4 z! r+ m( F) G
    : [4 S7 W# A6 I# n- H3 A! M% H4 Kimport org.springframework.context.support.AbstractApplicationContext;
    1 Q; }( Q7 _; i8 Jimport org.springframework.core.env.MapPropertySource;: B# C' Y% X! d. z0 _( E' c

    5 z4 v3 S" D* b8 }( q: Aimport java.util.Map;3 a, ]' I+ r/ E" J+ Q  v" P& q
    5 ^* A: x) T) [8 N1 Z
    public class RefreshConfigUtil {. u+ e0 j& \! {) g3 ^$ O
        /**
    1 Q4 O) a2 h, B6 {  n8 x( [; [     * 模拟改变数据库中都配置信息: B, {( i/ s( R" A0 ~7 Z
         */
    & F$ p1 b6 r" j6 h( a    public static void updateDbConfig(AbstractApplicationContext context) {  e1 h; [# J$ q) J
            //更新context中的mailPropertySource配置信息% K3 v& d  F2 ^: D6 f
            refreshMailPropertySource(context);$ o6 N; N* _: h! F) k7 m+ E' }3 \
    9 x1 D. l% d/ E1 \
            //清空BeanRefreshScope中所有bean的缓存( r# Y2 P" ^. o/ D8 z: H4 A
            BeanRefreshScope.getInstance().clean();
    0 ?9 A& f( k, o; F2 Y$ S    }
    5 `' A7 G, ?1 z$ W* T, Q/ {7 h( a& d: ?1 I4 w
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    / n" H7 b( j/ P* ?+ e% O/ _. C+ Z        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 H7 i$ p6 o8 M: x( Z, B
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). ?4 d- ]; X  A# J! b8 b- ?' j
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( K+ r, r+ {3 e- z' j( n        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);2 }2 y8 n* H+ M& _2 N
        }4 Y5 o* @' S5 C1 s  S1 N9 {3 N5 t: I5 A
    , l2 c; K" A  D; U2 E# z# j
    }5 _3 s6 ?6 N" h1 j6 `
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    # i" C* S% q  |' @3 p  E" d% L8 G/ }" I: k+ m- E: w9 C
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。( V4 {8 T. ?  ^* |4 \  Y+ e
    - x% k/ [, e) }  b6 Z7 s
    来个测试用例9 K3 G- E" z) `' y, Z
    : V- a! ?( x' l, X" Q% v
    @Test# ?& ~: g9 k3 B. N( b0 e; ^
    public void test4() throws InterruptedException {& Z& N1 M9 n5 L+ ]/ g8 O1 t3 D
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. i' e' ~+ Y4 e. f
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());( t! {' |0 x0 y+ g3 D, H
        context.register(MainConfig4.class);
    * ^- P1 \  H; [% k, @# M    //刷新mail的配置到Environment
    3 N5 F5 }2 O) S    RefreshConfigUtil.refreshMailPropertySource(context);# D$ j  O0 U. C1 e/ b; m
        context.refresh();
    : V- S* d+ V' B$ q' Z; X1 W
    ! l! B+ I9 F( H4 \1 G4 l" h    MailService mailService = context.getBean(MailService.class);
    1 N  L! d7 t5 Q. r4 m/ W0 W# w    System.out.println("配置未更新的情况下,输出3次");
    . s& t- C. F6 ~; ^4 d, o    for (int i = 0; i < 3; i++) { //@1
    * s: X, Q9 N& B' Q- h        System.out.println(mailService);0 w6 @- c6 a2 `( r2 U8 h/ q
            TimeUnit.MILLISECONDS.sleep(200);! W+ E, ^. [# Z' C$ Z5 @# L! q
        }
    6 X# V4 ?( ], _8 U( e. P3 k. Q9 v- n
        System.out.println("模拟3次更新配置效果");' V  s; ~1 y; V7 n" j* k
        for (int i = 0; i < 3; i++) { //@26 y9 G. Q9 e8 n8 H
            RefreshConfigUtil.updateDbConfig(context); //@3
    ' _! g: i+ ?- Z9 F8 ^        System.out.println(mailService);
    % @- _3 M: x; z4 A" F        TimeUnit.MILLISECONDS.sleep(200);
    4 j7 z/ O3 \- Z8 X. A. I/ y    }0 Q) _* t) Z8 {* X
    }
    7 U+ j: o5 {4 a& p. L@1:循环3次,输出mailService的信息0 m7 [/ g% @4 S7 q

    $ S( v) j5 d% t3 ?4 T4 v@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    , a8 p! e) l7 U3 R7 \3 D
    2 k- V8 A# f% O9 V5 s% v见证奇迹的时刻,来看效果8 `3 A: o  S4 a* O! c

    : A( t7 ?! U2 l, r配置未更新的情况下,输出3次
    / {& k1 Q# b6 tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    , O& U8 x! i' k& X  X8 JMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 }7 t' J  V/ T. c% gMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    / c" o# v& }& C/ H4 g模拟3次更新配置效果
    ; h% ]# n8 ]+ f9 m4 f$ x6 oMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}( J6 ?3 A2 C; b) Y( L" ]" p+ _$ m
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    $ R" @, e7 x. y, {MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    * m+ G) ^. Z; A6 D3 U' {上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。  ^- k6 L: L0 @! }& O5 G% H, y
    1 C$ y+ ]  j6 m) `6 M9 Z4 W% u
    小结0 Z" X+ ^8 N; c( X$ V  g7 @8 \' ?

    - k  Y$ B' X- I% c' o2 m动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    ) D6 E/ T5 f) |+ D$ e' I9 r2 C: Q5 v( @2 I8 e
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。0 V9 a' s& I& }1 I% r4 E7 H' P

    3 v8 M% z. s- q* b1 g/ v( a总结1 ?% C5 p. n- T: e
    $ L# j1 [) z# n1 |, K
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!' d, a0 i; ?: W  K7 [0 J  m
    " J5 g# d0 w; }  K5 t& Q
    案例源码* J( ~& q" Q4 T3 d

    & @6 k9 H/ P  Whttps://gitee.com/javacode2018/spring-series# V2 b2 l( c/ q7 M; _
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    9 Z4 h4 Q( }/ [7 b/ S, P5 W+ d
    ( _1 T7 b' g9 j% bSpring系列
    ; b! c& Z/ V4 q% u! m4 K/ j; v4 L6 g/ Z4 q6 C: m: ~
    Spring系列第1篇:为何要学spring?( p8 N$ r. m1 Y0 ^' H
    . i0 _7 w9 B" n
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)2 ^2 {5 z" }0 Z( T& ?- a
    ! ^  B8 b+ z$ C1 c; P. ], f
    Spring系列第3篇:Spring容器基本使用及原理+ G: h1 q) ?( x: [  _
    - L; Q! Q" i4 i6 W3 L4 C0 e
    Spring系列第4篇:xml中bean定义详解(-)
      ?/ i8 J( h% R2 J" p" r8 _( ~) b$ [" z# `
    Spring系列第5篇:创建bean实例这些方式你们都知道?6 n% L8 X& x& {5 c$ M* D
    2 C% T7 u7 V: w; Y# Q
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    : L; S) @  _6 o2 w5 I( k3 B" H8 p" K, m* C2 u: e
    Spring系列第7篇:依赖注入之手动注入+ X( a6 F; ]' M, t

    % e3 ?; U& |* ESpring系列第8篇:自动注入(autowire)详解,高手在于坚持8 a' d: Q5 _: b+ L6 P8 I5 q

    $ m% W3 V. _6 F2 z# Q! h7 J1 b0 VSpring系列第9篇:depend-on到底是干什么的?1 a7 [( g' ?9 y" F! x8 }' P
    " \% {! @) `8 Y; g& {' [8 K2 s$ ]
    Spring系列第10篇:primary可以解决什么问题?
    2 H; w  q4 a! E; t# K6 S* p2 m2 u9 H5 [) S& [7 `
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?9 y! s( v; J# Z
    9 H- n+ c& D& y% M' q  w8 Z
    Spring系列第12篇:lazy-init:bean延迟初始化
    : v8 v# |. U) J8 G( x
    3 C+ n% T% f8 T9 W* y1 b: \Spring系列第13篇:使用继承简化bean配置(abstract & parent)3 f9 _% U7 A: F3 Q3 T: \
    " S' ?& K  U5 v. z. n; M
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    * ]5 l/ t' p2 R" d
    ; B4 A  d+ V  h1 r' E1 MSpring系列第15篇:代理详解(Java动态代理&cglib代理)?( X. r$ N5 O: p1 o; G0 R
    0 r8 k8 N& Y1 T* m4 v" V
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ' F3 ~, a0 Z1 n2 ?& L) O
    ' Y# F7 s4 d6 K' oSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    : t2 M$ H" m' ]% H6 x5 D/ _. U) c: t2 q" G
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    $ H. m( E# ^% R9 r8 k
    ) I( Z$ r: Y; j) u7 ESpring系列第18篇:@import详解(bean批量注册)0 h2 i+ X$ p6 F  z0 D8 P* P# n( [/ c
    & Z2 e, k+ c, J. u2 U1 f
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    8 o! o, b% A8 P, |/ q8 F& `: c: M# v% c
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    & ~* o. a" e3 j: v5 I1 |& Z
    - F( A5 S" w' u  _1 ySpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    ! M: r  l; }! P1 o. }; n2 e' k( Z. @* J5 u3 `. L. _
    Spring系列第23篇:Bean生命周期详解/ ~# ~) ]' i# [  r; x- _! G0 w
    6 N+ l* n1 |9 Y- T. K/ W
    Spring系列第24篇:父子容器详解
    ' e" c6 H! H" X" j2 T9 z" E# J# r$ i
    9 k# D- E- l$ M更多好文章
    ! p+ [' t& {( e7 f$ q! _3 G
    . x7 W- |9 Q! K1 yJava高并发系列(共34篇)
    7 g8 o& u# l" A  }% L: e* K+ [8 V) ?  Z
    : ]) }* N% A: O( h( M  JMySql高手系列(共27篇). B. n: J( T. T

    0 b) E( @! [( l: q- o" {Maven高手系列(共10篇)
    % i, f7 d# b2 q& U
    & k1 B% m( m( U3 F3 xMybatis系列(共12篇)
    8 p- ~  @8 ?; }% X5 |
    2 v4 M0 H' ]. t聊聊db和缓存一致性常见的实现方式
    $ j  N' B! R& W* o: o8 M2 z4 z9 H$ F2 w; t1 s* ~
    接口幂等性这么重要,它是什么?怎么实现?
    " f/ X- C, u1 I- |
    0 m/ Q6 P6 C& f泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!; ]0 M, {- T6 P/ U  a3 f
    ————————————————. n0 d+ v. |! [9 b. A3 ]
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。. g) k( ?8 @/ B
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    3 M7 l8 x2 [+ s3 a
    2 l' l8 t( ^' l, m% o
    # l% \" A' ?' f4 I+ @( w0 i9 Q
    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-10 19:52 , Processed in 0.487506 second(s), 51 queries .

    回顶部