QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5618|回复: 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!
    . u, u& a# R0 l& v) D疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    # u  w' I& u" X6 l0 k9 W! u
    6 d4 i3 y- {, W/ V+ D( Q! n面试官:Spring中的@Value用过么,介绍一下
      V* P7 y3 B8 W% Z  ^& _1 D$ X8 L# x$ |' J( G' U! H
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    $ ^) v. U. }: i9 ]8 ]) `+ W1 i, K
    面试官:那就是说@Value的数据来源于配置文件了?
    . x# l& i! F0 e5 L; h2 A( v4 P. c
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    % P! A$ z4 y! a# k( p0 I3 Z' O7 k1 \% O# e
    面试官:@Value数据来源还有其他方式么?2 w1 n. ~/ F( @# {9 n# E
    % N2 ?, |/ ?- x# [  D, _- t- a
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。6 ]9 I" c4 q; R; n

    8 s' L/ T3 r! B* B0 U" C9 o面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    3 ~* f3 b6 k6 e. q$ F4 ]3 o, B
    ) w# \! u; S, f( ~5 b- I* N: ]我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧) X6 |2 B  K# P. y
    $ j1 |) w0 C; i1 Z
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    ! B/ e, X8 |5 E  k. w% A; J! I  C( N: y0 j% x3 T: @. I
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能* ^, @5 `$ M" P, O4 a* H
    / h0 i$ G5 T2 b7 T& g
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    2 B. l, U0 c, [7 U
    & J3 i1 A; q7 g5 k# K. k我:嗯。。。这个之前看过一点,不过没有看懂2 i+ I% c1 d! J- u
    4 t4 T% w$ @+ N) u  y3 d, @
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?/ i0 i" A( z$ \5 V- S

    ) \1 X! i: e' E8 m我:3万吧
    ! F; i  w  F! ?: w! y
    1 |- E0 Q% L2 @# W$ \& H) t面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?, i0 \* G4 h3 a! G
    ) E! F. ^- M: p2 Y# S
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万" @1 a; V, r1 G1 K' S& \  a
    - }. }3 q5 s% ?* o' i
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    , x5 r/ a3 |9 j- q  r$ {7 [- P0 J" n3 F3 f/ @
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。- S4 {3 Y) m7 Z* f

    , h+ }3 ~9 c; M- ~, L这次面试问题如下5 w- i+ K  X6 a& }* |& S6 ]4 p
    7 {3 ]/ i9 f( ], j5 v8 F
    @Value的用法
    3 g' w1 V  M8 I& n7 o& Q
    + D" t# _! Q1 P@Value数据来源2 K% ?  e* p# A" t- `
    , S5 `$ @. B. X  N8 R; B
    @Value动态刷新的问题) i+ S7 E2 ]. X9 @. x5 U

    9 }* w. n% D" `$ M% N下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    7 o' S1 y5 S8 a; g( \# i9 |! m! w+ y' `6 t. i4 ?1 \0 x
    @Value的用法
    8 j* p" d8 R8 K6 V
    ; y2 _5 `3 l6 n! K$ Q! N系统中需要连接db,连接db有很多配置信息。
    / x- N, d: c& `& T9 O. q) ]4 G  K1 o& ]9 v" I
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
      R% p- Z$ _8 U4 }! I) b- i5 Y6 o5 K1 i
    还有其他的一些配置信息。: c4 d6 {8 h& ^2 v2 C% m
    , x6 K( e6 }( B7 ?
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    3 G) m. A8 C9 l
    2 X7 f( m' ~* g. @) x那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。5 m# v% r' e3 V# U' q+ B
    * S, y" K7 ]" j+ R& u1 M5 j! W6 S
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。& Q) ^" S6 n; z8 I
    ! b7 ^& W2 N( w3 k, {
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    . E( v/ f1 ]# {
    6 I( {/ j( p+ ?7 P+ c1 c& G" b@Value使用步骤! d& T9 q3 F! g- K! P

    7 r3 w( K; E: q步骤一:使用@PropertySource注解引入配置文件* U6 r* c4 G1 Y- F! A4 \( \

    & L+ A$ [& H0 p' W* V6 W3 I将@PropertySource放在类上面,如下
    1 A/ D4 S& [" s7 ?
    . i. |  q) @  O5 a- W0 ]@PropertySource({"配置文件路径1","配置文件路径2"...})  v- K! L) I2 |: A5 e6 V1 q
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
      |- d) T+ g$ b- k: L
    ' T1 m* _5 s8 I+ R如:
    + ]+ d9 ~/ Z; h6 X' e8 v( k! \
    * j& ^$ ^0 Y4 a5 f@Component
    6 ~% n. z$ I) X% p! P+ v0 x/ ~1 d@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}); U( q8 Z3 Y0 [( {  i
    public class DbConfig {
    " b9 D9 L" f; u  K}
    % [; N+ a# ~4 _步骤二:使用@Value注解引用配置文件的值
      P: w9 A- n! ?# d! g1 x+ m' z+ r4 ~; V; J* `& o
    通过@Value引用上面配置文件中的值:. y; `8 F) p8 C. j1 Z# ], q
    + `$ |2 s! V7 f2 h5 R" b7 o  H1 X
    语法3 @4 j9 I4 m3 w

    7 ^4 w$ ^/ v/ S4 e& v! ^) D  Y@Value("${配置文件中的key:默认值}")8 q, C- v' y3 \4 l' Y1 \0 R' V+ P$ A
    @Value("${配置文件中的key}")
    * i$ j. }& \& V  p* Q如:6 P1 E# t) O# y' {: @+ x
    0 ?5 Z6 F2 k4 t
    @Value("${password:123}"). z+ D! K, g* m9 w1 ^
    上面如果password不存在,将123作为值* ?( `* M( Q5 d; h

    9 z( X  S/ D; i2 x@Value("${password}")
    & @1 L: ?) L+ A+ q5 R上面如果password不存在,值为${password}
    + J3 Z8 S. Z. C5 _& x
    3 }) b$ U. o6 Q5 F. _  ^( o' |& s假如配置文件如下
    + ]) t$ s3 [7 k; W4 p) W6 l' h6 C5 ^1 K5 P5 Q5 a  {3 m6 ~
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8. L- @+ m  x% K+ [! e
    jdbc.username=javacode0 w) s' R- Y* J* ]0 \
    jdbc.password=javacode4 i2 W  R; {0 a
    使用方式如下:
    2 n6 R. I" A" Q; l0 E, f9 r" ?$ @% O7 q2 o# Z1 u; D$ `0 a
    @Value("${jdbc.url}")
      w2 E3 E, v7 Q* C: y1 xprivate String url;8 k1 e$ z2 Q3 F' o' S9 Z
    6 F/ b! l( P1 O0 U  {% A. ?3 h3 p
    @Value("${jdbc.username}")" L0 |7 u, ?/ J
    private String username;$ {( l0 B' _7 o
    ! b7 y5 T7 M: w- z( h( Y
    @Value("${jdbc.password}")
    ( K; {# \; L( j2 f6 _$ gprivate String password;
    ) j! @2 m8 v6 p2 ?2 W4 j5 d下面来看案例
    3 C/ f4 K1 I8 b5 c; |- t
    - C9 U% B" O9 F! Y% ~8 L案例
    5 q. d: c* Y8 A5 D1 t7 M
    - Q9 h7 I+ e5 ?) M# i来个配置文件db.properties
    / q) c$ h6 K8 `( L5 x: F% R0 c$ w$ b: V
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-80 w; l/ z4 `1 G$ ?% d) D+ J
    jdbc.username=javacode
    ) q; Y: t6 s/ t, Z" @. Ajdbc.password=javacode
    & e$ P% R) H) n, [+ ]3 z来个配置类,使用@PropertySource引入上面的配置文件
    5 @* n) p) g1 `( s
      T# }; l) c5 @# b+ A* z# t9 xpackage com.javacode2018.lesson002.demo18.test1;$ T$ q$ N3 }3 Y9 g, j. y
      {  `; _7 j* d; j$ ~$ J4 J3 j
    import org.springframework.beans.factory.annotation.Configurable;, l4 @  I4 e, d: O
    import org.springframework.context.annotation.ComponentScan;
    ( _4 b% `' G; t! _# r# G! o$ I0 aimport org.springframework.context.annotation.PropertySource;
    3 n3 q3 o5 l: O% m  \
    & F6 x; ?% B( x$ o@Configurable5 T5 W4 b6 n0 \" d/ y6 [
    @ComponentScan
    $ A+ A6 [: D. @7 Q% V@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})+ J, W" R( W2 }" @+ H5 N" S
    public class MainConfig1 {# z/ }% P$ N, ]
    }
    . e# `4 D" ^( O9 H来个类,使用@Value来使用配置文件中的信息
    : @/ w! `& g3 Y8 `
    * B5 P9 A2 h6 I) ipackage com.javacode2018.lesson002.demo18.test1;# S. f5 d1 l9 y: t

    " D" P. w8 q9 [" ?, I8 X' d* yimport org.springframework.beans.factory.annotation.Value;
    1 `1 F& b7 E  m; C. cimport org.springframework.stereotype.Component;- f& Y: l! ^/ e: m, {1 G

    # Q6 V* X3 u5 i2 p. m* n@Component6 ^2 I: x3 ?: Q! }/ E7 E' g3 ?$ ]( p
    public class DbConfig {
    & p4 X: `& y( D/ O
    ; c) X  _) u' }    @Value("${jdbc.url}")
    ' ?0 N7 |) y: y- Z: }    private String url;8 F" j" _; V8 W5 q% A, e
    / D3 K& w$ x$ ?8 v" s+ S* y; z4 {' n
        @Value("${jdbc.username}")% g# ]( [1 k' b; p( n9 z7 G  K
        private String username;
    4 s/ c6 S9 z& l/ r4 j3 |
    - \# z) a0 }; E    @Value("${jdbc.password}")9 ^( _; E3 G* s
        private String password;
    ; F$ F% a' e, ]6 |8 i6 q4 c$ p$ t; W$ X8 P! Y$ m3 |; V
        public String getUrl() {
    ) @  B% F) K$ Z& Q0 W# o        return url;
    : n7 ]5 [1 _& S- v$ g4 r    }$ Q" M% I  [+ V

    ' K# {$ k% c  S/ ~0 o    public void setUrl(String url) {3 E5 Y. {( e7 y/ A- n" h6 _  L
            this.url = url;1 b% s9 u3 P1 `9 U2 F/ z
        }6 L) H. q& X8 f9 L
    5 r9 [; [1 E  J) m, |5 x0 y% }. H% s2 p
        public String getUsername() {! F1 R, w- I6 _* g- s% A, V
            return username;/ o# j. k  X9 b' ^; |7 _0 U
        }
    6 |) `. q6 _  |/ Z* R  {
    0 [7 X' g7 h1 w7 D; ~: d    public void setUsername(String username) {. r, c4 S5 X& Q
            this.username = username;! Y: j- q' U. X# N8 S$ v/ z
        }
    1 _' @% U# g0 m1 u' E# y
    0 {; b: c6 K* W. Z" @) q- |    public String getPassword() {- C/ |3 }% D2 a3 U( {1 M- S  Z( R) U
            return password;& T, w( x7 |" [, q5 g6 X4 L9 j# |
        }
    + a1 @4 Y( Z" {( n. g* l8 b4 J' G" }; b* r( @
        public void setPassword(String password) {
    & A' K. |! V% |6 ~/ P- X6 E7 O        this.password = password;
    / k3 x  D% d5 M3 g0 j- |  i    }
    6 W/ p7 j" X# K0 Z/ R  u& `
    " ]  `$ \- d5 a  x5 I    @Override0 t- k% m/ H! N% y5 Y& E
        public String toString() {
    3 Q8 ^% D: z5 F5 l6 A2 Y! m# q; g        return "DbConfig{" +: Z6 N5 ^+ W. G6 }- t, q. a  X- ~
                    "url='" + url + '\'' +# L5 D: S" i- ~: L
                    ", username='" + username + '\'' +6 T$ w5 y# A5 Y1 O! m
                    ", password='" + password + '\'' +
    8 l# H. t4 d+ f$ A5 H8 g/ e                '}';
    ! w4 X( h2 Q8 n* x$ ?8 [/ |- n    }
    7 Z4 I8 Y7 n; n9 k}
    ' @) l" U- r5 o5 `上面重点在于注解@Value注解,注意@Value注解中的& G- x) m6 D. n+ w$ L+ C5 t  X0 r
    6 ]! [) r/ z! z; U" f
    来个测试用例9 b: _1 T6 b' _

    3 u+ {6 L0 j& q+ w) Z$ O- Dpackage com.javacode2018.lesson002.demo18;" l- _; z9 M3 x9 i9 Y
    ( b: R  h& H( N) M/ k# U
    import com.javacode2018.lesson002.demo18.test1.DbConfig;1 d7 g  u* {3 F! `3 N5 e& ?! P' w. e
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    2 ~$ p. k" S- W: N6 X; v* M9 f. jimport org.junit.Test;
      u! N( i% a7 t9 kimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ; \. f& L' u& g' E1 X0 r' B
    - P# n; V$ v# O, z( ?% apublic class ValueTest {
    % |$ P! L% x  G5 J7 q2 ~6 c0 E8 Y2 G6 |9 x! W; j% K% U
        @Test  x2 F  b* G% W
        public void test1() {
    % j0 B) n+ l9 I  r  I& H# F        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 S5 D! i) ?$ B% i8 @9 k3 U8 \
            context.register(MainConfig1.class);
    $ W- Z6 Z7 {$ f% V        context.refresh();
    * Y; [# n& p" ~
    : b5 A5 \& S  \0 R        DbConfig dbConfig = context.getBean(DbConfig.class);% V/ |2 s, ^8 p; }' W( g
            System.out.println(dbConfig);
    3 @6 d2 r2 a  v! M7 N" u    }8 h  y+ C- W5 s" h
    }
    ) h0 q4 D7 Q: |4 E5 @( P运行输出. n! U! T8 L1 C: D" P5 M& {

    ; C7 i$ K8 [! M7 u- L! l' mDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}( g+ t6 Z4 j* X8 k( K
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。* D2 H! w- n/ P
    ; Q# E: v& O6 m4 h! @/ `5 W
    @Value数据来源  P3 `# r  a! j& @; o  w
    - ~8 u7 h- m. X0 f$ {/ x4 |
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。0 x+ ]! G$ g( T( ^9 z0 I6 }+ `: p
    - W9 l7 c1 V& A* f
    我们需要先了解一下@Value中数据来源于spring的什么地方。' Z$ L6 _% b' ?1 L' O
    8 o- {4 E3 W6 i( f: j
    spring中有个类$ F* N) g' k7 m: g
    " X) X- S7 W. d( I2 {
    org.springframework.core.env.PropertySource
    ; L" }) J6 S( k4 Z可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息6 ?! f- E/ n+ G% A' C
    ; m. n) M- `9 a: J# P! I
    内部有个方法:
    " C: h2 n6 i' V: U9 `9 U5 c+ i) Q# G9 V+ c! C
    public abstract Object getProperty(String name);* O- D! I' Y( L1 d- J
    通过name获取对应的配置信息。/ r: _7 H& }% @
    " @' F/ r; n, K: m4 p1 F
    系统有个比较重要的接口3 V. ~% U# B' N1 l
    ) X, z, M+ J2 t0 n( X) R: c# \
    org.springframework.core.env.Environment+ M  @' r( p# Y* f$ |" E" A
    用来表示环境配置信息,这个接口有几个方法比较重要
    ( j; D9 v9 M* o1 K  D7 L
    6 q: K- h! l! k  v7 lString resolvePlaceholders(String text);& v3 X0 X( F: r2 H" N
    MutablePropertySources getPropertySources();
    % B" F2 d! J2 z$ q- C6 s# a9 zresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。$ y9 ^5 a) L5 X0 S

    - \: Q+ r4 ]# l( i6 i1 P$ h' lgetPropertySources返回MutablePropertySources对象,来看一下这个类5 C4 i! L2 s; i# d/ a, p( d

    ; q& }# P* w  epublic class MutablePropertySources implements PropertySources {
    5 K' m9 @* [  s0 S" {& n+ F+ s2 _( r: L/ L
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();* e! v2 }1 ^9 j3 l
    0 V  u4 `  A+ M. ~5 c- {0 [; X
    }2 h- d' ?/ B3 b$ f6 E/ K8 \
    内部包含一个propertySourceList列表。& i& W2 K3 S3 }7 o4 g. r+ R6 t
    6 d7 y( k& o* a0 D
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。7 v" R  N9 X( W) O6 {
    4 Q; ]4 C/ a+ L  h
    大家可以捋一下,最终解析@Value的过程:
    * S- E( T5 B& Y' [/ g& g# p
    ) P  I6 T1 O+ T* T8 x. e& y1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析8 d2 E& }' G$ p" x5 w  M
    2. Environment内部会访问MutablePropertySources来解析
    " K) |' N' X' M& H7 b# R3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值' `2 d+ N2 v7 W
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。* b, k6 q7 @4 G7 p! f" @
    8 G1 e2 |/ R3 A5 U9 H6 Q/ a
    下面我们就按照这个思路来一个。
    . v1 a; i& P9 l
    9 A. o" ?3 M) V& h来个邮件配置信息类,内部使用@Value注入邮件配置信息- R" n" f/ W: i% ]4 q) M) S$ m
    , i1 ]5 J- C# g% N; j0 v1 u% J
    package com.javacode2018.lesson002.demo18.test2;% ^/ h8 _; s, I: H4 e- `, {/ i
    # O/ N+ k- q8 _7 E
    import org.springframework.beans.factory.annotation.Value;0 E+ J) P& y! J. {
    import org.springframework.stereotype.Component;' r3 o6 N' l+ ^) U1 v. m" F, v
      N& y9 ~+ O2 u+ @1 q
    /**1 P: z  L1 F3 f6 `9 T
    * 邮件配置信息
    ) w9 `. H+ {' p& [# G/ ^ *// M1 ]- H' b# ~# \* e9 Z' H
    @Component; c3 `/ F4 U; s' B$ {* a
    public class MailConfig {
    " H1 S" h* B4 [" U8 z& i, y* g3 a' |9 D7 G( [/ w+ a
        @Value("${mail.host}")4 H, e# a9 k. B. @: S- g
        private String host;
    : c  }3 r5 @/ T
    " j7 T& m9 f4 e! ?& W    @Value("${mail.username}")
    % i0 w' Q$ U5 F  w- M+ ]: U5 x/ c    private String username;
    ( _% v* S, _  m( q& z+ d
    & `/ L9 s6 i1 e* V8 f    @Value("${mail.password}")
    & W- Q! J( |1 n, b% Y5 E    private String password;& m. n( ?- }& }8 N5 F
    9 R) V9 A  d3 Y1 x) ]  Q% G
        public String getHost() {! H7 p4 t, [+ U# s  @) q+ R
            return host;
    # i" a( K: n# R5 Z    }
      D) \+ I4 s3 n7 h9 v2 g7 y0 O6 X6 r2 I
        public void setHost(String host) {+ j+ `0 t1 L8 Y$ o/ v% r0 f
            this.host = host;
    / [8 d  [. \* o  F2 U    }
    7 [2 s) j8 S1 Y
    * p& K6 f5 U: a  b- w, Y    public String getUsername() {
    ! @1 @0 N% O$ t. t1 X# R        return username;
    8 N1 u; D+ N) ]    }
    9 G7 s. a% A- a/ A' _7 u% @# I" Q: P' Q  o, G
        public void setUsername(String username) {
    2 v9 F, J6 N6 M3 i: r2 E        this.username = username;
    : O* `9 W* u7 b, \+ {, ~( I    }
    $ ^  a- }8 P7 E1 w: v1 ]
    ' S- @# T4 O( z; n5 A    public String getPassword() {
    1 c6 M* J: W- Z  I        return password;' {6 S; o5 `1 s9 s/ i8 P" d7 M' I
        }
    1 }. o* a' }0 h% Q; r$ V$ s% x: [" k, R) p
        public void setPassword(String password) {
    , o+ Z- t+ A! o  M" J$ @- k% u        this.password = password;
    - j8 z  a  D8 s, d    }" ^' K2 j1 j1 B" \% Q

    - b; Y/ U9 z: p( l) ?1 G    @Override
    ' a; k* U9 s0 d/ v    public String toString() {/ n7 n( r9 ?6 M
            return "MailConfig{" +& W1 e7 E3 R2 `2 k* o, a/ q/ a
                    "host='" + host + '\'' +0 b3 G8 _* L3 z# L: M0 @! Q
                    ", username='" + username + '\'' +
    ( z/ n- J; h. g! B5 x6 O                ", password='" + password + '\'' +; Q; \/ \+ L6 W- k
                    '}';/ f5 q& d6 w0 m8 i' S. q- l
        }; F5 e3 Q7 a  k# \" A# T
    }
    0 u6 j1 i" Z6 p再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中2 G% t8 v* v. G0 C; E

    3 M2 Z! H- {; w  F! k% \package com.javacode2018.lesson002.demo18.test2;
    6 a7 t* C+ G8 X1 I$ }8 Y8 [
    . j; n, d& B" m7 rimport java.util.HashMap;
    : y0 ^$ U9 s8 K8 b5 zimport java.util.Map;* A* [$ \/ q2 _  Q0 g( f+ T

    * g- L1 n8 l0 R0 a$ wpublic class DbUtil {
    . z% U5 x8 _( V* A% I0 h    /**
    , k! Y7 _, ?5 V# ^; C     * 模拟从db中获取邮件配置信息4 O, Q, n5 m& z" q9 M
         *0 F5 h- f" q2 C0 h
         * @return  Z$ X. ]5 |- {1 [' d; q0 u0 W/ d
         */7 ]+ W2 w( J" b$ Q" U6 U
        public static Map<String, Object> getMailInfoFromDb() {1 d: w8 w; R& A: q
            Map<String, Object> result = new HashMap<>();0 u/ M. l  G7 W; U$ }2 K3 G' N
            result.put("mail.host", "smtp.qq.com");
    1 E# b. x6 I$ i$ B' t& d. \$ P        result.put("mail.username", "路人");( j0 V& s7 `- o
            result.put("mail.password", "123");
    # j6 L9 D" y% q: E* b6 S        return result;
    0 I$ z: B, D  `& G3 {    }% s4 X+ T5 g8 K; ?
    }7 K$ W# q; Q, w$ o7 N: Y; R
    来个spring配置类. [4 p$ c8 C$ ?, Q* p# n

    : ?- G; F; N8 Q/ A5 {+ T9 wpackage com.javacode2018.lesson002.demo18.test2;
    - G) r% d) Q% v& a) q7 f; L3 z% R
      o2 `4 H+ [# e4 a) ~+ O, E* Wimport org.springframework.context.annotation.ComponentScan;% i; z- B4 x/ G: D$ g3 b8 C
    import org.springframework.context.annotation.Configuration;
    8 d+ X3 m4 ]- F, B/ l& y, d/ q  y4 c! g$ i! A
    @Configuration5 g" |7 K2 o$ c) p7 o: D
    @ComponentScan0 m1 x2 {( I5 i( j% U+ U2 g, @
    public class MainConfig2 {" m1 s" p  u; I" C* t4 J# \2 |4 R
    }
    & J7 u6 {0 N' j' z( M5 @下面是重点代码
    7 v5 N( W& K3 z' M2 y' J; C5 H- a! g# f# a; s+ x
    @Test+ i, E5 [" o0 n# A7 o( A
    public void test2() {/ Z) h4 ]6 ?. U: \2 u" D
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();! N. U, R, w' _9 C

    7 k% Q  ]( w+ H. l+ S! ~% O    /*下面这段是关键 start*/
    % b3 k1 [- E; ^6 F* s8 [    //模拟从db中获取配置信息
    & q- B1 @! I( U5 S' b    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();# B) b0 W3 X0 N6 O% e, p% _
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ( I6 z+ E, {8 w" \2 c    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ! C; z  S/ s* R: X3 j; F9 l8 g    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    . D3 z2 @9 |& R) Q5 l2 V0 p1 s    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);* H$ \) b- M) ^
        /*上面这段是关键 end*/9 @$ d/ |, y! D
    % O- N' G' b1 r
        context.register(MainConfig2.class);5 b1 |- w1 ?7 v( o# U
        context.refresh();2 v! B5 e! `; J% Z
        MailConfig mailConfig = context.getBean(MailConfig.class);9 [2 N  [5 C! G  k3 `0 L
        System.out.println(mailConfig);6 ~/ Y# y. k* j8 Q! H. S# n
    }
    1 O, e4 m. L: j7 ^* Z注释比较详细,就不详细解释了。* H" N" ]" x2 ~2 F' L
    7 t. g3 Z' K5 S7 h" Y! A
    直接运行,看效果
    ! d' ^& K3 K" _1 R! m; l1 b6 ?- Y
    ( G* e4 b5 v$ B9 vMailConfig{host='smtp.qq.com', username='路人', password='123'}- i) G* j2 v0 ^7 t
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    2 u! B' j6 m7 j3 s
    : g2 g' z* i" w9 }6 \上面重点是下面这段代码,大家需要理解
    ; N: y; p& E9 d2 s" V2 q* p# L6 [3 p" P* H% H1 V2 u8 [% y
    /*下面这段是关键 start*/
    ; O8 P6 u# H" [' S# Z//模拟从db中获取配置信息
    # h: J: T1 X" m! H& HMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();) T. x1 ^) P8 E5 J1 c
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    9 m& w9 Z1 {- V$ \. t: k7 ^MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ! r+ ^' ]0 r; m, Z2 S//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高  Z9 e: p0 t) `) j" F9 h
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);6 M# r$ J! y* e8 {8 t7 M
    /*上面这段是关键 end*/
    ( a2 B9 f! X% Q& J3 m" ]9 x咱们继续看下一个问题
    + e/ g2 K. z3 p8 {8 w
    ! _% s# h# N9 R如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。0 k6 Z* d2 i: l/ x6 O- e

    3 `: G. K' P" @7 p, l@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。" f! x) i* ?( w# Q* ^3 _  h- d6 T

    ' A7 \0 I! b8 a- \2 _4 C# A实现@Value动态刷新$ N7 E; U( ]3 v1 d. V

    / \' v# |* O8 V0 X6 P7 O: G* x先了解一个知识点$ E+ k7 y# z+ r) h
    2 P. T  y; Q) e5 L/ G' o
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。5 E2 {# B7 I( _1 e6 a- J
    # l: ^6 U8 U0 m8 ]
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解6 f0 n5 o" s: T) t, t9 L

    & c* w2 O8 B; b1 ~- Ybean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    # Z' p3 n$ W7 l; c. M, y' y- U& I7 I( F# P8 G
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;9 B7 P2 {7 Z: g4 b1 p
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    ) I. f% T/ P5 w0 `1 G- w
    ) D5 A4 J) @: G8 s; kpublic enum ScopedProxyMode {
    7 D5 L8 h# u) W2 b) F% O+ F    DEFAULT,
    ) S1 \  v# X4 j& @, I    NO,6 @9 O- q* j5 I4 F% m1 `
        INTERFACES,) d( m; F' b. j: p( Y
        TARGET_CLASS;/ Z9 W8 y/ F) W' e7 n/ t
    }
    7 n0 U4 P' ^" P& H9 z( x/ b前面3个,不讲了,直接讲最后一个值是干什么的。
    8 ]1 \  r* C8 J: V/ c+ z. |
    ; u2 [2 L* F, _& D6 g当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。3 G: u4 M' `7 \) h, J1 U

    4 g1 Z1 R! r& I. F/ `3 L理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    + E) J$ ~( {* l  S$ H# \% C) v8 R8 ~
    自定义一个bean作用域的注解
    . I! V9 Y  T' C' D5 N0 v1 w. T4 [$ p8 ^) f
    package com.javacode2018.lesson002.demo18.test3;
    1 c  V9 l( `. r/ h/ I, X1 w% ^9 i  n5 P
    import org.springframework.context.annotation.Scope;
    ( F/ }4 `7 k4 V  Bimport org.springframework.context.annotation.ScopedProxyMode;  x2 f7 B( \3 ]. p! r, W
    3 ~5 L3 x+ T4 k  T2 f
    import java.lang.annotation.*;
    $ X( j0 U+ o% q* F* {2 I
      f3 Y, W, V3 p@Target({ElementType.TYPE, ElementType.METHOD})
    ! E5 Q. B7 L: z  e" U7 p" c@Retention(RetentionPolicy.RUNTIME)
    $ x2 D' k+ q" B# Q/ A6 d@Documented
    2 w' l! w" [) r: ?5 c@Scope(BeanMyScope.SCOPE_MY) //@10 S: M# _+ X3 ~7 s
    public @interface MyScope {
    : @5 I, p* B0 S. r, E6 Y. r0 f    /**
    0 D# v0 x$ J( _6 F  l9 q     * @see Scope#proxyMode()/ o, a) P, P7 o% s* @
         */
    % ?  o7 m( o& B/ ?3 ~: s    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2# f7 ^. Y* G- J5 c6 l+ P. h
    }8 }+ ^8 q8 N. e+ s7 y2 C
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。4 J8 @" [5 ^5 ^' [2 Y

    " o' l* n+ u- O0 M@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ' {, Q$ p6 S/ u, M- b0 w* F
    7 M1 }' ?+ c. ?" M' `5 P@MyScope注解对应的Scope实现如下
    8 Q  w6 n0 @, t2 ?; `/ s- c" l
    , d: z( s. e/ ^/ m* T  O/ u2 j- \$ Qpackage com.javacode2018.lesson002.demo18.test3;
    - \) o2 b; |$ O8 {3 W1 W$ L1 {  w, y: m; w3 S: K
    import org.springframework.beans.factory.ObjectFactory;6 Y1 x8 ]' p* Z7 ^* s; {
    import org.springframework.beans.factory.config.Scope;6 L: X/ [  C/ H, p& V2 D6 Z
    import org.springframework.lang.Nullable;! y$ E5 y+ W3 W( r
    * N3 r6 N' N; T0 ^$ ?& h; V4 e2 z
    /**
    ; L# t  B8 v( k3 M0 Q% {& q * @see MyScope 作用域的实现
    + y* p: O, p* K! C2 |0 L5 p */" _8 V$ K0 X/ e; h- ]
    public class BeanMyScope implements Scope {
    $ \- b/ B9 g5 Y& u, L  Q1 }
    3 t8 v7 c3 F! x' ?  `4 j$ j# A: W2 F    public static final String SCOPE_MY = "my"; //@10 r5 ?4 r0 T7 s

    & C  L! p$ z% H# k- i5 a3 y- a    @Override
    " P6 m# ~: h- V) \( a% Z- |1 X    public Object get(String name, ObjectFactory<?> objectFactory) {
    0 u0 \  L( i0 i- x! z5 Y7 H% I        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
      r# i3 J9 m" C. Z' q        return objectFactory.getObject(); //@3
    + }) q! d- ?0 R, T+ U8 G% j    }* t* v( ?5 Y5 V1 Q# q
    & x2 F2 l6 A; W. n) Q# e
        @Nullable. w$ V% r) J0 Q* P. K
        @Override
    * b/ Y* R! f) \$ J5 e/ n' z3 X+ w* k    public Object remove(String name) {
    ! e" J2 D& s+ h5 G8 `& W" x        return null;5 B$ M$ i  e2 E+ y1 p
        }- O3 E/ g9 c6 i' F) P8 U
    / c/ C/ a- U5 [
        @Override
    # j2 V/ ~/ V5 Q* ~  j    public void registerDestructionCallback(String name, Runnable callback) {/ |" D1 O/ I1 |
    ) \, u3 F$ w) N0 |' O$ C' v+ L
        }0 [- W. `7 |8 _) n  ^+ A) k
    * D9 A9 u+ M' M8 a& Z: K
        @Nullable
    3 S, a6 r2 \' j% u8 |    @Override
    4 R2 T; s7 Q% {8 r( ]5 q    public Object resolveContextualObject(String key) {
    , U; S& F" O% o7 ?+ q0 U+ G        return null;3 r" ~5 w, R! l  A0 i5 K3 m
        }4 ?! L# h* ^: N. ]- y. R/ T& ?% K
    ! D5 n  ~+ w, n/ z% q: R
        @Nullable
    3 W/ d& ]8 d3 ?- m8 Q4 w    @Override
    2 Q& B) M0 ^" N0 o    public String getConversationId() {
    4 p0 P( n9 \( t1 U7 k        return null;. L' e) x& m: ^. f4 e3 N' R1 Q8 F
        }
    ' |8 i1 O. d2 ^* T) p. A# S2 U! R}& [. B% }2 E) z/ }' Y0 @
    @1:定义了一个常量,作为作用域的值
    4 N2 z2 Y* x4 U/ N$ D' @8 h$ d% N+ y0 r! m* p
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    8 V5 _! R. F+ u- F  r6 D# b4 a& l+ T! X0 n7 u/ E7 c
    @3:通过objectFactory.getObject()获取bean实例返回。
    - @4 H9 {/ E: d8 m6 |/ b! P6 Q7 p
    : |* E1 ^! ~6 z# {; l* U% j2 J下面来创建个类,作用域为上面自定义的作用域+ ?5 T1 }5 h+ k2 s5 g2 E

    0 D7 k& i. s% _package com.javacode2018.lesson002.demo18.test3;
    / u% o$ m' D- x( A! P' X0 ]0 [, |) w) ^' g8 t& a( I. K
    import org.springframework.stereotype.Component;4 T: C8 b3 O% D0 z! {" ^7 d0 n( Z
    / B! q* b6 ?. j
    import java.util.UUID;) K, I: y) @1 Y' c: J# ~: [

    4 q; b) D9 Q+ o@Component
    + _7 Z8 {6 Y; V@MyScope //@1   x/ |3 P1 L# m  U8 z4 ^
    public class User {
    + d7 k5 E( ^. a! \& U
    , _; I/ m/ v! t- l    private String username;7 a# Z- f! y* a. q8 S+ q4 J
    8 B( B, _$ w1 ], y) D& j
        public User() {
    . Z; l+ w; ~' H+ p, n: c        System.out.println("---------创建User对象" + this); //@28 k2 X9 u; j. Q: N
            this.username = UUID.randomUUID().toString(); //@3
    ( P1 `4 R1 Z8 l% s) ]    }1 A- m- z* ?7 h' \! ?

    3 t2 h* b( V# w9 i% \    public String getUsername() {
    ; Y; a4 S" o( K5 S/ X        return username;
    ! L4 G# G( [( g6 T- _2 c% A    }
    , H9 @, ?1 j( W  L; R& S/ P
      c# `% g9 N, R. d* q    public void setUsername(String username) {, e# M) J9 L0 o$ q  P
            this.username = username;3 J/ [4 I! z# Z
        }
    8 Q+ s( q: z+ `& Q% p& \
    0 V5 |9 |( _; j# Y8 O  ~( I}0 k9 |( N( |* y  D4 Y# w+ @- l
    @1:使用了自定义的作用域@MyScope3 O1 }% l6 g8 q& j% h+ w

    8 v0 a+ |0 G  }@2:构造函数中输出一行日志
    - q8 S5 d$ c! s6 u+ E" y' c
    + A! M7 E/ _) L+ b@3:给username赋值,通过uuid随机生成了一个
    & }# {. R+ ?! X! `) E3 `! i  r: l1 ~8 i
    来个spring配置类,加载上面@Compontent标注的组件
    6 f1 J: X, |" j7 ]
    3 v% o$ K" o  ^; }2 qpackage com.javacode2018.lesson002.demo18.test3;6 ]4 I9 K  f5 m8 G( C6 J
    1 b  i. `4 K9 {+ B
    import org.springframework.context.annotation.ComponentScan;
    ' V8 q. C7 [! C) J% {import org.springframework.context.annotation.Configuration;& ?+ O/ s1 l( U1 ]! t3 i
    ! Q$ z4 T# {9 D2 I5 K+ G
    @ComponentScan
    ( I$ m4 ]2 B+ ?1 P) Z) R@Configuration4 \% b$ A5 T  p- O9 Z& a* h
    public class MainConfig3 {8 `/ H! [2 V- f. u4 s' B; Q
    }
    # R, k6 O0 f1 K9 t- Y8 }4 H1 v下面重点来了,测试用例
    " `& V5 ~3 O& J% g, W8 Y
    ; C3 I& n9 m. H! q' @' z@Test. b4 }* a& D3 X# u
    public void test3() throws InterruptedException {9 I0 e# C7 G  k9 Y1 M
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; @% O: D  M4 }3 f# E# s    //将自定义作用域注册到spring容器中7 k4 f% }$ V% J  _* I2 v( J
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@17 @+ u' S8 P4 z5 d) a+ p
        context.register(MainConfig3.class);2 }; ?2 c. W5 S) o: `. _( }8 E
        context.refresh();
    ) y. s' q+ l$ K( k  ~/ v  Q3 h! r, m# c: d% M  H9 }) R
        System.out.println("从容器中获取User对象");
    * a, L6 `% I. r0 I$ u' Q    User user = context.getBean(User.class); //@2/ J) S0 Q0 B2 ^9 L1 M7 K
        System.out.println("user对象的class为:" + user.getClass()); //@3
    0 f( I' \/ m* ~% G* b1 Z) G) ]
        System.out.println("多次调用user的getUsername感受一下效果\n");' v8 d3 j+ R& {( E+ a
        for (int i = 1; i <= 3; i++) {4 @( F6 z2 f$ t" A* Q8 [* F
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));8 v$ I: x1 L" k) ~0 F$ T
            System.out.println(user.getUsername());: R6 m+ B) L9 m! Y3 [' V+ T
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    8 Q" `# U' ]. K- V& m. m    }+ d6 h' W- `0 b3 A6 h4 S
    }
    9 x3 i8 S/ m' E/ T) T8 i@1:将自定义作用域注册到spring容器中
    ( C; p" Y8 g2 T1 V  Z! L- J/ R
    # \4 m+ c9 \; D  Y@2:从容器中获取User对应的bean& q/ r! G  k' i8 g& C5 T

    , X6 x: P9 c6 X( [9 ~@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    . }# `6 v/ N9 s' x" R9 |9 e7 X& p- q; O: y
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    * S; }3 X2 |- w9 c8 Z" X( H, ~0 t+ U) |- C* t$ g2 T" T8 J
    见证奇迹的时候到了,运行输出4 f" a) {4 |" T4 H% S& P2 H
    0 S4 u8 |$ Y" [: h1 {$ |) s% C  n/ W
    从容器中获取User对象( r. I% s; w% c% Z8 i( Y0 p7 D
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
      A: g- V5 X+ X! _多次调用user的getUsername感受一下效果1 f6 B9 M) C$ y! u) N: _
    ' W' C, l5 L+ i: b0 X# C
    ********
    " b  `( e9 q1 \* t2 }+ k9 g第1次开始调用getUsername# T. f9 s/ C  r
    BeanMyScope >>>>>>>>> get:scopedTarget.user" G2 z/ _# }$ f' C. }/ P6 d1 U; ^
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    , O' _4 d! @# L: }% [# a7 z7b41aa80-7569-4072-9d40-ec9bfb92f438' x- \# [& q4 T7 q6 O# V
    第1次调用getUsername结束
    % D  C7 L  P. ^; N, h0 ~( C7 N( {* V, Z********. C0 t+ P8 O: G  s+ g, Z
    4 j7 `: y8 ^, @2 `2 p: Y9 W
    ********
    ; ^* c( m7 g$ c# g! l8 v第2次开始调用getUsername
    3 f: Y4 t8 q  c1 L- O% C7 l8 LBeanMyScope >>>>>>>>> get:scopedTarget.user
    8 {. X, M( \& O% P+ U4 }---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b: M  d4 @7 j# i7 T" a
    01d67154-95f6-44bb-93ab-05a34abdf51f
    3 c' D2 Q! n, p7 V) j第2次调用getUsername结束
    / H" O% _) _7 V$ ^2 d3 ^********
    3 O! }$ P# z- ]
    6 d- D/ N2 v& i" F********  _  M5 X" F- Q! Q7 Z4 D: C8 ]$ _
    第3次开始调用getUsername, p0 L+ ~& p! @; q  s0 w6 U! O
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    . l; t9 B+ I. Q, l9 ?' c; B3 L---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    * s) d" h6 H% d& e/ K+ d0 c76d0e86f-8331-4303-aac7-4acce0b258b8
    6 v, C+ S1 Q0 o3 k, j5 V7 @* A第3次调用getUsername结束" F+ `/ D# ^3 f6 w, p
    ********
    7 N8 W+ Q- Q$ k$ M  w( s1 d; n从输出的前2行可以看出:( H, H/ x, L3 Y. y2 A9 p2 `  _
    ' x0 V; Y! j5 x+ [7 L* G9 N4 [" ~
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象# d5 U" o& C: S' K& Q
    * C, `! \- W, [2 A1 T7 t8 J0 ^
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。$ y; |: `) T- {

    , v. p  c2 Z5 a8 i后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    ; @7 X1 O' r; k" D, k5 V( ?& Z$ |2 W& |0 e4 f# A! P
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。$ T$ e# Z+ c+ t: P, c6 Z

    + m0 `! U( p: y0 N动态刷新@Value具体实现7 N5 T5 m! s! }- E8 n

      ^4 F( s, u0 `7 ]; g+ s5 S那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。1 r% [# q0 o6 |. x5 U2 ]& F7 n

    ( {* B. G6 u6 a6 m: X先来自定义一个Scope:RefreshScope
    % a3 \, K( P" I! O) G3 E: ]0 x. z, v0 m2 ]9 R9 L$ A+ V
    package com.javacode2018.lesson002.demo18.test4;* a5 L2 N2 P% X1 u
    / R( T# P" T/ X$ D8 a
    import org.springframework.context.annotation.Scope;
    + }! T" v, V4 R9 ]import org.springframework.context.annotation.ScopedProxyMode;
    & E& B8 V/ P8 |/ h
    " J- z: }! [" b. F! G# U- j' Mimport java.lang.annotation.*;
    & D: b. X2 v8 k: Z* U) u6 H1 U3 r8 j0 {7 x
    @Target({ElementType.TYPE, ElementType.METHOD})
    8 n- D' X9 R, g5 O3 Q@Retention(RetentionPolicy.RUNTIME)  q5 \+ H9 ^$ f( W) z; O2 ?
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    ' w& c5 S, J0 h* W0 H@Documented7 x: v) _) Y2 u+ ~9 `9 O1 @5 G; i
    public @interface RefreshScope {
    : y! @" m' \4 S& q6 [, @* L; F: V0 _    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    0 [: }6 e& ?8 H+ r# _7 s# Z1 Y$ s}
    ! |) z! R. V% g* S: `要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    ! }# N$ Y/ C2 R# v1 X$ N2 U1 Y' j! a8 j3 S( O6 i9 I
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS; D. S3 o- `4 v2 Y
    & k0 s& S- m5 t* J6 @
    这个自定义Scope对应的解析类
    6 Z9 F# I2 F$ p4 |5 a8 G* M0 _( t( H: h% T7 o
    下面类中有几个无关的方法去掉了,可以忽略, ^! F3 Y) W6 `3 f. ]

    / l- i  q: F+ L) Y9 wpackage com.javacode2018.lesson002.demo18.test4;
    ' u, o1 f. ]& U' t2 Y0 H# @- b8 f7 A- x) s

    1 H0 l1 N4 F1 _. e8 _  F) |import org.springframework.beans.factory.ObjectFactory;8 i* v& L5 z" f# n; Y9 @$ X
    import org.springframework.beans.factory.config.Scope;3 M5 d1 [" A% e2 h# v6 w4 Y2 p
    import org.springframework.lang.Nullable;. b- b* D# m9 b
    7 X6 A& t, z+ d- `8 r3 P0 E
    import java.util.concurrent.ConcurrentHashMap;
    ' p; E& ^: X& Q7 n+ Q) g
      A& }( x( B3 w' Kpublic class BeanRefreshScope implements Scope {
    4 p- f/ E2 L/ P: A
    8 h0 D6 c" L, t1 h) ^: I% I    public static final String SCOPE_REFRESH = "refresh";; X/ Z" p+ }9 r" W7 c+ {

    + U" ]( ?( k% S* x4 l: }( W& C    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();# g- R0 b, a6 F, g# V% f$ J8 q

    * l! g; F+ z3 @. x# h3 d; M# |    //来个map用来缓存bean+ R. m0 {0 v; y, g1 U/ Y* v7 s+ I5 ?
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    ' s# N! y1 l+ ]9 z' D- p" X3 j* O( F
        private BeanRefreshScope() {2 I$ D! `$ y! T; b" y$ i
        }
    % r3 [$ M4 y5 X2 ^( d2 p& Z5 H5 W
        public static BeanRefreshScope getInstance() {8 U( V$ R  m; ~) o% h' @
            return INSTANCE;8 l! i4 t, N0 T$ t$ t2 a
        }
    , a/ q5 k; \6 W5 z2 M( v  p
    3 n# D( h; G( b* d$ U; L    /**$ r' ?1 P7 C6 a% ^
         * 清理当前1 o8 u9 P% c4 q. e$ N- [0 ]0 i
         */" r6 p5 Z3 q8 W8 h& R/ G
        public static void clean() {
    * Y  u$ @) T# Z5 l& G. Q        INSTANCE.beanMap.clear();5 h& y- [  R# @
        }$ J  i4 m7 L8 x; h
    8 K' \/ S0 {8 w* x: Z" f/ v
        @Override
    ! Z7 N8 h& Z& l! P! [. c    public Object get(String name, ObjectFactory<?> objectFactory) {, p5 s" U* T, {- _
            Object bean = beanMap.get(name);( A, P+ \) r7 p+ v4 M7 J
            if (bean == null) {
    ( J4 Q; f( ?1 T, X: K; I& l& d( K" j            bean = objectFactory.getObject();
    6 y1 `. A, s6 t- F0 W            beanMap.put(name, bean);- e: T# B" Q# C$ W
            }  M) j# h) M2 y& [' c) O
            return bean;5 b" D. Y) o3 X3 a) U
        }
    2 |9 y! {% k9 E3 ~( v3 @  [9 w8 q% k# Q0 {( u5 f
    }
    ) q; N5 u) ~  Y1 f上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中& Z# _( j: D2 {7 p

    4 ?# Z$ A# G6 B( s- A& k0 }6 C( Z上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ; w# L4 Y( p; T' `( f- Z
    ! ^# O1 o& }8 t$ o3 Q4 r来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    6 o* j$ U# }0 T' Y
    6 N, s# `+ K9 G2 s. |package com.javacode2018.lesson002.demo18.test4;' \  S& x3 p5 t+ I
    # _: \6 J: t# h0 {& M4 d! _$ Q
    import org.springframework.beans.factory.annotation.Value;; O' L: @1 y, C% z+ E. z( E
    import org.springframework.stereotype.Component;" B" T8 A9 U+ C2 [+ y; }. _

    ( |9 C0 q7 j! S$ i, A/**7 n+ V$ }$ `8 o4 q2 R7 s
    * 邮件配置信息
    1 v$ g; k6 I: \: r */
    + z" u9 c# E  Q* ~7 L@Component( o! H# n  s# D9 M& G0 l# F0 A& u. E# C
    @RefreshScope //@1
    0 K# a! {, K' |/ Y2 x% y5 f7 Hpublic class MailConfig {$ ?; R' c% a: c4 _4 z$ X

    6 V8 R8 ], M, h$ m+ a' ~    @Value("${mail.username}") //@2! J% @# S$ z7 V; M8 ?
        private String username;6 t. X  @1 _( p- X8 x; X5 n
    ! |# P8 x. J5 `' r% h. B8 l
        public String getUsername() {
    4 d* F+ `3 _! H( N' x        return username;9 @5 V  y- |& |: D, @
        }' Z6 j% [1 R9 }6 r% e& V
    & R' \) h$ `3 T, h( p
        public void setUsername(String username) {7 T# p6 J& ]1 d0 D& |
            this.username = username;
    & K  ?) y' E% @' E/ T" V2 s& T: G    }2 C3 O; k8 _4 T

    + K; w& g0 w! s; R. [- u% N2 G) d/ R9 o    @Override' o4 E% h9 r2 c' U9 e
        public String toString() {
    ' }1 B8 Q$ N$ G6 j0 ?* c        return "MailConfig{" +
    8 z# D4 l& G4 a, {" Y                "username='" + username + '\'' +1 x" r. ]6 g3 v  ^' ~& W
                    '}';) e+ l4 U1 a; V& X
        }
    5 r! s8 i5 f3 k9 Z9 X' u7 f; B, G}/ ], n) u! p; L* _0 Q7 K
    @1:使用了自定义的作用域@RefreshScope
      D$ c% j/ `% Z8 K% I- D
    0 P& n; E2 m- i% R9 u  ~@2:通过@Value注入mail.username对一个的值
      {2 d% T; M$ v8 i2 ^
    $ L+ L1 i7 i1 k重写了toString方法,一会测试时候可以看效果。4 v! G6 L% B6 s3 K
    . e7 f7 K; J$ w8 j
    再来个普通的bean,内部会注入MailConfig8 b* I1 Y  K6 O/ ~

    9 F7 Y+ ~3 f; W4 K- P1 @' wpackage com.javacode2018.lesson002.demo18.test4;3 K) _& q: O. N" }1 Z% B

    $ G/ S' {' B; l' Q8 Y( wimport org.springframework.beans.factory.annotation.Autowired;
    $ N, T) {" F7 \8 t+ ~( d& f+ @% L8 Cimport org.springframework.stereotype.Component;
    , k3 i; p( u9 b4 X# q0 D1 Y0 j" K  F% O! r7 Z0 A, p2 n  i
    @Component
    , ]. X  S3 Z8 h( Fpublic class MailService {" M# K8 P3 c1 Q; F) n
        @Autowired. q, N' ?/ [: V7 e  Z2 Z" V
        private MailConfig mailConfig;5 @$ B# ]3 ?' Z, {- b/ D2 l& a

    ' N& L' p9 E( g% a; o7 \6 x    @Override
    4 a1 p/ |; W. r: U9 s1 b    public String toString() {
    : T, a2 D# K: S; n! X# S8 Z- T. d        return "MailService{" +; C8 F7 A# }1 E# ?
                    "mailConfig=" + mailConfig +( i2 Q, r; \) C# I+ b
                    '}';
    2 C: j+ H( _# P    }/ w: F& h' h- [; N! d
    }
    7 R5 F( Q# _6 K$ I+ J4 B* k2 V代码比较简单,重写了toString方法,一会测试时候可以看效果。8 D, J" Z! `( T3 B
    6 i7 N) Q* X' V/ D% [5 W
    来个类,用来从db中获取邮件配置信息% P6 R" R2 E% u! J

    & A* `' A5 A' U8 h, Jpackage com.javacode2018.lesson002.demo18.test4;& E# B( U* m6 |3 K( W$ y
    ! s/ d9 X* @' L& k
    import java.util.HashMap;
    % ?$ X4 D3 P* j% ^import java.util.Map;
    / X4 E  R( j& _+ Rimport java.util.UUID;) p# d; i  l6 `

    - N) K" z# F& p% P- Z  Q0 N, Spublic class DbUtil {
    * M1 L1 [' m5 Z7 H( C, l2 L# h    /**
    + ~! F  S: u" o2 m: \  f     * 模拟从db中获取邮件配置信息8 H' n3 m6 R) F1 q6 z' ^! x' \, z
         *: f% j5 N+ j6 ~3 Z& Z
         * @return  D8 Q. q8 e# H, y1 @4 e1 p& O+ ^( e
         */5 e1 M/ g) Q& j, F9 B: [
        public static Map<String, Object> getMailInfoFromDb() {
    6 p- }& j  `3 H, J$ _        Map<String, Object> result = new HashMap<>();' V  l2 ?$ }  n) _& k4 k
            result.put("mail.username", UUID.randomUUID().toString());
    8 u- {; X1 X( T' d) h        return result;
    ( I3 }. L% H1 G1 m    }' t9 X( g4 V( \8 C  v  x' d
    }* |, {+ O0 K( @* y0 S
    来个spring配置类,扫描加载上面的组件
    5 Z$ I! ]) @, T1 f0 o1 D
    9 n3 I4 ]3 W$ u- E- T7 y) k1 \package com.javacode2018.lesson002.demo18.test4;
    6 ]4 o: V. M8 h3 p8 l. Q% d7 u" z3 }/ v9 q6 f/ W: H/ g5 ~
    import org.springframework.context.annotation.ComponentScan;
    % ^3 ?$ ~3 Y# _# I: v) @! fimport org.springframework.context.annotation.Configuration;6 @% L5 x- A. a7 n; P0 l: m
    - Z) F) c3 @- r0 N* y
    @Configuration
    : K) G. U1 r0 S9 x  G3 V@ComponentScan, Q0 p8 q( z4 S! v/ `! x
    public class MainConfig4 {
    . t& b. P9 J1 t# D8 i8 Z3 _. E}' |: `4 q9 J  X) J
    来个工具类  b8 {' w- C' v- M; S* u, U
      e$ `! @* Q. i
    内部有2个方法,如下:
    7 Z* g( s+ R& n& [! C- P( t) J7 [& d% m' f+ g1 S# q
    package com.javacode2018.lesson002.demo18.test4;
    + v- z& U& Q& M' U$ E4 L2 M
    0 U4 u# b/ Z" y6 simport org.springframework.context.support.AbstractApplicationContext;
    1 Y3 S# l7 G+ X" f4 T: h4 qimport org.springframework.core.env.MapPropertySource;
    1 b2 U) _- K7 d8 ?' h8 W" {( y; ^- c) [9 _% v
    import java.util.Map;9 k5 A! y# h$ U1 l; i

    ; F3 v2 `4 v& u, b2 @, Upublic class RefreshConfigUtil {
    8 O4 ~( {! [1 U. q, [* W0 N    /**
    ) Q0 B4 N6 r$ u- J6 o6 p2 u3 V     * 模拟改变数据库中都配置信息6 H" U3 I$ x+ B& {1 k& _
         */" H! H" P& |; A3 a
        public static void updateDbConfig(AbstractApplicationContext context) {
    7 |7 a( A3 b* S) j4 w: p- m        //更新context中的mailPropertySource配置信息% [" A# I$ h! t$ ^
            refreshMailPropertySource(context);. U! u2 p- i0 f9 h1 K& X; d
    2 n' O/ x7 W3 z* G
            //清空BeanRefreshScope中所有bean的缓存
    7 b; }0 {- I1 h3 g' K/ d; M        BeanRefreshScope.getInstance().clean();2 q6 y5 n0 M3 Q$ B5 x
        }
    0 g/ S# [- v6 z. P$ ^+ ?$ J, H: y7 G9 z
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
      J" _8 x! w  q. H        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    $ i: y1 U) `( f+ A# b        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)! e) K& @# ?% }8 U
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    3 \% y2 O8 w1 p- l4 h        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    - F# }2 @$ ]3 y; b    }
    5 J' I% s3 n" H5 r7 I, M7 P$ b; R2 h9 u# g
    }
    7 `6 ]( N/ I& R* uupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息, ?+ h" h  G+ W

    % p7 x% D/ i) f/ Q0 wBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。6 F' Y, s$ M; n6 Z2 i; ?2 A( T( b
    ' _8 {0 f8 }8 R- `( _' N4 _
    来个测试用例$ L2 h9 @. O' W) [: q7 R% a2 t

    & J: P- f- S0 O@Test: E2 q, s% b0 J) \. E' O0 e5 J
    public void test4() throws InterruptedException {
    9 B  E3 M& t. E# ?* b4 e    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();4 }5 `/ P2 ], Y' ]2 u! r
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());7 b. Y. t- }: l. O3 U" g0 w  z
        context.register(MainConfig4.class);
    6 s0 \2 v6 H7 E2 L$ l+ k, L    //刷新mail的配置到Environment
    , U6 f* ^5 U1 ~; D6 T, R6 C% d    RefreshConfigUtil.refreshMailPropertySource(context);
    % [$ |+ Z( a9 g4 L% M0 Z" t- I1 X    context.refresh();
    3 G; q9 {( }7 I- b/ I) R
    1 F. X8 w" [" X9 \- A) l$ D    MailService mailService = context.getBean(MailService.class);0 N  a3 n' }: M; g
        System.out.println("配置未更新的情况下,输出3次");
    9 ]5 i6 f9 y6 W% v    for (int i = 0; i < 3; i++) { //@1
    # H6 ~6 N  |: F( Q( u8 `$ ?        System.out.println(mailService);/ M0 ]0 Z. q" n  U2 O, m
            TimeUnit.MILLISECONDS.sleep(200);
    ; I' S9 ~: \! E- ~    }! R: e! G: ]5 f) j6 x* B4 ^: [

    : U& h4 q# n' B6 N8 x. H, R) P    System.out.println("模拟3次更新配置效果");( a* r* b  H# ^5 Z+ V0 }# H
        for (int i = 0; i < 3; i++) { //@2% M( Q' j/ F/ l! m
            RefreshConfigUtil.updateDbConfig(context); //@3# g! ~# [2 t% Y  A: q& j: r
            System.out.println(mailService);4 t& H$ m9 @, J
            TimeUnit.MILLISECONDS.sleep(200);
    ! L$ Z  o* \) b6 ?# z3 T9 q    }% q! K, t% E# O0 n) j6 X
    }
    + n& p# N: v% Z' Y@1:循环3次,输出mailService的信息
    8 l( |7 ?; D) M1 U* j1 [- \3 k1 {$ g
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    7 s: _% a$ j4 w9 e" X- S! K/ l3 I* v0 L
    见证奇迹的时刻,来看效果
    : G$ T* s: s! {# m; i8 |9 c
    4 p8 m" D# T  X; E' a" _) @3 n配置未更新的情况下,输出3次6 U/ `  a' j/ G1 ?
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}7 r% n( q) e+ Y, [6 Y6 y- X; s
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}" J) r5 A; E6 O1 |5 s7 i3 c9 G7 U
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}: m% G0 Z0 L- |
    模拟3次更新配置效果
    - {- V) {$ \! \6 C5 bMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}. E4 G4 G7 ?0 D) S+ q& N5 v; ]$ h8 o
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}1 Q) \2 V: A# Z' E
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}! d- b3 P/ R; a+ U& R
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。8 f; d! X! R+ ?! z* o/ \$ v4 u6 X
    5 _" t3 L  O/ r9 o
    小结
    ) d7 u8 L  \6 h& J$ `3 _: I8 K- {4 E
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    6 ^* |) ?+ [* G5 X) R8 W" }7 o
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。; H  Y# {' Z( \
    5 r6 V0 C* y2 {0 w( d8 [1 q; D# G
    总结
    ' M+ W5 j. {8 c" B* w3 i  M& m- D
    " M& Q. Y2 s2 Z- U' P( ?% a本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    - b0 x+ Z( U4 S4 c& f$ a6 s$ P* w
    案例源码
    ( C8 j2 f/ C) n, V+ K4 `: a1 ~5 j4 S8 g" o$ K' Y7 `5 `
    https://gitee.com/javacode2018/spring-series0 M) z$ a: [1 ]9 B% z: `
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    3 d" `" Y! z2 D) E" @% E4 s% p$ g2 `9 K  S4 C; r* o7 H+ v" R
    Spring系列
    * L. b1 |) q+ t. E4 z  G. n- C8 j" Q6 s; r, i
    Spring系列第1篇:为何要学spring?
    5 a% z# X  K& T
    : \. h. D# `3 l1 N  H7 ~: lSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    . @. W" }- ?! b. R* x& i
    1 v0 x% ~" q& z0 A8 s6 F" ^Spring系列第3篇:Spring容器基本使用及原理
    4 ~6 g1 W- w; r+ ^( V/ g! x. p' {* s$ l6 d! e
    Spring系列第4篇:xml中bean定义详解(-)
    " Q$ m, d% Z6 R: d
    4 b  \2 Z. y5 z! ?Spring系列第5篇:创建bean实例这些方式你们都知道?* h6 V3 s" Q3 p  z

    6 ~# w  Q0 @6 |/ C" e9 `Spring系列第6篇:玩转bean scope,避免跳坑里!
    # C. m: H8 D6 x$ h' n3 d
    , h6 M' r7 J& @0 d# JSpring系列第7篇:依赖注入之手动注入
    4 c  [- N! H" j4 W" X# X1 s
    1 ^$ S- w3 H  q% X, K5 }Spring系列第8篇:自动注入(autowire)详解,高手在于坚持3 w' x" Q( [2 G" _
    - p. L& |3 T  m% [" t' p5 \8 E
    Spring系列第9篇:depend-on到底是干什么的?6 A) c+ t" g+ ?% O/ g

    1 P- i1 ?+ r  g4 R! jSpring系列第10篇:primary可以解决什么问题?8 ~/ r2 S) Q% y

    : n$ m) k; K0 R: P8 U0 ^Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    ; F5 M* r" u  i0 _. g4 }; D
    8 l2 e5 d4 [6 C0 `5 z: ISpring系列第12篇:lazy-init:bean延迟初始化  E  a$ b7 C8 M

    - @: P/ g8 \. Z" ^( Y6 g4 ~Spring系列第13篇:使用继承简化bean配置(abstract & parent)) S. z# l* `3 ]) P1 i1 |. k
    6 \9 S2 r( ?2 J6 X
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?: h  f% K( w. n* D+ d: r
    + h  d# |' u* g( I: {5 M3 r& L
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
      o0 C9 E$ g2 D3 @/ ~. _) G
    8 ]+ S5 o$ f: X5 pSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ) }# W+ J5 ~- A' f% \+ i3 p* f) e. A$ s" T; l
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册), V. f7 d; z2 g3 Q# m: [, V+ {

    ' e4 P' _3 p1 d: u* j% D7 kSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)3 w/ u2 _9 h, I2 L& h  O9 b, Q
    2 }; D. n+ a, d' c; e+ \; k
    Spring系列第18篇:@import详解(bean批量注册)
    & y7 h* j" B9 h1 X8 t# f. ^# @$ U# ?/ o, h, t1 N: g. e
    Spring系列第20篇:@Conditional通过条件来控制bean的注册5 T# V" L' u3 f9 _, ?* ^; X
    4 _) x2 P3 [- P" B% }
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    7 w& v/ i( V! N. |# ?! a. s5 z# o  N: j& {# F3 P8 J
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解7 h' t8 k, L3 S
    + e9 H/ ?) c& b  ^. e4 f
    Spring系列第23篇:Bean生命周期详解1 A' ]3 s7 _& i+ ?( }! e- N
    : A  B5 x" f# g
    Spring系列第24篇:父子容器详解
    / b8 O" ~1 D1 R6 v+ i. r% E
    ; V3 {& n. s5 d; V" U# \+ G$ ]1 F更多好文章
    . j- s- G. G2 b6 D- I. ^
    5 C+ ^& D( f$ j; a3 e/ N7 uJava高并发系列(共34篇)% H' z+ s% l2 |6 d- u$ _
    ! U; j% B/ M# j, n" F
    MySql高手系列(共27篇)
    ; C7 t( E; X8 j4 e  S! l, I' v9 ]8 d; ^' ~
    Maven高手系列(共10篇)
    % h0 B1 W, z- L+ S+ B1 j& a4 Z7 g7 g6 C8 M; v, P
    Mybatis系列(共12篇)) M+ O' Y" M. w1 T* ^

    ; U% F$ a# f/ M) a) ~# a聊聊db和缓存一致性常见的实现方式
    7 c  |" L! d& t8 Z. U; K
    8 O+ L4 }3 ~. u( _. s接口幂等性这么重要,它是什么?怎么实现?, A2 d# E4 ]* }% t
    & m) h1 I7 J7 w
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!! L" X" v0 Q0 P4 {
    ————————————————
    2 ^7 u" [5 O/ Z版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。( C% W- T+ s" \4 S+ H' d) M$ k$ |
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    3 a) ?6 |$ y3 I! f  p$ J
    ! O  e: V! K# n& l1 H9 n
    # F- d' D$ ^& S8 ?( r
    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-9-22 18:17 , Processed in 0.516944 second(s), 51 queries .

    回顶部