QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5096|回复: 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!
    : z# B, V, h) I4 B. E疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!& |% j( s$ o' o8 m8 V! m0 h
    5 S5 {7 S- G* l
    面试官:Spring中的@Value用过么,介绍一下: t; Z# K* @$ P8 X1 }8 `  T! `

    2 `( ~! S! Q0 e4 A" T- o4 b( j  X我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ! T  `# m" R* i9 D3 Q+ j- X+ ~) p3 v5 G3 }, P
    面试官:那就是说@Value的数据来源于配置文件了?
    : \; E: p; D" k: k# c- t- r/ U7 S
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置1 ?+ W( T* C/ n: T, O) d1 g' V. S

    $ Z& p5 n% f  a8 u+ i, }! d面试官:@Value数据来源还有其他方式么?8 ?$ a. f6 ^1 s, R0 e

    " u% J! t- X2 E我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。# C5 [6 U8 P, E( o) J
    3 ^' b4 C( G$ j
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?6 Y) y: P# ^2 H' h9 Z4 x3 `7 |
    9 K/ p6 h. _5 T- s0 L! }: h
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧% c4 o, n- Y) V4 U" u+ b  u

    8 n/ t8 X7 }) S. u6 V面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    / B3 M) y" \* C9 h3 n" r: ~' W# y$ t" ~% ]% ^4 B. p
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能! x" ^9 v! @! a; K: c! v+ _# E

    ! c8 f& W/ i* S6 |面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    " N/ X0 c) I. W. E# a3 y, @3 e- t0 ^" Y6 V  a: ^
    我:嗯。。。这个之前看过一点,不过没有看懂% Y' `8 w9 V0 O! }0 [: y

    7 L9 w- Q3 b( |: b- k面试官:没关系,你可以回去了再研究一下;你期望工资多少?$ E. z4 ^4 x" y" P7 T, B' W
    3 S$ {/ m; Q* s0 _  y* M- U
    我:3万吧4 i7 e" }2 ?. l  |; H
    + ]: f% g, o, f, A4 F
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    + A- f" p8 G. h0 x7 d) i) l  V: r6 Z8 L$ S+ d+ w$ z7 Y9 M
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万0 T+ l- m( q) [& L8 B% J

    ' I# e5 e1 a5 E( N6 g. U面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    + R9 k2 x1 \0 c/ U' A& A; z9 Z  S) V6 x7 }, r& _' p8 q
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。5 D. m) t! J1 [& `1 ?7 g! a
    0 ^; B$ s1 Y' ?& Y! u
    这次面试问题如下
    % J* J2 ^3 \2 t9 g) O6 C
    ) {" Q3 H* k' Q: N+ [: f( r% F@Value的用法$ p0 I( c3 t4 S' {: _) t( S
    , F7 Q9 D$ [# p  z! M! R
    @Value数据来源
    8 i1 l: B+ A$ U; x4 ~
    # s# T) S6 @+ r  K8 s# z@Value动态刷新的问题
    + Z. r, F' `5 h7 S6 j4 d& |& R9 Y( S6 d
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    - l, n* w/ J' y# Q0 t
      K) b5 g1 t! _( t@Value的用法
      r. s9 v/ C* W* L  @4 a+ N  F
    ( x+ ^" V/ d. k: K6 U) K, o/ ~系统中需要连接db,连接db有很多配置信息。3 d! D+ v  X, m- ^- g
    % ]* G0 B! {) h7 A
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    ) t7 s1 M1 B" Z2 X+ [( }, o; \# L% p2 m+ c& t% w! f8 C6 P
    还有其他的一些配置信息。% Y8 v, Z( |0 i9 v9 f5 X

    ! r4 c& J' t; t我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    0 N, V+ `7 k" ~; o6 ?/ _) U6 }+ C( i) n& e7 J( P9 [
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。% M" t! T% `3 q6 {) t3 Z" \
    * j/ i0 o" d6 t! T" i
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。/ F. I4 E# N8 L# w5 k" Y. m: F6 I5 T
      ?% k0 v, s6 i) B
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。0 k7 a0 Q( A6 y

    . ?+ L6 N" ~* W7 d, }2 k0 Q@Value使用步骤: @0 W# c- ]1 i  `, R2 o
    6 w! b& y: X( U. _9 U9 E' u0 d
    步骤一:使用@PropertySource注解引入配置文件
    ) e) s  l# n6 N7 w! m* G" x& K3 B% c. Z' |+ l
    将@PropertySource放在类上面,如下% H/ A+ T# I& l/ V2 _
    1 `% S, X1 E( m6 |- x4 x
    @PropertySource({"配置文件路径1","配置文件路径2"...}), B/ P9 v; d: b2 \
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。/ M) k8 s& x) w' a" m& ^

    " {. p3 v' P4 X7 U: U. ~' F6 R如:
    ' M& I; f) m" p3 l7 C" u$ D' O
    7 m9 H* ~/ w, t7 S' O! s7 P@Component
    / S" n1 Y) o# `5 Q' t# S+ _@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    9 K) j" J; n7 W- H( ~5 Mpublic class DbConfig {7 M2 m" {& @: ]8 T6 z8 B: z. d
    }
    8 q- d% x5 U, B% p- b" R/ i步骤二:使用@Value注解引用配置文件的值7 ]* v% G: s( L) ~5 s% r- J4 W5 N; I
    & U' a+ L3 S% f1 m
    通过@Value引用上面配置文件中的值:! o( ]3 X" u# j: J" p# W5 \# v

    # l: a& E+ p( Y7 t语法4 O- @- ?. `+ x! R. p

    : `' c5 w2 P8 M. x9 E& ^& r9 L" O@Value("${配置文件中的key:默认值}")
    3 n& u5 o# t% B  t* Z@Value("${配置文件中的key}")
    # R: M9 Q2 O" E如:) A9 R+ b$ O& o
    9 U6 j5 C: b7 m$ s
    @Value("${password:123}")' @' x& G/ Q& v/ m0 V
    上面如果password不存在,将123作为值
    2 i& J7 K& Q( L( X5 q5 l; e; f! `% g" J5 \$ o
    @Value("${password}")6 \: N" A9 R- F
    上面如果password不存在,值为${password}
    ) D# R$ |5 V" I& Q! ^
    & }. c9 m6 B5 ^( z& j+ d假如配置文件如下
    + |; X9 e! x" j' P; A, R8 ~5 G9 q: A( h
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    & T+ y: o; [/ I& E* w9 ?jdbc.username=javacode# @6 S" r( z4 {& ^
    jdbc.password=javacode# }7 T" H; O/ q) b0 F/ q' b( I
    使用方式如下:
    1 Y3 {$ d7 T- w" k( {
    : e# O: v) z6 A; s6 I@Value("${jdbc.url}"), s0 P6 l# n  `
    private String url;# _+ C; r- l: a  o3 Q

    5 v4 k) t$ n3 z: q" a@Value("${jdbc.username}")+ S6 J+ n2 F% B  A+ b* v+ ]
    private String username;' D, T2 p. m! b
    7 y; N8 U- L. x5 N' o$ b4 z- Y
    @Value("${jdbc.password}")/ i% b+ K0 d' P% y# v
    private String password;7 a4 @) x( e3 A& V& E
    下面来看案例
    & q, O+ h6 D7 `
    - s+ \& Y0 ]; q1 k案例
    3 F9 Q3 ~% H/ ^1 L; Z, ~
    4 E$ E! U6 L" a# k' z' X; u3 C) W来个配置文件db.properties0 D3 r  P1 Z/ R$ k9 |% g* R6 J' T

    ' D+ Q* i) K( Z/ a: Hjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8; a) z. k+ N4 ?5 ]
    jdbc.username=javacode5 u$ q; f. s: K" h$ h
    jdbc.password=javacode
    * V. i- e9 O* M# U9 j来个配置类,使用@PropertySource引入上面的配置文件
      }2 y$ ^5 F% @3 o# K7 Z$ o& H: J: W6 K& ~
    package com.javacode2018.lesson002.demo18.test1;
    5 o5 S; D2 p0 G- e; c$ D
    * W  g/ `7 b" \1 b, Y3 Nimport org.springframework.beans.factory.annotation.Configurable;
    - H! w& t9 J" T0 iimport org.springframework.context.annotation.ComponentScan;5 ?$ I0 t1 ?0 N, T! E, p* [" N
    import org.springframework.context.annotation.PropertySource;$ \8 n# Z( [% V3 [

    / L. c: n# Z# H: L6 ]0 v: M" y@Configurable0 |. p$ A. m2 I2 {3 H
    @ComponentScan
    ! S/ b( ]: ]0 N1 Q: Q( `% j@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})3 e' ]5 H9 ^! }. E
    public class MainConfig1 {0 @4 W7 v; C  w0 C+ r, u
    }
    / W# A# j# o3 }3 G' b; g来个类,使用@Value来使用配置文件中的信息
    ! q6 }7 M" A2 _3 Q' a, O8 ~2 R9 M* o& b/ N
    package com.javacode2018.lesson002.demo18.test1;9 |5 y7 _1 }- t" v6 Q' [# z; m
    / c$ x; g! ?7 d" y8 x  {
    import org.springframework.beans.factory.annotation.Value;* o  G* N) J) t( W6 Z
    import org.springframework.stereotype.Component;
    ! x$ Z% i- i; `( C: R+ |1 L' y: Q! p6 u  b2 P
    @Component
    , S; b3 L( Z% s+ X+ {- t: v( Fpublic class DbConfig {! A; U4 o# p. ^7 {  ?
    # t2 a- e0 Q% ?1 l/ l2 U$ X* K) e
        @Value("${jdbc.url}")
    4 `6 t5 W4 G' }- c& D! W! p& v6 z    private String url;$ H1 z& ^2 s6 M( e

    * r9 g1 E9 T* @. l" V0 |* B6 v    @Value("${jdbc.username}")" p) z' ~2 |6 C& d& ~6 z" k! h
        private String username;
    ; G, B5 m  n3 I# Y% j" Z
    2 O3 s& s% |8 d8 C. M& P/ o* ?+ E    @Value("${jdbc.password}")6 m: n. X2 ~; W( }5 z
        private String password;: _% w' e: K1 q* T. g( \+ R

    & j  s) Z' H0 z    public String getUrl() {
    1 [# d+ F+ t. Q9 f1 L        return url;  N" X) i8 g6 {/ }2 d& x+ m1 I6 i5 G
        }
    6 m7 c# N0 h- e- w4 t: P* q! Y6 B+ w* G/ N" F5 ~
        public void setUrl(String url) {
    ' i- V9 K) o: ~/ t9 j# R/ _        this.url = url;) h& y) d0 S, r' g- ~2 z0 r# p
        }
    3 f$ _: P9 {% C. J) w0 l
    $ ]6 e. {% C! @! `" U1 z% g  h    public String getUsername() {' b0 r/ K; K$ v* y, c
            return username;
    , T% F! ~8 r# D, Q    }
    : ]; d; j' I) u3 B+ L5 q: C+ s1 y  E0 Z
        public void setUsername(String username) {. L* A! V5 N. Q
            this.username = username;! U( ^" s& K1 s; u
        }
    , l) R/ c0 F6 [+ `4 }& |5 s$ u- M! {; U; E; @" g
        public String getPassword() {! |+ z, k; }+ y7 M8 |
            return password;
    : S' H( S8 ~1 `/ i. l) z  h8 _" m/ W, Q    }
    - H' z/ V  ?# K1 p" {$ r2 J+ D6 Y
    - l8 X/ m) T5 j4 y9 E    public void setPassword(String password) {
    % j- I- _$ q, d+ J8 e& }        this.password = password;
    6 w& L+ h$ `1 x5 ?" h8 o    }! J/ f. X9 ~, n8 d+ K
      Y0 R- q: m" R1 W( l9 x
        @Override
    8 e" S4 M$ e5 g* v# P  u  P0 q    public String toString() {$ A5 B. m* A3 ]  o& u5 c+ A# C) O
            return "DbConfig{" +) @6 |# V# L" j! y& f2 l
                    "url='" + url + '\'' +" Z* c( P; Q  h
                    ", username='" + username + '\'' +' a8 ]* i6 O3 n
                    ", password='" + password + '\'' ++ y* ^6 T+ h, [$ s5 i$ H. E) K
                    '}';- S" I1 n+ |, q0 z* [
        }
    , O+ V: ~7 E" y, I4 |}- F8 Y* i2 l# L0 P
    上面重点在于注解@Value注解,注意@Value注解中的+ [6 i# E" C; z+ U9 ~$ \

    & S+ p+ b, ^/ d9 @" T& k+ q来个测试用例% O6 x! G2 w# f; p

    " L, b& J. E: J" jpackage com.javacode2018.lesson002.demo18;
    7 Q& f" v  W* m4 }
    ! N, d* L" O7 v4 ^0 U3 H2 Gimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    & X9 d9 o# x- Limport com.javacode2018.lesson002.demo18.test1.MainConfig1;( M% ~1 g/ r( R
    import org.junit.Test;
    . u3 }( [0 M& [9 f8 simport org.springframework.context.annotation.AnnotationConfigApplicationContext;/ Y. {& l3 A! T3 A

    " U/ l5 r  ]! Y) X6 }/ ]public class ValueTest {( J# I) B4 E8 R: D4 }9 l* {: p

    ! C8 M5 E2 J" D/ k. o    @Test
    2 ]* s7 W2 n# P" J+ a    public void test1() {- [6 M& ?( F9 Z3 e
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    0 o9 W+ h5 H/ n  T, @/ B$ W        context.register(MainConfig1.class);
    - E6 @' T9 }# W* R: a7 g        context.refresh();
    9 `; s+ V/ O; `7 J% ?6 ?) x# g& l$ x7 _  o5 i# Y$ }1 A+ q
            DbConfig dbConfig = context.getBean(DbConfig.class);# P: L) s* v7 n' H, @) n
            System.out.println(dbConfig);5 @, W6 x- r! Z3 J" p( h# @: Z& c( r
        }" [& ]) i, X: e% F5 p# b3 i
    }
    6 {! \6 W& ^2 R" g( U运行输出
    , k- Y1 a% R2 |* g, L$ S8 C9 p! S2 {
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    " K) Y, M" Z2 Z; t( w" C上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。+ Y% L) Z% p; D5 ?0 }5 c

    ' k+ T/ J# R2 K0 d1 k. Y6 g@Value数据来源
    " Y/ {/ b! M7 w; \& O. {8 c" w5 ~) ?* ?
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。0 y8 t: X6 v& O: K; G
    ; ?+ l$ x6 V6 V* @6 g
    我们需要先了解一下@Value中数据来源于spring的什么地方。1 y- A( m% e3 w

    * D! U6 D& O3 n5 C0 `' v: o% i# \spring中有个类
    6 i  Q9 \0 C4 r/ u, F3 j: J) d: I0 L+ F+ H
    org.springframework.core.env.PropertySource) o/ t+ h. V& o) R% {2 ]
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息( {1 o% L1 Z/ M) g+ K' ^: ^
    4 F) T! i" u( Z8 m% I' s
    内部有个方法:* d; t% l( g* h0 K

    2 Q# i9 D" }- Y! i1 A( npublic abstract Object getProperty(String name);3 Q$ @, x  Q) @9 I! M) b" {  k
    通过name获取对应的配置信息。  K% u2 F5 t* Y

    / _, q! ?; P, e* t系统有个比较重要的接口0 `; L4 z5 h' |* v. [$ C, W$ ~- Q
    5 I/ w  C1 h  w% D8 H
    org.springframework.core.env.Environment8 r6 l# o& i8 E" Y
    用来表示环境配置信息,这个接口有几个方法比较重要+ ]" g) U0 v1 u# C; n' w1 k

    ( y' ?4 _; F5 }; E! a' S2 q1 j9 W" s! _String resolvePlaceholders(String text);* r' J1 G2 P1 E( ?
    MutablePropertySources getPropertySources();' G3 R6 ]3 C* {) n- e; C
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    ; @" e: W9 |8 x6 Q% Y# W7 S" v# v, c  J  n0 M8 R; @# z* C
    getPropertySources返回MutablePropertySources对象,来看一下这个类9 P' k" K5 d+ z; E$ R- t
    ) q: ~( L0 c) h; j( |7 e
    public class MutablePropertySources implements PropertySources {
    , o3 j( L$ M; T8 E& @% I6 ]( y$ T: L# d2 ^0 p: {2 e" u4 i. y
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    2 O0 |/ h2 y' N+ O% {/ V5 W: n" c- z5 I# ]  D0 f7 R/ M! a
    }2 \. V" S8 T3 K+ |, v' y
    内部包含一个propertySourceList列表。
    ! P) }2 \& g( `+ K- b$ B& p9 k: I: S  o
    : Y/ }/ a6 Y9 A* bspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。4 j$ `) I$ l6 r" a5 E6 l. D

    - J; B  W2 l3 g- N/ \+ R大家可以捋一下,最终解析@Value的过程:/ q8 O0 d6 F8 @* p. I) V$ T
    ; L+ X6 W/ D( @: }' ~
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    : z& Q- ]7 Y4 B* P& z' N9 p2. Environment内部会访问MutablePropertySources来解析
    8 O* G+ M& V3 W$ X# L: L( z  P6 x3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    0 I# B/ D2 v  s! \4 d通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    8 j! B- P* K: V7 j" O8 Q: m/ Z1 U" ~. a3 E6 D- K7 p( w( Z8 O
    下面我们就按照这个思路来一个。
    8 {) Q& X# H, g) b" O5 ]% f3 j( M- m3 _" z3 X
    来个邮件配置信息类,内部使用@Value注入邮件配置信息% R# e; h6 T9 b

    + Y( p+ c" d; s- Apackage com.javacode2018.lesson002.demo18.test2;
    $ I6 @/ H6 b- G/ c
    , G" R3 ]- z9 q  ?* gimport org.springframework.beans.factory.annotation.Value;
    " O0 h' m4 J$ {1 b$ p  \import org.springframework.stereotype.Component;4 m9 i* R7 J7 L! `1 k# d+ i

    % F6 L# o2 Q) Y% l9 R2 @/ H/**
    ! V( w8 \# s; M4 ~! S9 P' p. \/ i * 邮件配置信息1 M) s- m- b/ n" e: j, {$ a
    */* v8 n$ @) |% _  N
    @Component* c$ d; q' O) ]( G' \$ M7 A
    public class MailConfig {
    & d' A8 Q& M: Y: B- j
    2 A3 }( q/ T+ N  u" r7 @% q/ r    @Value("${mail.host}")9 W' A3 m% P" ^( b- d0 r: ^
        private String host;4 @3 m) f8 ^6 t! Q: d
    - o9 o8 @; I" y+ {2 c' @6 G
        @Value("${mail.username}")
    + X9 g+ a' Z: ]. l, {6 G3 g( a    private String username;
    $ k1 f" R( f- b) L) i7 l* s  W* o/ s0 w" B
        @Value("${mail.password}")
    / b* E- B6 R8 F    private String password;
    + w. m! p  n& h0 g/ d+ x
    ! R7 Q: `: M: e3 ?. k( B    public String getHost() {! u3 P1 V. r3 o, F
            return host;
    " m( a& \6 i. }! ]9 C    }
    ' t7 A- V1 f% u) s* }( d1 K' P; T- c  T6 }; E3 _
        public void setHost(String host) {
    0 F- z- @. W9 ]. Y( ^$ n( x" r3 l        this.host = host;" U) X$ |  k( I
        }& P' S0 O' c3 Z% v7 b4 B& U
    , P3 h$ G% x6 E+ y
        public String getUsername() {1 g7 L, A: t: @" C4 @7 h3 n1 r
            return username;
    + V% i/ r! _7 \$ S/ Y- r    }& A0 V, Y; W( \& d/ a
    . H: p8 ^, J0 o
        public void setUsername(String username) {
    6 P* I- v% V# O5 a        this.username = username;
      m5 z5 j0 X% |3 z% Z8 [! c    }8 T- I# v3 ^8 ^9 x5 E% Y

    ! d) u! D0 `5 S$ p" B+ H1 v. V    public String getPassword() {
    9 x, c8 m0 ^$ y, j3 b- R        return password;
    & j% I7 g3 r1 }/ v; y    }
    & N# x# \# h9 k0 h* h
      [# m, c# M. E$ ?    public void setPassword(String password) {7 f4 s/ D% d; k. i& i# @4 p6 b
            this.password = password;7 Z3 L& T0 r$ B; F/ X) P
        }
    ( r. Y* ?6 o: o" }) _! _; S. C: x+ Q8 I! F! w; v* M; `
        @Override; e3 |9 o* J+ n/ S, }- I
        public String toString() {6 ]. p3 d- m" x( ~# ^; K; W
            return "MailConfig{" +8 A( r$ _$ I3 B1 v
                    "host='" + host + '\'' +
    $ h1 o1 ]- E7 `6 z                ", username='" + username + '\'' +
    / R- @* @+ W0 J# h                ", password='" + password + '\'' +
      Z9 k5 _' I1 p, v! R( c6 E                '}';
    4 f/ J" E/ c0 {+ w2 o+ x2 }    }
    ) C2 Z6 m( g# G}
    2 s, [" t& W8 e. H: y3 {  l3 K9 @0 [再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中% x3 u! y4 F6 ^. w
    4 ?0 |# }9 N0 H+ D$ v
    package com.javacode2018.lesson002.demo18.test2;
    1 ]! H( M: |3 Y8 n! V) w2 E
    ; r6 t6 x: P/ F# B8 ximport java.util.HashMap;
    ! l  y3 @' j0 {* o' {3 Simport java.util.Map;; w1 \% ~" Z/ |% s' |3 ~

    ' y0 C: V% E( H# }1 k6 Jpublic class DbUtil {" C$ i5 i! |2 Q  N+ u5 g/ ]8 p  b
        /**; @2 T8 j4 m! H+ [9 [5 y  y
         * 模拟从db中获取邮件配置信息
    + L) }  r. v9 i/ y" j     *
    8 C6 G  D( z  _/ O6 o, f     * @return
    ; b4 e5 K- U/ U8 o     */
    / Q! s: ~) b# P% I    public static Map<String, Object> getMailInfoFromDb() {" N: E0 H4 K, t( Z9 A* W
            Map<String, Object> result = new HashMap<>();
    ' N2 x0 g( d! I  q% r1 e3 o; P        result.put("mail.host", "smtp.qq.com");5 y( H  P& y( H# ?
            result.put("mail.username", "路人");
    ! u$ @. V. r' V$ L        result.put("mail.password", "123");% F8 D' `+ D; [, a( l$ v/ F  ?
            return result;
    ) F8 ?( o, ~8 M    }! X' C9 r" I7 E0 S, G8 |6 T9 ]
    }1 @* p4 G4 l- K  i
    来个spring配置类  Q' U9 @( c; ]0 p  ^3 W/ i4 {; L0 m9 g
    ; c' }; j: C) L7 m
    package com.javacode2018.lesson002.demo18.test2;
    8 u1 E& j! }0 K5 B3 [9 Z  m9 w) l
    # A0 r, D4 D& x( q$ Y4 cimport org.springframework.context.annotation.ComponentScan;+ J! B( Q3 y  C! M3 S
    import org.springframework.context.annotation.Configuration;
    $ X0 {, ]% C& b# c2 c! q- y, L) l
    . Z; ^3 _0 Q0 Q. H$ R, I& k@Configuration
    ( K" j/ F; u/ Y# h- S- O@ComponentScan; L. O9 `6 l* A! k, y9 c: X$ v. y" G
    public class MainConfig2 {
    & \- A0 ^4 z% f1 p}
    ' e" `# l+ q2 O, b/ y) Y- c下面是重点代码0 X' w( m. O8 W3 A7 F" T* c5 e" T
    - {. Q- }" Y: n0 ?2 X+ l+ u
    @Test
    7 [# b/ ?3 p( R0 z' \% q1 z5 {public void test2() {
    ) T- K8 ^7 l) r. `    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    & Z3 f( G8 J/ q! ]: D0 |5 D% l7 d0 k
        /*下面这段是关键 start*/
    . s. V8 W/ l" {* k    //模拟从db中获取配置信息, V3 d5 Q+ M/ ]6 U  n
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();$ i7 J% l, S! B0 C8 g( r7 d
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)- \% o/ f5 I8 G, W! Q/ p
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # c5 ]* q8 {6 j% @" n    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    " l8 Q% x7 z4 N2 |. t* f    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    / u% h! u5 V  ?    /*上面这段是关键 end*/
    $ `) E+ e4 E: c" O% i& ^. G& o3 y' _
    * l( S/ a7 y& I: O, E4 C* W    context.register(MainConfig2.class);9 p. A" m# {8 a
        context.refresh();/ y0 X' \6 {8 f3 D& ?1 f! i+ v
        MailConfig mailConfig = context.getBean(MailConfig.class);5 a3 f3 h0 G* U8 u8 i
        System.out.println(mailConfig);
    ) W: s& L( j, h  Z4 ^# m9 ?) z}
    ) ^/ k  A( V/ v# M$ d5 D. u& \注释比较详细,就不详细解释了。) f9 u2 S$ R- e6 ~. ~! G0 y- F
    + t& T3 P& e5 n, d% u, j
    直接运行,看效果; W  e- E" L. e) Z6 q& }) x

    - A1 c' G/ m. p0 x2 p, A/ cMailConfig{host='smtp.qq.com', username='路人', password='123'}8 u0 ]) B1 A! }! |  `) ]
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    & b. @/ ^4 H3 ~3 e: u$ m! r: ^
    ) Z; \7 h- e- z# D, d上面重点是下面这段代码,大家需要理解! D5 C$ ], Z  x( i

    9 b+ \: a# T3 a; l! w- w/ b/*下面这段是关键 start*/
    / k$ @; p+ T1 H1 _* C2 I; r1 t9 O//模拟从db中获取配置信息4 B, z" g( e0 ^+ B
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();% i3 Z5 l7 F  w* r, I
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)6 W9 |; Y; W9 W. g, \
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);" r3 k  @! T+ k3 X) K
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    9 B( v7 k* s  w0 O( e7 G* Icontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    . u/ J2 q, ?; l7 L# a' S( H5 x/*上面这段是关键 end*/' q+ ^" Z+ C, Q- r, D
    咱们继续看下一个问题: O7 U( e5 O( i  Q. b$ e. m
    & t+ q- d' A- w# o
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    + K1 N+ `0 f+ m8 y9 W5 g! q/ ~) ~' y( O" g& {# d
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。- a3 q' w! o& x1 ~

    * m7 o8 E6 k6 `5 ]; V实现@Value动态刷新9 @) y! z: _9 b
    ; ]9 e4 {: H4 M7 |4 X5 K
    先了解一个知识点
    + U. ^- F8 Q8 D$ J8 s( v* }' b. R% M+ Q6 O
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    6 @# R4 l, t4 ?3 b4 g9 Q2 I2 T# D8 B4 f$ e6 t: @8 G% O
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    / B8 o+ o& S/ v- s
    " E3 _2 ~0 M1 `% Pbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    ; ?7 ?7 c' M9 O: |/ {
    " {9 j! l$ I4 G3 V: C: pScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;9 J6 {+ l* r& y: Z. g
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    ) B5 F" [4 O7 P
    5 Q" G- ~' S% E( y6 U) l3 g' Fpublic enum ScopedProxyMode {
    0 W& N- `5 a, @5 q: a9 J    DEFAULT,2 `# Z; J7 w/ S
        NO,$ Y  H; w7 n% U  u
        INTERFACES,, G6 v; {, e8 c! t. U* z7 N; P; }$ W
        TARGET_CLASS;: @/ z  O$ k8 T7 Y
    }+ G" j9 s. J( v
    前面3个,不讲了,直接讲最后一个值是干什么的。* N: I1 y8 T6 R" X+ }  O. W, L

    ( e/ D+ a- X/ f0 o  D, c' r* F当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。9 @6 ?. S! y% e. n7 i0 H3 {" G

    ' v) G0 j, i6 ^% P* x' u, f! U+ p理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。! r& _: W" A- J% H/ h2 e( i

    ; u3 m- F- `2 F自定义一个bean作用域的注解
    " Q6 c. D1 I5 ~- g
    / M$ W" r5 ?2 p1 r, }! |: Ipackage com.javacode2018.lesson002.demo18.test3;: X# X$ U+ F: [/ P; u* m
    3 ^- N! c  o# N2 \+ R! ]
    import org.springframework.context.annotation.Scope;
    $ [) v( h# T) Zimport org.springframework.context.annotation.ScopedProxyMode;
    ; h8 c7 v8 z$ L$ d+ C- U& i2 |; G
    import java.lang.annotation.*;( J0 ~) w- R- u

    9 u- O! C8 S# X. K. B4 t@Target({ElementType.TYPE, ElementType.METHOD}): [9 t; ~' A9 ]
    @Retention(RetentionPolicy.RUNTIME)
    ! R- \) k8 d1 Y8 S2 L% J  {4 z8 W@Documented
    8 f" w  [- ^2 P* Y7 k  v@Scope(BeanMyScope.SCOPE_MY) //@1
    2 N  N0 y7 [: {: R+ }! v+ T" `4 ppublic @interface MyScope {& _8 n- z2 j7 g& l8 e7 d, Y
        /**
    ) }2 _0 @3 B" @     * @see Scope#proxyMode()& a5 v7 n8 r- @& Y6 b5 K: a* L
         */
    1 }3 N9 K3 a5 C6 E    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2( c- e4 T3 K( y9 q
    }
    ) `4 I0 k. W3 {@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。) V* r! s; r4 K5 n7 p. i! k
    0 ?  ~: J& {1 H6 o
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS5 r8 @# n1 J, _( {" L5 K' v
    7 x# L/ |* H; J
    @MyScope注解对应的Scope实现如下
    - V* t1 z$ Q) k' k8 i- k1 {# V, Q- W$ n) _( K; C+ i; y
    package com.javacode2018.lesson002.demo18.test3;) S4 ^4 T+ O0 o5 `; g. |

    , O; K  i4 O- C$ v2 eimport org.springframework.beans.factory.ObjectFactory;
      J1 s0 c8 p) D2 K' bimport org.springframework.beans.factory.config.Scope;& E: c  ?  h2 _
    import org.springframework.lang.Nullable;
    8 U) Y' \7 k6 P5 c7 v9 F" k" B2 i- w' }6 b. g2 f
    /**- a+ \6 ^& x/ y8 b: f
    * @see MyScope 作用域的实现: T  Q8 }5 p' \( `
    */2 b) E! B* g0 @- N
    public class BeanMyScope implements Scope {
    ; L+ |( c, d+ {  M; C$ N0 h$ Q' _/ |! V( M3 _' Z& J
        public static final String SCOPE_MY = "my"; //@1
      P0 @- [1 ~' ^, O8 u) p5 H+ A, v  e2 o) p# r
        @Override
    ' _/ e: w8 f: F; |% b# B/ K6 @6 K7 Q6 G    public Object get(String name, ObjectFactory<?> objectFactory) { ' k+ Q" m2 ~$ v: d+ Z
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    $ }& v" V& {1 P/ r5 a        return objectFactory.getObject(); //@3! Q0 H9 Q9 n" A! a# o
        }, R, J' s( k# m3 B

    ! D1 v& C" t% {  C6 T( b8 I4 R    @Nullable
    5 L% M: A' i* x3 [+ b0 ]    @Override& M* J/ o- \: l0 S5 K$ r
        public Object remove(String name) {$ t4 \/ d) L5 I' M  u, a8 z0 i. h
            return null;
    0 v  X6 X, N2 A1 @8 `- Z0 v5 S0 j    }
    ( h; u$ B1 c9 j; Z+ q) z
    9 Y. X/ o% q6 n4 W$ C# r    @Override
    / q* z1 v1 a3 @% _$ e( n    public void registerDestructionCallback(String name, Runnable callback) {4 }# N* k0 G# P3 b% G5 d0 F
    2 j8 H* O8 w, @4 M# R3 c7 G
        }8 G$ W8 w) U' j

    6 b# s1 r1 g7 k1 Y& S7 e    @Nullable2 K7 X% N: `8 G" Q! a
        @Override
    5 f+ Q8 e; C' y    public Object resolveContextualObject(String key) {! M  A' U" c. [# |9 p* K6 R7 n
            return null;( h; O0 e8 |0 e, ~+ b" O
        }) u0 P4 p4 V, e- ^. N; F4 Y7 A2 R

    & T6 Z# E6 ]) p/ G; ~    @Nullable
    # P3 B/ M! Z# o' f7 F( t    @Override" R7 Y( t! s+ }  u% h
        public String getConversationId() {
    5 A2 O$ c6 B6 C$ N8 _        return null;
    * k, k$ M- D3 w: ?9 X    }
    6 o. i5 F- u/ w  [: w. A9 s}3 m# t7 B( R! R& C3 w( l
    @1:定义了一个常量,作为作用域的值
    2 F5 F. [+ d+ r# i0 O& c( p: V* Q
    ( Q7 b. k2 b6 d. K$ k* X7 t' @) b9 }: M@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果" a/ n) s0 |! _3 U
    " q" z* c9 f' w6 \4 j6 Y+ H
    @3:通过objectFactory.getObject()获取bean实例返回。
    $ E, |- J8 H9 k2 C5 E
    ! X. O% P, m8 x下面来创建个类,作用域为上面自定义的作用域
    ! d, j  S) h! y+ G' V
    - e6 H! g4 O# M% Y: A2 H1 w" `6 q. hpackage com.javacode2018.lesson002.demo18.test3;. i) O# T1 \5 s2 _: I/ u
      r. G, O5 @' W$ Z3 `% Q! N
    import org.springframework.stereotype.Component;
      X( }; G% A& A2 o' K, E" @( ?$ d
    6 a1 i, Q- _8 l0 Q& n9 d! yimport java.util.UUID;% p$ x0 P. v' ^; v/ C( W
    ; P/ j% I4 k. p% `  X$ r* {3 S
    @Component; ~' O$ n# Z, z4 ?; m( i% {
    @MyScope //@1 9 f8 V7 [7 v9 B
    public class User {3 l  n/ T' T, H4 Q: j
    $ Q* T0 H; z8 q5 N% w$ n3 R) p
        private String username;
    / c. M! e9 q  U+ w  }% p
    % S) d' O/ s2 l" w    public User() { ! g' i9 Z2 s  v( s0 e: E
            System.out.println("---------创建User对象" + this); //@26 S1 g) M9 f/ T0 q
            this.username = UUID.randomUUID().toString(); //@3  s% G. R$ b6 E% F1 Z- P
        }$ P% S0 @5 t% k7 _6 b3 y/ ^( p/ l# g+ `

    , x! J, v, f  h. n& ~    public String getUsername() {
    ; w9 O) V+ s; \2 \        return username;% e; w0 _7 a5 f1 S9 x5 j
        }
    ( a. i* c5 b  b: R6 T& L; Y) s% t* P9 _9 ^. }; m: d
        public void setUsername(String username) {
    2 f+ B  R$ m7 v4 A& Q  u        this.username = username;
    ; `$ z- n& m) R( o" b6 P% v    }7 {- c! g4 I  ^) A0 i; [
    ) B) ~2 m3 Z) `5 A5 b6 T# W; L3 l
    }
    2 `$ @+ x# ^$ E2 |+ j. x- K@1:使用了自定义的作用域@MyScope
    0 D7 [' B8 Q1 y
    % Y) n- j/ w8 S' d* `' @@2:构造函数中输出一行日志( Y8 ?4 n- ]6 U1 p
    5 v" {# J3 D! m7 i# b  b
    @3:给username赋值,通过uuid随机生成了一个
    + Y9 J" A' u+ n4 ~
    # H' a; P" k8 E* w, s来个spring配置类,加载上面@Compontent标注的组件
    5 ^$ t" W/ ?, k& C* Z9 d' z- ?- K$ }. x
    package com.javacode2018.lesson002.demo18.test3;
    . \  P& K( t+ y* g- i3 [! W- y, `0 [2 m% |
    import org.springframework.context.annotation.ComponentScan;6 E( `  w# o8 G0 Z
    import org.springframework.context.annotation.Configuration;2 Y4 n8 G' s% W1 ]& y5 L  ^( p
    9 P6 q7 v# w8 X( \; Y/ e
    @ComponentScan3 F6 K9 r+ g( d; F
    @Configuration3 V* }8 S7 M6 o" i
    public class MainConfig3 {
      I4 T1 P% }$ i5 n% w  J6 l}
    + n& P+ p8 n! V下面重点来了,测试用例* v, k4 f3 q/ g6 M; E9 G8 e' K9 ~, b

    & w, q. t2 l, \( [0 m# ]+ E# k4 l@Test
    ( k" ]8 h5 \8 ?% o& q" Spublic void test3() throws InterruptedException {  z' e9 t0 _' [: `2 ^4 q
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. c( k0 L, p5 F( a5 Q
        //将自定义作用域注册到spring容器中
    * O# Z: ?. p7 K: x7 T    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    0 G. ]% ~( G0 K$ X& l: B& n    context.register(MainConfig3.class);
    , Y$ {( @+ g/ i    context.refresh();$ {* d$ A- \: X! z
    $ ^, G% r& ~, k$ K9 ]  \) l# |8 i
        System.out.println("从容器中获取User对象");2 k5 ^1 `3 z2 q" o7 R
        User user = context.getBean(User.class); //@2/ p- J! z7 Y* f9 X! H
        System.out.println("user对象的class为:" + user.getClass()); //@3$ G/ j7 i3 l  M

    $ {$ x- i" a, D: T6 m    System.out.println("多次调用user的getUsername感受一下效果\n");# s+ f7 g* F9 D5 V+ J- `0 K
        for (int i = 1; i <= 3; i++) {
    ; M7 G& k" @1 m/ V        System.out.println(String.format("********\n第%d次开始调用getUsername", i));+ b. V7 X5 w5 h, n0 ?
            System.out.println(user.getUsername());
    8 G. l3 M5 ^4 }% l5 F: S1 T7 }0 X        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));$ p1 F8 A2 ^, y( U: D
        }# f! y, _4 \$ d& M9 W7 J
    }
    $ Q  C. u2 ?9 k( J; o* u@1:将自定义作用域注册到spring容器中
    ) ]2 A6 `' {! O) h! e6 z
    * }1 j& p4 b& m: b$ w# ?@2:从容器中获取User对应的bean
    / O' }. T2 w, P* c! _( J3 _; G7 J% |9 A$ W( s
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的7 }" @  ~4 d+ h7 M$ N; u0 W

    8 [3 s6 H% P; w9 B0 S$ X" K代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    - D) V+ v# ?9 j3 U+ x
    6 e' c, v" v8 c1 M( m见证奇迹的时候到了,运行输出
    ; b  \) k3 S6 A4 l+ J8 R/ k( x
    % O# @. p  S" T* ^$ }5 i: R从容器中获取User对象
    ' M0 E( X0 V- I- [/ ^( Iuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    # d, l, q1 C, j( S8 x' Q- e) k多次调用user的getUsername感受一下效果
    % j. v: L- Q  m& k, D7 U! c: M  P$ E& E( {% \3 U; O( N
    ********% j' @7 q+ s- Y
    第1次开始调用getUsername
    ) k& `5 Q# Z7 E. q6 ZBeanMyScope >>>>>>>>> get:scopedTarget.user
    1 z7 B/ W& I; U+ w, y/ f% i---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    * w' A1 V1 h" k% c. B  ]) d  H+ m7b41aa80-7569-4072-9d40-ec9bfb92f4381 X1 g$ C+ U' h4 Q3 h+ c0 z
    第1次调用getUsername结束3 I4 I6 e8 \0 T
    ********" m1 Z* M) f1 k+ z0 }/ S

    0 `+ p0 F$ b3 j. ]) X********: T) |7 b. f) Y) n" _1 J& J
    第2次开始调用getUsername
    ) f: p# D7 q4 L; K/ I3 BBeanMyScope >>>>>>>>> get:scopedTarget.user
    2 y( A1 k4 a8 M% |8 ]" ?$ w---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    4 m& U1 O# W3 A( I4 @( ]01d67154-95f6-44bb-93ab-05a34abdf51f
    + z$ d* j" e2 I0 I' R第2次调用getUsername结束
    1 c  ^: N5 j- d********
    $ y& T2 L+ p9 z: g9 Y& @* _5 }
    " R; T& S3 D) ]9 `, B' x& Z********* H" B" K7 _/ s; `& L8 y4 P1 Y# w- k: ?
    第3次开始调用getUsername
    ( X1 d. u8 U0 S6 jBeanMyScope >>>>>>>>> get:scopedTarget.user3 {- i+ B* x! s9 j' M. ?
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    $ F: C& [, L. u7 ?/ @6 F76d0e86f-8331-4303-aac7-4acce0b258b89 K/ R0 J: v. @, L, Q/ i
    第3次调用getUsername结束
    9 r7 B2 l. u. {1 s) t# @9 Z********
    ! z) ]+ L% J, `3 M& x% |" E从输出的前2行可以看出:9 J2 I% g$ `  e

    # f1 N$ G: V) g- p! k% \( C9 s调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象! |; F, O, b& d

    + D, q6 w# ~: E, j第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    1 b. ~; o( `7 h2 @
    9 X5 l) o8 M. p2 o! m  }( l后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    ) ?4 f5 M* o+ K0 t# y  T5 T" k, R" f' _# @* k0 ?
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。% J; I: X: d$ k% u$ M& G, X  A
    , p  @' I) m" H; u& X: X2 C
    动态刷新@Value具体实现( U6 e9 f6 K7 T
    : i1 S  A# k/ Y9 D; l
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。9 Y  h3 D8 K8 z% y9 {$ {1 p

    6 W% }2 F+ P* f7 f) F0 ]先来自定义一个Scope:RefreshScope
    , f1 n* s7 h4 ^/ t+ H/ t. N( [9 W. _; n4 ~1 f
    package com.javacode2018.lesson002.demo18.test4;
    5 I! M6 A2 k" P0 F2 s* f! l  Q
    2 S; C$ m& X9 C! C2 ]import org.springframework.context.annotation.Scope;% g& h8 r. j% E: z  S6 N
    import org.springframework.context.annotation.ScopedProxyMode;
    # C! F% {/ F7 o& c/ g% s9 j6 }
    import java.lang.annotation.*;: \2 ^; k+ P, _  v6 X2 r0 B) t0 ]+ n9 e% d

    " R) |. g  u6 \9 k@Target({ElementType.TYPE, ElementType.METHOD})
    ' n8 g! h: y, u) o@Retention(RetentionPolicy.RUNTIME)+ m3 l# A- [# {) a) z1 W
    @Scope(BeanRefreshScope.SCOPE_REFRESH)/ l2 T! ~8 y7 }
    @Documented
    ; x1 @( \+ A8 S+ J& [public @interface RefreshScope {. S& k+ [6 _+ {- P# O8 x
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    5 {3 K6 B, O% B}! P5 L& x% a( \3 ~2 Y
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置% {4 _7 {4 L1 [2 o: |/ `4 M& {
    / h0 D& D, O, z5 H1 K! `. E
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    1 |0 a% e' k/ e- b. R8 b" T
    3 j# z. a7 E8 Z/ [4 O5 m这个自定义Scope对应的解析类
    + Q  ?+ O" H2 p4 Z* I; W% o3 o  D
    下面类中有几个无关的方法去掉了,可以忽略' y* J, R& ~: Y0 E
    ! @' F3 K2 e) K! a
    package com.javacode2018.lesson002.demo18.test4;0 a1 o+ p1 X+ D' c$ U
    ) H8 V8 y0 X% c; A

    1 |+ a8 Y2 O3 j- G+ o0 a3 n% n9 I, oimport org.springframework.beans.factory.ObjectFactory;
    2 i; N  [  N& B& m! k% `import org.springframework.beans.factory.config.Scope;) V- j& P; `! |, |' _
    import org.springframework.lang.Nullable;
    / P6 E* `4 T1 ], V3 S) [! i3 L: t: d" g# u# D
    import java.util.concurrent.ConcurrentHashMap;
    , }! X. a7 F. h! c. |4 P7 G. z% V0 r  u5 o' l6 Y! `2 j- I8 h+ [
    public class BeanRefreshScope implements Scope {
    * \2 k/ L  A+ a, i  V& n
    % M+ z/ ]* ^' P5 b9 h3 ~    public static final String SCOPE_REFRESH = "refresh";  ^% E! }" T/ M% i$ s
    5 z: G- E+ t1 |5 V- ]5 G: z& h
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();$ _  V, B5 E% x* H3 m, D
    7 \- X' `5 m/ a
        //来个map用来缓存bean* p7 f! B* T( l  ]( j
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    % V( u/ G  l+ B) y
    9 i2 K$ m1 K. y3 h& T, A    private BeanRefreshScope() {$ r9 O# M" S4 i5 U
        }* {6 m' {; @* ]$ G
    ! F- q" r% G+ t/ n- B  j: B
        public static BeanRefreshScope getInstance() {' k# O6 N! Y% J- G# o2 [
            return INSTANCE;2 E8 d, R9 V( y& c3 t
        }
    + a; V. n' O, Q& V" i% ^1 l
    ! ]+ K4 g6 B1 e    /**% t  Y& \# K/ w, Z4 Q. G
         * 清理当前
    ! v( Z( y5 l% g) E9 X     */
    . ]" _, w" t' y" g2 b: `    public static void clean() {; B9 ^3 [* e0 g. ~/ \* ?8 b# z* w
            INSTANCE.beanMap.clear();* ?% v0 A, Y, v# N$ F1 f: M
        }  G8 G6 u; p3 |3 l2 ?* c

    ' p" h% I) @! a  s/ k: [, I* p    @Override. w5 U' R6 q! D8 F* M8 M
        public Object get(String name, ObjectFactory<?> objectFactory) {
    $ ?  m  q) I% x0 \% i        Object bean = beanMap.get(name);
    2 b6 u' h8 c: a9 ]        if (bean == null) {( n+ P# _" E% l* f8 d( d) J6 ]
                bean = objectFactory.getObject();
    , t: _/ L2 e: Z6 P8 C" y            beanMap.put(name, bean);
    6 M6 ]4 @9 q8 ?% `, O7 a        }6 b3 E1 m; Z$ w6 M; U/ W: I1 n) m
            return bean;
    * U- a; ~, S2 @5 F. O( t    }* ~. p% q! s( K

    3 W0 ^- S3 E; ^* i3 N}( j2 I0 N8 d) R, l; {
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中( O, o! k; Q# h9 j5 u

    2 u. r1 K5 T4 w' V4 j上面的clean方法用来清理beanMap中当前已缓存的所有bean9 B5 u1 N" |$ T0 V- M1 \
      k2 @) ~& ]& M7 }- n" q
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope( Y$ m9 \2 l% m6 U) g5 @0 U

    4 b8 j8 W! F6 @0 u% Bpackage com.javacode2018.lesson002.demo18.test4;
    0 g- `0 }( J% B4 T6 L: S# b9 {- `' Z
    import org.springframework.beans.factory.annotation.Value;
    : C1 \& _) V: o6 V; [* \: J1 cimport org.springframework.stereotype.Component;$ Q1 Q$ U* T' b/ ~: z8 M1 ]% v

    - Z- }& B5 c: _9 y& {/**
    9 n& D/ [+ {6 r8 G * 邮件配置信息, g* X! [/ c% F6 }( J! ^0 ~( l7 A
    */' [, _" m- x0 \
    @Component; U0 c, r7 g4 [% n* r& l/ G/ L. L
    @RefreshScope //@1
    ) v, N. v# U, O# C) T  O$ rpublic class MailConfig {
    $ T3 q6 \0 C% ^$ {* `& q$ ], ~" A& D
        @Value("${mail.username}") //@2
    7 Y/ B% i& H6 Y8 H7 @7 O" ^6 ?" Z    private String username;
    + d; E6 o: \1 N6 D, L( ~
    % i% x, }" ]' I" \; {; ]    public String getUsername() {2 f) }( p2 S7 h% U
            return username;
    3 B1 |9 W" n. m8 k  {/ n+ [' @    }8 p/ }4 d; D: l. L1 Q) q
    2 w2 r* M9 ]- I9 W- \
        public void setUsername(String username) {
    ) s+ f8 V" i, p7 I) @* N3 _% i        this.username = username;; J& b; [. z. C3 C' n0 m
        }
    9 z5 P& q, c7 ]2 G  \
    1 C1 O% N* L$ c% q. \    @Override
    + y7 `/ p# I' A" K8 k" g2 Q    public String toString() {* \& f& W% [5 f. b
            return "MailConfig{" +$ J, X3 Z' E' H9 {8 v: b
                    "username='" + username + '\'' +9 O$ h3 ?( m+ |( R
                    '}';
    ' T, t, e: ^4 w5 i    }  G3 S  s( q% _1 X
    }3 B3 M2 _& ^  b' I. o* x$ `
    @1:使用了自定义的作用域@RefreshScope
    9 e  r% k/ P/ R+ L3 A' k6 D6 a1 Z* ]! i0 k0 Z2 o' S( {: V
    @2:通过@Value注入mail.username对一个的值7 L8 B2 U; _" L; @9 f
    ; b' [) ~, N( E+ O5 W9 @1 x
    重写了toString方法,一会测试时候可以看效果。$ f, T; |0 p6 h: e9 e
    & N. o, F2 i" c- t6 Q
    再来个普通的bean,内部会注入MailConfig* _- {- |# S5 s" L
    7 }8 u. l: ]* j$ L' j
    package com.javacode2018.lesson002.demo18.test4;
    5 g+ b7 b2 D3 k$ Y" J
    0 {6 `; o$ {1 F, z( s  u" Timport org.springframework.beans.factory.annotation.Autowired;
    - {7 A2 A" U! mimport org.springframework.stereotype.Component;
    8 ^+ U* V! y& m$ i, z( R! M, ?  F; C5 p6 @. L8 a8 c0 |
    @Component5 @' v) n7 v% P+ \; L
    public class MailService {
    9 \  d& J. g& k' ~8 M0 f, Q0 E    @Autowired
    9 D9 x) C+ B7 p$ H: w( _    private MailConfig mailConfig;0 c; P/ v# O( @8 n: F& \
    ; a5 M" |1 H$ ?/ @- A/ y" J- X
        @Override6 ^% l" ]6 w6 G5 p0 C7 b
        public String toString() {
    ( |9 j5 N' f" k. v+ z5 f        return "MailService{" +
    : s* W2 V6 N! i  I# v  a                "mailConfig=" + mailConfig +
    9 R* Z! z$ q% g) Y  _6 @8 v                '}';( }4 K) d  C* A# K
        }
    - v* A4 y- H) Z) u) z# F4 B5 k0 c}/ H) k7 c& B% M* H  p* J; G
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    7 E6 n( r8 t+ b" @3 v+ A: g
    7 E/ m( p- Z/ c' @8 l来个类,用来从db中获取邮件配置信息7 z! [1 u, R  Z8 z; h7 o! I

    0 W+ R8 B1 G0 w, W2 v- c7 bpackage com.javacode2018.lesson002.demo18.test4;4 J2 T+ K/ k! {7 S: t$ W' _1 Z
    0 Y/ t! w+ b7 i6 @* U# @
    import java.util.HashMap;8 I- g' F5 U' G' R- {1 A
    import java.util.Map;
    5 j* V, k$ E6 D6 w+ K  s7 simport java.util.UUID;
    ( L6 T% A( M1 Q4 F. n
    ' H+ [5 ^3 B" A. j& Mpublic class DbUtil {
    0 b6 G' p8 y, M9 i5 ?2 ?    /**
    / ^! }( X- n$ ]     * 模拟从db中获取邮件配置信息
    , Y* D2 ~: g2 a     *: O% p/ l: K/ A* g
         * @return  Z# \1 ^( I0 ^2 ~' ^4 k+ s5 H
         */0 n( y! j7 b$ D: a- @( Y! q
        public static Map<String, Object> getMailInfoFromDb() {
    : ]3 [0 r$ V, B7 N7 R/ k        Map<String, Object> result = new HashMap<>();, @2 T" f3 L8 s1 V& Y
            result.put("mail.username", UUID.randomUUID().toString());
    ' m, c/ e+ j1 f        return result;  B! H& W* a: k( ~+ ^
        }  G* M/ d  S: p; C. g
    }
    & R% ^1 m4 l$ s- Q来个spring配置类,扫描加载上面的组件  F1 y3 Y8 M5 t  `  u2 t7 b
    * U8 `' M+ z( Y3 R* B
    package com.javacode2018.lesson002.demo18.test4;
    4 N8 @" `, J3 G0 m& M$ I" u9 H. V# T
    import org.springframework.context.annotation.ComponentScan;
    ; X% P( p6 I& V4 z$ @2 Mimport org.springframework.context.annotation.Configuration;
    + G" \* J# L" ^& Q
      h: `4 s7 k- m* Z& X" M@Configuration
    8 N8 N# h5 B' I: M/ A5 q5 p9 W7 B@ComponentScan; o( l. w$ ~$ T6 z1 b
    public class MainConfig4 {% C6 T0 K3 m" e- k7 K7 `
    }
    # i7 M" F8 R& {* r5 J3 @* W5 y2 v0 o来个工具类
      |- e6 \1 x# v% K2 ?
    5 A3 b/ r& @6 T) |  X8 t4 h内部有2个方法,如下:0 A* ?* ?4 v' D# W4 r0 E
    8 x) r' {% S# Q3 B( R  _: H* R
    package com.javacode2018.lesson002.demo18.test4;; d4 v6 q2 Z7 c9 Y, ^' A
    ' ~7 A" ~$ B8 r! K) i- x% |  n6 ]
    import org.springframework.context.support.AbstractApplicationContext;, b3 z7 a  ~6 v6 Q
    import org.springframework.core.env.MapPropertySource;
    " b. r8 n* k' W4 ~
    ; N: w+ s# G2 R- f% h& dimport java.util.Map;
    " l7 o) \) ^/ C3 e0 q- [' M$ s9 d: h- J! h6 e' f% Y
    public class RefreshConfigUtil {
    * S' H  [4 o5 l6 Y% S. O) N2 e% a9 ?    /**# f: w9 U( z' L4 v& O
         * 模拟改变数据库中都配置信息
    $ ]! H! T- E1 X* I     */* f7 M9 P( a2 b$ [
        public static void updateDbConfig(AbstractApplicationContext context) {3 c" K9 ]! W6 U# ]- M2 \
            //更新context中的mailPropertySource配置信息
    - L, V8 w% H5 i, |        refreshMailPropertySource(context);* x" _7 i* Y% s2 l1 Q) w3 S
    2 s8 ]7 J& X: b2 T0 A
            //清空BeanRefreshScope中所有bean的缓存
    / |( k& i! r5 v' v, W        BeanRefreshScope.getInstance().clean();# G$ S4 G7 K' i- V
        }: {$ {+ L$ }/ F! o" C" B9 p2 o

    : [( b! m$ r& Q    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    ; F: \7 _9 K2 p9 _! z        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();0 E$ B& t; E7 c6 J
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    : V) o4 y  ~4 B+ H        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);4 m+ j' n0 A& @" g5 y4 f' z
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! ?  H0 `  ^# e) A. D/ w8 b, Q  U. s    }! j, c2 N) m: S8 A7 k. w: K. s

    3 ]: f# k/ \) D( W" D/ j! @" Y}
    " X' H: u: r2 V/ q, ]- O! ^8 fupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ' l& p# z; ]. L1 m
    6 [% v% b, c% O# V7 d8 J- H" O) z# }BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。: U! G) ?8 G/ B) G) g* U

    ' }5 @  S7 A  M$ _7 C, ]来个测试用例
    ! u- d. @$ b( c; P# s4 ]* I: j/ Q1 q; E' K7 p9 B( G$ M
    @Test, H# v- G) ?5 D4 i5 [
    public void test4() throws InterruptedException {+ m. B0 ~( D" r9 C. [
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    * p$ M2 @+ H/ E) K. k1 g    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    + ]: h1 E1 ?7 |- Z5 h" G% Z    context.register(MainConfig4.class);1 W( O- h8 |+ S
        //刷新mail的配置到Environment# m. b& f% v# N. F/ r" }7 R
        RefreshConfigUtil.refreshMailPropertySource(context);
    6 K# [% U$ w3 a' l8 _    context.refresh();8 t& b8 k& c" B3 n) [0 T/ W6 d
    $ U2 N' m: j+ Z/ Q
        MailService mailService = context.getBean(MailService.class);8 G; H, N' Y4 `- r* x
        System.out.println("配置未更新的情况下,输出3次");
    , |5 w) c$ ^4 X$ c4 i; {8 O) x    for (int i = 0; i < 3; i++) { //@12 V9 f. W; ?/ n  x& a
            System.out.println(mailService);
    6 ^$ J" ]; ~7 s" t        TimeUnit.MILLISECONDS.sleep(200);$ t+ ?  e1 e" @+ }
        }
    & [1 [4 ?+ M3 B
    / b: r. _" G8 I$ M1 Q    System.out.println("模拟3次更新配置效果");
    9 A& V( u* T, U0 j9 V( I$ o- G    for (int i = 0; i < 3; i++) { //@2
    4 Z- k! T, a; S+ H% t6 ?, m6 x        RefreshConfigUtil.updateDbConfig(context); //@36 a! t- w9 [& ]. R! [6 D: i+ S+ t
            System.out.println(mailService);  G9 q$ x$ f7 R' j8 x  `; @
            TimeUnit.MILLISECONDS.sleep(200);
    . B  N+ l" [% y! Z8 f    }8 u7 A5 z( P' g
    }
    3 |7 |, l/ ]3 W@1:循环3次,输出mailService的信息! w. ~  H' _2 M: n8 s: S
    % J: t7 A1 z! a
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    : d8 `; z" B! ~/ H- r0 E8 [  e5 `9 S; Q- q* M: H1 W( A
    见证奇迹的时刻,来看效果
    1 `' |, z$ Q0 Z# ?; {
    + }+ n0 b5 I; N* H2 Z配置未更新的情况下,输出3次* T5 Z: @. _: C
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}- _2 s4 ~! Z- {% w' g
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 i- j3 h" ?" t- }% ~9 S4 tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}7 d& ^# O2 P8 f$ o+ K: k( t8 Q- r
    模拟3次更新配置效果. g5 W# ]2 \; \, w7 q, w$ s
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}* a) r  `+ ^; l: Z) n7 M) U" ]9 A+ L
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}* ^; z0 L5 f1 r3 O% _" _9 O
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}- C" @8 n. p3 s' l
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    7 D8 O3 K/ N( {5 R6 A
    8 p# z, N" Z5 @4 ~# w3 m小结1 H1 T0 b, @- f
    + V; [! G9 `* i' Q+ D
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。2 ]! p$ S6 D' p8 Z8 e9 ?, P2 Z5 g
    8 h9 r, |% X) c5 O% x  d, J
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。) f* e5 c: A4 o( S
    ' I; G8 u, k' K2 S3 I" _9 [+ i
    总结
    1 w" J" e6 v  u7 b# g3 g* x- U2 G
    ' S7 Z+ \; c4 ~' ]. V9 K本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!9 P. M( d( b2 J% c" t7 Z1 h/ _
    3 p8 W& q6 s- k" L% |6 K5 X; ^$ O
    案例源码" i- D! U1 e: u8 Q9 b0 H# F3 o+ ]( O

    4 W+ [/ O( J+ D9 K. N+ mhttps://gitee.com/javacode2018/spring-series
    ) H9 K9 _/ |/ [' c! e' `路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
      X3 z" y$ p* C* F8 h/ k1 z% k, [5 d! @! M2 N8 ^
    Spring系列
    5 ?" o: y0 h9 R3 ~
    : v7 H3 S5 x1 y* ?Spring系列第1篇:为何要学spring?
    3 J$ `7 C/ y% E2 \5 Z# H
    ! i  j  @( \1 Q* `9 \7 iSpring系列第2篇:控制反转(IoC)与依赖注入(DI): B( Z1 _! L5 s3 X5 E% m* b

    7 j$ t' p8 k4 i# q1 _Spring系列第3篇:Spring容器基本使用及原理
    ; d5 ]% T$ }# D! ?
    # w( m/ w* y/ F! x' z. x5 a* HSpring系列第4篇:xml中bean定义详解(-)' m( o7 Q7 \# o

    ' n6 E- W$ v, N! ^. q8 iSpring系列第5篇:创建bean实例这些方式你们都知道?8 \$ f. N# A2 ]% v3 V* h* G
    . ]* V) G9 k" W4 I. u$ q
    Spring系列第6篇:玩转bean scope,避免跳坑里!. d! z" c4 L5 ^$ |1 Q3 G

    $ P( w5 \$ q8 Z. V8 g5 U6 c% }! eSpring系列第7篇:依赖注入之手动注入
    ' z' `# j  n4 A. M6 w- z3 j
    * x* N  P4 x! I, P8 e# i5 z3 R" oSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    * w& f- R& {1 X7 B: }9 v/ w8 f6 W' C
    Spring系列第9篇:depend-on到底是干什么的?) c2 `" i; |9 `$ Z: C

    8 t1 d1 |- U9 g* f# \& O9 K. vSpring系列第10篇:primary可以解决什么问题?0 x9 Q( g. d8 c/ U& _8 H' e
    & J$ j' m) U* `
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    " `. q. D% \& v! M0 u# Q9 F
    7 |' X3 h$ f: {. l: H6 d; qSpring系列第12篇:lazy-init:bean延迟初始化
    / Z8 A- S+ p4 U: A1 A2 v7 [9 P+ x
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)& D8 |- ^% z% ]1 x8 ^
    , Q& D* S. }) M4 X! m) G
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?2 N! @2 k6 }& k$ `/ ^* |7 f
    ! u' \* P$ |) V3 C
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
      W) _1 U* f6 b8 f0 q  e! |( {/ h: v& x$ u, _
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    9 Z3 t$ p2 w- S% J- f/ W
    ! `: C: F' c! Y7 F8 t7 Z: j  fSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)3 d9 D8 ^- _. Z% d& p

    ! T( j' c! q9 P: T) a& F& oSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    6 F8 B6 |7 r4 y& X5 p) G9 E" C7 S/ p5 j+ ]- n7 Z
    Spring系列第18篇:@import详解(bean批量注册)
    9 _8 A; m9 P6 L' K; W6 \
    ! B% F# v% O, r$ `6 m( L* uSpring系列第20篇:@Conditional通过条件来控制bean的注册
    . `& {5 ]6 U. T# ~/ @6 x  l+ w& ~
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)' l, v7 ^9 q# m

    2 H* A* q4 t9 ?% zSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    * t  u* Q' ~6 U7 d! m* u1 q  s0 t6 t, o: C8 G% {1 H
    Spring系列第23篇:Bean生命周期详解8 z* F' R, _' T# [: z

    * R! p& N' ?) j* h9 U3 p* TSpring系列第24篇:父子容器详解
    + Z( s% t% I, x" y* M+ r1 K& n6 f0 ~
    8 Z0 G" n1 e" q$ G4 j! E* q0 I更多好文章% y* m* c; r1 q+ J3 b9 a
    + r% D" j& i" b/ i( c* e6 Q' z8 L
    Java高并发系列(共34篇)1 E& r2 ~3 D, e- b, ?6 {& L
    & D# Y7 L- S, o( z
    MySql高手系列(共27篇)( _5 c  T3 }- M: R3 X4 _9 e, o1 \1 n
    5 c- b, m; s) w( Q5 {
    Maven高手系列(共10篇)1 ^# x6 m6 I5 t% `1 J2 c
    / _: k( ]+ U2 o' ~* _" i& e* d
    Mybatis系列(共12篇)
    + q, E5 ]1 X. ~3 i: e* d- i% m" M/ m% E/ w& \  A7 b% e
    聊聊db和缓存一致性常见的实现方式
    % L  r. E' x) H1 ~1 _2 r# |
    * {$ B3 w# F7 I0 M7 o6 G接口幂等性这么重要,它是什么?怎么实现?
    3 |0 k" y  M9 V! |, X4 a, C) \: T$ o
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    + p- [% v' b2 G6 D( K; O3 a————————————————4 l) v( K$ R) u( ^6 @" v
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    " `% ]' m2 \6 K8 {8 ]! G原文链接:https://blog.csdn.net/likun557/article/details/105648757
    , `1 m9 J5 g& n( p# y( H8 d9 r6 T; m" ^3 b6 B/ s0 |: `# T
    & l1 `4 e. U4 d
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2025-7-16 21:58 , Processed in 0.920603 second(s), 50 queries .

    回顶部