QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5523|回复: 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!
    * y! u0 {0 c7 y; T8 H& q+ T1 O* t疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!3 l+ u9 _- U( s  `" x9 d, |$ @

    ( F, Y; g( R2 v. l( k& n! ?面试官:Spring中的@Value用过么,介绍一下8 q- u1 s0 x/ ]( ~. R1 @

      Y- v" |' _8 l* B. {+ `! x我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中7 v/ Y8 z# u( n( O* J+ {* U4 t

    9 r6 S% g$ m5 w0 x( n1 X面试官:那就是说@Value的数据来源于配置文件了?
    2 y& z% b: w( ~* J* e* S
    ! E" [/ R9 `# K+ O+ z! ]& K) l/ D我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    1 }/ n3 H2 M( {( l/ u6 _( K) Q3 K- D
    5 \$ q' H4 _! [' O. @5 _+ {面试官:@Value数据来源还有其他方式么?( C, v$ K7 J0 P

    # i9 R4 l; N. O% z- B% j9 n我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    , |. f/ b2 ?0 D, b+ h) G
    9 z" v! ]  a( N7 S3 i面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    ! D3 x, q& ]' X( a/ ^4 q
    % W& o$ |# H% W* m( v8 r+ @我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    ' G) ~; W' s7 m4 Y: W: V& B& A' m
    % |6 f( \$ D' H. W面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?: u' U* p  O' i% A% z- M" ]8 [
    ! c# U4 B1 h7 L( h# `
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    # @- n$ ~. Y# x) o$ H+ F" f5 D, j  k4 P$ E% G) I$ z4 I
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    5 h7 ~! Z/ d' ?8 s9 }: n
    & T# S7 i/ ]- ^) b) E我:嗯。。。这个之前看过一点,不过没有看懂9 a! O; r$ a# Z& A+ d
    / d+ t; \" u; i9 _! h" W( p
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?9 l* j6 h( F8 j( X0 F- y, _8 p) l
    + j; ~+ X9 V8 z# q& L. g; D& W
    我:3万吧) M" L/ b! {, q2 X, F+ |! [
    7 w% M! [4 c* n5 ^
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    2 [& _" f/ a; Z! ^% A- l7 }+ T6 {( D2 S
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    . o* d4 H3 Z! L- I- C
    / V4 K% p4 B2 Y5 F面试官:那谢谢你,今天面试就到这里,出门右拐,不送!. [, o0 M* b% h' F1 n  k

    ( q/ N% S* T7 u$ @# E/ o4 r$ T我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。3 N' V6 Q! ?: h/ N

    ! W$ m/ \( U, G. k: b( b6 ?  |) D这次面试问题如下0 R) D. I# g' R( D2 n$ d% I+ [% G; m

    * |3 E8 H* Z  y@Value的用法
    3 V/ t2 |/ i% \6 X' p: m
    4 R0 @% U/ n. T@Value数据来源  {0 `8 D- D0 G) b( T

    8 U# f% g& h3 Z  I! _@Value动态刷新的问题" F4 H; o' z$ t
    . r# d! S' D( k5 E3 o5 }" S3 G
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。/ A% `: d6 k2 ?% h9 ^, w

    # m+ \8 S( d- D. O: X@Value的用法& V+ B2 }! h% o; c( K: z5 f6 F0 e6 E
    0 r. U7 k8 I" P7 L: x
    系统中需要连接db,连接db有很多配置信息。7 g" y5 N% B2 n# Q/ {

    & J0 R  B6 b; @( l系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。! C& y( t9 t1 [

    $ B# Q8 O' X' Y/ O! f! n8 x% k还有其他的一些配置信息。
    8 r5 L& r+ s. S. q2 T* X8 p1 C4 k3 Y, I$ t- `: h! S4 A
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    . |* A+ S/ \) q* U+ R+ A) _* o  N( ?8 p
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。; G2 w+ s4 {) P$ Y
    9 M$ o, d5 P* |" V
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ; Q) ~4 w5 @# s  y* V/ {5 q+ @; V! Z( Y- k" p# V, q
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    6 [) A5 b/ Q+ X' d
    : c% u5 |8 ~. p  z@Value使用步骤  k0 N- G3 U" E1 _  ?, M& Z# g

    ; j  m- I. h! Q5 @/ {步骤一:使用@PropertySource注解引入配置文件
    . h8 u1 k8 S& R" A% z
    ; d9 x/ r# }, ^. y$ ^' O5 W将@PropertySource放在类上面,如下
    * S6 F8 [& w9 P1 V2 h( v8 t6 f- u  z' n0 j  L, C/ G' V5 o
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    9 D  `- l9 [. M5 D8 A( q5 Q6 z  D@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。, a# G  V! I" R

    / q5 B8 b6 p; I9 |2 }2 u7 O如:
    / {% i3 _# S* I* D- b  E4 e+ V4 w3 a+ u  `# y: J' P% _
    @Component
    $ d7 X  c- \! n2 _/ F2 C@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})2 l/ T# [# A9 [- ~
    public class DbConfig {
    6 y' i+ d7 T4 R% J6 Q/ F; i}/ P# A! E% u+ V# t. j
    步骤二:使用@Value注解引用配置文件的值" g( K3 Q, L" J0 U4 d) w4 _
    . R" q" _7 v+ n5 a# F
    通过@Value引用上面配置文件中的值:  J4 ]2 o- A2 s* C1 L! C8 c
    $ M; I/ [0 o$ O
    语法8 ]$ b% c( W# f
    * m9 B* K; @: n* X
    @Value("${配置文件中的key:默认值}")- J+ D  p/ N1 p8 m' |- q
    @Value("${配置文件中的key}")! J* |/ o/ G) t, K
    如:
    ) V+ ^( _6 i$ M! O+ b# J/ o
    : i! d2 i8 a  t. L% u@Value("${password:123}")
    1 O$ |* ^2 N; \  s+ P; X) b上面如果password不存在,将123作为值
    - Z" u7 R% n% a' c7 b3 {' P1 M! z) B) L1 X, I) _
    @Value("${password}")7 F( T/ J: `3 t: A# E* m
    上面如果password不存在,值为${password}( H/ u+ n/ {: {; V) E

    6 I" \- t( A6 Y+ o; ~假如配置文件如下
    ) `# v1 K3 R) O) n# k: V2 i
    8 p! ~# F; ~/ _' I8 I7 c* A, W* Ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8* e  g2 I1 x- ~% i6 x0 H
    jdbc.username=javacode
      y1 b' `+ w1 fjdbc.password=javacode/ r/ p& @* o1 f) a, W, {
    使用方式如下:
    ) T2 `$ F3 }+ D1 [: n0 Y8 a1 b2 C# ~
    5 S$ T& w, ]+ k* o" @% @5 Z@Value("${jdbc.url}")- _+ a  ?2 k9 @; j! i! G6 ?; r
    private String url;
    4 b2 t$ u0 z# S7 N' \3 k1 V6 b; T1 ?9 ~# d" ^2 H- @. Z6 L
    @Value("${jdbc.username}")# D! N6 t5 U3 ?+ R. [9 O
    private String username;
    7 U5 y* J) R! O1 o; k9 \; I$ c5 r2 d6 s; ^) ~
    @Value("${jdbc.password}")
    5 B7 [3 n! h+ o5 a8 H2 I6 xprivate String password;
    8 W1 M: r7 J* }% y下面来看案例
    3 B+ F' R: J; W( D: O( U: g' F# x/ n
    案例
    8 q4 `, }4 _' i0 p' @; P
    0 j. r3 B4 J, k' c% j' M来个配置文件db.properties5 z3 ~- g1 O  I' D7 C: a
    9 Q+ P' t; ]* U( ]
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    , }- y! I+ _0 W* N7 O0 fjdbc.username=javacode. p4 H3 C5 g5 ?- [4 m# E8 v5 k+ g
    jdbc.password=javacode1 i9 J7 ?1 ?6 M# u5 @
    来个配置类,使用@PropertySource引入上面的配置文件
    - ]- w6 W5 D& c( y0 h5 L/ F% x+ H$ f+ ]6 |, s
    package com.javacode2018.lesson002.demo18.test1;  R9 q' c1 ~9 C+ z% Z( x! J. D

    $ B0 x# J0 q# s8 P& ^* nimport org.springframework.beans.factory.annotation.Configurable;
    : J4 }! q2 Y$ o  jimport org.springframework.context.annotation.ComponentScan;. ^: W0 b: u1 v  S
    import org.springframework.context.annotation.PropertySource;
    1 m8 G% n; {# @2 t( _; b, F5 l) e
    # h) G: y$ R' }1 f@Configurable8 n. I9 k7 D$ u1 N' L
    @ComponentScan2 I  }, \; I$ N; W7 W% a
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    - {& z; R2 f7 F" G" ~/ J' E7 M, o+ Epublic class MainConfig1 {! Q1 g; t/ s5 u$ x8 {
    }6 a2 o; d. N0 J
    来个类,使用@Value来使用配置文件中的信息
    ! a$ S4 ]! N9 d  b' ]8 F5 c% h: @8 p) L! }& z* D" |
    package com.javacode2018.lesson002.demo18.test1;
    * p0 E& n4 ?! F" C( |4 V* D8 j
    % Y* o7 \% j- `! b+ }8 Oimport org.springframework.beans.factory.annotation.Value;" [) l  N# T; M5 `
    import org.springframework.stereotype.Component;
    / S1 J% ^( Q: e8 t2 ^+ o! x+ P- D. X  s- ?
    @Component
    - H; U7 v! @2 [public class DbConfig {
    6 y  b8 ]% Y/ F+ C/ t2 Q/ X: e
    ; d" S/ N: q: Z( n& p4 Y/ u) c% q+ J    @Value("${jdbc.url}")
    - e+ o+ R8 ~" o: S    private String url;' p* l. q" e9 a1 h" q% w. e

    2 Q/ i" F6 K" o, U! t7 ^    @Value("${jdbc.username}")
    ) P7 t. w8 l1 E    private String username;
    * c0 v+ ^8 s/ ?$ x5 i; U+ F. Q
    $ k+ ]' p0 v" U* Z3 \+ s! ~    @Value("${jdbc.password}")
    ' C& d% N4 Y3 P7 a) d' ?# U/ t2 v    private String password;6 Z1 z  b: ~' }5 q' E% H

    . F' a0 W3 q3 F9 e% c" e6 T* R    public String getUrl() {
    8 I' A- J* {& C( B' |        return url;
    * B% l- [& B" X4 b( l/ _    }, ?6 ~* |8 L6 j2 _
    - S# p& S# t) j
        public void setUrl(String url) {
    # h2 d- C# s  f        this.url = url;8 m3 N6 m4 @0 X+ ]% C$ ^" e
        }6 P& {+ I/ r/ h: C' s, y: C( p; W

      t& g& q% G5 J2 H" p    public String getUsername() {
    4 d5 b2 f/ _& N3 ~        return username;
    & \! }( {4 }; `) w& F3 K    }4 n7 S/ ^, i! e6 g% y& z
    ) h3 {2 p1 P$ r; F4 ]  w8 x
        public void setUsername(String username) {- Y$ a; `' j8 p) i* l
            this.username = username;4 K1 [; x% {8 ]- ^# Q2 o: I( N
        }
    9 W0 C2 T5 B+ g8 o6 }
    ; B" @! o7 f1 i2 i% C3 q8 }    public String getPassword() {
    ! S) {  A- w" o# J! A9 ^- J0 Y        return password;
    1 T" M8 x  a& T; H- G, B  ~# h    }
    0 K: J8 [8 ~$ c, W7 l0 _2 s6 U& v" K# h3 U! ]
        public void setPassword(String password) {8 H# Q, _: F5 X7 f+ ~1 k& ^& w# t
            this.password = password;. {/ l5 g6 `$ [$ M: Q8 j6 j4 N
        }& f- C: p. f) j2 B) _; {, t/ U
    & D1 v! q3 b. U) u
        @Override
    & o2 L9 ?: {2 d' o    public String toString() {% J$ e9 I0 a. n
            return "DbConfig{" +( a; [/ ^# K1 z: H( ^) c7 ~9 g% J
                    "url='" + url + '\'' +
    0 m$ [: V; V' ]' ?7 D                ", username='" + username + '\'' +9 |$ @* s8 H" j( c- n
                    ", password='" + password + '\'' +" B$ a0 c( I+ }0 Q
                    '}';- i% A" i" L9 G8 W7 o5 r8 s
        }% ?# ]* v* q! G+ z) @
    }
    0 A- k, a( u7 \+ E/ j; f上面重点在于注解@Value注解,注意@Value注解中的1 \# V$ \* N% o& t% d

    4 l- Y# k: L2 X" C来个测试用例
    5 a  H( g% k$ m7 Q( E' \. c: G8 D. [: M8 t2 o
    package com.javacode2018.lesson002.demo18;
      D+ \$ d* L- t. R/ y9 P  {( |* u8 g* q
    import com.javacode2018.lesson002.demo18.test1.DbConfig;( s3 N- W: r( b$ V
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;/ Q+ T1 T$ ?- Z7 \2 S. z3 {
    import org.junit.Test;
    7 B% T$ D  a( X) _0 ~- T/ uimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    & [. B( r4 Q- D) g( m7 O- E6 h, j' `: x2 X6 g; g6 Y5 Z
    public class ValueTest {; X( ?  M: E0 r. Z# |. h. ]8 A, u

    ( c. d% e3 ~2 s    @Test1 \1 o2 t# J$ }7 {: ^
        public void test1() {
    4 s! @) I/ C; z3 r: N        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();6 T: @7 v6 h. J; j0 ~( X
            context.register(MainConfig1.class);
    7 N2 X& p/ u( |        context.refresh();
    3 b* _2 \' ?, [0 W1 P
    ( ^) y7 ?: V- ^7 `( x        DbConfig dbConfig = context.getBean(DbConfig.class);
    , k& [; J6 a& o* ^; A0 C4 a, i3 |7 }        System.out.println(dbConfig);
    3 l1 }  H/ b; B2 q    }4 |- u2 [( r. D- b3 M! [' S
    }
    ; ]1 `, g+ v. ]% L运行输出
    1 g1 Y' ^3 S: M' [  g: Q8 }/ X$ ?$ x- S" r8 a
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    1 f/ q# Y% z% t( K" _% `上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。, A0 @& J5 Y/ ^3 t" q$ K

    & t3 s; z6 Z  ]% b* L@Value数据来源
    0 ~% K. @7 K. g" j! [
    4 E3 H$ ?- r2 U, Y2 e7 [/ N通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。- [1 M3 v" W- {  m
    * b$ h0 h: U4 j4 I4 G
    我们需要先了解一下@Value中数据来源于spring的什么地方。" |* Z* Y& C# C, ^# i5 X

    2 q5 E2 g2 X! o$ `spring中有个类
    $ j9 h- G$ I, }8 I8 N" X" U
    & t: d# W! G( A2 o1 @$ P4 rorg.springframework.core.env.PropertySource! a0 B# K; k0 a9 `. z( G8 S( _
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    ; L( a' h& a8 v" L2 z
    1 P2 i9 A- n# ~3 F: s内部有个方法:
    6 W: P. M0 X0 j/ d" n/ |0 Y
    # [$ j3 f. J: C4 L: H/ j6 A: J* Rpublic abstract Object getProperty(String name);3 X" o  H/ t* [6 J+ E
    通过name获取对应的配置信息。
    $ u, T* _& B. a  E. q4 Y
    & {) {) {$ }4 ?: F, E系统有个比较重要的接口. A7 z6 p3 v3 ~' `- F8 w. t

    ! ~* a( G; T& R% I, `- zorg.springframework.core.env.Environment% f5 l" s: \" B, Z
    用来表示环境配置信息,这个接口有几个方法比较重要
    , C3 _" G$ P1 F: D; Z, u1 Y( C+ e: v3 ~( b; p
    String resolvePlaceholders(String text);5 E  B) D* E- U( A, Y
    MutablePropertySources getPropertySources();
    ' j" Y+ y/ {4 G9 WresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。( O$ C& L, ~5 w. Y2 D$ ?

      V- `5 G2 b, ^) ?4 I: O" ugetPropertySources返回MutablePropertySources对象,来看一下这个类
    : Q3 y+ x4 E/ Y& ]+ W
    9 H6 N% Z( I8 F* \  R: G5 ]public class MutablePropertySources implements PropertySources {2 }7 @+ L: e8 Q" b) b/ s
    " z0 F8 R! n4 V- o# O- v
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    7 `, |( }4 e3 G0 {5 u
    % @, `- W8 g$ D% I+ ~* H}
    ! @( e+ c7 C6 ]  ~, Z% s  n: X内部包含一个propertySourceList列表。
    " @# A$ s4 L7 n, E9 q* |% E
    0 [5 W& G! `0 E; {+ H6 o# fspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。* Q$ A& X. |! c# y
    5 i) d5 t  t. W4 n$ a+ L
    大家可以捋一下,最终解析@Value的过程:9 s1 K- T% z" E( n# u

    & T  L; I9 C* F9 z3 o1 ?6 X1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    : ~9 |3 r, g# w* o0 Q2. Environment内部会访问MutablePropertySources来解析
    3 Y( ]* M! }8 B+ ~& ~3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值5 j' F+ \3 B9 g. s7 k4 P
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    " R/ M& \6 @! ~# _" R: t" r1 c. T; y) D) Y: c, q
    下面我们就按照这个思路来一个。4 f( Z: B6 ~0 e$ }: w
    ! G. F* R# k+ R4 S2 o3 j. ?
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    / S+ o. X9 B' m& e8 s8 @& K, h4 Y$ j
    package com.javacode2018.lesson002.demo18.test2;
    ; i$ X' S! C( i: Y  H+ |3 O  S0 ]8 g3 |3 ?# m$ N; z
    import org.springframework.beans.factory.annotation.Value;
    7 L; F6 r8 c1 \7 J. dimport org.springframework.stereotype.Component;: H4 O$ Y, ?! i% v6 u

    , M2 J0 \8 G3 w9 V. n7 ~/**; r4 K: r9 j7 O+ S
    * 邮件配置信息
    9 L$ O  h0 ^/ Z- e4 K  g. g7 ] */0 x4 C$ X& W. c9 t- W
    @Component
    8 K$ J& c+ p: w! ]public class MailConfig {8 w2 P6 O' J2 a. e
    ' R! N8 n8 [% i
        @Value("${mail.host}"); {7 p+ V0 i7 l) G+ d  J- y% g
        private String host;
    3 _7 q( m! l  e$ E( e0 ~: R; q5 y9 K/ ^
        @Value("${mail.username}")( U$ Z' a' \# p' s- [
        private String username;# e" L# X. a# T/ ?/ r! F$ a

    0 e6 j& ]; b) [7 T1 j    @Value("${mail.password}")
    0 @9 x4 d1 j/ y$ E    private String password;9 E; e6 ~/ O; J) Q8 G
    * C  y6 D2 V% p9 L
        public String getHost() {
    * u4 Y/ B6 r' D% h# }2 i/ T        return host;  k* o/ G) c' q2 S( h2 V
        }& A/ F) P; q' T0 Q

    / a( V6 U  d- n2 a    public void setHost(String host) {
    / v9 l) |& t6 P# T$ U        this.host = host;3 G& `  f- B8 z& T( b" `/ F
        }. C9 m! I$ X; @- [/ \9 _

    1 U% F8 {9 s/ Y2 W) _5 s8 A1 l    public String getUsername() {1 D! G- D) g. X/ Y9 p7 `
            return username;/ ?2 t; N9 ^* _4 z) u
        }, J+ W$ H7 H7 X

    / W2 p/ v; I0 d    public void setUsername(String username) {% A  |: B, q1 W' M) M+ k% S
            this.username = username;  u- i2 o( [: ?( J' o/ t
        }
    / S' \! T0 k& ?4 E3 m! K$ S* c4 g
    % |1 ~8 S$ _' d3 c+ Q    public String getPassword() {1 r; l' U% D( F& E0 `
            return password;4 ^6 I4 J, E, [# p. \4 G1 N% f
        }
    + H* m5 Y* x6 a' T' ~& i, |
    * }9 ~3 L2 h- |$ B0 ?. X    public void setPassword(String password) {  b' ?' q2 h( V& U
            this.password = password;
    , `7 ^" P/ e; s+ D. Y. B' h  Q& Y    }' j- `: @& W3 y# K/ M

    & n, E7 N5 r2 E1 n9 b    @Override
    9 N# ?2 M- r) ?) p: I% E    public String toString() {
    3 D+ i3 Y1 @( M/ O        return "MailConfig{" +8 {9 D, s9 R  l
                    "host='" + host + '\'' +
    8 F& ~& h8 s6 B# e' z+ @                ", username='" + username + '\'' +
    % }( w/ u7 G7 T3 M+ Q4 }3 M                ", password='" + password + '\'' +
    3 m4 K: z, m1 z                '}';
    1 t. ]. }$ U0 [. B0 x% X& ^    }
    , O8 z+ F5 \& q/ P}% _7 `, G1 D' `7 Q
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    $ y+ h% x3 ]9 {( z. F- t$ g: B. |6 Z" _! z5 J
    package com.javacode2018.lesson002.demo18.test2;
    0 O. K6 M6 O7 @1 L6 E* {0 M
      T. P7 E) s& y- O& uimport java.util.HashMap;/ w3 V( p+ \" U; y. L0 P& [( U
    import java.util.Map;
    % s0 I* I6 m' a9 u7 @# X% @4 a& @' C
    public class DbUtil {4 l; S8 F- Y1 j& f+ N. h' m; `
        /**
    9 Y' \- N1 d6 I. l7 t     * 模拟从db中获取邮件配置信息4 K1 A  E/ u; B  _. y
         *8 b* }' |+ t2 X* O, e
         * @return8 {& d5 r# B; y
         */
    * o2 ]' U% i4 a, v4 S6 F    public static Map<String, Object> getMailInfoFromDb() {
    ' H( }, J. b6 A' T4 T" y  F* A        Map<String, Object> result = new HashMap<>();6 E) F5 L7 e" q+ `5 K
            result.put("mail.host", "smtp.qq.com");% Z4 u; C4 G: U  Z% B% v
            result.put("mail.username", "路人");+ c' ~$ d+ z/ |/ {! H
            result.put("mail.password", "123");4 h! Q- J: A- @
            return result;
    9 S" \, y0 x. F, W+ N' v0 w    }
    ) }6 I3 r( o, o, ~}
    ) A) w1 o4 x$ f8 W8 r" d" z8 _来个spring配置类
    % I/ @& R2 C  S/ K# y1 J6 v' U2 K* r0 G
    package com.javacode2018.lesson002.demo18.test2;9 X/ \0 o+ m7 J. f5 Z9 ^
    % v& U0 _' x9 J) l# @
    import org.springframework.context.annotation.ComponentScan;
      e/ \$ I2 ]5 Eimport org.springframework.context.annotation.Configuration;' m* S* A# [% h/ P. W' ?
    7 t. q9 V! k7 S
    @Configuration: q8 I/ N, }4 j- i: ~. V8 A
    @ComponentScan$ A6 e% B( ~6 c7 n! v) o# x
    public class MainConfig2 {) A& Z6 c$ R2 }# h) _
    }/ o& q* H$ F* F: `7 t0 A$ E' I$ Y
    下面是重点代码
    - T! T7 B3 _- R0 l
    3 q9 Y& J% s* O+ u& H@Test1 f' r& P1 ?$ ]3 ^2 ]
    public void test2() {0 y' Q: c5 v/ t, Z5 e
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      [9 d+ x9 S- D4 {+ Z
    4 b( }- U" w8 w4 G5 ^6 L  ~    /*下面这段是关键 start*/2 }# [! |: q- x& q0 o+ l
        //模拟从db中获取配置信息
    + m7 [  n  A( S) i; K. _! E6 z1 H    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 |, N2 l! _, {' n, I  N2 u  L9 \
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)3 B# G" p! w$ }6 v
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);7 ~/ @) B" X" b8 \( j; r$ K- m
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    8 z6 z+ I% d2 z2 C6 I    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);1 C- q$ C" q; x4 c7 r
        /*上面这段是关键 end*/4 z+ J9 |$ j5 W

    6 f0 }. H8 Z  ~* ]0 G    context.register(MainConfig2.class);
    3 s+ g4 X+ {% Y6 A' H    context.refresh();3 I: l1 l  l0 Z) ?- N
        MailConfig mailConfig = context.getBean(MailConfig.class);
    9 m8 p. f1 C- q) T1 @' S    System.out.println(mailConfig);
    5 v* H1 T) {  s9 u  }) D: R2 R1 v' a}: D* t/ V/ `& }/ ]
    注释比较详细,就不详细解释了。* R7 E2 z7 {. h  e4 }1 D8 K
    $ N3 Y7 h' d5 a" b; S
    直接运行,看效果( o( [9 _5 h" y9 |6 c3 R

    # V) N2 X4 ^4 ~$ JMailConfig{host='smtp.qq.com', username='路人', password='123'}9 _9 L/ O0 K2 p
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。/ X6 m. l4 b# j) }

    5 }: h! J, ?4 Y) ]7 c上面重点是下面这段代码,大家需要理解5 A# |& V$ r% v1 R4 w5 c

    4 n0 x7 V, w6 n: R7 H/*下面这段是关键 start*/! h$ z5 C, |/ R
    //模拟从db中获取配置信息
    $ k5 R# i+ B8 UMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    4 h  e! k9 U0 q//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& `7 K0 i* r( V, F+ c' A. I
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* @8 [4 J8 |! W3 D9 b  j
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高5 ~3 \  r0 m, q
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    # N( j6 O5 Z' _1 y  @3 g/*上面这段是关键 end*/
    ) v; i* U4 Q/ @3 ~咱们继续看下一个问题4 Y5 s6 I' k/ u0 l3 l; h" n" X+ h
    # M, C2 \+ I% e; _! \  H. d
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。9 r' T( |, k$ l7 o( j

    7 Y/ L: d# ]9 b* t) t@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。# B. Z. I7 q+ B+ K7 ?7 g2 @
    0 U$ W7 }" a0 v
    实现@Value动态刷新$ X, D% q5 b2 i% P5 A" g/ n5 S
    7 L* ~% N7 E' x& j. N
    先了解一个知识点
    1 x( f! D+ @, J* ?- |" Y  R' G, x) n4 z* U' J& V% J7 K! b% z/ N" ^
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    , n( |& \- z* Q! Y2 {- w0 Q+ A/ `: V
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    3 B' B! i( D* G3 x
    3 S, r1 U" ], N3 X3 P/ z! Ibean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:- C$ {$ I6 B7 {) d

    4 u' h6 E2 c- o! P# C* CScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    " s9 k5 C2 _8 ~! k- Q这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    6 W& j+ }" V/ Z# q  x* c2 }- P. `# T" V' Y$ {9 Q$ [; ]2 k+ D
    public enum ScopedProxyMode {. Q' x' u1 M6 b/ ]% q
        DEFAULT,' r* q# V/ ^& k' ]# x) L
        NO,% f8 I" w; ^. a! m; j
        INTERFACES,: }9 V+ X4 j! {9 H5 c+ N
        TARGET_CLASS;# O% \% S1 M. g- Z& l$ d
    }  X; \4 c  a! c0 ?8 W/ a" L
    前面3个,不讲了,直接讲最后一个值是干什么的。
    - g7 x8 a; }1 w+ W5 p% m" p0 D+ A+ q6 ]1 H+ b" H8 F
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。2 ^8 B; E' F' f0 B" U% H

    7 r  I; t, g+ a理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。# Q, L  w' v% ]. [! W2 B; A

    " o' @% J  }, B5 |7 i% S自定义一个bean作用域的注解6 j* r3 s! V6 S% O* B" a% E
    , x( I% D' R# C. x8 a
    package com.javacode2018.lesson002.demo18.test3;
    # C4 I( ]1 D4 k+ K, G
    ( t9 q$ r0 C! Y' j( ^. o9 ]& Iimport org.springframework.context.annotation.Scope;
      G' k) ?: v2 U* ~' j5 ~# aimport org.springframework.context.annotation.ScopedProxyMode;) X1 W+ V8 w6 W: v3 I; i

    4 R2 O" c/ _. j* Gimport java.lang.annotation.*;" V* L% N, G& Z6 s

    % c$ f; w$ ?- g@Target({ElementType.TYPE, ElementType.METHOD})
    . l+ w# H/ G  k; C' E& U@Retention(RetentionPolicy.RUNTIME)6 @/ K5 ?7 ~' d5 `' A) O
    @Documented1 X( g; G% z: [* X/ `3 V
    @Scope(BeanMyScope.SCOPE_MY) //@1$ X. S$ w+ ]5 x1 l+ ]1 o) V. ?; q0 w
    public @interface MyScope {/ N6 A9 a' {3 h
        /**
    5 l" m' u, r8 q- Y     * @see Scope#proxyMode()7 U% C3 w5 q* [3 v8 u! }1 ?; g7 j
         */7 w8 k/ l9 _0 H5 c& b& s; m9 e9 V
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    4 C$ [9 S4 [7 p0 @5 f' ]) Q! o/ b}
      k$ ]$ M' R* |6 g/ \% \( n  I; ~@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    . H" C9 F* a. f1 Y/ P; w8 n7 D! P8 e3 i0 t
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS4 f' y. S+ Y* b! O

    5 W' a) [" g2 U8 L@MyScope注解对应的Scope实现如下/ {1 {# t7 I8 X, t* N# W4 }4 N6 M. S
    ; U- A5 d; R. b1 e; Q8 w, P
    package com.javacode2018.lesson002.demo18.test3;- }7 f/ j' P2 }$ h
    ; T# {: k4 B" x& G8 T
    import org.springframework.beans.factory.ObjectFactory;2 e6 d4 X8 R3 ^( a, c  W
    import org.springframework.beans.factory.config.Scope;
    + o" h# F2 B8 s* eimport org.springframework.lang.Nullable;
    - B  N+ F8 `3 T) j% J! i4 W9 ]0 E' l$ k$ n
    /**6 C6 P, G. L' a6 ~
    * @see MyScope 作用域的实现+ H( a7 j  T1 O$ g
    */1 ^( \: h& w+ H/ x9 ?
    public class BeanMyScope implements Scope {
    9 j4 `6 @8 O1 q2 v/ j7 h: y. l
    * d) D- q4 h4 W+ I  M0 Z( V    public static final String SCOPE_MY = "my"; //@1
    / @9 B" O- L4 n( J, J% [+ k1 a  ]# p+ v4 P1 U4 d+ E9 i
        @Override
    ; w& }% ?9 C  H3 M7 m; W5 `    public Object get(String name, ObjectFactory<?> objectFactory) { " V( a+ T* b+ U, t* D
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    3 h7 ?- J* p" V$ A- ^- p1 |        return objectFactory.getObject(); //@3
    ; t/ H/ a" a" q( v5 V$ N    }' D: ~6 |/ U# Q4 [* n1 S* p

    6 @+ W: n! f0 D+ T; ?& i3 @4 F    @Nullable" N; f2 M7 _3 G! ~- a& ]( T
        @Override5 z( ~4 H, |' a8 i5 r
        public Object remove(String name) {$ J! c5 ]: A7 w& J
            return null;0 U5 h1 q9 g% N; G! |7 O# Z" k5 |
        }
    3 t! u# D8 _) q) @9 w* V# h6 R) c$ S! W, S) o
        @Override# G) q; ]/ O4 L; ^+ W+ x- u
        public void registerDestructionCallback(String name, Runnable callback) {4 }" w/ J( q/ U$ i& x( i5 @3 L1 r
    4 j; V! S. P1 [
        }
    6 [0 C$ b  g% Z5 _7 H
    1 c! @6 @9 S8 Z    @Nullable! t, ?9 V: [) u. C' |+ g
        @Override, @6 b8 M4 C7 ]
        public Object resolveContextualObject(String key) {2 P* H$ }& \$ m/ T! J
            return null;
    3 v3 }2 e, [; h1 _; a( ^0 ^6 J    }  g) A* }5 E) o- v* ]+ O: T/ @

    . M9 M* B& l/ A% ]  X    @Nullable! R3 O6 ]$ `0 R* o5 a+ y7 Z2 p9 N1 s
        @Override  X  k# M: ]5 `2 l3 G- X: M3 F
        public String getConversationId() {
    . ~2 Z) v# p9 Z- V7 Z/ N4 R1 g        return null;
    $ J; n( M/ G9 C3 z/ w    }3 z$ q1 w2 \$ O" |& B6 o4 K: _
    }5 r0 N# ?  c% V% a
    @1:定义了一个常量,作为作用域的值; l5 D+ \8 x! \) D2 I
    3 d3 P; Q2 W* D1 G
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    $ Z& X  ~# _; Q, G6 c& G( r/ B
    @3:通过objectFactory.getObject()获取bean实例返回。+ @9 @% s! k9 w( X2 w

    ; _* K( X! Z# z下面来创建个类,作用域为上面自定义的作用域
    - t4 T+ C7 e- l# O
    1 V" n! ?5 y0 Y+ [: Mpackage com.javacode2018.lesson002.demo18.test3;
    3 w$ z0 e# y1 [/ K) E0 W* e8 @3 ]" k3 L- C. N5 D1 T
    import org.springframework.stereotype.Component;
    # n! i; K, v/ |5 a% I" Q0 p9 k& z1 q1 I
    ( @8 d5 |- z4 F9 B8 k" ]9 {& g- Wimport java.util.UUID;; ^  [! j1 W* Z8 W, o; I- }7 {
    1 L! w( T- l0 v) V& h0 t9 e, q
    @Component( b7 u5 X- y# H; `8 ^0 C
    @MyScope //@1
    9 v+ k2 k% r) W( V$ x- u' fpublic class User {
    * F! G' y- a0 b  V) O3 |- I( B! Z% Q. o8 G" U+ v( x% f, Y4 G
        private String username;* I2 ]9 y7 x$ [% _$ b- p$ S4 T
    . {. u, Q5 l9 @  U/ P# N9 o7 A
        public User() { - D1 L  @/ X/ I+ S; ]7 d- m
            System.out.println("---------创建User对象" + this); //@28 a* o: }$ r6 ~
            this.username = UUID.randomUUID().toString(); //@3
    * l8 e7 y  J0 L9 a- U" P1 ^. Q    }
    ) E9 e6 d+ _& D% z" p4 T. l5 [1 ^( b: P! z
        public String getUsername() {/ a& b/ _, o# X) k" S1 i. b
            return username;3 ?2 H- P, z- B) w% o
        }
    & m, J: o8 _! k& c4 x
    , ^8 }! Z) a" h- Z& R, H5 Z2 O    public void setUsername(String username) {
    9 \$ B, j7 Q+ p( R8 i        this.username = username;
    # R# {9 I2 P& _: ^% s, w    }
      ?2 i. j# O' W
    , _5 V, v5 t0 ?+ V6 [}
    " S9 b# N3 I- r# ~  r( l@1:使用了自定义的作用域@MyScope6 w& W4 y" D+ f) g, V' P. M

    + o+ V, O( P: c; F@2:构造函数中输出一行日志
    & W4 {: ]* |; e1 f
    6 @1 A, x) a; i! V@3:给username赋值,通过uuid随机生成了一个
    & w3 `1 g7 h9 j$ Y% k* `/ C$ Y6 ^6 H  X* X: j
    来个spring配置类,加载上面@Compontent标注的组件2 Y  M* @& t' T8 G8 z
    4 D  x4 l2 n% F; z& h7 s* N3 ^
    package com.javacode2018.lesson002.demo18.test3;( d: T5 }+ `0 [+ m/ g1 [
    1 C2 b! Q# W& o8 s/ W& R& P5 C
    import org.springframework.context.annotation.ComponentScan;! `% D3 M/ c3 S9 i6 K
    import org.springframework.context.annotation.Configuration;
    2 |7 c$ {7 j3 l8 X, X8 Y+ W4 p! ?# H( @( g
    @ComponentScan
    : {8 |- p& S$ J& [5 p; y& w5 j/ \5 A@Configuration
    - Z0 f! F9 A" `! o0 `1 ipublic class MainConfig3 {! c* l1 t. ?; B4 _* x3 A& w
    }* ^6 j* c8 k* P9 p( A" y9 ^
    下面重点来了,测试用例
    1 R# `- e& z+ x2 Y: q. l; ^$ R, Y0 A  K
    @Test
    1 G4 J7 o3 \& n: V& c! C/ Ppublic void test3() throws InterruptedException {
    ! J) A( D8 y% H3 ]2 s    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    3 e5 g0 d$ M# f1 A    //将自定义作用域注册到spring容器中* {1 o; F4 u- r8 d
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    3 O3 V$ g+ m3 t9 E6 b* C3 h6 q    context.register(MainConfig3.class);& @; z6 Z8 j* E6 {( V$ D
        context.refresh();' _9 ]' X( W1 p% z. L& J3 l

    : `/ q5 ~) y' N% o' ^% d; h" M$ D    System.out.println("从容器中获取User对象");- Z( W! ?$ p4 U7 D
        User user = context.getBean(User.class); //@2) [' L+ s3 A- P2 {0 b
        System.out.println("user对象的class为:" + user.getClass()); //@35 P( T$ s9 i  ~3 B; z( l% S
    0 S. Z& t* p& S8 @, i  v
        System.out.println("多次调用user的getUsername感受一下效果\n");
    & h9 i% N% Q- I; g* @    for (int i = 1; i <= 3; i++) {
    : Q9 j4 n" ~. b6 l" ~. b        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    1 l" U- t7 v: i6 e+ w2 f1 \        System.out.println(user.getUsername());1 n/ l7 L, k2 I! Y( |: Z
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));. i- W  C7 [/ N6 Q. k8 n/ O
        }
    . O/ G4 V$ T5 V4 Z9 ?& k}! x1 J7 Q) e& m$ u
    @1:将自定义作用域注册到spring容器中$ W9 m- D( F; t3 q
    * |( P( z" A, B
    @2:从容器中获取User对应的bean' q3 d6 i( d( I: x$ ^7 \
    7 `: V* f5 V, n+ W& ~
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    - z1 U4 G6 Z( e( x; h' j
    ) v1 E$ z1 E  V4 w. T9 i0 i代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。/ m- r3 n; ?- B2 @) k! `0 o9 M0 L

    ( I  w4 w2 }  {. `0 |5 O+ |9 |3 t见证奇迹的时候到了,运行输出
    3 m: t2 ~! K0 `
    $ f8 e0 z) S) g6 }8 C5 U) }从容器中获取User对象
    5 X+ {7 q& w! T. U- ]8 fuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127" r0 F# |7 V0 h3 c6 `( j
    多次调用user的getUsername感受一下效果
    , f2 C" [: N. e2 ~& Y6 s, S- j0 c: a; @5 q' n5 d/ Q
    ********
    2 u7 m$ j! T4 d, o; k& F第1次开始调用getUsername9 q; w" P9 B/ p  d# _! R
    BeanMyScope >>>>>>>>> get:scopedTarget.user' q/ x9 d8 [4 v
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    , u, \/ A) G# g$ B- ?4 h7b41aa80-7569-4072-9d40-ec9bfb92f438" n, R4 f4 l" [5 A$ ]8 F
    第1次调用getUsername结束
    1 |: o2 @4 M3 J# `) _" t1 f, Y********! D) P7 b, R+ o# n" H

    4 D+ ?# p+ _2 q# U/ k4 [* R********  U% ^4 {! ^$ m/ o6 s4 k/ P/ r
    第2次开始调用getUsername6 R' e. e6 u5 f4 q0 O! b- \
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    9 T% O/ S0 \$ R" A* P+ n9 @---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b8 x' @2 b4 K& G6 e' m, Q0 r
    01d67154-95f6-44bb-93ab-05a34abdf51f  M! U0 ]4 z) u
    第2次调用getUsername结束9 g. F4 `# m7 @! i
    ********6 S4 y  Q, k6 c3 F( v6 W, E! G$ z$ ?
    , V- \& {8 g( \& _0 B
    ********
    2 w% ~2 M1 Y4 ~. n) K第3次开始调用getUsername
    8 }! X6 P& S' c3 G- @4 y/ NBeanMyScope >>>>>>>>> get:scopedTarget.user
    ! W* Q# g; P" _- [0 `---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    + {3 i4 T" [/ H8 [76d0e86f-8331-4303-aac7-4acce0b258b8
    4 q; i: _. }8 S% Q第3次调用getUsername结束! N& H( a5 q; m1 |) e$ _
    ********
    . H* S+ x  {- F) [# u1 \从输出的前2行可以看出:& P2 T& ]5 W, h- f1 e5 N

    0 k% B4 F. e/ B* Q" [+ B7 U" q调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象# ~& K  j! t' G" q6 I0 v8 {$ ]& m0 o+ ?
    5 k2 u4 x9 A. r6 v( o# h$ y0 A
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。- Z" e( L0 n3 K% T! o8 Z
    2 ~9 G% q# G* H. e: o) P
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。! i) L# h3 j7 g/ A
    / a( X8 M3 Y) t) H* W8 j# D- v
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。% q7 o, v: }% X, }

    4 W. w- i$ `, M8 h2 b& E0 l动态刷新@Value具体实现
    + I# |" C1 W; @* Z: k. ~
    9 e8 R5 e/ a; N8 h1 A; _, V6 ]1 Q那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    ' _$ ~1 _" f, v! [8 C$ B
    0 j9 ^. ^4 S2 p) F先来自定义一个Scope:RefreshScope
    7 ~, v* G- z) a* _! |, {6 _) v1 c0 U) ]. C
    package com.javacode2018.lesson002.demo18.test4;3 M* D4 M& R8 ]* D% |) }/ D4 M

    4 {$ D( J5 [6 D* B9 q/ r$ c$ Bimport org.springframework.context.annotation.Scope;
    / X. d! W1 u3 s! B7 r* f" e5 Limport org.springframework.context.annotation.ScopedProxyMode;" V  \* B0 W2 f- h
    7 e4 y" s6 z! P
    import java.lang.annotation.*;
    , s- \) `+ W9 P5 t# o5 _
    7 V8 J( @& s4 x* I- _# D9 F! p@Target({ElementType.TYPE, ElementType.METHOD})* L- \1 `) R$ Y9 z6 S1 U  `
    @Retention(RetentionPolicy.RUNTIME)
    ; Q% L1 ~* x- q$ D& q@Scope(BeanRefreshScope.SCOPE_REFRESH)/ n% X- Z$ h7 @
    @Documented
    % H9 ^3 M( a6 y2 gpublic @interface RefreshScope {
    ' j; s( }* v% A) }5 y. q. h    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1* x- E  b9 K9 J8 j" F' z- F
    }! T) C9 z9 R/ R9 ~- B
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置, j% o/ p6 g6 x# ^- F5 `3 ~' w: @( P
    0 O! {% |. k: R! {
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS2 X0 E! Z: t$ {: S# m

    : ]8 L) q# g/ w  ]* S% `" x4 T这个自定义Scope对应的解析类
    " U3 u4 G1 Y( e& B; D3 t! y
    4 _. S: R  {+ S2 @' M; F* u" b下面类中有几个无关的方法去掉了,可以忽略
      U# @$ e0 y3 |% e
    6 k1 c8 V) u% C0 j/ a' F& f/ t5 e4 rpackage com.javacode2018.lesson002.demo18.test4;
    5 C, }& |3 g' `* S  Q5 b; b3 `" X7 d  h2 D* Q+ e% ~

    % d  j; n. M; ~) }import org.springframework.beans.factory.ObjectFactory;( Q. ^* ]% ~# [
    import org.springframework.beans.factory.config.Scope;4 W( [1 y3 I! S
    import org.springframework.lang.Nullable;1 d3 v, l: s5 G5 L- Z; Z) l3 V

    ; b4 f( L1 H" Z6 f2 ^import java.util.concurrent.ConcurrentHashMap;
    ! h4 f: N% @( |4 l9 P/ S3 C/ |# _4 ?  y% j$ R" E
    public class BeanRefreshScope implements Scope {
    " [. J  Y" `: P. y. M6 v. t: ]% J5 d* E, a8 t) K
        public static final String SCOPE_REFRESH = "refresh";
    ) `+ x8 w# l, a0 G. q) [# H/ N5 N& b) s0 y
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    - ?% f% t  K$ J: N$ ~! {! w% n& q  p' F) m* S
        //来个map用来缓存bean) X: [- Z% X0 G. W- u( R& o! D
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@16 k6 x/ y: i9 x! ^: f

    ! f% L, ~1 i& v    private BeanRefreshScope() {
    ) h6 G+ s* y; A- S" @4 x" g7 w1 P  ~* @    }. ^1 m& V. A9 ~7 P
    , m% H" E' ?8 ^" w7 N
        public static BeanRefreshScope getInstance() {6 w, d, I, G4 h/ y7 \  O- K
            return INSTANCE;# G1 M- n) O! J* O. n! Z1 \
        }+ q3 e6 ~8 g; O; f; j# L

    # ~' A* \* w* U0 }4 `! d    /**
    " T) ~& j- R9 k7 L1 T     * 清理当前# Q+ D5 B8 v. q3 s4 Y: K8 E
         *// N) p+ S: C3 ]
        public static void clean() {
    : x) E+ M) {/ v" Y% \" H6 u1 m        INSTANCE.beanMap.clear();  ?' A8 [% K9 u& [& l
        }: F: Q% ?' s3 a

    - G! h2 C% e0 ?' d+ F! u0 F/ U8 s    @Override
    , G, I5 F) T) }+ E1 f    public Object get(String name, ObjectFactory<?> objectFactory) {
    * k! F; h) R% e( e6 P% }, a        Object bean = beanMap.get(name);" C7 B1 a( s; o
            if (bean == null) {
    ( I& T, c& p0 U+ b; t6 P            bean = objectFactory.getObject();! q8 W, b, B& S) g" O  Y
                beanMap.put(name, bean);' b5 u7 c8 c5 d: J" ?" N8 l8 P
            }! v" P: _& O: y' p9 d9 e
            return bean;  J2 ~6 L0 B2 n. s
        }0 J1 P, t, X8 q- F( f1 Y
    8 ?( k+ k$ W$ P# e6 K; ?1 q
    }
    9 Q$ M4 @! [8 _/ x3 L( A& h0 _上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中/ Z8 }% `6 Q  h: @
    * ^- B$ A: D% |
    上面的clean方法用来清理beanMap中当前已缓存的所有bean7 O) U/ s1 A$ l. J3 y* ^1 a# @- B

    7 b) L! D- j. O/ [3 ~% ]0 I来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
      Z1 n8 W$ u2 r/ [5 i$ G( h
    * x% \+ Q# O& m& J$ j$ X  Z5 tpackage com.javacode2018.lesson002.demo18.test4;
    ) G$ L% o' _. ]' C* N* e
    5 B' V3 D( b0 ]/ g' E- _- vimport org.springframework.beans.factory.annotation.Value;
    ! o$ D3 o9 _7 j3 Mimport org.springframework.stereotype.Component;
    3 Q6 u- d1 e0 l: H7 y/ H2 G. R) c
    4 c) Z! k% d& _# y& d/**
    ; K4 C  u) v- A * 邮件配置信息  \7 R( ?0 d" [& U0 U/ Z
    */
    5 H* f& i; M) O8 J3 s@Component  ?5 M% I. @( a
    @RefreshScope //@1
    & ~2 U- g! e* o* ~0 m4 V* o* m" tpublic class MailConfig {9 L" P3 b1 E0 I2 @6 B; g6 \* o- s6 p

      f: @. |$ M* p% M: I7 b! }    @Value("${mail.username}") //@2
    9 Z2 H1 \: n6 |- F" b3 U# Z    private String username;2 H( }2 Q; [4 d9 B% U' b+ D& F

    4 `6 P, p) U1 C- v/ n. k7 f    public String getUsername() {3 M" u( w- o! c! a
            return username;; c7 f3 f  a! y6 L+ u- V
        }
    2 c. E' H9 R# O" g
    # I7 R# ]$ C6 W" R+ u$ u1 @- ?( s    public void setUsername(String username) {, G2 h% ]/ `: x9 T! ]6 O
            this.username = username;
    3 F( i/ I9 i0 ?. ?, e    }6 Y- @2 z# S0 \6 u9 D( V/ p: b
    ' P1 S8 [9 e2 H3 t
        @Override+ y6 A; {; B- S+ j6 R- f1 k
        public String toString() {
    7 `) J" s3 F, }4 E1 N+ o% n        return "MailConfig{" +
    8 O3 V# j2 h  _" u9 j% g7 O0 ?0 w                "username='" + username + '\'' +
    " C% D3 ?$ }6 C8 |1 P5 a/ x                '}';
    2 q4 W5 e+ B0 T, i$ `0 `    }4 Y% q- N& H3 U' n3 w9 H. `) s
    }
    & Q( L6 u* Z, J; U1 S@1:使用了自定义的作用域@RefreshScope
    2 r4 m2 u$ E4 _! }+ k3 [* O! a6 v/ a! X: }/ n+ [5 p
    @2:通过@Value注入mail.username对一个的值
    ! ~: I4 y) J0 {& i, o" U& x2 _/ b$ ?% l! j
    重写了toString方法,一会测试时候可以看效果。
    & |& F9 Y1 ^: e! v! j) `, Y5 l# y, S. ~7 \; t4 O& W$ i9 v
    再来个普通的bean,内部会注入MailConfig5 ^3 i7 o- @3 I. z
    3 }- a% p' d+ W( ]
    package com.javacode2018.lesson002.demo18.test4;6 D5 p, ]/ T" ]

    6 ~: x/ `6 b+ y9 Z. Gimport org.springframework.beans.factory.annotation.Autowired;, s7 [# p: g2 T/ ?9 n/ Z: |
    import org.springframework.stereotype.Component;$ c4 C. p/ N6 l
    ! c5 b6 r2 l+ P( F; `$ u
    @Component
    ; r3 s  U& G$ b' O7 F/ N& Cpublic class MailService {
    $ D. I+ ^7 f' p8 ~+ P$ r    @Autowired
    % L' K; B! V% {    private MailConfig mailConfig;
    5 Z/ l% I. M3 L, W% V+ d8 q
    / t( `3 K( H. u9 D+ S5 Y) J( X    @Override
    , [# S% S* E  j% v    public String toString() {  z! F+ s2 d% A# T/ q; K/ F
            return "MailService{" +# x4 U$ o+ Z' @# L: P. s9 I/ t7 p# q$ }
                    "mailConfig=" + mailConfig +3 x9 D0 L. B6 D2 @7 x/ G+ e7 R
                    '}';
    5 ]6 g3 w& z- Q* k    }
    ; K, f& L& J. h}; _% C! {) K! P
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    & F: `9 C  l. u% R6 ?  U3 a7 d$ b' h6 C
    来个类,用来从db中获取邮件配置信息0 H1 K8 ]& s3 V) P# J/ G* N. B

    & X7 u& ]4 h4 E" R9 O4 b* ipackage com.javacode2018.lesson002.demo18.test4;
    # L% W; u9 O& \7 f4 A- o( W
    7 B5 Q2 X4 x! q2 ?# d' L# t! jimport java.util.HashMap;$ }0 K1 x4 L: _- z
    import java.util.Map;
    6 O% A, T: C) O* l, j# z; Iimport java.util.UUID;+ t" Z  W8 a! o7 b2 K
    8 _, b+ \3 b5 [. `
    public class DbUtil {
    9 l. E! J4 O* R3 X2 e" s5 y# [! k    /**7 M! k( t$ B( c# j( p- r# C
         * 模拟从db中获取邮件配置信息
    8 E& T! M0 T9 _     *# i* s) i$ R" z# }6 `5 R
         * @return
      X+ C6 e- f" L, i     */
    ' L# m1 _$ M6 j" m2 P8 G3 Q; V( C    public static Map<String, Object> getMailInfoFromDb() {8 {( l$ z5 j( x
            Map<String, Object> result = new HashMap<>();
    ) w: m: [: b6 |# N+ v* P        result.put("mail.username", UUID.randomUUID().toString());
    4 B/ |9 g% k7 v' p/ Q& b        return result;
    $ ?4 E, ^3 n: `8 {4 ^    }
    ) O6 N8 D2 b, e}9 G% F' [+ f  z# p
    来个spring配置类,扫描加载上面的组件
    6 a+ a' R$ ^$ A- ~0 Y* M2 u; \! _6 z  {: {. n: n
    package com.javacode2018.lesson002.demo18.test4;
    ( t4 ^" V$ @9 i# p# {; u- {0 w* A+ q
    1 `, m, ]9 b) H1 |. eimport org.springframework.context.annotation.ComponentScan;) H2 v* V3 C# H
    import org.springframework.context.annotation.Configuration;
    % w( P% H* @0 n. Y$ u' T* P* Q9 |( G* F
    @Configuration! M$ F# m- w! H- u9 s
    @ComponentScan
    ! p3 }' f+ u: e3 e2 O0 Cpublic class MainConfig4 {
    & e) T9 @8 U) \- E1 P( M}4 u# q. ]/ ^3 y6 c" A
    来个工具类
    6 H% G# m7 ~" |2 {$ a# \) {- A7 g; m
    内部有2个方法,如下:
    % [4 O/ Y7 L! V; B
    5 z! R$ [* a* U. V4 h0 p* e( Qpackage com.javacode2018.lesson002.demo18.test4;
    7 ^( B$ l) w' Y! a* T
    9 E( t4 h8 x& n5 O( ?2 n4 C2 oimport org.springframework.context.support.AbstractApplicationContext;9 _4 C( h9 ?4 T% u  e4 |
    import org.springframework.core.env.MapPropertySource;
    " r9 i8 x6 p! s  r: n9 n# ]8 _
    0 f( `8 `0 e9 E  r. [& U" W4 Iimport java.util.Map;
    6 n3 n) ?# ^/ h; l$ f7 N8 |
    ' V! G' A9 ~( U! C* h8 R$ Q0 Y) V4 rpublic class RefreshConfigUtil {
    9 n+ H# t! I+ @7 d# T9 f    /**, u1 L2 X' O2 ~2 \- L  a
         * 模拟改变数据库中都配置信息( g, Q$ z: S/ W/ h( _- C
         */# b# Z$ \& [( ^
        public static void updateDbConfig(AbstractApplicationContext context) {& T% _- u9 J' e/ C8 i
            //更新context中的mailPropertySource配置信息
    0 v/ @  `& @/ c7 g; r$ S        refreshMailPropertySource(context);
    ; S  i. y0 e: a3 }; r* ]8 v: D) N! d9 M9 @9 f9 {& z& O
            //清空BeanRefreshScope中所有bean的缓存
    , [  A: f5 T% y8 M' P2 T* k* X) K        BeanRefreshScope.getInstance().clean();
    ( h1 R. T, H/ v5 D  y    }0 x7 f( j2 U/ K9 m) j3 ^
    " O/ r7 [; y# }
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    - W" ~, I, O, K. |7 y$ Z4 [        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();) X0 n7 m0 J  Y+ |
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    7 [2 o# m. T% d( h( P        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);7 A) U, g- I3 }4 e$ U) W- Q$ ]- W6 Z
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    0 M* y4 ?5 @9 x5 z0 i    }
    7 E0 G6 P6 w( ~, f5 q( P
    2 W4 r# j8 [2 Y+ l5 {; {}+ [! m: P7 E: \1 f) S
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ! B- V* G7 p9 u& o; b  }3 f8 D- }* [8 s0 k& |7 ~" g0 I
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    # C& ^2 Z: k  H6 `
    / ~! ?: {8 k/ [- C; [7 ?  s2 @来个测试用例% C" q! G$ W& e& A  T0 q# Q$ y

    ( t3 @$ S7 I. d2 l1 A& y@Test
    6 d: y$ D+ G# T( k8 z4 G  {) Bpublic void test4() throws InterruptedException {
    ) j& @; e! x; o- [, E    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();2 V0 o2 A) O& G  P
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());7 h. x7 @( A, o2 c- n
        context.register(MainConfig4.class);
    " C- B/ \7 x, _; g( r    //刷新mail的配置到Environment
    ; ]. g% z. D8 H: ^! s    RefreshConfigUtil.refreshMailPropertySource(context);
    + ?/ O* d$ }7 e    context.refresh();% L) Y& o/ ~- }8 E

    . Z! ]+ t9 Z- [4 O" y2 _    MailService mailService = context.getBean(MailService.class);
    % G  @$ J  q; Y5 u5 M- j- x* B    System.out.println("配置未更新的情况下,输出3次");
    ( T% S5 i% o& ~: L* L    for (int i = 0; i < 3; i++) { //@17 W) J( t$ x5 |! Q
            System.out.println(mailService);" y' E& j& D6 Y- X* `$ B, x
            TimeUnit.MILLISECONDS.sleep(200);( L3 r& i" J: E- b) X  S
        }" R3 O$ {- Y9 N7 V

    - O0 l9 `1 y& t' w* W* ^    System.out.println("模拟3次更新配置效果");
    0 Z2 I- ~! w( m    for (int i = 0; i < 3; i++) { //@2
    & V1 N( H% P% g        RefreshConfigUtil.updateDbConfig(context); //@3
    ( |" C7 c  o9 W5 ~2 w! E3 x. T        System.out.println(mailService);: q: A; @5 j" N( ^, Q
            TimeUnit.MILLISECONDS.sleep(200);
    ! p& l* M) n2 J6 ~    }- g( I# L  Y; j) A) z
    }2 J3 n9 b8 d; S" g9 ]5 Y
    @1:循环3次,输出mailService的信息1 m3 O3 A2 O  K# s8 n
    1 ?6 m) M( U0 {: s0 J) L
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    3 ?+ j, O9 t6 T2 O' V4 t" z% ?) x, @2 m5 E
    见证奇迹的时刻,来看效果4 _) ?) x; n, j3 D( f" @2 m! |
    : ~; t/ H4 a/ K8 D: U) i" k
    配置未更新的情况下,输出3次$ i( Y% g& [7 P. i9 x
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    / F1 b/ e+ F( p7 ~! k! `MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}3 J7 p& m  B2 F
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ) m9 T# w8 r1 M模拟3次更新配置效果/ h( i) J! F$ V) ]3 @/ K- O
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}' O6 z- y& G/ x/ N. b7 Z; u9 E
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}9 H4 y$ ]% `9 z/ O9 |  m4 ^
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}0 }$ d3 m0 H4 o/ n) S4 }# A. l
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。* Y3 u! V3 @: k

    8 ]( J9 f! N- h1 P7 E小结" \, [  C- \4 s% K1 c- S

    $ J; @2 I/ E4 B动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    " T* e4 I  o. v& r+ Q' q* w: ?3 T0 c
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    7 C$ L2 h4 k# ^( `+ v1 `
    4 o: R9 p0 O! l& J# m4 c总结
    % j* R7 _$ v* v% D) X% W/ s& }& b1 e: H) w3 j$ }5 ?' o4 ~
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    : E- S  H- N) \- t3 G$ f
    ) a8 Q0 Q3 `$ C9 C$ l案例源码$ i4 j8 g0 E7 o/ `: |$ ~6 i9 ~/ ~
      z5 W) [. j& N) j+ P
    https://gitee.com/javacode2018/spring-series
    / b* f- e. u% K5 R路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。: p% `* o6 J  Y  E& F, y- X9 |
    - K0 z' |" ^9 J  Q
    Spring系列0 ~3 t! V$ |0 s+ \! z
    3 X$ a$ a: ^/ c2 M" @/ z; Q" X
    Spring系列第1篇:为何要学spring?7 O4 |8 ?$ a8 B7 h; Y
    ! ]& v! J+ c- k6 t8 R! x
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI); C# j( y8 s+ b4 o) _1 R
    7 D% N# v) k0 q  }  O1 L
    Spring系列第3篇:Spring容器基本使用及原理
    & Q/ D. u& e. B  j, w( c) h& k! y. p8 M8 v6 {
    Spring系列第4篇:xml中bean定义详解(-)- w* w8 W  Z3 w8 _  J% P% C

    2 z& `" `, w0 O' i  [Spring系列第5篇:创建bean实例这些方式你们都知道?3 l6 A' z2 `  j2 j0 d) p: @
    ; }8 n- q- \- X& Z3 R2 _1 ^0 J  P
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    : j: \+ H9 Z7 k0 o3 t) ~5 s5 t  m  B  }2 P: H; j" m
    Spring系列第7篇:依赖注入之手动注入4 i! `- o/ {7 |7 Q: f( _" w4 E$ a1 ]

    4 {8 F! ^& V0 L+ r7 H+ B( P; QSpring系列第8篇:自动注入(autowire)详解,高手在于坚持* `. Z9 b4 R) e" D6 ~) T

    ; D& K, B+ o7 V1 fSpring系列第9篇:depend-on到底是干什么的?9 H# G5 h3 I9 G# Z

    ; i/ ~  S! F( }' B* O5 C& QSpring系列第10篇:primary可以解决什么问题?2 n! ?3 q- C( I1 q! f7 K1 {8 L: `

    , g4 ^# g5 S$ ^Spring系列第11篇:bean中的autowire-candidate又是干什么的?
      _: B/ G$ t9 m9 W. Q3 h
    ! Q/ c( Z5 j4 N! i( R5 mSpring系列第12篇:lazy-init:bean延迟初始化! [2 F; r9 R* |: L) Z/ x

    # ~% I2 [7 t% |: PSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    ) \% `( K3 M$ a7 x7 x1 C
    ) K( T, q6 k3 h" r1 K$ r/ [Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    . O4 o3 @$ I9 X9 C% B' o' I2 M1 }
    8 `& v' ~8 ]5 K$ MSpring系列第15篇:代理详解(Java动态代理&cglib代理)?. _! C5 S# a2 v7 r

    & ~* b9 Z7 D7 P* t. CSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)  I% }" _+ V9 h* [7 [
    ) J9 g1 Z( q2 m7 L. g! s$ g
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    * j% i; ]+ c& j! l  b* _  `: X* ]6 g- M
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)" ?; N9 z. S& B' x

    1 H6 u  `. e# y" _0 G, J' w+ z! u: lSpring系列第18篇:@import详解(bean批量注册)
    0 R& o+ u1 ?+ \/ O- L% W8 i. Y. P' C$ ~, H
    Spring系列第20篇:@Conditional通过条件来控制bean的注册% w+ P: A0 H( u, {  I4 }
    ) R% i9 P% n6 Q3 o& b3 p1 X9 t  D2 B
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier): t' x4 T. \6 n7 _$ k$ T; x
    $ c$ ~4 \" U# O. r
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    # t8 b+ r% W* E( f
    : t% L+ I  M8 r2 m' x- }5 HSpring系列第23篇:Bean生命周期详解
    7 z1 y5 _. s1 Y7 j
    7 v6 g& _( D- b& z% BSpring系列第24篇:父子容器详解
    # q; R8 S( \1 @1 I9 N: L: i1 \& c9 \0 U  u+ e% q8 i) f! b6 |
    更多好文章& e! ~$ T; Q$ Y" |. l
    . |- ~0 \4 N5 T6 p  S6 h8 I$ m
    Java高并发系列(共34篇): n9 V1 O5 c+ X7 j2 [) M7 Q+ z; h
    6 b1 G1 [! |7 N
    MySql高手系列(共27篇)
    4 L: e' Z% l8 G' K! _% ?3 z, ]1 j. U4 A' a; V
    Maven高手系列(共10篇)
    3 m7 _3 `; p  L; |/ ]& k4 z4 u) M* |% n: e
    Mybatis系列(共12篇). E+ u3 i* N- t8 E6 M, u
    * W6 Q5 b8 G% y: n3 M2 y
    聊聊db和缓存一致性常见的实现方式# X7 R( E! w7 Q' Q# ^1 e: Y4 K

    : a( G: }. n3 I. f, z- R8 N& }/ b接口幂等性这么重要,它是什么?怎么实现?
    0 m3 P- I+ ^+ K" h' ~! n
    ! [8 [; o: |6 _, \& v- D0 M泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!( q: L8 m$ P! {& U: q; n& H9 w
    ————————————————* F4 m: e2 j4 n6 R3 H% y
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    8 d/ c4 r1 ~/ h% H! _原文链接:https://blog.csdn.net/likun557/article/details/105648757( R' x( b, |5 S5 u  u7 _
    % ^  s6 r3 U4 F/ k. y

    " v: }6 }8 G# [' v/ J/ e) ^
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-7-27 01:40 , Processed in 0.444592 second(s), 51 queries .

    回顶部