QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5568|回复: 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!
    - _) E, r* m5 [3 ?疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!+ E- K5 T$ C9 [% L, `# B9 G

    6 I6 O+ R' l+ U! x- b' p. O面试官:Spring中的@Value用过么,介绍一下
    - M$ s# S7 V8 E! G3 R/ r
    + Z, }% w& a' `5 t1 i我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    0 N; W; T% D4 M) {2 W% l
    3 d+ C4 U" e* `2 v面试官:那就是说@Value的数据来源于配置文件了?
    9 X( _2 ], x$ T$ Y  x% H) y/ K$ }2 f. g6 G& I4 m! r
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置. A. h9 f* W" o0 Y0 h

    % Q; u: {1 h' x8 ?; ]面试官:@Value数据来源还有其他方式么?
    / ]; ?0 l* \2 Z  }4 {1 p4 m0 G% w. ?5 ?+ I* v# s/ Y3 \
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。5 E7 l) V9 H  F2 s/ E

    " T6 W7 I1 e- z. ^- d, m面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    * z3 ^. d. T+ V6 ^2 G$ ?1 I. _" x" Q4 E4 U% s/ b6 p
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧& _: R4 j" M6 O0 _2 h
    - @( n3 ?1 Z, m7 g! W
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?7 h6 K; j2 W# M

    0 g  j$ l4 L% n: z我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    , S/ B2 Q  z' |! D9 C, X
    3 d* c7 ^' ^( J0 I* |) Y面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?& I  U0 |2 a  R& _; h

    ) [1 r( i; ?4 Q我:嗯。。。这个之前看过一点,不过没有看懂: K/ c3 L# G& T- G8 k- [

    ; a$ F. d8 V# T3 o0 c; l面试官:没关系,你可以回去了再研究一下;你期望工资多少?" J  j/ e/ b4 i+ O( g/ c
    - G- [' C7 E5 A. n1 v
    我:3万吧
    # n2 [* A2 S9 G! s  d
    ) N+ S+ T, i' K; V; \面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?2 B! h' i/ l$ Z" F
    , [. O' M& e+ U% O2 U$ P
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万1 s$ h& l# I% A/ a

    3 K3 o+ a; S' a' d面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    : g. T- d- q2 L8 N7 b
    " a9 b% [' ~. k! S! K; S我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。; Q9 }4 q( _) W9 h' H
    : d$ e: w1 |7 H, ^. ]
    这次面试问题如下* E. R3 ^  O# w0 B& X0 e* |# c: p
    5 f5 H  t5 h9 E  W+ r
    @Value的用法7 U# o* R9 D. q% W0 m7 U3 o

    % P5 f( E: W. ^# Y@Value数据来源9 m) A/ L) F1 A& I. j' A8 T6 m
    9 [; |! Z4 N4 X& g
    @Value动态刷新的问题
    , [  W! }* |5 f' X& j- Z& c' Y( \. I
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    5 Y* a4 g- v3 |) |  i8 X; b
    2 ^" O% g: L. B' D$ m# f@Value的用法# T6 }+ f- D" M2 M) c: G1 }8 s0 t" L

    ! V* o' Y8 m7 T, Y. H# F. U7 B系统中需要连接db,连接db有很多配置信息。
    2 S( y8 t% A! J( P* J0 D, P& \( z0 v9 q4 G1 D' {
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    - q9 ]9 E9 f+ d5 G8 B/ }
    0 @) U! B: h+ x0 m7 Z2 k. b还有其他的一些配置信息。
    4 g4 {9 W0 w% p5 h# j9 }9 X$ f7 Z% }* z* t3 f, ~: w4 ?
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    . D" V3 M/ @, C: ~+ q3 g1 L( G. N7 ]9 W
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    0 G  M# q5 l, U1 `
    5 x( w$ O. p* q6 L0 s" C. `6 g通常我们会将配置信息以key=value的形式存储在properties配置文件中。/ U- p: d+ d) J1 ^* u5 ^$ B
    + k" \1 _4 T& z9 R4 m) C% Z% L; n7 {
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。% n. B# n/ Q+ P  L2 A

    : n4 o! W' a+ U* S+ v% f@Value使用步骤
    & ]7 d# G! E. B( n$ R: t$ d
    ! m7 l9 X7 P' x0 D: g& P, E  N8 Z步骤一:使用@PropertySource注解引入配置文件
    ; s; j8 N/ g4 b7 {/ j% n. d9 k, C8 C, H3 j
    将@PropertySource放在类上面,如下
    / c% l! _3 b9 g0 ?% r# F7 K- [- k3 G6 ]
    @PropertySource({"配置文件路径1","配置文件路径2"...})& [$ h) ]% C1 M% z. q
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。" s& k' @1 \. q5 }$ i* t
    5 X: Q$ l( k6 ~' }6 p4 }6 y
    如:9 ]9 T  f' F, b/ f9 x
    - M$ `' ?. c$ k" B: d% [* y
    @Component" K- g' x; M) L
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    8 Z- n( o# {  @public class DbConfig {. G! _6 x& _. E5 s* I  T( O2 p* V8 m
    }
    + k# z2 V% L  z步骤二:使用@Value注解引用配置文件的值% u( s) o8 w: }, e8 d8 P% ]( W

    * h6 f0 J; [- w* n" `通过@Value引用上面配置文件中的值:
    2 u$ W$ H! ~1 F7 X) q! M2 ?% q8 |  K1 A, f' Z# W' o
    语法
    # u4 [' w: \* l$ `, ^- f
    + o" l0 B9 ?) W; Y  }+ ?@Value("${配置文件中的key:默认值}")7 ~" D1 ?. n3 s) y5 n. o; h  d
    @Value("${配置文件中的key}")4 F1 j: w" K, R! A2 u
    如:6 m2 `1 ~3 `# @  W. ?. s
    + k5 N" ~4 F0 _5 s( G
    @Value("${password:123}")
    5 }; {" `. S7 m8 _/ _上面如果password不存在,将123作为值" M& D7 S/ k1 b9 q/ }

    $ `! K; T+ A4 r$ I; T0 g+ \@Value("${password}")
    : G/ d  ~1 `0 S上面如果password不存在,值为${password}8 x3 h' Z/ F' v) Y

    7 g9 Z- M% h. D. r: J- F. t, x假如配置文件如下
    6 M! c9 U9 Q& F8 E3 \& S
    , f7 Q+ ^1 J3 m- {jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    9 ?1 F4 t# F9 zjdbc.username=javacode! `- s  x6 k- l6 _) Z) X! O- m' F
    jdbc.password=javacode
    + J. [& m3 X+ v, f使用方式如下:7 G- S/ Q/ e% p2 L" S( G

    + R6 ?5 t8 k/ y: h2 M5 v@Value("${jdbc.url}")8 Y. o* Z: U9 n0 M$ v6 {4 t
    private String url;
    - _. a9 o0 r9 Z8 }% [
    & V" R8 i+ m) Z@Value("${jdbc.username}")
    ; T& h8 _) F' ?* R' kprivate String username;
    $ J3 T, V7 x4 \6 s7 x. k9 q8 @; l, }( q
    @Value("${jdbc.password}"): n  _6 t. v$ w# _( w- G3 m
    private String password;8 e4 u! H0 M" ~  u- z
    下面来看案例
    % |# ~4 R1 _; a# K$ P. N
    - s5 S8 L& Q* |/ q# J& p$ o案例
    " E! v# d* j: F% v# Q' f/ Q
    1 w% Y4 g6 a7 D! N8 T+ ]& p来个配置文件db.properties
    % a+ Q* R3 L4 u) S6 o+ ^( E2 B) H, G# C7 t' o* U' U
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    * D9 G' y$ u& ~2 ~8 U% g+ b; cjdbc.username=javacode1 p  _1 v$ @7 P7 ?
    jdbc.password=javacode+ u0 ~. n% a4 S  x0 s
    来个配置类,使用@PropertySource引入上面的配置文件7 j9 d* q' u/ {$ c9 T
    6 u" G1 Y: T; P
    package com.javacode2018.lesson002.demo18.test1;5 L3 Y$ ?; }1 d

    % w; K/ f) N" e1 u2 oimport org.springframework.beans.factory.annotation.Configurable;0 A1 g( ?$ f4 ?; W/ V
    import org.springframework.context.annotation.ComponentScan;2 d0 x2 o6 w9 Q8 N, w
    import org.springframework.context.annotation.PropertySource;
    ' ^+ c% O: q! `+ N5 T0 t# n
    * G: G. \# w" g; f) S' n@Configurable
    . a% t2 A6 j5 ~2 E! F@ComponentScan
    # V: o, P% S6 L: b+ J2 m. n9 _@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})4 T8 H% v2 C2 c
    public class MainConfig1 {# N) E0 ]" Z7 S. S
    }
    ( J3 D; ]5 K- O4 ]$ G) K/ }, W来个类,使用@Value来使用配置文件中的信息
    7 n% ^  a( c( w
    - M. ^: K) \6 f! |, f# Apackage com.javacode2018.lesson002.demo18.test1;2 ~- |' R4 Y# B" [& @; h9 S

    1 h0 F& j) o% D+ W. p- T( |import org.springframework.beans.factory.annotation.Value;* \/ P8 P% q$ T# ~1 W# S
    import org.springframework.stereotype.Component;
    * x+ p  ^* \# @( T  {9 ]% l
    + y" b- |# q) r! K@Component
    2 {6 ~8 K: K! b; e" u; k* w% ~) Cpublic class DbConfig {
    2 v( X6 H9 }" y/ |4 V  Y" @4 m$ A! u  h0 T$ [
        @Value("${jdbc.url}")
    3 Q; I2 t, {' H4 U* O    private String url;1 C) _3 V9 D% C/ X
    , |9 U, ~* \- V
        @Value("${jdbc.username}")+ ]5 l' O1 N6 k# [
        private String username;' U8 y+ i/ U# \2 o2 K
    7 Q( o* l) r8 _/ B. i$ _, |
        @Value("${jdbc.password}")
    $ g1 a6 a# u9 m; Z) S' F( h: P    private String password;
    , C/ `/ v) X" B$ N7 j+ E6 G7 C% y! J- [: E( {' i
        public String getUrl() {
    , v' G* N: Z$ @        return url;; I0 e* ~! g' r% j( \' ^4 s1 R
        }
    ( ]& j, }. R. n9 S/ o, e+ X$ z" ]6 i, R' j  n( p+ M, `
        public void setUrl(String url) {  Q( {, ~3 y  Y5 Q. U) y
            this.url = url;
    0 [' b) D2 I! J1 Q" z/ _+ g% U" ^    }2 s! H% I3 W1 G" |9 t. G
    % w" t3 ^0 x$ l- h* N9 `2 H
        public String getUsername() {+ e( m+ b: k9 Y& C+ P( Y8 K& d1 q6 y
            return username;
    8 @7 p* }; {% v* o5 x    }4 {2 U; A9 j% `9 x
    & [. D) C+ q; v3 ^0 W
        public void setUsername(String username) {
    7 M9 i) f+ ]; ?7 T        this.username = username;, y) S5 E% B1 d  Y
        }
    1 Z8 Z: @) R, @* f0 z
    , c0 k( t' s1 g: s6 S    public String getPassword() {5 v. D0 G7 A' p4 k
            return password;, \& [* c2 t+ P" F
        }
    # o' C% p# n5 f8 X$ C  O# l3 S
    0 I8 X2 w5 d. V- ]    public void setPassword(String password) {
    8 C3 O* A4 z+ r% ?3 _        this.password = password;
    / E2 X) Z/ N# L$ z    }
    2 r, s' n7 |# Z/ E( c2 x9 v
    & J# z' }2 Z; t: g    @Override/ A- S9 h: _8 X# ]8 E2 K' O, I4 a2 ^
        public String toString() {, H# ]- |5 P- T
            return "DbConfig{" +* x! ^1 h7 `9 ?- U( M, P- @
                    "url='" + url + '\'' +, G6 s+ h4 P- l& {! r# W5 h) t2 N# q
                    ", username='" + username + '\'' +
    ) l. r0 |8 b" P- K8 P                ", password='" + password + '\'' +
    * u- D) y; X4 |# G3 h; S                '}';
    4 m7 g' o. Q# d0 l7 o0 N    }7 R& c( U8 m5 Q+ \
    }
    * g) D4 `" {& E上面重点在于注解@Value注解,注意@Value注解中的# O" W% Y0 [3 ^4 n$ o* W" }. z
    8 K( j9 j; k1 s/ [4 I
    来个测试用例
    2 A$ T: K, _5 P1 ?, g4 d& l8 J5 m' y# G; _+ W, n
    package com.javacode2018.lesson002.demo18;" S! V$ q4 J2 T  R5 `+ a" F6 @) K
    ' Q/ i' p6 B4 d0 Q3 V, S
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    1 u2 J4 z( [, |) E6 v! Y; jimport com.javacode2018.lesson002.demo18.test1.MainConfig1;3 L8 @; S+ A) V" }) X: J9 m! x+ }
    import org.junit.Test;. J* p* P% m* h  _4 {
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    5 B2 L. Q; w7 u" F1 \( C0 ]2 ?" W( ^
    public class ValueTest {' w8 S' }* M, K( m" j

    3 c6 P# P7 M8 e    @Test
    : \3 l  I) A: W, v; M: Q; E    public void test1() {. E- l) N1 Y7 ]; l1 \' D
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();/ o$ l* A- ]* O& a3 E% F( e
            context.register(MainConfig1.class);
    8 D6 [! S( y5 Q0 W* x) E  V( T        context.refresh();0 \% O+ ?; q* y: A
    ; T5 U6 m7 F- |; q
            DbConfig dbConfig = context.getBean(DbConfig.class);! E; Q$ p0 I( s$ {' D# D
            System.out.println(dbConfig);+ R& x5 v; B5 b, [' G$ S0 h0 a* h- ~
        }" S9 F! a" V8 o/ R% g# c
    }
    . `7 f6 G" }: x2 |/ F( k运行输出* l! j) \, A% N# }% H

    3 U5 N5 b3 F: M# t. i4 Z% KDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}5 }0 s! h  l: D6 L% q
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    8 V. M, t1 K% A$ |$ h0 C& h' Y8 }( y* C+ Y
    @Value数据来源* y! M2 F9 Q! M2 C8 H  J
    * s; `$ D$ ?6 P$ q/ S
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    : N  h: N! g  Y8 D" f# }+ \$ T; G6 K) m) D! j3 i9 Z! R
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    + @! Y2 u3 b+ U, ^$ Y- k) f& ]0 e& l$ Q( R: Y, w' g& r% p  e4 K# N( Y
    spring中有个类# o& g' d, ]- I. ]+ O' d

    7 c. w# w3 i4 ]! V# L" A1 sorg.springframework.core.env.PropertySource
    / a" s! w6 k; z" H8 J可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    * F& D8 u* ?+ u, _0 Y/ x
    4 P2 o$ d" l9 |$ N$ E+ l1 b内部有个方法:; m' S& G$ n( I# k) W3 F3 A* x
    ! I& a/ Y! \/ f+ t! u
    public abstract Object getProperty(String name);3 Q$ \8 y" f. `* k2 P! E* y
    通过name获取对应的配置信息。( R' z) I* k1 ?+ m4 Y5 v. g
    5 F! B" x4 w1 S2 g+ M0 ?+ A! n$ _( n
    系统有个比较重要的接口4 e! u% N6 ^3 {) E; U+ b

    ; c5 D  j7 u4 ?1 B" G: {& rorg.springframework.core.env.Environment& ?+ T* b+ e& w
    用来表示环境配置信息,这个接口有几个方法比较重要1 T! Z2 n$ u+ l, H; ]
    ! {9 |" ~* w7 d* r$ }) |6 Q
    String resolvePlaceholders(String text);$ Z8 e4 o* u: w  G* }" K" A
    MutablePropertySources getPropertySources();
    7 F' {2 o  P/ A& o# \resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。" G" }9 ~/ K( M; G) ^$ b' |

    * \+ x$ Z. n$ P+ J  F2 bgetPropertySources返回MutablePropertySources对象,来看一下这个类
    2 N) [) O# a2 A% r3 ~
    0 O$ S9 ~' D, t0 @& i) Ipublic class MutablePropertySources implements PropertySources {  j* g6 f0 G! ?. m. |' Q& f* p* z+ J

    9 A1 ~6 K) N  J3 v# N    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    4 ?4 C# l  C( K5 k
    , y# S7 E! J/ u/ @* m% W4 C; j2 C}
    ! R% D1 p/ g! s2 V内部包含一个propertySourceList列表。. \8 Y, M7 w- F/ m" [

      y& H8 y3 R1 ~8 gspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    $ Y: V) G& o. k: O, ^6 ^; F& |8 F) ^0 U3 g- n% N
    大家可以捋一下,最终解析@Value的过程:
    ) ?* @  T3 T, Z9 ]* X3 W) @: K7 }' }) T8 o( k5 H! V! W# K6 {
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析* A4 o- W% R- i) i
    2. Environment内部会访问MutablePropertySources来解析
    ! c2 G4 H0 F+ B+ b/ i6 m- n3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值% V/ b0 \5 k% H
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    " i9 D* M" ^& Z0 ]0 p* B' k1 \; W0 Q/ b
    下面我们就按照这个思路来一个。' H% Q" o. s) H, @6 Z

    $ y9 z. r$ S( f  u& W来个邮件配置信息类,内部使用@Value注入邮件配置信息
    # {7 [# C: c2 C8 Y* B: O7 x6 W2 h! a: H, T0 q
    package com.javacode2018.lesson002.demo18.test2;2 b" w4 p9 Q4 W+ |& {
    1 ?6 B: @7 E7 p9 \6 p+ T
    import org.springframework.beans.factory.annotation.Value;' B9 t" |4 B. V
    import org.springframework.stereotype.Component;9 f" X9 R0 Z! f/ ^+ [
    ! q# r& K- p; S! `( d/ f8 G; O
    /**& `4 ^' d; k! g+ D( i/ z
    * 邮件配置信息# T* }* f; a' K4 A1 s% R
    */
    2 p+ H- Q; k- {- l@Component6 @8 q. |6 Q/ n! B! [. u* E
    public class MailConfig {
    0 x, W: {* e6 P# |/ T: Q4 `. ^, ?
    & A  ]. Y3 N/ r* R1 `3 y; {0 P    @Value("${mail.host}")
    . b( V. i8 i# |8 s2 b6 |+ i. v. g    private String host;
    ; M% i; b7 E: W8 y# y# q* W' ?, t# P/ i* _' d# I; [
        @Value("${mail.username}")3 q# f' Q$ n2 g' p
        private String username;7 Y+ Z3 H* C$ O5 _1 N" c+ d

    ; u: X, w* f/ g, w' H& W$ G5 `- N    @Value("${mail.password}")
    : c1 p' e' i  H, B+ m- S( K2 n- q    private String password;  j. C+ g( m- ~

    5 F+ {9 j* w  m8 s    public String getHost() {" X. t$ o. }" V) ^
            return host;
    . ?: {% j$ a! J    }4 [" Z8 G# P, d+ j
    6 F3 a! ]+ L. D
        public void setHost(String host) {
    - V/ [: p0 e  z0 n' k. W: T        this.host = host;
    6 C! \$ e2 b/ \) q* r+ b5 T& p    }1 D5 Q6 {  v7 r! D; u3 z; S8 L# x
    " N- S5 }9 n" x; X5 c& q% o. |1 x
        public String getUsername() {
    6 e$ Z3 ]$ M5 T2 J7 N# {        return username;
    4 A* k# ?/ K- F: M' m0 x    }! v0 `. j. i4 \" U( i2 ?& b

    ! C3 G) w$ D8 s" L2 Q* e' U) p    public void setUsername(String username) {  m3 R+ w8 r) E1 o% }* |" g
            this.username = username;* Q  }; X9 p# N' u2 d
        }$ s0 y, ?+ z6 [: x& c
    9 S* b* `7 Y6 q9 K- |# w
        public String getPassword() {+ X/ X. ~5 r" m) q; K: I
            return password;
      a* E* I% C0 r+ I" e2 B* c7 Q+ d    }
    : w7 ]) J) Y) |" v! [: s0 {
    4 y: U) f# o3 {, x    public void setPassword(String password) {
    7 I+ Q$ ?) d% a) O# h/ C        this.password = password;3 o* T8 p4 ~* h; K' A' J  m5 r8 o0 ^
        }
    & I4 x, s$ C* G+ z$ i" R7 F# m
    # {5 u3 v4 Y, }& X    @Override5 R  D) h: c0 N& \
        public String toString() {# e: c" W2 S0 z, A: b# b8 n
            return "MailConfig{" +* e2 ~1 l" @( C7 ]; w9 @+ |  Q
                    "host='" + host + '\'' +
    ! k) X; j3 k5 n& z/ O6 _                ", username='" + username + '\'' +
    , t/ f- u8 x/ C7 J& p                ", password='" + password + '\'' +/ b: a" v' _$ I$ w+ ?) N
                    '}';: ~( v$ v, W: `7 |9 c5 L+ O# u
        }
    : W) E, p) F" {5 T}5 {- K; v9 U' L% j: W
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    $ V+ @2 Y, U% H) A
    , `! g' T5 |2 m5 N: xpackage com.javacode2018.lesson002.demo18.test2;: ?% ^* e. u4 d$ v% ^' C

    - c8 Q6 E9 [7 u2 [. yimport java.util.HashMap;
    ; `( [' B+ Z; @, A9 iimport java.util.Map;; ^- l0 C1 w; |8 R/ s
    ! `3 d7 V- h3 q  Z
    public class DbUtil {/ f( q3 A  _4 E3 E1 U# U
        /**
    9 Y4 w3 J: P' P" l% W; P     * 模拟从db中获取邮件配置信息
    5 D) m2 R3 P5 e3 H3 P& o! `     *; q" ~7 C3 U- ]' [7 e& \- m4 z
         * @return$ g& T+ q1 I. {" v: j7 X7 B2 Z
         */
    ; ^% X4 V" [& o/ `    public static Map<String, Object> getMailInfoFromDb() {
    " l& P& n2 F4 N: l6 O        Map<String, Object> result = new HashMap<>();
    ! A! C) [8 C  F; t! V+ B" ?8 c" `5 A        result.put("mail.host", "smtp.qq.com");. N6 ^  S$ k- a3 K) u" `
            result.put("mail.username", "路人");
    8 o0 \7 N7 C$ E        result.put("mail.password", "123");
    9 [" `6 |- N/ w# I, R, C        return result;
    8 C& b$ `) E5 W5 ~: Z+ |    }% y1 S8 X0 ~9 r0 n- U2 F* b: K) \3 I
    }
    # d' [2 h) W+ T9 G. G来个spring配置类
    / g! Y/ K0 @6 z# ~5 ^. z
    7 m, C$ K6 D3 H: Y; qpackage com.javacode2018.lesson002.demo18.test2;; }1 L, V3 g( `* ^

    $ `+ ?1 g7 g$ ^. w% dimport org.springframework.context.annotation.ComponentScan;5 ?5 \! i# l) z1 ]0 F# ^" V
    import org.springframework.context.annotation.Configuration;
    : a3 {% I6 d9 C+ v' @: j' P6 l9 D5 |9 f: l/ g
    @Configuration
      Y/ c4 i. f( ^7 O/ h9 U@ComponentScan" f# C1 Q# o% K$ A3 @" {
    public class MainConfig2 {6 D' ^1 ]9 I! n1 }: }2 Y
    }
    3 s9 ^5 L8 Y' s4 ?/ Z下面是重点代码
    & e2 A9 y, E# [' K% U6 g
    " N$ X2 w3 X+ u' V3 c/ Z) A@Test8 M% ~6 M' j+ @, n* e7 y9 R. \  f
    public void test2() {
    & Q$ s0 w4 {3 \/ Y+ N    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();/ @- n" Z* }( T  U4 d% i

    + P# `2 m5 B. m7 N5 o/ r2 A    /*下面这段是关键 start*/
    3 N4 g2 w0 ]/ u* w5 o4 h    //模拟从db中获取配置信息
    % Q; ^, O7 U( N3 f/ ~    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();5 v# S+ H( z+ w, k  s
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    " D, \$ P' H+ S9 O. D    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);) x* B$ O7 t8 y( {* _, ~' s2 j( y  F
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    0 t- g9 s- Z& x; e8 ~& V  K2 c$ A; U    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);' W( i' T! \! L8 R* k9 }" k
        /*上面这段是关键 end*/9 C/ G+ d( G  W9 y: _
    $ Y) [' ?" r- m) P8 g; @* t
        context.register(MainConfig2.class);+ r- ?) t& [- |+ u8 K4 w
        context.refresh();- b# Q" c! `7 z, f
        MailConfig mailConfig = context.getBean(MailConfig.class);
      w2 Q( L7 O' e) y    System.out.println(mailConfig);. L9 q3 P1 ]5 A. Y0 z) u" R5 l& }
    }
    ( L* d4 {6 V6 I; t注释比较详细,就不详细解释了。  X# j* T& D7 _1 b- S) i4 L$ b
    3 p# C0 I( z3 w( f3 A, q
    直接运行,看效果
    ! C. N' L* p  [8 Q  \5 o5 E4 W0 y$ K) ~# i3 O: d: s" V: Y
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    $ S+ ^1 y+ \$ R" l9 J( [  Z1 ^有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。* i1 h$ q. r0 W8 v0 g2 l( E  v' D

    # j& S9 u% D/ t, u% @( S; X# `上面重点是下面这段代码,大家需要理解
    6 `2 ?# ?/ A  Y) ]/ u
      U) h: R  n# @7 z+ I2 H' n/ d4 t/*下面这段是关键 start*/6 Q1 ?; ~+ V- c0 Y: y
    //模拟从db中获取配置信息! N4 a9 m) t" H! g/ H1 [
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();" m/ o7 u+ `( P
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    . e/ f4 J' M5 I" B! jMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);$ e& n9 v: D% g) k, I# h, g
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    % w% B7 C4 ^4 Y- M$ Fcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);" p; d* n/ |# w8 G
    /*上面这段是关键 end*/
    ( ^* z- }& @" V9 p" V咱们继续看下一个问题
    5 @9 C; j0 M& j! Q8 v
    2 v- d+ }: ^' Z& [$ I如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    $ g3 S! p/ o# i% ]1 b& j: X4 g: r! o- I5 T5 |- X1 _4 C/ x
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。. X+ T# z' A" G2 U  k4 Z) a2 N8 w
    9 `7 T& f5 r% W) V! u
    实现@Value动态刷新
    8 H" X# E/ W9 [" y9 w( T; H0 t3 s! }
    5 {3 j4 a& C0 j  W* U先了解一个知识点- _. y8 B  e' z' V; u. n) i% V

    5 m4 M. N- f% \; ?5 @5 f这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。# `  E% t# l% W  R
    5 L# V5 U4 d; E" G
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解4 L% G$ S( |% P6 Y5 b2 ?0 a

    / U$ a- g! V1 u1 |bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    ! P% a) f4 S, Q/ E$ F- R$ x8 L& [: [5 j
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;6 Z. o, I$ W- P# \  [* c+ O0 E
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    : N1 [  s5 U7 d% ]4 A3 C
    ; X$ Y' M/ m3 ]# S3 _public enum ScopedProxyMode {* g* S  F. a: Y0 R7 j
        DEFAULT,
    " L9 Y# V5 o1 w% J9 j+ p+ l- R8 t    NO,
    4 [2 S, D4 X& b+ `, J9 S' Y    INTERFACES,
    9 g$ y6 b! o6 I6 C8 N* Y    TARGET_CLASS;" U: w& M/ Y  g
    }
    - I6 L: X1 B& |前面3个,不讲了,直接讲最后一个值是干什么的。$ b! k, b6 Q3 G
    " T* K7 X/ z6 h: c! ?
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    4 E, r) A& a0 ~% ^1 V6 x7 ]
    % c( @# t" ]" M; N5 v理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。. O. a; b' R( v( u7 X6 j( B/ Q4 n

    % ?+ s& a3 U1 P. n& p4 @自定义一个bean作用域的注解
      O3 Q" n+ F, x1 C+ Y
    ' p; V- g# i& G* Kpackage com.javacode2018.lesson002.demo18.test3;
    # _4 A& ]( R( ]/ D
    8 [4 w( ]4 J6 W: k  Fimport org.springframework.context.annotation.Scope;, s- P, W; D% N
    import org.springframework.context.annotation.ScopedProxyMode;- J9 u, `* P2 z

    " W( I* e' ]% {) yimport java.lang.annotation.*;
    5 B+ G. P( c* i$ K1 Q: G3 q4 K, X9 C
    @Target({ElementType.TYPE, ElementType.METHOD})
    # s# q( o4 p" g0 z5 e) V@Retention(RetentionPolicy.RUNTIME)
    + B0 Y3 a$ D/ Q, j/ E@Documented
    ! l% f0 s$ E. n$ _# z@Scope(BeanMyScope.SCOPE_MY) //@1/ k  S$ f  [/ P5 u  R( O0 W
    public @interface MyScope {
    ) r! {& K# l4 M2 h6 a    /**
    7 V  q7 ~5 z+ V; e4 h     * @see Scope#proxyMode()8 _1 K7 r) M+ ]) X1 s
         */7 g) }" N' X7 C% k( N+ Y& Y
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2, L: C  D) J$ m$ p# `1 L- O+ O( p
    }( L  s, A' Z$ @0 g; z
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    5 `2 \' C7 Y/ o/ W5 j: T  V4 {5 A8 X
    : p! X7 h. q$ f9 X@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS7 ]5 e4 K: S+ e3 n/ W

    , F2 n- r# G6 j, f@MyScope注解对应的Scope实现如下* H0 y5 }8 Z# G) F

    & v  U) Q, N4 Npackage com.javacode2018.lesson002.demo18.test3;# @& Q5 U1 k4 D8 e( G8 F" I3 C8 k

    ! l" g5 R% u( V) V5 Aimport org.springframework.beans.factory.ObjectFactory;
    * x2 p& @% P& rimport org.springframework.beans.factory.config.Scope;6 P; D2 M- Z2 o5 p
    import org.springframework.lang.Nullable;& G+ _* u3 {( `- E4 K# r3 ]/ z* F

    ' O, ]5 x: u0 v/ K2 }' j/ P/ M/**
    % J; l) j% c$ P/ ~1 v" k# M# d * @see MyScope 作用域的实现
    ' h  a$ g( r  C  d: T */
    7 `( a0 }& G; g3 q2 Y) G  @public class BeanMyScope implements Scope {1 ^% }8 i* ]# p  u
    4 r1 f  z1 n* H8 I8 X; ~, e' P
        public static final String SCOPE_MY = "my"; //@11 ]/ a# z( y3 F( K
    " R/ I$ f1 h7 ^5 U
        @Override
    . @( X  r0 x! ~7 j+ H1 d    public Object get(String name, ObjectFactory<?> objectFactory) { $ m" K* N2 E2 J
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@22 T9 z6 v) Y: v- q) s0 S3 v
            return objectFactory.getObject(); //@3
    # a& ^5 i5 ?; T& \    }1 K, u% g. j( {* V4 \& F7 H
    & g9 C7 D; ~) G
        @Nullable
    8 k- {. s. L5 \+ Z2 |1 H    @Override
    ' R' J: l" e; ~/ Z5 g* A    public Object remove(String name) {* g3 D% f! ]" S- S! @- l$ d( K( b
            return null;
    * y2 b) M4 l$ u8 [  g    }9 i7 m2 B; m5 o. b. E
    . X4 ]( z7 e9 t
        @Override
    7 V* w. @7 v4 {: ~    public void registerDestructionCallback(String name, Runnable callback) {
    8 ^; R5 W4 q& C. N6 T
    % P. j1 a, ~% r$ ]    }6 p' L: S  n) }8 G3 x8 s

    / ?7 ~; H4 B+ f    @Nullable! e. L' t# `: l8 Z
        @Override
    + x7 q4 I& H$ T    public Object resolveContextualObject(String key) {0 X5 \' f3 X3 j+ e2 |8 |4 y
            return null;# [. Q* f2 y9 y0 j; W' M) b% j5 e
        }: c2 W$ y9 Q7 L9 c* R. v0 t

    " @3 D) W8 w: Z, ~1 K* }. {    @Nullable, _( Z. u7 h( ]. n
        @Override
    1 P/ H/ ^# j  G2 T8 i  _- }. }    public String getConversationId() {& |/ m6 d& c% m
            return null;
    : X; I2 {( K; L5 S! B! ~8 X' {( p    }
    " A0 E3 g" {. {7 o: i}, J3 b9 [* ^3 i% n- l" b: e
    @1:定义了一个常量,作为作用域的值
    : Y: E8 G( q9 l4 T- v3 m2 W' B, H
    / H1 E8 U/ H5 L@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    / I/ r8 ?& R, J, q7 d7 q8 L9 q9 T$ Q& O# x* y+ ~6 D! j4 ?* m6 G, Y
    @3:通过objectFactory.getObject()获取bean实例返回。7 V+ |, K9 r& z3 I6 L7 @0 S& H
    ) t$ U& F. X: \! R$ O* x
    下面来创建个类,作用域为上面自定义的作用域
    2 J( r( v: c* L9 Y/ ~
    1 j/ c$ G0 a4 c! y$ P# h6 vpackage com.javacode2018.lesson002.demo18.test3;
    ' t0 B( ^/ o, }, q" [
    1 v" q/ @: K# v2 y9 ^, j5 mimport org.springframework.stereotype.Component;4 d" ~" z8 M  [2 X* X
    8 U) Q2 z; H$ _  g0 U
    import java.util.UUID;; s. ~3 }* |5 L1 X2 {# G
    , i  }% }  s( ~* P6 \
    @Component( S* S) D7 k6 s6 M4 [4 e* Q
    @MyScope //@1   }& b# c. V5 U+ U2 p
    public class User {
    0 u. _& l; w  o4 u8 ?& \/ |+ a- q+ Q% t  ?- X, |
        private String username;( Q% e4 l- n" o" v

    ' V1 o  h# Q  D& F( M5 Y    public User() {
    . \# F" Y' ~7 \! O+ d$ w! b. H3 ^        System.out.println("---------创建User对象" + this); //@2
    : U; j- ?6 t2 G& n/ f& Y        this.username = UUID.randomUUID().toString(); //@3
    ) m+ l5 d' N: c" w8 D- X; h    }
    8 k- u2 q  p  P8 I: \' i, l: h+ n6 ~9 Q( Q% N6 z7 D
        public String getUsername() {8 T! `+ Y5 F( a' Q' h& I& l* X
            return username;
    1 s) D3 D& P/ b" X4 t) `" B9 a    }; B* K6 e% U5 L& j

    & W' e. s, k& h( m. x5 R: B8 J9 W    public void setUsername(String username) {+ S9 r) W7 j/ L
            this.username = username;
    : s# R% h/ g3 s9 _7 N, U    }
    4 W- k; x9 Y# f3 P8 @  t  t, I5 v, C4 {0 T
    }2 ?) N" p$ e0 [0 n  X5 W$ ]
    @1:使用了自定义的作用域@MyScope( L% e1 r) j, O% f
    4 G4 B. B. n1 x- ]5 r# Z
    @2:构造函数中输出一行日志' |8 z" w& Q( m& [- E9 G' i
    ( Z) z/ R9 D8 y3 ?% j( j
    @3:给username赋值,通过uuid随机生成了一个* Q  a( \4 R6 ^7 [9 e5 }4 u4 L. X

    5 p$ T# \( L' m& D8 p# I来个spring配置类,加载上面@Compontent标注的组件
      m9 ^: j* }+ h/ X7 L/ w
    ; P/ v/ C5 ?& k6 y1 opackage com.javacode2018.lesson002.demo18.test3;0 p) S. k: Z; o" R
      v- ]2 \( `  G6 T$ |& m4 Z; d# Y, ]
    import org.springframework.context.annotation.ComponentScan;
    : m$ ^1 y0 S1 z! [$ u; t( Ximport org.springframework.context.annotation.Configuration;& W8 E9 Q; ]  U. F1 o& }
      v" \. F  ]' I0 [' d7 Y6 O
    @ComponentScan( u( w5 K9 g% `  d
    @Configuration7 U9 w1 u1 G0 l/ @& Z6 X6 R& J! C
    public class MainConfig3 {( R$ a& c: `& `+ b
    }8 w: l" I, b" T) y7 q1 G8 a
    下面重点来了,测试用例5 A* z" y% m6 w: T& S

    : x4 A) a& U" D1 x9 [  m6 }@Test
    4 p8 c  b" C- P: \- T' V, d  Ppublic void test3() throws InterruptedException {* `$ h% r; B2 F: P& a. p$ q
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    / G; k# O$ _  J' X/ e  l    //将自定义作用域注册到spring容器中
    0 Z" _& n2 k* }5 h    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    0 o+ g5 o4 W- D8 c# v6 a& A9 n  j    context.register(MainConfig3.class);4 V, @0 O! P! n9 S+ j( E
        context.refresh();
    2 ^' n2 G0 J  e- O, T+ U
    4 d* F- e- s" L5 n    System.out.println("从容器中获取User对象");
    + r3 f2 j9 \/ d    User user = context.getBean(User.class); //@2
    5 J: ?5 k" x! N& s; q  Q8 [    System.out.println("user对象的class为:" + user.getClass()); //@3! B: A1 y2 O2 D  \4 a0 h
    9 W$ o& `1 N! G) M" s# ?8 r
        System.out.println("多次调用user的getUsername感受一下效果\n");
    . R) \! v6 R7 q) L    for (int i = 1; i <= 3; i++) {
    5 y6 R% }: e1 D        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    - W' O1 W0 i5 Y1 X        System.out.println(user.getUsername());: B& @7 I2 L- b" Z% s4 J
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));  [+ I" o- c9 T# f
        }/ g# B4 J, n: ^& x$ ]7 w$ H
    }0 c& H! {5 g+ h6 g( c) D# b% |
    @1:将自定义作用域注册到spring容器中# _/ P7 |! ?, C8 S2 I2 I0 d
    ! G4 S1 X8 R) N  w2 i1 Q: [$ Q
    @2:从容器中获取User对应的bean
    % `3 W5 B! S4 E2 G' @  a: d  P
    5 d9 h8 r( w: I$ W$ K9 V6 X@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    - f. x; G/ A  k( H7 q% ~) I5 q* s& J: ]+ |7 @( W
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。! [! m, r% O, I
    & l3 d0 e0 U: D
    见证奇迹的时候到了,运行输出2 _" e# j/ S6 T# V
    " O' b( W# S4 t4 h4 o# T* B2 G
    从容器中获取User对象, p* A8 o5 a6 j# b1 M( C
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331274 O5 {+ H! f& D8 v3 @0 s
    多次调用user的getUsername感受一下效果
    7 |( T* \- w" d, F; B4 U
    6 L2 y) n) U3 C6 i" o********. M0 G; y" O' b& O+ I6 y
    第1次开始调用getUsername: k  t+ j2 b7 i) c* J
    BeanMyScope >>>>>>>>> get:scopedTarget.user) ?/ ^4 A: L* Y* m
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f44 C. o6 e( H$ K  ^% G0 N% x7 U
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    ; k2 p6 [0 B, n) w, N$ P第1次调用getUsername结束+ s) S( |8 w) d$ P. K& s
    ********
    ) n" ~  R) K2 s' Q0 u: y! L9 l7 Z% y, d5 q. W0 m6 `
    ********) _0 {" d4 r( n: R3 r
    第2次开始调用getUsername, D0 }/ t, z9 A& X3 f6 _0 j
    BeanMyScope >>>>>>>>> get:scopedTarget.user. R' ?. N: t+ z( h9 c2 H* _
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b* Y+ P1 s5 N2 T
    01d67154-95f6-44bb-93ab-05a34abdf51f9 l1 B7 g+ v0 D  ?; K' x3 i
    第2次调用getUsername结束
    1 n$ P% w3 B2 s. H* a" b1 I6 y" }********0 G* B% |" D( a

    8 k, R6 W1 K9 p********
      W+ ^4 ]  a8 h5 D第3次开始调用getUsername: b5 j) @0 r* o. r/ `
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    # ]6 [& I% c5 ~3 `7 c---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    % u& [8 o+ _4 t; D  N76d0e86f-8331-4303-aac7-4acce0b258b8
    1 Y0 I) U3 [& Y第3次调用getUsername结束! ~1 K4 Y" f" A. G
    ********9 v/ k/ y7 K" s
    从输出的前2行可以看出:" e- @" j1 H; w, d
    . B( u* i6 }/ h. h5 `
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    3 F# q! S# f! h, o
    : G! n$ S. Y7 X# r第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    6 r+ l6 t2 z! d6 G& R, y' l) B) {- e5 s+ b% `8 @0 O, {. \, E
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    6 A7 O0 u2 [0 {2 K9 t
    - ], x0 _$ \6 y  _& T2 B3 I. z通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。$ s+ s6 ]; Q: i5 Y

    $ }1 a# f, A& r, M0 a5 w& p动态刷新@Value具体实现, ~6 B! a' b0 K5 C% H9 [
    : @7 P3 A8 Q  E. F/ E! P- {
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    4 R) J4 Z+ t2 }: v
    ) E# h1 o% l* F. {3 v先来自定义一个Scope:RefreshScope
    2 @& V( ]" h1 W3 v, N
    * g, i2 W8 c% X& P8 H* x6 qpackage com.javacode2018.lesson002.demo18.test4;8 ^1 g+ d. N) k0 ?8 Y, q
    ; v' Z& `4 ~$ }" _
    import org.springframework.context.annotation.Scope;
    + ^5 U5 E# L2 ?: p+ wimport org.springframework.context.annotation.ScopedProxyMode;2 N: w2 e6 }& n1 j+ ?. E5 q
      K" n+ _! G# I7 b
    import java.lang.annotation.*;
    9 N* q2 B. h+ E! \+ ?
    + M5 N. {* f5 D@Target({ElementType.TYPE, ElementType.METHOD})' a2 [4 J" y8 Z) X& ?4 F
    @Retention(RetentionPolicy.RUNTIME)
    " L" o' m: N4 t+ [@Scope(BeanRefreshScope.SCOPE_REFRESH)
    6 W: `) {$ ]* k1 `* ?3 s# L5 x@Documented" M+ A( S; J' I" @4 r1 t
    public @interface RefreshScope {
    7 z; C; m/ V* q+ e- c# o1 f    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    8 }2 j1 ?% g$ G/ a}6 i: |5 ], `) f2 R6 I
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置) Y2 Y8 U: ^3 `: Y

      b- _8 M; U) I- `$ k@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS1 E0 X7 D  `: r) j7 D( w4 u
    5 s2 V  f1 Q/ K0 f5 g, ^0 k7 g
    这个自定义Scope对应的解析类/ L) @: c7 f+ T

    % I1 j/ R! X: P' o下面类中有几个无关的方法去掉了,可以忽略
    , q) k' g5 j0 J7 U
    * _; |) D  s& V, L# ~package com.javacode2018.lesson002.demo18.test4;
    - l! E, ~. @+ Y1 C& v( s( w6 J0 C1 ]1 m1 L1 u) s; ?
    + ^, k8 v- l9 R( L, F/ w
    import org.springframework.beans.factory.ObjectFactory;
    # {, _& }( @7 h; iimport org.springframework.beans.factory.config.Scope;
    ) z7 _  G* |$ y# z3 C/ Zimport org.springframework.lang.Nullable;
    ' a- i+ n# g( m0 M# e1 q/ g& a6 j$ D$ P
    import java.util.concurrent.ConcurrentHashMap;5 ~1 |8 ~! O; D- r7 S( i
    # v. \9 y" h! d# B+ U0 N! J- Z
    public class BeanRefreshScope implements Scope {
    8 [9 I& x# I8 x1 W$ J
    & d. G3 ^9 @6 S4 ^4 O* d    public static final String SCOPE_REFRESH = "refresh";
    - m7 K2 j) z, M+ z8 }; q9 c! M& Y4 q8 ?
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    + F( t5 n# [2 T, q- `) ^
    $ y& Z  O) t& v: N1 x    //来个map用来缓存bean7 M. J2 y1 D1 [% W' L* _
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1) }* B0 n5 @% l% O% }3 R5 x
    3 n% A6 W) |9 M* r1 `
        private BeanRefreshScope() {
    1 S5 P* i, a* |0 Q$ F    }* V, A2 \0 C$ a0 O; r

    " l5 R/ x4 V3 D: w3 J: k    public static BeanRefreshScope getInstance() {/ b2 z/ q9 {0 N9 {8 n+ p& c9 @7 i
            return INSTANCE;8 U/ g8 y$ L2 a; ]) n% y
        }+ T0 e' G2 x5 b& E9 [1 ?
    , M" h5 P% [$ Q( U, \6 Q7 s" G) D2 @
        /**
    . P, w' ?) _0 x8 a     * 清理当前8 D" a0 }7 K. L) A
         */
    . v+ n8 h0 L1 x" H$ ~    public static void clean() {
    3 ~2 U, |4 S- R8 T1 X        INSTANCE.beanMap.clear();# T  g: N+ y; t- W
        }2 h4 u9 P4 A% q  g) P) B( F7 a

    $ C/ k! A8 b& C/ I' {! g; V0 G1 ^    @Override
    ( w8 G5 F- j, A( b( j    public Object get(String name, ObjectFactory<?> objectFactory) {. N1 }) b+ M  M
            Object bean = beanMap.get(name);% |$ W8 B8 m) D0 h  K
            if (bean == null) {  O# K8 G+ o1 r4 @5 T5 L
                bean = objectFactory.getObject();. y8 Z5 E; H6 T, r8 A& N
                beanMap.put(name, bean);
    : t. D( R  v; w8 y; F& J        }% x& ^# l( r5 g: r4 p2 _2 n' m
            return bean;
    # y6 J: {8 b6 _$ H$ Y( E0 [4 e2 S8 j    }
    7 c5 S# N0 s) V6 d0 u  J1 \0 ?3 ]% ?3 k3 p# N( d
    }
    ! s2 p% e4 w# x# C  M2 q上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中" d& a9 d1 a# x7 N4 B* o

    ! a! _& G. r; }- k2 j7 R  S9 ~上面的clean方法用来清理beanMap中当前已缓存的所有bean
    , X6 f2 a; }+ a/ o! N- ~* W2 i; d7 k! z" `
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    ' t, D- j* a/ E9 _+ V) ^
    " u: {2 @9 u# S$ C: Rpackage com.javacode2018.lesson002.demo18.test4;
    - N: r4 O( {3 Q( ~
    1 m' W( P9 d5 f9 `; O8 k7 J$ _0 Mimport org.springframework.beans.factory.annotation.Value;! q9 w8 X7 q/ S9 Z' @- ^, Z
    import org.springframework.stereotype.Component;* B7 v6 f3 Y! O+ }8 ^
    " T9 p* T% ^& w
    /**$ W3 c2 Y) g- F* b. B% c6 E; b- r( @
    * 邮件配置信息
    2 ~- U' a/ H* |- Y/ f8 O9 i: ^ */% S, m+ ^4 G1 j) v/ J: k7 c5 T
    @Component, h. r3 m+ N" i6 S3 S
    @RefreshScope //@1
    4 N6 f- X/ |, F. X" |- U2 fpublic class MailConfig {1 O% g+ B/ f6 L# Z5 o6 q
    ; v; `8 ]3 Z  }
        @Value("${mail.username}") //@2
    2 w& n& x- p7 \& Z* w    private String username;
    / F* w( z6 N2 _0 B' L3 Q6 q4 e5 v% A8 T% ?
        public String getUsername() {
    ! i. y+ e: P0 v, W  e        return username;# S; X2 g8 n- t  |8 k
        }
      S2 }9 U2 P, i# X
    + X8 t3 J& z) P( p8 B# ~    public void setUsername(String username) {; B# U* q- B! O( Y2 Z  e
            this.username = username;
    1 f+ D2 S: `. g    }
    % ?9 A) b& p. T; n  C
    0 F. p4 {1 x; N+ V2 p    @Override
    1 h  r7 B0 c/ z6 Y    public String toString() {- y2 h; N/ f' n: L  [' u& _( D% i, a
            return "MailConfig{" +
    . `; N* F. F) ?                "username='" + username + '\'' +
    : z9 i3 ?2 H. V3 ]6 S7 d8 h                '}';5 R( f* ?* B6 j+ C
        }
    6 G) w# v/ ~" |+ S( ]; Z}0 ]/ E" v3 J8 `2 i0 t9 [! p$ n
    @1:使用了自定义的作用域@RefreshScope
    " w9 F' s0 w4 g
    + j5 q& u# z/ q- ]% L2 O@2:通过@Value注入mail.username对一个的值( M  S1 S5 M! p$ A  e: W' \& r

    3 @: m0 K0 \9 y- L2 W重写了toString方法,一会测试时候可以看效果。
    ' Q- V7 U5 ~* m& }( u
    : {& D' P1 ^, d. s7 j* e+ q  j0 p( [再来个普通的bean,内部会注入MailConfig
      o* X8 x, l8 H$ [& K! o
    " h7 O6 `. s; t) c, s/ j- gpackage com.javacode2018.lesson002.demo18.test4;
    * z: s# _& L+ E6 _( }) @9 P) z" w0 _3 T( U1 r1 S" _+ G4 Y' t. t
    import org.springframework.beans.factory.annotation.Autowired;
    ; y9 Y$ U3 ?, S5 N1 o2 Gimport org.springframework.stereotype.Component;
    & g. S2 Q1 o3 C; J' Y: @
    6 @4 j" z( w3 b" G  P1 K" u@Component
    7 x% r1 |" [+ P# a; {! d1 O% Vpublic class MailService {
    & A- k- n. P% i, d; O. `5 K7 T0 R    @Autowired
    / j" z" F1 H- _& u4 t    private MailConfig mailConfig;5 {; i3 G: a. g2 v- y9 v
    / Q- M" W# C, E$ u/ u+ Z
        @Override
    1 S5 R5 f0 G+ N8 U    public String toString() {$ r# r" d5 H5 V9 A2 A& D
            return "MailService{" +7 ?* P- g7 t; |/ g9 D; @
                    "mailConfig=" + mailConfig +- J2 _8 T$ C8 F! M$ D
                    '}';0 J! ^- g" _* b( S1 {
        }! K, ~% g/ C) ~. f/ ]% r0 ~5 ?
    }1 s1 f$ r2 s) l" w
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    ( p6 G- ?4 R+ A& Z: _1 |" D' m- e: o8 Y$ c7 Q8 t6 {9 o! ?. s
    来个类,用来从db中获取邮件配置信息5 S* s0 v- ?; B4 a& X
    8 U5 N, D. o, p; [2 u& a7 Q% ^! R
    package com.javacode2018.lesson002.demo18.test4;
    % g+ M9 v# @! V3 m4 r1 `" V
    . X( w  Q' }& Cimport java.util.HashMap;4 Q4 O/ a, B5 u* v* U* O
    import java.util.Map;3 e, {$ f1 e! `4 D" Z  u
    import java.util.UUID;! n; y6 |3 F& T9 [" e
    . c, ?9 O- D4 L7 ?
    public class DbUtil {
    * E1 {1 ?+ Z6 a" a3 \    /**
    6 p' u! e5 y: F     * 模拟从db中获取邮件配置信息
    ( P. Q7 f6 x8 {- }; Z     *
    ' ^0 W# u9 U% |$ h+ v3 u) f! f     * @return
    # K8 T' ]6 X2 N% ^     */7 b% w+ ^/ D- N  ~/ t, u
        public static Map<String, Object> getMailInfoFromDb() {4 a% M, O9 T5 r9 ]
            Map<String, Object> result = new HashMap<>();
    ' |' R( ]: J0 v$ n, Y# T6 j& j! r" T        result.put("mail.username", UUID.randomUUID().toString());8 \$ j3 R7 |# H# n: H' v0 E
            return result;- o1 D4 K, _4 e) B- r
        }) f& g& P4 w$ C( o! F' n
    }
    ; h. D1 @( j/ K: W! Z. {来个spring配置类,扫描加载上面的组件
    , f/ A# K9 x1 T' z
    7 B' z+ T; `* k1 i, cpackage com.javacode2018.lesson002.demo18.test4;
    8 k7 f% ]" C3 ^) f' K0 z
    & y; |, q7 a9 A3 I5 {import org.springframework.context.annotation.ComponentScan;6 r: L1 |) Y3 H4 m
    import org.springframework.context.annotation.Configuration;3 l( E6 Z! g9 g

    * h. g! @. j) a@Configuration; H/ p) g' Z: W3 g. R
    @ComponentScan
    9 m6 P$ t5 g0 E" ]; c2 J! U' ~public class MainConfig4 {" m9 |5 P7 p! |( ~& y; B# q4 @
    }
    $ l& Q: z; {8 l! Q# G* g+ D来个工具类
    , M% Z2 U4 {8 i# v1 X. K5 Q0 S, n& N; g: I3 L) A3 _
    内部有2个方法,如下:' `- z- z5 v" ~# L9 m& g

    & Z# k% q! g- `$ X/ L3 ppackage com.javacode2018.lesson002.demo18.test4;
    , ]) S( F1 ]! _5 m1 F) ?& f" z" q, T3 [- }) z
    import org.springframework.context.support.AbstractApplicationContext;# I# A$ Q  C- K" D& ^5 O
    import org.springframework.core.env.MapPropertySource;! ?( C& @# K% n$ w( ?# E

    ' p9 Q" U; V2 c$ A4 _* y! ~# Oimport java.util.Map;+ d+ _) ^5 U& H' ?% x3 `

    6 V6 |3 q" Q  z# v: n: }public class RefreshConfigUtil {7 ?( i7 r, c" p
        /**
    8 ^% [2 f0 ^9 L3 g     * 模拟改变数据库中都配置信息" F1 }4 H/ L( Q1 A* k* [1 J' k
         */4 ~+ v. ]& k) p2 s
        public static void updateDbConfig(AbstractApplicationContext context) {
    3 Y0 ^3 R1 \1 t$ p' R! T        //更新context中的mailPropertySource配置信息
    ( L. i' a* e9 X8 E        refreshMailPropertySource(context);
    2 G8 N  T  w/ h9 G! \$ o! J/ B6 S  W4 ?. |0 G- [$ j
            //清空BeanRefreshScope中所有bean的缓存
    3 @) a* L) \/ _4 D" R        BeanRefreshScope.getInstance().clean();
    4 h$ r5 Q' d; i+ s' T" z4 G- t2 n/ Z    }6 b" _+ K) i. Q: n8 x! D
    ) E1 k* ?! a9 e
        public static void refreshMailPropertySource(AbstractApplicationContext context) {, T: R  [9 }; G0 w
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();# p. a8 n8 |  S( r: ~' w
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)' H4 V0 Q! e+ ]! U# y9 e9 h, x' e
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    2 E# z: y) I' U$ i        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);: W$ A: C; n  b' d& k
        }
    ; y# i1 Y# @; ^5 d/ e" b% G% g# o2 O$ s
    }$ G% k" R& K' j( K7 ^; @2 g
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    3 w% E. j% j  M% {4 d" Z1 r- r) a2 ?8 [4 f
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。' D$ I& o/ }5 ]! Q! j' F

    ( M& D% \$ P- a( P来个测试用例0 ]9 V0 z3 @$ `  `9 t$ K
    . q# y, K# p/ E1 T- z
    @Test6 A9 Z, `6 K7 A
    public void test4() throws InterruptedException {6 O0 ~7 z5 d- h
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 x* u# X4 t! p" V: i* m( Y    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    # B: w: b- Z# S( v$ \$ g    context.register(MainConfig4.class);- f5 K9 c/ \: |2 c8 x' j2 H
        //刷新mail的配置到Environment
    , g, |# S6 Q6 [- k% ^. _    RefreshConfigUtil.refreshMailPropertySource(context);3 a, K6 d$ x6 k* e/ d8 \
        context.refresh();7 m$ t+ Z5 |1 ^6 \5 ]% I. b# F
    1 M* `5 h1 j% z8 V+ I
        MailService mailService = context.getBean(MailService.class);) [0 _, G7 Q# D& X
        System.out.println("配置未更新的情况下,输出3次");
    0 _3 |: J' w, L    for (int i = 0; i < 3; i++) { //@1: l8 a7 E, ^, L( a; @+ T* m
            System.out.println(mailService);, k0 |' n6 ]* n8 ?) O
            TimeUnit.MILLISECONDS.sleep(200);3 F/ K' |; m) O
        }
    * O2 k' m7 e# g5 T7 h8 H! ~
    - K: ?$ m$ w: `# {    System.out.println("模拟3次更新配置效果");
    * B, G+ y0 b( l; f+ v0 [  F    for (int i = 0; i < 3; i++) { //@2( k! [3 Y* `% r2 [# O' L
            RefreshConfigUtil.updateDbConfig(context); //@3
    * V* V$ E1 F% K4 K        System.out.println(mailService);
    3 C8 Z5 b! f3 V) S        TimeUnit.MILLISECONDS.sleep(200);6 r) D" ]: I3 z( O& T
        }
    , T* u8 e9 r7 q8 I! a6 r( j}7 d, w8 ]0 _& I( q& [/ N) C; W% {2 N
    @1:循环3次,输出mailService的信息
    9 ~! x+ i$ ?  r+ b5 g% s, X/ Q& A
    9 U* c5 N3 i; E! t7 r- l@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    8 i/ Q5 ?5 H# ~- i8 ]) t2 `. ?$ }5 C1 n, v2 s& b; Q
    见证奇迹的时刻,来看效果  ?1 d2 v$ S% ^% u; r5 |5 n5 @
    ; S5 F# a! V0 k
    配置未更新的情况下,输出3次
    ! O# W* d$ e5 SMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}9 w( J+ Z& _' O# B3 h) G
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    8 D- S8 S4 f1 z5 eMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    * g- X3 _" C- k; s2 S模拟3次更新配置效果: M) l' h/ |3 I  W" Y
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}, B& J, ^1 H  G6 B' f7 X( N
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    $ U9 Z) W: M5 L8 P6 R8 TMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    % Z5 K6 K, Z) T: p" J) f上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    ; V' {, [- t% _: [( B9 l; P6 J6 f  m7 r2 a! g/ m& E9 C+ J* R
    小结, |* [1 x- E6 p

    : l2 t$ r8 ]8 J0 r/ y0 R动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    " S9 ^5 T- N$ [6 s" _
      ?; j- Q$ D% Q; g. D+ D有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。2 w8 ]+ x3 c% _+ K

    9 Q( u7 k- H8 v  N$ t2 d$ k! L* P总结9 |9 i: O7 m8 a+ M! ]) y
    : Q) x0 D5 w! i
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!- S  g! C( B; N

    : b9 {' F  d! N& t9 Q/ b* w" `案例源码: f" Y0 D6 j4 i) t) H5 L
    . N2 S  w4 Q+ H+ T
    https://gitee.com/javacode2018/spring-series; W) m) I% K2 |( Z& U
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    & D$ e; [4 x  h9 r2 m/ P' u$ U7 a2 q! H2 v& F
    Spring系列% T! R2 Q$ h% L) P8 u5 ?5 B

    3 g* |) v9 X: a, d% b$ NSpring系列第1篇:为何要学spring?
      t7 [7 k9 n* W" k2 f2 b1 V& V, P  |4 Y$ o0 S+ e
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    & O2 \# z! g' v6 ?1 q& k  F, L
    Spring系列第3篇:Spring容器基本使用及原理. t" ~) m/ |3 A
      r# H4 r3 c/ _+ V7 Y  M
    Spring系列第4篇:xml中bean定义详解(-)9 m0 G8 C1 g% ^  B! I

    & K( V3 y- K7 QSpring系列第5篇:创建bean实例这些方式你们都知道?
    % R" m( v5 v0 \: L' }
    ) R1 m3 l$ l) QSpring系列第6篇:玩转bean scope,避免跳坑里!" a0 J# o$ U0 v9 r
    & L) v" ~* a' v
    Spring系列第7篇:依赖注入之手动注入. {8 |, ^) ^" a* i1 M+ s

    ! n3 L6 K6 b( x8 h$ O( S1 B7 cSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    ' E9 t* O, V7 O8 r2 w4 p- d6 |4 z8 d2 n3 K+ }3 r  s
    Spring系列第9篇:depend-on到底是干什么的?" g3 m) v& j) l' A1 z% G$ Q
    - `0 a7 m) p9 z6 K# g5 Z/ d" F* K
    Spring系列第10篇:primary可以解决什么问题?
    & }+ x2 C. R+ B0 a  ?6 ^- [8 f: ?3 ?- x1 u; E1 S6 A2 T; v5 x
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?: a! f" {, S3 N- ]' X, A9 g9 m

    * A: @' U" x0 \Spring系列第12篇:lazy-init:bean延迟初始化3 B1 K* \* A9 Z$ K  b
    5 a' n) {. f  T. h# V. ^6 S
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    * z" H- T3 I& D7 ]6 ^8 `  a8 d: p* d0 U
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?5 Q* U' {( e5 p3 R1 x) A
    * r% P/ l3 q3 G! p( N& g& b
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    + D/ [/ u1 V7 f9 M) M7 p5 N0 t3 J% y! E6 }& L; r# p0 \
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)/ R& \5 @5 D( L( M
    , `+ \7 g* q) c/ ]" N7 {2 V
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    , Z# L- f: A( [' S# h5 J" D9 u
    + L4 v; Y; `7 J0 ~+ USpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
      D1 @. ~; W& i, A, O# _. o- i2 M3 r. S4 d  C" b; |, f
    Spring系列第18篇:@import详解(bean批量注册)
    & f4 M6 z( |$ i/ W9 B( B8 J
    4 ~, y+ j6 z! A: D- C: Q. B9 eSpring系列第20篇:@Conditional通过条件来控制bean的注册
      P4 Y9 x& @# S; K. A3 k- z" D- s3 f8 {2 x
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)' f" }: \' V! @# c: l3 B/ Q) @

    ) r6 i, w; f. Z! T5 v7 FSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解3 Y& Q) y9 [' [5 [% E
    5 @4 @! o7 B3 m: {7 D) M
    Spring系列第23篇:Bean生命周期详解
    1 d# r7 X0 I( y' L' Y& H+ W' }7 i& Z) U3 _+ u5 M) N! U9 m  I
    Spring系列第24篇:父子容器详解/ V! T' |, F4 i# q% c

    5 a- X$ v# r) Q! \更多好文章
    ; ~* |* x1 V$ W# G
    0 U8 X1 _; X* R1 c1 w/ ~- ]Java高并发系列(共34篇)
    & k& L# ?4 @( n
    / U2 Q3 U) |; S/ P3 p$ ?" ]( w, fMySql高手系列(共27篇)3 i% D0 A1 t5 M+ V8 x: _0 u

    $ X: q% E) E& Q- _Maven高手系列(共10篇)# P8 p, D( l' l# x6 Y
    2 P/ C* C5 T* u# o0 u
    Mybatis系列(共12篇)1 X& C+ M4 |) ~7 x1 D
    + t3 C9 E& H. @
    聊聊db和缓存一致性常见的实现方式
    + B% x  V. Z8 P8 `8 f$ F
    ) J( G7 _9 B1 W" B接口幂等性这么重要,它是什么?怎么实现?$ K9 ~1 o6 T2 Z$ u! ^

    ' s4 E! M0 L6 j) X泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ! T1 Y  {* V" Q1 L+ Z————————————————$ g/ M8 {- H- C, Y
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。, s0 f/ e+ W1 O1 O3 A
    原文链接:https://blog.csdn.net/likun557/article/details/105648757( m" u" h1 _7 e. S
    8 l8 Y9 n: t! o; f

    ! b; v# [9 n' `8 N" y, d. _
    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-9-1 05:51 , Processed in 0.521777 second(s), 50 queries .

    回顶部