QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5399|回复: 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!/ C3 O2 `0 G  U
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!) Q( k- N- d. p; D  P
    5 ]2 v7 T! @, Z1 ?7 o
    面试官:Spring中的@Value用过么,介绍一下
    ; u& j8 [* {' X* Z3 b
    . X8 ^4 v8 p# S" ]2 {! J我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中' {+ c* C. Y! T. ~
    : p0 J4 H0 ~0 |
    面试官:那就是说@Value的数据来源于配置文件了?- S2 V/ }3 a$ S- I7 h! \
    0 V8 ~0 @  Y+ {' i- O. H, `& B
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置; ^  \5 {! ?! V8 M

    * T# t; e) j9 G, j! p面试官:@Value数据来源还有其他方式么?
    ' }4 {% s0 P: H- [2 G1 O+ ?! }; ~5 `" w2 I
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    ! a' P& |2 P( ~2 R- Y  R! i8 l  l; R% L, c! N- S. G
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?% k; V4 f2 @2 A& O) @
    " p6 }0 R9 b# J" q$ c' q; U' B
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧" {& j& A7 B7 E
    % u9 J& f4 p4 [) |& p( y
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?* O4 o4 x) k/ i0 r1 L2 [: c; s
    9 y! a$ o  E" G7 `. g7 M
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能! b; G8 r  r7 Z7 g2 H5 O3 z7 v7 X3 m
    / u& {0 D3 s. z% C6 V, M# X  P
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?7 j7 C  R  Z4 E. e% z

    1 a; w+ B1 L" s( H我:嗯。。。这个之前看过一点,不过没有看懂
    8 _; h+ V& x" U; t* [# |+ E
    * y/ _: \3 p( T面试官:没关系,你可以回去了再研究一下;你期望工资多少?1 v7 F# T/ o! e
    4 Z# [% f1 C0 y0 |
    我:3万吧
    # p/ |3 `2 S- r
    / b# ]/ O& x3 [面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?' r+ J* Y. \' t8 o

    ) y1 s% z0 e( R7 A' H我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万" O# X5 x; W- R- B* B

    6 U! b! d! Y5 b9 E面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    7 }) S/ L. {2 ^' Z1 M9 N8 B$ z* N7 [# }/ p. u% z+ l, G6 t/ l9 ?
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。0 k8 A# w+ f  q: h
    8 M3 N) l/ A1 O  l
    这次面试问题如下
    / O" j, |% H/ p; y) g6 Q' T8 @  u) L' e1 S+ ]
    @Value的用法! |1 N( _% E+ s6 w

    ! h  t. A; T) y! f$ }@Value数据来源
    ( c( H7 k' |# P' V7 E$ A$ i4 A/ {4 D" r2 _5 ]5 ]
    @Value动态刷新的问题
    ( z; c; y; F' c; ?. N6 J# Z0 _3 d9 }" }9 w: L
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。" Q  S2 N( E2 B9 X5 x# a
    1 H# C5 p) \/ C9 e( N
    @Value的用法1 ^+ @( n# C# W1 _% _

    : p( y5 E, C- Z& B3 W7 ?& k7 w5 T系统中需要连接db,连接db有很多配置信息。3 ?, x* y$ \6 N$ W
    6 c4 Z/ \2 @$ X: K4 V' B- ~" R
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。8 ~- ?1 k) z% r+ S! M3 F
    # W5 b( }, S" `2 R1 o3 z$ s% w
    还有其他的一些配置信息。/ ?1 B7 w3 i* ^: U( F0 i
    9 u5 V/ \# ^7 }" N! W$ z# z/ o
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。* E( N+ p2 ~' m! }8 I( e3 Z
    , ?' u# e& v1 y
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。( r1 z" P* S6 k. G7 L$ B4 t
    0 b. b/ A; O4 U$ r
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ( E) A0 c+ n; t8 v: K6 k+ m, @! R' t' j; K, O
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    * f% b5 e8 m4 m/ D
    & J$ P( m' K9 \; |1 \8 D$ a7 T@Value使用步骤
    $ Y( t+ J# G$ d2 y7 [) V: A/ e  f( k7 ~0 n
    步骤一:使用@PropertySource注解引入配置文件. {$ @, z( F/ T5 v8 \2 ^- z7 i

    + n1 F, e/ j( Y# c将@PropertySource放在类上面,如下; Q2 D, o0 Q& c- n* n

    ! T1 }+ w' Y% A% B8 D0 Z7 y0 U@PropertySource({"配置文件路径1","配置文件路径2"...})6 L- ?- J8 r, N7 |6 p+ O, R
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    * w& o; `3 A. V" a* H4 M4 V* g/ i4 L4 K; }  @2 o
    如:  h1 @& b1 t# f2 g  t
    . O- n4 D! c# ?# g5 H. z
    @Component
    + h# z$ m3 m3 A8 m7 ~@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    ( b# [$ c7 X+ I1 C% }- G! C# {public class DbConfig {! x" C# Y# p3 M5 P6 `/ f8 d5 N
    }
    5 K( J- y' ]) q( X步骤二:使用@Value注解引用配置文件的值# E. H( K" }( N5 i& j, S

    6 s1 D7 n$ J5 I0 v通过@Value引用上面配置文件中的值:" g3 l1 {& u$ S* Z; }8 B' v$ O" n

    # Z0 H% r* F1 C8 O" u  x0 Q5 d语法
    , y6 g. t6 k0 j4 o7 `  o5 O, H8 C" u* n
    @Value("${配置文件中的key:默认值}")
    ) g5 Z3 h, Y; t5 \@Value("${配置文件中的key}")
    + S5 B3 D' Z0 h8 h" }! G如:
    & L1 S" m& ]6 W6 m* T* V% E/ W
    @Value("${password:123}")8 U& L& q% Q3 a' {+ |
    上面如果password不存在,将123作为值0 a0 i: P* g1 }9 F- a2 o
      g( f* ^5 W' d$ k4 d+ }+ J
    @Value("${password}")
    $ _, \) z; o3 K  p上面如果password不存在,值为${password}- G2 i6 }, |3 N5 W# p4 B* k

    ; k1 N: v7 t* n6 h& J* I' A假如配置文件如下
    ) g8 L, B5 ~" v! h/ L/ i- V0 Y/ M5 c" k
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ; E2 Z4 h, U. }; S: B/ }jdbc.username=javacode$ i! @( N4 w6 B3 n( t7 m
    jdbc.password=javacode
    0 E. |( U! H) f" n6 ]使用方式如下:
    9 I; I; D8 M- s7 E0 l: R# f3 L% b9 m+ t( l
    @Value("${jdbc.url}")
    3 T' F9 `6 R) @1 u* o  rprivate String url;& l; ^" v# c8 \5 F
    7 S  B0 _. t4 P  A2 I
    @Value("${jdbc.username}")
    ' K3 u1 B2 k8 W# \2 Zprivate String username;+ u. d8 O3 R8 [% p5 ?

    * t* ~* C+ C; }! r- h' \+ U@Value("${jdbc.password}")" c" y  V6 p) p, e7 }
    private String password;. v* @) ^* ~( t/ l, y
    下面来看案例
    $ f- D% \- [; s9 O6 K9 @( k0 r7 P, P8 Q
    案例4 q; b+ n# k7 I* o5 I

    1 F" I' O: |6 K7 J" `0 x3 P来个配置文件db.properties
    ! R: Y0 U3 m) n. o
    4 B6 `" Q* q$ p" t+ c7 }* G  cjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-82 o" D' E2 K" D; n" l/ w# r/ j% S
    jdbc.username=javacode
    8 u* s- p. F7 Y# Hjdbc.password=javacode
    . q+ H% F0 @1 ?来个配置类,使用@PropertySource引入上面的配置文件
    ) J! h) S- X# G9 J% g: c  I2 |% z
    ( {7 G$ ~0 O. P- _, r( g9 G. {package com.javacode2018.lesson002.demo18.test1;
    $ O: C1 v8 i; s4 I8 v+ d* v3 E
    7 U" S4 `  v* Q0 f/ Simport org.springframework.beans.factory.annotation.Configurable;. \" _7 b/ v) A& A
    import org.springframework.context.annotation.ComponentScan;) [$ j& J# s$ P" y" _! h* _+ ?
    import org.springframework.context.annotation.PropertySource;; p: n# d! v5 _4 S0 y' C) ?
    0 B5 k% G: |4 R- d( b3 A
    @Configurable
    " w- B4 K, U+ f1 f5 h@ComponentScan
      c7 v' J9 \& T7 m# z@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})& V0 Y. p6 h& j+ g% H
    public class MainConfig1 {, A$ V) `4 E8 a- i; R; z1 C- \
    }
    / V& l3 {1 F( g0 D来个类,使用@Value来使用配置文件中的信息
    # s* B# m6 s2 S2 ^  m; T9 {, x, H$ {! h
    package com.javacode2018.lesson002.demo18.test1;$ g6 m! m4 q$ A. M) c1 R3 o3 ]
    # t! x' o" c" D: B, J0 F
    import org.springframework.beans.factory.annotation.Value;
    : u- |2 K2 U" h+ U* _' ?import org.springframework.stereotype.Component;
    ; C+ T/ L# [2 J' ?" c+ f; q" n$ B0 n1 ]# P
    @Component
    5 V8 X7 x6 x/ y' Kpublic class DbConfig {1 P% H+ H; p) E: G( _
    $ B% W& F- A# K  X0 L& m1 b
        @Value("${jdbc.url}")
    $ ~. O! P9 u  H( }$ ?& d# `. G    private String url;* {6 u8 I$ @& I5 c- E4 n. ~: H

      s) h: Z6 c# o* b+ l9 Q    @Value("${jdbc.username}")9 [6 R3 N$ W* z
        private String username;
    5 \% [6 G  r8 d0 \2 [6 ]  z
    ' [% w0 t" [8 V8 R    @Value("${jdbc.password}")
    5 Y% j. V: X( J/ \. x( u    private String password;" i& \3 w' R/ R9 Q
    ( S9 M5 W& o8 a& \. u3 m6 |* D
        public String getUrl() {
    1 G4 f0 A* v9 G/ B. T- I2 W& u        return url;
    4 e# E" ^$ D" ?" {# H! l" W! c    }
    - P& ]. ?( G- Q
    ; k* K. r9 c& N% ~% i1 s    public void setUrl(String url) {5 r6 u$ {1 E0 e
            this.url = url;1 f# B4 P# H, A1 d/ Z
        }
    1 h+ i1 O. X) t6 ]9 c' [3 @: W& u% `, [- c4 m. A
        public String getUsername() {
    ) H# ^+ g8 }: f        return username;
    $ y8 g* ]; u# O7 t4 s    }9 p2 f& N9 y7 R) C  O2 T% o4 a( m

    + N# l, R; l* w: a- J( D; z* l    public void setUsername(String username) {
    * ~0 X; m8 P, M1 X7 }# q8 H1 E( n9 {        this.username = username;( g: u: v& R2 G4 f+ l" {. H$ Y
        }
    ( C' K: f7 M5 n- W. I- u  r6 |! l! V, a% r' z7 @5 r7 Q7 ]  s8 h; x
        public String getPassword() {
    . o6 n( ]. w5 S% }* U% ^6 z, R/ i        return password;- }1 \( q3 o, v% c) l# l6 b1 s$ c
        }
    7 D3 }  @" H7 r1 Z' M# [+ g9 `) ~$ @4 G5 @. i* N
        public void setPassword(String password) {0 J# u+ N. Q8 E: N& D" V
            this.password = password;' X+ a0 }3 @5 d
        }
    + Z/ Y( a0 k7 f1 G+ J; c0 W0 S, C5 T9 x* t$ z. O1 e
        @Override# s4 D! q7 @( Q/ b' @
        public String toString() {
    8 \2 N; H7 A3 n& N7 p        return "DbConfig{" +( v# U1 s1 U* {" i3 {1 F5 W- F) @$ B
                    "url='" + url + '\'' +
    ) L6 [# w( q  v  j2 ^                ", username='" + username + '\'' +) z# o/ [( p$ |  @0 X; y0 z
                    ", password='" + password + '\'' +! [: ^& ~3 d) ~% Q2 N5 ?9 t" Y
                    '}';
      p; K' @! E7 M5 S8 {    }' w: }& H* `" ?+ `0 Y
    }2 a# i) g8 O( q1 i) Q; A
    上面重点在于注解@Value注解,注意@Value注解中的3 Z. _3 ^! f1 w! U! b3 c1 |% m7 k
    1 F) ^; y! M3 E$ {1 }  J
    来个测试用例
    6 F; j/ n2 r: c: I% U
    4 V' z) X, ^! o+ W% _" O$ l* V, Rpackage com.javacode2018.lesson002.demo18;- Q- v1 Q7 S; w( w$ T+ @
    $ m  d% U0 o& i$ _7 L7 F: K; D8 t
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    , z5 [$ U: |* R; {import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    / t. e- o4 k" j% h) R3 o9 {import org.junit.Test;
    2 E( J$ D3 m  O# p5 Gimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ) j, V, O+ z5 P% m$ [- i* ^% L+ _' Z# e0 d& k
    public class ValueTest {' E: u  n9 G- s/ r

    2 N& u* j. k) u4 o$ L    @Test- I- C) l) K0 k) V* @
        public void test1() {5 c2 D- c9 k& J! n
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();& D+ A7 ~- h" I0 @$ n, f' s
            context.register(MainConfig1.class);7 {! C* D0 c, P" F8 o
            context.refresh();' D4 z5 M6 N* k# T" ]' U

    ! {; d  q2 x* d        DbConfig dbConfig = context.getBean(DbConfig.class);) C& n$ o' D, J; F3 p2 t3 q
            System.out.println(dbConfig);) c- d3 g) A- X1 F6 E$ \
        }
    % F% ^5 U+ h& r& |4 l) [}; X5 V% d5 I3 I! t% w& m
    运行输出
    . K6 b: `( D3 T! C% r: j( z( k
    4 v% @! i, X: Y% l/ FDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}" d- b* B/ o$ k! w. P% m3 Y/ k
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    " b7 x: a9 U0 }# E. V+ b1 V9 H- |0 I3 a
    7 D8 K) F9 Z$ y@Value数据来源  y6 u3 s0 N4 A& {+ Y

    # c. }9 p5 O+ }- M- T- ^通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。1 [2 r. X) C: c" n9 y
    3 S3 J! G+ f; B3 I" m
    我们需要先了解一下@Value中数据来源于spring的什么地方。4 ?" [2 x0 U2 P) r! n3 Y7 g* Y
    ( h$ J' J9 u' h  A: K
    spring中有个类
    4 P8 S5 t7 a* `; G5 A$ o$ l& e; ?2 x& Z
    ' N' q2 m1 G6 C5 s+ v: P' korg.springframework.core.env.PropertySource
    . K$ {" D5 x; _" H可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息+ n) }" v6 k, r1 G! v  d% w

    8 S9 d) t% s2 x内部有个方法:9 ~0 o8 P) r- L" }6 X
    6 B# j! i! P& W6 ~! q1 {
    public abstract Object getProperty(String name);
    ; F4 f& L5 C/ f% A+ [; |! o) R通过name获取对应的配置信息。
    " E7 k, Y( t* X, d5 E  B( S7 \5 p# A6 ?
    系统有个比较重要的接口
    1 b4 S0 \$ m0 u. c8 s- @$ j3 q% D6 A
    3 s- K. n' h) t5 K# a0 `org.springframework.core.env.Environment# {  [, J3 h& ]
    用来表示环境配置信息,这个接口有几个方法比较重要
    8 p7 W- k* m# X( D' c% K
    # R1 q( Y" X& f! W, h' W& H0 e; nString resolvePlaceholders(String text);
      q4 M) C& p  M' ^- E5 F6 b$ y8 aMutablePropertySources getPropertySources();% x2 [1 m5 i. \! h" w) O8 U
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。5 k  a6 }+ Y& f' m7 ]
    1 }% F# \0 F/ [1 |, K8 J
    getPropertySources返回MutablePropertySources对象,来看一下这个类" [0 w/ J9 }, R# c

    ! ?) _; d) k& R' ^public class MutablePropertySources implements PropertySources {
    : v- D' J, {& z
    / A# L- D+ t; W4 V* K5 ^    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    % [+ W* z+ y! p' u' y6 v
    7 v' h5 l; q) ?! Q5 \0 e}+ {6 t, m+ [( H+ I5 g: s% m' ]
    内部包含一个propertySourceList列表。9 l& [. S. s  n& t1 Z3 x& K
    - T% V; s  k! a! x: a
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    , U0 ?* }! J+ c" D
    " i6 q0 D0 L# O' Y4 W6 J, n大家可以捋一下,最终解析@Value的过程:
    + j( M4 _. o( r8 J5 k6 e$ b% n& ~$ D; ^
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    4 @( U2 h. U. z5 g2. Environment内部会访问MutablePropertySources来解析) s: K( m; T( H$ G; a. p6 x& D/ z4 R
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    $ P7 c& k6 [5 @' ]( h通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。. K/ I& G2 H) b
    ! t* G# b* }- t0 [
    下面我们就按照这个思路来一个。
    ) P2 r' T0 G" c0 Z. f! `$ s: w/ n1 [" V8 y
    来个邮件配置信息类,内部使用@Value注入邮件配置信息9 P. [: }/ H: M. ?8 L2 m* X& z# O6 ^

    / i; ~' E' K$ |: w. Q6 xpackage com.javacode2018.lesson002.demo18.test2;
    * J) y) ~7 b' _) {7 m7 |$ g% T  W' y  r( Y% t$ f% [2 y* t
    import org.springframework.beans.factory.annotation.Value;! u2 E: \/ {% M8 }
    import org.springframework.stereotype.Component;
    , z( x2 R2 k6 H; ^/ K
    3 _4 \1 y4 h8 T) [/**
    ) o# X6 o2 i7 Y * 邮件配置信息
    : p8 @6 [2 Y" ?) M */
    : N9 j7 v& K0 B@Component( ?# @2 G4 t1 `
    public class MailConfig {1 \. O' K; d) i1 A2 A% v. M
    5 C9 z5 ^" E& k0 Y( Y
        @Value("${mail.host}")
    + e7 G6 W, g) Y/ m6 q# r    private String host;* I  m% Z3 u8 `4 n! x8 e( t; e

    ; d" s0 L) _% a( s" c    @Value("${mail.username}")3 Z  k" w5 q& [. ~; B
        private String username;
    # d* W5 x' r: {  i& K
    2 r& V% b9 g9 f$ ?2 y    @Value("${mail.password}")2 Q% Y" Y1 p% ]" a: r4 M  S
        private String password;! U% @7 f8 a$ f3 j5 R$ m" K& p8 l6 r

    1 M5 z6 b7 U& a- s3 d    public String getHost() {
    / D3 L( F8 M* a, E        return host;5 w; G: r8 A7 g6 S1 }: {1 b
        }. a  X' F! C$ ], i6 N

    ! L8 G9 P$ e& b! w, G7 u    public void setHost(String host) {
    1 T5 L, o) v1 F& @        this.host = host;
    / d5 l% X4 _% z. O, F, S+ B    }
    . s( q( T) v, n) A6 G5 g, P, Y6 `& X1 W0 H1 r' _2 Z
        public String getUsername() {0 I! U' j+ S5 b2 b# n' d/ O
            return username;7 F+ ^1 b9 c3 f- Q
        }
    : q( ?4 i  ^3 K8 I" ?8 A' R) E4 |# }9 V! w
        public void setUsername(String username) {2 `6 w: T* r! b8 p' F4 X
            this.username = username;& N$ h4 \7 l: {3 o1 V
        }/ i# K" p/ ]5 V- N* W

    * j+ e1 x% N& d) t9 i    public String getPassword() {
    / L' x/ C/ r  o5 C  _* p  S7 @        return password;
    - u  R8 n1 p9 F" C    }9 t$ O) Z& E5 G% H6 a+ g- c. f

    2 M1 C) N& T$ s7 e& |, s  y    public void setPassword(String password) {! \, }  `7 X8 \- t' d
            this.password = password;) L2 k4 F: \& C5 t7 a9 q( [
        }
    , K% q' F$ c  b" {
    ( R) f" i$ V6 H3 z    @Override
    / q" A/ G; U# d4 s7 G8 x    public String toString() {- U  C$ i# h0 h# U9 I7 {' c
            return "MailConfig{" +8 F+ N. W* A; Z9 y! w! j
                    "host='" + host + '\'' +6 z1 r$ R2 l. c5 |& K
                    ", username='" + username + '\'' +0 V, _% L9 H4 |3 Z: l
                    ", password='" + password + '\'' +
    6 x9 T% @6 _$ Q4 i/ H                '}';2 x+ E/ ^5 A. o; h( m" Q; n
        }8 X7 V" @( Z/ o8 H9 n& h
    }
    # j' q2 V* Z  J6 ^# g再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    - G$ `9 T; c4 Z% n  f
    0 B7 o' C1 u7 ^: N' B! N+ w( Vpackage com.javacode2018.lesson002.demo18.test2;
    5 C4 f4 t* G. V5 F4 J0 |- C4 l* v5 A6 h
    import java.util.HashMap;4 q) P  b; z5 M# W1 M5 i2 j
    import java.util.Map;3 @% u4 y  E: @% i& o* V# p
    ) H; V9 s2 v' C3 t
    public class DbUtil {4 o; T% u$ |1 U8 A7 F+ C% Q+ M
        /**5 m/ g  f3 A) k& W
         * 模拟从db中获取邮件配置信息' \/ t4 A) e/ r
         *
    # l/ F/ ~& j& q* j1 e% N7 J* Z     * @return$ {: d6 _  ^1 b8 x8 u9 N. A
         */4 O8 F2 ^$ K: i# K4 R
        public static Map<String, Object> getMailInfoFromDb() {
    * @& Y; k# R( _) h' D4 ~" V; x        Map<String, Object> result = new HashMap<>();
    1 X. J: a- d" [4 E( U        result.put("mail.host", "smtp.qq.com");
    , ^) P( X' J9 Y1 [( V- n        result.put("mail.username", "路人");# D6 H) w$ f* L/ d  L+ `
            result.put("mail.password", "123");  t* @# E6 E' K
            return result;: J* P, A2 F0 b
        }: B) B) y  B! j  L7 ~, \/ g/ z
    }+ n0 ]" P; {- k
    来个spring配置类
    * `- [: g! q. h' j) A2 z: j7 Y1 P( d* s  U0 l2 k
    package com.javacode2018.lesson002.demo18.test2;
    3 f6 L, G* N' M0 [' |( i; n
    ( y" I7 l  l* R6 x; D. N; timport org.springframework.context.annotation.ComponentScan;
    # S5 G+ t( _$ [& Aimport org.springframework.context.annotation.Configuration;& {0 Q: ]' x0 S" {1 `  j2 n/ ^

    % w  x7 ?7 N- w- Z( w( y7 A/ Q# M8 i@Configuration+ u# N. V, [! z4 _$ y
    @ComponentScan
    ( v$ k& }! k& s6 n5 fpublic class MainConfig2 {2 I) T6 e+ @( c$ @3 Q$ n
    }
    7 N: q" t0 t2 K下面是重点代码
    # S: {) d: x0 M% q8 [! E& r0 d2 _+ B2 \' }  y& ^$ |% ^
    @Test4 S8 w( z$ f5 E- g7 Y- F+ s+ M
    public void test2() {
    5 M' p2 m" z* B3 V$ L    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    6 e7 C" s" @* `3 Q& E" u( r# W6 z& F% X7 {- h9 R( D+ t" L$ |
        /*下面这段是关键 start*/5 h: G$ n3 V; p- t9 H% {; b
        //模拟从db中获取配置信息
    4 B" T. {! f+ c2 B: T* B  W. n    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    # A8 @8 A# p" n    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 E$ V5 ?; o4 y
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);" T0 D3 K) a, G3 t! w- l$ I& D
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    9 H/ A# B+ N% I# a; K  x, ^/ p    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ; L" @5 a& z! n+ J" d7 [" O. L    /*上面这段是关键 end*/9 _) n  C- l, S" u; u

    : J" _: D& Y) ], o    context.register(MainConfig2.class);
    1 v+ g) F# S+ k& r* W1 `    context.refresh();( ~4 t- L; I. o. Y
        MailConfig mailConfig = context.getBean(MailConfig.class);% x2 Z/ ]. L/ z
        System.out.println(mailConfig);
    3 b8 @+ x5 C- r& T}
    9 r1 W- f5 @3 k* Z( H! \! C4 R3 I7 C注释比较详细,就不详细解释了。
    5 m* N; p9 a; ~4 g; H2 R. s; ^+ P' \9 ?" q) w* a- S5 C) x
    直接运行,看效果5 L9 Z2 u0 Z; J7 ?. x% @
    9 {1 ]! `# w' e( ?# S% T& L. ?
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    " S+ Y2 d- {  x4 p* `有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    $ q( F) f: Q+ n& i. c6 c* z% h6 g/ h
    上面重点是下面这段代码,大家需要理解1 A% g  M5 A# o: O' M) I

    . N7 Z+ a4 w7 ?/*下面这段是关键 start*/
    5 u% A+ @2 O( @% c( J//模拟从db中获取配置信息- L, O' x5 C1 f) S7 g
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    0 `, I9 v: P# X* }! v//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). `# S& @7 N5 t3 Z* r4 s" i
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);+ L4 C5 f0 ]% H: D4 z
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    % H; \! r  w* o) ccontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);" P2 s& p! D; f/ g7 V
    /*上面这段是关键 end*/" ?+ l( {9 _) g& p/ H1 O. e- P
    咱们继续看下一个问题8 \" D& U1 z1 T) O: \% S/ S" |

    % J' ^3 Z1 X4 n* X7 }, O$ r如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    # a* ~$ J- l: W0 H) f
    3 _0 v. c. R: W  @@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。" y) ]+ Q: [( t0 l
    % C0 ?5 R, M- f+ s7 f  m2 `4 ~
    实现@Value动态刷新
    1 B" O; z8 g: a4 i9 y7 S& f& A6 k
    9 H) ~; T& |" s6 h0 S先了解一个知识点  l$ E% k6 g: @1 M: J
    , [3 J$ a( ]' d4 s( @4 ]
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    & x0 c9 |2 t# B$ h4 T% x/ E  x: _; B2 v7 E5 k- m& u0 {+ F5 O$ K/ X
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解( n7 C& ?- ?, B

    ! p6 B5 r% A4 ^  e8 i: vbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:, C* o+ Y; C% _/ q9 x* k
    1 v4 X+ P7 @" ?. \, U
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    / W9 U$ p/ d" O) C这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中3 [+ `+ z' E( y, V  H

    8 W! a: m. P0 U: B; {  C* J1 b- vpublic enum ScopedProxyMode {
    : Z2 ^; u/ [3 Q3 O4 a0 m, U! w    DEFAULT,+ r' s* H, Z, R! c
        NO,
    3 B% M6 q; W1 X/ G    INTERFACES,% {( p9 D) l* Q* [: C! {
        TARGET_CLASS;5 [' h, c' ]0 }8 t+ P- F2 V: N; E- v
    }
    4 w+ {4 d: m% Q* V& U# j) O2 k前面3个,不讲了,直接讲最后一个值是干什么的。9 `2 y# G2 ^3 b2 K1 a3 s- {! O: i4 p
      I* K3 J. C) i3 _  }; b+ H+ @
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    ) T- e" R$ s5 @8 N6 I/ g
    , ]" {- N' F. N8 _( @% _8 Q理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。# T5 Z9 E- R2 T9 ]

    , |1 ~8 w: T- A6 D自定义一个bean作用域的注解8 Y& ]" v0 @; N) K

    ; }1 j# a7 _1 _# q, ~6 z, [; B7 F4 Bpackage com.javacode2018.lesson002.demo18.test3;
    # D( L, C; R! P8 o4 m% {4 s+ f( r" C8 i; u* m
    import org.springframework.context.annotation.Scope;- Z  I+ g  ^# o0 n/ n6 G0 e
    import org.springframework.context.annotation.ScopedProxyMode;
    - P7 W8 c; O# R( Q7 a+ L0 G& U
    % T6 Z. r  v, b( S4 H# limport java.lang.annotation.*;
    , G, v- c$ H2 j: _' h8 k2 _+ y& V( A) Q7 `
    @Target({ElementType.TYPE, ElementType.METHOD})
    6 C5 b; @* p1 `/ g: u5 S- K+ W@Retention(RetentionPolicy.RUNTIME)
    1 u& I  g6 j+ d; E. b5 L9 ]@Documented
    5 M! h0 ^7 p4 v) w7 o4 e0 R@Scope(BeanMyScope.SCOPE_MY) //@1
    / f5 e/ K) \/ H% g/ F! G6 ppublic @interface MyScope {& i8 F4 z& F: S7 `5 X5 M6 Y
        /**- r; Y  v$ K) ~
         * @see Scope#proxyMode()0 }& m, {1 G* t# `
         */
    & P. s, |( {: |2 E8 E4 ~. T    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    / E1 L' d  F) K/ R* Y}4 {& o1 s% u. H! u* j
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。; g2 C0 K! i  C5 \$ Q5 _

    - b% ~% p1 e) F- u" B6 k@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    - Z$ I# U5 c6 x  V# K
    9 q7 l6 V. y" Q, s, A" V; P, _@MyScope注解对应的Scope实现如下
    ( B0 c0 X* @  g# O) t' s  r
    - i1 a# N- d* f% r  [package com.javacode2018.lesson002.demo18.test3;
    ) f' e/ O) P. D) X. M
    1 h- E+ i( ?% p, Fimport org.springframework.beans.factory.ObjectFactory;/ Q+ ]' ^2 ]# ]2 C  H
    import org.springframework.beans.factory.config.Scope;
    ! W. h7 S, e* e. d7 Eimport org.springframework.lang.Nullable;
    ( D) l. E: B5 E9 J3 i4 C2 q( y* N$ h. V2 A4 G" P; Q- c1 \5 b- r
    /**
    6 N8 y6 h4 A/ }! \5 d8 a2 J * @see MyScope 作用域的实现
    : a- \( z0 @1 G! g4 B */# V+ j7 p# K- S* _- x
    public class BeanMyScope implements Scope {; Q+ g, n4 j/ J# T" y; N, ?& K
    & y& }7 u4 G% v! w( ?6 l1 g8 N
        public static final String SCOPE_MY = "my"; //@19 x+ T4 @2 O7 y  x* |4 ?

    & k6 ]: \+ Z6 ~' b- g  Z7 ?    @Override* x0 [/ d6 y& q5 d8 T
        public Object get(String name, ObjectFactory<?> objectFactory) { - b' t0 T8 i& g
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@22 t4 }* K7 M3 N. I4 M1 Y- w
            return objectFactory.getObject(); //@3- e4 G  f9 E$ n( `, P7 S3 v; Z
        }; c2 F7 V; r: [1 D

    " |- a1 B+ ]* p) K8 a  |3 A    @Nullable9 B; i. {0 t2 a0 }9 u* o
        @Override
    , N0 R/ s; q3 a  S# A    public Object remove(String name) {; r6 m* G" C0 u' g+ `8 M8 K) W
            return null;
    0 S" n8 ?5 Y7 I* d8 G    }# f" x' P! m* @0 N1 S
    8 F. ^& w5 v% `! q) g
        @Override0 m/ b( ]; h6 S1 c8 i0 P( P2 w  X
        public void registerDestructionCallback(String name, Runnable callback) {
    - E( n+ J& N, @9 [+ x% r, E+ K( J; o5 k# \9 |/ A) @4 @' @
        }
    3 n* T9 K& F! T4 x0 y) f' K8 u' Y: ]
    / X% f8 l0 W; J4 ~    @Nullable# H- G5 L! M) w8 f
        @Override
    - X3 \* y5 d% C7 `3 M  U- l    public Object resolveContextualObject(String key) {
    # ?" n& K& @$ Z# L        return null;8 R7 n! h  r( k9 z* v8 K, E5 o; j
        }
    2 ]0 E" R* W, L1 Q  t2 m2 @  @% T, {. O' a" x
        @Nullable% J% r* ?5 @0 F6 q* D
        @Override% B5 J& {4 A: P2 S+ g  x! I8 Z5 M) ?
        public String getConversationId() {: j7 c* B5 Y- H( T7 J
            return null;
    . V/ s* C9 w7 L" @, l    }
    - |+ R- o- f0 W* @$ R}
    & s5 K- H# x' _2 _, A@1:定义了一个常量,作为作用域的值
    * D0 P0 f8 G5 }" M( u) r- X- l* d' o1 W0 N  S- M" X
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果+ i- _/ c- ]( E- M. p6 _# P0 j
      O% c( ]+ w% b* E$ K: s- m
    @3:通过objectFactory.getObject()获取bean实例返回。
    7 w$ @1 S/ Z! A2 L9 d
    . e; L/ I& S; w8 p8 D9 h下面来创建个类,作用域为上面自定义的作用域2 ~/ [/ w4 Q2 I: U
    - b0 b4 S6 K1 X) E$ C7 i" V9 m
    package com.javacode2018.lesson002.demo18.test3;2 \2 C& ~; s  O  h7 K1 k( |2 @8 B
      y  h, q7 D' `. e; {3 A
    import org.springframework.stereotype.Component;
    - e# Z9 f0 d& J
    1 q5 l( m& o! D% I6 bimport java.util.UUID;, T& z- Z+ f0 \2 v

    $ R0 c2 H. I) J8 c  B3 o@Component
    3 g8 ?1 o3 X6 I* x3 g+ H5 u@MyScope //@1
    2 `. k8 z6 Y; k/ u4 ]4 ?public class User {; i# w0 h% O* W- U  o* T

    & k, S  o4 n/ }# D0 x5 |# g, `    private String username;
    $ x! q; [: j9 \9 c/ ~7 y
    9 O) ?3 n4 \- U$ F5 C, F    public User() {
    0 u0 ]. E; \# P; J, k1 K8 O, _        System.out.println("---------创建User对象" + this); //@2
    3 j* f) ^  J& ?, d( |        this.username = UUID.randomUUID().toString(); //@3
    - R  f9 c4 \0 x, p- s! h    }
    % A) z3 C% `) v: M. o$ z, R1 u) l  ?' z* W8 q& l* c8 s* G
        public String getUsername() {+ d1 f8 q9 g; w8 Y# S; n8 N' p
            return username;* g4 t: T# L8 h3 S! b
        }
    ! A" I* _, O) w* W+ I0 t6 @) ~$ K. {4 D/ Q
        public void setUsername(String username) {: u, w3 q8 S2 _
            this.username = username;' X1 V8 X9 S) U! x
        }
    ( u6 ]# m0 D7 C
    $ m2 j5 {$ }4 S* e& Q% O}7 }% \1 F* E  r
    @1:使用了自定义的作用域@MyScope5 [  n, f+ a2 b' X! k, N. K  g1 r. P

    * [& P! I& Q9 K$ V0 e8 `' M) k@2:构造函数中输出一行日志6 k; u2 S1 r, U* Z9 c: N
    ; x* q4 C, l- p$ ^" d7 O4 B: \
    @3:给username赋值,通过uuid随机生成了一个# H# }3 U7 @. H8 P* @+ d4 f7 g

    # U4 s9 c' W0 e3 M4 |% x来个spring配置类,加载上面@Compontent标注的组件
    ( d$ d6 \2 |4 n) q, J- [% G) |, N2 A* q& u2 b
    package com.javacode2018.lesson002.demo18.test3;1 `( _, s8 J/ z4 E0 o8 ?9 C7 ~$ t
    ' n! `/ ~4 Y; C7 k3 L. z/ X- e
    import org.springframework.context.annotation.ComponentScan;1 g. [% ?; T8 }  Z4 ?# r
    import org.springframework.context.annotation.Configuration;* z( G9 }  Z9 i: ^; p) u0 @+ Y
    # ~3 O. q' |; R
    @ComponentScan7 F1 @0 n0 T5 Z% s5 N# c
    @Configuration+ I* H" S! E4 |1 }" @7 E0 y
    public class MainConfig3 {
    ( k* o1 g  w$ Z7 K- F7 A}
    * ~& U) J2 T1 v9 M6 C9 S下面重点来了,测试用例
    8 s8 Y& k1 l" i" n+ e& |) j+ ~- R  ~/ T
    @Test
    : m& Z% P$ x7 a. A& S! W+ K; {public void test3() throws InterruptedException {. j8 j; c) m* Q
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' `+ r5 P# `* g/ c7 D3 ^, H6 H
        //将自定义作用域注册到spring容器中
    % h; ?( [4 X0 s    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    6 u) f. Y+ p8 w% }6 K" V! }    context.register(MainConfig3.class);3 V, f: A/ w; D1 X+ A1 C5 x7 [' e: t$ c
        context.refresh();
    9 V1 z4 D- M. y- N, U& A7 E3 X
    2 T; k- M9 L9 T+ Z4 l    System.out.println("从容器中获取User对象");- u8 F) _) |% j+ V4 r
        User user = context.getBean(User.class); //@2! ?& M7 c5 l4 L& Z( [8 @
        System.out.println("user对象的class为:" + user.getClass()); //@3
    / K0 b9 R  M1 ~
    0 T- s9 L2 e* W9 `. X    System.out.println("多次调用user的getUsername感受一下效果\n");. \; G+ C; |5 }8 x& @
        for (int i = 1; i <= 3; i++) {
    8 W( m' U+ h! D' z, c. f        System.out.println(String.format("********\n第%d次开始调用getUsername", i));, Y6 G! |7 N3 p! _4 [( ]4 Y& Q
            System.out.println(user.getUsername());
      N; U$ W% G+ M0 a        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    ! s( o7 M1 ~. ]& S- o/ U9 o( g8 |    }
    , n$ n# G) ~0 a8 p  `% f6 J0 q3 A; ?}
    & t; m4 o+ x9 j) r) a@1:将自定义作用域注册到spring容器中
    2 a( |) q; x& r$ _6 g0 \9 i1 T
    # u% @& V/ `$ I4 m! X@2:从容器中获取User对应的bean4 a! d, z/ h" e& R# p3 ~: |$ A

    - E* B* {( D' h6 }- R4 N. j@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的+ u& Z$ C1 f, O, a
    % b, A2 b/ \+ e3 m% R+ ^5 p
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    0 G: D" ]3 V8 j; t( u9 h4 a/ l/ ~# C# \
    见证奇迹的时候到了,运行输出* {2 |' {& ?+ a: N% ^( C& w

    , J; u5 T: x6 m8 }# S* y6 d* o从容器中获取User对象8 Y9 ^7 \4 P: G/ x* x
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127+ p, m; W/ b7 e! [, L$ k/ B
    多次调用user的getUsername感受一下效果7 L2 \/ j; X8 C" t2 o7 M7 x) X' _

    ; N+ \5 T. i6 \' R: K********
    / H6 x& E5 X+ L第1次开始调用getUsername3 l* Q  }+ Y5 B: r
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    ( {5 D( {8 M: w. ~---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    1 V" h9 k! ~7 I! G7b41aa80-7569-4072-9d40-ec9bfb92f438
    3 H( v+ Q6 o1 M- B1 T" h第1次调用getUsername结束: }3 C4 x$ ?( E. g0 v7 f8 @/ b6 Y
    ********
    0 _' R, t4 `4 U7 [5 }
    ! v; ^6 N$ T+ M1 a********+ s: G( T. t( b
    第2次开始调用getUsername7 L2 d0 T: ]6 Z; O/ y- X4 n$ l* ?+ v
    BeanMyScope >>>>>>>>> get:scopedTarget.user9 S' u! {3 t& g: {1 D
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b8 r2 r; ?% i0 B0 N
    01d67154-95f6-44bb-93ab-05a34abdf51f) U. v# H0 w8 w  t+ r8 i
    第2次调用getUsername结束
    4 n- @* J  Q5 }! j. a********( z" k: R; I  ~' v) ]( Z& U: r
    4 u8 o$ n' w" {, w8 G6 G# }
    ********& ~6 Y/ h$ X, }, ]+ E
    第3次开始调用getUsername' U6 W( a% Z% m2 s2 Q8 B" G
    BeanMyScope >>>>>>>>> get:scopedTarget.user9 k( n, \& N4 Z3 C
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d159 w. B5 D: n) T$ o* `/ G" z- N+ x
    76d0e86f-8331-4303-aac7-4acce0b258b8/ y9 I& C$ ]# y5 L$ q5 X! U2 b* Z
    第3次调用getUsername结束
    7 t4 p9 D0 O+ A* \********! \" N# H: T9 g8 k) ~& O
    从输出的前2行可以看出:3 @4 E# x/ K" r/ b3 B

    / V  ^- O, K% H8 S调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    & k, a) l4 }0 A0 d; E  w. l4 V' {1 H$ g. V
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    3 U8 \  c6 f/ P: l5 y% t8 e/ z1 a+ d2 l8 b/ F( V4 H2 B3 \5 D
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    & [5 U: ^. \; L1 @3 E5 `/ G5 y9 c
    " B7 l% `. G% Q: Z+ H: g- N; m通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。! R8 H* ~  b' s

    , l! g6 M& c! K; N( }( ]1 n* j动态刷新@Value具体实现
    / T5 ?: `/ S3 H3 B5 [4 V) D6 b2 G& {* R8 h% j- a7 D6 e4 ?
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    7 g( M. O' T5 ^- ]. q+ n
      U# t. v& g. P) M# O& n先来自定义一个Scope:RefreshScope" l# J% |) ?/ A1 P
    6 n5 S: j" V0 u: k$ H" @* S
    package com.javacode2018.lesson002.demo18.test4;% h- ^' a1 x! k6 p# R

    7 y. P; u. P( y, q/ d; g4 Q4 yimport org.springframework.context.annotation.Scope;
    % n/ m3 p# k  w; Aimport org.springframework.context.annotation.ScopedProxyMode;
    / z$ F1 D6 C7 \0 @& C$ y1 L# D
    ) ~- i) N. d  l/ l0 f! wimport java.lang.annotation.*;
    # a- f7 {' |; D8 s
    0 b$ h( H# b1 ~" S& s@Target({ElementType.TYPE, ElementType.METHOD})) Y; [" ?+ w2 H0 u: a' P' ~
    @Retention(RetentionPolicy.RUNTIME)
    4 W9 n: y! L% ^9 V# A  r  h@Scope(BeanRefreshScope.SCOPE_REFRESH)
    & P- M0 r+ }7 h5 @0 |' w@Documented
    , q8 T9 |' j) zpublic @interface RefreshScope {
    / {* n5 v: }/ J    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    9 _$ P. l, K4 f* |2 T}; y4 ]0 C- s' u
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置- ~* O. \  m9 h. U1 d% y6 n! W
    6 g# b8 G. j3 U) @( k; [3 r* l
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    ! s$ w( E' G  k$ u# G; A2 q, g
    # j8 Y1 t4 F* [6 ?这个自定义Scope对应的解析类# [- U# I4 t& F5 g

    ; s7 N* P( \; e- H下面类中有几个无关的方法去掉了,可以忽略
    ' i# B- n" s% ?7 ?  g
    + Z5 D, |2 t: y7 m" _+ spackage com.javacode2018.lesson002.demo18.test4;7 x2 B3 e: X/ n! s$ r7 ^# c
    9 H$ G  V! w: b' T8 T2 n/ _- ]5 A
    5 X8 c/ Y9 F, R, O; s
    import org.springframework.beans.factory.ObjectFactory;9 S& x, I9 s0 c3 s: s& p
    import org.springframework.beans.factory.config.Scope;
    * C8 n4 @% y" e3 d3 pimport org.springframework.lang.Nullable;  g7 Y4 i# ~' f

    0 L) s4 g5 u# X  Gimport java.util.concurrent.ConcurrentHashMap;
    3 z  ]( n, n0 X$ y+ J' i. n) `
    ' Z5 H) [5 J/ Q% z) J; `public class BeanRefreshScope implements Scope {
    - d+ j8 \+ ^4 c& N! B
    + `  C8 ?" Q( T/ K: U' N  d* K    public static final String SCOPE_REFRESH = "refresh";8 Q/ X6 i( E$ f& A  r0 {
    2 [- J% u9 D: o* Y* C
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();# S9 S- q: Y. m5 D) V3 o

    - C7 E* Q0 E/ a5 L- m, d7 d    //来个map用来缓存bean
    0 Z  D6 v' a0 Y    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1' f* }- p# i* h) w
    ) l, V' n& K& `5 l9 B/ _" P
        private BeanRefreshScope() {
    / S' b2 v" h9 X- x. T    }8 }) e2 Z0 h; d! K9 T5 N
    ' u4 u* U# T  U2 B. p3 a- V
        public static BeanRefreshScope getInstance() {
      u4 u6 \8 P4 J; T' R0 R$ S        return INSTANCE;
    & |8 c  Q9 }8 ]2 d6 k9 f  _: k    }
    % G; L( c7 H  z) R0 s- e: K" U+ Q0 J
        /**
    : h* o. q- w6 }3 m6 d     * 清理当前( i9 p# {8 W4 e) Z9 `
         */8 j  ^6 t9 I. G# \
        public static void clean() {5 j4 b8 j  @  D" I$ J" u! a* u
            INSTANCE.beanMap.clear();
    3 x, B6 f# k3 B) y. n0 W9 l    }1 }, [- s& h1 @* d
    % f, d) u9 p# [2 z9 N7 P
        @Override* ?; ~2 T6 S+ n. U, S5 n, P# T- U
        public Object get(String name, ObjectFactory<?> objectFactory) {
    - [- H8 w0 B) A- r* @: f        Object bean = beanMap.get(name);4 G$ |  w: X7 U: o9 d- n. c" h
            if (bean == null) {
    * U' r$ I' k  g# F+ ]5 Z2 @" X7 O            bean = objectFactory.getObject();
    * S( d1 w" y, N            beanMap.put(name, bean);: O) @/ j. I& l& A3 K/ N, D
            }8 q1 F6 z' @5 ^; b; r
            return bean;
    - }) h  l2 x! x, T2 x3 M9 j4 W    }) k- B" s1 D# g* e' u( x0 f5 w
    9 k- d: \& }" i% U# n) w% M8 z9 {
    }
      Y. y0 s, S% y5 Z, |; _上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中9 D" h! e+ s- K. M) z  i
    , n# c  {8 a$ d& p7 ^* Y! J' p
    上面的clean方法用来清理beanMap中当前已缓存的所有bean  `9 G& n* W3 {3 ?

    * ^, q- t, g" _3 |来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope0 r4 Y. D2 q  p" f
    / I: @# z+ P- i& r! y+ ?
    package com.javacode2018.lesson002.demo18.test4;
    ( o$ y7 C7 f4 s
    . X4 p6 x/ P! Q3 U! gimport org.springframework.beans.factory.annotation.Value;
    + K# ?+ J, }8 X9 j- u) vimport org.springframework.stereotype.Component;
    ' _2 S9 b7 M; _. N  L. U' S
    % d9 J( z' q5 m1 _1 R/**1 Z* M9 M( Y  s- K8 c7 \% a4 q! F
    * 邮件配置信息
    & c% M& {) t: I8 Y* Q& ^ */
    ) j# A0 v; O/ c: g- {3 O) J@Component
    ; y, e0 p7 k+ @( d: O3 y0 k@RefreshScope //@17 H. \  S0 @  Z6 `+ B( }/ {: ~
    public class MailConfig {( M$ K/ f; W+ x/ q0 {
    ! H! X+ s, q0 j: B* m
        @Value("${mail.username}") //@2
    . S; d: |9 s; ]: Q( b    private String username;" F  y$ Q; n* H

    & R8 A$ s8 Z% n2 B$ a: T    public String getUsername() {
    ! w/ W* j8 }: H5 @, ~- ]        return username;
    - z& H: ~, ~0 x3 [    }4 f. ?' I9 p1 R0 N

    " a3 r3 s! A' |' S: ?    public void setUsername(String username) {
    , l' T0 `4 n3 }  d, H        this.username = username;
    ' m0 @/ X4 C) u. I9 o. Q    }
    1 ]  z$ O5 C4 F' |+ A% Q' I+ s4 f6 C" I- G/ m# C
        @Override: }: p/ k3 n5 z1 j
        public String toString() {
    5 p- l3 z8 L$ r0 O5 C( Q        return "MailConfig{" +
    8 y* g4 d% F+ d3 n3 l                "username='" + username + '\'' +  K: C) c9 c% K6 A2 P
                    '}';4 @: k3 L# m4 W2 K* T
        }
    " m  p  g1 Q* ], Y" ?}! x( M" h; w. u1 f' K# |
    @1:使用了自定义的作用域@RefreshScope
    4 H5 P' D  h, a( O4 W% e6 G
    - f2 S7 {) n/ f$ P( J@2:通过@Value注入mail.username对一个的值- J% u- f% ]& A' h2 ~* ?2 B

    0 P; P# g" }% ~重写了toString方法,一会测试时候可以看效果。
    / m4 K. e& y3 q/ A6 M- l
    / P4 \6 c' h% r  F: ^再来个普通的bean,内部会注入MailConfig% s+ b1 w8 y6 Z$ V$ E) l* D+ t
    . `( L9 i4 b) K
    package com.javacode2018.lesson002.demo18.test4;
    7 m" m! l6 |. u- r; h- k$ O  U' g* ~/ p: h2 S! m
    import org.springframework.beans.factory.annotation.Autowired;
    8 z, [+ G4 K/ W6 K2 {% ?9 J" p, J$ I5 j6 timport org.springframework.stereotype.Component;
    , s# Q. E! V5 H  ]+ |, c
    ( P4 x5 l, ?- W, B1 ~0 X" Y" X7 K% ~@Component+ |7 t! \- D1 S! k: `! T
    public class MailService {' J" I( V  t1 V  ?* x7 C0 ]9 j' B
        @Autowired' \; m7 G5 o  p0 G$ H& ^/ f
        private MailConfig mailConfig;
    + D  Q& r" @0 ~% v/ w" q4 b1 f- A2 p. a
        @Override
    ; F, d  T& a7 n2 ?% Y* D: Q    public String toString() {
    / K% G6 }1 v' p9 d: ?) `        return "MailService{" +
    ) ]( `# o7 {4 h% A                "mailConfig=" + mailConfig +0 a# l7 Y' ]( j* ?
                    '}';
    # O- Q6 ?' N7 \3 S. t  Q    }
    # k) Z3 l1 |6 h8 z' s/ t}
    & l5 d1 Q3 O4 Q: i+ Y# b3 [代码比较简单,重写了toString方法,一会测试时候可以看效果。
    2 j: r' A0 m' {. m- X9 U" S; Y4 }0 y' `% r! s4 h$ |' U8 H
    来个类,用来从db中获取邮件配置信息
    6 v2 _% [4 U( n+ Q, A
    % g5 R( v! e: E4 u/ {4 t, cpackage com.javacode2018.lesson002.demo18.test4;$ Y1 W4 Y' H( E

    : M5 P; V9 X+ C$ W0 a) Rimport java.util.HashMap;
    : O& C0 P/ d3 K- p* |3 C# eimport java.util.Map;
    + n' `* M: L: \9 `$ i  t+ ximport java.util.UUID;' X6 P4 _# E$ V4 J

    % S+ O# I( f' r0 vpublic class DbUtil {
    - u: q- @( _4 v$ `5 a7 G  }    /**
    * Z5 f" O+ o. N( X1 X" V  O' I     * 模拟从db中获取邮件配置信息
    : T$ n9 S* ~5 ?; P4 L2 ]6 k* u     *! [  ~! u( D  c4 Y
         * @return
    1 N" z$ w. S  h0 O. r; \     */2 R* _& r! z. o1 w8 |( w& C
        public static Map<String, Object> getMailInfoFromDb() {: A: s* E; a; |
            Map<String, Object> result = new HashMap<>();3 I7 s( u7 l* L# N" C. t
            result.put("mail.username", UUID.randomUUID().toString());
    / w" _; }6 I: J; D3 `5 [! m$ |        return result;7 k' V0 X) R2 W0 N- o8 o7 z1 y
        }
    / O: @. x& u+ s3 m$ ~& [; b4 k}& y4 i% L4 k+ U2 W
    来个spring配置类,扫描加载上面的组件7 |  o+ J( P3 ^2 y) Y
    9 G' `9 }* y- M6 Q+ w
    package com.javacode2018.lesson002.demo18.test4;
    + J" n& T% g9 u+ k7 A+ `) f
    1 L( x' |$ V$ @; z9 {import org.springframework.context.annotation.ComponentScan;; |+ r0 A! ^0 J
    import org.springframework.context.annotation.Configuration;
    9 h& L0 L$ E; m' W' t8 ?- ^2 I/ A5 a9 g
    @Configuration7 G% X# I% }/ z7 {
    @ComponentScan; W% C2 T( z# @# G: Y
    public class MainConfig4 {/ h' s+ r6 }: Q8 {7 _/ Y& f! ~! n4 q
    }
    - I) ?0 R' ]# h- @/ C* x% u" x来个工具类
    9 O5 v. L4 w% A% {  f% y: U4 X& w1 Z4 O/ ~
    内部有2个方法,如下:6 ], D6 |9 i0 t  [: |) N5 x: Y

    4 U& _: c% B( spackage com.javacode2018.lesson002.demo18.test4;0 O8 Z- U9 Y4 }- V

    0 R8 K2 z* J( U+ p" Cimport org.springframework.context.support.AbstractApplicationContext;8 p; K, u* H! |7 j: ?3 d% A$ M
    import org.springframework.core.env.MapPropertySource;: a5 f, P$ E% L5 h! N- ], j) q

    5 H$ I6 O/ f! w' f4 \* L: I. ximport java.util.Map;
    ! V( v* u1 y  ^1 P) u6 Z! c
    . F5 l  B: k4 {8 P: w  spublic class RefreshConfigUtil {
    / U! ^  X  n% c. u' {. ~- s; B- C7 y    /**2 x' I+ x. A: K- [
         * 模拟改变数据库中都配置信息
    ! j, R$ d0 w0 c     */
    4 e/ {( B5 h8 b0 c& a/ y& D' m    public static void updateDbConfig(AbstractApplicationContext context) {- w; G" |/ E6 I2 M2 [. I) U
            //更新context中的mailPropertySource配置信息
    ; {; M" ^0 f! L( a) B) m        refreshMailPropertySource(context);5 ?$ L2 M$ ]# O  g; k6 w$ L5 P# P
    ) U# ~/ }! D) C- f. f
            //清空BeanRefreshScope中所有bean的缓存7 u2 X' N9 c( k6 m: S# D3 z( y% Y
            BeanRefreshScope.getInstance().clean();
    - e% l, K0 K. f/ x  j5 m+ \7 V    }9 }4 l# R  r* B8 c* h% b% o/ o

    & F0 B! F6 g- x; P2 B    public static void refreshMailPropertySource(AbstractApplicationContext context) {* I! v1 r. Q- n7 H
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    6 D: \, y7 c. C; c# o7 l        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)4 l( _# @/ n! e5 F7 b
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    9 Y: Q2 C' |2 u/ q& Y- V( L        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);$ T  D$ D6 o- z0 P+ i! p% M8 p% g% ]
        }
    + n* z" {( n" N/ M# E8 K- N  F, v9 z$ K/ {6 H, B
    }' F7 s  u+ ^9 m: o/ L( c6 l1 B9 Z! c* b
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    1 {5 C' K( F0 t. H: P; F1 I( Z+ R& W
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。7 P5 f4 l: m4 u3 d
    : u5 T2 n4 O6 l( j  i; N
    来个测试用例
      E1 f/ V! O& z1 A4 |9 e; d
    4 d" U" q6 S0 R5 l  U8 Z' n5 v@Test, F* T( y) S' N, [* S& s3 L
    public void test4() throws InterruptedException {8 I# j9 H+ c% q5 V( |! C! m
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 P4 r5 m: x/ q    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    7 U: I* i0 N  Z$ v9 C* U4 V    context.register(MainConfig4.class);
    8 e& v3 Y4 l7 Q* {( L5 Z    //刷新mail的配置到Environment: V* H; N* P5 k% s! o
        RefreshConfigUtil.refreshMailPropertySource(context);4 Q# h' q" `* {
        context.refresh();$ M$ l' c) i1 {2 {; K" ~$ R+ g
    # q/ Y4 {0 ]* `- ^- @$ X- z/ @
        MailService mailService = context.getBean(MailService.class);4 E; u! N! u5 B' |( N1 ^5 u$ p4 J
        System.out.println("配置未更新的情况下,输出3次");: c& J* Q9 k5 h: R
        for (int i = 0; i < 3; i++) { //@1+ w- r' Y& }/ z1 C8 o, R% q. W/ g
            System.out.println(mailService);- r% _. a) z4 G1 s$ H
            TimeUnit.MILLISECONDS.sleep(200);
    6 h' U& W+ v+ n% l$ s    }! v. e6 F# _: r' J

    % w: y2 b$ x! W$ c/ k* _    System.out.println("模拟3次更新配置效果");
    2 K2 |: E, V4 E) L    for (int i = 0; i < 3; i++) { //@2/ [1 Q9 ]9 |, s! ?
            RefreshConfigUtil.updateDbConfig(context); //@3
    $ Z/ H9 q/ Z0 t        System.out.println(mailService);
    * y) X% P6 L: J* |. ^5 o, h. r: a" W        TimeUnit.MILLISECONDS.sleep(200);
    ) X" T' s8 o! C4 k    }# P# ~% P5 h' u* `  }
    }# B1 z1 q1 Z! U$ r& g
    @1:循环3次,输出mailService的信息7 i  D% \9 m; s9 ^
    3 i2 }8 G4 w' [/ _& ?/ ]9 \+ W
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    1 w, P! M8 N$ y+ j1 P9 C6 i
    ' ^! I4 c. v  A, @见证奇迹的时刻,来看效果2 m2 _  [$ U/ H1 f; A4 G
    & u8 E0 q9 T2 l4 ?
    配置未更新的情况下,输出3次
    ! d& w; }( f. V0 C8 Y+ pMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ' F+ K7 b; W$ O# u3 WMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
      R5 E  w0 n+ |# LMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ! e  i- P4 O$ s8 T& p( H5 g0 _模拟3次更新配置效果1 {' n/ f$ Y/ B6 @4 Q
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}8 W3 v* v  N! Z
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}5 t) X- K' G' y9 L  _
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    ( F7 @  B1 G( t" N  {2 n" [" s' }# f上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    ! S) d) A$ Y% U  y) _) R8 M$ E9 a( c! \; d8 R$ j
    小结; Q5 Y$ j5 K5 T
    . B+ R' @; s+ T5 o' M/ U8 _6 X
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。9 @9 a% j' Y  P; m1 |9 M
    5 ]% |: ~1 B9 Q6 x% V$ L
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。. T2 B" t' K5 \

    8 ]1 \; M5 H$ g3 g$ ^# ?总结
    : E# b. Q; K# L
    ' _# f( I4 @; a1 M本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    2 S( o; R% D" T- s4 ?5 y9 E1 k; Y
    案例源码
    , L; t) v1 q& ]) M$ A( n# J/ O
    # L; s: d) L( z( rhttps://gitee.com/javacode2018/spring-series
    / ?! n* x9 E) K/ @路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    % x1 N, u; p+ b4 w6 H1 p  {8 ]: ^( y3 y- s; h+ C1 [
    Spring系列# ?6 N" N  A" Z# J
    8 {( z# y. e- H* O8 ?% T5 l, @6 O
    Spring系列第1篇:为何要学spring?
    $ Z; K# h1 M) F' |' B* G6 q0 A5 m
    ' Y7 k; [- c7 a! R+ LSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    # t+ i2 G8 y' X9 s! G; g: V. \, [3 Z. `( V0 F7 K" h6 o6 ]
    Spring系列第3篇:Spring容器基本使用及原理, t: m2 K9 ]3 y+ o
    . C! [5 ~, _0 E7 y' u9 m9 Z
    Spring系列第4篇:xml中bean定义详解(-)
    4 @$ Q  t6 r1 Z2 }/ _) V3 T# N6 i/ V
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    / |: _' Z0 B8 y$ F/ _0 J# {, L" o7 d6 {( L- w, _! g! B# T, f9 ~
    Spring系列第6篇:玩转bean scope,避免跳坑里!) L6 |/ p' N* b6 }
    ' O4 b; p( W- L) K9 B2 V
    Spring系列第7篇:依赖注入之手动注入
    9 ?( C! W* B2 M6 I  C/ d) x0 T3 A4 f4 A) R  Y
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    1 O' }9 p- C* O' N
    5 I2 u& s& H, xSpring系列第9篇:depend-on到底是干什么的?
    $ _" D' i) E) U" C% M
      A9 N) F+ ~' [5 e8 }4 PSpring系列第10篇:primary可以解决什么问题?
    / }  C3 N+ T" |4 B8 N3 g) K  `3 r& c. I/ \3 m1 n6 Y: b3 e
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?" r: ~, G! W2 M0 y* V( r
    * p6 z4 ^7 I4 o: l- D2 x
    Spring系列第12篇:lazy-init:bean延迟初始化
    6 c5 ~6 D# f; x2 b; X$ I6 Z6 y, y5 q4 Y2 L) z; O# [
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)% |3 ~6 H" F7 t; `

    2 H' C$ N  p8 a- S+ |: vSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?; J6 K) ]4 w& e8 Y4 u& N
    8 o) u' b7 a0 Y$ F6 T
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?$ O) e1 {4 X/ m, V# v' V6 v
    - u3 S# {8 y6 m) f
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识); l! j6 L6 Y0 h
    . L% o* Z. V. b4 b
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)4 l8 ~5 n! m/ G
    . S/ S) M4 ^" }$ \. T
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册), Q7 z/ i/ a5 s: k& n
    + o% }2 q2 h# e
    Spring系列第18篇:@import详解(bean批量注册)+ ?. P3 R) G5 O0 C( R
    0 t1 A( D# E' L5 ^/ [
    Spring系列第20篇:@Conditional通过条件来控制bean的注册* J/ z7 {( o4 G
    ( u- S, n5 J% h- o
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)( x+ R! O+ S% E2 k

    6 ]# C8 A1 i1 X( y# VSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解9 T# s. @% _& o  h5 j- \1 Q  }* D
    ! K  `# P4 |9 L; ^
    Spring系列第23篇:Bean生命周期详解# F1 G3 m+ s" x: ^

    8 h' |: }0 q. B$ Y- n! M2 PSpring系列第24篇:父子容器详解" H; @! o0 q: l0 P6 [0 i

      B* W) X$ s  h, _4 [# ]; L更多好文章1 Y% s2 I2 Y* ?! }

    9 {3 ~% T# W' {  k' R% _Java高并发系列(共34篇)4 s( H" W2 F8 ], v! `% V
    / R% T6 L' K; J$ U
    MySql高手系列(共27篇)
    5 O9 }7 X0 U5 }3 i6 j2 }8 J6 z
    % t, w% N& x$ Y" @7 AMaven高手系列(共10篇)2 S+ v$ F& |8 E' d( k' n4 K) J

    / J4 U& J8 y, s; N+ a. yMybatis系列(共12篇)
      {9 k, c  w# Y) B* `4 R: h- u; h8 x% P( x
    聊聊db和缓存一致性常见的实现方式& r" f' y6 X+ K  Y+ Q& o2 v
    ' ^1 U" H4 u( i: K
    接口幂等性这么重要,它是什么?怎么实现?
    8 x7 D8 x1 J/ X* Z( B
    , P, u8 b1 F  C! F/ y# b# w# l+ k泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!, ~/ K# q8 o) M6 Z; Q) }9 _
    ————————————————- K8 V6 i; [0 i7 R5 R
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。2 G7 ~  C( W9 Q2 C* Q" i
    原文链接:https://blog.csdn.net/likun557/article/details/105648757/ [& C, w8 L& f3 }" Y

    # B. g& G- L. q
    4 X: |  \7 l; d: e
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-20 22:05 , Processed in 0.415302 second(s), 52 queries .

    回顶部