QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5397|回复: 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!$ f* Z/ Z: v) y; L" U! }0 B/ K
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!4 f$ y, ^. d7 f1 Z; O3 R; O3 n$ I4 u

    9 H9 a, ]8 k: ]面试官:Spring中的@Value用过么,介绍一下) ~* e7 H, I5 O3 f+ T% S! P4 o; \2 U: b

    ! C  X! p1 B) `3 k: l: `( X我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中6 h7 J2 l1 l+ p3 P' I9 V

    6 g8 X- L. W8 j面试官:那就是说@Value的数据来源于配置文件了?3 }7 K" {( ~  t2 _' a9 Z/ h

    # r' T, |. G8 X, {5 @我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    . l2 ?; V2 w  M) [) e! T8 c4 V/ J4 {  J2 H7 }+ a9 J+ `
    面试官:@Value数据来源还有其他方式么?
    # [, ?7 x3 |; [/ N% G3 |' [! P# y# }4 p4 w* U5 \
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。! Y! i9 R3 C. N0 J( f" M
    + h! M# c+ M" N) k0 F
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?- ^; c/ ?1 [& s" g% B9 \

    + g3 O8 @0 h7 T8 t  u我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧" V" `- e# P: i9 e$ `

    3 _) n! u9 ]# u. }  I6 Q面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    " @& Y6 z7 u6 @4 L9 w+ k& e2 ^' c4 Z  k  P. H1 G& s% I+ O
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能& c5 b2 x. z9 d

    1 N) ~  H- C( K3 S面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?; l) a) U: y, U3 H+ ~1 k

    - m  m) Y3 |8 t0 C7 S. M我:嗯。。。这个之前看过一点,不过没有看懂5 g" g/ E$ v2 G  Y1 v9 L7 s

    2 I" F! L7 V$ R9 K2 v面试官:没关系,你可以回去了再研究一下;你期望工资多少?7 i/ D7 q' K- c2 n; m9 _" y2 E

    0 i. W) @3 S0 y% z- W2 W! h我:3万吧% P. [( X  M& t0 V) m
    * e1 L- o9 @+ `! l* v
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    5 t' L& k8 r3 ?) P* B  w/ p  s& v; N& Z* T! @& U
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万% c/ R' h8 [) s
    3 S3 m/ S0 r2 a% |3 I
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!- n& c: b$ U# Q1 Q# p9 E

    ) t  c; [9 o8 X我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    4 D1 ]: T4 J% h, w! W2 t2 ?/ ~* R/ p( |# d+ k% ]7 j* }
    这次面试问题如下
    " h4 D1 U& d, J0 S* o' h0 }/ X
    / i* b/ X4 O. t@Value的用法
    1 t: S8 e, a+ M
    + V: K+ ^5 E4 |@Value数据来源* k* v5 L* g" a) S' ?. [% F$ h% c
    ) b8 P2 [! u% y! s2 z
    @Value动态刷新的问题
    0 K0 w; s5 E, H2 V' I- H- M1 J! {3 q/ h
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。, p4 ?- a5 @9 f: t' R

    . F7 X# O: c0 ~' ?( e2 s@Value的用法( G  W2 Y- Y( g; ?; r

    3 d. z5 Y& |6 v% q系统中需要连接db,连接db有很多配置信息。
    $ c" p$ w8 ~: g( t* z- \2 P+ c; y
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。2 x- ^7 m' p' L3 B. Q3 ]
    + {' {. Z/ S  u( C7 }2 r
    还有其他的一些配置信息。4 X4 }; k5 Z# q& z7 `+ A
    ' ?3 Y9 g1 n" n" S. d3 v
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。7 f) l# L( M. D9 D; F

    9 G4 ~0 g7 N- L7 S那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。. A, V5 y; Y  l! I2 q
    4 _8 G* l. h) V8 A. i% U
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    : v) U. @: K; n" z( O" b; Q) G0 R1 \! ]/ P
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    # Z; R( K. W' @& f( E0 o
    0 W( T# _; O/ U@Value使用步骤
    1 t" j) ^9 `3 G9 N; |+ j& T6 @1 @4 g3 ?: _1 G/ H7 P; v( M
    步骤一:使用@PropertySource注解引入配置文件
    0 U1 E4 K: H4 p9 d( u! A
    & D2 x) C  m% ]! @4 G4 B* W将@PropertySource放在类上面,如下
    7 k( D  j& G1 ~- }' L8 y- @/ Y
    " n8 }3 b& n: Z' z@PropertySource({"配置文件路径1","配置文件路径2"...})5 Y& S! R. |/ W2 m
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    * v: K+ r4 d# D6 g' R6 h
    - S1 v0 a8 O1 J; ~如:: w) M. T* }) ~1 ^2 ~) s

    9 e9 c' d- G0 c4 I@Component
    " `' e+ L& F' x' G% C9 }@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & a& `( Q% Q+ ypublic class DbConfig {9 o0 I% l$ Q% v+ o4 f4 T
    }
    ) r; d7 @* V* Z( J; L! _# J步骤二:使用@Value注解引用配置文件的值
    6 F; {1 R  y- V1 i! p5 F! w4 @4 X4 `. k% \
    通过@Value引用上面配置文件中的值:
    4 x0 Y5 k% C4 b$ ?. E  ^
    & [( B; \% O- l语法: |) ~7 P- a3 |' V/ G
    $ W, w2 c+ |: E! J
    @Value("${配置文件中的key:默认值}")6 t* _' ^' f! N5 d
    @Value("${配置文件中的key}")0 Q4 P+ X$ a* M" s. c2 ^
    如:
    , d/ h2 ^7 e/ @1 C9 a+ m( S- b7 `! k& w+ j- b
    @Value("${password:123}")
    . V  m& i1 V- C2 u, q2 u上面如果password不存在,将123作为值
    7 x, [0 H, K, J  h
    1 P6 M5 Y0 M5 n2 |8 G9 d@Value("${password}")$ E+ N8 ?: M5 L  X7 L  |
    上面如果password不存在,值为${password}
    9 {% Z* h6 z. G' F% k. e
    ) @4 b  x5 j7 O假如配置文件如下; {, S" L# M1 R. I( r

    * W7 B% s; w  C2 Q4 _6 pjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8! [# ~: f. M3 N: q4 z5 ?( a$ _
    jdbc.username=javacode* `  W. r- c! w, }7 o' E3 ^
    jdbc.password=javacode* i: q% w) `7 Q  L+ P( Y4 w. {; O
    使用方式如下:
    5 c  m1 \: E5 |. H% Q7 w6 R* W" I. ^/ X6 X1 a* u$ n6 m
    @Value("${jdbc.url}")- A& t% l# a" b% t
    private String url;
    * }7 y& _0 P! e- W7 j. |/ L; U1 D5 I6 _4 Z* v8 S9 |- {- P
    @Value("${jdbc.username}")% n8 r3 _" K$ q2 l( ~
    private String username;
    - f& x) d$ [/ j' A5 O$ e+ U  N, W
      L( [; F- `$ v& r8 f; G. H1 T@Value("${jdbc.password}")4 s* m  E, a! J7 V( Y! X/ B
    private String password;2 q  d; D" l, X* o7 e0 a
    下面来看案例
    - X" D7 s7 _/ L' }& S
    # b+ B: u+ R1 ?案例
    3 b4 m+ C9 S! |" [+ e4 ~% r9 Y; `
    * _, m8 S5 C4 G5 X% W" Z来个配置文件db.properties- L7 V8 j  S$ C$ g& O' n6 N  n
    # E& b# J8 W- L: B4 F0 ~4 Y* V
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8/ M* g) {2 ]8 S6 Y6 g
    jdbc.username=javacode
    ) V& q# ~- A; G# pjdbc.password=javacode; j8 y, F* D( U9 o# L
    来个配置类,使用@PropertySource引入上面的配置文件* G$ C& A8 U$ F# i6 U

    : n/ \. D% a9 G" L7 mpackage com.javacode2018.lesson002.demo18.test1;
    0 W: d# @3 ]: j0 Y/ t" w* L6 x: C0 X
    5 g2 g: P8 o+ Q# Vimport org.springframework.beans.factory.annotation.Configurable;
    6 V. ^7 q  j3 a2 R. E: qimport org.springframework.context.annotation.ComponentScan;9 w6 c) c3 T" a6 ^- Z: [/ @( Y
    import org.springframework.context.annotation.PropertySource;
    3 D/ n* X; V! ]+ f0 i; X: n1 p
    ' _. e8 t  h2 S* G) N4 ~/ X  V1 Y% R@Configurable
    " w1 j- s$ ]& t@ComponentScan" U* s% l, x2 N) _6 S' V* a
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    ( j) C9 f( |  xpublic class MainConfig1 {/ z0 p5 [2 B4 K5 v! s  B  b# }
    }9 W7 M$ x3 C! L# \2 O+ d! V9 i% {
    来个类,使用@Value来使用配置文件中的信息' X4 M; v, W: n1 o. m6 P

    9 I1 \! {0 G& a7 s! npackage com.javacode2018.lesson002.demo18.test1;' I9 I3 x+ R5 _9 X; C% S% c# `
    9 ^. C# f5 _* N1 O& T# A
    import org.springframework.beans.factory.annotation.Value;0 \# {- R+ b- B, i# V
    import org.springframework.stereotype.Component;
    , t+ T/ P; Z9 m2 s' \9 `: @8 \' }0 `0 ~1 W: V  W1 A
    @Component
    % l1 T4 x8 x, c0 t- x! vpublic class DbConfig {, {+ s3 c, |, A: a0 Z

    ) }0 L' ~5 S- c: m! p1 o% e    @Value("${jdbc.url}")  k, ~& Z7 ^3 F5 e, O$ v
        private String url;( i. u, ~: q* Z
    3 b7 R# Q8 t: m* k9 q, L- e) g
        @Value("${jdbc.username}")
    ( E3 M  `' t( N; _6 N7 X1 E% ^    private String username;% ^2 r# c8 S+ L  e

    2 |( a3 \5 D  j/ |* K+ w' `( m    @Value("${jdbc.password}")
    : J# [! Z7 H- a$ u* V  R    private String password;
    1 e6 Z+ u! U( L* u8 G; X' [% i- p7 Y0 n8 L# a
        public String getUrl() {4 @- Z: \0 M, m2 M7 Y
            return url;
    0 N$ @9 F6 P/ I4 j' K, {    }( P, l0 W/ S6 \  e. T+ \* J3 d) L

    ! G2 t# I; W; q; j9 P6 y    public void setUrl(String url) {. c% j. V# |7 L5 I& ?
            this.url = url;
    / _1 K, \- y! [8 [    }% B8 D; `) ~$ s7 n! ?7 I# c

    3 C6 e7 e8 v8 ], x0 G    public String getUsername() {
    0 e! h/ y( v/ z8 N        return username;
    5 B, ?. X9 E" |, o/ t    }
    5 v' n6 b  I5 s& m$ a  }
    2 Q% L/ k" @4 T6 V, {    public void setUsername(String username) {
    ! e2 D% N' H% ^, T        this.username = username;
    # }9 K2 C, N8 w. P    }
    6 u+ {" ?1 x+ Q# _9 M9 }% o$ z' f
        public String getPassword() {
    5 m& D2 {2 x4 g% H+ m: _/ P        return password;
    ) k* |9 t! t3 e5 ~# O( }    }& o) x, Q, f5 O, Z6 ]. {
    # s$ T. p4 {' A/ P+ J9 w
        public void setPassword(String password) {
    4 V: V9 O/ \" Y% g7 h% b4 J        this.password = password;
    ' d2 k. t+ S1 s9 F    }
    4 d  D) n  v2 v% K! s+ z' A
    - x8 o, P* {# v  s# {    @Override+ Z6 Q$ d2 A6 j" p6 j
        public String toString() {
    ! o, o, `. P% b; d; q        return "DbConfig{" +
    & _8 N9 D; [/ w7 P7 x4 k                "url='" + url + '\'' +) L" n. F3 p% }$ p8 v& ?
                    ", username='" + username + '\'' +6 f- n1 |- D; t( e: @$ N
                    ", password='" + password + '\'' +
    / u2 v& l; F& a. ^. G                '}';1 a9 u( f3 ]+ }* ]1 a0 n
        }$ I$ D1 E8 V' d% G
    }( ~0 ^) h& n+ f7 a* n. b% g
    上面重点在于注解@Value注解,注意@Value注解中的& b* H5 ]8 L, S! m+ U0 C
    % h2 T2 R* K* a/ Z% U. J
    来个测试用例
    & I' P0 Q7 p" ^% G, \' T4 G# M% `1 j# C* u0 O1 r: S9 ^
    package com.javacode2018.lesson002.demo18;
    + m1 o6 u7 q/ B' g: L4 Q! F0 i9 U) k
    import com.javacode2018.lesson002.demo18.test1.DbConfig;/ O7 B  G- C1 @# b9 S) H
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;: Q7 ^# z  a1 H) a8 G4 _3 C9 {; X4 [3 J
    import org.junit.Test;
    ! y% H; X* ?9 x" Fimport org.springframework.context.annotation.AnnotationConfigApplicationContext;' m: h9 U2 d$ b
    8 w  A% i9 M" M2 p
    public class ValueTest {
    : Z) M, e$ K8 C, W2 @* f) C3 f% W# `4 n! v2 Q$ ]
        @Test% T4 i6 D" ]/ N" f/ t
        public void test1() {
    ; Q0 L. J9 l0 U; e) v: t* N        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ( F- G9 M* O2 a4 H8 \6 f. q5 G# c# V        context.register(MainConfig1.class);, T' ^# L5 C' k: u, ^: n1 m* x
            context.refresh();8 P: X# g: n$ _/ e. a

    # x5 Q& j4 l2 p  ^! H' V8 Z        DbConfig dbConfig = context.getBean(DbConfig.class);
    8 e% b/ a1 X5 {0 a$ [* ?        System.out.println(dbConfig);7 f7 i4 ^% p0 ~' K! L9 {  A1 S
        }
    1 v' j' P5 D; j0 X$ R3 u}
    - ~! T7 i7 ?5 i运行输出9 q( X; {3 s0 p( e

    2 q5 r# Z" f7 @3 g7 WDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    * m, Z+ n" E' r7 Z' H上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。- v: o9 T8 H# [. ?

    0 q' d; ]- E4 x0 o3 i@Value数据来源
    ! K. U$ x; k' K7 _4 {
      n6 ~' h" _* P; R' a通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    ( b; H( U# T6 b$ n/ ^2 h! c
    6 k* j- g8 s0 Z' {我们需要先了解一下@Value中数据来源于spring的什么地方。
    , G1 n4 f% F5 ?! y
    3 z  Y5 w# V# y" A  lspring中有个类/ y5 |( m$ L! V4 c  o/ `$ H7 Y  J
    # _- y5 a* D7 ?7 c- r; Z7 K0 w
    org.springframework.core.env.PropertySource
    ! u. p: T* J+ Q$ n可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息0 a0 A* M5 s' `# w
    - b; K+ t- _% r7 D5 ~4 ~
    内部有个方法:
    " w( M. X/ V8 C
    3 V; W; w5 V5 L1 O; \& |public abstract Object getProperty(String name);
    8 T0 H" c9 U) Z# R" _# B通过name获取对应的配置信息。) j5 p) c- j2 {& ~' b" v0 U" A; r

    3 F# X) D. D( K: g$ L& S* i系统有个比较重要的接口$ U% O; t7 G# u
    & p, ]0 I3 N+ U  W0 k$ Z& N- l) D' M5 H
    org.springframework.core.env.Environment. u# J+ |, M  {0 f7 B
    用来表示环境配置信息,这个接口有几个方法比较重要" g8 S# X' c' c  `2 V- [) T: \0 {

    ) X' O, l' U2 zString resolvePlaceholders(String text);/ t% k6 q* h, i7 ~
    MutablePropertySources getPropertySources();
    " z: w1 |/ L+ wresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    ( e% t# A) _0 Y7 c
    ( ^4 \- `7 n& ?+ l9 Q- FgetPropertySources返回MutablePropertySources对象,来看一下这个类: h+ T# K2 v  m  X7 \& j

    ; J" [; g' ^6 H) R9 fpublic class MutablePropertySources implements PropertySources {
    : Z7 `$ D! `# d
    / Y7 u  ]! P8 @5 {8 k+ o' r0 ?    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ) P- f- x* ~( l1 S# j5 m; U3 C6 |% H* O% r% O0 J
    }
    7 B2 C& W6 K  ^8 N: H% I' h/ o内部包含一个propertySourceList列表。
    : h, ~8 G5 {4 D/ y6 G- M1 [3 W; }; e" ?2 v0 |
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。" ^- B  h9 t* ]& j' _7 W4 o

    9 L5 R: e% \/ d9 `% s  E7 g+ x3 }' ]大家可以捋一下,最终解析@Value的过程:
    . g6 ?# I4 \( H- S% K& ~2 b# A+ s' D" G7 F  a) A8 {( U
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
      r  }% w  Q' R6 F3 W% k, o2. Environment内部会访问MutablePropertySources来解析+ b4 |, x# R1 z: R  d; u7 w+ |, O
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    1 g8 l4 x% _0 m  T9 y' P通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。( w' l- D, i6 e( u! w( v# L

    6 c: M1 P$ y& h  z7 u下面我们就按照这个思路来一个。3 U) v% c, z% g" t! i

    / p0 E7 H7 o: i4 g/ g. S: J来个邮件配置信息类,内部使用@Value注入邮件配置信息2 \! C4 c: u7 T# R/ q7 G

      E( I7 X: r# `0 i1 V6 r6 Dpackage com.javacode2018.lesson002.demo18.test2;6 }/ [- x: T( [1 L' n6 E- R
    0 Z! v; F" r  p& H; u& \- `) R
    import org.springframework.beans.factory.annotation.Value;
    - W: ?& w5 X8 h! qimport org.springframework.stereotype.Component;
    2 ^% `. t/ t! Z9 ]* n5 @* f+ l! I* o# N' j1 X0 O- A0 L
    /**
    " r$ {  C% o8 G2 F) B) h/ S * 邮件配置信息
    $ U! x9 }. @1 M */
    1 Q- R! F1 `& J1 ^$ s8 {3 j! d@Component: i1 Q* ]! ^* C; V6 ~2 Y( n
    public class MailConfig {* m2 [$ Y, X0 k+ t' w
    4 C/ e" D: r3 h3 }' D
        @Value("${mail.host}")1 `/ H% ~# M2 S" z6 n% a8 I  U7 {
        private String host;
    * r. J/ [# y/ D
    4 i$ o& f0 C$ e8 z6 A    @Value("${mail.username}")
    + j$ d1 ?$ M; \2 R) b# D& E    private String username;
    , V, J& u2 Q& K. Z
    / c% n9 z7 l( o, Y$ `    @Value("${mail.password}")
    3 e% d! Y: @2 s2 x. H6 l0 x    private String password;
    / D- }# S3 G9 S* C+ F
    ; }9 U2 e. `; r% @    public String getHost() {& m) n! N& ?. X5 o7 k6 S
            return host;5 I& E7 s2 v6 k, c- [* y
        }
    1 i7 \  v8 R+ x
    1 z: J) w* d( Q0 _, ^8 g, w: |    public void setHost(String host) {- s  K. j4 l7 w2 x& Z4 U/ S! l, E
            this.host = host;5 P0 b& o3 p9 U) j
        }
    % @8 e3 C! q' y- |) D! g" T: {# K$ M
        public String getUsername() {
    8 Q6 V+ X, t& g  r& f        return username;- K+ X3 q+ p- _: h) D
        }
    # J6 W+ X. ?# S; h! I
    ! `9 g# E$ b6 G6 ]5 |1 k! C8 K    public void setUsername(String username) {- q5 s& A8 W& \
            this.username = username;! S" S1 e5 w" f- p2 Y! \% X
        }- _- Y# m/ H1 m- o6 d8 r9 c# \8 {

    ' ^" }+ n2 C, Y, J2 a    public String getPassword() {. T3 x# [4 y# k. h' e+ Z& U
            return password;% i, L0 r7 i0 q5 X, H1 v
        }
    ( g" i: s# [5 C) w1 {( |0 h! D, f* r; G) e. L' u
        public void setPassword(String password) {
    ) v+ m* b! U; j) D2 B' x3 Q        this.password = password;
    $ E' a. d+ ^, _2 C    }
    3 k6 d5 f+ f6 A! I/ z% s* C. e: }" B$ y0 a2 C+ f' Q
        @Override/ T2 t& B9 t% `: b) {; T: I
        public String toString() {
    5 x- @+ ^. h% c' q- Q! L        return "MailConfig{" +" K% U( T% M# a. m' g$ M* X( ^3 K
                    "host='" + host + '\'' +) t- U# V$ j/ X5 Q3 Z
                    ", username='" + username + '\'' +
    ; N7 P  U- W: h2 S, c, A                ", password='" + password + '\'' +
    . B' K" j7 L  g, m* K4 K                '}';
    5 D* j( a% P  H; J    }% j- b3 R! O6 A& p5 m$ D; b
    }. T3 s3 A' w% v
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    & n$ w8 z( P1 \) u
    * m; o4 S# D2 o- bpackage com.javacode2018.lesson002.demo18.test2;2 C* M7 R9 k: P. ~! o: u

    6 O9 p0 h) ^# N! L2 J# Pimport java.util.HashMap;! t! W8 y8 C2 u" {! Z4 O$ l& Y
    import java.util.Map;
    ( O% f1 i) L, Q0 B$ V: u
    / a, ^4 R* U# {9 Dpublic class DbUtil {  @5 ^) t% Y: `: ^: a- R' [
        /**0 k, a7 m9 a8 H
         * 模拟从db中获取邮件配置信息4 c2 u! C6 }# g5 H, `
         *( I& t/ Y5 k9 i* |5 {7 r/ D
         * @return
    + {# N3 o7 e  @% T/ [) e' ]     */0 B" Q9 }* D, f
        public static Map<String, Object> getMailInfoFromDb() {
    3 a! T/ P% y1 p; J8 \( M# h        Map<String, Object> result = new HashMap<>();
    . K0 o0 t* ~( [0 ^0 i- V) O        result.put("mail.host", "smtp.qq.com");* W+ W; l( K3 w; N! r8 v9 k
            result.put("mail.username", "路人");1 v, Q- `4 R9 q  b, f& b8 W
            result.put("mail.password", "123");
    & _+ g1 `# d' D2 }. L        return result;
    ! l  B9 l8 g  e; D6 H0 V    }
    . u6 R* ^9 f3 P0 v3 X, z}  {3 [2 t5 z2 j! K7 ~0 m
    来个spring配置类* c( v$ F2 q  \
    3 F. f8 c' s/ c2 T1 a  j( \! e
    package com.javacode2018.lesson002.demo18.test2;
    6 l) L( @6 s; f" F) ?# X; P. \0 L" W; y' P. }: V0 B
    import org.springframework.context.annotation.ComponentScan;
    + W6 P; F0 J  l6 F. Z9 Q: P2 rimport org.springframework.context.annotation.Configuration;0 e, z$ L; Z! N* b" A
    9 D4 N* }+ {. t& M$ z
    @Configuration" q; A: g; D: e" X5 n
    @ComponentScan: {2 W8 W8 X# k$ T
    public class MainConfig2 {
    ' G- F1 g/ B: M+ p, p0 b: v" m9 M}2 K- O% `# C$ d# Y
    下面是重点代码7 q9 ~" x; a3 Z( p1 r5 l

    6 C( g) o2 o0 h# s. e) O@Test
    ; c4 s" D# h. V8 G& U: p/ G+ _public void test2() {
    9 F3 Y/ ~- U  p. S2 I$ Y: u    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();# w, {2 c+ }' n; m

    9 T: Q2 i3 A* y; g, F1 y8 Y" R    /*下面这段是关键 start*/
    : n8 b1 X: |8 g8 |2 y6 t# d, K    //模拟从db中获取配置信息9 d8 B/ G2 ^5 g4 \" J& `
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();- a. c8 u6 d4 B. |* W9 {# o; F
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)$ T3 @0 ]/ t: s% k% y0 u
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    7 P7 o$ |% |9 B2 P* l: k    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高8 s- a) v  b- q: h2 c+ D, h4 n
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);, s1 A$ P; i% n9 l7 H! ?
        /*上面这段是关键 end*/. B+ S) s! W- \  i
    - A( X( J: ]  f! q* |
        context.register(MainConfig2.class);
    : E* B+ H) M: Y$ W    context.refresh();* V2 q/ k* q* V2 J$ B  b
        MailConfig mailConfig = context.getBean(MailConfig.class);
    + J9 G, U: `9 H9 C: w# @1 [  ?    System.out.println(mailConfig);3 [" L, l/ u0 K8 [* b* F5 W& u8 H
    }9 M2 t8 d" E. c+ N' p- o8 U* G
    注释比较详细,就不详细解释了。
    4 f' e9 A6 c+ R4 Y* I0 E" G( h% n& R" i8 p) M! _9 e" }  h
    直接运行,看效果
    & U0 @! Y4 f: \$ F, W2 |
    , I5 j$ S! C8 B, Y. F8 I6 u- W5 \: sMailConfig{host='smtp.qq.com', username='路人', password='123'}
    % Q9 |# h3 d( a) c" G6 |7 J6 k4 ~有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    ) S  L2 q7 J$ j6 b8 F! p# y& Y" h: b- D! ^
    上面重点是下面这段代码,大家需要理解
    & {) M& D3 F% R$ z; p7 j' a- O, |/ q0 X( h  A, R; y# p8 \$ @
    /*下面这段是关键 start*/
    3 C8 B0 s- e3 K' @6 c4 C1 U//模拟从db中获取配置信息/ J: F4 f% S$ l" o3 n; e  ?
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();5 _6 D) G* A7 t8 N9 _4 G; S
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    0 P2 S0 V6 D6 y8 A8 AMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    2 g$ u$ V4 e0 R: X//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高0 n/ T* o6 g  [. y
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    7 D& f' A+ S' N8 F+ \3 X" U/*上面这段是关键 end*/
    ) y) X. K8 S6 h& D: w/ u咱们继续看下一个问题, S( _+ z! K1 E2 W

    % d# E! }. U2 V+ m$ Z; [如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    % s: n) N( _# m7 ^. B& B5 `
    ( \" G4 H1 M4 `0 s5 o8 e@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    $ e$ Q& U7 i  o  \! a+ ]
    - c- `7 J* b' T7 H, g+ _' M实现@Value动态刷新! S5 r: d6 s/ }3 f/ [, u

    8 Q9 D( [6 J' q先了解一个知识点. l" ^( ^) _/ C& s* K3 l" J
    8 ^2 \# k% o! j3 @2 |
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。8 W  l+ ^1 x. S, i, R+ l

    + e  Q4 O' G) p" G8 j这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    % E0 k7 ?- L* r; L6 r5 V0 p6 F; z+ r  p  Q5 |: r7 ]$ l
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:: a; _6 M( e# j$ b$ m5 i) r, T

    % M4 M) l/ t) b; P/ t8 f( WScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    6 U% D+ j  r8 x# L这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中4 J. G: ?9 h3 E. X
    ! j" C1 P  M. z) d8 H; e: U/ d* y
    public enum ScopedProxyMode {
    " t5 ~' W2 \9 `( w5 K$ I    DEFAULT,
    0 H5 o& i3 o1 c4 L    NO,
      P: K) k! @8 K% N- A    INTERFACES,
    ( a- @" M+ k( k5 l& z    TARGET_CLASS;% A5 {* m9 A- R+ B7 o2 w  V
    }
    . A1 Z: ^# ^. B5 a7 W5 e/ E前面3个,不讲了,直接讲最后一个值是干什么的。  h$ U" m- I; O3 \. N
    * |, y4 y  X; l3 H+ @4 x
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。! j5 `) R' K# q& U

    6 Y) v9 B0 q$ l理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
      {, k  k/ F; q. X& l
    ( h* i# [: y* }& k自定义一个bean作用域的注解5 `  P! W) z% R( Q! c, A; |! u
    2 E1 w: L8 ?/ z; t7 f9 x1 Z% L8 c
    package com.javacode2018.lesson002.demo18.test3;
    4 J( M, R1 v' ~2 Q+ G: k" i2 @7 I; ~: S& b: |, I+ b6 K
    import org.springframework.context.annotation.Scope;
    - S3 M! p& V' ]5 A" limport org.springframework.context.annotation.ScopedProxyMode;
    0 f& |; x6 W' G2 x& D- Z8 p9 X
    : K- I# F* g6 W4 i, Q" \0 W7 himport java.lang.annotation.*;
    " c, i( c% @+ Z( ^: h3 x
    1 H; f8 c( y: X/ @$ b5 k: m@Target({ElementType.TYPE, ElementType.METHOD})
    8 y  K$ r; @/ R@Retention(RetentionPolicy.RUNTIME)' t6 M5 h5 f/ i+ D+ J* V- }* N$ z
    @Documented  {% G1 w* T4 O, q% G
    @Scope(BeanMyScope.SCOPE_MY) //@18 ?5 H2 r6 X/ w# b; e5 ^5 p& G
    public @interface MyScope {
    5 A% S5 g5 A' |) c# [, W/ F: X; q/ Y    /**
    5 b# ]( ^( L" C* V$ N% G. K5 X$ i     * @see Scope#proxyMode()6 X+ ^  s# F9 U( e$ m9 S6 y# ~! s
         */
    * O( ]- k" d* \# `4 c    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    / x) B9 l, S5 A; g7 s+ e# t}: z* T+ ^/ w! U" t$ l
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ( d* A% q2 ]$ i! S7 _/ L& b1 ]- k4 @  Z) y
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS) c5 F) `5 j) `1 t- f
    6 w* t8 X+ Z4 w
    @MyScope注解对应的Scope实现如下
    ( ?; L+ ?4 i& L  B. [
    & ~+ d0 M1 p2 G% u! ~package com.javacode2018.lesson002.demo18.test3;5 E' U1 t- x5 Z+ ~' k2 _$ w  ~2 m
    " Q' U, W! {: ^' K2 P
    import org.springframework.beans.factory.ObjectFactory;
    7 c! ^; r/ g) r$ K/ o" S6 }$ u) _import org.springframework.beans.factory.config.Scope;
      y) V; s: Y1 n  Z, f# W1 `import org.springframework.lang.Nullable;+ S: M+ t. W0 Y, g6 P

    ( @1 W( i% D, @2 a( _( f% j6 V- ^/**8 Q# o& F$ g" H# o9 N$ M) Z. ~1 o
    * @see MyScope 作用域的实现4 u' H& a9 g8 `6 s) A0 H
    */1 R% g% v  P5 q$ x, b
    public class BeanMyScope implements Scope {
    - ~* L) x& E! @4 t! X1 L, f- K1 C" W/ i5 C* B/ O
        public static final String SCOPE_MY = "my"; //@19 Z+ G3 v* A( _4 O  H& x9 u

    3 z! k, D: m5 V% c    @Override
    - ]  D' N' W5 K8 l& {    public Object get(String name, ObjectFactory<?> objectFactory) { 5 J; \) u4 I. ]
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@25 S4 |, Z8 V. `- q
            return objectFactory.getObject(); //@3
    - M# B- O7 O2 W- N    }
    ' T! j2 z2 B$ n- ?* G* Q; Q# }+ Y) ]7 }" d4 k9 |' d
        @Nullable8 }! P  F4 {- _! @* V0 w
        @Override4 L# L1 l: \8 X' b" H5 u$ Q, x' m: E
        public Object remove(String name) {
    , x  W& u3 z" a5 @6 v        return null;
    0 k  o1 o2 _4 Q6 T% J% c    }5 ~0 [* j* a1 c. G' \+ z. s
    9 D1 \) p' ~! M3 e3 f
        @Override
    5 v, S) g1 B% ]- W9 w/ _. v) }% i: h    public void registerDestructionCallback(String name, Runnable callback) {" F1 {2 q, X( g0 v) S$ I1 B$ q5 J* Y

    7 i0 H6 ?- |2 ?( C    }
    1 p' |2 W3 W+ z% _' ]) b
    ! a1 v4 e4 k9 Y: N+ L7 [  L5 o( A    @Nullable
    ) q+ ?1 e" W6 W) q( W    @Override
    5 z$ S9 w9 k% R: j" v    public Object resolveContextualObject(String key) {3 ^, ]) ~" K6 l
            return null;
    , ^' ~. a# d2 H1 R" p' t( r+ c    }$ f5 K0 ]4 i- X# y

    1 v; M; H' z6 N5 `$ q6 x7 Q# D* Q    @Nullable
    # k4 E5 L" Y4 ^    @Override6 \" d" ~9 R! |
        public String getConversationId() {- {) `0 h5 [5 V% l7 W
            return null;; ]9 V3 G" b7 x6 L/ L/ J4 w1 n
        }
    2 C* M, \2 Y4 c$ Z5 p7 F}
    3 @" x  b$ T8 m% h! u@1:定义了一个常量,作为作用域的值* Z& I! C  q2 Q" K% @
    % Q8 |/ c& S& L' Q
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    7 n0 I* \" }+ D, V9 V" a$ [3 k* F5 Q1 u( z* J+ _" @
    @3:通过objectFactory.getObject()获取bean实例返回。
    0 B1 x* _/ J. ~
    6 T. `; K6 m4 m4 H下面来创建个类,作用域为上面自定义的作用域! r- c9 W& T! v; Q: r7 y
    + d: O- r- f* M: C' J9 q
    package com.javacode2018.lesson002.demo18.test3;
    / e, z# ?& V. t& |# ]  M( ]+ P3 h# W' h$ e! ?; |
    import org.springframework.stereotype.Component;8 R5 J3 N( j' D
    ' F7 d2 }/ P! y, W' [
    import java.util.UUID;7 Q& f3 h7 Q9 o7 P

      J9 o  ?3 l$ N1 k- ?@Component
    ( p' {* r) \( T1 [& b6 P( E@MyScope //@1   w: [# {7 M# _/ z- p* N# O$ z
    public class User {
    # Z) D5 ~. K; e! R# z5 z5 [7 T6 C4 `8 }& j; Z* [, i6 z
        private String username;
    : i0 C) h5 {! f
    0 a5 r% {% u; u- J$ j    public User() {
    ' r) c7 |, l+ [$ ]+ Z+ |) s8 x        System.out.println("---------创建User对象" + this); //@2. w; }, Q' e2 _4 U/ a9 m1 k
            this.username = UUID.randomUUID().toString(); //@3
    4 w1 k1 E  n5 @/ G    }
    6 |  p3 A" f0 V4 b; x$ Z6 b; f5 F' F# C8 n% R3 L
        public String getUsername() {# T' g1 x7 _2 Y/ X8 `! r7 ]& j
            return username;
    6 X+ U. Y) m2 \( f, c, x    }2 s  c/ x  M/ H# b4 P
    6 k- `) P5 z, @9 h) X
        public void setUsername(String username) {" K+ M/ x% o5 f& k) u
            this.username = username;% m5 |2 `' n; \
        }/ `7 b# d. M8 v0 }& z# R1 v9 \4 h
    2 ]4 d0 S& O# S9 \7 v! l1 h- l
    }
    ( s# ^# y' @6 m8 M6 [@1:使用了自定义的作用域@MyScope& i2 N0 U- t9 |, ^/ `

    - X# f( X) W" F) p@2:构造函数中输出一行日志
    9 L8 \' @* A( j9 S( w1 v8 z2 ?  O, J3 D; t+ C
    @3:给username赋值,通过uuid随机生成了一个
    + j' s$ c0 F8 R. r- T, N1 b" W
    9 L5 a2 o4 e& v来个spring配置类,加载上面@Compontent标注的组件0 u0 |; {+ a/ u
    # i# \: z) X+ s- Q% E& ?
    package com.javacode2018.lesson002.demo18.test3;
    ! F/ w; ?, p- ^& [  ]" b. m' P  N) f- f0 E" T/ e6 e
    import org.springframework.context.annotation.ComponentScan;
    ) j0 p( ]. V+ s8 _, x: }& Y3 a; Ximport org.springframework.context.annotation.Configuration;9 F, m/ U  J! }6 E; t

    ' g6 h6 {' |! e@ComponentScan
    3 E0 X8 v$ X4 d: U+ w& h: U@Configuration9 ?& i8 T2 m; _/ z1 q+ a
    public class MainConfig3 {4 D3 ~  H& b# q  Q* o! `4 H
    }
    " F/ w+ W7 h! N/ w; s2 C下面重点来了,测试用例: P( \% x5 J1 U; s; U+ J
    / p6 \9 ?2 y5 {2 D( k$ }8 I
    @Test. n5 v/ u7 \# j* j) O' n
    public void test3() throws InterruptedException {+ F% n! Z- a$ ?3 p
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ X7 J2 o: S3 G  j& m5 b' D8 X$ [    //将自定义作用域注册到spring容器中
    9 W8 [+ N" ?  Y0 @' a    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@18 g, ]* j- {: g6 u& x
        context.register(MainConfig3.class);
    % y0 G$ ?: h5 n% ^0 w    context.refresh();
    % t( O4 y7 O/ H: g0 o
    5 t4 S) G" q8 f& q: N    System.out.println("从容器中获取User对象");
    6 l' F, r6 W7 g) O: c, e3 U    User user = context.getBean(User.class); //@2
    4 m+ L7 K8 D7 T, n+ g    System.out.println("user对象的class为:" + user.getClass()); //@3
    ' u6 P) H& M; H1 \4 U7 y6 x7 l! B% s6 s2 C( e, t: c
        System.out.println("多次调用user的getUsername感受一下效果\n");
    * k$ D' `( m4 |, V( ?    for (int i = 1; i <= 3; i++) {& u1 t+ y1 L2 r
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    7 R( @8 Z  Y, y1 Z5 v8 t+ O2 U        System.out.println(user.getUsername());7 ^2 C; N2 ^- Y, O+ s) u
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));2 Z9 y0 m' n; a- p, G- f
        }/ B# v7 k6 ?( r3 }; x- a1 a
    }) ]4 \6 V" M2 Y, _5 Q* @0 l
    @1:将自定义作用域注册到spring容器中
    % n# N5 n! a5 |) C4 h/ \
    ( N# b2 W  `0 K( L  B4 c@2:从容器中获取User对应的bean
    0 b; y$ Z# |" A; C3 ?) q/ u" n4 x5 m0 V7 Q* o, y: p4 C5 l
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的) h1 U5 Y4 _0 s2 F
    6 t* G7 N( j2 l; M$ J# [/ i
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
      O/ Y* s3 G3 W9 M3 h/ k- t# A# d4 N; i( d% J. u6 }% {
    见证奇迹的时候到了,运行输出9 u* o& }! F. d$ ^. e
    * R1 Q4 ?0 S8 }8 j
    从容器中获取User对象
    1 z/ c. L( h. q. o) |  ~5 iuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    # n: P% i6 Y6 I( C$ w多次调用user的getUsername感受一下效果4 r* V9 ~% s" K+ b

    & ?) Z7 U! E& k* p9 b2 K+ w********
    8 s/ E5 M6 o9 F第1次开始调用getUsername
    ( c* F6 o( u5 L  \BeanMyScope >>>>>>>>> get:scopedTarget.user
    ; C; d% @. J+ B1 L' Q- @( P0 ]---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4& b8 v# D7 N# D/ r/ ?) E
    7b41aa80-7569-4072-9d40-ec9bfb92f438* E* m8 K/ n* p# s0 N5 w
    第1次调用getUsername结束8 Y% `  v4 g7 |0 k4 H
    ********3 F5 S" c$ V8 Y, d# V, ^% o
      q1 m5 [3 N' W0 t: a4 o9 _
    ********
    - |1 b1 J4 D% k" \5 _# n- q& Q第2次开始调用getUsername
    ) i* o, ]% I3 Y* X- XBeanMyScope >>>>>>>>> get:scopedTarget.user
    ! z' J; P+ s1 f/ V) [---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    0 _& V# ?! [5 h: C: E. l) k01d67154-95f6-44bb-93ab-05a34abdf51f( X. U% E% S% Y, u& ~" G
    第2次调用getUsername结束# X5 L7 R& b7 Z% D5 R$ n
    ********
    " X5 l2 V8 `3 O2 O! X
    , T: r- c7 w; u: K: J. K; j********* k; @9 j3 J+ s1 |; {
    第3次开始调用getUsername6 H& Y! z, K- `& L! P7 _
    BeanMyScope >>>>>>>>> get:scopedTarget.user
      E$ ?9 N" T2 i1 v; H' Q---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15* `$ t  Q% C# x7 J- l
    76d0e86f-8331-4303-aac7-4acce0b258b8
    6 r+ X8 y6 b; F8 ?第3次调用getUsername结束6 V2 x, g6 i0 i2 u2 _. ~3 C
    ********
    ! ?- l9 s, Q& k) E5 K/ v2 v从输出的前2行可以看出:0 ?4 l3 [9 q$ C+ n
    ( G9 O2 n  k4 H4 M1 j; `, V5 c
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    : B0 [$ }# a6 J+ u- t4 `; m0 ~/ W2 W9 V/ F8 }  v
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。( E# T; k( B7 `) I6 t+ o8 f, A

    2 v4 J5 d$ D2 {1 Z后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    * B7 A; p7 t9 y$ I9 f1 F; X- X7 M* n" o& e; p5 W3 w
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。$ D# P# n2 L+ b! V3 M4 [+ m; j- U
    5 I' ^8 y3 s1 r/ y0 e
    动态刷新@Value具体实现
    # C; w; r8 L5 s% b; `$ S2 d- N' Y( _- h
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    9 ~& d1 _% E' H& Q# |) E; ~! z4 ]. j+ F5 ^4 z
    先来自定义一个Scope:RefreshScope
    , H2 t! M; Z# E) M3 P: d* d" F2 N0 f+ u2 N
    package com.javacode2018.lesson002.demo18.test4;
    0 L0 a( j, Q- Y$ e) i1 W$ Y
    8 H/ n- u6 w8 qimport org.springframework.context.annotation.Scope;; A/ N6 r9 V. F. L( {
    import org.springframework.context.annotation.ScopedProxyMode;* \8 C3 N& `" [9 ]- }
    , [: t# R, \( j
    import java.lang.annotation.*;% j2 Q1 |+ C; a* m, t6 w

    $ x0 F$ n" u8 m% M6 b  K@Target({ElementType.TYPE, ElementType.METHOD})0 t9 a% c, r" n$ c7 E5 d
    @Retention(RetentionPolicy.RUNTIME)
    / q: P% v$ W7 q$ B2 \@Scope(BeanRefreshScope.SCOPE_REFRESH)
    2 [9 e# u/ ?+ F4 J1 }9 l$ M' s) r@Documented
    % u5 M# n) ]' ]! A) ?0 ^public @interface RefreshScope {
      v# P, Q( _9 t2 n3 v    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ; T- o3 f" ~- S0 Z# d/ e7 E4 s}
    ! I$ w: H0 t9 x& `/ n: a要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    / R$ T* x7 |5 Y% ?, ]. R; v
      m7 u# N! `' e* X4 I2 S@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    2 H4 l, L3 M- y# e: z: ^  E. Z) P. b* ]
    这个自定义Scope对应的解析类
    . L; P# }) H0 d. z" ?7 v6 Y0 u  G! e0 N
    9 X. e3 T( {6 _" }6 K& w下面类中有几个无关的方法去掉了,可以忽略
    7 ~3 |4 M( s0 Y8 W& H, J! z* l; N1 z5 Z& h! z, z* N) `
    package com.javacode2018.lesson002.demo18.test4;
    % [2 |9 Z+ x5 ?
    7 C0 E0 r" m8 [) L" {! J
    $ b' P8 x9 q( |/ U/ B& _( Vimport org.springframework.beans.factory.ObjectFactory;
    6 L0 @, ~+ T# M, q  b) L0 ~import org.springframework.beans.factory.config.Scope;% T* x6 ?3 W) M5 E8 H
    import org.springframework.lang.Nullable;
    * h" ~0 K* [, E  e: a2 j( `2 X0 y% A, [
    import java.util.concurrent.ConcurrentHashMap;" R$ S- h0 T* U, K" K2 c! D

    5 k% b5 p6 c* E* Rpublic class BeanRefreshScope implements Scope {2 o9 C4 o: p0 e- P. v& J
    1 R3 S- a( D/ R$ L
        public static final String SCOPE_REFRESH = "refresh";
    ; c$ [$ J6 T7 R) W# M( X/ J+ x, B6 \. S+ u) G5 W3 D  j, a( g
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    1 i5 a7 h' X6 z% @7 W
    - W( L: V& g" w9 C) N! {    //来个map用来缓存bean& y2 k5 P1 f8 i
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1$ K$ W2 a7 s/ _: R
    , t0 {8 U- I6 m
        private BeanRefreshScope() {
    : r0 `$ ~" D" m" j$ o    }
    + V, O# |) @2 o2 Y  y& n. ?
    8 ?! c) J2 j% E" T3 h    public static BeanRefreshScope getInstance() {/ g. [/ \1 m7 Q; j, Q( X$ p  X
            return INSTANCE;
    / h( T- d! Y+ W- J: k$ H, n) m. U    }
    ; B7 m/ X& B; ]; C' J9 C& j, {
    4 b& l: y$ c6 O* Z8 a- K    /**
    8 o: f0 L% E+ m! ~% r7 d# V" r     * 清理当前
    5 x/ V7 H( p, ]8 o6 ^$ a$ E     */
    " Y/ r) k, W' }6 n! p9 X" P    public static void clean() {3 N7 S; V9 {" Q  `; h/ ?8 a
            INSTANCE.beanMap.clear();& d3 D( i: U4 x' R( ~
        }% N0 `: V; Q+ T+ u/ s! `
    : m6 Y3 g8 t; R: p2 }/ T
        @Override, `! R2 ^4 I6 |- E/ _
        public Object get(String name, ObjectFactory<?> objectFactory) {
    ) c  ^3 }1 G0 q  V        Object bean = beanMap.get(name);' k7 r: H: L; z6 j
            if (bean == null) {
    : V" `$ E4 j* X            bean = objectFactory.getObject();
    , r9 X5 [; a: G0 Y' G            beanMap.put(name, bean);( o- I- Z# _- V
            }
    9 h+ ?: v; ^% I7 [0 u; L. n        return bean;/ b3 z( P' j% u
        }
    8 g+ d1 r  o: j3 }8 q2 F% E
      [! p  Y7 p/ Q) Z}
    ) @- |& |, g8 j7 ?2 T4 L' w" ^1 K3 A. P# w上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中5 M: N5 Q( L. y# l0 d7 N7 A6 v/ Y

    " m* R$ j* I; x& @+ }上面的clean方法用来清理beanMap中当前已缓存的所有bean6 p8 T! q+ q' ~8 h$ M( ^8 j$ Q% a

    ' l; e; Q* x6 L来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope- e) Q2 S0 C* s/ S
    ( K! t1 [, |0 [0 \! g( X
    package com.javacode2018.lesson002.demo18.test4;2 t  K& e- K: z- E& a6 ^+ r" X3 Z& u

    ( x% ]- L7 H7 P9 n5 l. fimport org.springframework.beans.factory.annotation.Value;4 ~: `. v' Y8 z4 f! d& [7 d
    import org.springframework.stereotype.Component;" p' o) w: w3 M
    + M5 j8 D$ k- d5 R
    /**
    & c. o" n6 `! Y- w. d% b * 邮件配置信息( r4 I2 K) F% y+ E
    */
    8 [: k8 K9 I( }- m& g7 c: _4 s/ A@Component
    3 L6 i( y4 e6 d+ P  V5 j9 H; T3 U; J@RefreshScope //@1
    8 N  d2 Z" m% E5 E4 Q" _; Jpublic class MailConfig {
    2 ^4 c* u  V- L$ l; c) I8 i5 ]
    + N- \  D# T  @1 i. U    @Value("${mail.username}") //@2
    : O/ P# w/ V! |3 t' Q0 U    private String username;+ C7 I( l1 L& _

      X: G' R: u+ v+ A, v& C    public String getUsername() {4 l# g  t1 O2 b9 X
            return username;2 v. L2 I( M8 p) W
        }
    8 V: f- H3 }$ a" y% l# X
    5 F" h( A: m+ l) H& O) E    public void setUsername(String username) {
    8 ^! E4 ~: O) N5 U( r1 Z        this.username = username;& B3 h3 j+ ]2 t) ^5 C
        }
    9 t% S+ b3 y  M- S& x' Z9 ]
    3 W5 k, Q* ~& n0 N. r$ a7 ^1 y    @Override, H( j3 H% Q4 Q& z; H  o/ n
        public String toString() {
    3 N$ Y/ m5 R5 F% c        return "MailConfig{" +
      y2 \4 {/ S! Y                "username='" + username + '\'' +" v) ]% }. f. v% Q
                    '}';, u% ?7 v- v! ~8 O+ B) Y' }' v
        }5 Y5 ]& x# _2 i& h5 ~  @% m9 v$ u7 e
    }
    : m2 `3 W* z3 t. ?@1:使用了自定义的作用域@RefreshScope, k9 R- G# R4 L4 |* q
    0 R! G0 j5 Q; A! A! `2 |4 O3 E
    @2:通过@Value注入mail.username对一个的值# a+ U: o- ~% P: G- B
    9 Z! l9 r1 c" [/ q6 v
    重写了toString方法,一会测试时候可以看效果。
    ! K7 P8 J6 r( ^) W9 F9 c% q. v# \3 v; B
    再来个普通的bean,内部会注入MailConfig5 @/ N5 A6 c7 r% l6 j
      V$ B2 h( a! r
    package com.javacode2018.lesson002.demo18.test4;) B% \# o3 R) G7 r9 w+ o
    % s; P  w4 N0 ~& x
    import org.springframework.beans.factory.annotation.Autowired;
    ' u0 W0 C( i& Y, ^% L" x3 Jimport org.springframework.stereotype.Component;5 B* O  ?. S- k/ T3 n
    - K; V8 {# V8 t) ?9 m
    @Component" v$ {/ E4 O% j
    public class MailService {2 U2 `) B6 L( ?4 U/ J' y7 r1 Y
        @Autowired7 G5 f% m0 {  v0 L, G5 d
        private MailConfig mailConfig;4 E1 E" m3 u6 T* F0 P. s3 k) w

    3 K) t( G- T4 i& b0 L    @Override
    $ ], }3 p: B* U! o5 B; U! R8 E+ m# V    public String toString() {3 c: `3 b) T1 y* o5 ?' p
            return "MailService{" +
    8 v) ]" K3 K% D$ }7 @* ?                "mailConfig=" + mailConfig +7 P/ @7 m7 f  D3 k8 N
                    '}';8 b7 N* m  `) l* V, P$ q
        }
      G5 {$ U$ x" F: N3 f- ]2 X}
    % h& w8 ^. M! ^# v代码比较简单,重写了toString方法,一会测试时候可以看效果。5 ~! u5 f, V1 I

    $ w' ]9 K6 n8 ]9 t来个类,用来从db中获取邮件配置信息
    + |' x% M& l4 ~% Y, Z1 e, b; `: N& m8 [
    package com.javacode2018.lesson002.demo18.test4;1 D) @. j  C: f+ G9 J

    & k: [. v6 ]( ^6 l. U. u; P7 p9 himport java.util.HashMap;( D8 {8 v9 O% d2 I2 }, s4 Q
    import java.util.Map;
    $ @* F4 D3 Z- {4 t! Y% himport java.util.UUID;! v6 k: L% s% ?' `/ k9 S

    6 V1 O. g& S0 _, ~public class DbUtil {9 q- v: u4 [2 \4 O; z) t
        /**
    4 Q' p( f( l9 @, x) o; o  C! `     * 模拟从db中获取邮件配置信息3 p/ K, d/ [4 f% P: \  p5 I
         *
    0 i& ]8 p+ x5 |- }, t; j     * @return
    ! m7 B/ B. J8 A     */
    4 g; e& v" o5 a- C8 V  \( R4 G    public static Map<String, Object> getMailInfoFromDb() {5 n5 x( U$ m6 g- [
            Map<String, Object> result = new HashMap<>();% R5 ~4 y6 J$ ~; s, r9 d( y
            result.put("mail.username", UUID.randomUUID().toString());
    - o8 _: F% t: f- l! O; \        return result;0 R8 y5 b% N5 v- b- O7 S
        }# E: x; l, E9 C* ^1 j2 ]) S
    }1 N$ ^3 l1 Z- B& B, A+ }
    来个spring配置类,扫描加载上面的组件6 P7 D5 w: d( u* K" {+ h; N
    0 }7 K; c. w  w9 J" e* w. j
    package com.javacode2018.lesson002.demo18.test4;4 r/ X* s: g- X# I6 C
    ; r* Z- w: C# Q0 G% v
    import org.springframework.context.annotation.ComponentScan;; N6 q- P& ?0 F* F
    import org.springframework.context.annotation.Configuration;+ e. }6 b) Z7 N: K# K: O
    2 ~8 P. M; I5 d
    @Configuration! z4 u0 ^6 r5 s( b
    @ComponentScan# Q% N# T) S. r) y9 u0 h4 a
    public class MainConfig4 {: [4 k- i5 Y2 v9 }$ `% }) ?$ U5 \
    }4 {' d5 w$ C& E6 ~6 }4 I
    来个工具类9 a- `: g; o3 w; z  G+ b. F

    - W( {  U+ Z7 n2 O内部有2个方法,如下:8 N+ [- Y# K( E8 O/ z3 L- M- c
    * M4 t" j+ V9 ]1 C1 M4 q
    package com.javacode2018.lesson002.demo18.test4;
    ; y& U$ D9 H2 u6 ^* f% i1 B- D: Q% Q0 k
    import org.springframework.context.support.AbstractApplicationContext;
    ! |; U( E+ [, t9 L4 j+ Y' \& g" oimport org.springframework.core.env.MapPropertySource;1 y5 g3 o2 V" Y, e5 v2 ]7 J& Y

    - n0 {7 J( x' k) q# Zimport java.util.Map;
    / b1 f6 E# B' n
    ' O$ M6 ?" \" N& rpublic class RefreshConfigUtil {# V& L9 a& K* S6 B# ^6 U, }- ]
        /**
    # R4 \# A4 Y3 h8 d% L     * 模拟改变数据库中都配置信息: L$ K& l6 _* \! y* a6 f$ m7 r
         */
    ' ^' C5 L& o5 |    public static void updateDbConfig(AbstractApplicationContext context) {7 w+ l& k5 s9 r6 `
            //更新context中的mailPropertySource配置信息7 |9 U: s$ D) H( {9 g$ r: u7 [
            refreshMailPropertySource(context);
    2 Z& N* x8 x2 n+ _" {2 ?5 o( P' ]; r- ?6 q% G
            //清空BeanRefreshScope中所有bean的缓存+ \7 h; I# M4 j% [5 C
            BeanRefreshScope.getInstance().clean();
    2 ?, x) w( d* f; C0 y    }
    % s6 L! e! y+ Y7 h; |3 F7 t( I, w( o: i( }% M" b& j, I& T
        public static void refreshMailPropertySource(AbstractApplicationContext context) {3 Y* @$ L$ o  I# r5 B7 k
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    * x+ k5 V9 B( Z* K6 B! H0 @# O7 r        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    0 a7 D1 i5 p* Y0 m7 D$ F        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    1 G" ]: |3 ?) K) t1 u% _        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);' R3 K' z, @1 @4 b4 s, Z
        }7 N1 }: K' z% z$ a4 h$ {6 m

    4 v% c  T5 ^! I6 C$ Q}( r! F+ P* i. S0 v, K
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息7 e/ \$ J$ W6 ?' @( u. S

    ' U* [2 `" p! k. k: U$ y+ xBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。# V4 ~' l/ G& b6 i  O

    9 C" ~/ e3 v' a& o! d& |  ?来个测试用例$ k3 d$ \+ {0 U2 ^6 Q; L- ~1 i
    + a2 x0 Q) a  ]* M, P
    @Test
    + S$ E8 E! n: ^" g+ I3 a$ Dpublic void test4() throws InterruptedException {6 I  N. R8 w' }; R3 }% j! i+ k1 G
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();& q7 l  T" g: Y: P
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    ' @  ]# ]" x! b2 V/ K    context.register(MainConfig4.class);
    3 |8 [5 h! |4 r    //刷新mail的配置到Environment
    . f4 l1 D( I, Z$ M0 D" n2 R8 n/ k; p    RefreshConfigUtil.refreshMailPropertySource(context);; x6 N: w2 m9 X! E' y5 }
        context.refresh();
    3 M- h, C& Z5 D& \, s  N& V. f( ?3 F" k
        MailService mailService = context.getBean(MailService.class);* N3 t8 \. ?/ ^! C
        System.out.println("配置未更新的情况下,输出3次");4 {* h( C# c. b7 }% P
        for (int i = 0; i < 3; i++) { //@15 E3 B& O- }& @$ }3 a3 e
            System.out.println(mailService);
    ' e- ]& ?& X+ w, j  h( Y        TimeUnit.MILLISECONDS.sleep(200);
    ) n. j7 N0 E3 N: I    }6 @( n( a1 T7 m9 H' L3 ]3 G
    ' b0 `) H1 E+ k6 E! u
        System.out.println("模拟3次更新配置效果");
    " Y5 W1 d. L% j! ]' g    for (int i = 0; i < 3; i++) { //@22 z7 l( s; S, G: M# F3 A
            RefreshConfigUtil.updateDbConfig(context); //@3
    3 C: F% c/ R. \  P' k; e        System.out.println(mailService);
    # q! |0 |) E* Q, |        TimeUnit.MILLISECONDS.sleep(200);5 |  `2 U: D, M0 B5 d3 e# B" i8 k: B# k3 i5 W
        }: _8 t5 }# A' q$ u0 g
    }% k. H( W" s) O2 g# W) X" _
    @1:循环3次,输出mailService的信息
    2 c# y) V0 y1 B! k
    + f$ G& I- Q6 a7 f3 W@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息: |  O) `4 O' r" ~$ J# M
    7 U5 Q* V4 D- q0 L3 g1 I; p
    见证奇迹的时刻,来看效果" N2 u" B+ q: y/ v$ M% u" O$ t

    - `  R2 C  A% ^5 `; S3 h配置未更新的情况下,输出3次5 y* J, @0 F8 T; F
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    # g- ]9 P; o" }- O2 k- m5 }& B% KMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    : h* l) m6 i2 r) B2 B8 F; z9 P0 h% gMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}" D8 G/ T$ t1 r; l" l5 P2 O
    模拟3次更新配置效果
    - @) Z/ |6 H& h( w7 JMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    " u0 B* ^! \1 i& |5 fMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}" {  {7 ^/ s: g% i
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    2 ^- j% {9 W( N3 J: ^上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。+ M" o! e+ b6 T/ E& o) j

    , z8 M' B; E: @) e9 ]# v小结8 _- ~: I2 P" W

    0 R; }' C. |9 ?动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    9 c7 k6 g8 y0 Y; D# _6 N9 g6 ~8 T9 _+ |* f1 b# s
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    # g; E0 B5 |+ n0 L. G- U6 g# C) z0 k" c5 j1 `' _- y( ?/ ?
    总结
      V  H9 t- j5 s
    + j$ S) H4 `( j( ^9 W+ x3 z/ [) M# b( F本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    - v" i) |9 @/ m: Z3 k1 T( {- O4 u) Y9 d8 d
    案例源码
    7 ~. v8 h& D! m! [( z6 `( A4 |! r' D! ?7 ]' ^+ U( z/ Q6 U$ W
    https://gitee.com/javacode2018/spring-series- H# W+ U% Y+ y3 l3 _
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    ( S; `, L; N$ R" k4 u  V3 d* [# z  j# |
    Spring系列0 C' V( Z2 ^; q+ M+ J( H- ?3 |

    ' g; L$ q8 D! H& B0 a0 W# iSpring系列第1篇:为何要学spring?
    + n7 N7 m) O2 A6 [- f4 O  h0 b4 t
    " j" ?3 ]! I6 p, @  e. vSpring系列第2篇:控制反转(IoC)与依赖注入(DI)4 y1 E* _- _" m  b5 X9 V# d* \: q

    8 ^: l7 b& n8 F$ tSpring系列第3篇:Spring容器基本使用及原理
    $ ?) r# S: j9 L* G* U8 A3 k4 T0 p* o9 ~
    Spring系列第4篇:xml中bean定义详解(-)
    9 z! s5 ~' z" }- K1 h  g/ \/ |0 i; \* O- j- k& Q0 n
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    / @" k" \5 U& ~& y5 U, i8 t) }! a% k% ^% h
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    & E% K8 x' f/ V$ H
    3 U9 Y1 c* u" W% ~, xSpring系列第7篇:依赖注入之手动注入
    : t: m, b0 g7 c2 y5 p$ P
    0 y- M, X" R" c5 |' O" VSpring系列第8篇:自动注入(autowire)详解,高手在于坚持) e3 j0 d/ B9 z0 c! P" _
    9 T/ _% o/ e+ T# C$ i
    Spring系列第9篇:depend-on到底是干什么的?
    9 r+ O7 U( M4 m* K& U, [
    # x$ ^/ ?5 A0 r( DSpring系列第10篇:primary可以解决什么问题?! V  z7 a& V+ F  e! h; p

    $ _6 H3 ]% C+ I8 Z9 S  ^/ GSpring系列第11篇:bean中的autowire-candidate又是干什么的?/ c) O" r# ]% i1 k, J9 _
    9 ]& B8 a. `( b' u2 J4 q  n# t; A8 d
    Spring系列第12篇:lazy-init:bean延迟初始化
    # ?* A( U( c+ d% Q" K+ l
    3 j% N) y8 s% z+ c. eSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    . l) t3 O' K1 J6 n" y
    * k( N" ?0 K/ I8 F* K# V2 OSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?* o2 U, L7 ~- x9 i  U7 c' K* X

    ( Z$ J$ @. d. mSpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    + D# i- Y& G7 d, J* S: s
    # k8 N( h+ j" d7 D4 _$ _2 gSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)1 W  `9 I3 h& C% s: I* W4 V
    & q# [+ W" W  j1 W
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)9 j! d' B: T. S6 ^9 g: Y

    : v3 L# e) e7 l  v9 \0 V" ISpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    4 b: Y0 {9 b! y- W. e" E/ A! @9 K, U5 j5 r7 A8 |
    Spring系列第18篇:@import详解(bean批量注册)2 P1 t: e) Z7 Q4 X
    0 R$ V2 ]* Z' I' o  a: g5 {
    Spring系列第20篇:@Conditional通过条件来控制bean的注册% }9 {  y0 a0 B
    % o1 @: z6 v- D* G
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    7 m8 J0 c& E6 K* Y/ x- p& J% d  G0 }* }  i& e4 h
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解% g$ n* ]0 {; L; h
    * X) p& f7 Y" n. o, ^7 `
    Spring系列第23篇:Bean生命周期详解
    ' @7 c9 ~0 ?6 V) Y6 e) y6 D+ l2 ]4 ^3 f
    Spring系列第24篇:父子容器详解
    , q- E; i4 G3 J% ~1 v! k
    : [4 J2 |( v9 B, W更多好文章
    ; r# o: J3 c& X5 }$ f$ l. t; p* ^) r7 k, t
    Java高并发系列(共34篇)
    6 |: U) N; H% P8 F8 v9 c- N5 i+ [  O: S; r# R' K
    MySql高手系列(共27篇)
    , l* m' K1 F9 `% ~  X9 r7 V$ _7 U* J& i
    Maven高手系列(共10篇)! ^& Q* h7 a' R9 Q
    3 j  M5 o% y" b
    Mybatis系列(共12篇)
    * s1 u8 V9 W) Y" S
    7 {8 \/ X  f" W; R7 p聊聊db和缓存一致性常见的实现方式
    # f: V- ^9 l6 ]: l" L
    : \2 X: G' l% q8 A* P  W1 S9 t1 P- Y接口幂等性这么重要,它是什么?怎么实现?
    7 b4 t4 b7 I. o# s
    ; ]* L' B( b5 T: b& E1 y泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!* _& K1 [+ F4 F3 d* Q
    ————————————————
    2 \5 z: u- y$ l$ d5 [, l- K版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ( J* d" u. I) T7 U: E- f原文链接:https://blog.csdn.net/likun557/article/details/105648757
    , q( j. b% F7 w" V) X6 n0 }% V7 _; T' N6 ^: o: a

    / R2 N7 X& ~+ j- h0 y
    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-4-20 13:36 , Processed in 0.445837 second(s), 52 queries .

    回顶部