QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5537|回复: 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!6 C1 `4 N( h8 L9 F
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    0 U+ W# c; _# p) {1 k- H# W
    " a) ^& p. i8 m' Q面试官:Spring中的@Value用过么,介绍一下
    6 s; J0 P& S9 q) P
    6 z# w$ [( p; [" z( e; n8 i& s我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中6 f1 W+ R# j, G& U5 U1 s4 Y( m2 x

    ( X' R. o- u" k8 f面试官:那就是说@Value的数据来源于配置文件了?
    1 W  D7 p" ]) V0 p
    * V5 |: ?1 ^7 U- j我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置) _! a1 t* ~+ P1 O" i7 W. ^

    ( m+ l5 a- ?% T! D4 V2 q5 K面试官:@Value数据来源还有其他方式么?
    5 T* R/ S/ H0 X( m* U1 W, m* u7 a
    0 u. N% f) T$ r! X8 R我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。3 c2 t2 \5 @% v# Q7 y& v$ E1 ^

    2 V1 n0 Y  A, L4 g) X面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?8 V& D  R: {+ V) B  c( h2 S
    . M9 Z3 o$ z( V
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    ' ?6 R6 g/ y) L7 X4 p' [
    " {+ ?# n) ]0 W+ o; ~9 c4 d6 m面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    6 C& U2 q8 |3 l# g0 i9 o
      a6 |; z& U8 \* {, g) Z8 w' U6 w/ H我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能5 I; p  }0 N* u' m2 W

    4 H9 j  E. ]% ]: S# T. c! C2 K面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    * ~& C4 w+ r# R6 Q/ R/ f; ^( b( t" u) F
    我:嗯。。。这个之前看过一点,不过没有看懂
    # o5 {; Z" p3 D' z- b4 j
    ! Q4 O0 C; N# m8 \& ?, t面试官:没关系,你可以回去了再研究一下;你期望工资多少?& S3 D5 k# v' i" q, S$ Y; v' ^& n

    7 x* b. T9 I& D( Y( ]4 f% q! z. N我:3万吧
    ( V5 h9 S  B7 ^/ l3 n7 ~( Y4 Y6 @* l, o7 u
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?, q  V, M8 n( {9 c, g' j; q
    & C/ |2 V' ?* u: H1 L) F* X2 i. y
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    . w# n& z4 w! x: C6 Z
    9 V( C  m  a. n, @5 _$ q! o面试官:那谢谢你,今天面试就到这里,出门右拐,不送!. {" {, J$ {9 S# E

    ( b6 q' v% t8 R) O, e( N# o% E& i* X% L& O我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    - m' D0 a& f, P5 C- H# H5 i3 }8 L& {6 V6 ~7 S2 Y0 ^) h
    这次面试问题如下6 ~, b: R5 E& v- ~* l

    2 J1 Z  p+ H' r& F@Value的用法
    / ]7 Y1 @8 T7 p+ h, V: G7 w) S0 L4 B3 A/ j6 ?
    @Value数据来源. O2 C7 F+ G6 h6 Z0 a* e* i% [
    % ~3 w0 M/ b  Q6 {9 p) E
    @Value动态刷新的问题
    6 K) R, f; L. S' e& W
    . X# j7 E9 {( e' X* A7 u$ ]下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    9 K- _. @9 s+ ^+ `$ G( @$ P7 i1 Y" m% N$ v
    @Value的用法
    + Y" W& V7 y; }8 w& Z9 M6 J- C( o! Z  V6 v
    系统中需要连接db,连接db有很多配置信息。
    3 W7 t+ |6 }% m7 q) ]) {- D7 ~4 M8 W# z% L% S
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。0 _: I7 e( S3 m5 Q" B: M8 H

    6 o; O8 l9 u: n还有其他的一些配置信息。  l+ G) m" S5 q& ~1 W% k  X

    / `. R/ r) B7 Q我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。2 N1 m/ u7 u, M/ ?+ b6 _+ {6 [; J
    # y9 e* D" X$ n: d3 W) G3 ]3 y$ h
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    , p$ _1 @6 _* _" f' n1 j" W- Q; b0 y9 w7 w3 R( {6 G
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    4 k4 L. Z. {/ F' |; N  w! d$ t, W& w3 O7 X
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。+ x: G! Q0 `! l$ Q' e! ?
    ! \' E6 q& C& U: a
    @Value使用步骤
    ' K) c! ~2 o1 I! b) u, P: I: ]' K+ k3 D, p! k, }
    步骤一:使用@PropertySource注解引入配置文件
    ' S( J6 z# W, t  |& ?0 z) K# j- T0 a
    将@PropertySource放在类上面,如下
    3 O; P0 G9 X6 r, m, H) b3 X: `: _2 y* {7 C& f7 b# v
    @PropertySource({"配置文件路径1","配置文件路径2"...})) m, ]: N- E6 E( P( w( s
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。" l6 A  T8 H9 A1 x% @( ~
    4 f& Y0 R, T* [* l
    如:0 `6 ?" a: u! E1 }4 p& l  F1 ^

    - Q+ R9 V; g+ w, ~% i@Component
    8 X, |+ i5 E3 s@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    8 o. ]: O9 S1 q/ A( }4 p; y# Q  _. Opublic class DbConfig {% ~( t- \( A5 y  C
    }
    ; y  k( }* ^/ |# Z" J! V9 o步骤二:使用@Value注解引用配置文件的值0 ], e0 a3 s6 D1 F
    3 {, j5 y  c3 \% e
    通过@Value引用上面配置文件中的值:
    : X5 Z$ g; u  O% Y. k2 A7 m3 ^$ U# d" M& k/ _$ G
    语法. z; X. k" |+ A$ j) J! O  O' v
    , W6 y( u% F+ ~2 j
    @Value("${配置文件中的key:默认值}")2 I5 z- x2 s! ?$ T8 ~0 P9 z: @
    @Value("${配置文件中的key}")
    0 E3 U- K! k" ?% |如:3 d# K: V/ z( S3 I9 K& l2 I8 V

    ) b! r! i  @) \; i@Value("${password:123}")4 y) E$ W+ G3 a
    上面如果password不存在,将123作为值& v( U& ~5 b6 e6 r) U
    6 v' l; _2 B6 q  ]9 B
    @Value("${password}")8 {* ^0 F* ]. E  F) [7 b. w& X+ U
    上面如果password不存在,值为${password}
    % x: _# K- ?7 k6 Z
    . _( S  M/ w  o假如配置文件如下
    8 y3 V1 @2 N5 r6 y1 s. ?8 G! U
    ) R% A) T, w1 C0 [$ Rjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8- S5 U4 O, l, k7 s; _0 i6 H# _
    jdbc.username=javacode
    # Z4 o4 f+ a( Xjdbc.password=javacode
    4 k# H1 z( f4 J7 r6 g使用方式如下:
    % G  b! u4 j5 j1 u& o" k/ ^- _! `. o' T
    @Value("${jdbc.url}")9 {5 m: d7 t7 O( f: ?$ \
    private String url;  F; i7 m: y$ v- t: f- S1 O6 j/ W

      j' b, @  Z9 U/ E@Value("${jdbc.username}")- [3 D! F$ I, c8 {. z3 V' L
    private String username;" h5 ]/ `6 _. J. Y3 T9 M& p) H
    2 ]* M# a: H/ j4 d
    @Value("${jdbc.password}")
    5 v9 Q2 {3 ?' b9 Q( x( k$ Xprivate String password;
    ! V! \# r3 m: r3 \下面来看案例
    9 u6 V7 J- J( m: d' I& z# u1 k- @5 \1 C6 P0 o
    案例: l# k% e$ i/ o( ], Q

    $ L* M/ ?/ D: B6 X' Q2 J% F来个配置文件db.properties! X7 ^6 }/ s" p5 `* h! I% m* v# T; `

    ( A5 i7 I6 c3 R4 C, mjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8( y9 R; g. D  Q8 q
    jdbc.username=javacode
    / i0 d9 C  A- d9 Fjdbc.password=javacode
    * f+ l5 l2 n0 E: y* G来个配置类,使用@PropertySource引入上面的配置文件/ J9 C; G; e0 {- F! Q0 p. X5 t

    4 G: T2 K! e: ~; n, D" c6 Apackage com.javacode2018.lesson002.demo18.test1;
    ' D: \* {; \/ z1 Q6 l4 a
      ]  [: a8 t3 [import org.springframework.beans.factory.annotation.Configurable;
    : c  B+ V. S; w$ S- Q; E1 [$ R) Y+ B+ Aimport org.springframework.context.annotation.ComponentScan;
    6 Z: Y' M7 T, ]import org.springframework.context.annotation.PropertySource;4 C/ F8 S) [2 b/ F
    ; \* w3 R3 g( K9 g# u" s! C# m
    @Configurable% u, `  g2 W, r+ U& K
    @ComponentScan  o. q! e! h! f; p/ T! u$ [6 ^- r
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})% f3 X  G. S' F4 T3 d# y
    public class MainConfig1 {! e7 O7 ]  K3 q- d7 X
    }
    9 l8 `, m, Z5 e8 m4 f2 \( I来个类,使用@Value来使用配置文件中的信息
    9 o2 [3 D" L/ m- F- C/ ~- ?) t& _
    # O/ R3 e  M, Cpackage com.javacode2018.lesson002.demo18.test1;) x8 |: P+ [& R

    ( o+ W8 X+ q( C! j. q7 O8 H% ]import org.springframework.beans.factory.annotation.Value;) O# Z. ]4 N, Q; W% E5 I6 I$ T
    import org.springframework.stereotype.Component;" r! w$ a4 O% W  I8 O# s2 s
    6 _) B( a& Z6 @% t
    @Component
    $ @/ E+ Y/ p9 y  Z1 v0 Dpublic class DbConfig {8 u; _# }$ u: _2 Q
    / k" G# Y/ {' q3 u  _
        @Value("${jdbc.url}"), t* j. a& J3 o) W' ~
        private String url;
    % G% w! a  y0 l' Y
    9 a+ V2 B5 K: o+ e/ x0 ?    @Value("${jdbc.username}")
    : P1 H5 {9 H: i0 R) V    private String username;& p6 I0 Y+ H4 `: H9 Y. J4 I

    4 `- |; Y5 [& f3 B5 E    @Value("${jdbc.password}")7 O$ W& @* n; S& e5 A
        private String password;
    ! u0 A% k. W/ L8 P& B5 g) ]* K# U$ x9 |, A$ ^$ R- `: U
        public String getUrl() {' @, ~3 G3 h. ~4 s$ j
            return url;1 z$ r5 V7 \' F' y: X
        }* l* A4 Y5 ?) i$ c% H
    0 {% b1 `! {1 e) a4 m
        public void setUrl(String url) {5 O" Z' f, _% z
            this.url = url;7 q2 i/ h: }! r
        }! L/ F/ l) {+ D: s" @0 Z
    , o1 a5 T" b4 n5 Z# ]5 N; m% l$ }
        public String getUsername() {! ~9 k. d( M! d  \
            return username;
    ) m) {/ }9 J6 v    }! s' e2 x2 x1 c+ a
    2 G/ M# ]% \; v7 f, w& K2 t! R
        public void setUsername(String username) {6 b5 c' E0 Y3 J! @6 ~
            this.username = username;
    8 v$ p/ M) T3 Q8 W; h! [! {2 L    }
    " ~4 I0 N/ r( S$ n1 `8 r3 H, x1 E* Y
        public String getPassword() {& N( s2 \. X8 s' A2 k. |7 h. k
            return password;  q) W/ _/ k, L% @  L) G1 y, S( L
        }
    ( p. v  @5 e! \7 P2 D
    : I9 y& e) Z0 G3 O0 B& M+ |% z    public void setPassword(String password) {# q$ F3 ~# `' A0 G
            this.password = password;- E7 A8 b& Z8 e  x) O9 n
        }
    9 {& D2 H: o: k/ ?& S  f2 n& J: `4 c
    * a9 K5 y9 J# @5 S& J) B' O) H    @Override
    : t& y4 q+ F$ X& |    public String toString() {( q2 ~+ b6 Q( J- L
            return "DbConfig{" +: J% n; ?( x  j' U" @+ l
                    "url='" + url + '\'' +/ E- g; y( T! |  R' w+ `
                    ", username='" + username + '\'' +
    % t! g3 q6 h6 `3 ~' U                ", password='" + password + '\'' +
    * T7 N7 ^% q1 V& d# h. q2 ]" ?                '}';! N8 o6 ]& z1 K# T( c
        }
    : X+ K. r& T& w% u  o+ O- |% S}
    - \* g% L: T* U' l' N- M上面重点在于注解@Value注解,注意@Value注解中的7 B; ~7 P6 ~& v, e/ X; D: r+ c/ m/ x+ G
    + t# \: s5 u! L* ^  n6 P/ [, _
    来个测试用例4 f* \5 C- T* v, ^
    - U' ~# A- V9 f! N3 X6 I4 f7 q
    package com.javacode2018.lesson002.demo18;5 |% u( r4 ^) V. G: H

    - I5 f# L) ]) o" G2 j4 mimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    , s* R9 W/ S7 K0 r: Bimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    0 s( v* U% J3 U9 ~; P& Gimport org.junit.Test;
    ! T; {+ J( S" }import org.springframework.context.annotation.AnnotationConfigApplicationContext;1 w" a2 N5 c7 v3 X8 E( j

    : P/ ^9 C, P. v) U  ]public class ValueTest {  F& o( m3 E# |

    . g: h0 L& t% |; u6 D. s    @Test
    4 X4 r, {# U+ o    public void test1() {
    ) g- \( u' u8 j! A        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    9 Z* R8 [9 ^) R. Z        context.register(MainConfig1.class);
    ) H9 s$ ~- n1 f! L, C        context.refresh();
    . N) p/ a1 t. V9 i/ U# m& l. Q5 G" K: @
            DbConfig dbConfig = context.getBean(DbConfig.class);6 a9 j& S+ p. }2 |$ n+ l8 K
            System.out.println(dbConfig);$ x7 }& U% j" |  k6 k8 }
        }
    6 B! v1 T4 _1 R  Q! U}& [2 w/ [0 p5 k+ O5 ?0 o
    运行输出
    ; a2 J; X/ w/ Q" I; |6 R  P0 A. A
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    6 h: l: x+ w/ Y/ v上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。' W* s: e: ^: p- t$ g3 q$ `

    7 R7 W! x- h+ K6 n# s@Value数据来源
    & v, l- y, _, l( g$ E, y- R( ~7 W
    9 c$ w+ k2 H" p+ h$ [: W通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。+ ^  c7 d/ z) X" m$ L

    " J' H+ K" C8 T$ K0 n& Z我们需要先了解一下@Value中数据来源于spring的什么地方。1 g; ?6 y1 C" _+ A2 k

    ' v, J1 }. S+ S9 ?/ y$ T' D, \spring中有个类" ~( U# }1 ~% @
      l6 B% Z- u' K$ X3 _: ?: E+ q) C( j
    org.springframework.core.env.PropertySource
    1 L$ p4 I& W! u4 p( w0 I* }可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    5 h9 l' m0 f/ r7 \3 G5 G  D( K. @7 n" g
    内部有个方法:) K. i9 m: h" P5 J$ ]

    ( b+ a' C9 r& q' npublic abstract Object getProperty(String name);! `6 C# d, C* Z% [& t+ o
    通过name获取对应的配置信息。1 ]# ~+ y& S2 \
    & C: r9 t; c+ y3 U& ]* @. T+ d
    系统有个比较重要的接口
      [5 F' `' w" |! ^& H- ?: P  e/ S. _1 M2 T% K' \& t3 G
    org.springframework.core.env.Environment8 h0 k- e9 R# c4 g% I$ f7 }
    用来表示环境配置信息,这个接口有几个方法比较重要
    " E. }+ x# |- E) p
    4 D4 E2 S8 D  O3 _( z$ ]String resolvePlaceholders(String text);& q( ?1 b/ [" H2 g2 I& Q
    MutablePropertySources getPropertySources();
    * D. L; \$ V, y7 d1 rresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。1 \1 R+ F+ F) U; n, a
    ) R# ]8 H4 g; z1 [
    getPropertySources返回MutablePropertySources对象,来看一下这个类/ q* ]# {: ?9 A+ [& _& [5 D

    3 O( p3 F* ?3 Z" g1 y3 z; ipublic class MutablePropertySources implements PropertySources {! C( t* _2 g; J6 \1 z% |

    $ d+ @7 V3 R+ u" T8 l* J  c    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();/ P  ]- _7 G1 ^& l/ W
    9 ~, M) `! D1 _' `- N
    }
    5 }+ c. h) Q  w; i- q0 `内部包含一个propertySourceList列表。# t" o! x2 E" I2 \8 Y2 h
    , y1 T3 Q" n. T' W# C
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。1 C$ R: Q: Y7 @* g: K$ E3 h! x! \

    , ], d# E9 a; k5 C) A大家可以捋一下,最终解析@Value的过程:$ M% M9 o6 h9 i" I) E

    # {! u  w1 i) Y  v6 W  ~1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    ! o5 `/ r; Z0 T( L, W! ~7 u4 [: ~3 A2. Environment内部会访问MutablePropertySources来解析
    , c- i) R: c3 O8 B3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值5 [5 [; S- j8 y
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。$ N1 g: {$ ?4 c6 r
    5 i6 d0 h% c7 R, |
    下面我们就按照这个思路来一个。3 i, X2 S6 E# e
    / E& i% @2 H. b
    来个邮件配置信息类,内部使用@Value注入邮件配置信息1 h0 u  I! I1 R/ |. \6 k: E

    % ]9 {7 d6 C& }package com.javacode2018.lesson002.demo18.test2;
    0 z! s& ^1 T3 T; i5 k. s% |/ B- j" }4 G: {# j5 K- P8 x
    import org.springframework.beans.factory.annotation.Value;, w2 t4 B) p8 R
    import org.springframework.stereotype.Component;
    $ q! A& o4 G# Q) ~2 v
    0 K9 Z1 f: k3 x9 e/**7 O' ~. s, J2 y" V- T2 j
    * 邮件配置信息- }: b+ b, t/ S1 d! A; ]9 V
    */& K6 }; i" q" H8 m1 m
    @Component7 o; f- b" I, T$ T1 E6 i
    public class MailConfig {
    9 u( i  t6 H  a
    ! V2 H7 @2 B! o+ K; z& y    @Value("${mail.host}")
    $ \6 U! Z! C4 W; R  I$ L' V    private String host;$ J$ z% |$ g2 ^$ z1 u

    9 B) L* l: S7 o$ c3 v. Y% i( z    @Value("${mail.username}")) T: E) e9 \8 V" X7 k3 f4 |
        private String username;
    8 B  o3 U" L3 \2 E
    0 a% t. t! U, k! d, H$ l    @Value("${mail.password}")" p# _9 t* u  D
        private String password;1 f# ?, ^! Q1 V$ F, Z( Z

    " m/ b0 d5 y6 u5 L    public String getHost() {2 D4 }. B) [2 g- V2 I" R% E1 j+ `
            return host;! V5 b0 E% z0 @
        }
    " B) P/ G- N" i8 g# A( W
    : ]3 o8 ~, L4 l2 F- x: W    public void setHost(String host) {
    - `, s3 f+ Y" _0 f/ W, V        this.host = host;
    8 o. z6 ?8 C' k' @    }
    * m% K  F/ `  |& Z- D" l; V* |/ Q4 v  w' v: i- C* D
        public String getUsername() {( U/ ^* l1 O; ?
            return username;; n8 v8 k4 P) _  |0 G
        }" R8 x: T1 C# _& R

    9 [4 y, u& p/ b  t% q7 N    public void setUsername(String username) {) A% X0 H/ U/ f
            this.username = username;
    ' U5 W( B/ j9 x$ r# G) P    }
    : l5 R- O1 {7 d1 s! d4 O3 l
    1 Y  X* J+ W8 ?0 T7 I    public String getPassword() {2 o& Z8 J8 v1 W* }# T
            return password;
    ! N& p# ?, N  V$ O1 X! A- G$ ], C    }
    ; y' H% q0 V1 V& W! S& z- W: f4 y- w' T. X+ J
        public void setPassword(String password) {/ g" [/ F# s  e0 N$ C0 e) k+ O% i, |
            this.password = password;# e: [( D) i* c% k6 ~
        }
    * c& B: i% }5 g1 W# J9 E& O6 g! d/ ^  D% l' h
        @Override
    $ e$ D7 F; Y- P    public String toString() {
    ( a6 L0 d4 O- c& m- a0 K  M        return "MailConfig{" +
    ! u, u: D$ s( u% ?9 b; k* N; o                "host='" + host + '\'' +. \1 i! ~/ V  A. r; I; {4 I
                    ", username='" + username + '\'' +  T$ K+ P2 E8 l7 b* }
                    ", password='" + password + '\'' +8 p  o1 N3 K4 `# A
                    '}';$ t6 J! I7 q1 V/ z5 ~6 f2 z% C) c
        }9 |8 `2 ]& W0 @- G0 a1 ^9 W
    }
    5 c! k4 \& J" N3 I& N6 N* h再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中. L7 A4 {4 ^+ Q9 a! Y" e+ U4 L
    & B7 [# o* z% P1 ]" C
    package com.javacode2018.lesson002.demo18.test2;
    # p7 P  I- `5 m! G9 ~# J% _  Y- e
    4 ]6 m8 ~6 f5 y: E) N0 z' fimport java.util.HashMap;
    / G6 ]5 G9 s/ r* m. {  g: }4 I- cimport java.util.Map;
    ) Y; T7 l# i/ a- ]) H- y
    0 i# \5 a* i7 d8 ]' dpublic class DbUtil {
    - N$ N" h' }8 d5 Z' U; B+ y    /**- F: A& o3 S  R. j. z
         * 模拟从db中获取邮件配置信息
      }1 \9 ]2 o. P; n     *
      T/ w9 R5 }: k5 o( J' `6 D     * @return% H0 B0 X( T5 Z$ ]
         */. d. W/ X$ B! q: k# U
        public static Map<String, Object> getMailInfoFromDb() {0 e: j0 B/ V& W% e! H- [3 _
            Map<String, Object> result = new HashMap<>();
    , F, r$ g7 i& E4 \1 H9 A        result.put("mail.host", "smtp.qq.com");
    7 @" y  g" X* l$ l  F        result.put("mail.username", "路人");
    5 B+ v( b2 ?5 b0 @        result.put("mail.password", "123");
    : `8 G5 h! d4 _& r        return result;
    : s8 G5 S9 s. r0 E9 p: _" d; U3 N1 O    }) E( g9 O0 I0 t$ \! s& l
    }/ J7 D+ F2 T8 x' G
    来个spring配置类/ h: W' ]$ k8 P6 R$ l# \, L$ t

    9 F! w3 N' x" F7 L. V  ]+ Lpackage com.javacode2018.lesson002.demo18.test2;
    ! O1 p# ~( w: G' K& [  s' N2 L+ h3 F+ e2 \+ \+ @, h
    import org.springframework.context.annotation.ComponentScan;0 ~6 h% i. V. ^; r5 E1 d
    import org.springframework.context.annotation.Configuration;0 Q% U" @) i. O; w9 B

    0 T8 Y- c, ~; z7 q" ~@Configuration
    8 c) F8 |+ w: K@ComponentScan2 A% [: Q( J7 \1 @# e# W
    public class MainConfig2 {5 h& U9 Y9 ^+ J, C/ V
    }( l" u; c$ O( x
    下面是重点代码
      g6 Z; g6 n. U5 o
    8 p- H6 u  O6 T! l@Test1 `' Q: l5 W! J! T3 A
    public void test2() {7 ?; |2 h; S+ l6 a6 ^3 I  Z; p
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();8 W" p- K) r' R, C$ Q. L6 Q

    ; t1 e/ M6 k# b5 m& N    /*下面这段是关键 start*/' ?0 S; ]/ r1 I. v& U
        //模拟从db中获取配置信息) L1 R' J; a4 `! e
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();4 |$ f2 T9 P/ C8 p
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    9 Q4 Z  j7 Z0 F: |; g    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);1 C' Y$ Y( i: f0 \: i4 a& F
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    % |1 |$ p+ S. c$ B; G    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);* y. p6 T; x# K) p# R' a
        /*上面这段是关键 end*/5 N) G& G' k7 V' T& T9 F1 P3 U
    9 t) ]) c! a0 a4 l
        context.register(MainConfig2.class);) V# @+ t; _" v5 d
        context.refresh();
    " z2 X0 J' [8 A. G/ v! }5 {; G    MailConfig mailConfig = context.getBean(MailConfig.class);
    - ?' I- j; H" d, k! i6 g1 A    System.out.println(mailConfig);/ v8 ^" q( Q! Y( I0 c4 c0 O" r
    }
    % N8 J0 Z! j& A  O注释比较详细,就不详细解释了。4 O) k3 w7 a  S
    ! d9 A4 \  B1 h8 I6 b1 {
    直接运行,看效果
    9 ~) A- \6 S8 i8 R# e: Z# y3 Z" ~  b" d) [
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    0 ^& g" i2 Z# G0 K4 Q& p有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。- B7 Q8 H: m' E; E6 T2 L7 I9 G6 R! B
    % H0 Z9 L& |/ q$ @, |. Q
    上面重点是下面这段代码,大家需要理解
    % {4 A1 P! ?. ~% u2 Z: Z4 ]: N5 Z' s% j8 B# y
    /*下面这段是关键 start*/. V, g7 W% `- f  P. S8 h& g
    //模拟从db中获取配置信息+ x# o4 Z' ?8 a
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();  O) c) q: K+ m/ ]) x% @3 `
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    % d0 L* o9 i$ B1 Q! yMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # _; W: M! S/ ?' A9 D//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    6 f! b7 w+ W4 l, J$ W9 Mcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    & r; v) g# f0 H/*上面这段是关键 end*/& @/ a# V2 T. f% [1 F% I  S
    咱们继续看下一个问题" }' g) J+ f- T) o2 F

    1 S1 g: |$ q) t. N6 W1 ]5 V如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。  t  }& {+ x7 B5 u3 A* D& l3 _
    8 z* p" y9 e: L' L( r+ i6 N
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    + i: e8 E/ [2 r( R" e6 A
    ( X6 m* x" B2 ]. e4 C实现@Value动态刷新
    $ q. L. Y& a) n) V5 m) ~. Z5 y  @3 W) v7 K" X# c6 R% k& \/ a
    先了解一个知识点
    " R' L9 @& w: e, I, K0 N8 v! ?: B. h* q* f' s3 A8 ]5 p( \& }+ O: Z
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    : L  c8 }; }& B5 y5 y+ f; T; C8 t. Y4 m! X
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解- t9 I5 y' b! l( C- ?
    5 f! W+ A+ q8 `- s8 C4 D
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    # i' u; [  B$ X5 N: f5 x! `
    ( z$ Y/ T& c' }ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    ; Z% k$ s, D9 l3 D这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中- d$ f! d9 J! q. S
    ) r/ ?% A4 Z3 `1 L: K
    public enum ScopedProxyMode {
    & r. N8 H, L2 n7 r- j- v    DEFAULT,4 O2 h  b% C8 M4 W: m% I5 v, \& p( [
        NO,, u" p  W/ F/ }3 c% w# j
        INTERFACES,9 P# w" k9 _% ^& d3 E3 T
        TARGET_CLASS;
    - H% Q, n( ~7 ~) q- x! H1 B% y4 h+ P}
    6 e* \: m5 e, _  l. s3 [* C前面3个,不讲了,直接讲最后一个值是干什么的。
    . R) q% \# F# c3 |4 h/ ]1 \& |% F4 j( t- Q# a2 S, W4 O
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    ; t3 J/ v! t! L$ B2 p% H  d7 Y: T5 T. ]. w& y  t, S; I( l
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    0 h) k' g9 l# D/ E  o5 e7 J; }' }# m. g& \: c
    自定义一个bean作用域的注解
    3 f+ N  A9 l: N$ e3 N7 Q. w
    3 ^" t& |+ N+ ~2 S$ j" G% W) Ipackage com.javacode2018.lesson002.demo18.test3;
    2 M* O6 Q1 i4 C" H0 R! v2 U, O3 A+ c( z! ~) q
    import org.springframework.context.annotation.Scope;* z, v) P/ }' M2 A' T
    import org.springframework.context.annotation.ScopedProxyMode;2 p6 j/ r+ i" P0 }

    9 H1 S/ k. h$ N1 I* {$ c) V' Fimport java.lang.annotation.*;
    / D/ X; p1 x& T" `
    3 V3 A, O9 _% A; o( [0 }6 w@Target({ElementType.TYPE, ElementType.METHOD})
    1 Z( x, ~! _5 c0 i+ k( r  L) s@Retention(RetentionPolicy.RUNTIME)/ Y8 q% m6 F) W4 T  s
    @Documented
    & i+ q* V( G7 S) Y0 ~@Scope(BeanMyScope.SCOPE_MY) //@1! o  V. k9 S! ?9 t/ [* X! _' s
    public @interface MyScope {! ]) a! H8 q# x2 Q
        /**7 C6 \% r/ b3 S( C2 ?
         * @see Scope#proxyMode()
    # q7 p( O; f  E& L     */
    $ b- @0 a# \0 L# N" G    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    ' T# M) z9 ]- n0 g9 D7 d* l' F$ B}! O! Q; |! j4 o: E! S# [- e
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。0 ]7 p' y4 r* M+ @7 A9 T
    / W: |! `8 U3 @% g1 i; S5 o( y
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    / t, D1 @( ^$ n0 Q& j( i1 }0 j6 _+ C9 h* \* ]+ ~) @7 a- t* Y
    @MyScope注解对应的Scope实现如下6 x5 j* R; L, k3 Q5 y3 K1 }
    - |" y) n7 g8 G# |, y+ E& C& y
    package com.javacode2018.lesson002.demo18.test3;
    5 }# e4 x' f& e( @2 ]* V5 D$ P. k, N( @2 n& c- w
    import org.springframework.beans.factory.ObjectFactory;  C8 ?8 t# L  n1 ?- X( Y8 }
    import org.springframework.beans.factory.config.Scope;$ @$ X% k4 K: B9 y1 Z6 z
    import org.springframework.lang.Nullable;
    / k+ [' A7 @7 M. x8 r% O
    $ J1 C/ l& U2 }7 F7 k0 k; w0 v0 a/**& v, P9 ^( T8 b$ G; @
    * @see MyScope 作用域的实现
    8 J4 `; o5 S( U2 l6 {* \! a' n */3 k9 n: }: C7 b1 }2 V
    public class BeanMyScope implements Scope {! P+ ^# Y/ O, S" w' n8 G  e2 R

    # ]! C. ~, p' r$ q( j+ L- r    public static final String SCOPE_MY = "my"; //@1
    5 ^7 v- G8 z5 ^" u1 G! }
    : R+ N# C# I) \' Z' ^/ R7 i    @Override/ \- |0 z) ?) p4 o1 ^% P
        public Object get(String name, ObjectFactory<?> objectFactory) { : R6 P. y/ n* E; U0 X4 A
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    : T( v. l5 g! u        return objectFactory.getObject(); //@3# c5 x. b# Z' T( R1 |5 J
        }
    5 g/ L  v5 `7 |7 b9 T# a  i0 r/ B; k4 W; L- S
        @Nullable5 w  F3 }, z8 ~% k% ^
        @Override
    ) q% m% [3 l' M( j; M. i    public Object remove(String name) {& Z5 q7 G3 I8 \% a; H8 F, U2 w
            return null;
    ! }, I! `  l9 _- J    }6 {% B2 b- S4 N

    & ^3 _9 [* t- S) {# s" s6 t    @Override
    4 Y! f6 E) l7 K+ l1 ^4 C    public void registerDestructionCallback(String name, Runnable callback) {. J: ^8 f- D- D0 K! l  G# B7 z

    # t0 S9 x# A0 l, q# J    }8 w  ]' p4 \8 Q; C1 _: {
    ! Q3 Y8 [" z+ t* r
        @Nullable
    1 e4 R- y! S  Y- ]    @Override7 g0 s4 `+ y) n' h
        public Object resolveContextualObject(String key) {
    2 v/ U5 t& }4 H# v        return null;+ t2 C1 ~$ q2 ~- \0 g8 O
        }
    $ C; R2 C6 \5 }" M  e1 ]$ x+ g, I- d8 y& @' {4 v
        @Nullable
    8 |. N  |7 Z9 t+ w    @Override
    , _) d" G0 q4 f: M, x* M$ r    public String getConversationId() {
    ) c, C' w9 F# K        return null;
    : b' T9 ~& x9 C    }3 p0 W/ |0 e3 @5 g
    }/ o2 @8 S7 F2 A+ f2 S" i" ]2 C
    @1:定义了一个常量,作为作用域的值
    7 S# ~2 Z4 w6 S% s* x  F
    : f$ s0 z+ p! X0 Y' a, }@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果/ k; O% C: p! S

    # G) b% ~- _5 z- {# Q, J& J9 i8 d@3:通过objectFactory.getObject()获取bean实例返回。
    5 W1 R4 ]' |+ G# f* ]! n- f0 U; o5 b9 A. d4 D6 o% r8 y. [4 q7 M
    下面来创建个类,作用域为上面自定义的作用域
    - b5 W) J/ Q. t" A! K
    , Y0 O5 J6 a# L% a0 Ypackage com.javacode2018.lesson002.demo18.test3;2 H+ ]/ {7 Q' M5 p: ]
    1 T" s5 O3 |+ ^  a- K& v$ n! x
    import org.springframework.stereotype.Component;; n8 e; U2 o1 s9 u: y

    2 Z: y/ k- `+ k) M) o5 Uimport java.util.UUID;, F+ a0 x7 d) u
    3 a3 n4 J% j2 f6 W
    @Component
    4 C* `/ ]. W, C9 d7 f: _4 G@MyScope //@1 7 U$ K9 j9 B6 e
    public class User {
    & N9 W. J; c5 n: n3 R( Y
    ; D3 G8 S. t2 X# z8 x% E5 n7 i    private String username;0 E/ r% t7 ~" t3 z9 _5 @& Z0 {2 y

    ) P' Z4 I5 E8 V8 ?    public User() {
    ) Z# K7 }  [: N; j, ^$ P% l        System.out.println("---------创建User对象" + this); //@26 A" v" Q9 T: Z7 U
            this.username = UUID.randomUUID().toString(); //@3
    9 ^5 }* O1 P5 V% ?0 ?    }
    % d' p8 l, M! A. s' W' G( C% c6 F2 ^, i% b3 k4 l
        public String getUsername() {! M8 {( _1 k! s+ d
            return username;4 L) B8 L3 g/ A; [% v7 o
        }
    9 O: n6 b3 ~0 D1 W6 ^  o/ k# I" W8 y* D. R5 P% e7 {) U, _
        public void setUsername(String username) {
    " w6 r; \+ c3 z' G. ~8 ~        this.username = username;, P9 T  N9 `4 Z# d
        }0 M, c5 U3 `, z6 @4 e. X( E

      ?: ?" M- j% u5 C3 K}
    5 r- m) `4 B! y+ Z# Q2 _1 S# [4 U@1:使用了自定义的作用域@MyScope
    ( a  N8 p- s) y- ^* Z9 _0 b. \. j8 ^% v- o$ |5 |# a, e
    @2:构造函数中输出一行日志
    0 v0 a& @5 I2 H2 x6 L* c; z/ W. t1 G, Y7 X4 a4 l! x+ o  S% k: @
    @3:给username赋值,通过uuid随机生成了一个- E, t$ z( v5 d( z

    ! v: M( S2 F* b! o. g2 C8 Q来个spring配置类,加载上面@Compontent标注的组件( u( n" a3 \$ |

    / d! v; O5 p: L7 s! Tpackage com.javacode2018.lesson002.demo18.test3;6 G  s( o; _( J4 d. {
    + [! K' @) R/ B% R" C. r; @2 g) V
    import org.springframework.context.annotation.ComponentScan;/ p) T: W$ S* T- U. h' c  R2 U/ c) d
    import org.springframework.context.annotation.Configuration;% ~5 |& z7 f, x% Q

    % p/ {" x& n" \6 k% d@ComponentScan
    : f6 h0 g& t6 _8 {. N, |5 k@Configuration
    . z' M9 z7 M) k* O! t* q( mpublic class MainConfig3 {
    ! e. D7 {/ Q- z) R2 L9 n: h& K- i" L}
    0 V2 D- X: w7 c7 ]- q6 C下面重点来了,测试用例
    4 ]+ z: L  d8 x6 }+ @4 w
    ' x0 e" K. i6 ?9 G7 F@Test
    $ t! o5 C- n* S0 z; @public void test3() throws InterruptedException {% `! L/ E5 x  I# v% X
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      ?  i9 E" T$ p    //将自定义作用域注册到spring容器中& A5 P9 `$ b4 a+ m6 t5 R
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    # ^" ]; p8 w, V) D    context.register(MainConfig3.class);6 p5 \; c- r! N% S) B
        context.refresh();* K% B/ h- K2 ^! h" _5 Q( O& [
    " E# k- x. g. K: e3 y# q8 n: K0 w
        System.out.println("从容器中获取User对象");
    & g8 }5 g% ~# T' f  M; {2 d    User user = context.getBean(User.class); //@2: A' J& X# P  N+ N0 }# [
        System.out.println("user对象的class为:" + user.getClass()); //@3
    7 U8 ~$ j9 u+ ~+ V- u: x# e7 @% ]8 z* D0 U, `
        System.out.println("多次调用user的getUsername感受一下效果\n");8 n6 Z/ h( K7 h$ a* r5 m) N
        for (int i = 1; i <= 3; i++) {& d& d* [' s: f
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));) F8 h, E5 o$ v
            System.out.println(user.getUsername());! A7 F6 }5 K- Y' Y; Y: h
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    % C( p7 ^4 s/ Y  c* D( P4 v: X: g! |9 f    }
    % H/ ]& V2 F1 Z* P}( u4 ^% a. G0 F( ?
    @1:将自定义作用域注册到spring容器中
    ; F+ D( l; x$ e8 |3 _0 g
    % i3 \) P  Q9 D; C@2:从容器中获取User对应的bean
    0 {" ]% F9 W- `% r
    ' g1 v! w5 X# J+ N; Z/ ?. D2 V$ @@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的! I: X6 _8 ~$ e' I/ t* t' x* Q; D) `
    5 ~& T) t$ R8 F% w
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。# M& ?! h0 A; q* U* W2 b) f

    : Q/ f* x5 b: u4 y4 z1 I' v见证奇迹的时候到了,运行输出. A2 N% c# h- W' C. }% m! e; r& ^- P+ s7 e
    % T: V( P9 G( |5 Q/ a4 E% ?
    从容器中获取User对象
    2 }9 X2 c( |- d  Y4 |user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331276 S  ]. R) [9 b( e  g" Z. J4 g
    多次调用user的getUsername感受一下效果# G  C: @7 ~# M" b1 b: H4 E2 A3 n

    # ^9 ^# ?8 z7 `/ y0 B********; e  r. e0 M; n. G: n! n1 T
    第1次开始调用getUsername
    5 u9 t, h6 ~/ p2 `  ?BeanMyScope >>>>>>>>> get:scopedTarget.user
    . i: i* J, k1 j5 D) f! e  [---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    : {$ _0 S6 S) @  Q: o! e7b41aa80-7569-4072-9d40-ec9bfb92f438
    ' T- o0 i) A: {3 K1 x' w/ y7 D, y  ]第1次调用getUsername结束
    / g2 F  ?. i& A( @2 H# l# H$ A! W********
    6 T0 E4 X# m+ c; U' [$ E: j
    * \4 O" K# [: C3 Y********
    ! V, R: C, O8 V/ s1 b$ I第2次开始调用getUsername% |6 b2 w# N) q' K: b' X# ]
    BeanMyScope >>>>>>>>> get:scopedTarget.user& B+ S6 c* `/ t( j3 D8 Z& f
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b7 t4 n- |4 [0 w. l; K8 M
    01d67154-95f6-44bb-93ab-05a34abdf51f
    2 w! u$ Z* d$ l4 P, J8 z第2次调用getUsername结束1 ~) E7 l/ Q9 _% }# W
    ********% M1 H, d# S# m1 f  {. k
    * T! W3 X5 r; C* v3 f- a0 y
    ********
    # K( ^- ?% s* u3 W第3次开始调用getUsername
    3 I' B4 c: W9 B8 K7 eBeanMyScope >>>>>>>>> get:scopedTarget.user7 N+ {5 U6 K: g, I  y! S  C
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    . f# J! |4 f& y9 v/ G4 Y76d0e86f-8331-4303-aac7-4acce0b258b8: t2 c; c7 H0 s1 t. ?
    第3次调用getUsername结束
    ( s2 j2 E2 m( T********7 h2 O) Z: Y& L
    从输出的前2行可以看出:
    3 u. S4 w6 H1 B4 s& z
    7 w0 p9 D4 {1 Z调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    0 _" @+ N' @/ O6 v" s, ?
    . ^/ w; v% B% t  I8 a7 e( `第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    ' Y7 Q2 y2 G8 M' V7 i, ?9 c* ^+ O: P. c" {+ ^
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。+ m9 k. U! E& N9 g9 }
    0 a# F0 x+ w5 Q
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。# s$ k2 o7 O; y1 l' P2 |  y9 G

    " K0 A& F7 `2 t7 w' H% c- h动态刷新@Value具体实现( R4 `5 l+ m3 n

    5 n! t8 m3 q+ F那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。, f2 I6 d. w) S7 G* x4 H

    % }5 I& @7 `5 D. x" w先来自定义一个Scope:RefreshScope/ O0 l) j  g) A) s' D

    ; n# `! l; `& [% V3 Dpackage com.javacode2018.lesson002.demo18.test4;# E7 ]* z1 d5 F: F: F5 Z

    5 o: v& ~- c# v7 d) b, X9 F) iimport org.springframework.context.annotation.Scope;* j; O4 @/ V! F8 t7 b
    import org.springframework.context.annotation.ScopedProxyMode;: {: p8 p) m) o

    5 Q" e( F- A/ c4 himport java.lang.annotation.*;
    ! l$ @; c/ l7 P' ]2 y4 p% S: j- a4 c, g& u% r5 e& C1 [
    @Target({ElementType.TYPE, ElementType.METHOD})
    ( @$ G$ m5 W# E- b. C8 w@Retention(RetentionPolicy.RUNTIME)
    * `8 b5 D$ a' o9 e& [& W. J@Scope(BeanRefreshScope.SCOPE_REFRESH)* Q; r/ r$ A6 \* e# w/ P
    @Documented
    , x: m3 A6 {% K' U. ^public @interface RefreshScope {, p; W: w3 j' _4 s0 s" I
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    / E! x* q% h* V6 ]* [}5 R8 r, ]3 R1 b' p0 A: G  b" ?- E. M* f
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    ' A% O% F/ A6 y9 o: I
    ; u& ^6 G% S: c5 u8 t@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    + [8 }, _7 E; A. S( O' ]" B( ^9 ?; [8 l3 ^, L6 J; I
    这个自定义Scope对应的解析类3 E7 {: f' D8 \  @" @& |' s2 D

    3 G& m8 |1 M- j$ m下面类中有几个无关的方法去掉了,可以忽略
    ( f$ W6 a' A. l7 X" ?. K7 |
    : h; x! a  V7 K* D% M/ W) xpackage com.javacode2018.lesson002.demo18.test4;
    & I* y; v5 s* |% h2 c
    % o/ s' A/ D) _% C; o. n2 w' O
    ' R6 C) H0 Q8 C; I. L5 Qimport org.springframework.beans.factory.ObjectFactory;6 K  L! ?& ]- U7 _1 g9 E
    import org.springframework.beans.factory.config.Scope;% x+ N) |1 A6 ?4 ~
    import org.springframework.lang.Nullable;1 d3 [2 D0 ]4 S
    / _7 `3 a0 ^$ Z. b% s) n
    import java.util.concurrent.ConcurrentHashMap;3 r. g9 ^7 T9 J9 Q

    8 Z5 m! p5 L% Q, Dpublic class BeanRefreshScope implements Scope {. V, M, P6 j9 @7 k/ _' e
    $ \& L3 {: {4 r4 g! Z( a" y& m
        public static final String SCOPE_REFRESH = "refresh";
    ) y" i: x( w  O' y* a/ D2 Y2 D; Z* L
    % D2 I( t: r, ]0 O    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    2 e! _) Q/ @3 j6 g: h# |! {2 r, L" m  Z" Z) F3 y
        //来个map用来缓存bean+ S5 k: ]6 S4 O& G
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1: R& k3 A5 ]* R7 t7 D" d3 I

    + Y* ]' B  D" l% |+ H! R. p    private BeanRefreshScope() {; E5 W( {0 m) d  }% S2 y3 F
        }) j. |% k' T! N2 X6 {! n8 O  {

      Y8 f1 \* @. ?' E3 \. E    public static BeanRefreshScope getInstance() {
    ! h4 B+ b! z. F2 t% k        return INSTANCE;6 m) o* u8 v9 W( e6 H7 y& b
        }
    ; l/ |: G) [* K6 \# b. A
    2 Z% V; N0 y0 g  _, Y3 F: {    /**
    6 @6 b9 {# p. V4 j- d2 J     * 清理当前
    9 c2 k3 X( r" l; g' B     */
    % O% Q  [1 |3 H- z    public static void clean() {! k4 N+ M( P3 B
            INSTANCE.beanMap.clear();7 K! L, b* H8 n* W# X
        }
    0 g1 B4 _; R. s5 r
    4 ^+ [5 c6 `! |" O    @Override
    : {; w0 K! v% M3 {$ \    public Object get(String name, ObjectFactory<?> objectFactory) {5 K. S) g' x& k* T& ~
            Object bean = beanMap.get(name);) D/ s. Q2 t1 |) W
            if (bean == null) {5 z0 N- N( F! U$ _) Q- \/ T
                bean = objectFactory.getObject();  U: a8 V# m' Y8 S, M$ i
                beanMap.put(name, bean);
    4 J8 M/ n4 C; z5 W4 _( |0 A        }
    4 u. d4 K. h/ F$ p        return bean;7 r0 o' b- J2 B+ ]
        }
    $ P, l0 m* I* Z0 ]
    9 U1 Y) C! k" S8 o}
    + Q# K) m- ^+ w! r6 q上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中; W, {5 H5 M3 E. X* A6 D
    / G2 X# a; T3 e# O8 `9 o2 V
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    / n5 ?: T7 s* {1 A3 t% Q: m2 p; ]. d# l1 ~& C
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    3 s/ \* ~4 j& G( M9 F( b6 L, C
    9 ]$ w: }, i. o! C* W: D7 @package com.javacode2018.lesson002.demo18.test4;
    & ^+ ]" L) f0 _6 D* Y' b" b, U- x, {* ^2 L! Z5 g; w* |) O: i
    import org.springframework.beans.factory.annotation.Value;
    6 d4 b2 c, ], }' I: yimport org.springframework.stereotype.Component;
    - }7 F; u! n( P( }, N# U2 _6 [0 I* O- U
    /**
    * Y3 R. Q5 w$ J+ N! c  |( \7 L% f& O) l * 邮件配置信息
    0 X: x, ~% ]3 u8 w' J% Y- b */+ [2 L5 ~# O2 p- K7 J5 k" a
    @Component
    " p5 ]% p) z0 H/ g, x  \@RefreshScope //@1
    4 f$ ?6 \' o( S* hpublic class MailConfig {
    9 T0 |9 ?% f* _& k$ `, S5 @& j+ c+ u
        @Value("${mail.username}") //@2
    + P( S) A- d. U; V) p0 @- v) _    private String username;, O0 `) E4 ]7 N
    0 o- ~6 v1 b& D! g4 _
        public String getUsername() {7 Z3 L& n& A7 s5 i
            return username;5 a6 |6 w7 k4 v
        }
    7 ^5 s6 ^! c- m' y, U" Y8 ~) l5 N7 H9 Y
        public void setUsername(String username) {$ f) _* Q( H2 E  E8 V0 W$ i
            this.username = username;+ `' p. K, j0 F; L9 m
        }
    . X3 E, o: m# M' e: }$ e- M
    " g9 E4 e2 N/ d4 a: s* M    @Override
    0 K  {6 ]5 x6 _1 M- J5 U$ @: a0 H    public String toString() {0 f# U# W# E) z2 _9 A1 {! H
            return "MailConfig{" +
    ; ?0 ^- e- ~* j1 \0 W! Q                "username='" + username + '\'' +4 G; X2 }8 }( }9 X
                    '}';
    ; M6 \9 |; d( V$ o# g! ~    }( Z7 ]5 a4 A8 X! ?# R( [
    }
    & X+ G2 V0 n. I1 x. T. O@1:使用了自定义的作用域@RefreshScope
    ( @$ a/ Y( T3 f( A' }
    ) u# W" o0 L+ i# k' u" ?+ z9 i7 c@2:通过@Value注入mail.username对一个的值
    * X. f, ?- `- }0 o  g  ]: K; H
    9 J& Z/ ?4 J) C- t9 n: O5 k重写了toString方法,一会测试时候可以看效果。$ M+ n* V" m8 C2 n* z

    * ]2 ^* s9 _7 v3 n' B  b6 P再来个普通的bean,内部会注入MailConfig7 Q" D* A7 |! c: m% E
    # m* c4 H$ r" l2 P9 _" d
    package com.javacode2018.lesson002.demo18.test4;
    . M, @! C1 r6 c9 b7 T2 {
      \8 ~9 A" q: C) o" W+ rimport org.springframework.beans.factory.annotation.Autowired;
    ( h7 c. [8 o) U$ y0 X) {- limport org.springframework.stereotype.Component;
    ; Y* X" {6 g9 O
    " K/ _, q& Z. ^5 F% ?+ G& |@Component; Q0 E. R# j# |* ]5 Z
    public class MailService {
    4 m* q! ^) Y! t) ~    @Autowired4 ^% I) b( _8 d" p: d* O9 t
        private MailConfig mailConfig;
    : z  Q2 P5 N/ `" j
    1 l. L2 k) H/ d% r* W    @Override
    - M" g* J* T+ ^0 l7 I, a' T    public String toString() {+ I* {; v" Y' |( P& I
            return "MailService{" +
    ! ^' X, f8 s* F) A; G9 x% F6 {                "mailConfig=" + mailConfig +) {4 S$ b! V& K9 q% K
                    '}';* b" @6 F8 _- {$ Z: N. a, k
        }* O4 Q9 W  T+ Q0 A
    }
    ! Y% U& U$ b# r- {9 v2 r3 u  B- Z- }代码比较简单,重写了toString方法,一会测试时候可以看效果。
    1 j& Z( G* u- t4 A5 k
    8 ^) Y' X, E8 B; |/ `( \来个类,用来从db中获取邮件配置信息
    3 P2 f. Y5 K# g, T! d" ?! _* \- ~" D' W' `/ _1 ?' ^/ o
    package com.javacode2018.lesson002.demo18.test4;/ n5 `# e# \! n
    8 S1 U& X, k3 n: M8 B- W
    import java.util.HashMap;
    4 j# X+ G9 Y7 {. B+ K# Uimport java.util.Map;/ Q0 `( p# m- I! O& s% e1 D
    import java.util.UUID;
    ( ^$ m/ ^  a) ?, ]& P2 e5 @% y3 K) v( ^/ c0 t, \; `
    public class DbUtil {1 ]$ _' q! @* `4 x7 a
        /**' N* O  k; G7 n; |! p% S- ~$ F7 k
         * 模拟从db中获取邮件配置信息
    & `; \9 g2 Z% y$ \8 X  {6 k     *
    ; |( u! q; x4 m     * @return5 A9 p3 m9 Y7 x# z
         */* s5 Z3 B% I. E' B  v4 G
        public static Map<String, Object> getMailInfoFromDb() {
    $ V5 {  ?; g# J. a( r/ ?        Map<String, Object> result = new HashMap<>();& x* ~* q2 O  m
            result.put("mail.username", UUID.randomUUID().toString());& o3 A' S1 i7 c7 C
            return result;
    0 K8 @% S/ |4 H% P6 E    }
    . x2 d8 [' v! n  k; N}
    - C" _7 m& h2 @来个spring配置类,扫描加载上面的组件
    7 {. S  @4 t* M+ w: r1 b- Y' y0 a& {3 c5 P0 }: X, c
    package com.javacode2018.lesson002.demo18.test4;5 i3 \1 Z" \9 k$ y, a" A: w  F) U9 E

    ! o! w- K- r) S( f# t' p' l  dimport org.springframework.context.annotation.ComponentScan;
    $ l- X; t9 z; I7 v6 @import org.springframework.context.annotation.Configuration;
    # ^) \. C, i( t9 E1 t9 ]: S& @/ `; S& C% B
    @Configuration
    : E8 B0 d. R" v3 O1 x@ComponentScan
    " ]6 j& d( d. I% Gpublic class MainConfig4 {% w/ p& h/ W6 S
    }
    7 `3 q& }6 n) s! w来个工具类, j9 Q: n- f0 F& O& E+ z- j. G2 Z
    ; Z- I: n& w, \& E
    内部有2个方法,如下:  c) \: L7 ~% e; ?: e& d/ D

    ; M% }2 j. m$ D( n: Opackage com.javacode2018.lesson002.demo18.test4;0 N% j- d5 G2 F9 R7 Z

    8 P% T7 R; L5 H, @import org.springframework.context.support.AbstractApplicationContext;
    ! Y. M' N5 C! v* y0 }import org.springframework.core.env.MapPropertySource;
    / v4 Q3 `# ?1 V" P) m
    2 ^8 A9 }3 F2 f8 Qimport java.util.Map;
    4 r5 h7 t6 p7 @; [4 v
    3 W. L* V' S6 R$ Tpublic class RefreshConfigUtil {1 Q5 W, y- P# z5 n4 b
        /**5 S7 |. o- X! X. \5 d
         * 模拟改变数据库中都配置信息
    9 m; R! u3 `) o, G8 a     */
    ) {7 G, ~: g8 r0 J: S. ^    public static void updateDbConfig(AbstractApplicationContext context) {
    8 P* |& R3 x& i2 q" x& t- M2 Q        //更新context中的mailPropertySource配置信息
    5 M9 z$ D4 P  l+ F. l9 g6 r& l        refreshMailPropertySource(context);
    $ e# y* a+ b) n* L5 }& E& W8 z1 s. q2 F; q9 k
            //清空BeanRefreshScope中所有bean的缓存
    0 ?$ u! d/ b/ x1 Y        BeanRefreshScope.getInstance().clean();
    . P! {8 a6 \, b" u    }9 O) u: Z7 a$ [$ e" f8 M

    ' J; X6 Q8 w* O# g, u    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    : c# B6 Y  g; {' o% R        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();3 F1 u/ V) d4 F4 {. ]+ |+ ]+ t# v/ n
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ) r* w% d5 f; B/ S+ o4 e4 H        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);  R) X" {# D, b# c) G* H
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);2 z, _7 L3 Q1 x1 r  o
        }
    , Z% j5 K+ U2 {: |' @# Y9 Y) N/ H7 [( A# F! b6 a
    }
    5 i" d$ p$ N7 o8 C( eupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息4 {$ N; S" F0 v4 a
    & |) [" _, X4 ^; k! _
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。# i8 L# v: c, F+ \+ X9 J( l

    . j' Q! F1 E1 q, Z* Y2 K7 a  f来个测试用例- x) ?1 I6 _1 G- A/ z
    7 P. d' P  S; e$ \+ d* j! l  {
    @Test
    # u6 C# ^  J; f% e- o$ u) Spublic void test4() throws InterruptedException {
    ) w; ^! ?9 k- {* x    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" U  N  m1 L" r5 `0 q+ x3 C/ \9 z
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    ! G7 Y( m" Y9 S6 {: _; ^    context.register(MainConfig4.class);
    / `$ X; E8 ?- o    //刷新mail的配置到Environment
    , E* b5 O$ F6 S' F9 c  F1 o    RefreshConfigUtil.refreshMailPropertySource(context);5 O$ s0 `: a0 W
        context.refresh();" m: `, a0 e+ {$ M9 N7 M7 {

    . T' u( ~5 S1 y, _    MailService mailService = context.getBean(MailService.class);8 h4 ~3 P' I% `- J% h* N0 t
        System.out.println("配置未更新的情况下,输出3次");9 a" {' U# A$ W! E
        for (int i = 0; i < 3; i++) { //@1
    " L& l" r+ g$ `7 b        System.out.println(mailService);
    " Z$ A; [# {+ y, I- R        TimeUnit.MILLISECONDS.sleep(200);
    ) r) C. E: C) h    }
    5 h/ B! S. n# v! |- B! v' r+ F! O. e) E. v3 N; R9 H
        System.out.println("模拟3次更新配置效果");% i5 c2 v/ \/ u2 _' M0 j
        for (int i = 0; i < 3; i++) { //@21 n6 K1 \7 G& Y
            RefreshConfigUtil.updateDbConfig(context); //@3
    ( e  \- N) z+ u9 D, l$ h  M        System.out.println(mailService);
    - _. C& C+ ?/ }1 N        TimeUnit.MILLISECONDS.sleep(200);
    9 U3 Z  @3 G7 r. W) k! D4 ?# [    }: V, ?2 G; j) C* P- _9 S+ {9 A0 f
    }
    2 ~6 [; O! ?$ F@1:循环3次,输出mailService的信息
    ) [( M0 G3 s# ~2 j! e
    . f1 r/ z* v& M  B. e; d6 ]7 B8 p% ?@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    3 x3 ?4 Q& T0 C3 W4 _2 j
    # n1 n; {( B6 H* E" z, i5 C见证奇迹的时刻,来看效果
      c) k9 s& d* r; t8 u3 v
      {! l. w# ^, n0 g/ Q配置未更新的情况下,输出3次7 K" g+ Y/ o' D- M& }& J5 {. o
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}5 j; @& R+ ]+ |+ x; C
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    . M- d6 `! T& M+ D0 G9 `MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}& q7 j' n/ H$ h5 \; j
    模拟3次更新配置效果1 C  q( O2 M# u* S" |- I
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    2 j- O8 b. ]4 {/ H' U+ }MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}( k6 g/ e$ F- D, p/ v
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    8 a6 y' p/ j) O, F2 e# f上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    9 u6 O" ^& `3 r6 g7 C4 g9 b! |' a$ p( _
    小结( O7 ]. S- N- V1 x

    . ~3 a( y8 x* Q$ u/ n动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。8 r0 S$ O+ g1 f. z. ^5 q

    3 `( n, k. @# U) z; F- M有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    - g2 ^6 ]/ Y* e4 a. }' [( R
    ' k) M5 `- Y+ K+ d. ~% X总结
    ) c3 D0 B0 y$ V5 R
    $ ]; w7 o( d5 K- M* z* A- V本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    * f& Z( l) {/ K* y
    : d( t+ A6 Q7 J' j& b2 r/ a案例源码; z' E$ Z& T2 U
    4 |7 H& x2 t/ C" y; J& t: _
    https://gitee.com/javacode2018/spring-series
    3 u3 k; m' e  _* p路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。7 o' U# _) M8 S/ E) ~  r' X

    5 S/ T5 |8 |4 rSpring系列
    1 G3 }- h- f- S+ z$ ~  ?
    . o8 U, L2 ^% M$ E) m2 E% uSpring系列第1篇:为何要学spring?* C: W# `; h. r5 Z( \

    $ d5 y$ ~% Y* P. o2 CSpring系列第2篇:控制反转(IoC)与依赖注入(DI)3 l8 M, \( j2 [1 a1 T) \
    8 _% \* F! {- d" E0 `- o
    Spring系列第3篇:Spring容器基本使用及原理8 J' `) u0 x2 l6 P4 z
    1 _. ~! V( t3 H% \7 M
    Spring系列第4篇:xml中bean定义详解(-)
    ) M3 \/ Y7 G! O; j3 u
    6 v) ~! u  b/ fSpring系列第5篇:创建bean实例这些方式你们都知道?  x( h: `+ `* A  L3 J% x5 \$ C; T
    & d: g3 n/ M5 K8 Z, ^, J+ e
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    4 ^, D1 `# q3 a3 y$ ~- l2 ]" U0 m5 C: ^. B0 n# |! v
    Spring系列第7篇:依赖注入之手动注入3 V8 X0 ?* r% h, l* ~
    $ z( I, i, ?; M: ]5 i" X
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持- p  ~: r; O* J$ B+ b- A( n" V0 A

    ' p3 Z( x" U! _. }, F7 Y* W( ~* ESpring系列第9篇:depend-on到底是干什么的?0 h" ?3 u+ g- X

    8 r" M; W, d0 ~+ }- uSpring系列第10篇:primary可以解决什么问题?+ I! d; X& T; ?! P

    / Q3 k% v9 q* F  a" v1 O* nSpring系列第11篇:bean中的autowire-candidate又是干什么的?5 W6 E9 Z! z+ s" j0 V0 _

    5 T1 ?/ g- z1 [1 }2 {. s  k3 L; RSpring系列第12篇:lazy-init:bean延迟初始化3 I2 b1 \) I. s7 M$ [  Q* g; Y
    ' F& ]/ O# Q% i" T
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    / @0 k. T: I, q# C6 S. i
    ; F1 `/ C, \3 d# ~' s& BSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    8 N4 [1 u* `4 x. m; ^
      R+ P. R7 d* v4 ESpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    4 Y/ t0 x4 V' P; Y
    / t8 B, j: o! [8 u, TSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ) Q, c" M3 m8 U4 U% A/ c  T) r' p" f/ `3 ~8 z  Y; M+ d
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    # X5 _0 C, r8 H) T* x6 V  o; }- G0 f% e' @, g% L$ o2 B
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    . t* M: i9 R) c# I$ p, X& [7 W8 k4 t& i1 \. l
    Spring系列第18篇:@import详解(bean批量注册)
    * {5 X- K* w- M0 [' g: U- n4 ~3 Q* i; R9 K, l
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    - Y& j3 g3 m( g% s. d$ f4 o$ ]
    ; Y! s  m' _8 _8 R' r& O1 |* sSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    3 I# c1 N% x+ B4 y. N
    , y6 g- t& \1 K) }* E$ y) aSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解  c& g: ~6 O4 p

    & F1 q' x0 o/ L+ xSpring系列第23篇:Bean生命周期详解  k/ V1 S' I$ |/ b- L
    " {& ?* m8 j- Z' W
    Spring系列第24篇:父子容器详解+ @* r! e7 ~4 u$ B1 K( p

    $ E9 n+ `6 w, K( F/ C0 Z6 g更多好文章, ?3 j. L% @! n/ [$ L6 J- O
    5 [+ _) a# P; N; O( H7 q
    Java高并发系列(共34篇)
    + n: K3 v2 @% b% ~; s8 v
    / _1 y$ [5 F1 \+ f1 y4 C- L/ V! ?MySql高手系列(共27篇)
    % u8 g( t- U. n, W! D
    8 C2 a0 Z6 C8 O/ |- @8 rMaven高手系列(共10篇)5 L( N0 }0 q. R/ F% j

    2 ?8 X: Z2 [7 X. z2 PMybatis系列(共12篇)4 L8 V0 ~3 R4 T1 f2 M& I7 z
    ! Q6 i, D% t* m" ]3 a
    聊聊db和缓存一致性常见的实现方式
    ; y2 V# V+ B: u6 a+ n& ~' O9 z8 C. z
    , a& F/ N; }: [8 H# N0 B2 g接口幂等性这么重要,它是什么?怎么实现?' M& U& k9 J, G/ a  Y5 |; }  P
    , o  w1 _( t9 f& ~9 q: W% ~
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!% m* y6 N2 l: e# v. H$ o* Y2 w
    ————————————————8 U. ^. b/ v9 k0 }9 t
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。- h3 A7 c! H; Y
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    9 L8 |& a( ]- U! _9 d, C! d6 }: y: V, _& D) H

    $ [) X* a. m% k$ f# f$ n4 |! L
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-8-4 15:41 , Processed in 0.457924 second(s), 51 queries .

    回顶部