QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5102|回复: 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!
      h" r1 F" f. I3 ?" V# `疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    # T+ z7 o" Q! k) M. Z+ f
    : B( \2 m# u' H* }; @& F- o9 c面试官:Spring中的@Value用过么,介绍一下
    4 j/ V1 R& |. L1 G$ ]
    : s3 y/ H/ C+ v( i我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ; x! V7 z1 h3 X
    - h' k) j3 {, j# F面试官:那就是说@Value的数据来源于配置文件了?  @( v% I* T# j- a8 M) `
    ' d( D9 [4 C5 |) g* e) ~7 d$ w
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置+ t/ e, K9 |0 P
    * S3 _5 S  W  R6 L# ~. ^& ^( y( l
    面试官:@Value数据来源还有其他方式么?& c/ t8 d, l' O( e0 Z6 ^, p
    ( b6 c8 ?* B" p0 W. T  c: K; X
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    0 l9 A) z" s2 b. ^% M
      |7 D/ _# Q& }9 X# a) F5 @面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    $ E+ z  e2 R+ g9 T# A, P  A4 y5 d5 D% p  }* v
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    0 s3 C' I' V/ M* r3 ?9 _+ _3 h7 N2 s: q+ F
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    8 _: i5 N0 ?/ B2 L9 H  u" ]5 P; H! H  t( a3 J6 {0 N3 |
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能) v& k9 `/ M% y* p% Y, z

    ! N. L. u. }9 }) O7 k面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?" m2 _; @6 d' _4 }2 m0 {

    : U* P0 K; ?& E( P我:嗯。。。这个之前看过一点,不过没有看懂6 u$ P" W, m: ~4 \8 B6 h% U

    ( K8 |! c6 x' h: g' ?2 D4 p面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    3 s- S4 }1 g. z8 i% _2 C: T1 c$ l/ g# ^/ \9 X' |( o# j1 N
    我:3万吧; R7 _5 r6 w- p
    2 a9 h- Q. {6 X1 k
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?& Q, L  [1 N; @

    ' h2 w& I; ^1 W' ?我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    & }( u# @% r5 m9 ^
    : s! I) r8 i  e3 j" s, g' t面试官:那谢谢你,今天面试就到这里,出门右拐,不送!' P# g5 P9 L7 P+ z8 R4 D

    0 b+ }7 L3 c$ `* O+ D我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    9 [3 L8 h; C& ^8 e- F  f" k0 v$ l0 |! R
    这次面试问题如下& e5 n( `4 b  d; |$ p+ w

    " M' ^+ Z' J, _0 Q) m1 p$ N@Value的用法5 W) t! @8 u' L$ w) }! \
    , \* E1 Q' E$ N8 f& P  ~0 S
    @Value数据来源6 R; W& i) K% f; n

    7 [1 |: L# W* s4 c+ j" m@Value动态刷新的问题. j" y' _: n. w  w9 V0 }2 u% c

    ( q: w  U# R- W: |# y下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。2 m5 t8 `/ i7 t: l3 r0 F

    9 e, ]. z) {6 _6 n  R2 r@Value的用法
    $ Q( D/ s( p% f1 t/ a
    + w( U& D; ~1 P5 t+ B7 J系统中需要连接db,连接db有很多配置信息。
    . ~0 U4 u7 K9 |  x$ V0 H& g6 T% ^" O. b4 U0 d; z: t" D& L
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。. o5 a3 I& t. ]% W( h: t! d
    , M) t( i9 n; F! s' b- p
    还有其他的一些配置信息。
    ; W  j1 a. M; \
    ! a( ~' ]3 n) [1 R9 Y我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    * i- W" \7 T+ r8 a) P
    9 s2 c& e% h% B+ }+ l那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    4 G! t& X9 W8 ]6 A) w5 B; T  o2 C( T- [5 f+ G. R
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    / T( F5 G' k. F) e; X9 c3 d) R* r: O1 ~6 J2 I* W% p
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。% t2 r) x3 c8 z; g: h  J! l$ D

    9 g! I! z5 {5 _8 E@Value使用步骤
    / s: V) W4 J4 P7 A) {$ S' O9 k+ p) T* ~  N
    步骤一:使用@PropertySource注解引入配置文件* `$ p; b" }2 w; a/ M

    $ V  y) P% y7 A将@PropertySource放在类上面,如下0 `8 ?- R# x9 g0 |" Q

    5 q' R# m7 N' Y: {2 x@PropertySource({"配置文件路径1","配置文件路径2"...})% e' i* W3 ~6 x1 [
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    6 }6 I2 l8 ?; q) r5 Y; X7 ^0 z
    5 Q( ]) X9 W. f1 N# F2 e如:
    ; Q# E9 b4 z1 c& e9 Z; {; ], A& z) X7 |# M1 Z
    @Component4 R; P6 F0 ^9 M, r2 n% G
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    # j/ u; V. K9 X7 _, Bpublic class DbConfig {
    - ?* ]" q, s. x* Z0 B}) m8 O) d2 Q% t
    步骤二:使用@Value注解引用配置文件的值
    - c3 s, |/ J; ?/ H
    # Z; w; P  f% |8 ~通过@Value引用上面配置文件中的值:, y0 r: d  ^7 g. y+ P+ R

    6 M" L% P2 q# o# K! q9 Q1 m语法
    4 k8 i; l! O) ?  h3 f% h
    3 \7 L( _7 y6 `1 P. ?% H@Value("${配置文件中的key:默认值}")
    ; l' N5 b1 Q( p' c# M@Value("${配置文件中的key}")
    ! ]( r+ L' n1 ^. I5 ]4 _5 O7 V如:5 i  I5 d" X4 b) y2 o; d2 L

    0 R0 a' a* Q& k" v$ u' `@Value("${password:123}")
    ; S1 ~: q; N+ r$ V$ z3 ~$ d6 |上面如果password不存在,将123作为值
    / M/ k+ S9 o3 D  h, k5 }
    8 U3 l; }& S% _, l9 E% t@Value("${password}")" ]: v! T- u/ U( R
    上面如果password不存在,值为${password}
    * S: n- {0 Y9 |" V  ?$ ?$ K% O7 r" H8 [/ w# Y7 u: D/ V1 e, [
    假如配置文件如下
    & ?5 M+ R/ @9 T1 P- |( q
    ! D7 N  p; ~# i% E& I- N) j' ijdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8% L- C8 P2 t0 f1 m" r( i
    jdbc.username=javacode
    / a/ @! e1 \/ g$ ]jdbc.password=javacode
    0 W1 f2 n8 O9 V6 f* ?2 e使用方式如下:; Z  ?; f/ z# M6 U

    2 B7 t2 ?' L  L4 }@Value("${jdbc.url}")+ N; Z8 k  \! q: v2 k
    private String url;
    * {8 J5 c# N8 X
    : L8 Z! z) v' D6 i& d5 t. F@Value("${jdbc.username}")2 J5 n; Q; U8 R; x8 v
    private String username;
    - Y& x$ [: ?/ x' X  C4 D, Z% U; |" v* i4 V
    @Value("${jdbc.password}")
    1 h* C; q" k3 ^" T! y! g8 Gprivate String password;
    + r5 P8 i6 A4 \$ Y1 I下面来看案例
    8 C& @2 x2 L: ?" a8 M, S* Y6 z- S7 H; D2 f$ b) N* S
    案例
    # @3 C2 h7 v- O* N. B; u: L7 ~9 a
    ! X) M6 _; ?! P' }0 c来个配置文件db.properties5 d& E: d: d2 W$ }

    7 B% r: z: Z6 R" Yjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    / j0 L  }$ o2 U( G3 c  Vjdbc.username=javacode# R& o% K% t1 j/ n, S6 v
    jdbc.password=javacode
    1 F9 }9 q9 C1 C来个配置类,使用@PropertySource引入上面的配置文件+ |& t2 z& s9 `' @

    * M8 [% T! [  R. Y' Qpackage com.javacode2018.lesson002.demo18.test1;
    3 e) I9 l6 q9 w7 [% u: [5 s  U: [5 j0 z$ g( X
    import org.springframework.beans.factory.annotation.Configurable;
    - ^# A7 ]" a  g, m) e. Dimport org.springframework.context.annotation.ComponentScan;
    7 l1 v8 q) m1 aimport org.springframework.context.annotation.PropertySource;4 z, s* O* E5 ]4 {4 ~2 R6 }9 U( Y

    - c$ m5 B4 `+ L5 r@Configurable
    ) {1 w; [! k6 x3 r0 x* v@ComponentScan
    + y- b* A; U: |@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    5 K# b. I. j$ _* dpublic class MainConfig1 {" H5 H. w  b/ h4 [
    }4 A' M8 f1 C& w) s4 k
    来个类,使用@Value来使用配置文件中的信息5 P+ U: v& ~2 k( {$ F7 V$ ^
    / w, v+ P' T9 p5 j7 u2 O4 n
    package com.javacode2018.lesson002.demo18.test1;
    / S, W7 ?( n$ j; V
    + _2 O( p; h& L0 I5 mimport org.springframework.beans.factory.annotation.Value;
    , _4 n  _0 C, m+ Jimport org.springframework.stereotype.Component;7 E$ |1 B& `9 Y2 `+ j+ t9 C
    : `: w- b$ z( K; p
    @Component
    ) m* G' O+ x. T7 K2 B3 I6 `public class DbConfig {
    1 `$ X/ J* Y4 {6 {% N" I- P$ ]  L
    7 u5 O0 Y. {9 D; C' \; m    @Value("${jdbc.url}")& t( M  b6 Q% f5 g, s
        private String url;, ^$ ]) y9 Q5 ^8 D% V+ R. v7 ~

    4 u" E5 ~& Z4 L/ [    @Value("${jdbc.username}")
    / k( u6 M5 n0 l' K    private String username;
    4 L: s; V, C  O! W. R& [
    6 ^/ ~/ o$ g& A) k    @Value("${jdbc.password}")
      M/ S# t0 V; L4 ]  b! U    private String password;8 K8 N* j+ Z5 }) r

    8 D& U% {& K4 p# z0 {; x" B9 X    public String getUrl() {1 N: D) _3 f$ D
            return url;  D% K8 a( c7 f5 |: F: n( n6 n5 }
        }( F1 P' `8 F3 @! h# g2 ?. ^
    # |5 \# f8 H% E% O1 z
        public void setUrl(String url) {# G# C- h! I  E$ y  R$ i( t' Q
            this.url = url;8 \* R9 p+ r8 I& x  a# C8 k3 _  b
        }# O! [8 x2 R' S$ X' M
    ! X4 @  c) h  O0 y" ~3 n
        public String getUsername() {5 |4 ?! c8 _. z
            return username;
    . `* R3 k) t/ M: t1 O    }
    ( O6 ]* {9 x8 }1 r/ l
    8 b! d7 U! u( @. A: d    public void setUsername(String username) {: Y- q4 z2 {! D4 u
            this.username = username;4 d, H8 x) }. k+ T2 Z2 B; T3 m6 T
        }
    . v4 f8 z7 t! m" C! p# V& g8 m$ _: m# m+ C+ o
        public String getPassword() {. C/ w7 _* ~$ v' s0 \8 }5 L
            return password;
    0 b7 L2 |: z* Y* V' `' B    }
    - A# B& W# |2 W
    ' Q! O4 q- d6 l* N1 Y) E& l3 s4 y    public void setPassword(String password) {
    ! l, b+ R! `) g$ F% A+ {        this.password = password;
    1 [. D$ i* X; O- t: `; e    }. d* L' y. Y) v/ b/ Y4 P2 T
    9 b" U9 M' o/ b4 P, O# G
        @Override/ p+ `. a6 o) ~0 `# U/ E8 I# H
        public String toString() {5 h& V8 o0 s" A# W% E* _
            return "DbConfig{" +
    , o/ x  V" R/ H2 C5 s                "url='" + url + '\'' +
    1 F, N5 W  B0 K- X9 Z$ |1 v3 V                ", username='" + username + '\'' +
    0 I0 t: T: `/ Z                ", password='" + password + '\'' +% E+ w% F% b( Z( G1 F! k2 v7 }6 r
                    '}';
    ; W  U8 ~1 P8 ~  M, a0 ?    }, C# u4 p  G+ X* A4 V5 i5 H
    }, B- v, J$ i8 J2 \. ^* z
    上面重点在于注解@Value注解,注意@Value注解中的/ Y9 T0 u9 b' y6 A! I

    # D) {9 C, l# ^* M: B5 ~$ T5 L来个测试用例3 M& x! I2 t0 c$ A; x0 H

    & ^# Q" c! A9 ~0 H5 S2 a( lpackage com.javacode2018.lesson002.demo18;# o8 Y/ e0 ?1 u7 S. l( @) t
    ! g0 ~' C5 @3 B
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    9 |" q2 F: v( z; Yimport com.javacode2018.lesson002.demo18.test1.MainConfig1;' k/ m" U8 @/ ^- Z
    import org.junit.Test;  W4 g3 f& N# y% p
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    % W0 M2 a3 q5 O8 x+ }& L7 d( n8 p. h& S" p: D/ m
    public class ValueTest {
    2 b9 J4 ~6 }  J$ J, f, P% |
    # X8 U# J( ~2 e" t* c$ y    @Test
    - p% {9 Q( t  S. ?    public void test1() {  g9 b5 R: W& O: d8 F- B
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();* k1 L# `" N* h2 ]. Z( Q2 X
            context.register(MainConfig1.class);3 x0 ^% V- _: n( h( q
            context.refresh();! f. ]  x# b0 e: ?
    8 q) s1 B, ?  J8 |0 M
            DbConfig dbConfig = context.getBean(DbConfig.class);
    . m$ N% l; s7 S1 O: S" V        System.out.println(dbConfig);; o- l4 D5 T  d. z1 S2 }" Q: j1 E* `
        }+ N" Q0 F1 y) Z2 T
    }2 L7 c1 P2 C+ b( B$ G( \/ \9 G
    运行输出: u" g" o: q+ z4 Q
    " t- ?8 d+ D$ t6 s. ?4 v4 U
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}5 p4 G, h2 q3 g% `% d
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    : ~# A- {% d0 e$ h+ I$ [% O' `2 x  t) s, O+ p0 T
    @Value数据来源
    $ N' _! E) [2 D' r7 {
    , S/ P& \7 V3 T& J2 o1 `6 t7 C通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    & g( [# a; v3 g, e5 Y4 D( k, O& m5 v
    我们需要先了解一下@Value中数据来源于spring的什么地方。: }8 @$ s! H5 O0 @
    # w, m. P) c1 _7 L1 z7 `
    spring中有个类
    & l$ p) v( g) @% B. E$ i) I
    2 ~2 o$ t" H: h  m. qorg.springframework.core.env.PropertySource7 z' R$ L' P$ ?3 w! o
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    / ?; `; j& {6 o' Q! s
    ! F# P2 H/ _0 u  ^* Y内部有个方法:  L$ H6 g4 p8 c$ R, j: L- M

    . L* o8 H2 Q( S, r6 C2 Rpublic abstract Object getProperty(String name);/ P* O& Q# p1 a. i) H/ o: Y! O
    通过name获取对应的配置信息。3 k$ u3 j8 _$ p, ], V2 `, ~( V

    8 I4 N6 g0 {, p- g系统有个比较重要的接口/ F" }9 v, n; v# H( g
    ( R  V& u. _% }$ W& y& M4 b
    org.springframework.core.env.Environment
    / M9 R0 Y, b# Q/ d& H8 J+ ]0 k用来表示环境配置信息,这个接口有几个方法比较重要
    - w& z* s/ z6 J6 B' _; b6 K( e# r: m" u3 S' i; C! l) R- M) P
    String resolvePlaceholders(String text);; _; V' z3 v: R4 T* |
    MutablePropertySources getPropertySources();: v/ @* ?% i& l* u# _) l4 D
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。+ R* n. h9 I3 c1 N7 a" w2 e
    " l9 I  \, E! T) _, L, c9 U" O. {
    getPropertySources返回MutablePropertySources对象,来看一下这个类  W# n4 `+ e6 w6 @* C
    & u1 s% V( x3 s; F) H; X
    public class MutablePropertySources implements PropertySources {4 }! U% n) ?& J' }/ w( a! x

    6 c3 V2 N; e7 f9 L    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();- H+ q0 O) t9 I: a9 e

    : J/ X5 r% ]" e6 K! w6 I}. X- G# F- B5 @
    内部包含一个propertySourceList列表。7 l( H7 G. V& |# J& W
    % c  \  u+ a1 Z7 e* O' G" _" _
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。; t$ \( n7 v; ^( `* F

    7 x! i; R. V- c  G1 U% e# I; p大家可以捋一下,最终解析@Value的过程:* W3 r; ~: m3 u$ k! i( W+ {: A
    " _6 z1 t  s' M
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析0 p  e3 ?# n) f1 [5 x" d$ s
    2. Environment内部会访问MutablePropertySources来解析
    3 q1 Z2 d6 u+ u8 ^" N& b3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    7 _" Q. o2 S* g4 e) X" X通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。  ]( j, |4 z: H, e' k6 s" D

    2 y# l5 E8 _- i* o& J3 R下面我们就按照这个思路来一个。
    8 o1 v6 b& O) a* M5 @
    " _/ b5 b" [" T4 {2 ?来个邮件配置信息类,内部使用@Value注入邮件配置信息9 `4 o+ z# ~4 Q* H; P" y& n

    # v" v, K. A' u$ u+ U0 Dpackage com.javacode2018.lesson002.demo18.test2;8 n0 q' w' W/ ?- h4 f; I- d
    # L8 N- P  m9 k. e6 {8 {' C$ w
    import org.springframework.beans.factory.annotation.Value;9 _6 Z& X2 n3 k/ |' P7 s
    import org.springframework.stereotype.Component;
    + V9 t. ~9 Q# N# F, S+ ?1 Z* b
    7 _% |$ V" ]0 _+ v/**( C! e/ m  m# M% V7 j! e
    * 邮件配置信息
    ) l% p' i( L  T0 W, Z1 y' x/ \ */
    ( u( V& x7 }2 y! Z@Component: h! t' N' w; `0 v
    public class MailConfig {" A! V, T* v4 H; t

    2 p. r8 c3 S* I) p6 x. q$ R7 G    @Value("${mail.host}")
    ! F4 N+ _- H2 W6 d6 Z( c# N    private String host;7 D- e  t. y% _# a0 `; G

    ' H4 t2 M# g* }) k    @Value("${mail.username}")+ V( l& l" E: @( f# @
        private String username;
    * K- H4 |4 n: ]' \( e3 G2 Z+ r
    - x% X8 Z  H* E: i' Q: ^    @Value("${mail.password}")
    ( f: h) X7 H" L8 Q  M% S2 k5 Q    private String password;  v1 |5 o9 p- d
    ! s: L8 [  h6 r5 `  p$ f0 [
        public String getHost() {
    , C9 j" }! \* E  Q4 K        return host;
    4 f1 {+ x# a: D/ g: b  V    }3 r9 R5 }( l6 p" E$ g  R$ D0 {8 b
    . J" a- B$ n9 |) W
        public void setHost(String host) {
    % r  `2 a" D3 b9 Z/ {! U1 A/ @2 o        this.host = host;: A3 r5 s9 B( ~) U8 a1 r. E
        }: i* t4 }" w) `8 O

    2 t! Y* a+ o' `3 g4 g6 f9 q) |    public String getUsername() {' B- o7 _, _3 g1 U2 H+ y0 F
            return username;1 f9 [  k6 w0 s# }$ R/ {
        }
    - I% E3 k5 S4 m7 k/ i9 J) u
    6 a3 t( |* i% C6 a% u  b5 V    public void setUsername(String username) {9 V- T0 r: g( i$ t9 k
            this.username = username;
    / U3 q# p$ D6 a" d- x1 h8 q+ L    }1 r' w+ O) i; @- f

    7 {. T; v. s5 [0 P1 q2 A4 u  Y    public String getPassword() {
    7 ]4 d9 |3 O4 O/ i        return password;
    # ]1 o; r, ?5 P& f    }
    $ |' m2 e2 `; q  Y( R! g- F8 d$ s3 m4 W" @. n  L' q+ `/ y8 J8 G
        public void setPassword(String password) {
    , E+ p8 _+ E3 A' }3 n, O        this.password = password;8 U  Q- R8 V! _$ G) r* ^1 s  {
        }
    1 E: K- g8 v, c4 ~% F
    8 q  x% ?2 B) y4 I$ x    @Override
    % l6 ^" M0 v- G. f; H    public String toString() {
    - {4 @7 k  e  I. q: U% j( l        return "MailConfig{" +
    - p( C9 Q/ f! z6 N5 D                "host='" + host + '\'' +
    / p$ @! X/ Q9 o$ P: y* r$ T                ", username='" + username + '\'' +
    * I0 h  V2 D# Z                ", password='" + password + '\'' +* p5 o0 p9 i9 |, k" y% `1 m" j  _* ]
                    '}';
    % w$ ]2 F# O4 w3 ?* h/ f5 }    }
    % A' R& P2 F* z! e* r}3 z: }  c1 d2 `# ?" `1 t5 |
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中7 o; w8 }" o/ O

    ( w; G8 c7 J) e3 H1 Z; R# J$ W7 _package com.javacode2018.lesson002.demo18.test2;
    1 P" K. Q( o2 z2 N$ S+ K9 s/ t- r- A. U* \
    import java.util.HashMap;
    ' k8 _) X  u( E  Cimport java.util.Map;% X% ]' ^! E" n' H5 c' i

    # {: U, S; G1 F. ^- {$ |public class DbUtil {
    " \1 u3 ^% j% W7 K6 Q0 ~    /**, v  `; W' \+ g
         * 模拟从db中获取邮件配置信息
    & Z4 E! K) J) ~     *; d; M3 o. i5 A4 O8 b
         * @return
      K2 m( [( \7 G! e, E3 u, M     */
    6 X, k$ ~* n5 Q6 x3 o" R+ L    public static Map<String, Object> getMailInfoFromDb() {$ L2 i3 ?/ a0 E6 [' O- b
            Map<String, Object> result = new HashMap<>();
    . G# ]; Y) Q4 {9 a) p( F1 T3 W9 _        result.put("mail.host", "smtp.qq.com");9 Y# ?5 I9 n- u. W2 W
            result.put("mail.username", "路人");
    ! x' E2 S6 n+ L/ Q& n- d, K9 W8 S        result.put("mail.password", "123");
    0 `  v$ k" Z4 x/ {        return result;
    # ], v; l+ J; H, r1 b5 V    }; M! X4 b5 e1 M- B# k, q
    }' R3 H+ I: |$ T' R( t' K9 a
    来个spring配置类
    6 G- F  b" V; c- x  r, p5 x
    9 n$ |7 U2 M% U: C: x( Lpackage com.javacode2018.lesson002.demo18.test2;  r& O- L1 ]' L, k' A# t

    9 z( ]+ u% Z; G! eimport org.springframework.context.annotation.ComponentScan;, A! R0 w- ]  m3 m5 r" q
    import org.springframework.context.annotation.Configuration;- u( l* }0 o* J: ^

    6 g9 U% [; G9 \1 D@Configuration
    6 x; f) T( P0 j, h$ G& T- r@ComponentScan3 m+ Z% e+ P* x
    public class MainConfig2 {
    ; o: L& `! i5 G}
    8 b; f8 D6 ^, q0 O7 @  n! o8 J1 q下面是重点代码# A0 o. N" v/ W% z3 a/ U4 _
    ' `2 e) J# M) l3 v
    @Test
    0 W; v1 G" u+ m9 Jpublic void test2() {
    4 @/ X* R7 J. K5 W3 _    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ! `2 U  u2 c2 S8 B
    # Q) R0 X) \' @: f, a    /*下面这段是关键 start*/
    ' o2 _! S& b4 Q6 ~) @6 v    //模拟从db中获取配置信息
    ( e* p. [* P$ g    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 G( U0 _) s' S4 r# m  F( r
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    & |8 b; I/ x* N; u0 C3 i    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);, v' r0 _7 u8 Q4 N5 T
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高& P5 c* c9 ]2 n; A1 B
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    4 p; o. b1 D/ i8 v9 N! _    /*上面这段是关键 end*/
    ( |8 e+ E7 p& R( D3 w$ i' M
    9 d; w1 o9 a" I/ _: v    context.register(MainConfig2.class);
    * S' Y, E3 p( O/ G* t! v0 h    context.refresh();
    ) M# V! `1 e. B# c9 n9 c    MailConfig mailConfig = context.getBean(MailConfig.class);
    / P' S, L7 Y' {1 H* K    System.out.println(mailConfig);2 L5 z+ H8 @' K
    }" u, r& P' j" f) k% m. t. i3 f
    注释比较详细,就不详细解释了。
    9 K  c6 d& B8 X! `# t# p8 }- z3 f; ~( J  ~2 v8 v/ c2 F6 l9 ~
    直接运行,看效果
    2 s5 o" U. ~0 K7 V) _
    + c4 v5 m- E1 I8 mMailConfig{host='smtp.qq.com', username='路人', password='123'}9 A+ m! V$ N: M0 K
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    ; h$ G3 ?! R$ o: M
    ) R* Y/ n( D* f* N上面重点是下面这段代码,大家需要理解
    2 N/ w% H3 K4 |% J
    : m( `& W9 J7 E2 {5 h/*下面这段是关键 start*/9 n5 D. N  x* ^; L( Z) F( M& z( o$ R
    //模拟从db中获取配置信息1 [8 w/ y) k3 O) v1 P2 i
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();6 I; E. ]$ c$ h* b! w& L1 m
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)' H( o% b, K; ]$ H
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    8 n/ w& g& y9 @' o1 y0 F, t//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高3 }5 F1 @6 Y; s/ y8 e( }
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! F7 B+ s8 w7 C$ |0 a. m( N2 X; p6 p/*上面这段是关键 end*/
    + r! M% [5 R6 y9 h: q" {4 S咱们继续看下一个问题
    # H+ X" z2 b. ^3 F* q+ I# V
    , D7 l( h* ?  Z) \2 m  d如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    - }' `) X+ q: g# k8 A( l7 X+ b2 X; ]* |0 q# N& n
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ( O2 j2 F% V! G3 F$ ]0 |' n, w$ W
    $ c0 A) |1 o" o实现@Value动态刷新/ i$ G+ B/ H# a% x6 X$ G
    , U# |; M: t: N8 @
    先了解一个知识点
    7 Q& [& s, g; b4 G2 [6 j* Z: k7 V# U
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。; F/ ~# D5 o) C1 n6 w

    0 O' X8 |9 n) l9 U这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    0 s1 a8 ]9 Z) {5 @) Y; M2 Z) t  e+ O" z
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:1 G0 L. t. A" n! [& S" q

    " j3 W- e. q) |, Z- {ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    . E' d. b) S# k9 p/ v' q这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    # [2 S$ s! R& k$ i& R1 L- V
    , i$ Z, I7 b5 b  N) Hpublic enum ScopedProxyMode {! {6 h0 P/ k/ X1 y! q
        DEFAULT,8 G5 a2 ~) |, {9 E9 \9 J/ k
        NO,
      ]: y$ c8 v5 _, K3 U9 W    INTERFACES,
    & a  ?& Z8 P% @% z8 m$ B    TARGET_CLASS;# H) s( Y/ G+ X! d4 p* A- h
    }% R9 C3 z! y! o
    前面3个,不讲了,直接讲最后一个值是干什么的。5 K3 }6 o6 V' H% ]* p8 I9 m" z2 w

    & e( K- S# i9 b2 E# \  Q当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    7 ~+ c6 ^- o0 s6 l3 i* x
    % G# r" I! K6 O5 t( x# E8 N+ r# i理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。# x7 U9 S7 L4 w0 M% f
    $ E- H' T( A4 l8 b) q
    自定义一个bean作用域的注解% |/ H) T& Y! Q* ?, J
    , P6 w& ?( r8 D4 w2 X6 L2 z
    package com.javacode2018.lesson002.demo18.test3;7 s# u+ _; l0 p* J
    + [5 t! b3 t5 \; R6 [5 u# Z0 b: E
    import org.springframework.context.annotation.Scope;
    ! _2 e  `6 ?- c- u: Q" |5 [: Nimport org.springframework.context.annotation.ScopedProxyMode;5 H& n3 g) N) O- X2 u
    + A6 H& s  p) ^! a  u# k
    import java.lang.annotation.*;
    ) T3 w, y3 g, p8 ?' B8 \& c
    ; K, E9 W/ I; s5 z+ M, k@Target({ElementType.TYPE, ElementType.METHOD})! S% h- }& _/ F# v0 |3 B# k4 U+ o
    @Retention(RetentionPolicy.RUNTIME)
    : r  f2 C7 @& ~7 P. ?@Documented; P$ y( _% _5 W) T7 S; h
    @Scope(BeanMyScope.SCOPE_MY) //@1
    ; a2 D$ }# l- l" j( P0 p9 f* Wpublic @interface MyScope {( x" s# A! X8 K0 w7 C
        /**1 P* U2 V+ e, K) D, @
         * @see Scope#proxyMode()
    ; [% C- n, U. v# O, z     */
    9 V! k7 |) o. x    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    & X% z: f" u) j# B1 w' v: `, D}, z/ A; r; R  c; E) \/ A& X+ K
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。" t* r& G/ b! R6 w

    0 c: j3 @  g2 W@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS2 x! n' I" J0 R1 u

    . |2 Y4 K6 a7 g. W, R@MyScope注解对应的Scope实现如下' ?& u6 l' x; O8 \# d0 Z* ]/ D

    + s2 a( n/ z3 X1 m8 Hpackage com.javacode2018.lesson002.demo18.test3;6 m# V6 U+ S+ Q0 F, t

    , D5 g, h; B+ J- @: simport org.springframework.beans.factory.ObjectFactory;- x  h9 b% H# R4 h! E! N2 U) e
    import org.springframework.beans.factory.config.Scope;
    ! o/ z& H4 j$ Nimport org.springframework.lang.Nullable;
    , \& C2 C8 g, Q4 k& v# X# K. F# h3 {4 ~3 C( d  I
    /**
    2 r/ f% N+ d, q * @see MyScope 作用域的实现
    $ f  K5 G; W( ~0 U7 ^  v */
    " H3 O3 C- I* ?4 n% j! w5 Kpublic class BeanMyScope implements Scope {* K" s- |: P9 _" c! R
    8 n2 y- c6 _& h% F
        public static final String SCOPE_MY = "my"; //@1
    0 s/ n. }. B& C# c! g1 c4 b
    $ l+ n9 K$ m4 n9 e" ~+ J    @Override
    5 f. X' U; Y+ \# @6 A    public Object get(String name, ObjectFactory<?> objectFactory) {
    * D1 ]1 {: p: S% {& n        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2% F: {) _; C$ B
            return objectFactory.getObject(); //@3$ L* p0 s, n( x% C" @
        }& W7 r) x0 ~0 Z4 H$ V& `, G
    ( j$ c$ k+ b* F, H
        @Nullable5 ]/ M1 s+ z. Z, X; B% b
        @Override( B4 ?) n  S# L. l$ {2 s, k
        public Object remove(String name) {7 t) B# i( b, Z" Q3 h" H  }# Z
            return null;0 x' M' `/ _  @/ ~7 A1 @9 M3 `# a$ }5 L% S
        }
    % y% `) H- G, T5 q) f$ e2 w0 l1 e% W% C5 j* V  C0 y
        @Override
    $ _. i& Q# |. V; y+ f& V) d$ h    public void registerDestructionCallback(String name, Runnable callback) {4 T" q3 Y7 b9 f9 m5 F" o, i

    7 G- h6 j0 ^3 u( H9 M. D% [1 w. `& {' B    }* v* B: M! O7 p$ ^4 F9 G

    + e( c: m+ r5 D' A' G6 T; J% E    @Nullable$ q1 N: i4 m! z+ q! k- m
        @Override
    % R) C, }8 G) H' s- Y* j9 F' o    public Object resolveContextualObject(String key) {3 z5 m# x# T1 f. M0 K2 W$ b
            return null;
    3 z, B! i0 C4 y. O+ P# O7 m  L    }
    9 O" B- Y( W  k
    . s* ?, a& e5 J9 U! x/ v  F    @Nullable
    8 I3 ^7 j! [/ L3 r$ L* Z    @Override! [/ m' ?  |: T- B# x
        public String getConversationId() {
    + \' ?6 s' r9 C9 f5 {  h' ]        return null;
    * r  l: u) x: s2 ~& `/ m. S    }
    " q! I- d* |  h}
    + Q+ {. R6 C  Z" g" \@1:定义了一个常量,作为作用域的值% o- h0 H1 X( Z3 H! v& o. ~
    . X$ I' Q8 g6 n7 E, w/ M
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    # v/ I( A0 R  F
    7 W! s) W# |: O  i8 X@3:通过objectFactory.getObject()获取bean实例返回。
    + V  `: t3 T5 u) q; Y
    . h" S: q5 o: @: g8 p, i' [下面来创建个类,作用域为上面自定义的作用域
    * D6 V4 M# Y2 {( t4 {5 u2 }7 j
    , j, }6 O: c' H" w8 g8 U% |5 f" X3 n; d6 lpackage com.javacode2018.lesson002.demo18.test3;/ g4 e5 B' Z. p# h2 _9 B2 K

    & x( x5 |, b8 y0 _8 W" yimport org.springframework.stereotype.Component;
    + O( \3 V' F: e+ Y& t4 U9 k0 S
    ' s; ^/ I& O: h* R, j0 T5 ~7 z! jimport java.util.UUID;
    & @- X. Y" d7 P- P$ L) C
    ; H9 V7 i' t3 w; Z@Component; g& n: H( Q6 T) o7 [: b
    @MyScope //@1
    ( d: C! i7 y: Y1 D1 {public class User {
    3 X+ O- _" L# q. s7 }5 E. {. d- n; R" [# E/ B0 S8 y% m
        private String username;/ R5 W7 m' V: S, j" g
    , R2 Z! h4 I. \9 E& d1 E; H
        public User() {
    . V/ L( u6 J* c6 T        System.out.println("---------创建User对象" + this); //@2; s8 k  N& J' ]
            this.username = UUID.randomUUID().toString(); //@3
    9 p3 |& y! e# j    }
    # s' W6 t5 H& n; `6 y, t
    & }) l  Z& `& R/ w1 d    public String getUsername() {
    . h% W& {0 E( G3 \' w* {1 r- P        return username;  R$ c& G. p4 i7 y, l9 G7 b. V$ a2 X
        }9 W% t5 ?0 F) `( L5 U

    + R+ p2 {% m7 h2 Y9 S    public void setUsername(String username) {& i5 [" h+ `6 [% m' ~
            this.username = username;
    & }8 o# ]" c- }) B0 f$ |    }
    8 C9 \0 m8 z! D7 J5 a! W" F; v: W* `0 }6 A% C" U
    }: [' G- s, E5 i1 v
    @1:使用了自定义的作用域@MyScope
    / h* m. i0 |% o3 K0 D1 @5 {7 K
    5 W. N* k4 Q9 }$ [@2:构造函数中输出一行日志' A1 r9 \) b0 y2 F  W1 Z  q

    6 |  }& D, E4 t) }* u@3:给username赋值,通过uuid随机生成了一个
    ! z- U: j& w9 g, g
    " q: _7 J& [- g" ?来个spring配置类,加载上面@Compontent标注的组件1 G; A! v$ e+ T
    1 m3 J7 r* e% f. \( T: U
    package com.javacode2018.lesson002.demo18.test3;3 Z+ E6 z6 m% j( C

    0 F, ]" n' w& \1 j: simport org.springframework.context.annotation.ComponentScan;
    $ b! [" |$ U% w$ m% v9 vimport org.springframework.context.annotation.Configuration;  D: R$ I5 J* ^# [

    . T6 }4 n# E9 b+ M@ComponentScan+ R# W, ?" Y( A& c8 f  h" ~
    @Configuration& e9 x7 f+ q8 ^" y! X
    public class MainConfig3 {, z" ]) y& ]4 b* X( v
    }  ~6 V4 r" M9 z
    下面重点来了,测试用例! }4 l' z3 I+ ~/ F% [0 l8 c' N

    ! {% m" n/ F4 Q8 n, F2 s- V@Test: N3 r$ a/ U  Z' [) H# y
    public void test3() throws InterruptedException {
    + V4 M! R2 l, B    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();, v; \* c6 d5 S4 X
        //将自定义作用域注册到spring容器中
    # o" H2 R/ W4 D. P    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1; Q! C" F0 @% w: s4 f
        context.register(MainConfig3.class);
    % k5 v9 r5 f) z* @% O- e1 z/ s/ B, B    context.refresh();) t8 O, A1 H$ ^+ d; D2 g3 }  e

    5 I0 N! v/ x1 p: B* l    System.out.println("从容器中获取User对象");
    8 R1 i$ M5 {5 C$ U: t    User user = context.getBean(User.class); //@2  @: A) O( \& i/ G% l3 A( T9 Q% [& O
        System.out.println("user对象的class为:" + user.getClass()); //@3& H' c# k1 Y4 [. _
    * U1 P* e. M. X5 \: D
        System.out.println("多次调用user的getUsername感受一下效果\n");; W# W6 |( {; V* u) G3 K
        for (int i = 1; i <= 3; i++) {
    - x9 p1 [- K, W, n) ^! y( u        System.out.println(String.format("********\n第%d次开始调用getUsername", i));5 r6 j7 r% |* Y/ x# g
            System.out.println(user.getUsername());4 }; x3 D- ~/ p" f
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));9 u) N' f3 Q- |% W+ N4 m3 [
        }
    . }. @' Z# d' f% M  u1 Q% {% ]}
    ' q9 t! o' B/ P: X@1:将自定义作用域注册到spring容器中* g9 s2 n+ e4 B0 c# T" o

    % h+ |4 V1 i- I- A( r4 |' |$ V# e@2:从容器中获取User对应的bean
    $ V  L. H+ c0 S( P, h: H% {9 d- ^3 T% U) u  z6 c
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的: ^/ e. e. `/ k1 o4 J

    " m+ k1 U; b) N3 |5 n代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    5 f& s4 X8 D7 c: R0 R
    & L5 v/ z4 g. B( \* v; C见证奇迹的时候到了,运行输出
    1 z0 K! {/ x+ C9 `" J$ d3 p' P4 ]* g4 I. S- Y3 x) w/ m
    从容器中获取User对象9 ^- P/ p$ K- R" L& X1 S/ U7 W
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    6 J( P9 p2 H0 p" b+ Y4 G多次调用user的getUsername感受一下效果) n! D8 h( w+ e5 k

    6 G4 B6 R1 G7 t********
    . `4 _7 J: Y* i第1次开始调用getUsername
    : Y8 n2 J. N! ]2 j: f% N% HBeanMyScope >>>>>>>>> get:scopedTarget.user
    . X8 q+ {, ?4 ~; H* [4 U---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4$ O6 t+ o. C; x, B7 o
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    9 w* P: K1 A3 W" ]" [第1次调用getUsername结束
    % A9 J* W) w4 n# m********
    ( m3 N$ k' p: v% H- g$ i! Y6 b4 C. _# M) U) m' i8 t- a- e
    ********! p5 b+ x  O! H0 S0 B
    第2次开始调用getUsername. x- t: q6 e' Y) f$ P! F7 S
    BeanMyScope >>>>>>>>> get:scopedTarget.user  a/ K6 d" |8 V; c/ p
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    2 P" j) z; \4 w7 i01d67154-95f6-44bb-93ab-05a34abdf51f
    * F5 {6 u$ S+ v6 w: w; p第2次调用getUsername结束' `+ D9 I+ c: y5 n
    ********. y( z2 l) G- u5 ~9 m3 u

    1 n; R# I# z$ `/ d( g********
    4 I) r! @8 K! h' K第3次开始调用getUsername
    $ n' Q. H6 G# d- {, QBeanMyScope >>>>>>>>> get:scopedTarget.user
    9 z9 q& d0 s8 [---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    , }$ v! A2 R' N! \) p76d0e86f-8331-4303-aac7-4acce0b258b8  @0 q# D; a' ]3 m
    第3次调用getUsername结束( y; u: K2 M0 f
    ********
    , {. x" n' k8 S8 R2 N) L* K从输出的前2行可以看出:5 P3 }" n5 Z) n+ j

    : f4 J8 ?2 _" ~) p: N% d, O- q6 \调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象, a0 U, K' }  X1 R3 J4 D
    * \5 j& i) P' o4 P) q. y
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。% M) F' a4 T  `- R! ?4 D

    + H" j) O; a: L% o; w0 G后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    : M( y7 q& B( j, l! J0 W* v
    6 g" {3 y3 W3 e4 R* i通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    & [9 U# n- K+ ~2 I1 ^* {! h% u  P! _0 V& |5 O
    动态刷新@Value具体实现
    3 g' P( F" d  u% i
    4 `9 h1 H* `% g. Q! |那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    * D2 t. |, I+ g1 `+ S! r: F2 P
    9 X  q; |8 a/ H& r- P先来自定义一个Scope:RefreshScope5 d3 g/ d3 ]8 y- u6 z. ]
    ( o7 n% \8 O* Y0 f3 ~' e
    package com.javacode2018.lesson002.demo18.test4;4 b" I& A8 ~4 N1 J7 f! V; }3 x7 Y' C
    - T) f/ {7 E' g8 O  W1 N: t
    import org.springframework.context.annotation.Scope;
    6 |& r, B: I! _- g  R: dimport org.springframework.context.annotation.ScopedProxyMode;2 E5 A2 k0 T3 V5 j
    $ r9 `. }: \1 g$ y: ^
    import java.lang.annotation.*;" a  u  z! P2 R" \

    & \0 c/ c9 y) P, Y( n@Target({ElementType.TYPE, ElementType.METHOD})
    : [9 Q1 J5 h& V- |5 G% R@Retention(RetentionPolicy.RUNTIME)
    ( P: r# r  }$ i; y@Scope(BeanRefreshScope.SCOPE_REFRESH)  X( M. ?% s: d0 M. C3 d  ~1 E$ s: J
    @Documented# O: @$ f3 V. ]1 B
    public @interface RefreshScope {
    6 n: c$ X) D. F" g    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@13 X  x1 D! ~" X' |( g  X, m" v
    }
    5 A. `1 c; P% {8 H# Q8 y要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    . B: ^6 V8 d/ x( v6 W/ Y
    - w. R5 Z" V. ~* A" q- q@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS" r1 }2 \/ K* {9 f, z- K; p7 t/ H2 [

    # Q# i$ j* ~( U6 s这个自定义Scope对应的解析类
    / J$ ]! I# N; o- ~9 b* \2 I8 a& A3 Q8 |2 A1 A4 m- ^6 i
    下面类中有几个无关的方法去掉了,可以忽略
    9 J8 }6 g! U- Y- U
    " m' I$ G# R$ Bpackage com.javacode2018.lesson002.demo18.test4;) s8 q% l& @# E
    - S$ v+ S- F2 O3 u( A, O; X
    8 h" A6 R; c+ H* N- e
    import org.springframework.beans.factory.ObjectFactory;
    3 ^# f& `% Y1 T$ Z8 ]import org.springframework.beans.factory.config.Scope;
    % m+ G' m5 S7 w% a" Simport org.springframework.lang.Nullable;
    2 Q, \0 A+ f* B  p, F: E, }7 `7 X+ p9 M6 [( a
    import java.util.concurrent.ConcurrentHashMap;! M6 v2 X3 I% |$ `% S
      p8 G/ N7 c1 U8 ?1 C2 v
    public class BeanRefreshScope implements Scope {8 Y* X0 E9 D0 i$ X

    % s/ l" _: |5 {    public static final String SCOPE_REFRESH = "refresh";
    7 k! c, z  c& N- t! U/ N; u* b
    % Z9 _3 Q) l/ w2 l$ g    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();( K2 C* I; t4 D3 D

    . P9 ~* s* g8 z- l: P% n) H' E# B    //来个map用来缓存bean
    ) i5 [' l4 Z# k8 i8 f, Z% }4 e    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@15 `' S  Z- k4 t

    4 t" T  H3 j  u& H' U( H) h4 l9 {0 |    private BeanRefreshScope() {7 s3 B* O1 G% s2 [+ v. R: e! O, c
        }
    8 L. t- x/ i) j* |" p$ k' l8 W" k4 |8 l6 y( g5 m8 x
        public static BeanRefreshScope getInstance() {
    : r0 U! o9 j6 h, e* t        return INSTANCE;
    ) Z, S- Z! m6 d7 w. Z    }
    , B- f4 Y& y- X. l* Q- f5 O" {
    5 d, g- A  F' K1 x% k+ A! Z    /**
    # I; A; V* c3 {     * 清理当前
    7 M- s9 x" w4 b* D# u" H: `     */
    ' `* \3 N$ v& E: I# j: Z    public static void clean() {
    ; C9 f' j' y" \$ c- w% F        INSTANCE.beanMap.clear();. q( N+ L: k: c; m, Z
        }' ~* Y5 B- l' W' t; e! B6 u

    0 O8 I7 o1 f% y, @6 b    @Override/ B+ x; c. h% o
        public Object get(String name, ObjectFactory<?> objectFactory) {
    ' [* ^: C1 Q( o7 Z  E& m* v1 X! t        Object bean = beanMap.get(name);
    % g7 Q$ C# |5 o$ l! y9 s, Q' m        if (bean == null) {
    ) j, ]2 w' @5 j; \            bean = objectFactory.getObject();
    ( V% e' v' n9 o/ V0 s            beanMap.put(name, bean);
    ! b5 {3 ~/ b- L& s4 A- s6 [4 o        }& ^7 g# V! N' I0 q* F* j
            return bean;
    " W* B& m) {6 q4 M# k# i! P    }/ p3 @! U) O+ q: A
    : z" e/ o) x% O7 ?" u0 H
    }
    / E9 J' I; D2 r5 }# k上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    & U( f' E! S/ y9 L
    # ^+ x8 G0 Q8 I/ |# \4 ]上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ; B5 V- G" u# v% N9 x; S1 ]
    9 u+ C5 D0 ~0 X; Q& A/ E: e! _来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    ) d. P4 {% r( X/ I; @! z* c* w7 y' d/ d8 |  ~. t
    package com.javacode2018.lesson002.demo18.test4;1 ?, ^" ?* |( C) W% g0 ^

    0 p5 q$ V6 Q$ j( iimport org.springframework.beans.factory.annotation.Value;
    / Q- ]4 I5 o( B. L+ ]import org.springframework.stereotype.Component;
    % j, n; w/ \. r+ E
    ; G% R: X7 V# R4 K: V  x# p- D* F/**& c$ V+ ?; N% x7 o
    * 邮件配置信息- P# G5 L  U" z6 q
    */# }, n  L" y3 x+ b% v; |% O
    @Component: D4 l3 d4 A- q4 _! ?7 z0 @1 W
    @RefreshScope //@1
    , i8 z8 Q+ Y, l! L1 S  gpublic class MailConfig {
    4 P# S9 B* L# z9 N# ~' p; ]" Y% w, M( `0 |6 a, \' \& p# a7 }
        @Value("${mail.username}") //@2
    + |7 g( ]9 G) E1 ^- v, R8 L    private String username;6 `3 Y5 }) w- j1 W

    ( L% I/ [( N5 x4 b9 c    public String getUsername() {
    , J$ n* D" n6 X# |        return username;
    ( t( @# h1 [7 b, u+ M! H2 }    }
    7 Y- [) l0 g) g. A& Y# G' i
      o% L# H7 t! Q0 s2 i    public void setUsername(String username) {
    1 T4 o: J" N2 ]0 Y        this.username = username;) r$ g* l+ T8 I8 x9 B. Z
        }( K8 i) B1 S6 Q+ R
    & W# N9 P% m! G6 Y
        @Override
    8 `/ K4 y5 @# b8 ?* T4 }. f! s    public String toString() {
    : q& {, y2 v7 v. V& m$ L  M' y        return "MailConfig{" +
    & L, U, L, `5 L/ ^1 f' f& D                "username='" + username + '\'' +  {7 M2 l2 e! U$ s; e
                    '}';
    5 Y7 f  o$ {  r' O* k) B( |    }
    ) @! W5 ?4 n/ Z( g; L$ h0 e  R}* Z6 t# Z% O. X0 z9 e
    @1:使用了自定义的作用域@RefreshScope
    ) \& m+ X+ N; M
    : r% `- S- G2 x. s5 G4 O! [@2:通过@Value注入mail.username对一个的值% x) K8 e# e/ L" U

    + T+ x5 O1 R8 u7 d% \: [重写了toString方法,一会测试时候可以看效果。
    ! P; }7 [' X3 v1 ~6 T* k
    * p5 G) f) A" L( W5 w7 A5 _2 x再来个普通的bean,内部会注入MailConfig
    . W5 K7 f; `  a2 }# }$ Y3 |* c! |; q5 Q
    package com.javacode2018.lesson002.demo18.test4;
    + N8 {% Z: M) j  w! J, h
    ; g. f& W- u$ \- G! @7 Uimport org.springframework.beans.factory.annotation.Autowired;& t& f4 H; Y! m: R$ u1 ]
    import org.springframework.stereotype.Component;# E0 k+ n! b# j1 e4 ~

    0 T7 E  ]* c( ~@Component
    ' [/ v$ D9 W8 K0 J) w+ ]: wpublic class MailService {8 M7 y; r7 b/ [% E3 M: D4 y* J6 }
        @Autowired
    + |& R* E9 T- w4 G  Q! y    private MailConfig mailConfig;2 L+ K$ A+ n9 {  M! i2 n; s
    2 b6 N2 y7 u3 P2 r: B; _- I
        @Override) H# D. [5 |, {5 F2 S
        public String toString() {, ^' F* V4 W! i1 {8 k8 U
            return "MailService{" +% l5 i3 t2 X6 |. N
                    "mailConfig=" + mailConfig +
    % }0 J' P! k& \* K7 W- N                '}';
    1 I8 [6 Q5 d1 V( h    }
    9 y6 T) r; g% @# b; ?}/ l+ y, b' j8 l6 {' y" R& ?
    代码比较简单,重写了toString方法,一会测试时候可以看效果。$ `4 o" H6 h1 t/ J% B! n4 `- g

    - H- M, |3 c- \$ J) L/ u& Q" N6 ^* K来个类,用来从db中获取邮件配置信息4 J3 j( k5 M7 B6 X8 r" e6 v
    ! Z, f! j+ S9 S, P6 @% c
    package com.javacode2018.lesson002.demo18.test4;
    4 n: T  M% @" P
    # ]& {) t3 j+ T8 F" `  }1 f0 g0 aimport java.util.HashMap;
    7 T% z+ w9 _" jimport java.util.Map;
    1 r4 J4 Z. v) g0 K8 b; Jimport java.util.UUID;. h% f5 u" q: J5 e: L
    0 h" B# }1 n& R. \' r3 X
    public class DbUtil {
    ( O1 ?$ y+ y5 M    /**( E- x( J; r3 D/ {; c1 G% }1 r
         * 模拟从db中获取邮件配置信息6 o* n+ Z; _8 T$ |
         *
    # w" o" X% Y9 H% r+ \7 D3 x     * @return, `! @* f; d% E, N" |1 x" a
         */9 I6 B0 k. ~+ o- ^$ P, @$ P
        public static Map<String, Object> getMailInfoFromDb() {
    ( D. i' |/ E  ?% i* K: q' _        Map<String, Object> result = new HashMap<>();
    ( u, o5 z/ \6 u        result.put("mail.username", UUID.randomUUID().toString());
    ' V3 }/ O  [4 s/ w2 }3 D        return result;# f1 H7 M7 |' t
        }
    9 Q' Q! {! L9 N  _* S) m2 M; t8 k}/ k# E; Q/ U& }
    来个spring配置类,扫描加载上面的组件
    % K, v- N/ _- d6 i5 A# t* e  T3 w# ^: E: H- s# u, S
    package com.javacode2018.lesson002.demo18.test4;
    5 e. ?, p0 g* G7 B( E" \
    3 o4 N+ I' K) A: M, J7 Eimport org.springframework.context.annotation.ComponentScan;
    - F/ O$ d( H; ximport org.springframework.context.annotation.Configuration;- @6 p7 r' n! U! ~# ~6 g

    1 q3 \  ]0 k3 o& b@Configuration* A! m, S+ U  i: |! f8 w
    @ComponentScan( ]5 E7 ?+ L9 _+ @  z) L2 y
    public class MainConfig4 {
    ; @1 X) k8 ^* z1 M3 w0 G. M}
    8 \9 w% s9 A& U/ E% h5 u- n来个工具类
    " S0 P# T, u$ Z( n  w) I7 O( U- r  Y! m/ r
    内部有2个方法,如下:
    . b0 P  v/ `! F3 z% }( A2 R5 T3 j
    ) D+ V# {6 e) B$ ~+ ~3 x' @( N: npackage com.javacode2018.lesson002.demo18.test4;7 p% G  _( D/ }

    8 I( |5 N. b3 u. bimport org.springframework.context.support.AbstractApplicationContext;
    ' l7 f% c+ d8 mimport org.springframework.core.env.MapPropertySource;4 x% h0 j7 Z7 o2 c+ @
    ' M: b% b( b: Y/ t
    import java.util.Map;# o. K" F) G' {8 C
    $ A) V5 A1 Y0 Z& L
    public class RefreshConfigUtil {
    " p- T8 `# M0 h  {    /**: N, c8 X, ^. H& r% s& \
         * 模拟改变数据库中都配置信息- \( P  y3 Q* k3 b
         */
    6 U# M0 U" T+ `2 S9 m    public static void updateDbConfig(AbstractApplicationContext context) {
    ; O- J. n. T, H- y        //更新context中的mailPropertySource配置信息
    2 d7 y; s. G9 d& c6 K/ N        refreshMailPropertySource(context);6 ~# C3 S  z, a6 r( l! s

    9 C/ A! J9 h% `        //清空BeanRefreshScope中所有bean的缓存
    # y9 X& D5 }; l* ?        BeanRefreshScope.getInstance().clean();
    5 }" A  H# S' }( G    }
    2 F. C! t1 ~( Z* q
    + o4 f8 X$ T4 p3 N4 d! E1 O    public static void refreshMailPropertySource(AbstractApplicationContext context) {9 M+ Y4 v- X- T2 l+ U
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();6 b, W6 h  |+ T) J' G8 c
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)3 v0 d4 H2 p; S$ j! j3 c5 y: B
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);- x" t9 f5 y% L  O; g
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);8 o0 @, R' v. m5 s; T* t  A1 W
        }
    5 Z8 s0 y* r: k. k4 G
    / y8 o% i; l' G; F" @! N}
    ) u. a) A; ?2 @8 S7 G- i  DupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    & Q8 w- v9 U1 j0 v3 y% D# C% c9 F  b
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    & G3 f& z( o0 A& [5 v( G+ K; T0 |* k# h' J6 ?$ n
    来个测试用例% }3 K- |/ q+ `! a# y! N5 M
    3 f6 Y+ d  b2 u$ f& E
    @Test
    / S$ |+ n: v( @# B4 y+ cpublic void test4() throws InterruptedException {8 g7 T( F! T1 j; V/ e
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' T7 K) y% o$ o9 G. w3 r' P' ^
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
      Q7 _0 L$ g5 y! l& z8 e7 J    context.register(MainConfig4.class);
    - T9 S+ b9 |" x' ~    //刷新mail的配置到Environment0 U2 z0 T" `2 s( E
        RefreshConfigUtil.refreshMailPropertySource(context);
    ! H& d  a' w& |( G+ g% R    context.refresh();1 T3 _0 b, ]. Y, E% O0 @
    8 W9 {  D% h4 a! |. K/ ]
        MailService mailService = context.getBean(MailService.class);) X  M7 F; o1 j* {5 a
        System.out.println("配置未更新的情况下,输出3次");
    ; W2 P" K0 B0 k7 m/ y- _  _* V$ L    for (int i = 0; i < 3; i++) { //@1- ~7 O5 V5 j: Q9 }) W
            System.out.println(mailService);! t( M0 V3 d4 m
            TimeUnit.MILLISECONDS.sleep(200);9 e; q) U% E( X, w9 L  d  h- c
        }8 v' s4 d/ H0 f+ U

    7 y4 p* R  m- m! e    System.out.println("模拟3次更新配置效果");6 n+ L+ p$ W/ W7 {- q! d
        for (int i = 0; i < 3; i++) { //@2/ c; e) ?$ M+ |" s
            RefreshConfigUtil.updateDbConfig(context); //@3- V4 Z; }  c. S1 ?/ k4 t; }
            System.out.println(mailService);
    / R0 t. f: s4 L% B) u        TimeUnit.MILLISECONDS.sleep(200);  y9 z+ y5 Q  A" f
        }
    : ]; x( a4 i4 o; {}
    , O( g1 i4 |) J% k) O9 c@1:循环3次,输出mailService的信息4 n: l* `; f' t- B
    + _+ n( t6 l8 n2 d# _9 _
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息: I2 E: y4 Z" p/ E" c
    5 l% b: f, M2 z4 }0 m" {/ ^. k
    见证奇迹的时刻,来看效果
    ' b0 v  V1 i5 Q7 ?4 s2 C( H6 ^
    " A9 m" Y/ ^5 k& F0 j配置未更新的情况下,输出3次- h9 A+ P' j3 J: X) O# W
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 b) Q. J0 z' rMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    $ A9 b: D8 e- t) OMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}( e5 |! y- T" c
    模拟3次更新配置效果" P! d$ ?3 g: \* R; n, V
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    ( q4 f' d* b/ L6 T# m$ S4 {2 |MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    : G2 W9 Y5 Q/ f8 S+ _, ZMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    9 G8 q& N# v2 [* M3 B5 h1 f上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    4 W* J! c! |# c5 Q
    2 o" W8 v+ }5 a小结* W& ]  x: M. q5 v7 l9 K
    ! K( {+ O  T! |. |( k# ~$ m, o
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    9 \' V0 h  |7 E- y" [( Z5 d1 |) h; @5 M
    * t; D7 Q# s) q, {0 c5 u1 J; j有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    6 g! F- @  r# D9 l' Y4 C
    $ Y- l0 W0 b; L" J总结( Q; ~4 C; w" @5 C# T% D9 {0 D  s
    ' Y- o8 t4 m+ Q7 ?3 u$ `, A
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    - S6 l  K! i  o& q# F* p, ?1 r9 J6 C# I4 J- w  I' B2 d$ f
    案例源码
    ' B- e- N/ y4 R4 ]6 ^# E  r* \& I4 j
    https://gitee.com/javacode2018/spring-series
    + ?2 ?. i8 X5 q# o$ J. s路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    ) G8 h+ |( c, N1 r' U
    5 m. b1 c- a. z+ E9 J& C/ E% m/ ySpring系列! X7 K" ^9 q% {" C. u
    0 n; Y! C' N9 v8 C  u# z
    Spring系列第1篇:为何要学spring?
    1 N- M5 u1 N' v8 l) Y& p. u2 v% X
    * e- C! S: |% ~. @7 v3 D4 Z- jSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    + S) Y; x5 g7 t% {" s  ]# I. [
    6 e+ I+ z2 ?. o5 `4 RSpring系列第3篇:Spring容器基本使用及原理
    ) G) b3 R) x( v: Q
    4 X# V/ \) N7 i) C. W& U. GSpring系列第4篇:xml中bean定义详解(-). h3 L5 r/ @0 C* ^4 X. B
    $ _1 `; n5 C2 S' _% a+ q; y
    Spring系列第5篇:创建bean实例这些方式你们都知道?
      z$ l  d& ~# E6 K" ?! C+ O, ^
    ' H$ N  i- N1 `+ [5 BSpring系列第6篇:玩转bean scope,避免跳坑里!3 |+ n- S* K3 ^% S% `
    ( r0 }- z' J& V- P
    Spring系列第7篇:依赖注入之手动注入
    * {) H2 c( V5 J7 c( o. a* N! e2 L
    ! T( j; H8 c) p6 {9 V( eSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    # S! ?: L. P2 t' S7 Y7 Q
      U* ]! A4 A) ^3 |2 f( [Spring系列第9篇:depend-on到底是干什么的?
    . [5 W, ], q. D3 L, \. @* Y' k( P+ l0 f" a  _/ L3 v, {5 E. ]
    Spring系列第10篇:primary可以解决什么问题?* `9 w; K- D5 a9 s4 a! Y

    2 s+ ]" R3 k4 @" z* [Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    + r3 a# {+ i. @- t! ~: n7 C7 i/ \0 I) K% R
    Spring系列第12篇:lazy-init:bean延迟初始化
    ; J: w7 w  y# R6 E$ e1 F' C$ e% f) r. E# H, X* v
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)& i! K0 s. A* p
    - g+ L* V* A' |' H
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
      P6 I& f9 L% T1 }, h4 \; P! W. F
    7 d( @: [2 w, U& M. G& Y# eSpring系列第15篇:代理详解(Java动态代理&cglib代理)?1 i# M) b& w  x, y# ]4 P0 ]
    - a2 o" j& s3 e( X
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    5 [' M2 y- m  ?# p4 t1 }9 H5 s- ]0 b9 l/ W2 \/ x
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    - H+ M$ k9 ]3 g, n7 n1 _$ S8 \, Y
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)6 B- }1 a# u- S2 c& Z7 `, w
    , s, |5 b, g+ w) A3 h
    Spring系列第18篇:@import详解(bean批量注册)- _- C* x; g! I/ Z3 t/ @, @
    5 \& |, c8 B" [/ Q- ]
    Spring系列第20篇:@Conditional通过条件来控制bean的注册. d1 }1 M& Q1 @+ W
    ' D. F$ y& D0 c
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    " v3 }8 v) s' O$ Z& D
    . D; |/ H3 H" `- dSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解, P+ P1 v/ n" G7 r+ f8 A

    6 I8 D1 C. q  V- z" m: P! TSpring系列第23篇:Bean生命周期详解4 s) p, X$ v" U4 B; t2 F. l

    2 d/ V0 C* @5 h9 t4 g& `/ ISpring系列第24篇:父子容器详解
    6 g  I3 b  g7 s* u& j5 T
    1 }* q6 p" }' f4 n更多好文章7 m" \. a. Q  y. j9 {
    3 G$ z& W  b4 f1 [' B5 F- O: ?  t
    Java高并发系列(共34篇)
    7 ^1 _2 n0 x$ M' \" I& u$ e/ p5 c& T( h& Z
    MySql高手系列(共27篇)
    ; U, M( y  o7 f0 P# d4 k$ P5 o  G/ A$ K' z7 n* `6 }3 @  ?) V% c
    Maven高手系列(共10篇)) ?, ~; f- u" k( \7 m

    ) ^; b+ S+ S) Z6 DMybatis系列(共12篇)
    ; T$ z  X" M9 \6 ]3 b. ?1 p5 q* v  }, H6 q9 q6 Y& t  j8 l
    聊聊db和缓存一致性常见的实现方式
    6 K4 Q# l  |" ]+ V$ Z+ W
    & m8 J4 V! D1 P% j8 h  w( |. u9 p接口幂等性这么重要,它是什么?怎么实现?
    ( f/ {* F4 n/ s* H, X
    + @6 P. v; X, _% ^% K) Q7 [# Y泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    " [- I  o- q- }8 y1 t5 V: T- x. K————————————————/ s2 D& j* j9 Y7 m1 L0 H3 H% e
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。$ K. Q& Q3 E9 G$ B) C# Y) o8 f
    原文链接:https://blog.csdn.net/likun557/article/details/105648757" [) L1 G# b# `" L8 k  v

    : {5 H' _2 I, |" ^0 l3 s2 `( t) m% O( J$ r5 y' e: N7 N
    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, 2025-7-20 03:53 , Processed in 0.717393 second(s), 50 queries .

    回顶部