QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5251|回复: 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!8 K9 ^/ l# W" K0 q
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    " s* @2 E6 N* m0 F2 B" f: a: j( W* b
    , Y' t( s/ c* |) E# H7 T' `面试官:Spring中的@Value用过么,介绍一下
      x) R! ~# `- p
      }7 F2 F, C! }& C  y: l8 s$ Y; Z我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    1 s( x  `3 [) r, g0 c% J* I/ w- D6 F' v  G; Q+ R: b$ x' L: z) y
    面试官:那就是说@Value的数据来源于配置文件了?9 ]. X5 U4 k# i( X4 d
    " _! W4 G$ K- K
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置5 R2 k6 P9 k0 ~7 U6 r

    1 O: `6 v: G" G5 o. ~面试官:@Value数据来源还有其他方式么?
    * Q& ~  V8 i  p+ t4 Y4 l" k; A' g+ `2 M% ]1 h" {
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    4 j: ?( n  A+ C- x) E- p
    - d' u4 ]* [6 N" ~4 \5 \1 l& t面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    / S% t" |- v. n+ @
    + U! I! ]; ?0 i我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    . _, ]  Q" x0 t; z7 n
    1 \' b' E: @/ r/ K2 l) Q/ _7 g4 u面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?7 Y# T8 B  Q+ h% ~, i
    $ p! F7 ^) p/ t$ s! Q; G
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    % I& a. Y$ S5 e3 A/ g6 }8 r5 ]( a- I& a( P1 }8 @
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?. v, u. h- I. }2 f) R( Y
    4 w/ d: _6 C+ ]( g
    我:嗯。。。这个之前看过一点,不过没有看懂+ @$ Q/ B/ U9 d
    - c+ T$ [. u2 O$ y' A
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?, J8 O# L" G! f- I2 f& o
    ; N$ D0 c$ C3 E
    我:3万吧* `4 t0 X7 c  i. Y

    3 ^  \4 r; {: X3 a  l  c4 C面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?- q, n; V' d5 M0 G9 k6 z  `
    " Y7 k7 ?% W# c
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万0 C. W- A" S% A" y  o# F- x" w

    9 f$ r( }" O4 T# Q4 v面试官:那谢谢你,今天面试就到这里,出门右拐,不送!. r7 t  K* }& h* U' _
    5 Y/ `& ?1 u6 q4 `
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    8 @( u! j' S7 C. ?
    " @% K& {& @1 Q6 B- h0 \这次面试问题如下$ X+ D7 Z6 Z2 Y2 d5 J( L9 f

    % `& I4 I' Q; U& d7 f@Value的用法; X/ v7 f- c" c& A
    5 w; e# P4 F% x" c$ G
    @Value数据来源
    ) h$ K7 a* C& T  j7 x. O
    7 I$ \' o0 D0 c) ^7 U3 Q* I8 e/ \@Value动态刷新的问题
    ) R5 r' n6 M1 s: U; U
    - p: n9 Y$ Z5 h5 l5 g" A8 Y3 n' A" g下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。( y/ n2 a9 f! c0 T& z7 c0 w. t- @8 A
    4 R" R7 N  z9 u. @0 R& A
    @Value的用法
    ' P6 Q  }( J$ X/ b- }& y: F+ H2 E+ j9 U5 ?9 l
    系统中需要连接db,连接db有很多配置信息。, J" c- g6 K& o
    : `# p9 p$ P. s5 R  ]
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。5 i: R0 j# f1 X4 \) [2 A
    ; O8 W: _! X4 e
    还有其他的一些配置信息。6 v1 i) [* W$ N

    3 p2 S9 l( \# ]: V, t2 X) {$ U0 f我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    4 g/ V( G3 E, T9 s+ b
    ; j$ q+ }, R& l+ ?" B9 z, S那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。2 S" Z0 k) v+ O7 [
    8 O% D* i4 M( t
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。: |4 ^; I! ~2 w: ~% n
      G3 O! n2 `! B- D
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。- t  C/ w  V" G
    : `5 V; V/ g9 E/ X6 Q8 {
    @Value使用步骤3 f( H; l* z" E" N/ p; {  u( `! z

    2 X3 O, @# q! E1 ]4 w) N步骤一:使用@PropertySource注解引入配置文件
    . \7 c/ P% H( C! Z- c
    2 W7 C. |# R& a+ x0 d9 `9 I0 J( k将@PropertySource放在类上面,如下6 v4 h4 {' L& s: Q8 d
    . ^# n- c% h  B  O
    @PropertySource({"配置文件路径1","配置文件路径2"...}); g' o8 H7 C7 F1 X% d* Y
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。) |/ V$ y$ `+ ]  a8 c6 z
    " K/ E7 p( Q5 Q6 ~
    如:0 ?. X  N; _$ ^1 {0 g- n$ g% ]: y

    4 y" {7 T* n; @' |1 b, e@Component
    + F- ^8 M: t! w$ G% I@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})2 n6 Y+ ]/ K3 }! {( r3 o; [
    public class DbConfig {
    2 L' X  b+ a! ^. N* e8 d+ ]; M( w1 l}; }5 g* O# z% K) w: `3 W! P) F
    步骤二:使用@Value注解引用配置文件的值
    + t: d9 O; U1 D! u$ s. q
    + q0 [, k2 q' H/ @4 I9 t, Z) b' f通过@Value引用上面配置文件中的值:7 x. c8 L% |4 {- T1 X1 w$ X
    1 I5 D0 R% I  c# u( |+ m" U2 e
    语法% t% h$ x  u! L" Q$ F" i0 B% ~% `
      X& O8 Z9 y2 T1 X/ k; K; R2 M
    @Value("${配置文件中的key:默认值}")
    8 ^# R# R% h7 ]@Value("${配置文件中的key}")! I1 K9 U, F8 e. I0 ?2 a6 M5 g: D
    如:
    8 }, y4 ^% P: H4 p2 b1 D. Q* j+ ~0 @& ]" \0 B
    @Value("${password:123}")) a9 N/ @/ }3 \! k1 k
    上面如果password不存在,将123作为值: ]6 r- C9 S4 r& J- L4 C
    % L: `3 Z$ v' S, {5 d
    @Value("${password}")
    . ~! j) C; [, j上面如果password不存在,值为${password}8 M/ U+ y* e1 K! n: |% R! G
    8 x: Q# Y5 `( \  H
    假如配置文件如下; E+ t, K4 z+ y: o9 A
    / F4 J% p) F  C% ^2 E2 ~+ y& e
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    8 ]7 U$ G1 `. X# n5 z( W$ qjdbc.username=javacode
    , o& p1 O5 @8 P$ I! S# ^% X$ Zjdbc.password=javacode! ]5 ^; q# R9 F- e! _7 g
    使用方式如下:% U0 F5 D: t: ?0 i# Y8 T. a
    7 @' ?, |. u3 x) w5 K% {
    @Value("${jdbc.url}")6 d8 x6 j- U9 I$ R7 z
    private String url;7 i$ x0 U. N* ]  t' Q# q7 |
    ( @2 C9 ?1 c/ Z0 h5 ]
    @Value("${jdbc.username}")
    ; \' Q4 v4 U. q. a. Vprivate String username;, ^3 q4 \8 `) W7 T* c8 o, B

    0 M% u' u5 ?" P4 d# X3 T5 G@Value("${jdbc.password}")
    - \) A% L5 N) n  L1 Bprivate String password;1 g( z1 l$ D- o0 u1 ]. C% h
    下面来看案例
    0 ]0 j# S  x4 [, z( X' f& {2 v4 h2 q6 F! X  O
    案例
    7 t) {5 j; l6 X* \6 h* q  L* x1 e( p6 A, A% B1 t
    来个配置文件db.properties
    % w6 K5 a. l( F5 {/ @0 {" B( y( w5 W9 p4 v3 }9 z* u% y
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8: ?. P' {5 H/ `& Z4 G
    jdbc.username=javacode. W! ?; o0 K( N8 P; C9 }
    jdbc.password=javacode9 c% |9 \4 p& f4 I3 H
    来个配置类,使用@PropertySource引入上面的配置文件& j3 }% o" g! F# N  M! d8 C

    6 C8 H$ \; F+ W4 a: g) vpackage com.javacode2018.lesson002.demo18.test1;
    + j0 I8 V$ j/ A& b  d* q- u" X  J/ ~; s: ]
    import org.springframework.beans.factory.annotation.Configurable;
    : k& a$ `2 l0 I$ C; \$ }import org.springframework.context.annotation.ComponentScan;8 M- l; a# Q6 Q/ F+ u1 g/ r
    import org.springframework.context.annotation.PropertySource;
    2 V, }3 T6 t0 h$ }
    " Z3 C3 W3 q$ o# c9 m! Y@Configurable' F1 ?- ~3 i3 B9 p2 c
    @ComponentScan
    $ o5 {7 n4 C- u  s) O@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}): u' c: g' i$ r
    public class MainConfig1 {4 [# f" s) _: t; ?* x. r
    }
    0 E: V* g5 F# {0 A/ g4 W; x3 }来个类,使用@Value来使用配置文件中的信息: K+ D# Y" N, _$ y5 i; S4 p
    8 j* B) p- o# V6 r' z0 r
    package com.javacode2018.lesson002.demo18.test1;. ?2 G# R9 }) |& Y3 W

    8 Y! T- |6 H* Simport org.springframework.beans.factory.annotation.Value;
    . A/ b! a- f% p: N' {& L) Rimport org.springframework.stereotype.Component;
    1 E) X: `+ m  T, Z  @  k2 l/ r" J  R3 U6 N* J: y* R. {( B
    @Component. U' ]( N' Z; Z2 M4 Z6 U
    public class DbConfig {6 i: a; A# e9 [) H) T3 G( F3 o
    4 |" M- B! A# ]. U% p
        @Value("${jdbc.url}")
    9 P* E  \, e' f) c  U    private String url;
    % [7 S1 l. t  a5 G3 C9 `' I# V4 Y( V1 E: q! y/ ]& r4 k  ~) [+ W
        @Value("${jdbc.username}")2 o3 u1 H2 z* e. x
        private String username;
    1 \. L. ^% ]5 z' \' }2 }
    * `  U7 Z) T: F0 H9 p    @Value("${jdbc.password}")
    8 H5 Q, }+ E4 ]$ }$ z. [+ Q$ b    private String password;
    % Z! h7 q, L* ?2 s/ s2 G3 V4 N! u& P! u) w( W
        public String getUrl() {
    # ^3 k9 X* p% l9 n( E        return url;1 @+ ]  q, x0 {. b: o0 \8 v( u2 D
        }# w" Q& K# U0 s$ ?+ h% C
    , w0 k, A$ A% D! F
        public void setUrl(String url) {  C! W3 e  p5 t' F' ?! T5 k
            this.url = url;' W/ k" j6 K  r
        }
    : S4 q6 c9 l& t* A# D2 W
    ' P9 Z+ m" ]  E* {1 ~    public String getUsername() {7 j# c- o9 Z) I* v8 l; I" S1 `
            return username;
    ' ^; X5 r0 \% i2 Q" }8 H8 O    }$ C& O4 C6 Z  e) k( o
      h( E6 X( C8 m# I, @% d
        public void setUsername(String username) {
    , e, R9 @. ?9 `$ g# E% s        this.username = username;
    7 P9 T3 a; F) w. o; `5 [    }
    0 v  s* p" F4 p" C* Y7 Y* b+ ^7 q- [& @8 o6 U, k( [! u
        public String getPassword() {, ^; Q2 s8 d' e9 G1 {, G
            return password;# P9 R2 @" u1 A" h' h- x
        }
    5 W+ B7 \& B9 E, u$ P6 x1 Y; X) b( o* p3 [, g
        public void setPassword(String password) {
    6 c+ D/ v1 c  z# B& b1 A        this.password = password;
    + b8 x, e9 m' r5 }: ]! \- j5 ]    }
    - D$ i9 q. g0 H& C4 P, |; K8 K% g2 ^6 J$ u
        @Override& J# W; d2 j7 m0 n0 g
        public String toString() {( {; t  M* e. _/ P
            return "DbConfig{" +
    % h$ Q+ E" r/ R) p+ D% D                "url='" + url + '\'' +$ A: v2 ^1 G6 b* h! a/ y* Y
                    ", username='" + username + '\'' +- o. R5 e8 o0 ]
                    ", password='" + password + '\'' +
    ; v7 h* E! @* E                '}';
    + d& \/ K9 X+ k/ k& F$ ?    }" S; r$ t2 d6 \4 r
    }1 |1 h/ y( }6 M: D
    上面重点在于注解@Value注解,注意@Value注解中的
    * @) a) c8 f. Z# B2 h( n/ c; {
      H, D& ^7 c/ }* E: S, S* x2 Y来个测试用例
    5 V7 l) f5 y4 s. e. E$ Y8 o: J2 @2 y8 l1 T) N+ R
    package com.javacode2018.lesson002.demo18;, {5 B: q; `4 Z2 y
    ( D% M5 c0 J! }1 k+ Q( A( b8 h
    import com.javacode2018.lesson002.demo18.test1.DbConfig;$ q2 w3 ~, k5 G2 o# m# h; h$ l
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;5 L5 m8 R/ C! [: s8 F6 [) V/ v2 k" L; v
    import org.junit.Test;
    . E8 C  h  C& l3 p  B2 Y+ y, Aimport org.springframework.context.annotation.AnnotationConfigApplicationContext;5 c* ^& {" K1 o

    ! `/ N9 W+ U; ~2 a8 _$ f. Zpublic class ValueTest {
    5 E& w9 W' s: {/ }' n- \" K
    ! S+ k% ?* C) ~" g' l- {) ~8 G    @Test
    " g: `# z: Y- t+ ]. m9 I    public void test1() {3 t# v# M% t7 G0 t: _/ n- u
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();2 _8 N) m( B  X6 L
            context.register(MainConfig1.class);
    7 R, F0 q; }8 C1 I7 }1 W$ r) S1 e        context.refresh();
    1 R( r; ~- j% {! |: N; E) A% o
    . k" p" i; ?, t' ~: Y. G1 F" R" j        DbConfig dbConfig = context.getBean(DbConfig.class);2 o' k& P% J. K3 m9 s
            System.out.println(dbConfig);
    , a5 H5 n1 u4 ]0 T3 O, H/ p. }    }
    1 K* j* k& w, ]" K% z}
    2 g( @2 g/ L, ]. z  N' z# M: ^5 i! p运行输出3 p! p! c1 n) `  T6 L" w/ X0 a5 [

    + s5 T4 b/ y( }1 r6 }$ L5 UDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}# P# L! l1 E/ E' h. r7 X: {- H
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。# s2 C9 i" Q  d& r

    8 v+ P; A& W# j+ F# D@Value数据来源  m& d" J7 N( r$ o' I4 i1 g6 Z0 J
    + d9 \1 f) o$ d2 v
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    0 ~7 T& c; U4 _( S, O& r
    ( _! u* z. F& R, @+ c1 Q* Y我们需要先了解一下@Value中数据来源于spring的什么地方。
    1 L, |* u/ r' F. h6 ?4 F2 W, C- d: T& H1 n" H
    spring中有个类
    8 S/ t, y- h5 n  ~: e& B9 H
    $ {5 H5 K6 X3 H) S  f8 ?" Xorg.springframework.core.env.PropertySource
    , @7 S) R" m. J) t可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    % o; J/ O; v9 D3 I( H) |+ o% z, T# q
    内部有个方法:- [) S" J9 k: _6 a
    ! D# B4 C8 e, b& u
    public abstract Object getProperty(String name);
    0 m2 Y( R7 C: x4 L0 R通过name获取对应的配置信息。* T4 t  `5 I% o/ O

      f+ B* `3 E7 a* s6 L6 w系统有个比较重要的接口, o! T" P/ I* B8 O0 V- Y% V$ a
    * v  Y+ f! p- E6 j4 F; E  w
    org.springframework.core.env.Environment
    - F# |3 J/ z0 E8 L1 g用来表示环境配置信息,这个接口有几个方法比较重要
      _! |4 W+ d8 z7 j7 |4 G1 t9 Z3 m& C3 P1 }
    String resolvePlaceholders(String text);0 [. w7 |  I: n' s8 V
    MutablePropertySources getPropertySources();# v+ J" V9 E6 q% e) Y
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。- U8 {2 |/ i6 H* C% a8 D
    1 E2 M; a/ a$ n5 F3 Y
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    8 o6 z( H4 v/ n  P( f* [( T2 ?" o5 a4 R7 R
    public class MutablePropertySources implements PropertySources {' E3 B% E1 D" t$ d
    ) F1 [2 j' [3 S% Z0 M
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    4 ]# I; Z5 z8 g* z' f  @" i
    1 J' Y/ s9 ?" E( x}+ M, G3 j' B2 R  n
    内部包含一个propertySourceList列表。# E$ Q3 j0 A' i. _
    # L; J! J3 r  \2 L# z  @; ]
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。& \: O" @+ `' }$ o, J! T4 g$ u

    5 l+ C: v1 J" d" N& g大家可以捋一下,最终解析@Value的过程:& p' T" g+ g2 a! @. x
    % s' f3 {) c; [
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析# S% `8 h1 Y3 p& ^2 r3 m
    2. Environment内部会访问MutablePropertySources来解析' f# H' x8 ]# S9 ?% M. U
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值) ~* m* Q$ [2 n' o3 P+ T
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
      ~' F$ n& r; T" a% f, I4 y0 r6 e5 l" {8 }
    下面我们就按照这个思路来一个。
    3 l) f, u% N3 p8 c9 G/ Q
    ( v0 [6 F3 @0 P1 t* @# R  b( J来个邮件配置信息类,内部使用@Value注入邮件配置信息2 I0 S- ?+ K: j2 y' L8 q; a
      H; H9 h0 F; Q% p- z
    package com.javacode2018.lesson002.demo18.test2;
    3 W& ?- o/ s7 U% Q! k( g5 v
    . g9 d/ `5 @7 T* \8 B! Vimport org.springframework.beans.factory.annotation.Value;- y, }- l; h# A2 s* J  ]
    import org.springframework.stereotype.Component;  ?& Q$ {: i* N% i) a
    ' r6 w9 |4 x' b6 T# T' Z6 q' U# o* L' ~4 m
    /**+ u2 L, L+ c; o) M: Y9 A
    * 邮件配置信息
    * w! [3 _4 Y9 h; x. Z1 }* r: x5 m */
    4 D; m+ p' t" z8 _3 s$ \@Component
    $ d2 K* p( J/ q, `) Npublic class MailConfig {
    ' o7 w0 e$ Q# V5 x7 b8 x5 Y
    5 ^$ h, `. L+ T% z2 \7 y% [    @Value("${mail.host}")
    ! b! `, b$ n+ x. F' {* v" F5 [' A    private String host;) _; Z; M" @: N
    5 e2 `2 D, u/ M- w" `; \( P
        @Value("${mail.username}")
    " t3 P# L: z: d! y! H! Z( y6 U    private String username;
    & K' _' i. ?" a& q# p
    - Z+ m) h7 J/ Q* P: G  d. V    @Value("${mail.password}")
    % G  D- u7 M( ?- c0 s    private String password;3 L. v8 j9 \5 h8 a, c3 t
    3 m9 f; _6 [1 d% t# g
        public String getHost() {7 B0 K/ [. O) U1 Z' ^7 s+ {
            return host;2 c- A1 Q; r2 [, Q
        }
    ; N: b, X( m9 U& E3 R. W. c! W+ p  V! B& F$ F; y
        public void setHost(String host) {7 L" p5 i# ]: z/ P8 O5 Y! E' t
            this.host = host;& D/ Z7 x; b2 F, L
        }) w7 v* l1 E5 S& I
    ( _. g) v7 ?' H) c& ~8 l
        public String getUsername() {
    , t5 [3 @0 ]5 m8 ^( k: b' K) A        return username;
      R3 }* [% J7 @8 F9 A4 l    }
    7 k  e* K: L, b/ [1 I3 A% I  q6 Y& V) p& n  j3 ?
        public void setUsername(String username) {9 c0 }& R0 |5 x4 A/ Q( T
            this.username = username;8 W9 ~: ~" m. V7 i
        }& C- ?! U1 x, s  w$ q: P- |1 w
    ' B# l# w2 S5 M  b& J- l
        public String getPassword() {
    6 V+ [  l1 V, P% U' v( ~" g0 b        return password;
    7 ~; L5 s( o& T7 w. w; ^) D( W3 h    }& H: S" C! K, U. P+ S

    . G: y  F0 ]. S9 W+ W, _    public void setPassword(String password) {  o. T8 C; }# ?2 I
            this.password = password;: `3 \9 [( [0 C3 K
        }
    ! a* k  }0 f4 d; b  W; f# h" [& ]
    6 X/ [1 N) s; @7 v$ Z    @Override4 V! k" y& ?7 b4 C) ~& v; x! g
        public String toString() {
    " A8 \( U, ^0 d6 z; n( t        return "MailConfig{" +5 q! A- H- h: g* U2 t6 h2 U& u- K  h
                    "host='" + host + '\'' +9 }, V3 G9 b+ R
                    ", username='" + username + '\'' +
    8 d7 d8 B; v$ O# ]                ", password='" + password + '\'' +
    3 a5 Z$ o  \! d7 Y) O. B4 J                '}';
    ; T$ ~8 k% @( e1 M    }4 P# W# N) l" b0 |
    }/ K) V+ a+ ?0 T" e+ b0 z$ |4 s
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中  F7 c! T5 h. C3 Z6 r
    " Y4 w9 D5 r9 X. n( x6 L
    package com.javacode2018.lesson002.demo18.test2;+ O( L  B3 @7 U: d+ p( ?# Q8 G$ f
    2 `9 f/ R! N7 l1 K, q1 C6 C
    import java.util.HashMap;5 d6 g& J6 t) ]& p
    import java.util.Map;, }4 ~7 o! U7 H, f

    , w' a2 B, }/ {/ Gpublic class DbUtil {0 x" P, g. I' n# o$ b
        /**8 z, d' ]$ E3 t7 _* Q
         * 模拟从db中获取邮件配置信息2 Q4 A$ h1 F( {. }9 c/ U1 h/ I
         *
    ( q) X' s6 `- v% j8 U5 z     * @return) ~3 ]* Z- S& g: Z% m2 w9 p
         */6 @- |- q/ @/ u* w# o/ R6 F% E  \
        public static Map<String, Object> getMailInfoFromDb() {9 |. b4 ?: i/ [% L- X
            Map<String, Object> result = new HashMap<>();
    / G$ M/ D4 ]& P! U, h0 p        result.put("mail.host", "smtp.qq.com");
    ) v# `& a9 u6 b% o        result.put("mail.username", "路人");
    + I3 a" I7 k8 o, j        result.put("mail.password", "123");
    " t( n, @8 x- A/ d        return result;! t$ J, S3 W, Z- b
        }, T. d$ _4 [0 p
    }7 ?' D3 e. q) c/ O4 B
    来个spring配置类
    ; b3 f7 Z& ^( @( p! `. \& i2 j& F& |9 q$ |
    package com.javacode2018.lesson002.demo18.test2;: n- z  {0 Y; j6 g) \+ T

    ( X5 }9 v5 o% J) y1 m) [  nimport org.springframework.context.annotation.ComponentScan;
    # [7 O: N1 j! Kimport org.springframework.context.annotation.Configuration;* x- Q2 L- h. T' Y, ^, S( j

    2 O& a& X- d  O@Configuration
    9 L+ j/ n" c" @! Z@ComponentScan: P3 Z: l% C- f4 X+ ?$ V1 q% X
    public class MainConfig2 {
    : i; q$ A/ X: e  v5 C6 B7 q}
      U# u1 u* U8 ^1 [下面是重点代码
    ( P- c5 x; M$ Z0 `8 i$ _
    0 S! Z, a' j. [  y7 W2 [8 E@Test! Q4 z6 h! ?9 C4 l( S+ o9 M! H
    public void test2() {
    4 s$ j. L  S3 p: \" A7 v    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();0 h( A1 H! s3 ?  r6 K
    & r; d  Q+ O1 v! S3 s& [# k# s
        /*下面这段是关键 start*/
    ) K) l0 u, p8 m7 S    //模拟从db中获取配置信息. Q. d+ r: w/ C2 z
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();: J$ ^0 e& q; I2 S
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    1 d. X) A& V: W" F    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);% a7 s- b! v$ O1 W1 b+ U
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高* U" ^9 b* z  X% K* F5 s
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);; t: i2 o' y. K+ H
        /*上面这段是关键 end*/' U) a, j! R: t% R
    % j$ h5 d# b7 a) x) `
        context.register(MainConfig2.class);
    : F- ^7 N6 r) N    context.refresh();
    * S3 z4 m/ \' J    MailConfig mailConfig = context.getBean(MailConfig.class);& o8 O6 }& w2 I- O( F2 {' f5 U( m
        System.out.println(mailConfig);0 {* u# R9 F; T/ S9 c
    }
    ' ?5 t9 w7 k# S; [  W& S注释比较详细,就不详细解释了。- w1 z% c! R0 f

    2 l2 u2 J7 ]$ E6 F% O0 A直接运行,看效果) q. y7 S3 X9 k3 K% {0 ?8 \. o

    9 a9 E) X- ]4 W# Y2 a# D% Z. JMailConfig{host='smtp.qq.com', username='路人', password='123'}
    # X0 n4 i: T  v8 z5 i* x/ C有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。3 J0 \7 y' e7 R6 K$ V2 U
    1 p: A5 B% ~4 M- B: R( q
    上面重点是下面这段代码,大家需要理解
    3 J' c1 e& o9 l2 I7 j5 t$ Z) C2 m) |/ N$ O
    /*下面这段是关键 start*/
    ' T. ^: m3 `9 P2 y//模拟从db中获取配置信息
    7 P" a6 K% A- _" X2 \% M$ BMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ( \( \1 }) h' D6 \//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)( c) m) o& |% `3 w4 O2 p
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    0 v; h& N" m- b( X8 M6 M+ l. G1 V9 y//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高- K/ {4 A' v! A. o9 ?  ?0 k# n
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    3 Z/ q5 ^& k( y+ |% s2 O6 i/*上面这段是关键 end*/+ Y& Y9 Y7 q* R7 v$ t4 X
    咱们继续看下一个问题
    4 a# _# O2 `# O! O
    - \1 p& `9 A: c; Y; e3 v1 D如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    ) k8 w; W8 X+ G5 E" y3 S9 n% Z
    % ~2 W4 u. D7 S  R; n@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ( b, r& d2 {/ n- V) L" Q- D7 C4 k1 a# h- B- B* w# Q9 e4 U, s/ C
    实现@Value动态刷新& r) Y# ~0 P& c0 B

    " ?6 l0 M3 N4 Y4 n$ E& M  `先了解一个知识点
    ! `1 B: H+ P: e7 ^0 b+ O4 d% T: o4 |. J! l+ u7 ?  n( z. w1 `: Q
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。0 ]. T; h! j% U/ d% s. G1 I9 m

    + W) n6 B4 U) ~7 {; W0 }这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    ' `+ T* S: v; `1 f! N- [3 w5 p- B) E' \- E9 t
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:/ h: P5 f8 o9 M- d' K
    0 w: v, r- y) s( F+ E6 A+ O
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    3 A% }0 A$ N( m7 P5 j! I这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中3 Z7 l# Q5 f" |! y7 j  u- {; \
    4 f! n/ M/ U& M) M7 P* ?; ?
    public enum ScopedProxyMode {; W: a- l' }' E: K. Q7 f2 g
        DEFAULT,1 {' f/ ~6 v9 v4 t
        NO,
    * L# x# r' B* e; ?    INTERFACES,
    ; ^% J* R3 E" X2 s    TARGET_CLASS;$ H; Q- D! t( ~% B
    }
    9 l; f% X) X: S4 y0 j7 y6 \前面3个,不讲了,直接讲最后一个值是干什么的。. U2 `: [. R; t6 n
    7 ]/ |/ M7 I$ h, s8 [" l
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。0 C" p- H* K2 Z8 N
    ( e1 u( s1 m8 ^) B
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    ; K" i7 E" M$ S' G; V& \
    4 D7 G' d9 ~( `自定义一个bean作用域的注解
    9 _  v4 t+ g7 Q* P4 z2 a0 o( O
    package com.javacode2018.lesson002.demo18.test3;0 }, h" J5 V* y9 ?- O0 b

    % Z$ x/ t/ t4 X8 {import org.springframework.context.annotation.Scope;7 }& t* S- f+ u6 }
    import org.springframework.context.annotation.ScopedProxyMode;4 Z- d9 N- W6 U+ S1 s/ m

    * y" a7 W) N& V# limport java.lang.annotation.*;/ Y6 j8 y* p/ H; P  K
    . C- A4 n( D& ^0 {$ D! f( s
    @Target({ElementType.TYPE, ElementType.METHOD})
    $ f* j' w2 a# w! `  ?% m) d@Retention(RetentionPolicy.RUNTIME)
    0 d: j: H/ A5 k) @1 f4 M% D@Documented: o3 [/ X; n' f0 z% b! E6 j1 |
    @Scope(BeanMyScope.SCOPE_MY) //@1
    ! ?4 l( T. i- }6 k' }4 cpublic @interface MyScope {
    7 R8 O7 t2 g+ Z, k    /**$ ]* y% o7 c3 l9 C7 ]& t& `
         * @see Scope#proxyMode()& Y4 N! V) W; h$ Z, R3 \+ C8 C
         */
    7 L! j' x! \" U& c7 m5 Z6 e# q# u    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@24 q' U! \' d8 y' Y' a
    }
    6 a) X& o: ?; w7 \# L7 N@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。, m2 b- ?1 ?! C( i/ m6 [' ~; O
    ; Q6 O3 C; ?3 B5 m5 \" T
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS" D1 q! \7 |4 I( E
    6 j; }* V  ~- h: C" w
    @MyScope注解对应的Scope实现如下; U' t1 r1 D3 h

    ) c/ B& I" A3 Q& [# G" z' P5 Npackage com.javacode2018.lesson002.demo18.test3;; U# i8 Q6 k- ^) r% C) Y! R& X) @! Y: ^" C

    7 |5 R' Z9 ^0 z0 {/ _, r! g" y9 ^import org.springframework.beans.factory.ObjectFactory;
    $ O9 z+ ]% ]! b8 n" Bimport org.springframework.beans.factory.config.Scope;
    2 u4 ~* |% j  z* O  Cimport org.springframework.lang.Nullable;
    . Z5 T7 J5 M* g! _+ ?) i2 V
    , \: ^3 |" T; U+ a/**
    ) G+ T8 q+ m2 n" ^7 R# p * @see MyScope 作用域的实现
    * L" e7 I, _/ N+ g& Y */& z6 q/ M6 ?( d* R. C
    public class BeanMyScope implements Scope {5 L7 O2 l" Z- e1 e' s8 g% |- e! G
    & W1 V* j5 d- n
        public static final String SCOPE_MY = "my"; //@1
    / h  K0 L* U! P; U
    5 d2 Q* w* g( ?) W# h    @Override3 ]% O' Q9 p4 `8 J
        public Object get(String name, ObjectFactory<?> objectFactory) { # ~! @5 s; ]' b6 p. O! h6 I% l
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    . ]4 U% q' m: O% |7 `* @8 e0 R        return objectFactory.getObject(); //@3) `8 T* V; D! F5 ~+ p
        }' ]4 t7 m; l) T) A$ S+ ?
    7 H' ~  b( R8 k6 T( }: O
        @Nullable; d% L- j# i2 o) x! O5 L. ^
        @Override: e0 }" Y% }' I+ ?6 `+ [0 k
        public Object remove(String name) {( K- U# A+ t- Q! L! C) J
            return null;
    $ k, m3 O& S, |( B    }: }. Z8 m/ _6 `

    3 C+ |7 O  H) ?/ W" a* W4 K    @Override6 Q9 b1 l+ x, b' F
        public void registerDestructionCallback(String name, Runnable callback) {& d, S* z' S7 V( k8 m5 T
    / O9 x# j) {* Z) i# V/ j
        }
    + z- x; v' j  B' W+ d
    / L, G& r: ]9 Y    @Nullable
    # L8 a1 u! u5 U$ @  y# Q    @Override8 g4 M7 V& Y; `! K. W3 D/ B1 L
        public Object resolveContextualObject(String key) {
    3 i$ K2 d6 c- h        return null;6 ^5 K: u& G- v3 j
        }5 [( j: |! N" b0 {2 C* q; l7 t
    3 J5 S% l9 J3 d. ]$ g5 J9 g
        @Nullable4 `% W. Z( z! b! d: x  P2 X
        @Override
    : i$ L6 b1 \- L- j3 W* Q    public String getConversationId() {
    ! T8 ?0 F5 W/ ?3 n" x( m4 P        return null;! }& U) E) g' w  Z6 M  A: c
        }
    ' o8 a  N5 S" d/ |8 ^}( Y5 d, L. `* M! r( M
    @1:定义了一个常量,作为作用域的值  u7 B" n/ r9 V& t! D  O+ g8 b. D) Y2 K
    0 \+ O2 O& W7 Z
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    . O' _) f$ y" M+ R% N0 I
    1 F/ G1 O. R  F- I@3:通过objectFactory.getObject()获取bean实例返回。, v9 j7 c$ ^1 K# }/ w# z. p. j
      O7 J9 S3 A$ S* n2 X0 |
    下面来创建个类,作用域为上面自定义的作用域
    1 C# [- L2 r- b7 E5 K
    # X1 s' ^7 W; M% y9 \$ }1 ]+ Epackage com.javacode2018.lesson002.demo18.test3;
      c( Y! Q' |3 b/ x9 B$ x5 x3 b8 G7 o7 C" E/ J
    import org.springframework.stereotype.Component;
    0 t1 Y* J( Y9 }5 }/ `( f0 }  n2 E7 x) X2 R% q
    import java.util.UUID;
    , ^4 V4 d- k& _: ~! c5 Q, V/ U' _- y3 D2 d1 G$ P( A
    @Component
    1 l1 ^! Y2 W+ [$ P! K5 O2 m% h@MyScope //@1
    * R% f, d8 O; l$ s& T3 {0 @8 epublic class User {
    3 E9 }* F5 y4 U: J3 g* ?* y, _+ n2 L; Z+ }' m5 [5 Z5 R
        private String username;
    9 z( x5 ]) H# ?  C# M: V: [* [2 J" C7 d
        public User() {
    0 r; {, O6 m) X9 O$ z; k1 [7 A. T        System.out.println("---------创建User对象" + this); //@2* h+ i: X! ?7 k1 K9 R2 ]1 g0 u
            this.username = UUID.randomUUID().toString(); //@3
    9 f; F) r, M: j: v  ^6 X. A) _    }% q. l8 x+ ]3 R3 R
    $ G0 y5 H  B9 o8 r( Z' d1 |1 w4 W
        public String getUsername() {: x3 A1 ?% x7 b0 }" n. }
            return username;
    ' I  n" y4 \) Z2 ~; L    }
    6 Q  M0 q8 j0 s: L1 n+ E
    * a, T4 _+ i. X. E$ y+ l    public void setUsername(String username) {
    1 X' g( ]; z8 N, C7 l2 ?! k" n        this.username = username;
    $ o3 \& [( K: W9 `# s1 k    }1 [$ b  L7 _/ N3 ^

    7 f5 _7 ^" J! b; l, b3 ]}( s% g4 L  n/ k& l* O* d2 [! h
    @1:使用了自定义的作用域@MyScope- W" p1 {/ [2 e4 f& e  z3 \
    - C( t3 p+ h1 l. b) J. b
    @2:构造函数中输出一行日志
    1 L2 G: K. c+ J. L, `5 j+ ~9 [8 Y1 J8 V0 M1 M! R
    @3:给username赋值,通过uuid随机生成了一个* m4 I& F) B9 C7 j0 [

    ' r" l+ X% y$ M# L& m来个spring配置类,加载上面@Compontent标注的组件8 `( y* @* D; p3 z. H. {

    & Y1 t0 r) m7 R+ a/ [package com.javacode2018.lesson002.demo18.test3;
    ! D/ G9 P1 C3 \4 ^6 C' L( m
    ) @: y- b4 }! }$ Aimport org.springframework.context.annotation.ComponentScan;
    1 E" v# [* \! U8 Limport org.springframework.context.annotation.Configuration;
    & T' ~+ I6 t8 ]6 C* ?- t1 s0 |# ?' y# U0 y1 x0 U
    @ComponentScan
    . r. b9 I7 r( Y' j- O: z@Configuration
      I  o0 m; T, Z" A: E2 zpublic class MainConfig3 {
      z6 p& F7 a( o. i- }) x+ t) ]}, E, k4 X1 ~! i/ x2 I! \% `5 R
    下面重点来了,测试用例
    1 m7 l5 X3 ^7 M6 C! Y9 M/ c
    ' Q2 }/ |$ Z& o, x) n# S@Test
    + j$ B) [- f, u* g. W+ a  O# q6 Spublic void test3() throws InterruptedException {
    3 e* d7 x$ \) Y9 {6 h" g    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    6 q1 v  o. z' H. L; q    //将自定义作用域注册到spring容器中/ i; V+ R" K' t" l/ Y. [
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1  C7 s% y4 k7 I. {* B9 j
        context.register(MainConfig3.class);/ X: [$ Y- f$ H7 l1 \/ F% z
        context.refresh();, u  O2 Y. }& w( c1 n* a7 A( _- b
      Q3 @8 t5 k1 |1 W1 \
        System.out.println("从容器中获取User对象");
    9 H; d4 D& n. k* I) k' F+ U    User user = context.getBean(User.class); //@21 g" i0 J; j! c6 ?2 g2 l
        System.out.println("user对象的class为:" + user.getClass()); //@3
    & y, g$ b3 C# w% p7 _- h& G/ Y0 x. }1 v' i" d. J2 h) Y
        System.out.println("多次调用user的getUsername感受一下效果\n");
    3 D/ S6 t9 o% M5 ?! B+ f: W+ I0 A    for (int i = 1; i <= 3; i++) {! o/ J6 ^% Q; H; e( ?. ^9 N0 l; W
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));) `" Y3 D# c- K; `+ ?4 m
            System.out.println(user.getUsername());
    , m5 c/ o0 `4 _" a& @        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));$ {" N; C8 P. ~8 G1 j
        }- S( W" H( {% t
    }% @, _" ]8 l; A& {2 u2 ~
    @1:将自定义作用域注册到spring容器中4 a. {6 o+ m5 m
    / K  {1 r( {: r: }1 ?  t, |
    @2:从容器中获取User对应的bean% `3 k2 i9 L7 v. r+ ^$ }+ {

    ! B& [1 N$ h( C$ J3 {@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的) C8 M7 b/ ?8 u

    6 {: |) e1 o2 x4 N6 b1 N代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。* O; ~- y0 D* G. g/ o5 v* D. B% W

    , e6 `' i: W" Q* T见证奇迹的时候到了,运行输出/ h4 t9 f5 ?1 R" Y$ v
    8 ~  j9 g6 l( m* [
    从容器中获取User对象
    " d' B/ v/ X. K6 T3 fuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127) `- d8 k+ b9 k: f7 M" |2 S3 n, ^1 e
    多次调用user的getUsername感受一下效果" e* I" Q' C$ k9 l

    % G, _# D! V- p/ `" ]********" ?7 U+ E* W0 p9 [% Y* D* D
    第1次开始调用getUsername
    ! b( T% X* \8 T; P) K0 b" y8 kBeanMyScope >>>>>>>>> get:scopedTarget.user8 B% ?  l; G: ~) b
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4( \1 X/ g. o! T; \1 u- l! X
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    + K% ^" O4 m% K- v7 Q0 F第1次调用getUsername结束  o- m) }/ h8 Z8 }4 x
    ********1 p( r( P+ f: b/ Y9 E7 Z4 t6 Q
    . q% s7 g% Y  g& q6 P/ o
    ********. P3 w4 `! p6 |( X
    第2次开始调用getUsername: B7 Z9 @3 s- }
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    7 E! G( i* A# c9 v7 |3 Y---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b. `  e+ R9 B4 B' l0 p; W& I
    01d67154-95f6-44bb-93ab-05a34abdf51f
    8 V( c$ ?4 |/ q5 u$ H, @7 E2 a8 ?; o. n) n第2次调用getUsername结束
      T0 c" j: n2 v& A9 \********6 w  n, v6 c0 }2 Y8 Y" v/ f+ }( e8 G' s

      j& C$ \1 ?! O+ \7 z********3 ?7 q6 F6 N+ c5 Z  k
    第3次开始调用getUsername
    $ p: C1 U, u7 m! Y5 n' v1 B- F* ]BeanMyScope >>>>>>>>> get:scopedTarget.user
    ) z3 U" p0 l- V1 ]  d& z---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    3 b% u, }1 F# G3 R76d0e86f-8331-4303-aac7-4acce0b258b8: J  g. }' t/ P
    第3次调用getUsername结束
    6 W# m- F' t+ H: g6 z+ r********% ]+ `5 |0 j2 A# ~6 V
    从输出的前2行可以看出:9 S) Z7 k# R( s! U9 T/ o

    - }' m% n' R( H+ u! w调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象& a0 e8 u# C) P4 o! h- A4 v
    : X: r) ?1 v+ u& u
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。6 Z! B( E( t% Z% c: t
    6 l( e0 v3 U+ D3 Z# T+ b( p
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    / h' a) o! O5 J3 p$ p: M: I  S
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    8 X. X1 r. ]( W+ n+ ^8 b0 {* ?4 F9 x$ o
    动态刷新@Value具体实现; _( n1 H4 B3 v9 V. k+ |- T

    5 z' j7 b* T, \) r那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。; a7 m1 Q2 D& D+ V/ U9 r
    & S  O3 p' \$ t1 Q/ c* ~+ h/ u
    先来自定义一个Scope:RefreshScope! Q3 F7 G. {- c  q0 U

    & X2 Z/ ^, G# \' spackage com.javacode2018.lesson002.demo18.test4;
    % W9 N4 z' ]' c, ~
    . k! |4 r) I: ~: t. N4 V, v. s; Aimport org.springframework.context.annotation.Scope;) h3 B7 i% ?$ }& H. P6 e
    import org.springframework.context.annotation.ScopedProxyMode;
    ( n/ {. `) b" F8 b, t8 K+ t/ X& ?, b. A0 g/ R" t6 u
    import java.lang.annotation.*;  O3 [& m. R: H8 `4 b0 i& c  e
    9 Y) U) {; x. a( Q
    @Target({ElementType.TYPE, ElementType.METHOD})
    8 }! p. J- F  U7 n) q! L/ c@Retention(RetentionPolicy.RUNTIME)
    : {' T8 D* G) k; I* h, n@Scope(BeanRefreshScope.SCOPE_REFRESH)2 |  b# c$ r1 I
    @Documented: A" X/ {! Q8 g0 s
    public @interface RefreshScope {+ K' L' X, i* ~* I, P
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    + ~! t3 X" i/ z. M1 x}9 c1 v7 O3 e+ z( t. n" o" t
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    8 N+ ~6 j- x# J9 m" G
    6 c% r% c# B1 R( `: \1 C@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS  k: \/ }% o; ?0 G& D! H$ \4 L
    + @% q8 t$ h& U' n( w
    这个自定义Scope对应的解析类
    . x* v! W# i% p+ `; d: u
    * t7 ~3 Q( {0 [# C2 E下面类中有几个无关的方法去掉了,可以忽略3 L/ E# y/ C, z% u
    : J+ p1 x$ _$ N" _9 J
    package com.javacode2018.lesson002.demo18.test4;
    9 S, ?, i4 x3 H; R3 d5 D! I! X
    ' }7 t. ?; P  R. `' ^) ~: O
    - Y" r* e; m, b# }9 limport org.springframework.beans.factory.ObjectFactory;
    / ~, z9 e8 O; v6 H8 H4 Aimport org.springframework.beans.factory.config.Scope;
    ) s/ N1 [- k1 U" g5 ^/ G* j( ~import org.springframework.lang.Nullable;# _& Y" _( J; J! f( y( m

    , E/ E: i8 Y7 _4 Z, v$ ^" Rimport java.util.concurrent.ConcurrentHashMap;/ F! Z$ C+ E! K6 Z( ~: a

    8 A$ R, o. H. W8 rpublic class BeanRefreshScope implements Scope {0 E; V1 i1 [' Y6 Z8 j+ p
    & k3 G; {5 B8 k6 [
        public static final String SCOPE_REFRESH = "refresh";3 O* I. e! z3 m2 N4 ~" r& ]

    0 j9 _& V8 w+ g3 X% e' V    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();% L6 r6 C. @9 v3 r  F) m
    + n( K6 X- ~, V8 j+ N
        //来个map用来缓存bean1 u" K  l0 ^& P+ l
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    , V- A- w; X. G# D5 ~9 f- M. ~* M; q- [9 H( e
        private BeanRefreshScope() {' r& I/ j( m7 H+ S
        }
    0 J9 d" R' v& P; f1 H. B! ~# p0 \5 d0 l& t4 C
        public static BeanRefreshScope getInstance() {: v- \+ ^2 s% S
            return INSTANCE;
    5 g( D1 N3 q, v! O: q% |  W    }6 b' ]" E) s9 G# R4 M$ Z8 b4 s
    ! i. |. ]0 u4 f) P. Y2 q' W, \/ n6 E  U+ K
        /**
    ; {& J9 x: Z  Q0 R$ K2 _. G, V     * 清理当前
    : H- f- d, p0 I( J% `$ P     */
    7 |" ]( g' @* L! B6 K5 C: J    public static void clean() {
    + Q) o/ R7 x/ `+ e        INSTANCE.beanMap.clear();7 f# c  e( Z' r1 |+ H
        }
    5 ~. c; S6 q. H6 T* w* C5 D$ n' C
    2 V: h) \9 a; _* H# v% L    @Override  r% ~( {) q( G- Z% @
        public Object get(String name, ObjectFactory<?> objectFactory) {% S3 n; q4 ]% C6 J! |4 q8 G
            Object bean = beanMap.get(name);* O7 R) e) J: n3 H$ |
            if (bean == null) {  P+ f' @9 |2 x# x! T' d
                bean = objectFactory.getObject();# S% ]/ f! Q9 L% S+ H9 B- Q% T
                beanMap.put(name, bean);4 ]0 Q' k% ]7 G# @: t
            }
    0 g% P1 P5 f4 X  G        return bean;
    . H9 _" g2 f0 B! i    }
    5 N9 y# c! m1 m# c0 d7 g" U, s, N7 ?& r# |+ S, T0 E
    }9 w% |. t- F3 s% O! Y
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中: X. _! ~( A' Z+ W
    7 L$ L# A1 D4 O! {5 `4 L3 M
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    : g2 c, _" n1 v  E0 s2 }$ l! o( n8 f+ b3 d* i( p; H; N6 ]
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope( @- Y7 y% ]: X1 }, E4 _
    0 g3 V2 E- n5 _  G5 _( u7 L- q
    package com.javacode2018.lesson002.demo18.test4;" c& ~1 E& W/ e/ [

    ) I, r; x; E9 {9 `* b9 Cimport org.springframework.beans.factory.annotation.Value;
    0 `2 b: b! t' cimport org.springframework.stereotype.Component;
    8 i- R; M! o( g6 J4 C. S
    8 m. j- [  J1 v/*** `+ d$ c/ Y% i; m7 b
    * 邮件配置信息
    $ P" h3 B. R; j- Q5 u */
    . ?. d9 j, K% S% T@Component/ p' Z0 i5 b6 ~
    @RefreshScope //@1
    3 @( y' |% g: {' T* d* Hpublic class MailConfig {" l$ T" t& E4 K+ N2 @# s% C1 {* v
    / V5 ?, h5 }/ N9 G  Q
        @Value("${mail.username}") //@2
    1 s& s! Y1 L# M    private String username;
    & d% ]. N2 k5 q0 X3 b  Y
    " W: f% N8 \" B2 q0 o  A* z    public String getUsername() {
    ! ?& h1 A3 ^4 B4 T; j        return username;( I3 q5 N5 m+ K
        }
    9 v% U" S/ x5 R, Z6 d( n
    8 ?# y* `" O% H2 O4 T; c+ p% T" l    public void setUsername(String username) {
    8 b0 ]# x5 H# v3 b/ t        this.username = username;
    ( H6 _: P, A5 Q. c9 ?0 o    }9 H) d8 w7 I8 A6 Q; i8 y
    8 w- J! _7 H1 r4 w( s; d" p' a4 _
        @Override" o! T' c) I. h; A6 _
        public String toString() {
    ; S% T+ @8 L" D6 X        return "MailConfig{" +
    * ~& X1 W7 j0 M3 ]- [  N. ~6 M                "username='" + username + '\'' +
    . N9 T, K) l; |% u* ]0 ~* C                '}';1 B7 j% z( Y" V' C
        }$ {8 H! D) X- I& q, @
    }
    2 h6 Q2 L# D" s& ^5 J@1:使用了自定义的作用域@RefreshScope
    % n- I3 |; J- z' k$ d  ]" z' A& A
    3 m8 y% {  a) ^+ t@2:通过@Value注入mail.username对一个的值4 }+ e0 w- G3 d. y8 L

    , x2 U7 @! W) j; ]) a9 Y1 Z重写了toString方法,一会测试时候可以看效果。  K- `8 c# ]* G* F+ X- c  k
    ) u2 H+ c$ \2 J  r9 K* M" I* e
    再来个普通的bean,内部会注入MailConfig1 r# e# p9 m! {7 a8 S  b( m

    ( |+ C! x8 r& Vpackage com.javacode2018.lesson002.demo18.test4;
    $ e4 [5 ?8 V* [& h4 z
    1 _  g8 B. X8 W7 z) t6 O! A9 `import org.springframework.beans.factory.annotation.Autowired;
    , D" m/ n: T' r5 B7 x8 Himport org.springframework.stereotype.Component;
    / _9 U5 J) S% H$ X5 o' r
    5 U! |. c$ ]+ R@Component# u( i5 M9 S6 W3 N& X2 T5 C) E
    public class MailService {
    " p! U! q. u" k- i    @Autowired6 C6 a1 Y2 u6 ~) n! l+ F
        private MailConfig mailConfig;
    # i1 c/ s. M5 t/ m& x+ ?1 @6 \7 H5 ?/ A0 H6 [% B3 @7 e% l/ D5 Q
        @Override
    0 A: E, c' z, c2 f    public String toString() {
    / z2 A3 N' q$ y9 ~        return "MailService{" +7 m! o) M  T6 {( y" O8 d
                    "mailConfig=" + mailConfig +
    ; c8 I$ ?2 H8 v4 u0 c. R) h                '}';) S+ J% f- ]* @" c
        }
    - {3 P( u; D- I}
    1 s. M/ U  x9 j8 C/ I& z9 I  t0 ~1 s6 f代码比较简单,重写了toString方法,一会测试时候可以看效果。
    $ T8 T$ M% `1 }
    * F( R2 \& h0 I# S4 e) U8 x来个类,用来从db中获取邮件配置信息& J6 ^+ P7 c8 u* E+ P

    ( i# i) u1 v3 Q0 J5 }* d& e0 Rpackage com.javacode2018.lesson002.demo18.test4;
    . a9 q  L2 c1 H) ?( ?$ {
    - I! o2 s8 [1 n! ^  }- b0 k0 @import java.util.HashMap;
    4 t# F% ?7 C3 P6 U2 Gimport java.util.Map;, `8 ~% O9 L' c9 R8 V! P4 \& i2 [
    import java.util.UUID;# H: r) @6 `& \1 k! S

    ( e* k0 a' A( L! O, z# A% o4 ?2 ^public class DbUtil {
    / O1 i) \" p1 p    /**
    ! Z0 B; E' ^# k5 E# R$ n6 K; R     * 模拟从db中获取邮件配置信息  ]2 N% {3 y6 Z  B+ N
         *
    & ?: N! D* t8 d9 }. b  f9 T     * @return! H' _: R$ O) x( g! o
         */. g9 j* l8 t8 S/ R/ s, W
        public static Map<String, Object> getMailInfoFromDb() {
    : a% Y. W+ g$ U1 V; `. a, H0 A        Map<String, Object> result = new HashMap<>();. v- V$ H7 V) ^# j
            result.put("mail.username", UUID.randomUUID().toString());
    & F2 a% [0 v  s6 f' P        return result;
    , M+ u' {- v5 F8 Q! _    }- S/ a. o4 F( m& _& K+ F
    }
    0 s/ G! O1 U9 |- k! C来个spring配置类,扫描加载上面的组件
    % d6 z) z* ?! o8 I5 g. W
    5 K% [9 z/ u1 O! Jpackage com.javacode2018.lesson002.demo18.test4;2 u# W  k( _; G5 f
    5 U' Q; z$ I  p3 w! [, d
    import org.springframework.context.annotation.ComponentScan;3 |' j' x# |, ~( d/ r
    import org.springframework.context.annotation.Configuration;
      N6 x% I* z' Z7 z; J- l
    - L6 t3 U* D" S( Q" P" o0 z@Configuration1 X7 v& v$ a7 C
    @ComponentScan
    ; [" R8 ?% E5 A* F) G, r- `5 ?public class MainConfig4 {
    2 N( k4 j" w" ^$ s6 j, p}2 ^& V/ ^8 c& z5 }7 q
    来个工具类2 L  M" P8 Q+ h% l& u0 c+ }
    7 X" ~* Y* T0 e9 |+ v3 D
    内部有2个方法,如下:
    # e' R3 `6 }" E4 h6 v
    $ [6 g0 h" T- V4 }# lpackage com.javacode2018.lesson002.demo18.test4;( y6 V+ b" B/ t) w- e& @! ^$ R3 w

    + g$ ~: [6 W$ V  Y: G' h' r# Uimport org.springframework.context.support.AbstractApplicationContext;; f" r! c, n9 Y- V
    import org.springframework.core.env.MapPropertySource;
    8 R& ~7 E% V' X0 `% }. X' p9 K0 q9 {8 v" b/ n, ^2 s% O
    import java.util.Map;
    4 L+ f9 B$ H" x! l
    8 z" d4 @! E! @; T7 D/ U- @public class RefreshConfigUtil {
    + F7 A4 R6 T9 c    /**
    . j1 J3 e* a% g% k     * 模拟改变数据库中都配置信息; g% X9 D5 q& M" T
         */* I5 [+ ^) ^. h/ }  N5 h5 A
        public static void updateDbConfig(AbstractApplicationContext context) {
    % O# E. P& ]2 L) ]        //更新context中的mailPropertySource配置信息
    ' M5 \4 q1 x$ F        refreshMailPropertySource(context);
    : O; y+ w0 @' _6 ]' M) _8 x% U0 d4 L& {; ]- d
            //清空BeanRefreshScope中所有bean的缓存
    8 i* X2 P* ]7 [1 I1 w        BeanRefreshScope.getInstance().clean();
    7 c$ j( @" ?( a3 G$ ~    }" F/ k; V  U0 |& D

      ~" n2 ?3 S' ~4 R    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    ) a* K+ e, P3 N  g5 E        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();  I, M9 j; y3 m; G' U8 X
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ) u1 ]4 x+ \1 Z7 Y8 X        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    7 Z! h* J( ]" Q3 w) g+ M# L        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    / ^$ t6 E2 e* G/ C2 J5 \    }
    + f& P7 B4 h4 Q$ a' q' ]. s) U2 \% C7 i7 i- J% ~" d$ Y
    }
    % D4 k7 f9 h* O+ u- QupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ! @, G- K9 k$ ~$ G3 \$ O% S- r1 V9 _8 S5 A3 F( J1 E" ]
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    : j9 I9 `1 ~: V; |$ i
    % T3 M! x% o) h/ `7 k0 R来个测试用例
    " |# W- o/ X5 m9 ~; H; x) L9 j/ }3 {2 Y2 \" n; ~
    @Test! B; k  K3 o$ A8 @  E
    public void test4() throws InterruptedException {
    5 X; C" a* F: D    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();4 K! }5 h. E. O! Y% N% l. V* I
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());: |( N9 M) P$ J2 A" K) X# P9 X! u
        context.register(MainConfig4.class);
    $ V: G& c3 \) g, @    //刷新mail的配置到Environment
    2 L9 A8 u# g% }! ~9 q/ I    RefreshConfigUtil.refreshMailPropertySource(context);# l7 t4 Z3 M; U, g
        context.refresh();3 h- _5 M5 e; Y# k6 `& X3 b
    " j/ L2 I' O9 |+ l. b+ G
        MailService mailService = context.getBean(MailService.class);( P  y+ V! ]& }( K2 W1 D
        System.out.println("配置未更新的情况下,输出3次");
    $ i8 R# V, j7 U; \0 [    for (int i = 0; i < 3; i++) { //@17 m0 G! J7 H, O. ]/ X
            System.out.println(mailService);
    ! F$ q: U! C# p# @        TimeUnit.MILLISECONDS.sleep(200);. c# V% a$ C8 t: f& Q
        }
    0 E6 _6 S$ L% ]; e" }) J- V4 T5 ?0 Q, p- z  q
        System.out.println("模拟3次更新配置效果");/ s: `2 q3 \8 b" l
        for (int i = 0; i < 3; i++) { //@2$ e3 F% J( Q* W. ?) f
            RefreshConfigUtil.updateDbConfig(context); //@3
    # Q+ O" N) Q  v" @) I5 v7 S9 z        System.out.println(mailService);! [( W5 Y  _3 s6 {
            TimeUnit.MILLISECONDS.sleep(200);
    7 q6 N2 J% H% Z    }
    ! e9 s0 A+ [, p6 ^% D}4 P# K& @, R1 O' ?) K" [
    @1:循环3次,输出mailService的信息
    # T- x, R- M; w' l! J
    9 b2 v* X# s  a+ ?& t9 k@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    * W( a4 s  G5 [6 `( H6 u* [" @% j
    ! @8 }5 p9 F5 u" s* j) g见证奇迹的时刻,来看效果
    & Z  h/ W# F6 {: X/ x1 K  m' _3 \  _0 A! v3 G' x8 ~
    配置未更新的情况下,输出3次8 C4 z& V7 m# G/ }
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
      t) n: F: N" ?& G. zMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ B; D+ M. J: `* ^" y
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}3 v) W# \- c, b$ @& f; x. e7 y; {. F
    模拟3次更新配置效果
    1 y" [4 n# D- Y* G6 E8 eMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    9 k# @% \& w7 c! z- GMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}' |1 l' P6 @9 E' L
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}' D/ J# s% [0 E+ G( N8 C
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    % Z. @" N3 ^/ a) N' o9 r9 y7 t& H- @
    小结1 @3 X0 Q# _0 X! Y% R

    : N9 [: E2 ^8 n4 j8 {( u) ]6 {( \动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。9 V  J/ k6 ]! j$ [8 e
    8 x9 s7 g# M2 _$ G& \- j
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    3 {# j% P" j; e8 I( n$ T$ E2 Y* X5 _* m% X5 k
    总结
    ) Z. S5 S0 a/ ]2 |) a7 q( D  ]3 c4 V' l5 m0 {
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!- X% b6 Z: {% T6 S" j7 C

    ' x/ r/ |# {' R2 @案例源码+ H' w, f: d. O( n8 i4 m; T! T$ }: Z

    0 c! U9 e2 M* k3 K4 o* }https://gitee.com/javacode2018/spring-series
    " o2 N9 w( j8 y* _" f路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    7 Z( w0 ]8 M$ m6 ^8 _& @
    ! x+ A+ g. X. `$ OSpring系列$ R2 `* f; @' A) X

    $ x1 a8 _5 _* ]( _' vSpring系列第1篇:为何要学spring?
    " m9 B/ v: @( ?. P5 G9 d* d7 E8 V, L+ k. a. t/ y
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)& H( z" Q% k: t; V, a

    ; T! N- M, y( Y0 o/ r- \/ s# Z  r! BSpring系列第3篇:Spring容器基本使用及原理# R1 {) R. z2 m5 B, k4 }
    . D  L% w# R# Q9 P; o& ^
    Spring系列第4篇:xml中bean定义详解(-)
    / `- g# K3 I6 k4 I( l# Z" @) w. W% }+ j
    Spring系列第5篇:创建bean实例这些方式你们都知道?) B: n1 R; N: v6 Z
    " P5 p7 n  o8 b5 B5 g4 E
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    , A6 ~. J7 m/ k& G7 d2 ^5 `
    3 L( E. N& T* J$ `. z8 }5 T, @. wSpring系列第7篇:依赖注入之手动注入% V( ?) Z( {4 [9 j" o  Z
    $ L* {. A4 l4 W6 l% N
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持, L' W$ K0 p/ W9 v  g% c+ |0 _* v
    ) f" l" g  `+ a& I  M  z4 A- {
    Spring系列第9篇:depend-on到底是干什么的?6 h: o9 B6 w1 ]0 ~& y: U# a- L7 N7 g

    & U1 l/ P) _  o  USpring系列第10篇:primary可以解决什么问题?
    8 Y% f& R5 D, G( P3 x
    9 {# c  W3 _6 f7 x; u+ @Spring系列第11篇:bean中的autowire-candidate又是干什么的?& b6 u6 h% F7 P2 }- }

    ' M8 @; ?# ?/ J) H/ lSpring系列第12篇:lazy-init:bean延迟初始化
    + D7 w) U# O; ^
    3 L9 f$ r/ l5 u) x6 w/ ZSpring系列第13篇:使用继承简化bean配置(abstract & parent)" w+ Y5 }7 E0 T2 V$ a: F, R

    * E, }; |1 g" I1 Y/ E" `Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?7 G- }- z8 Y8 Z% z# f+ l

    - A; f' Q" A1 vSpring系列第15篇:代理详解(Java动态代理&cglib代理)?- U) |2 I2 W" [8 A
    * F5 j9 B1 J1 y
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    6 t1 D) v% |) F! s
    & Q. q0 g. c  j3 s, CSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ( D; y6 T" @! U; i/ W$ o% D4 Z. h" R# |+ n' |
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    , w  M! V) b0 O1 n" k9 p. H" l  B: y. ~7 I% B4 h7 A
    Spring系列第18篇:@import详解(bean批量注册)+ u/ e7 r( b9 W' h# J
    + o! k! n" T2 c3 w% I, Z
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    ! f) _  G& d; A# c1 j& M- S  C. q% X8 ?# Z' _3 ?4 }
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    ) N% x1 v: ~! C
    ) x6 Z7 j' C6 ]0 VSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    5 W! }- }3 m* e+ _, B
    9 L% w, o( o$ ]) o4 y! @) g5 USpring系列第23篇:Bean生命周期详解# ~+ H& v0 G- h6 c+ U0 E
    4 M: B6 c% E7 h1 y& g
    Spring系列第24篇:父子容器详解
    # j; r- c! ]# k% g* L5 q8 }
    4 B  H  b) w! F1 @$ ~更多好文章
    3 u$ m4 k* t+ j7 t
    ; F/ k( e* {) P4 T) U8 f( SJava高并发系列(共34篇)' g* B: t4 i% s. ?
    . x$ a1 \- q, x
    MySql高手系列(共27篇): ]0 j3 [. F. j+ m8 j2 j9 H

    / E  W( H/ u2 l) f" N0 f# [: K3 p8 wMaven高手系列(共10篇)
    : s8 H; e0 p4 M0 ?4 f  w% q: L' |2 O2 z+ b1 p
    Mybatis系列(共12篇)
    * M; X( S6 E/ }8 ~9 U# K
    # I5 A) P( t3 ]( Q4 Q9 a5 {聊聊db和缓存一致性常见的实现方式" @7 p3 \* X% Y8 o4 T
    4 @( v( @" s$ C% Z: {
    接口幂等性这么重要,它是什么?怎么实现?
    + }5 a0 x7 \3 T: Y. J* N% R* o/ Q% V9 V* L8 ?
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!( s- l' Z% `) g" S7 K4 f, Z
    ————————————————
    - }  H) a, n3 o+ }版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    2 E- o$ l9 ?9 j1 X/ [9 \  y原文链接:https://blog.csdn.net/likun557/article/details/105648757) M9 n( Y) D+ B- }7 {1 k) h/ ^
    ( J( ?2 @" @+ E! v' c# S

    4 o' ~  d5 l; g* v
    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-11-19 09:26 , Processed in 0.574722 second(s), 55 queries .

    回顶部