QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5531|回复: 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!
    - F3 X7 F  z# L- V, `2 a疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!! e/ G' X. U1 W4 a  w0 u) X. W3 b, N8 K

    ! Q2 L, f: |/ Q0 m/ r/ }- V面试官:Spring中的@Value用过么,介绍一下/ |* h- S/ N& _# d7 O' D  @
    ! ^  V6 m7 R: v$ a4 _2 B. I* S
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    0 W8 \( I5 n/ `# c( R
    3 H8 k- u8 }4 [. ~. t* C6 v面试官:那就是说@Value的数据来源于配置文件了?) u) y( h4 V$ I# J- p- k

    ' m+ O1 `' @  S. D7 ^! B5 K! v我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置; t6 x3 Y! d7 ~- _- K9 p  N

    ; H3 l5 w4 S) T( v  y4 T' {! O面试官:@Value数据来源还有其他方式么?: r! l" v: O; Q

    , \: S! f5 n% A$ o; L7 A3 ]. O我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。( F! {; i" F) M
    : M& Z7 z/ w/ N7 Q, v2 U
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?* F) [. p& n) h7 p

    ( Y5 S7 p8 d0 m3 a+ F) t我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
      w& h* [. |+ I9 y4 \- `7 [1 `/ C/ I% q
    - A' s; S/ U9 ~6 ^/ p$ c" h* l0 V# T面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?* K+ @" r" _" s
    ; h9 ~: {, H6 w- s" \$ m8 H/ S
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能7 u$ s8 k" g) b. g" W

    , f( h5 |1 N5 U" t: N面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    + Q$ U+ l1 P& e" N5 G& f
    ' A* ^) E% @3 e我:嗯。。。这个之前看过一点,不过没有看懂
    " x8 {8 R9 K, M, _
    : J. G/ A# @2 _2 P; y面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    ' V% Z+ A! f/ L4 J/ e, [
    * B5 ^7 s5 Y; w" }( R我:3万吧0 R- \! o4 L8 J7 G- t
      A$ y( S! ]: N9 n5 r
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    , {  C$ O* }$ y$ B2 j
    - x5 X! S' i& K+ e我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    * r9 W* H' U/ Z5 \8 T) b, [5 ?1 D; _, S% s+ L
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!6 o1 ?5 g# ^6 u. @2 W' `# Z! }4 ?! X1 Q

    3 o  `! P+ G9 A我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。# z8 q, o7 |" q! ~; y
    3 c5 Z! P5 |" G/ S1 L
    这次面试问题如下# C- t( b+ x% S! \
    % \7 g( k( I$ @
    @Value的用法: A9 C5 p+ p2 Z4 K
    / m! Y3 D$ g; M, a2 k
    @Value数据来源4 [1 L  v) J, n, P" |

    ' `" T" }6 R  T@Value动态刷新的问题
    ( W) F: X3 b5 {0 O0 Z6 }5 q4 ]+ _/ U& a# I
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    $ Q( X4 r+ a( |/ z' v) D" N  ]& }* \2 d3 t7 |/ V* V* {3 ^) @
    @Value的用法
    / z; x# F% y, F: G
    ( P: c0 O+ e8 j' ]6 @+ n0 L系统中需要连接db,连接db有很多配置信息。
    / H7 M+ K7 a* h- O# ~( J) t! S! R' g3 m7 c7 Y1 G
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。' k) j; I! e' l" O# y
    " ~; a  T- P$ Q7 h2 [8 ^* u" v
    还有其他的一些配置信息。
    4 I( x& T; M& _+ n0 }. u# N1 D( [/ c' _! F
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    + Y. c, F+ W, ^" O2 V
    9 B8 [8 H* {6 J3 D8 L: `那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。& c8 t$ `; l0 C. L0 [2 F3 }9 h
    1 H/ r- Z7 v7 b
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    1 P1 _& f3 t6 D" ~/ n# r
    9 R7 R' i% i# `通过@Value("${配置文件中的key}")来引用指定的key对应的value。% V5 `0 i) R6 Y
    3 ~# L; ^4 |5 T* m1 S9 a
    @Value使用步骤
    + }. [# P& v4 W% @. T$ T1 O9 ~$ }& x+ R# s
    步骤一:使用@PropertySource注解引入配置文件
    2 B' v! B3 p; W/ a9 D2 t# P# B4 X- S
    将@PropertySource放在类上面,如下$ ^. Z. B0 C9 F7 v, k2 ?1 ~! R2 l

    1 v7 |5 n2 i" c! _0 ^, w: C+ t@PropertySource({"配置文件路径1","配置文件路径2"...})/ W4 X  _% B/ z9 `  Y' ?  X: z
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    - Y. ?+ N7 [+ G- {) u6 v$ R* c8 S# ~  ^3 i
    如:# x" d1 z+ w0 l
    7 w3 B- A) y4 G/ H, Y
    @Component7 ^9 F6 g9 g7 T! t8 L  x4 @0 U; A
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    5 i# T$ O) E4 h! Dpublic class DbConfig {
    2 }, d* G. P" Z0 L3 e' y}
    ) k% h, A' e% S* c# n步骤二:使用@Value注解引用配置文件的值
    9 B2 s/ j( v. p* W* q
    $ ?3 H3 j0 x% m通过@Value引用上面配置文件中的值:( y) T, ~/ j7 g$ T4 J

    ; P9 `0 P0 k8 n$ ?7 f- }7 e$ j语法
    , \9 C! T* V1 z
    8 F2 [" B8 O; X  i2 T- b@Value("${配置文件中的key:默认值}")7 _( e2 i- W5 u! i9 r- @9 i, B! |
    @Value("${配置文件中的key}")
      G: Z/ e$ @2 g" z如:
    2 y$ \7 X3 ^* `7 r- s$ H& S% b1 F
    . R2 ^4 |3 q0 R" B# y4 v3 @@Value("${password:123}")
    ( W" ~$ f9 Q# A上面如果password不存在,将123作为值7 [' B( s0 D! a$ {7 }
    * \; a" o/ g- s$ R4 h6 Q
    @Value("${password}")- ]" u: [7 L& l: {/ G2 ?7 _) `* N
    上面如果password不存在,值为${password}+ g+ [7 L5 P4 u: ~* L. d0 G
    3 N, ^: y7 d- w+ E" F/ \2 S
    假如配置文件如下  X: ^9 n2 V1 g, l
    $ r0 S1 d# `3 i  g" {, V% d
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    + F+ {5 I6 Z2 _( pjdbc.username=javacode
    $ j' l& D# v$ N; L- d0 }4 Wjdbc.password=javacode9 W" F; K" E/ k$ Z; d* n8 C
    使用方式如下:, i& `7 n" S1 E( z2 ~3 }% T
    - W, L8 c4 _; A$ h  Q
    @Value("${jdbc.url}")+ @6 q0 _4 N$ |, j- S
    private String url;
    3 j3 J; |6 |; q! h) f; o/ E
    : ]/ F) y( ?3 M8 e  r@Value("${jdbc.username}"); q8 G: O! E  Q0 k- M+ A4 M
    private String username;4 C! U3 j' I+ ^: N: c$ [
    ! z: S- v* i, Y) m- U# ]: }
    @Value("${jdbc.password}")8 t/ P  A# E/ i  m
    private String password;
    4 E( \% V) ]. K/ s下面来看案例, e/ f) z; l% v

    3 C( i9 r& ?  H; V# e0 p. P5 H案例% u( A6 S( e" K% q( l, t: p

    : {4 G9 ?; N* Z+ M4 |- m来个配置文件db.properties1 f# s: B: P0 o3 {) D
    % L4 J4 S6 `$ m& |+ V% C4 O7 }
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ( C: k- _% e8 g6 a  M- v& Bjdbc.username=javacode$ L4 p; p1 ~6 i( z. f  r
    jdbc.password=javacode
    8 [5 R9 [0 X" Y来个配置类,使用@PropertySource引入上面的配置文件
    1 s# Y; h7 E8 `2 B- }, X% V/ a; i
    $ [9 R- l6 v* g3 A3 ypackage com.javacode2018.lesson002.demo18.test1;8 w* I2 }' |. U3 i4 a5 g9 ]

    , p1 L3 h/ \) a' nimport org.springframework.beans.factory.annotation.Configurable;6 n( L( q! T9 O: ^: `3 S( `
    import org.springframework.context.annotation.ComponentScan;
    5 a' y0 Z! s2 u- j+ |import org.springframework.context.annotation.PropertySource;' J5 _: u+ O! c8 F& k
    / |0 @6 A% X  S! J
    @Configurable: ^0 b, C8 t1 k
    @ComponentScan, d8 B( _& i/ A4 F: _; `9 c
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    ' {: i- u4 N/ E0 k( `3 Epublic class MainConfig1 {
    8 s# l/ @( w: n; C3 y' U}
    & F& w) p: f# a, H' }2 |- V来个类,使用@Value来使用配置文件中的信息, ~2 |2 }, E: E! b! q5 b
    ; @1 p( z( d$ g, ]! o
    package com.javacode2018.lesson002.demo18.test1;% p  [) ?4 X+ D4 j1 Q2 h* t
    9 c" y' D  B8 j. ?
    import org.springframework.beans.factory.annotation.Value;/ G/ }1 v* G% m7 t' ?$ B; V' j
    import org.springframework.stereotype.Component;
      F2 Z+ l) G2 @5 Q3 a. \+ Y& q7 i* z4 @+ m6 ?, F# A/ e2 r
    @Component
    & r0 X% B7 G( ?1 |/ xpublic class DbConfig {
    ( @" l4 E( d; X; ?' n; t
    * ]' K' d* p+ D  e6 n    @Value("${jdbc.url}")
    / n. R. ^1 u: V) K% B    private String url;
    9 [: g/ }4 H3 ~# F
    ' E6 @% W9 Z! {2 b) H* Q. w    @Value("${jdbc.username}"), O! w. e4 P  R3 n) P* |9 k+ a; e9 ]! {
        private String username;
    4 [/ e0 ~5 e! n9 @9 v' b5 ?: Z
        @Value("${jdbc.password}")
    , @3 g- S4 y& M3 J    private String password;
    1 A  q7 M+ x5 K& f& s
    & ?' D4 N6 Y8 ?9 x- U    public String getUrl() {5 c- \( U' k0 s+ v% C1 d0 s6 l
            return url;
    # J9 o  l  r8 b9 w% Q6 ^    }, `4 r$ z/ W3 V* j
    $ t+ n/ @$ c) o
        public void setUrl(String url) {
    3 a% S1 F" g9 G+ M        this.url = url;
    5 x& m" L& i, S) F+ t  i    }
    1 y  U6 ]1 {7 ^5 [9 w3 @
    ) \9 K/ F: @7 J/ R1 D    public String getUsername() {
    6 P3 K+ w1 p1 T( h& l3 ?  c        return username;, h, f9 @0 d* x9 Z2 U3 Q0 ^. y
        }
    ' W! x) H1 r9 I& y8 B: R8 T) e
    & a! ~" D; L, i: C    public void setUsername(String username) {/ p: t  i4 m! J1 j, E
            this.username = username;5 w" F7 l6 l9 Z) T. i
        }
    . ?; d& d1 U! s( i% r3 u
    9 S* `  A2 @6 {    public String getPassword() {
    ) d% D1 t# ^0 v8 A; U        return password;
    ( e* ~, O2 Z% K( [% \* ~8 s  Y    }
    & R, u& u. T0 s$ r9 o/ m; b2 o
    ' }% P. I! @' Y" N, S    public void setPassword(String password) {# z# o# y9 |: u" n$ T* A, ?
            this.password = password;
    ; m' m+ Y. @5 u) o: J0 {    }) o2 ]: Q" a, J/ H

    8 ^, E1 C: \; A$ n4 P8 M# k& a' q    @Override
    4 A* N! ~# d5 K) I# G' z    public String toString() {
    ! B0 |$ G6 d$ n4 A' H$ X( N& P        return "DbConfig{" +
    ' \% V: S1 A% E; b$ c                "url='" + url + '\'' +: g- {; p* p$ C, A; [
                    ", username='" + username + '\'' +
    - Z1 T; ]# v' ?  J                ", password='" + password + '\'' +0 w) }% |; K) a% E1 g! \1 G' ~
                    '}';
    ) m5 K/ B! h0 k% M2 f    }
    ' N' c  }4 X  `" g; {: `3 J}6 m* u$ U0 _/ c. L
    上面重点在于注解@Value注解,注意@Value注解中的. Q: F! C1 P7 r* ~
    % @/ X3 `) j1 b' n/ c; X! H
    来个测试用例
    , j4 i. d( Z0 o% X! m( s- I5 E$ u& [
    package com.javacode2018.lesson002.demo18;
    2 n  Z+ g) W" x5 Z# W( }8 f( w/ H- _" u1 C# M
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    3 i- S5 `0 y- w( e4 u2 oimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ) E; ^7 B, |# g4 E; qimport org.junit.Test;1 m# C7 H  D  L, B. [" n2 s% H
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    * a6 H1 d: ]$ P! [; d7 w  Y8 G1 u  j* S) I3 F/ i5 e6 S6 p
    public class ValueTest {
    ) M: y+ \. K1 D& v8 _
    , {7 _: s4 r3 ^    @Test
    " ^$ N( P0 X) A& d1 F    public void test1() {
    7 v# N: e, k9 I9 m        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();$ g6 y8 N4 [9 c6 o
            context.register(MainConfig1.class);
    1 J- ^9 p6 \6 o& r4 F        context.refresh();
    ; K/ ~" K" k& F, Z
    " k, p5 k; p6 Y/ ]        DbConfig dbConfig = context.getBean(DbConfig.class);3 V; \1 ~, a# k! k) L8 H
            System.out.println(dbConfig);
    . X% k- L. L, u( i- r. h  [9 Y    }' P$ Q/ Z' U4 ?2 R! d; m
    }6 ]' }" E; B6 c+ t
    运行输出) f" a# T2 |! F
    " C; ?4 j. v) H5 A8 Y, p
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
      C; L. u& ]3 L2 r上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。; `9 ^  t7 `- j. `
    % O2 B- v# q$ U$ E8 ]' s
    @Value数据来源9 E2 t3 s5 ?& _* q' D

    ; L# Z# a" I4 Y! g7 n( `6 Z通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    , I- U4 ^3 j& {, v0 t0 E5 d1 g
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    , b6 a/ f  Q% _- K2 N( w
      W1 y! q" f% }" [+ c$ w# |! Xspring中有个类
    3 I( C) p! n8 J6 L. }8 s% P$ d% ^6 }% a8 R8 {9 v( w
    org.springframework.core.env.PropertySource8 W( P4 r/ G+ w( ^
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    ! H6 V0 h" `! @$ G4 v0 C$ \! n2 d2 o9 y) X3 F% e
    内部有个方法:: Z5 c6 s8 S; f" k' t9 S. H. i& `

    / I1 w% c8 J* L1 |. G: {3 F5 Cpublic abstract Object getProperty(String name);( |- _2 A1 s1 P# M4 \1 Q' a$ Q- d
    通过name获取对应的配置信息。7 K( D9 R5 A/ S- S

    ' U. r5 }- v0 d$ o, A系统有个比较重要的接口( [7 J5 V+ N8 K

    . P! M* N6 U  t1 s) m! W$ korg.springframework.core.env.Environment
    + r1 b/ O; e3 B7 P" e用来表示环境配置信息,这个接口有几个方法比较重要
    ) ?2 i. K  V1 ^0 }
    7 K+ w5 d. z. i" e# x6 {" \; CString resolvePlaceholders(String text);( P) e% i) o9 K% \
    MutablePropertySources getPropertySources();$ u8 X( s- V0 u. ^; R; q
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    , B% L+ H1 r" o
    " f  Y& |! d, @% M2 l8 I- K/ _2 RgetPropertySources返回MutablePropertySources对象,来看一下这个类5 J! c5 G0 A( b& K4 H2 ^, D( j4 \
    7 Q; ]% ~( c- X8 j0 {  J. G. h9 h
    public class MutablePropertySources implements PropertySources {
    ; d! B3 I$ j- u) q: I+ c/ n5 L9 T1 k" Y
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();9 }& }. w! H; z
    * F+ w" F* d4 q* Y/ B3 N- ]
    }$ X- P" ^/ `0 L8 i2 M
    内部包含一个propertySourceList列表。
    1 {2 v, \: Y( B; Q
    " y4 v$ p" L* `( J& Z- H: d; K( mspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    ' R  i/ c+ I. c* ~! g" l& f
    9 z, L* r* @& v/ f大家可以捋一下,最终解析@Value的过程:
    ; @* r5 F/ F# q
    " r2 Z8 |6 N+ b6 z& W1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析; x+ r0 g* r6 |: N5 k
    2. Environment内部会访问MutablePropertySources来解析
    * C8 T% z% R& Z/ M) h5 i8 z+ Y3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值" z- W1 S* D7 V9 ]1 }" i
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    2 L; A+ \" Z) d( x- f
    5 q  i+ i  P& q9 Q8 S下面我们就按照这个思路来一个。
    " C$ W! W& ?4 F9 X
    # _! N! N3 e; u来个邮件配置信息类,内部使用@Value注入邮件配置信息: S: R* ?% L* x4 s+ ^5 f  q
    2 j0 T; K8 Z6 v; X! l2 Z
    package com.javacode2018.lesson002.demo18.test2;
    # U0 B4 H& H" A# P7 u' ^9 d; m1 p% L) z$ U
    import org.springframework.beans.factory.annotation.Value;
    : _9 i) s5 u% S  K$ a6 o# s! \. Cimport org.springframework.stereotype.Component;
    2 m% x$ l+ g2 a$ b3 Y) s9 t9 T) S9 p. m+ ?
    /**3 B1 ^! T: ~, r
    * 邮件配置信息* K- C6 Y" M  `! a! K% r6 A3 Z7 j$ V6 x
    */2 r! u8 C9 q9 ]3 M$ C) `
    @Component
    * M' m- w* a$ t3 c2 b, R3 Epublic class MailConfig {2 V  D( p' }$ U  s

      Q. ^7 q7 F; ^9 z4 [' R* b    @Value("${mail.host}")
    ; T- W9 s& U8 E5 l4 ]) }  A! ]+ W    private String host;
    9 Z$ n9 S1 T5 k9 C
    . }9 ?. z$ i& T% a7 D: C8 a    @Value("${mail.username}")" S* ^/ }" F) `3 g' u9 ?0 n
        private String username;6 n6 n) K+ z* k! y/ \' ]5 {- }
    # |' n8 _5 e. S6 ?. K
        @Value("${mail.password}")
    % y0 A/ B. P" F4 y0 d+ S    private String password;  E  S% E  |& D. x- n7 a" U

    ! j* R0 v. s: r1 z# s+ |- |    public String getHost() {) d. l& m% }5 e- t, A" c8 q
            return host;
    1 W6 a8 c7 n1 i) {5 Z, [    }, G* L/ ]! _) `6 y7 b" X

    , g: e' ]( g+ M) G1 I+ p    public void setHost(String host) {
    , g5 g, ^) K# z& F( B        this.host = host;
    1 @- w: r( f) Q7 v: s    }3 T3 ~4 m6 _: \; y
    $ u2 H" s9 U7 G  w- o) e
        public String getUsername() {
    . Q8 K* N+ x$ b, T$ C        return username;5 o8 P" t, j9 @
        }5 m4 m+ l/ I. g9 E

    4 O8 G( j2 |# W+ E* m    public void setUsername(String username) {* D- X9 g7 N  ~3 S& \, z- t
            this.username = username;9 ?# ]$ }9 B8 R
        }
    . D# m8 ?& H3 j# E' I6 T
    / w: `5 ]; `3 N5 \& |9 D    public String getPassword() {8 y$ i+ u4 f) Y  ^7 Z2 `+ V1 D2 i
            return password;
    4 |. P* d3 {, o9 ^; |& s6 s% h3 z9 U2 h    }  {, z" r0 Z; e! M

    & ?2 \' b4 A* r( N5 `3 h1 e    public void setPassword(String password) {
    ) M) Q1 h' F3 k/ E- ^8 S0 k3 z        this.password = password;: x  h+ K+ f+ K! @8 Y
        }
      i+ j' \( l! ]0 L' }5 M7 C2 _1 x
    6 s6 i7 n9 x( v' W) q: b* s! a5 x    @Override- \) D/ D$ v( N$ h  S
        public String toString() {9 S- J) P0 w4 Y3 g- j! ^% y
            return "MailConfig{" +
    8 h4 A, I7 K) L: E3 ]  `                "host='" + host + '\'' +
    5 n6 G/ z" H: H2 x; N                ", username='" + username + '\'' +3 l- X5 }# ^7 o' c
                    ", password='" + password + '\'' +( e2 g6 D; L$ A5 q3 M
                    '}';
    + W5 K5 B5 r: V' |$ D) ^2 i. G$ r    }
    ' e4 J, d2 ]8 r}% K3 x  ~) s! i% \. Z" i. L
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中4 G8 F& L4 C0 b& z& R/ C( b

    / ^0 g8 U- J; y; p; a+ j; |. [package com.javacode2018.lesson002.demo18.test2;
      [* P! @- E: c7 y  F
    9 U! s; K9 Q; x9 Q' Himport java.util.HashMap;
    $ v! \* Z0 U: v/ ?' B, k/ D0 Wimport java.util.Map;
    & G) A5 l2 Q4 R  ]# x0 C% h
    1 M# Q9 w4 X2 A3 B6 i+ fpublic class DbUtil {
    ; g& z0 D( v5 d' m1 _    /**! d+ ^  b; g& e( |
         * 模拟从db中获取邮件配置信息
    # r3 t4 }4 T% h9 T     *
    ) R! K, s( v; f3 d0 k8 ?     * @return
    / l% L& b5 i& G2 `5 _     */, a( M% Y3 u8 U) N% U7 T" p* A! O, A
        public static Map<String, Object> getMailInfoFromDb() {
    7 q; {. ?4 W: f2 P# j2 S        Map<String, Object> result = new HashMap<>();5 m* O5 P% ~" d- E1 c2 A, Q
            result.put("mail.host", "smtp.qq.com");" L& k* s1 z. n
            result.put("mail.username", "路人");
    3 K: k3 g. b  Z& {2 {" N        result.put("mail.password", "123");
    7 ^( n& a+ O$ E' {+ a$ h        return result;  p4 l" }6 z1 Z7 u# i8 v6 F! {9 `$ l/ }3 t
        }* g- \" ~/ q# y& F* R) S: A
    }  t3 v* l1 n6 h/ E0 s6 K' o: Y
    来个spring配置类
    * N% W8 `; R1 B3 K, r6 }: B1 n" v) z1 z0 M0 F) E! i/ o. O4 `. N* j
    package com.javacode2018.lesson002.demo18.test2;
    6 L- Q( W6 h5 G5 J
    ; V& r4 b$ v2 ^& \" S: D& Eimport org.springframework.context.annotation.ComponentScan;
    5 L, Z0 Y4 p& Yimport org.springframework.context.annotation.Configuration;0 m, l% V% b% L7 o* T
    : n- }( ]/ O4 I
    @Configuration9 c3 |; u* m5 }# s2 b
    @ComponentScan
    - r) p2 Z- |9 A( M( t6 jpublic class MainConfig2 {( G5 }+ W: h0 g4 ^8 Q
    }* m9 x' m: t0 Q. H
    下面是重点代码
    , y9 t% D  e( d3 M; z4 e
    + M1 r0 U( ?" d0 p. K. W6 S@Test% c) d4 S- S9 j5 t; G7 t9 g2 ^5 Y
    public void test2() {
    ( r! U; s" p; X; U: p    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();# r2 K+ A0 y0 J

    1 a9 c  W! ]1 R    /*下面这段是关键 start*/( c- @0 C' m* g; k* O0 e9 O7 ]
        //模拟从db中获取配置信息# |7 C5 q/ O# p+ v2 E
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    # l6 I6 P$ q3 s: G- y    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)1 s; N, i2 D/ {3 t
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    . J, ^* j4 `2 q1 h7 o$ g8 I    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    5 E) M6 j2 x1 v" ?: l) M    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);5 V/ \) p7 c$ ^6 ]% _
        /*上面这段是关键 end*/
    ! F# P, T$ [' {1 ~8 L: h, I) `: O" ^- n- ^; A
        context.register(MainConfig2.class);
    # ]" e' S4 y- p: T    context.refresh();2 \: M$ H& n' q5 h0 {! M1 d
        MailConfig mailConfig = context.getBean(MailConfig.class);5 E" `9 |4 l8 g; k% `' n7 O
        System.out.println(mailConfig);8 ~0 ]0 o% j3 z) d, e% n* c/ e
    }
    6 \& Z+ `& h7 L: l6 E3 j- P- w注释比较详细,就不详细解释了。1 Q, r8 `0 }7 o% g4 |, T5 |" S

    # l7 G3 _" e% M5 Q直接运行,看效果% {# v5 I6 f' W" m

    , }  X+ L! w$ K$ _. }MailConfig{host='smtp.qq.com', username='路人', password='123'}
    / {6 F1 o* b+ I7 \6 @有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    : \2 O2 a! I+ `  e2 {; O6 D# O& N/ l* L( O9 @# [& s
    上面重点是下面这段代码,大家需要理解$ I( `5 D3 e3 P& r5 z9 u

    & m; u) z8 p% p+ k6 y0 y/*下面这段是关键 start*/
    4 e. R* O0 r) U; }# c//模拟从db中获取配置信息  J8 W6 h8 ]. G' m7 \
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    # `1 i7 I$ z4 \! d# |9 n7 x//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)" P7 t+ I/ `, K7 n3 O4 ~
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);1 E0 k' ?5 Z7 e8 v# m" S
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高  {1 H5 S' o0 H& j7 [2 Q- u
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);% {! p* Z$ i5 Z, k& ~+ ?
    /*上面这段是关键 end*/
    % Z3 \! ], _/ A6 E咱们继续看下一个问题3 J( S! u6 V: ]
    * {1 M; o( S- z* n3 k9 d' s- u% d% D
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。; B7 z! M1 C" L0 U! o! Y8 g% J2 t
    2 L/ A. i( e  c: C& p5 y5 i
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ( n2 }  l# w  h+ o  }, C9 ]% ]2 [; K$ Q, W
    实现@Value动态刷新( d: N! Y  }  M2 w# @% j
    " b5 R5 `8 \0 g! u
    先了解一个知识点; u: p0 X7 {) `# U1 f
    & z/ I  A5 r' ^& }$ w/ O7 X
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。' [* |! N$ x5 q% ^: X

    " p; o/ K6 n6 g/ E$ |) M这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    $ O8 ~% k/ w6 W9 l( Z1 Y% ]5 k2 i# D7 D, o
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    & b: Y, |7 K0 t, T* j, y: D4 K$ x7 t( q* j1 B& a
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;* c) ?6 P3 ^. H6 [& W2 J
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    + n$ ]) q8 I) P: o) B" j  u6 O9 K. u7 c! Z+ `
    public enum ScopedProxyMode {+ z) ]- s7 f& m. Q  }
        DEFAULT,
    4 n/ F4 D7 V! U8 E0 {    NO,9 }6 `  |, j4 A+ k4 |
        INTERFACES,
    & O# u  c' N7 Y8 y: y# B    TARGET_CLASS;2 j- i: V" M* J% i. y7 y
    }. ^+ G# C% Y, Y) w9 i  [9 H
    前面3个,不讲了,直接讲最后一个值是干什么的。* `/ N8 a  F! x4 ~5 B
    , G, C& \5 w* q
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    1 C; F8 Q. P$ L( C
    . R' g/ K. V1 U% d理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    ) f4 b; p& d4 @$ r! f  z8 v/ b, q2 }- x: Z% W
    自定义一个bean作用域的注解
    1 Y" u' N: `3 L/ n9 X) D. W0 r0 z( Y6 d( R6 ^1 `5 P# ^
    package com.javacode2018.lesson002.demo18.test3;
    : {9 c: J( X! ~  I" e" ]+ j8 r$ }2 `/ d5 w1 R9 y6 }! h. t
    import org.springframework.context.annotation.Scope;
    ' Q/ R# [6 T! j) `) [% S/ S. i/ Vimport org.springframework.context.annotation.ScopedProxyMode;
    2 e/ G  E& f% s; h9 G& g3 y) i
    - v, j, A9 {3 O; L. `import java.lang.annotation.*;' }# n3 m4 Z% t& l: [

    ; X* v, H2 t4 W5 A+ G@Target({ElementType.TYPE, ElementType.METHOD})/ F& O6 |/ ~3 E8 {: B2 i
    @Retention(RetentionPolicy.RUNTIME)
    0 Z" j2 \5 a  d0 ]5 M@Documented
    2 w- W4 K* Y; \0 i@Scope(BeanMyScope.SCOPE_MY) //@1
    . s# E5 A7 w5 Ppublic @interface MyScope {- W" l% E7 ?% U2 ~
        /**' |" E6 y: C. b# s- ?' r) s
         * @see Scope#proxyMode()  B7 d7 W, p0 o, N/ G. {& l
         */$ ^. v) \( }. t# Z5 J
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    , c; c' Q: h2 S6 L" E& |}
    7 G$ H2 d2 _1 d8 G! x( _6 e@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。. i; {$ z5 f/ U9 o7 S6 r/ M
    , @0 i1 H2 b/ n* n3 p& l
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS1 e$ A: i, b  }& [! n* m: z7 x/ _

    - k1 x9 A5 [+ c@MyScope注解对应的Scope实现如下
    1 T/ l8 T5 E3 |8 b2 c0 n# V- @# z/ d, b7 H' o9 r- q* q3 |
    package com.javacode2018.lesson002.demo18.test3;
    ( h: B( ~. v8 d; a9 c$ w, r: L& C. L3 |8 O2 R
    import org.springframework.beans.factory.ObjectFactory;* y) c8 g: t" k$ M& B
    import org.springframework.beans.factory.config.Scope;
    9 L" n& r. a4 ~7 m2 @1 p) \" Ximport org.springframework.lang.Nullable;
    0 y6 v2 @  V; q7 d$ ?; \, i# Q" c' z/ W
    /**3 ^  S# Y! _+ N3 U
    * @see MyScope 作用域的实现2 j) O3 i. r* D9 s/ t: L( n8 @
    */9 z- N- @7 t8 c0 P' H7 [7 J
    public class BeanMyScope implements Scope {
    2 U* ?% E2 s, \  v0 k0 Q
    & O- t- f3 s* O9 b( e: a    public static final String SCOPE_MY = "my"; //@1
    7 P2 v& a; R; t; H' s8 C2 L! Q" k- B5 v' a' s" u8 H( r
        @Override
    2 k) s# ~& U% E# ?5 S    public Object get(String name, ObjectFactory<?> objectFactory) { $ ^* U+ N9 j8 ?" g/ F: g/ M3 C" n' U
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2% G# P2 _7 J- V- ^% J7 B+ @
            return objectFactory.getObject(); //@3! ?; [1 m( A) {& B2 y
        }- |7 ?) F/ A# ^9 i
    ; ^4 f% m" k: G: W' g0 Z" K
        @Nullable
    ) k5 Q- g( r% m0 J" L) W    @Override
    2 q' y5 x# [; U( F    public Object remove(String name) {9 t3 a+ v2 a* [% l' }" r" w! ~5 w
            return null;9 y7 G( B' L. r6 n# ]
        }
    ; L/ }$ t# {' H4 i, Q
    4 V: Z" L# a7 N    @Override
    ) A  e0 ~2 c5 Q; z) Q7 n1 j# Z    public void registerDestructionCallback(String name, Runnable callback) {5 a- o; }4 }! N4 i  h7 c  P! ?

    7 y/ ?8 I  f8 U6 f6 I    }3 V% s$ x3 a% b( Z: A, f
    : N  o/ x% }+ _  ]" ~0 i
        @Nullable2 D' u, C; n( ]( p
        @Override! d' W7 {; v: J, U+ E9 i
        public Object resolveContextualObject(String key) {
    1 A' Q( j/ }( Y# c        return null;
    : ?; W$ J; a: U    }0 p( f1 b6 e) P
    0 u0 I) K- s5 U) V" A  b& _$ J8 K
        @Nullable
    6 ?: ]* R) D$ j: B    @Override
    + {  b( T5 p$ Z, b9 P    public String getConversationId() {3 i; `' _" I  _) z  M6 w
            return null;( m1 E+ c; g; C2 G+ Z* H
        }
    - u3 K- ^5 G& V, K}
    6 Q: |2 B" Q: h% @5 f@1:定义了一个常量,作为作用域的值/ l& m- |$ S8 W7 J9 T# k

    6 J" ^7 K  b1 C5 j4 M: g* h" K5 m@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果( i0 d5 C0 p; K8 z
    . J9 V' E1 z  }4 p
    @3:通过objectFactory.getObject()获取bean实例返回。  r8 Y; {' s1 I1 I; s4 z  m% ~0 k2 c
    3 v/ c# `  |4 @4 I
    下面来创建个类,作用域为上面自定义的作用域
    , g$ C+ G/ n5 ]: A' M% L% X
    9 O& `( y5 w) @& I. Mpackage com.javacode2018.lesson002.demo18.test3;, o7 Q% k  @- S; o( j, V
    + G& o6 Y) }% v& `
    import org.springframework.stereotype.Component;8 t- u, }; x& s* s) Z

    4 l: B+ @  \8 X1 s1 jimport java.util.UUID;
    ! I. Q6 m* w# |7 {" e7 l3 _  a& ?/ d: K* S6 N! t
    @Component
    4 U2 K, R/ y$ A& P+ b& }@MyScope //@1
    " @' R; D. L/ @% l0 @8 S8 vpublic class User {
    ! }; m4 \* f; Q8 T  I6 `! c% T- @/ R+ }" R! o& |5 }6 t! L  a
        private String username;
    6 U+ L4 z! g- v6 q* B
    " V8 G. o" }7 \) s) i$ _0 b! F; s  ]# s1 K    public User() {
    & X3 q" Z, X# t+ N5 }+ b        System.out.println("---------创建User对象" + this); //@2
    6 W2 H3 l% N: l$ o' K. u        this.username = UUID.randomUUID().toString(); //@3! \8 m2 w' e3 p- b
        }* ^4 G4 [* ~* c6 p
    ' S5 ]7 H8 I8 H4 S# Q& M# S
        public String getUsername() {9 W& Z4 _+ D$ A8 d, t2 n8 ?  F
            return username;4 N& ]9 W5 T( |6 y% K" A# `
        }
    + R2 l* p- d+ o! \) |% u3 O0 b+ {9 o3 V$ V* H) U  L
        public void setUsername(String username) {2 ^: d& N" @3 s; g/ ^( z
            this.username = username;
    6 B/ u+ W+ \4 J& f$ S# B    }
    . j& _: A2 W7 q
    & P/ C4 U4 G% S! ]9 ]}
    9 H0 g" b. N+ z  @) F# k6 ]@1:使用了自定义的作用域@MyScope2 T/ i! q/ [" n2 S

    7 f8 ?; A' G6 N@2:构造函数中输出一行日志0 b6 I& i- ]) z0 b) _9 P9 w+ u4 W; h

    : Q% g) A, v; O" O( p9 d@3:给username赋值,通过uuid随机生成了一个
    / j- }8 i. O1 g1 A% V: f' h* P3 {7 y+ \9 x9 ^2 F( x
    来个spring配置类,加载上面@Compontent标注的组件
    7 v3 X- R0 |* l. J. l
    2 o4 ?  A% c+ I6 Cpackage com.javacode2018.lesson002.demo18.test3;, Z" o' O3 m& [3 P- X
      {3 r& l% h+ A5 E
    import org.springframework.context.annotation.ComponentScan;  D$ Z: ]: M. c! a4 J8 V8 T4 q
    import org.springframework.context.annotation.Configuration;
    & I  [1 Z7 w: d( m7 N$ z" a  P* m: g; i8 }: s- Z9 W
    @ComponentScan
    - D- p" n; E, g' W# o: W@Configuration
    1 N$ C( u3 q; M- O! _& Gpublic class MainConfig3 {- B7 g3 e# i$ |  C/ y7 N, M1 Y2 ]
    }
    9 ?8 c6 g/ W. w. n下面重点来了,测试用例
    5 v  `2 a& d  \& D7 W$ L' k" z0 d
    @Test
    ' P4 d. ~7 ?" Y6 K. ypublic void test3() throws InterruptedException {
      b6 y- E& B. M5 a7 w# M    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();8 P7 e- E' Q/ R" O  t
        //将自定义作用域注册到spring容器中
    3 ?' g& j- E9 M$ b    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@16 _+ a( z6 {4 `1 L, J1 C( u7 i
        context.register(MainConfig3.class);, A- ^: Q0 x$ d3 [  J: I5 {) t
        context.refresh();
    ( t/ @1 x/ T/ w
    - a' H7 V3 f( M# w" u5 q    System.out.println("从容器中获取User对象");
    5 p2 D) X8 N% L, M    User user = context.getBean(User.class); //@2
    1 ~: `0 c3 s$ N3 A7 `; b. Y    System.out.println("user对象的class为:" + user.getClass()); //@3
    7 O5 N* Y; ?1 F1 @2 T
    : g, }% ^7 `$ M: }0 R2 E    System.out.println("多次调用user的getUsername感受一下效果\n");9 f4 n6 H% Y/ U# ]( R# T
        for (int i = 1; i <= 3; i++) {
    8 a: E* n3 S, [* G        System.out.println(String.format("********\n第%d次开始调用getUsername", i));5 s* m7 I0 H! {1 _* |# J$ o
            System.out.println(user.getUsername());
    : `* `/ U0 p8 N        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
      J1 U& C/ o+ m" K! a    }
    3 T* S6 [7 {7 }) U# c$ c/ F& M) h}3 s$ g9 {3 [5 P6 v4 m5 h
    @1:将自定义作用域注册到spring容器中4 U% `- _+ o! v* e' o
    % M' o! k: j# }8 r. ]! }+ z& [
    @2:从容器中获取User对应的bean
    5 e" |. y5 T* V8 h& l
    - Z( T) K* u, q- L$ u& ~) a! k@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的  F) S* a! ^! Y/ |

    * m7 g( ]5 \" O: y# ?/ q代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    , D. W% D; {/ X& O+ s' M
    % X* A7 U' T/ a5 S见证奇迹的时候到了,运行输出
    4 w: x% L- t1 k* @: \0 s2 w; N* L( \8 K2 Q( L
    从容器中获取User对象5 R8 f  C- g7 `
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331278 T& [3 P) q2 \0 w# T
    多次调用user的getUsername感受一下效果
    8 D( u+ x1 h1 J' W' p7 x/ D8 J8 ?: T, \6 H$ {, P7 D; ^- p
    ********' b+ L7 g3 _9 n9 r. q* g8 K
    第1次开始调用getUsername. t1 f, m# L$ Z6 b
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    ! G% P7 l' F$ K) n---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    % H8 e3 d/ Y, ~3 x4 l1 H+ {7 U7b41aa80-7569-4072-9d40-ec9bfb92f438
    5 F# n: [4 `9 ~3 r. L7 t& u第1次调用getUsername结束, p. N$ ^5 p3 S
    ********) P3 Z/ m. y0 m+ F
    * g, z" G, m; u( j, l% `8 B
    ********% h+ f! j9 E" \9 ?1 i8 M8 f( n3 {! ?( |
    第2次开始调用getUsername
    " q! M" ]( \- s+ @BeanMyScope >>>>>>>>> get:scopedTarget.user3 F+ _( j: K! U$ Z
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    ) b: n* {, a9 p1 ~01d67154-95f6-44bb-93ab-05a34abdf51f
    4 w) _, F) U( B4 M+ `6 Z2 \" }第2次调用getUsername结束
    / u# W. [5 m- D1 c********9 K% l. X& ~! ?4 u2 J$ ~. k
    : C5 Q: S7 O9 D- d
    ********
    2 [1 K3 N9 b8 ?0 s2 A5 X" l第3次开始调用getUsername5 w& R7 l, L$ _5 |* E
    BeanMyScope >>>>>>>>> get:scopedTarget.user  h7 P- T% _6 P) \/ B
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    # I( D  e0 Z  `& g$ q76d0e86f-8331-4303-aac7-4acce0b258b8+ J3 n, K2 l# N- ^0 I  [
    第3次调用getUsername结束: ^1 A5 v# R4 X$ _
    ********
    4 L  R$ v& p( `8 }/ x8 E& H从输出的前2行可以看出:
    0 y0 L& a4 V; y6 U2 }: X
    - ]+ ~6 n. c. G4 x调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象; {0 c' W2 D7 n" x9 H% M0 s

    8 u6 J6 [2 V- H第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。2 _- T9 E* `  G, R% m2 L
    ( H, ]8 R3 b' @0 G
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。5 x& U; ^, w7 G6 O/ f
    & I$ F( v1 N; o( ~* P5 l
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    ( u7 `* K9 Z- w1 k3 f* t9 t0 j
    ; J( q* X6 ~/ }! v  O) C动态刷新@Value具体实现- I) l2 a5 R; `& D, G) F

    9 t! z4 t( h3 }, y7 C- n那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    $ i$ I9 {# Z# p8 z( K' p4 i# O
    ( ]$ w3 Q9 H. y% t# ^8 n先来自定义一个Scope:RefreshScope, \* X: @8 s1 _
    ) ~9 [* ~1 H7 p3 u2 L
    package com.javacode2018.lesson002.demo18.test4;
    2 L2 S. B" Q9 I
    ; q0 P8 `" M0 Y  Y$ T2 uimport org.springframework.context.annotation.Scope;" [+ B) L4 R# P1 S& \: n; E
    import org.springframework.context.annotation.ScopedProxyMode;5 R1 R7 \$ V+ k3 b- o5 I
    " Z- h0 \+ s: P' l, z/ c: \4 S3 _
    import java.lang.annotation.*;
    ( H; s$ m% w# d% o
    . }& [2 j/ {5 y4 c* B$ [@Target({ElementType.TYPE, ElementType.METHOD})0 L% f4 k* O; T
    @Retention(RetentionPolicy.RUNTIME)
    : [1 c1 V: o* Z* L' f3 e@Scope(BeanRefreshScope.SCOPE_REFRESH)7 j/ S0 N* @! D) u8 T
    @Documented! A: W* l; R+ F
    public @interface RefreshScope {$ i# \0 r5 j5 a0 I7 ?8 h
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    1 y$ |5 E) _. G- h, L( Q' Y' i4 C}5 e) R$ h3 z3 L: p, h5 V
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置) o& _7 y; ~) s' V6 Z

    / h1 ]3 N3 ?# M3 D8 I@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    # y( z8 N4 v- d# m* ^  P
    9 v) i0 O5 c7 t2 j2 ]* e& w, x这个自定义Scope对应的解析类
    ) k  f. `1 r+ H! Y9 m
    2 b+ b0 U, T" B4 ^0 x( ?; f下面类中有几个无关的方法去掉了,可以忽略6 G2 B7 g  }0 Q  \5 t
    9 V& i; L! ]( a9 a
    package com.javacode2018.lesson002.demo18.test4;
    * d/ \2 O1 g' P% }9 s. U0 Z& J6 \6 [' c
    ! c, Y0 x' u8 E, [' D( P* j
    import org.springframework.beans.factory.ObjectFactory;3 G3 E" h5 H7 y% {0 y
    import org.springframework.beans.factory.config.Scope;1 j3 q3 x8 j. v& g* [
    import org.springframework.lang.Nullable;$ B" u9 O+ Z5 e  W4 H
    / ^4 @8 P8 x- v
    import java.util.concurrent.ConcurrentHashMap;9 y: z' G* z+ }

    & W' ~9 @- L8 G/ J6 v# ]$ ]public class BeanRefreshScope implements Scope {- w+ u/ B. h. v. D+ r! ^0 ?
    3 ]3 d6 }! {/ q! P
        public static final String SCOPE_REFRESH = "refresh";
    & x' N' l: o* q% O9 j$ V* `2 Y" \" ~. m# ?1 y5 _
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();9 o% Q' c/ u/ B& [( _8 _
    ' U# b$ H& i; W5 a3 G
        //来个map用来缓存bean
    2 A% C) e/ B. Y7 z: p, G    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    ! E  O- k2 W6 z! B5 v7 l: c& `. B1 R8 u3 P0 l* H
        private BeanRefreshScope() {
    7 U2 c6 Q; z0 I! i0 F- d; t5 B3 m6 b    }
      S/ S! ]8 g+ G* [! _
    ' m" I+ Z5 }  E3 h* s    public static BeanRefreshScope getInstance() {& B5 v' V* R$ Q& r! N4 z
            return INSTANCE;
    ) O: u8 i. Y5 U    }
    ! X* E) a3 I. d- _
    ; x# k% H" E. F; Q9 d* Y    /**
    5 _# L) a$ d' s3 g3 |  X     * 清理当前  K1 ?. s" P4 i, ]
         */1 H& \1 w( V' b' K
        public static void clean() {
    " ~. p7 E' u- l+ f$ I        INSTANCE.beanMap.clear();
    , q0 O! M  X4 f    }
    9 O. ^4 w' m; y: o$ P! m
    ! R2 l  ~) B4 g0 |2 M' A  J7 ]    @Override
    3 h5 F+ ~3 ~8 @* A6 q    public Object get(String name, ObjectFactory<?> objectFactory) {! W4 ]( `5 Z: a0 o( x$ O
            Object bean = beanMap.get(name);0 u4 Q% v1 i+ `* W. K. J
            if (bean == null) {
    2 i' i% J, j  x8 k2 H            bean = objectFactory.getObject();
      n. h: @2 g) }# [3 w, B            beanMap.put(name, bean);
    1 R! N3 O0 j7 H        }
    / k% {5 P- N/ e# Q2 r        return bean;. j( s5 G* Q! Q& {7 F5 J2 F
        }
    ' V& B2 h. ?( j( H& D0 W/ ~& _% B8 t  D5 r3 N
    }
    ) o* o  E2 G; l0 C8 X2 M上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    " H. Z' U' o& _) |: v: T4 c3 E0 r* a; r) S
    上面的clean方法用来清理beanMap中当前已缓存的所有bean, t& O5 T1 L0 d0 t

    ( c  [4 v5 B9 ^2 d. k+ P7 {6 ], ^' ^% |来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope/ ]5 t6 r% n5 `: c; b8 J

    / b9 i. q) N+ V* \3 ~# d! E# D5 E  Lpackage com.javacode2018.lesson002.demo18.test4;
    : \& o+ z0 p  R/ v
    5 `. a; V9 M2 Mimport org.springframework.beans.factory.annotation.Value;/ X5 Q. L' d' f, ?! G
    import org.springframework.stereotype.Component;
    ! w& Q1 V& j$ F7 b9 v  O- r7 e; b! h" p# |! y1 `7 \, `9 c0 b9 e
    /**, u& Q7 l. x( [* o8 E4 }9 d
    * 邮件配置信息' x3 p; q  W2 N4 o
    */
    $ E  p+ s5 X5 d- e7 t. B@Component9 W6 Z' U) _) T% m9 k
    @RefreshScope //@1
    5 K$ M" v  p/ F/ _4 gpublic class MailConfig {$ ^/ X! z( n/ h, a

    2 V* H+ U/ i; Z    @Value("${mail.username}") //@28 T9 ]9 Q: {& `8 U1 S
        private String username;
    % Y/ a/ c0 ~. c& b) C1 k. V5 I9 }6 V7 [+ W
        public String getUsername() {7 X2 g/ {: Z5 a5 g1 M
            return username;
    5 e; b* y" s4 P/ N2 C; s2 @9 f5 }    }2 Z9 l- ^) l: v/ h# C: X* ?; p
    4 W. L) y# Q0 T: `( ^6 {2 w. D- m8 Y  Y
        public void setUsername(String username) {
    5 _4 Q, S: W3 |0 V9 y$ r. ~  X. A        this.username = username;8 k$ i, N' {- ^6 V* S/ q; \. K$ {
        }- x4 o! ?3 F" W# C& b1 Y1 I( C, f

    * \+ C6 r# T2 `    @Override, p5 D  w( e  @: a+ n3 H5 B
        public String toString() {/ z% q. `5 y  w! U4 e; o: o8 b1 `
            return "MailConfig{" +
    1 g! d0 H) D" s* Y5 S/ F+ Y! `                "username='" + username + '\'' +' n' d* N; \3 u1 d8 \3 z/ I
                    '}';& U& K$ P: E$ ~+ ^
        }$ H  l0 T, _/ p
    }
    * s  R8 G! L( Y# N6 S4 J@1:使用了自定义的作用域@RefreshScope
    " @& {: @; B% g2 s6 X7 F9 z
    ' }) c  x4 T2 A: L' Q' j@2:通过@Value注入mail.username对一个的值& a2 R# X# w5 O$ {" E' ^
    3 ]" E, @" P2 X
    重写了toString方法,一会测试时候可以看效果。. }" s; o0 C7 ]+ ?/ O" G' J
    ( w$ G( _% A2 t4 r$ \& g
    再来个普通的bean,内部会注入MailConfig
    & G, ]4 W; f: {  N( t$ Y, K7 }' [, z
    package com.javacode2018.lesson002.demo18.test4;
    2 j7 X1 i5 S& v; f! {% w
    ) U# |3 g" Q, simport org.springframework.beans.factory.annotation.Autowired;
    0 G$ [7 a% b9 D% y4 b: X" cimport org.springframework.stereotype.Component;. b% Y! b9 W, q! h% f; c$ q; X
    * C3 L7 I% M' r& L$ k
    @Component
    5 w$ m" T1 _) t5 C, E- rpublic class MailService {! Q2 [) @9 R! J# \0 Q( j& Y, _
        @Autowired
    ' F0 x* n% B3 D    private MailConfig mailConfig;
    1 ?8 g  K6 a+ l6 M, Q4 x4 `
    " @0 O9 X2 U1 y6 s5 ]    @Override
    . [/ v6 s4 Y' k8 l5 r6 \    public String toString() {
    3 C9 P" A) l/ t5 [" h        return "MailService{" +
    4 Z3 V) o/ T" x                "mailConfig=" + mailConfig +& m/ ^2 O) W" _
                    '}';
    5 J8 V/ O: F# Y' @/ ?; U' X    }% f  M- Z  H* _7 S+ p6 h* q
    }
    $ @' [+ x/ d8 j代码比较简单,重写了toString方法,一会测试时候可以看效果。
      X  ?  P4 _+ z# z) K; ]" Q
    # ?' h; y& a1 y1 _: Z% f来个类,用来从db中获取邮件配置信息+ f$ }5 E5 Z( X, d$ L
    . S% F# U1 T8 I" U+ l) V0 ~
    package com.javacode2018.lesson002.demo18.test4;1 X! Y9 h; ?& y1 N; |& ~

    : N8 \6 v& z7 L3 K; r$ I$ eimport java.util.HashMap;) t" Q/ H7 F- R5 Q- ^9 h2 n6 y! @
    import java.util.Map;# V" N5 d* e+ k( b
    import java.util.UUID;
    - ~4 ?/ b/ v, q& v1 b5 G) B9 X5 ]0 ~8 Z% o1 [/ B7 ~
    public class DbUtil {# g( w6 `8 }0 E
        /**
    6 W4 X: ]" o" y4 _% ~     * 模拟从db中获取邮件配置信息
    7 t! m' S4 `5 A% g     *
    ) @6 d, f8 Y5 M5 ]* K4 ^# V, X8 J     * @return
    0 F! b, p7 ^' F2 V8 M" S     */
    * g1 I. c# u; Y8 M* S    public static Map<String, Object> getMailInfoFromDb() {& Z* l5 e1 ^* Q; P' C# y
            Map<String, Object> result = new HashMap<>();7 A. _: F  n6 ]
            result.put("mail.username", UUID.randomUUID().toString());4 z0 j: g# X" `, P, d
            return result;6 o5 L+ D1 t7 M, r! U$ g( o- J
        }
    5 {+ M0 M0 R0 P% k/ ~}
    4 O5 L6 k' d7 N' F& u* ?( V6 [来个spring配置类,扫描加载上面的组件
    8 U/ `" D' U3 q2 A  E4 @$ S% N/ C2 c, g& B
    package com.javacode2018.lesson002.demo18.test4;
    , a" @" _8 X2 p: ?' o
    " a6 _% n" K  W$ V) ]: C" T, ~6 `. Himport org.springframework.context.annotation.ComponentScan;% F- ?1 K+ r" L' i
    import org.springframework.context.annotation.Configuration;
    ; P$ G- R, p- R0 j4 B, \# N* A* x2 b: J8 i* `0 j
    @Configuration8 \6 r' a: U& s" g, F
    @ComponentScan
    3 o# e& L& h0 x. lpublic class MainConfig4 {6 F) w5 Q. ^7 K1 r) @0 ~
    }) }- L6 B! K0 b$ d- V! J( S7 @) Z
    来个工具类
    6 |$ T% c9 n: s! ?. f
    / |2 y. E& K5 h4 j2 s2 a内部有2个方法,如下:
    % d7 s+ D8 v: Q+ Q. p5 }2 Q2 a! ]# H0 A: ?% L& Z
    package com.javacode2018.lesson002.demo18.test4;
      l* P6 `. g7 b/ f- l5 W" c' Z: N2 O
    import org.springframework.context.support.AbstractApplicationContext;
    5 ?0 q6 s3 S! l7 w# Iimport org.springframework.core.env.MapPropertySource;
    ; |5 m) {5 H+ q  ]% T) h) s
    5 m% T. i7 E5 z% t6 m6 D- \* Fimport java.util.Map;
    , ]0 M: j5 {, i( `! N1 X6 K" T
    % B; Z; N  J/ q4 L: Ypublic class RefreshConfigUtil {3 l  I) D$ l# J% D
        /**1 g0 _+ L3 P+ V7 @
         * 模拟改变数据库中都配置信息! W: u2 W' L! J" O, g3 j5 i9 L6 U
         */
    5 p( S. f  O# N1 ?$ p7 W    public static void updateDbConfig(AbstractApplicationContext context) {
    % d5 X* _) ]4 H' {/ D  a        //更新context中的mailPropertySource配置信息( s/ S7 O+ E( e0 s" N8 n- h6 T
            refreshMailPropertySource(context);
    8 y1 q0 T# `6 e
      t, ]% o7 D, t5 r3 C        //清空BeanRefreshScope中所有bean的缓存" G) }, w" d1 f1 ^; A4 k
            BeanRefreshScope.getInstance().clean();
    3 a8 V( J- D- l8 @" l    }0 a+ a9 T' E0 Z3 Y, S: E$ i. C

    - X" u% y" E5 _3 G0 S3 ?    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    ) i$ l2 g1 Q0 C* G7 j5 n        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ( t, C. U: m" X4 ~        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类); C7 t& Z) M2 A
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* T; T! a) o0 N# p( x3 {! O, C
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);4 [$ r/ e$ n6 n+ }
        }
    * ?- Q0 \' \/ j9 Y  y7 t5 g: i' \6 r/ }/ \
    }
    ; k% u( M5 v  lupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息; M7 ~, Q0 D& ^* D* _* c
    * o, ~- U1 _/ s0 A& D) `
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    : @8 ?" I* }9 M# G' _& @0 l
    $ [- C- [, @! N6 O) Z% F2 l来个测试用例
    9 u5 {7 G; ?2 q* s! G7 }2 f# h# o1 N( L8 C1 e" _/ X
    @Test
    * G0 l; [# N& x* X. dpublic void test4() throws InterruptedException {- {, y6 V! J9 {  F+ ~
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ L  a: U- g$ J" Q( u    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());2 {4 a. M5 }, y7 w9 o
        context.register(MainConfig4.class);9 f2 Q( x3 u- A- P* n, w: D
        //刷新mail的配置到Environment1 c+ J! S* t3 `; M  \2 Z4 ^
        RefreshConfigUtil.refreshMailPropertySource(context);
    ' ~% Y4 \) s9 W2 }* }    context.refresh();
    - x4 G; R1 T- Y* V" O& X8 K" _# O" X5 q; v" u( k6 u- o4 b" W3 a& P
        MailService mailService = context.getBean(MailService.class);2 I, E& Z8 u1 B6 l
        System.out.println("配置未更新的情况下,输出3次");+ I4 G: M$ A& p0 z* G
        for (int i = 0; i < 3; i++) { //@1
    " k, ~& L: I3 i, K. d) n        System.out.println(mailService);
    2 M9 d* f! C2 E/ ~6 n        TimeUnit.MILLISECONDS.sleep(200);
    $ U6 v- o; B0 n! N& z% y    }
    ' r8 ?! b+ P9 a, L1 x  m& r8 g2 V
    / x0 P, O0 L! M7 x& ~. n7 n    System.out.println("模拟3次更新配置效果");
    8 I3 |% k3 g: s  `: V1 {+ F    for (int i = 0; i < 3; i++) { //@21 |5 Y9 y$ Q+ v+ d; P
            RefreshConfigUtil.updateDbConfig(context); //@3# ?2 S+ p9 A' h/ d1 i
            System.out.println(mailService);% P/ q8 E  v3 P( v- |
            TimeUnit.MILLISECONDS.sleep(200);
    % }$ E, ~0 _! K1 e    }
    ) d! b' }1 Y4 T% J* _9 U}
    0 F3 q, U+ {$ e: M! h@1:循环3次,输出mailService的信息
    + u- m+ P' D/ F" H& `  D& Y4 W- s: h2 Q5 a' I
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    + z% F+ p2 `/ F
    6 \) `+ w: a5 j" d6 T3 c见证奇迹的时刻,来看效果: s* R" Z' Z4 _) z& S5 @& R
    ) Z; b6 x- T- Q! h
    配置未更新的情况下,输出3次
    ' _7 {/ H+ ~* [0 p7 g2 @MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    4 ?6 v, \( ?9 V: M# F/ {: eMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}/ k% p  W9 {* \
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}8 r" d. w' w) X8 ^' [1 F
    模拟3次更新配置效果/ D* D' `. R2 O9 }1 A. ~
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}8 k, b- z* L3 D9 A7 _; R8 F" t
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}. j3 _* `' a  \' |
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    # B5 J1 P* S" ?* t0 u' R& \上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。) [5 y3 u4 s8 j. r4 B

    2 B5 Z1 g) \% S# o5 i( {小结
    * H, y6 m. D  q0 D2 A& w. q# h* {6 w1 E3 u, R- G
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。6 g. L# C# l$ i9 b

    6 E3 p: m3 B/ i! y# J  N' h有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    ) G% n4 n9 U1 q, D- \
    7 Y, b5 E4 m) }9 N5 S  E4 h总结# s" \( q% l4 h6 N

    7 f6 j& ^' H2 @  y本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    0 l0 m) o/ S" T5 k9 [8 M; K+ X* p  q* M( ?, I% G6 k$ f
    案例源码1 Y9 r4 T2 r5 C( P! k0 X- L

    ( u+ a. m4 R$ a/ I$ H5 shttps://gitee.com/javacode2018/spring-series
    # J3 z7 J( n/ v3 q' a" _% k路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    8 Y# o2 U: V1 s; R7 c' f' ~
    & p, Y: b0 V# x* ?. d. y+ `Spring系列
    : t' d7 O: S- d' W0 ^( B
    / u2 `" v7 q: \Spring系列第1篇:为何要学spring?
    4 E  d, a2 ~, \2 I- x1 D( ]: p
    , G* V$ v3 ^& ?: cSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    * w" F0 A" ^$ X" a4 ?% u. C8 I7 H5 V0 o4 N9 z7 n
    Spring系列第3篇:Spring容器基本使用及原理4 h9 i0 ], I8 a1 O

    & h7 Q' Q& W+ c+ a7 X8 f5 O! \Spring系列第4篇:xml中bean定义详解(-)
      p( K7 R4 D  N% l/ [$ m
    ( M2 t$ _" f' E8 D, ^Spring系列第5篇:创建bean实例这些方式你们都知道?
    0 m3 x1 G$ S3 v+ g+ P: M
    ! q) H# S+ N% c& y% T9 {- N7 I- [1 hSpring系列第6篇:玩转bean scope,避免跳坑里!; o, ^' i, f$ s1 R$ V
    % l% ^& v" u' z  ^
    Spring系列第7篇:依赖注入之手动注入
    / Q: I" ~' H6 Q& O) M# i7 @5 y3 Q* _( s/ t
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持9 E: I" A1 v# C* a5 h4 q1 e2 `

    3 A) J6 C5 T& h; e% kSpring系列第9篇:depend-on到底是干什么的?
    ) u' U' E% `% }
    7 E; q' B" h) j9 Y- G0 {* Z; _Spring系列第10篇:primary可以解决什么问题?
    8 k& t% z' a5 g% y3 {% V9 B, y0 u$ D
    & r5 _' [8 k2 TSpring系列第11篇:bean中的autowire-candidate又是干什么的?
      u$ O  p; h# T2 k. o* G
    * |0 E0 b' s; X" N" u9 ~, xSpring系列第12篇:lazy-init:bean延迟初始化& L: f0 W0 O) c
    * z5 ]/ J9 g, D1 Z9 M2 A
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)0 [' t" u7 e& G5 e+ w

    + v* o. C+ K- I; b0 M2 N: V$ [Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    6 q- O* R  R' v- F2 M" P
    ) @  b7 n  v' F4 v  z0 dSpring系列第15篇:代理详解(Java动态代理&cglib代理)?% e' d7 k0 s0 E2 f
    , r; h* ~. D, ?# F# V: \( v# ]
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)( a/ q8 T% ~  o
    " s6 F, |+ B, n7 f2 m. k' U
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    4 F; L" f5 o* ]
    * H* i/ l0 f. z/ c+ MSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)# }# H5 P" I$ \

    4 w/ n. [  N2 d8 Q; p" DSpring系列第18篇:@import详解(bean批量注册)9 L9 W! O/ k! C" \
    & p1 R' F9 p3 C& k
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    : K  \5 n/ z1 C: P. Z
    # j( s8 {: ^& ], Y: f3 J( C& s5 h; SSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)9 X( T0 M4 q! S$ g

    9 `" z/ ^0 ~4 X1 tSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    9 L& D5 J: |! D: I) X6 F
    " \! t: B3 K: }1 I" |8 A, Z3 }! P$ F- nSpring系列第23篇:Bean生命周期详解; S3 ^5 R; ~5 F4 H
    ! H0 M3 S) `8 x; E% t  d) ~
    Spring系列第24篇:父子容器详解1 \% r8 R! ^1 T
    # [' C3 d6 b' g8 b1 }; R+ f5 L( W0 Y
    更多好文章; e" e6 g5 h. v# s: D

    ; V5 i* y0 ^) o: x: G1 eJava高并发系列(共34篇)- r3 t8 S2 N. u; C) m4 s$ ?9 t
    - Q8 V& J/ c/ ]* V& ?
    MySql高手系列(共27篇)
    4 ^% h3 |- u9 w) l( M
    7 I) I. j7 }. G# LMaven高手系列(共10篇)
    . Z* J& O, V# Q$ N! T) y9 X/ E% k3 K6 R" B" J1 m
    Mybatis系列(共12篇)
    ( }6 m$ v  [) `7 d. F8 J/ E1 j, ^% z: h  w
    聊聊db和缓存一致性常见的实现方式
    & Z- n6 D8 i: q; S) T
    # C. q  G6 j! g0 q7 {/ u; N2 H接口幂等性这么重要,它是什么?怎么实现?
    . A3 W! D# X( a9 @7 |' D7 ?8 ]2 ?' t# Z, L2 B! @! K
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!/ @7 J6 y# M3 y! D+ E7 o4 N  M
    ————————————————7 X$ f0 W. ?1 F1 ^# K3 A7 S+ j
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。+ }& ^# R, P' I9 |7 `% G3 t7 I
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    3 {6 e& D, h' F  O2 a4 Q6 H$ H  X( `2 d0 F+ ?# f' ^, G2 o) n
    , i4 e' N, t; {" B5 n7 S
    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-3 08:52 , Processed in 0.451884 second(s), 50 queries .

    回顶部