请选择 进入手机版 | 继续访问电脑版

QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4287|回复: 0

太狠了,疫情期间面试,一个问题砍了我5000!

[复制链接]
字体大小: 正常 放大
杨利霞        

5250

主题

81

听众

16万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    发表于 2020-5-23 11:01 |显示全部楼层
    |招呼Ta 关注Ta
    太狠了,疫情期间面试,一个问题砍了我5000!
    ' w9 p9 M) @7 {5 i; i- C疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    " [+ b" x' E8 K+ z2 \2 R' l
    % M/ H& t  J) y  }* @面试官:Spring中的@Value用过么,介绍一下8 C' k, _$ s1 P) u# j) X

    - r( a$ [: A. @% Y' _* S. y我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    : J7 _$ {# M# K0 Z
    8 j# C0 M! K$ t3 k% }8 p1 D面试官:那就是说@Value的数据来源于配置文件了?
    9 w* X  T0 U+ Z& i
    / I8 L9 G- F1 C" v' H我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置) J7 ]  u! Z5 A

    ) I$ I% }! G% ~: `1 {8 ]面试官:@Value数据来源还有其他方式么?9 R4 c3 H$ E' n8 Y/ k( p; J/ i

    " C# L- v2 {, T9 n' N9 s我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    . t6 @3 U( w! X+ H, _
    0 {7 ], @& c- M3 e: f3 [面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    $ ]) K* K! f8 @6 y. I4 R
    ) q' u: {& L9 |; z我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    # C( x6 `; B4 N% i9 k0 x! b
    * T6 `" l# M. i, u& n$ c! d! {面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?. h6 z) C/ l2 h3 ?, ]

    4 t( O1 s; z6 J. \/ E我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    6 y# U  x4 l' Z
    6 |% L; p* q2 ^" Y8 ^" s& w面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?0 r+ H2 k+ U! d$ K, E
    . w; a/ L! v7 F
    我:嗯。。。这个之前看过一点,不过没有看懂) T: F8 Q: N4 l/ E3 @$ T! j! D! O

    6 y& c- T( t0 M面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    , Z6 y! q6 S) Y3 u# v; I2 }- A, F
    ; r) X# ^/ r) n" I4 m; H2 k我:3万吧
    * e3 L6 V* C1 W) }0 F( k0 v7 o8 S: S7 M& o* N5 C
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?8 X( W' m9 k: d2 S- u- r! y  w
    1 ~/ S# t0 X& C
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万% [0 `# \/ }0 J* F
    ( \$ C. d7 l% @3 i$ N
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!, @; w% m9 N) [  I: }! F

    8 p3 ]1 G  p/ ^我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。/ v/ c* T% z" G5 c4 ~
      C4 J3 Q# D' |4 `
    这次面试问题如下
    9 b( M4 V/ d! m7 v- B4 o: n: u+ ~( b( f
    @Value的用法5 b2 a  C$ e2 c" R
    8 ]  w) Q, t9 C0 M3 J
    @Value数据来源7 ]& p& x- [* G' ?
    # C9 e, e9 e6 k0 V  V. J
    @Value动态刷新的问题" Y& n3 X0 |2 x$ S' F3 B

    . U/ w) U5 ?( x/ |/ h0 I下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。# w2 \# E+ D6 n; {% P+ |6 x

    3 P, K; E+ x! A8 y4 T@Value的用法. Y1 g% K# Q  g) v& S+ A. u- B

    ; T9 M% T! q( Q' O  _, U系统中需要连接db,连接db有很多配置信息。
    * `: }% {9 M* n3 I7 J6 R+ ]' b" ], l& X' |. _
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    2 x( a' W( k1 i6 q% g' l* i
    $ R% g/ N+ t( T; M: W还有其他的一些配置信息。
    3 Z5 ^% J8 v& q+ w% f4 q& c1 u/ Y& s5 L
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。* I* i) ?* G" U8 A
    ) a; s! X/ @' n
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。3 M0 M4 b+ V! o3 o+ w: r

    % I- T! s% |+ W$ K通常我们会将配置信息以key=value的形式存储在properties配置文件中。) T1 ]7 S9 E" @6 ]" O, P2 T
    & V3 Y' |$ x' t
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。  a" ^3 \0 R/ [
    6 N% }) A- [/ l  H1 V
    @Value使用步骤
    5 z3 j' s" E5 w8 W, X: T1 V& ]+ o) Z8 S, A' k0 Z6 o3 t: R
    步骤一:使用@PropertySource注解引入配置文件
    - t" {& h  F( d7 |+ @0 k/ v; A/ x' V- ~
    将@PropertySource放在类上面,如下0 ?, J2 w0 t  @' l

    6 V/ V5 J* ]+ n0 I+ Y: U@PropertySource({"配置文件路径1","配置文件路径2"...})
      G! h" {+ v5 a2 o/ k- N9 J@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    ( P: K" ~* R- {8 M2 `) O$ ~) F  ]: b' x  w
    如:
    0 j- Z* b! K+ K; p. ^, q+ x7 f$ h% y  ~- d2 x& y, C! H4 P. ]% X
    @Component
    0 M& A! r4 X  A% I4 i@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})' c# u( ]# q  t, V
    public class DbConfig {
    - G# e4 b# l" a5 y; t}
    5 s+ A  O/ K+ c" F9 M' q; e步骤二:使用@Value注解引用配置文件的值3 O' j# N* A/ a: I8 q2 W2 ^
    1 H# ?. [' c* @9 `. D
    通过@Value引用上面配置文件中的值:: P0 D5 O* o; b5 ~0 p
    $ m! Q- _3 f1 t' |
    语法
    4 y& z3 {* q* R# q6 g* j- u( ~& E1 }1 p* T
    @Value("${配置文件中的key:默认值}")
    9 w; Y$ ~( j* b$ V@Value("${配置文件中的key}")0 `6 l; ]! V5 {. O/ S3 W! F4 L) e& z0 a; J
    如:
    6 p8 A0 i- o- n! I9 |- z' i, `1 @( X! j4 b
    @Value("${password:123}")7 S& B' }, ]; z/ D* m' u& k9 V3 X
    上面如果password不存在,将123作为值
    4 i7 M0 ^3 I7 s, E( x, W; V0 B0 n: l8 w: x6 t* k+ l6 c6 @' T
    @Value("${password}")) t7 n6 s: Z$ |$ i6 t- t, s
    上面如果password不存在,值为${password}
    / \, ^& j, y& }" g4 H" G3 J1 i8 I% F
    # X; b! n" s$ }2 L1 z& f7 m+ C假如配置文件如下2 e3 d4 L/ ]8 U/ G2 J* Q% |

    8 s+ A; P$ T3 E$ o2 z. cjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-88 P  e: X8 U; y5 t8 ~6 P, b
    jdbc.username=javacode
    0 C" p! k$ i6 _7 F% H7 a/ Qjdbc.password=javacode
    ! X& G0 V8 S' u使用方式如下:
    1 e2 X/ M" x6 ?& b' M8 I
    . G8 K& _, B) u, \% H; f@Value("${jdbc.url}")
    ; b, V/ P1 `+ q2 i) q' Bprivate String url;2 s* e/ }7 [+ n3 U0 }
    6 ?& i3 E, q7 W+ {# s2 h4 h
    @Value("${jdbc.username}")
    1 U$ [2 w) I# Sprivate String username;
    # I6 f$ {- `, H8 l* l
    # _; u2 T' v/ R/ _$ j8 R@Value("${jdbc.password}")
    $ K, I/ y2 k( N5 ^  _) oprivate String password;  O, x+ g! i) ?9 d. g4 k2 s
    下面来看案例) @$ O- S  a4 O

    1 Q- Q( Q) `2 n6 C0 k9 v  O案例
    3 N: G, w2 B- I$ J9 B. ~, A' j8 M2 u8 }1 l; u" e; ~& b
    来个配置文件db.properties
    " y0 G* \, V9 X, P' `% D2 N
    2 l1 @/ ~% }3 ^: E8 h+ Zjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8/ [1 |( d0 V0 x; H# o
    jdbc.username=javacode4 h9 x4 a7 Y) T* P7 y) R( Z
    jdbc.password=javacode3 L7 d0 F3 |$ ~4 n3 F6 Z, ]/ b
    来个配置类,使用@PropertySource引入上面的配置文件
      G2 I/ z# r9 a! H$ c( K0 k) n6 B! d: m
    package com.javacode2018.lesson002.demo18.test1;# l" X: g$ s- B
    8 ?, y1 v+ s& `2 c8 M, c4 p
    import org.springframework.beans.factory.annotation.Configurable;" N: ?/ J1 _5 m$ j
    import org.springframework.context.annotation.ComponentScan;
    7 j8 r: ]# K! S; g5 |3 `) e2 yimport org.springframework.context.annotation.PropertySource;/ d! a( B! Z9 V3 ]: s: [
    8 R4 d+ k) E0 o) S4 ^* c. k" d* G& C2 ]
    @Configurable: [7 M9 T. x+ y+ |: \0 V: J
    @ComponentScan
    2 b3 ~8 t+ N! r7 a7 K@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}). K' O/ m+ b. }; E# G) V$ _
    public class MainConfig1 {9 P' x' ]# l4 U' m
    }
    " U2 m5 Q7 t6 o% w来个类,使用@Value来使用配置文件中的信息: G% z% I8 V# q+ s/ J& D4 }* `* G/ b

    , i, h8 a5 e, _! I. gpackage com.javacode2018.lesson002.demo18.test1;  y* Z. ]& j7 O

    ' t9 R$ s, B- ^1 Y  w8 v' n% wimport org.springframework.beans.factory.annotation.Value;
    0 B9 |7 }, R% l: f  T, b/ _import org.springframework.stereotype.Component;
    0 j& |' o4 V% x$ F: m$ |: r& O2 m1 c
    4 y' {5 r" c# H" G) X@Component4 T5 B  ~  t/ H) h
    public class DbConfig {
      V9 l4 M9 s1 }6 H" e! ]" i8 a, K  t  s- ~) Q! J8 I* z
        @Value("${jdbc.url}"): j; z% O) Z( F& K
        private String url;
    + Q& S0 @. Y; {7 X% Y
    / r% R7 q( {5 A* b    @Value("${jdbc.username}")
    4 z. g' J  J4 @2 s" S9 `2 L    private String username;4 j) r8 R: J4 B, N7 K

    $ U* S5 k( b3 o2 Q4 X' q6 @    @Value("${jdbc.password}")
    ( p; k0 u7 ]3 x  W& {# M  l0 J    private String password;
    . P3 a5 N: E1 O8 X0 w4 |8 R- a% o9 h7 g( w5 ^
        public String getUrl() {
    # B% J. V6 t+ m: V        return url;# J% s* M# b% M6 @" K- E- C
        }
    4 c1 x1 ?: E6 p1 D0 G& n8 P) c
    " Y1 Y1 a) I) J' x    public void setUrl(String url) {- [4 Y0 |6 t8 R$ g8 p6 K
            this.url = url;! F2 a2 S5 V9 G
        }
    " M. `! {4 v  [% A& i
    % ?4 m3 `4 c9 a$ @    public String getUsername() {6 o5 L* n7 V9 o. x2 E9 H& h
            return username;
    + q4 ^6 g% O0 m    }
    , a$ L" X4 G# I2 H9 t& G$ W
      n2 Y. _0 Z$ c& z: `2 O    public void setUsername(String username) {0 p' o* L4 k( N' B5 L
            this.username = username;% A- n% Y0 E. f2 g, S) A
        }
    # \& q% ], J, C. `0 i; E- D- |- D! f. b- z2 B
        public String getPassword() {+ k- x( \9 ]& z/ y: W# G& j
            return password;
    # A5 I; b9 n/ p, P& R0 V9 N( h    }
    $ }- `2 t! x$ L* _" l  Q9 K! M0 ?$ E$ V. E  A: F8 [& X
        public void setPassword(String password) {8 {- p) Q+ d6 X! l' s2 h
            this.password = password;
    / B0 c4 x: L# z9 h    }) Y& ^% L3 Q' Q
    ' q- J1 l( g* w/ Z/ q4 q
        @Override
    % `% ]9 c, h/ c% g    public String toString() {
    5 k' Y& H# p7 x5 X' I6 B) ]7 q        return "DbConfig{" +8 h+ v5 X8 u$ M6 o7 [6 Y
                    "url='" + url + '\'' +' ]% |7 W7 Z( q" G& d( H
                    ", username='" + username + '\'' +
    . y; Z' Y& _; d: n                ", password='" + password + '\'' +
    2 s9 x. n9 k5 [2 V1 b1 s4 P+ l5 k                '}';7 _$ Y5 G" q' V7 O! v$ R% J
        }
    + L/ k8 a- @7 {5 y; w3 j}& t4 c4 e' U  t
    上面重点在于注解@Value注解,注意@Value注解中的; z# @$ Y% J: u6 k. B

      r. u- S! h9 \; B6 ?5 v0 Y来个测试用例
    $ E* h+ q4 l( i5 X; k% M# x7 z, ]  o" s+ }% `/ K3 \) [
    package com.javacode2018.lesson002.demo18;  g8 n- j0 L5 w4 U" a7 I0 ~

    : @" n- ^$ W" T! x2 ^" `8 fimport com.javacode2018.lesson002.demo18.test1.DbConfig;) u# ^' K) o, I! l
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;3 P8 S- q. D4 r) k- L4 M, Y+ S7 Y. k
    import org.junit.Test;  B, Y) ~) h! C5 Z7 G" x; v7 q( S
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;- O% Y9 l: Z3 t7 \

    % Y0 x  P# F) k: i, ]8 f0 Cpublic class ValueTest {
    $ ?. f) d8 T" O5 d( P
    + l! e- _3 e* }  P7 U    @Test
    " }" U5 u4 J- q9 f( y* F    public void test1() {% |* S3 n' t% v; B! {1 e, B- ?( {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    " J& ]  d4 M; b# `5 n% Y9 a        context.register(MainConfig1.class);8 W! G' E; z. s6 b
            context.refresh();4 u+ l2 F7 U2 ~+ c0 i! W
    4 p8 u; n, s$ n2 {  R" [! `  {  [
            DbConfig dbConfig = context.getBean(DbConfig.class);
      R' |: Q4 A! L/ _. A        System.out.println(dbConfig);; \9 Z' p; |4 d( f) y
        }& e$ s/ q( P% W4 j: Y3 y
    }6 W9 s) C  z& \0 ^5 E: e: P$ l
    运行输出
    ) ^) z# w* T- x) j- `+ s4 x8 g% Y/ a8 T" k$ W' ^
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}/ Y' \+ i, y2 v7 E: }; F. A1 F0 a  W8 Y
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。; j6 C% }6 G: J2 ^5 I+ {1 U
    / H) U- a) d6 W) H1 x! p6 W+ {5 u
    @Value数据来源
    ( b  k3 u) K& V  q
    9 A5 u( j! P# x, k通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    - b6 s4 x0 t5 e8 M7 x$ ]% ]# e2 v; A" ~2 F
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    - Y7 q. d- c( v. l/ C$ i# w5 U) z" b7 z/ j. \5 s) g
    spring中有个类
      o) k0 k5 R/ N. V, W/ G) C5 y5 f: K7 \* [% {, E! A; p" C( _, d% P
    org.springframework.core.env.PropertySource4 V! s/ L/ c% M, g6 ?, Z
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息) v' J1 W3 L: K3 y" p
    3 o3 y. _4 g5 d' v6 u2 b: H
    内部有个方法:8 a  E# Q0 T+ B5 L' O& t

    ! k5 H& l0 i3 X+ K4 v0 s6 epublic abstract Object getProperty(String name);9 C. I8 p# x9 D) s" d! \4 x! v
    通过name获取对应的配置信息。
    0 F  _$ _; w6 e' f: @2 m) R# B5 E/ T6 E3 {
    系统有个比较重要的接口
    & n6 z- D6 _; q. Y  I3 B3 v; P1 ~2 \7 G
    org.springframework.core.env.Environment
    / \) L% S' x% \- d, p* g+ l$ M. a用来表示环境配置信息,这个接口有几个方法比较重要0 G* W9 ^& x8 H

    8 _3 Q1 X9 k9 ~% J' I5 h) m$ S3 LString resolvePlaceholders(String text);
    0 M3 x5 h  B0 c, Q) O$ m2 VMutablePropertySources getPropertySources();1 r, V6 w# ~- Q- l- r/ Z* @' b* T
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    1 e/ W* R9 t5 {3 r- j
    / z, ]& Q0 G% F6 R# m" A/ x6 Y* n, hgetPropertySources返回MutablePropertySources对象,来看一下这个类
    6 ^" D+ `# Q. p# ?, R
    8 \2 D, @) c& A9 V2 e% bpublic class MutablePropertySources implements PropertySources {/ t; D3 z, }5 I5 d. H0 [) r

    + ~+ i. T8 n! ]" \6 q2 H/ e    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    " ], Q  w' Q1 l8 v9 j9 O* _' K
    8 z8 G. w# p) {5 p. B& M* D  A' |}
    8 b$ s( E* o, d4 F) T! F+ W内部包含一个propertySourceList列表。; ~5 n. ^# g! P/ y- B

    : G* X2 c/ a2 N  d' wspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    7 W+ {) S( i0 i6 N0 g! B, [) W) K+ U/ @
    大家可以捋一下,最终解析@Value的过程:
    - r. v: i/ ^3 {+ j3 i$ U4 \
    9 |' }2 }5 V: A7 W6 \9 O1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    . S( v) J# d% |  R$ u2. Environment内部会访问MutablePropertySources来解析2 P* n' m; J, Y5 T
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值5 C6 G4 R9 m9 M: z+ c
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。9 L( W3 ~, r0 A: ~9 w4 ?
    / Y3 o3 z5 F  ]4 U% n
    下面我们就按照这个思路来一个。
    " y4 S( i4 t. Q1 \* Y3 c
    0 J& U! v6 g2 |4 x8 W6 {2 h来个邮件配置信息类,内部使用@Value注入邮件配置信息& k9 s7 ]& L+ x
    * i( G) R/ r( {+ D
    package com.javacode2018.lesson002.demo18.test2;6 P9 f/ \0 e2 d" ]# M) N4 Z4 o
    + O8 D9 h. Q+ k; A( G9 Y0 r; L
    import org.springframework.beans.factory.annotation.Value;
    6 z3 Z  e5 w- m+ v6 D( Pimport org.springframework.stereotype.Component;
    # i/ U" o8 }2 [+ h, [) h5 _3 b
    7 H7 Q+ E( O3 i3 Y: _, H; j/**
    - U8 M' b4 e( {& h3 E; K * 邮件配置信息
    ) W; T4 H6 _7 F) T */
    / ~: P& `% G0 t* F@Component
    9 G; D8 ~: Q& `' ]8 Gpublic class MailConfig {
    1 j  S7 F6 a& A2 S; t
    8 t2 b. L& [1 n: D" {6 c    @Value("${mail.host}")
    6 G& B- f1 K) i, a    private String host;
    2 U( b" i7 e; U  A- r- P" Z! ^0 t, U( [( a
        @Value("${mail.username}")) r! v: k: P# X0 b  V( n. j
        private String username;$ [9 h: I  u8 A, J& i6 Y
    " A% T7 i) g2 t4 a4 K- y- n
        @Value("${mail.password}")% x+ X! M) H, K5 b7 k) Y+ {
        private String password;
      j8 U$ V, O4 I* h+ l  n5 i: [$ B; H$ {+ @2 `7 S2 q
        public String getHost() {
    8 [/ W$ ]$ \' G/ b& O5 y        return host;
    : R9 Q( o+ I0 u2 ?7 V( u/ Y    }
    2 d/ `% Z3 }8 A8 s' }8 `' q5 {- W5 r( e8 _% {0 h
        public void setHost(String host) {% L/ x+ R, m  d3 Y  z
            this.host = host;) \6 O6 r! q; Q: [0 X' a8 C* K
        }
    - ?% [4 V& f% p% Z% ~, k. W9 @
    8 V+ Q) Q3 V$ U: F8 X" G    public String getUsername() {/ r" O; z) D0 n! k! M# `2 S# ~
            return username;) t& E' P3 x! t+ R6 c- ^
        }2 X) @7 t  Q7 q; f7 Q' l

    , ]9 ]" H% S" B- k" [    public void setUsername(String username) {1 b  I. N3 [1 W- n8 s8 f
            this.username = username;8 W/ m8 @8 _0 C+ _: G) Q! \3 i
        }0 j" H% T& k) ?# A1 K2 n

    $ F6 r, F  Y1 y0 D4 k7 Y8 y    public String getPassword() {
    1 N' D* }  z4 j: U+ A        return password;
    2 @! x, X9 \6 b: a9 O( d    }  U7 z: S  S3 I0 w: P

    * m5 S) ?% Q- R/ @5 s6 b: q    public void setPassword(String password) {1 ?( f% M+ o) }. C; p: L7 O5 Z
            this.password = password;$ c5 b8 x  ?- F) b4 q
        }
    4 o" @# _3 z4 L* y; t. {/ _) o1 k, d1 @) v2 p" o5 d. |' \
        @Override
    4 T5 Z! e+ ]. H  t    public String toString() {3 }* e- z- U4 Z
            return "MailConfig{" +
    ! f. w0 j% S3 u0 N% B' a                "host='" + host + '\'' +. j/ U, o4 _; O  h. S4 L3 ~9 M
                    ", username='" + username + '\'' +2 T9 E5 d% l# ~2 ~; z, A7 B5 Y) b
                    ", password='" + password + '\'' +
    . J& X! E& `, `6 ~                '}';8 Q4 R0 v  K" L, `2 ^5 c1 `
        }
    % j1 e) q+ P9 E9 h0 L}
    2 {% c* l% s  d- H再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中, Q. z6 l% z) w; x+ C
    & D  V1 k6 i+ ]2 B! G! [$ E
    package com.javacode2018.lesson002.demo18.test2;
    3 y6 C: x9 c) k5 |& J7 L
    + D0 }3 `  M5 [2 z5 S, himport java.util.HashMap;" Q3 }2 T. Q/ k4 _! |) Q
    import java.util.Map;) Q8 T1 c2 t  ~

    + w. s4 B7 }% _; p( T, O/ a0 xpublic class DbUtil {
    ( _/ C' @9 U0 C    /**' H. [% C" @+ K+ E
         * 模拟从db中获取邮件配置信息
    1 n8 A/ y+ K# o6 d2 r0 |/ z# [. e2 F     *
    , [, T& S/ R1 e9 [% t0 n/ h     * @return
    9 V3 ~) W& d" Y) W5 R6 @2 R+ F; J     */+ F. s) ]9 S2 K5 T, J0 k
        public static Map<String, Object> getMailInfoFromDb() {
    ( Q- B" ~5 O% K  c/ t6 [        Map<String, Object> result = new HashMap<>();
    6 p6 T7 y3 |# K" d        result.put("mail.host", "smtp.qq.com");
    7 i8 z/ I$ y0 Y3 ?5 {5 b* I: k        result.put("mail.username", "路人");0 z% Y, f; S  t8 w. A  Q# J
            result.put("mail.password", "123");! j+ V8 N5 a: \0 E& p9 E; q
            return result;
    6 _6 A8 q+ x, D6 h    }
    8 U) |$ ^7 J4 A& Q4 Q- l$ @}
    & [: s3 m, C5 \! G  i来个spring配置类5 x" f( q5 F+ ~, p" E# J
    6 `) _/ \7 H7 C. l; M9 @
    package com.javacode2018.lesson002.demo18.test2;
    $ X, y' B' i* y6 \" H. a, x/ f" f
    / c$ e" E4 d, W9 R, O& q: Gimport org.springframework.context.annotation.ComponentScan;9 u, W6 D9 o$ K4 t& e% Q* N# b
    import org.springframework.context.annotation.Configuration;
    ( N! e& a: t- B9 _" f; S% W6 M, i% I. X3 A4 m( W) d
    @Configuration
    " M: Q3 O3 `% l2 Y  u0 [@ComponentScan; l8 @8 T. ^- `
    public class MainConfig2 {
    1 r% c! m; j" u) u6 c}
    / V; W  R1 }( O& T下面是重点代码/ Z4 H4 g& j  B  a
    , A# n  R2 a" j6 ~- {
    @Test
    ' \) ?5 P* w# I; v9 _public void test2() {& j5 R6 f' n; L
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();, M* Z2 V" c/ t
    . I* K9 {8 j) N. C& @
        /*下面这段是关键 start*/7 X: t2 ?& s7 I; y$ J& ~5 a8 A) r
        //模拟从db中获取配置信息
    # C& @4 L' a/ l7 s2 s    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    , ^4 W' J& I5 m# C3 J0 D( V5 ^    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    9 D" v; c' u' `+ a- J+ F! z5 i    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( a# W& w: Q. R. g* t# p3 G    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高, z! h5 p/ w  K8 Y
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ( F* ~# Z/ \' D    /*上面这段是关键 end*/
    0 L! y. E! I! F9 o# a8 ^. P8 C* S5 m7 ~0 a
        context.register(MainConfig2.class);
    , {* y2 n# g, E0 N, Q$ W) f2 ?    context.refresh();, o8 ?, j9 Y+ [: U4 O, G) H
        MailConfig mailConfig = context.getBean(MailConfig.class);
    4 G8 r1 c/ q9 P9 C! ^    System.out.println(mailConfig);0 g% ?9 [7 S& o
    }
    5 r% Z6 k& T6 I8 M: d/ |注释比较详细,就不详细解释了。
    4 Z% a+ g2 \- a* a4 h6 Q  Y$ A7 A
    3 d  b2 C& l% U+ ?8 H6 P直接运行,看效果3 x! {0 ~4 J$ m) [4 i* l5 f

    ; `6 f3 y1 T# s8 z2 N, i2 SMailConfig{host='smtp.qq.com', username='路人', password='123'}: ^# e7 I' Y1 \9 G/ S9 A, f
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。5 c( s5 s6 o9 q4 C

    $ {2 t  i5 ~' f/ k9 y8 ?! w上面重点是下面这段代码,大家需要理解9 c$ ]# `* e& b% x+ j2 Z! k  _  S
      Z$ f( y2 U* g/ k
    /*下面这段是关键 start*/
    ' K% b/ I  c% R- c  T& b; `  z//模拟从db中获取配置信息7 D* U( |# t) N$ V6 r
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ! x: z% w( ^( ?% q( P9 e+ H3 l//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)* g& s. e- c) `& i# ?# ~
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);6 |. v1 d$ _9 ^. p, |# @
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ' {8 n# z9 e4 P2 S* v9 J: a4 ?' Hcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);2 s/ r2 z4 j2 _4 n
    /*上面这段是关键 end*/
    7 ~5 E" e8 M2 u% I" G# |咱们继续看下一个问题
    9 ^( f# Q% i# j2 {% M
    4 ]9 @" o  P) b4 g* P3 S3 U2 @6 I$ k如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。8 k8 `0 x+ `/ X+ z9 ]$ y% J
    5 B  u6 U3 c  F6 N
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。9 O, d* q8 ^/ d3 O2 Q
    6 v5 d4 }3 a3 T: ^0 N
    实现@Value动态刷新+ k$ l7 o+ t4 ?

    & t5 e1 K2 ?2 G7 B) e. t% \* q1 e, C先了解一个知识点
    6 ~' @# y* z+ b6 _0 T8 J
    # k0 o" D/ P  G& i2 G这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。, |) d; D" A) c+ N. w( q5 A% P

    9 \0 I* ?  }# P这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解8 E4 c- l6 M* D
    ; L* }, F- g. B. {2 h! \
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:4 w3 Z0 d; O+ `+ I- t% A

    4 k# X' n, n: }9 ^) d) a; }5 k" l% vScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    ' h$ E. ]/ n  h2 Q' X5 h3 D) W( D* r' R这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    $ S9 k. U! c) H& a& K( R1 b7 q3 y" g! {) R1 X( y
    public enum ScopedProxyMode {2 a& o& U1 X. g
        DEFAULT,0 a( L- F1 B. U8 @* c4 |/ @8 G
        NO,8 |7 Q* p0 C! K3 I- u% D; e0 s
        INTERFACES,
    % i# f, B0 s- S6 |0 p# t% H0 A  O    TARGET_CLASS;' h, {6 S* Y! e, M& r7 ]% s
    }
    & Y' ?2 E2 h. p0 {前面3个,不讲了,直接讲最后一个值是干什么的。
    & ^9 P% F2 Q$ i2 m. Y" C1 X& _' V2 Z+ h8 n) O# E( ]/ ~$ S2 V
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    % q1 j7 @5 r+ S' h( P' K' o' l& T: k) h: R6 T
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。& g) f/ y/ Z; }$ D' S2 c
    1 f/ m" @$ g# z1 H# s
    自定义一个bean作用域的注解4 S8 x2 Y. _* O7 ]) S

    3 I, k  m  T+ ~% |4 g) Tpackage com.javacode2018.lesson002.demo18.test3;
    ) o& F( {7 [" f9 p' |4 W  B
    1 U( y' ~2 P- M+ mimport org.springframework.context.annotation.Scope;7 m3 U4 u* r' {; Z- I6 ^- t
    import org.springframework.context.annotation.ScopedProxyMode;) g/ n/ x. O# H! B

    # g/ a* H* O/ Limport java.lang.annotation.*;" c& j) D- t. b9 `( g) c
    8 ?; r5 }* u8 r: m+ D
    @Target({ElementType.TYPE, ElementType.METHOD})
    6 g% B+ A+ l! A, X- T2 H@Retention(RetentionPolicy.RUNTIME)+ ]$ ~. U) e, P5 p  _3 J
    @Documented8 K5 P  \, ^) n& L5 y+ ~; _1 d  T4 x1 k
    @Scope(BeanMyScope.SCOPE_MY) //@10 z" }% h6 W* {4 q. h& W% }3 u/ {3 l( p
    public @interface MyScope {
    : L- r, }& M4 j6 ^2 J    /**
    , c3 p, \/ e  W7 i- f8 m; R- e; W     * @see Scope#proxyMode()7 T2 p8 F" R: g7 @& c
         */
    - Q0 b( X/ ~* b# n    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@20 C9 D, t1 c, o# v
    }6 U  i6 d- f; J+ N4 @
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    0 V, C7 F2 }, Y0 g, b% a+ y3 F
    & o1 l* p* ^9 W( Q/ h% C3 ?! {5 ~! R@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS+ ?4 W9 ]- d$ Z
    6 T- Q; P# T* ?1 K5 S
    @MyScope注解对应的Scope实现如下
    ' o7 Z  f" n6 |
    ) E/ V" M( ~- v4 Tpackage com.javacode2018.lesson002.demo18.test3;/ x" N3 ]: i' c" E9 v! }9 |

    8 O/ L; S: v9 ?2 |import org.springframework.beans.factory.ObjectFactory;- v8 ?, e) Z$ S4 O+ {# b
    import org.springframework.beans.factory.config.Scope;" a& V9 R5 v) O5 K6 s
    import org.springframework.lang.Nullable;* }3 V2 W/ R" c

    - [, f( j) r! ?9 Y5 C% M" ^7 [/**
    & b' q/ g0 C5 E& F- p& ~) A* o * @see MyScope 作用域的实现
    2 H3 u; M) W3 X) h/ n( p */
    " ^* e+ D! J" [  E" I& ?; ~/ J7 gpublic class BeanMyScope implements Scope {4 d! {4 p' N# v$ j7 Q

    2 o# m4 {6 d7 T5 Q5 L; V    public static final String SCOPE_MY = "my"; //@1
    5 S' X- x& A; t( B$ [, U' r0 c8 O/ s! l% @  o
        @Override! n" l. K4 i5 N* H5 J) O/ P
        public Object get(String name, ObjectFactory<?> objectFactory) { 5 F, y1 M; z* n5 b2 l
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    : J( s9 e0 [/ p2 w- h% T) G2 i' j        return objectFactory.getObject(); //@3
    6 V" O! A' u7 |! F' g' v  {    }
    1 @5 ~, m8 I9 z! d5 W! {6 A4 Z: p8 O# G
        @Nullable
    & V" b! U: R$ \8 y    @Override
    3 Y6 I# ?& p2 N$ Z9 S  I. @# R    public Object remove(String name) {
    " q  J8 M+ @# `5 p        return null;$ r5 E* q, V% [. E1 r$ x+ G
        }
    & o, b; x+ ]2 m3 I5 R, y* T! C) F8 w9 Y8 v  _) I. L: ?
        @Override
      e8 R# {/ y7 t2 \    public void registerDestructionCallback(String name, Runnable callback) {) U; N0 p: {9 F, q' C
    " a( [4 {$ A/ C. @
        }
    $ o0 y- i7 f! p: @- O+ p  E+ a* l2 t1 T$ A
        @Nullable
    4 q4 p/ V" I. ?* ]+ b    @Override
    7 _% h4 X! c3 ]% u" ]# e3 ]! t    public Object resolveContextualObject(String key) {
    # r/ O8 }4 t( H, D        return null;: P8 Z/ H) ~! X
        }
    5 K% s. @8 J5 l% G& ?/ _
    0 |  H- O9 s1 O. y* u. G    @Nullable
    5 t6 F- F( `3 l+ h% [9 ^    @Override4 O  _0 u( c' N1 p
        public String getConversationId() {
    6 [$ E& `6 `4 F. E4 V        return null;1 b; t# i. s8 k5 e
        }8 ?& L' |1 z+ A4 g8 y
    }2 C4 o& u6 s, p& I% \
    @1:定义了一个常量,作为作用域的值# x1 j* E7 }, _- P# c5 g/ |. K0 u

    8 o% ^0 M# d  H0 H@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    8 t7 x+ W5 ^% Q% F( p
    ( N2 G4 {3 U$ G! W/ Y# ]@3:通过objectFactory.getObject()获取bean实例返回。
    - \5 W* I2 i9 T0 s$ ~5 d5 i7 Q% G& g- P0 N4 e% Z" V% H9 \
    下面来创建个类,作用域为上面自定义的作用域) p( C: _* C; A6 b. P
    - l% r$ s% m5 l
    package com.javacode2018.lesson002.demo18.test3;
    8 R) l9 c0 c* b6 F# p6 {" N
    - @- R6 b& m0 ^# o4 }, m3 `3 Aimport org.springframework.stereotype.Component;6 D8 E; A6 D# ?, \  m
    6 ~( ?: ?  n) b/ O
    import java.util.UUID;
      k! k& o% T5 R  }9 g) r
    * r  I  G8 Z0 B" l@Component6 g. h* M2 }* P) f( I2 @
    @MyScope //@1 * o6 v3 ^+ m1 E1 n: a  Y
    public class User {
    3 N. {) k; N; y2 `
    % ?% b$ C2 n% l    private String username;
    * M, o7 o! Z5 S
    ' o& @. Z' y1 u$ W    public User() { " g* i9 J, h' B& ?
            System.out.println("---------创建User对象" + this); //@2& A8 b8 Z3 X& P! H; M; d$ Q
            this.username = UUID.randomUUID().toString(); //@3
    8 ?9 p; ?9 `$ }3 z& w  t4 {( V, E( C' q    }, v9 l3 F, w, Y) p; m6 b1 J

    . U. O  _+ M' r. ]    public String getUsername() {
    " G/ p9 Z  h6 p0 A# m        return username;% a  M% s1 G; y6 m
        }
    " I- \' i: ]: g5 U/ G$ U
    ( G; j0 i) N, w    public void setUsername(String username) {
    . `8 o' M, h+ O/ b1 ^' E        this.username = username;2 s0 \6 X% \  {0 w
        }3 h; }& j- `5 O, X7 @
    ) G& {$ R& R- E/ o+ l- r3 _
    }
    0 U' ^1 |+ ^* H! R@1:使用了自定义的作用域@MyScope% P+ z- @7 k! |; X* n* d& a1 e
    5 q% s0 i: w7 A5 [
    @2:构造函数中输出一行日志0 h. R0 X7 T. M. |: G
    ; a2 \% c* ~. }' Y( R; i) [" H1 q9 ?
    @3:给username赋值,通过uuid随机生成了一个; C- n* f2 `. r: c( `  B

    5 G; a" }8 ~" v6 ~# |' W来个spring配置类,加载上面@Compontent标注的组件
    2 [) ^! k) o$ Z3 a# v5 J
    1 H2 K' i; K9 O, U, B& Z$ w: dpackage com.javacode2018.lesson002.demo18.test3;
    ; F, u7 r  j: W1 t  L3 g7 T4 z6 ]: ^0 l, r# I. O; \
    import org.springframework.context.annotation.ComponentScan;
      ]) y$ z: W6 G# D3 \import org.springframework.context.annotation.Configuration;
    % K/ X2 g; G: J) k1 u" u& {
    $ q/ E7 l) S6 e  ~0 L$ j+ T- D& o@ComponentScan
    7 ?: }3 w/ r3 d@Configuration( N, E) _7 v2 ^6 ^
    public class MainConfig3 {: z* h; U7 J3 B8 U  p
    }
    : t( Z) Y! Q$ i; j* T+ H下面重点来了,测试用例# O/ s5 N! u* |9 |
    1 |' w9 w9 ^1 F$ K& S
    @Test% i2 ^4 y; m$ @3 b- x
    public void test3() throws InterruptedException {
    / G' p& R9 r2 w  N    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ( ?6 {) b( P* D  x* l# C0 L: z. S- N    //将自定义作用域注册到spring容器中" k; L" P/ `+ r3 @9 Q
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1- ^7 k% s0 m+ C8 N3 C7 J
        context.register(MainConfig3.class);1 x0 G; q9 [; l3 }6 X
        context.refresh();
    + L3 Q! @9 S( g6 r) ^6 b1 [$ C5 L
        System.out.println("从容器中获取User对象");
    # g- L! B6 W7 l& G6 b    User user = context.getBean(User.class); //@29 W4 l! {0 ]/ {$ j
        System.out.println("user对象的class为:" + user.getClass()); //@3
    - `0 C6 A4 \3 e/ L/ @) Q$ ?7 t& ?. @/ l
        System.out.println("多次调用user的getUsername感受一下效果\n");
    9 e/ n6 N( Z; ]! v4 F# k+ E: I2 z    for (int i = 1; i <= 3; i++) {
    : Q1 [4 c( H; O( n1 s' G- s( W        System.out.println(String.format("********\n第%d次开始调用getUsername", i));; o, U! W. O1 R
            System.out.println(user.getUsername());
    # }* s1 ?' l8 c% l: K( m$ f        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    # f- N) _4 u1 }2 x! R! k    }
    . n2 J8 E; j& ^. ^* a/ a2 M}; ?5 G- |( M5 T3 O5 P) h
    @1:将自定义作用域注册到spring容器中
    * ?) b8 l8 L+ S8 I% j% x- h
    + F8 ]: L( r# o' i7 P" F@2:从容器中获取User对应的bean
    8 v5 x/ u" f: D" m) y9 i) M$ L% H2 O2 a5 B+ N
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    8 [* `4 ^8 ]8 b2 ]& N7 q  K  Z* b8 I
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。0 Y5 w' D+ O7 g$ O  `

    % i# m* o1 k3 r0 P0 L见证奇迹的时候到了,运行输出6 g4 o, ~- s8 B7 R4 W" F2 R2 w6 p# I

    8 E$ \& s1 y% o6 b4 {. N9 E% W& t从容器中获取User对象  c" o. q% k, i
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    & N, u7 p# V0 D多次调用user的getUsername感受一下效果
    " j7 n  \2 ^+ ?$ f5 B2 R2 @  @% _9 K( Y
    ********
    1 [/ }2 e$ E: n& J. Q第1次开始调用getUsername' M- F4 z. i; B- i3 g1 P) f
    BeanMyScope >>>>>>>>> get:scopedTarget.user
      p- X6 P: n  Y---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4  K0 S9 X. n( |$ }+ m/ Y7 C
    7b41aa80-7569-4072-9d40-ec9bfb92f4381 J; k4 Z, I! E5 R& {: A
    第1次调用getUsername结束+ q$ i1 x  n8 G, e$ @6 }
    ********' o1 L) ~$ z  e2 M

    " e( c) |+ A6 `+ k********) s" F( E$ s6 s; |/ d2 L( A4 i3 g
    第2次开始调用getUsername! f: t) \( v' }8 R7 Q" P8 X! q! z
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    / T, Q# P3 K6 L! j+ U6 X4 {3 G---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b. L/ E0 j% j+ [6 o2 w  @8 T
    01d67154-95f6-44bb-93ab-05a34abdf51f
    5 B* R4 E. X2 U& y- {4 l) j& ^第2次调用getUsername结束" I0 E! C7 L% Z1 k& @# j2 R% b& J
    ********1 k# K8 o) n% o; V4 X5 F0 e
    / O; K8 B3 y) g! o* r  J
    ********
    5 s5 Q, b3 `9 [! C5 M第3次开始调用getUsername
    $ \9 c% x! P1 Y/ Y% K+ {BeanMyScope >>>>>>>>> get:scopedTarget.user
    * L; J8 U& `- B1 n: U9 t---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d155 Y" R$ F/ _4 v9 _& n
    76d0e86f-8331-4303-aac7-4acce0b258b88 @4 H2 n4 O& p$ D% d6 {
    第3次调用getUsername结束) Q2 o6 \6 Y( j- J# U$ V
    ********/ ?1 ?" [! ~: l1 }
    从输出的前2行可以看出:/ y9 }, d& r, T& o$ b! s
    4 F- h, e0 v7 z6 W
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    7 {" S( C) f  y: d+ O) [) W
    . W" S% m2 S% g* I% m第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    ; t3 O3 T$ \% W' l( \) o; o; t$ D3 l+ I+ L7 }8 @3 e
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    + L: [' _9 _! Q: S
    5 {) @: N$ n( x# j! ~通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    ; J5 U) K" j3 e! c
    6 _: E# g0 v* n7 l动态刷新@Value具体实现
    / v& x' f1 ^: }8 \0 B% F2 Q7 G$ @2 F+ o% v5 t* Y4 Q9 K
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。( }2 x- s% M6 H2 |
    4 X/ W: V2 ^4 t6 @# A& V
    先来自定义一个Scope:RefreshScope
    8 P1 B& I+ x- u
    , ?! Q: n+ X$ j  i& rpackage com.javacode2018.lesson002.demo18.test4;8 ^: s( r6 X2 O3 [
    % e! u8 s# e0 [+ N
    import org.springframework.context.annotation.Scope;
    2 Q. z3 o- t* t3 vimport org.springframework.context.annotation.ScopedProxyMode;
    + Z. U# q9 v8 m( D& E) q" W  J2 G
    import java.lang.annotation.*;
    , y1 ]0 O& d# y6 `9 Z  o% r* i% z# O6 D
    @Target({ElementType.TYPE, ElementType.METHOD})
    $ W5 t% }/ f5 F# b! x) k* j$ P0 k* J@Retention(RetentionPolicy.RUNTIME)
    7 y1 Y* g' r+ v/ g@Scope(BeanRefreshScope.SCOPE_REFRESH)
    6 H( b( w0 C. J1 f$ V@Documented2 M- N9 \, K, T$ ?+ Z+ T- d
    public @interface RefreshScope {
    4 M6 D* y+ A, j; F& q    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    2 @& b$ q1 T* o4 |4 @}
    - T( S4 R" G7 @4 ^( S要求标注@RefreshScope注解的类支持动态刷新@Value的配置: d9 y- g3 v# h7 y
    8 I. J; m% h* S' q
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS, [4 c% G$ `, W  [
    ! J6 c- w* ^. e' q2 h5 {
    这个自定义Scope对应的解析类, p% L' A7 Q" n( `4 Z; P

    * q8 ~$ K" z! n  T" Z下面类中有几个无关的方法去掉了,可以忽略
    . ]0 ?9 c* A! x3 C+ u$ E4 m6 @( j# [, a5 n' ~* ^
    package com.javacode2018.lesson002.demo18.test4;# c! {  d' a; J. o0 r

      t% D% q  `" g' O" t" j! \
    9 I& E: p, x. u5 W. O8 |import org.springframework.beans.factory.ObjectFactory;4 w+ K/ {  e2 M  o6 ]* y
    import org.springframework.beans.factory.config.Scope;
    # E: Y* H3 ]4 o4 ^. ^# [import org.springframework.lang.Nullable;6 Q7 f& e9 \1 {  @$ R

    - L# B9 x. [4 c* [$ b/ @import java.util.concurrent.ConcurrentHashMap;
    ( ^4 }% ?" e& B! i0 p2 x" N
    % X5 b# ?9 E" V$ r4 K8 Kpublic class BeanRefreshScope implements Scope {
    , e) Y" b1 L9 g' f2 m4 ?  r+ z
    & B9 k5 ~/ a( V$ K+ J3 R  H2 q8 L    public static final String SCOPE_REFRESH = "refresh";! }1 t- v' W* s6 K: }

    . I6 V2 n- F: }8 k8 b, c    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    7 I" `+ d+ u- F( ^8 ~5 m
    + Y* q) K$ d. {    //来个map用来缓存bean
    7 Y' z' m! G1 e/ H' V4 b. |    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    6 t8 y$ i; W% W8 ^4 r
    5 q+ \0 L) m0 ~! g$ y7 `1 }    private BeanRefreshScope() {2 m8 W9 X, V! h, q: v0 b
        }
    7 o  q9 N  }6 D7 |6 [$ Y* p
    : F* L% g# b* t2 _- g    public static BeanRefreshScope getInstance() {& j- ]; p/ v" o. p! Q5 A$ q2 I
            return INSTANCE;
    ) I8 f, Z  q7 r* n2 m    }5 k* F+ q) W. r# R9 _2 W( h3 |% v

    5 N' Q  K2 ]% M. U' L% f    /**
    * i# ?" q1 j, ]( g     * 清理当前7 W) H  g: ?1 i- S4 x4 _& f
         */! {1 f# ~# d, {8 v
        public static void clean() {8 S" B% P/ F" V$ }$ ?1 u
            INSTANCE.beanMap.clear();
    1 Q. m& h& S+ L% o* z1 m) K  z    }
    + b+ p# X1 k2 c1 J9 w2 V5 V, R  Q1 b  S8 L
        @Override
    ! @& J2 P# D+ p3 ]    public Object get(String name, ObjectFactory<?> objectFactory) {
    + p9 o) D2 G/ z. O  r        Object bean = beanMap.get(name);
    % h% x! o7 O# w2 h        if (bean == null) {
    ( b, @7 w2 h0 |# X            bean = objectFactory.getObject();) \. q% u/ J4 N3 g  \
                beanMap.put(name, bean);  j& Q8 h' i3 `4 o9 C
            }" N2 z/ i$ y9 \+ o/ d* }% E$ z( q
            return bean;$ l7 p) _$ e+ x
        }; p) [0 W9 _" H
    $ p* z9 x/ @' B- J# {+ C" @5 U
    }, M* O; C- C9 V' K" i1 Y) Z
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中: }8 L* V6 s$ E2 T

    - X# Y2 h2 g! L6 t0 K9 H7 ?上面的clean方法用来清理beanMap中当前已缓存的所有bean( L0 l  @+ R, v: c2 x. O3 g
    4 S" b" Z2 E* N* }5 t0 m; B
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope1 ~4 X0 x2 Z' q( ?

    4 w/ k) I6 Z8 O% H4 r- R% T4 fpackage com.javacode2018.lesson002.demo18.test4;# T7 d$ J' J1 P5 k" n/ [; T

      A7 q6 T1 B$ c4 _* wimport org.springframework.beans.factory.annotation.Value;
    $ |; m! l# ]: x' e6 t7 `6 Uimport org.springframework.stereotype.Component;
    1 ]( v# A6 Y3 L5 L
    & _$ m' C; M' D2 N1 x/**0 L$ g) w8 Z, X4 I, _3 [
    * 邮件配置信息6 ~' s& W5 g) @4 |
    */
    9 _7 ^  C9 u: w$ m5 s3 _+ ^4 Y, B@Component( @8 ?) K/ N2 _( `
    @RefreshScope //@1
    " ~; {. A% E3 A7 O; Y, ^$ xpublic class MailConfig {
    7 |& J- b6 {# `& u8 ~; h& O
    ; E3 @+ [( w; d7 g    @Value("${mail.username}") //@2$ z+ Z, u. n& q8 |$ u  i
        private String username;8 j# h* G4 \0 s! p) O* W' s

    4 L7 T( u! I% q) E7 M' y    public String getUsername() {
    # E# r+ N5 b  Y4 V- n* K7 w2 g2 }2 x        return username;4 v6 O! y6 Q$ L: u; D: K
        }1 T# `+ f, V9 ~3 N- @7 R

    ( n% T# ?# M" H$ g+ A+ O+ s    public void setUsername(String username) {) I  m" E1 @: T& B8 N9 ]- @
            this.username = username;
    * K- e0 M7 _! k    }( M: [8 x6 n* f* V, Y

    0 I5 g# Z) [( o0 ?    @Override
    / R  F/ Y' w+ h9 E  m2 u    public String toString() {5 M1 o0 H+ d2 w% \
            return "MailConfig{" +
    5 _$ r' x4 o. S" v& a                "username='" + username + '\'' +
    5 v* l1 S; X* M; r                '}';
    : F. V. c! t- {7 [    }
    4 H& |, ?6 x/ i9 g" r! Z& _}
    7 y4 d1 ^; _% v@1:使用了自定义的作用域@RefreshScope$ n5 V; g: W: [8 L1 K4 a
    ( f) F  k; Y" e
    @2:通过@Value注入mail.username对一个的值4 N9 {* c* T1 ^6 H7 A9 {. h
    2 i; n( z+ F( S9 m% l. H
    重写了toString方法,一会测试时候可以看效果。/ K5 ^! i" }# l
    , d2 {, ?. Q4 k- W2 \. K
    再来个普通的bean,内部会注入MailConfig& ~2 d9 g. ^4 D: Z

    1 z  h4 t9 L+ g! ~package com.javacode2018.lesson002.demo18.test4;/ Y$ P8 E6 ~- ]9 f5 Y; _
    % {: M* t; g$ ~6 f1 S8 \
    import org.springframework.beans.factory.annotation.Autowired;
    8 |$ k9 O1 h% F- i$ limport org.springframework.stereotype.Component;! g# L7 E" Q1 |) h* y7 v4 Z
    # ^! X" e& z, h
    @Component
    ; X1 b7 b. A9 i# v  f! ppublic class MailService {
    : i  z7 H' o; F    @Autowired1 R# Z$ o$ a) p) s7 o* N+ A) L
        private MailConfig mailConfig;' S$ V+ {' y- v' G
    1 @8 _5 C% e4 I" ~) D
        @Override
    : [9 X2 w9 t% l5 H; ^* ]    public String toString() {4 j( Z( q2 \& W% n$ ^4 ?9 W) @' u
            return "MailService{" +  x' V0 J3 X1 v6 O$ K1 i8 ?8 B
                    "mailConfig=" + mailConfig +- Q; g+ [" c. R8 [" ]1 s5 }
                    '}';3 |. {/ P3 V" d$ F- \' y
        }
    # l; }, Y( f: B: w& @}
    6 c$ x" A- A- P. ~代码比较简单,重写了toString方法,一会测试时候可以看效果。
    ( O: {1 G' v$ v7 @1 ?2 G+ F9 i. d6 d
    * M: N( Z4 t7 t来个类,用来从db中获取邮件配置信息8 q, O. T; G0 o& K. j  T( Y
    / P& y  U7 Y# T: m/ n% `9 F
    package com.javacode2018.lesson002.demo18.test4;
    ; @  T6 C9 \4 v( ^+ A' }! p1 Y8 l% G, r  T9 w( ?$ M  H. n2 W1 S; I& {$ f
    import java.util.HashMap;
    , Q0 W' K/ b+ X) o$ Kimport java.util.Map;& C6 G" u8 f# H2 O7 r3 O$ G
    import java.util.UUID;! X$ G" T5 H1 F& m2 N* t

    9 h/ z8 \: E! {0 bpublic class DbUtil {: y+ F1 E1 D! j- i. c! A7 ^7 x& Q
        /**/ @7 I  H- {5 x+ \0 y1 k5 g
         * 模拟从db中获取邮件配置信息
    9 Z4 K8 b; D1 f& H- b) W, }" L  f     *
    % q, J2 w/ l4 {     * @return
    4 S) z: i0 n9 \7 ^! R5 ^     */$ F: j) F; P5 ^
        public static Map<String, Object> getMailInfoFromDb() {1 D$ K0 `# a& V- o+ [) V
            Map<String, Object> result = new HashMap<>();
    1 r, V: R- A. p- X, P, S        result.put("mail.username", UUID.randomUUID().toString());
    4 G' Z/ F- f/ A        return result;
    % f5 N" v8 f9 A+ J& D! U) ~    }; I# `7 J4 Q9 s# D6 ^5 E
    }
    $ O6 T  T% O2 a/ d/ ]& R' }来个spring配置类,扫描加载上面的组件' a1 G2 n, g/ W& O. `: C  \) V- `
      M" y0 @1 I3 B3 I5 U
    package com.javacode2018.lesson002.demo18.test4;" W, L* J2 m5 v
    6 j6 m' g* ?/ a6 ^( T" n
    import org.springframework.context.annotation.ComponentScan;* F1 j3 [9 i. j% z) |
    import org.springframework.context.annotation.Configuration;
    / R. J  P1 @, f
    4 S5 `- B. U  c: J' }@Configuration& s- ]% X& o: B3 L
    @ComponentScan% N  u& G6 W0 w' P5 |4 c1 M7 x
    public class MainConfig4 {1 }( {0 E. j5 C( C( [
    }( l* J" O7 r( u' \1 Z
    来个工具类3 y+ t% n( `" `9 C7 k0 f2 f' t, D
    ) F; M4 X5 |- E) [% j
    内部有2个方法,如下:
    ! p5 k$ _7 u8 o* n5 T! {& m& E0 S# i5 U; ~
    package com.javacode2018.lesson002.demo18.test4;
    , ?6 g! G$ z9 P4 U5 U  P: e
    $ s+ d9 n) k' H  B! |9 limport org.springframework.context.support.AbstractApplicationContext;: C: J" h+ d5 @" B# l% ]
    import org.springframework.core.env.MapPropertySource;& L$ D: m% ^8 s3 l7 Q3 X& k! C
    4 Z" y6 v  F2 C
    import java.util.Map;
    ( z1 R6 c; R6 g8 ]
    ( a* Q/ u5 z( L9 Bpublic class RefreshConfigUtil {
    2 T$ e4 T' ]2 w) I* J+ D3 J! n% |% r    /**) v6 @4 T$ G% W
         * 模拟改变数据库中都配置信息. @& R4 R) N, z. t/ {% X0 c
         */
    " D/ C) s5 _$ r( f' C    public static void updateDbConfig(AbstractApplicationContext context) {
    4 @) R3 [$ Z8 T        //更新context中的mailPropertySource配置信息
    & m9 J2 M' x* Q/ r: z5 h# [2 `        refreshMailPropertySource(context);
    7 ]0 H3 Q4 C2 ^& B
    & C& H: ~) j5 ?4 {, D        //清空BeanRefreshScope中所有bean的缓存
    ! I( t2 i8 F2 r        BeanRefreshScope.getInstance().clean();3 z& a7 q# A/ @/ ~" d/ }6 t+ D$ m
        }
    5 _) y! Q" z! L
    ; |! j) E$ _* u9 L: S- K    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    9 ?  D0 k4 F) ]! N0 S        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();" W  R! a# p( ]& Q
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)) F9 D: i! n' Y7 j
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    8 M* v# X0 c+ s( L$ ~        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    , E' T# g( i9 P2 Y6 V7 u6 Y/ z    }0 W" L# w- M3 m+ k% P7 M/ L
    ; b$ W" X# J+ I  {* u, ~8 r9 w
    }
    ; Q$ U, a8 W% Q( Z1 NupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息6 _$ |: w" [. Y3 D% p0 |

    + I. r. n+ h5 B3 _8 gBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。$ s6 }" k% s: E+ u

    ( N4 A. m# p. \5 w+ }来个测试用例: A1 T+ Z! ~3 Y1 O. e

    ' c! \' v% c5 N/ ]@Test
    ) r, u# i+ V; M) j, g/ P# s6 W. Q+ ypublic void test4() throws InterruptedException {
    " s2 U! f3 w9 h4 k* F    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();( e# k: a/ J! f
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    - c' I' }$ Q+ I" z5 k    context.register(MainConfig4.class);8 Z" ]! C) @' O/ a/ [
        //刷新mail的配置到Environment, l2 L5 |3 B! Z0 f1 G/ w; S
        RefreshConfigUtil.refreshMailPropertySource(context);# w, j5 S6 N# o. d
        context.refresh();
    3 v/ D8 ^! H& k; ~! t
    1 e+ R6 y1 t2 N/ j5 I- [    MailService mailService = context.getBean(MailService.class);" N4 ]7 Q" @* o" x
        System.out.println("配置未更新的情况下,输出3次");$ v( `, \8 m$ d$ y5 P  M
        for (int i = 0; i < 3; i++) { //@1
    ( [9 i5 J) w+ \; `        System.out.println(mailService);0 k) \( o" y( e8 X5 d
            TimeUnit.MILLISECONDS.sleep(200);. ^" _3 k9 v) a* s0 V3 i) t
        }
    . L9 d5 z6 S3 b  Y" [* r4 ?
    0 K- Z# N0 R% l! n- {    System.out.println("模拟3次更新配置效果");
    4 L1 ~9 g' c% y6 _* I! h    for (int i = 0; i < 3; i++) { //@2
    4 @  g1 m$ r+ p: }        RefreshConfigUtil.updateDbConfig(context); //@3: G* Z) y# C+ p2 m8 T1 f: M
            System.out.println(mailService);
    $ a4 V  n3 I" R1 l        TimeUnit.MILLISECONDS.sleep(200);! v( L5 f/ q/ k
        }9 D: b& l3 f( X9 n" x1 j" I
    }2 c% i8 S- |( H1 }1 w/ U
    @1:循环3次,输出mailService的信息
    3 U! l1 ]/ X$ m0 k- e$ D' x  G# \/ h  i" c6 ]0 s
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息3 T2 c! J9 b# @# c  J
    8 ?8 `! }% J+ b* e
    见证奇迹的时刻,来看效果- V" U$ i5 k3 x" d# y% Z: Z

    $ J. F. K* [& C1 i配置未更新的情况下,输出3次7 O. S, }: S$ k  d0 M4 s
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    " p  Q' `( n, o3 F9 e- H) dMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    1 v" a! a5 o1 m: z7 NMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ p& s6 K1 i* B7 F9 j$ r8 C& E
    模拟3次更新配置效果
    8 W! e% j1 _3 t2 [- |; sMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    4 P  r. J) ^& z1 v9 [$ }% XMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    - |/ Y0 [6 G; \  bMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}1 [$ ~  n0 ~) _, G, D' [! l
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。4 v5 |& o0 K- r2 c" w

    4 l7 M; }- [6 e5 s% w( m小结
    # {: U2 A0 z; Z" V4 T! W# v$ ^2 {9 i# V4 U$ T' r
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    - @1 i+ V3 b  l7 q+ V! \
    + d# |! y5 G; i/ A有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    0 d2 E) C1 h  Y$ j  d4 @6 l9 u' n0 a+ w0 u0 V
    总结
    5 X) `. Y( p! f5 B0 ?
    $ B* B  H6 ~0 {+ O; a0 s* ^本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    * {/ o3 ?, y, q$ [+ U
    1 {+ @4 s, Q/ i6 V案例源码
    2 L9 I* I2 t3 U2 [$ S5 m
    4 w7 }5 `6 p7 p$ y! J% Nhttps://gitee.com/javacode2018/spring-series  q8 L( k  I7 [9 c/ ^& h+ A: T
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。3 {( ]/ @) f6 @" E
    ; X  W1 F! N5 z7 t
    Spring系列
    1 E6 |3 S5 V' [. ], i9 }
    8 h0 @, i/ V6 j3 G' w7 j- @$ y1 JSpring系列第1篇:为何要学spring?
    % ^) p, S7 O$ M; L( p. H, ?0 e0 \& o5 K
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)7 c& o1 u! }  u

    ) Q" e! o1 \2 a' F$ [# uSpring系列第3篇:Spring容器基本使用及原理* p, m' U3 J4 p- K/ ~' A3 @" Q. X& M

    , j& X. O' z, D8 T9 xSpring系列第4篇:xml中bean定义详解(-)
    8 U& P8 G7 M5 |# N- Z' [# w" y2 r# c" O) P. I: S0 q3 s& ^
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    7 Y2 p3 c5 ~* _* H+ e) H* f
    . x* f2 `/ @/ y  A4 O& g5 l' uSpring系列第6篇:玩转bean scope,避免跳坑里!4 w* m% A& A: Q: D

      ^+ t. |7 |0 p3 O- VSpring系列第7篇:依赖注入之手动注入3 d" }: i4 G  T; P. R
    + Q, ]4 l% L) `
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    ; h4 H# L# b1 {( Q& I
    1 V" Z/ |0 A/ f. m& Y; K- HSpring系列第9篇:depend-on到底是干什么的?$ w! ?7 K& R1 y* b

    # l+ ^8 ^/ Q2 s6 r2 \, y( a; YSpring系列第10篇:primary可以解决什么问题?
    $ m, u3 E* Q  b* F! L
    ; ]9 j3 V! f6 g, a4 D8 I; QSpring系列第11篇:bean中的autowire-candidate又是干什么的?! F2 O# \6 y5 r% ^4 Q0 ~4 ?$ T
    - w! j+ @) k3 r
    Spring系列第12篇:lazy-init:bean延迟初始化8 H  v# ~3 O, {* ]. E; r- ^
    : S9 Q) C7 u& |. K& y$ m
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    1 Q! T; h0 y4 Y- ]. D+ `
    " M7 V  ^' a0 u3 VSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    3 H3 a/ e' `. u; ?5 m, ?# Q. X$ S1 w6 T: Z7 P" D
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?, c! |+ [! B! G* ]' R, C

    ) g( t: f4 |& {' `  VSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)2 g0 ^, L& d- Z1 X8 Y0 w

    0 z$ N! u% Y& {Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)% L7 ^  f/ U# \

    7 c3 Z0 ?! q) nSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)9 y4 {4 g& i  N# E
    ! b% F* B& M/ E- @" n) k
    Spring系列第18篇:@import详解(bean批量注册)% Z5 X7 V8 [) }

    * ]3 Z0 V7 h) X' m9 a2 C8 I8 uSpring系列第20篇:@Conditional通过条件来控制bean的注册
    / L, v& d% Y/ R" b* r+ c/ k
    6 a4 i$ Y: k1 S# GSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    9 `" ~5 n; K3 r7 M
    2 D8 g6 ?: g% o. r0 u' RSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解, `( G, y$ E4 ?- b- Z& s

    ( K  T- ^  O: R' |% Z  w4 W5 h8 H1 BSpring系列第23篇:Bean生命周期详解6 [. {0 g6 J3 p1 G2 e
    1 g5 q/ g* V; i9 e" }) y0 s
    Spring系列第24篇:父子容器详解6 z# b0 x  ]+ ^- Y6 Q

    - ^9 U1 ^5 r. _2 R, {更多好文章
    8 H1 t0 Y1 Y2 {" m% \3 w/ T) K2 O' }' S) q
    Java高并发系列(共34篇)
    3 @; Z0 {) m' P$ U& ?; C! I
    ! F$ c: w* X. l" s3 o' e8 WMySql高手系列(共27篇)
    / q; E6 B- U" y( g+ a2 l+ D+ U+ e5 S( c: w" @
    Maven高手系列(共10篇)
      v& V6 i9 n3 m9 O' k6 U% Y6 M; v. i
    4 I1 ^7 B% A( D% E$ O7 g% F6 r" yMybatis系列(共12篇): W' w8 `5 s. a- J3 T5 O
    / e% v8 m( c' ^8 J" B: n7 g
    聊聊db和缓存一致性常见的实现方式
    0 t$ t4 `+ L: |' j0 [% _3 L5 t. o
    5 t, z# ]: s6 D9 |; L- `: H接口幂等性这么重要,它是什么?怎么实现?
    # {- r- ^+ p. y. m5 S/ k' s3 B) w( }1 @/ O
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    & H! N# Q* H8 u( d$ x* p9 m————————————————+ f5 Z5 M2 W5 F- l9 _. a
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 s1 _% N# x$ M( f1 K" J7 U& k3 o
    原文链接:https://blog.csdn.net/likun557/article/details/105648757; p0 G5 v/ x8 [+ j

    8 r4 h, T! [" h5 O0 o$ s9 n: A! `$ M8 Q) `
    zan
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2024-3-28 23:55 , Processed in 0.440572 second(s), 51 queries .

    回顶部