QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5395|回复: 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!
    2 ?! l3 g/ Y" |6 z# \疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    . Z; q: D! ?- f8 d8 A6 n6 Q9 ~; T* b0 H6 e( R
    面试官:Spring中的@Value用过么,介绍一下
      M, [3 L7 @0 v
    4 g. @4 `, I1 ^- @8 V3 O我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    0 j: j' V+ D% ]1 I% G, o
    & v! Q. G2 f: c1 Q* V面试官:那就是说@Value的数据来源于配置文件了?
    8 e7 u6 x0 g, t) y" \4 i# i/ f1 j, [; L$ A
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    - K/ o3 d5 i2 |) m; O
    9 W* y/ [/ M3 _0 X8 x面试官:@Value数据来源还有其他方式么?6 m2 |+ X, G6 Y0 m) S2 G

    8 M# q1 Y( X: |$ H- Z; }我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    " o2 Y* t+ G' r2 ^5 j: L& M0 b0 e' D7 x+ u3 I$ x) I" A# I5 H  `
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    / n& I1 j& @3 j/ H) D  `. n4 d5 H
    ! Z) s% ^% ~$ V# x0 Z我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
      q  k3 X) O: Y# D0 \4 E& U8 P" G" T. }1 l+ T
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?9 a- Z4 |) m: m) x+ b9 v
    - C/ h0 o8 M6 h: |8 c+ v
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能. R" ?+ Z! m/ g# U; K8 i
    / t1 L( E' ^4 P- J6 A6 g
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    0 r) D$ [+ t0 w( X
    5 {0 x) n! f3 y3 x我:嗯。。。这个之前看过一点,不过没有看懂! H# Q9 x0 J7 Z( ~! A* g. U
    / h  m1 J2 x/ X* x
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    2 ~" p6 L8 \9 ]% {  k8 z' f$ i/ Y! t$ o9 t% z
    我:3万吧
    6 t' H" u5 A2 d( h2 o. O: J+ g. b! N3 _; ^, x& U$ ~
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?/ ~5 m# n$ V: j% c; z$ U
    / G" Q! ^. p  O, T
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    ( N. O: R; Z# {: y$ I
    $ r4 v6 Y4 y! {面试官:那谢谢你,今天面试就到这里,出门右拐,不送!$ y$ e4 ?( ^9 A! o
    2 r' F2 Q+ j/ p: j
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。$ x) y# e7 k( Q: t+ i: f
    3 g- [' c! @" `0 A% N
    这次面试问题如下
    # w0 d* p! P6 k  \1 w
    6 ]! L7 ~0 t/ i! N6 }1 U9 A@Value的用法
    & u8 X* U) w9 j, P$ H8 J
    , z: w6 S' D) Z! Y! H3 j# L" l! q@Value数据来源$ z% E, v) f/ }; x. Q: w# p7 ]

    1 |* B2 R  b7 |5 X# t( G$ u; L@Value动态刷新的问题9 F: R$ Q% {4 A# `) t
    2 Y3 ]) n8 ?/ x: J. s4 S
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    1 Y. @1 m& o& d- _3 R
    6 D# K  v2 S8 h@Value的用法5 N' [5 _; O0 v+ S- ^2 W1 n& S
    7 p6 u; x' f& x! J2 l
    系统中需要连接db,连接db有很多配置信息。- H- x6 r# P& v" k
    : i; u% D& r1 Q% k, R  C
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    . r( H- h" v& H- g! b, H1 t
    * r0 z. S" H' d, J  |还有其他的一些配置信息。. Z3 L, a/ v8 g

    3 v; D0 h$ y3 @1 i8 W: y我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    : s8 v# E; f7 u% _9 b) a. T
    ' @7 @2 x# ]& c0 [' \. U那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    + E7 i! K. T# N0 J2 m: B
    ( P7 g9 [2 J. h1 C通常我们会将配置信息以key=value的形式存储在properties配置文件中。% K3 Q. [& [- z) {7 c. ^9 p5 B

    * k& B8 D# L( T通过@Value("${配置文件中的key}")来引用指定的key对应的value。' A5 O3 J1 J! ]+ h: |, d2 \

    6 ]" ~+ u( f/ b' h. s- v@Value使用步骤; z. c  u) h# E

    # f+ ~1 _1 A: N( S步骤一:使用@PropertySource注解引入配置文件
    ( g; j. V) s4 B. u" x( m' M9 c% d
    7 Y- a) y) r! G( Q将@PropertySource放在类上面,如下
    - m$ F. u) W' h' J- g- ~  O9 i/ W/ \
    9 L7 l6 v- M: k7 z@PropertySource({"配置文件路径1","配置文件路径2"...})
    9 i6 b$ q6 e4 m) m6 y@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。% l1 _8 v  S  B- v; |9 q; B

    + I: L  p, y+ D# a$ [- R如:; Z% I" i  c5 k1 E) n0 O

    5 s1 \8 |% H. z& C@Component
    0 ?, T' ]8 J& a; }  e5 c@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})0 y+ ~4 `  V  k0 j2 P! B: r6 t
    public class DbConfig {
    6 R& j7 b- h+ M& X}5 z" O" p6 _0 `6 ~8 P# P. x$ a0 _8 X
    步骤二:使用@Value注解引用配置文件的值
      _  o; _# \4 Z$ z$ y  R0 Q
    & f5 }0 ]' \7 z$ B1 S通过@Value引用上面配置文件中的值:
    : O( \7 o+ e8 t" ~8 |. [0 z& H- U% m, Q) ?6 [% m  Y
    语法
    8 Q  ^! d0 ]3 b4 j4 p* V$ P* L5 ]
    @Value("${配置文件中的key:默认值}")% |4 O! f4 g$ ^% ~3 P) e
    @Value("${配置文件中的key}")
    ! C3 G8 \1 w& \# _: a如:
    ( O. P2 {. A5 z1 h7 h& x9 S% D- z  L- K( w/ W3 ^6 {# w5 j
    @Value("${password:123}")
      _5 h: r3 ?6 [( Y上面如果password不存在,将123作为值
    4 y1 y' o# b! x- `, R8 e' K9 S* n0 _. G9 L0 T
    @Value("${password}")3 z, Z+ I( j5 T( q
    上面如果password不存在,值为${password}
    0 E9 q+ z- E! u0 o: y! _6 g# R5 \. @+ Y" p
    假如配置文件如下
    - ~' E, W/ T! g' b, k3 T' G1 y- R7 Y  o7 t' C" ^* q- t" Y" A$ _
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-87 }+ g$ W) R9 b9 t3 c) u
    jdbc.username=javacode
    1 S: a; V4 z2 r. H, pjdbc.password=javacode8 ?3 O: J0 C# n4 ?
    使用方式如下:% r. j3 l* n8 K  H! r+ Z9 M

    1 |7 c$ i) }) f- }@Value("${jdbc.url}")0 [: Q, o* z! c; H
    private String url;! S& S: C' ?& r2 g
    7 ^9 R9 E: h$ I4 j
    @Value("${jdbc.username}")) \3 b+ l+ A% O7 T( x7 ~- ?8 y
    private String username;& b0 H9 M* e: M3 J
    ; i$ x* I( I% [2 q
    @Value("${jdbc.password}"): i7 A2 p# y6 ?; S! O9 @" s
    private String password;7 |$ {5 m4 `; p6 t- o# O
    下面来看案例
    ! d; J, x7 q. s  H3 m4 }, L+ [* O( l4 u% A  z" q' X
    案例
    9 \* ]* i3 {7 j# K; e4 P: _
    6 d# u' E' v/ k/ l来个配置文件db.properties
    / j6 l9 D  R; N6 D. x* l! U, D0 U; ^. M/ S, q% M' n
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    5 {$ h" t$ u8 ?jdbc.username=javacode
    9 q, H' }$ x- h, O1 c8 kjdbc.password=javacode
    ' T4 v8 T0 I2 `# E0 C来个配置类,使用@PropertySource引入上面的配置文件! A1 j1 Z  k. F/ D# H% P8 \

    4 t5 T1 W3 G5 l) U- Ipackage com.javacode2018.lesson002.demo18.test1;% q. s5 Q: L" s7 S1 F6 g
    " \, V; d) C8 d: \! i
    import org.springframework.beans.factory.annotation.Configurable;
    : e: M, |0 U9 P% s  b1 limport org.springframework.context.annotation.ComponentScan;& l2 a" ~5 T4 K( N5 C( {% N: W
    import org.springframework.context.annotation.PropertySource;2 r  Z8 U+ M) I( }! Z! f# Q

    6 }3 t2 Y. T$ h- ~6 R+ P" W@Configurable
    8 z8 k  ?1 x* w: h9 a2 `. a@ComponentScan$ U  g6 A: J# F2 C7 j1 }* j0 O
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    - g; t: Y  J2 B- J; `" Ypublic class MainConfig1 {  c0 m5 x! p0 \  ^
    }
      `% B" A; a- o" m来个类,使用@Value来使用配置文件中的信息
    ) z- W) }8 ]0 n# L5 w
    , |+ }5 Y/ O. `- W5 ]package com.javacode2018.lesson002.demo18.test1;
    - L( G: P5 \9 W8 w5 X4 Z8 q% [
    7 z5 u; y3 G( s$ C# C/ limport org.springframework.beans.factory.annotation.Value;2 T. R7 c$ J( a1 c5 Z! n
    import org.springframework.stereotype.Component;
    9 p& j; X, j5 o
    $ L8 r: V" K- G# k@Component
    % g9 U6 O% p# C( A! Apublic class DbConfig {
    * _2 ]6 P) K3 s. \6 I
    ) q% l9 T$ f9 @; m. Y/ S    @Value("${jdbc.url}")
    4 A4 H' s! w9 d7 i3 v    private String url;/ F  z) I  z2 w+ H* B9 E$ d
    * M, C, W' j0 T: e: E5 e9 L' r
        @Value("${jdbc.username}")6 y$ k$ m) A5 v8 j/ T
        private String username;; V: \4 k9 k! s! P! w+ m

    ; c9 U1 G; ^/ _0 h# M; A+ b) `    @Value("${jdbc.password}")
    4 I  J* d5 H/ z' H% z8 q    private String password;$ M: M: }5 `6 k; E5 s
    % G; S7 a1 Q3 x3 z, P/ @! u
        public String getUrl() {
    / I5 X% b- S% k+ I  i! w+ A, F9 Y        return url;
    / b' Z0 Y6 V, A, U5 {( ^  K    }
    - b! \1 S% y" l6 E  m
    4 ?# L; g' i& i" G    public void setUrl(String url) {
    0 [! d. U4 t( W' S9 z# Y2 u        this.url = url;
    ( z5 k# M5 V/ g7 r. a0 j9 s/ u/ r    }5 }) c% D. s7 O
    5 G3 o% O+ _  f& Z' K
        public String getUsername() {) m8 U& O' F/ H, u8 ]
            return username;9 M/ z3 u$ [$ D8 x
        }4 G3 {- C3 v, F! T7 O/ }, P/ {

    % X  [/ E& x& X6 C" |8 u$ [% T    public void setUsername(String username) {
    ; u' _$ `, ^* E; T        this.username = username;
    8 m- r; D* q- u    }* T8 }& m1 ]' O

    7 c, P1 |5 ~5 ~7 x    public String getPassword() {
    ; F+ v5 l" \5 Y, h) ?0 o3 I9 j8 Q6 v        return password;) E/ [& R: f5 f$ ]
        }
    ; j! t: ^* r# J2 d3 h* N' z) `, W3 J1 o3 ?! H5 x4 Z6 Y' U
        public void setPassword(String password) {3 u! ~2 s  G4 F: @& I7 f) }, W
            this.password = password;
    ( k; [: _/ i( ]' e- U" d+ z    }
    : s# B7 Z- C( b- A* n: R( t" u- C- X& k
        @Override1 Z4 ]1 I6 b( @# f7 g$ }" |/ H
        public String toString() {
    $ ~# y; W; x9 [9 f  ^- K/ G7 u/ Z        return "DbConfig{" +! V. ~% e, C' L7 k0 X) o7 J3 q
                    "url='" + url + '\'' +) t: m- }; F; Q, T. T4 R. \* e9 J  U
                    ", username='" + username + '\'' +! |, n( k' r! l+ t
                    ", password='" + password + '\'' +
    % ?& {* }) w7 u) m                '}';3 E& n7 T/ k+ y4 p* T5 q/ K! o5 V
        }7 v1 i: |! n( w3 X& B; K) j
    }% K% |$ H; N* R' d+ n3 _
    上面重点在于注解@Value注解,注意@Value注解中的; I, y  t3 O" C: d
    $ ]/ s) p" n6 K+ _# ?+ S" f0 q5 Q2 z
    来个测试用例6 j# \8 Q8 _% Z, a1 U
    7 W2 y" `) U3 a8 P% v! O; P# ?/ K' Q
    package com.javacode2018.lesson002.demo18;
    $ M  [: t: C- ~+ d; j& A$ K* Y" x( q/ p: W9 c6 V; A2 v4 H$ J3 R
    import com.javacode2018.lesson002.demo18.test1.DbConfig;6 F9 z  E, U/ ]+ A$ \  Z
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    . Z! K/ k( }" eimport org.junit.Test;- d/ l1 `3 W# x; {  \; b# I
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;2 p6 {# T, U1 u3 @6 J" r
      M3 r: t! H4 z! w9 }7 W5 ^. E
    public class ValueTest {
    ( W' d7 T2 ~) x5 a- o/ k% s: t* K+ I8 j' @, M) Y( K4 J
        @Test
    , H( p" j# Z2 z. `4 d    public void test1() {
    2 Q' Z0 z% m9 V" C! O; |        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    8 s% D: Z0 \! V% A$ M        context.register(MainConfig1.class);
    3 n& `) m4 r. L2 E# I        context.refresh();
    & p* w* @  G* r8 D6 h4 h1 [8 @* s" K( @  s7 o3 e( U/ w
            DbConfig dbConfig = context.getBean(DbConfig.class);
    8 G- i9 N# ]1 P! i* e! {0 r! O        System.out.println(dbConfig);
    7 j9 J/ J2 c$ l+ ]$ [. s    }
    # V, W+ r3 w8 k, J: _% e}$ q- J% p' P$ T$ F$ {3 y# s3 M
    运行输出; Y+ @* P) \8 Q% f2 a
    1 ~" f: v' }' q3 Q* h
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    # T" {6 r# b, n% v9 O! n4 J3 K上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。% l( W" [4 k; S* ]; Z- {) f

    . A9 o  v! ?; l; K( @+ k@Value数据来源3 Q0 i1 g- |7 G5 H+ V3 d2 _" n

    ; V( m! b) g1 l通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。6 T. ?; P9 R; ]- T

    ' O( J- x* ^+ ~( i我们需要先了解一下@Value中数据来源于spring的什么地方。5 i+ b1 q* e4 ^' x4 `% U
    & w- G/ g. }( m8 e! f* q$ e; |
    spring中有个类" d6 c3 C& m4 o# z# e4 I  n2 E

    5 t. K% G7 \& V% d( [- z4 Q3 Forg.springframework.core.env.PropertySource
    " D2 p" i4 a2 u# _  b+ E可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    . x( b- O5 C+ D' z- [; G/ a/ J  \+ A
    . e) A/ A& M$ y& Y5 v+ Y" w. G内部有个方法:' N% T# b2 H( n+ G
    5 v( _1 c! N* @# p$ g
    public abstract Object getProperty(String name);6 y9 m% B$ o- b' `: W( @
    通过name获取对应的配置信息。
    0 u: j: `; h+ `/ R8 y% m
    ; @% M* V# }; \系统有个比较重要的接口
    , v3 `7 f$ T! A0 r2 b" Y  ~: x7 X) k
    org.springframework.core.env.Environment
    0 y: A% n( a' l4 ?用来表示环境配置信息,这个接口有几个方法比较重要7 o3 R9 J4 K: ~/ P( K
    , p" q# J4 _0 j9 b0 O3 Z+ `
    String resolvePlaceholders(String text);+ l6 N1 g8 j) \* `
    MutablePropertySources getPropertySources();8 ?- {7 z$ C+ V" o, m4 i
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。7 S$ ^, U' w3 G1 `5 T
    / _, [8 h* _- B" J! j: F! A* G
    getPropertySources返回MutablePropertySources对象,来看一下这个类8 a* ?: Y3 H9 K

    / k- M( o  m7 {public class MutablePropertySources implements PropertySources {0 y4 w5 E8 H# n! p: a" s% C
    0 k, z: Y! }% L! Y2 H1 l% h
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    % v- L. k% `1 g, {* e/ n$ k* \" i0 h6 k5 n2 E8 I
    }8 |2 d! j4 l+ A9 H
    内部包含一个propertySourceList列表。* s8 Q: D3 @& {+ U, l

    7 k  n$ r6 |  H2 Z9 Q" H- Mspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。* R9 O2 o. e' E7 }6 O" ]

    * }! H' A. g* u0 ]7 O大家可以捋一下,最终解析@Value的过程:
    3 N1 k; s. F3 X$ s
    * z5 K4 I! p: _+ G7 ^9 s% o1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析* H: e# g4 D2 h3 b
    2. Environment内部会访问MutablePropertySources来解析0 N: P0 B! g- j* O2 p1 A- k. [
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值$ _) F% M" l$ E( f' x2 P5 Q% H) ^
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    4 }+ n* `4 x! [  J& N6 [: w5 \
    8 M% E. c" k3 U8 P" m& p2 [1 e下面我们就按照这个思路来一个。$ e3 z# o- K8 S. O  u

    5 U: v& v6 R; J( e来个邮件配置信息类,内部使用@Value注入邮件配置信息
    / p+ o% I/ S5 P5 @, y
    + s/ Q3 n( T% V: U  qpackage com.javacode2018.lesson002.demo18.test2;9 r1 r: E8 h8 s' e8 x5 x
    8 Y( g; J6 _2 J. m  M  j$ `
    import org.springframework.beans.factory.annotation.Value;$ ?1 D" U; ?$ N, }* B. L: \- I
    import org.springframework.stereotype.Component;
    6 o! n% {: ?3 n/ _  F6 l
    & Q# @  D' Q" e7 ]/**7 z1 {6 A) M5 q3 {; ^, b6 Y
    * 邮件配置信息$ r# w5 n# C2 t1 |# \: U) M
    */5 k) E( b* J3 Z# ~
    @Component
    2 h3 D" D) \4 l0 G+ Hpublic class MailConfig {
    7 N9 v7 M' h  `1 H/ F
    + g$ p6 |% R" i4 }* u1 D4 X, M# ~/ C    @Value("${mail.host}")
    9 H& M! S  w( q3 }; O7 h& }9 E& }" M    private String host;$ h' s" ~' J* u9 y7 N0 d( }
    3 C6 }8 h7 B& F- K4 z6 G  A$ q/ N: a4 {9 w
        @Value("${mail.username}")* Q! E; g2 _+ n) E% J4 m5 s
        private String username;7 L' a7 L5 E/ b% L  {9 U' R- `
    " R' ]1 D0 y& t6 [( Z3 |; E  I
        @Value("${mail.password}")2 \- }- N, ]" {: ~
        private String password;- ?/ A$ e+ E( c

    + i6 _3 B5 j6 w6 A% Z7 E8 Z0 y    public String getHost() {
    + @3 y1 m" b$ j2 K  Y6 r& |        return host;* l0 t, S" T4 z6 [& B, {0 {) n1 Z
        }
    ( l- ]" A) P1 R7 l
    $ k1 J; R- t: Y" Q" |* x7 C    public void setHost(String host) {& B0 ^* B9 w3 k" \/ u0 x
            this.host = host;
    + k# p- _2 O. `! J- l; K/ p    }; b: `+ r; x2 ?) K# ~

    6 q4 @+ V% z" d3 z$ @. ^8 L$ x% r1 w    public String getUsername() {3 y' C3 o7 R/ n8 i7 f
            return username;
    % v4 I7 h: w6 k  L+ l$ K' {0 x    }( Y) c4 ^; b- a2 z! D3 C

    1 @3 Y* V  U9 B, S1 c$ [    public void setUsername(String username) {
    : X: q7 p3 @/ R+ q! J3 x        this.username = username;
    ( b8 k/ J3 `  z5 F7 T    }
    3 s* ^" Z( x. U$ D7 O
    6 |5 ?, E2 ^2 U7 |4 g    public String getPassword() {
    " g/ O% h- ], M) H4 z8 K9 _        return password;8 S% b) P) S  A7 n: e
        }
    3 X* S, y; Y5 q# I2 u) V% ]7 T. `- O9 ^
        public void setPassword(String password) {1 d, c$ p& q3 Z; R( w7 L. q6 }4 r% F" W
            this.password = password;: A; c* N2 o) O  S
        }
    : S# }/ d" ~0 z+ [8 n
      o6 [3 Z  r' D; {5 a7 U+ K/ g* {6 L& D    @Override% x' q! v/ \! v- J7 i
        public String toString() {8 C- L; C: ~2 P3 P' }' L7 t
            return "MailConfig{" +
    , P& b: ^5 s9 x: ~5 F                "host='" + host + '\'' +
    ( ]: u& S- s6 j                ", username='" + username + '\'' +
    . P: _. g+ N9 O* @$ u9 @                ", password='" + password + '\'' +2 h9 _& r8 {- d( `5 l. _
                    '}';6 O0 ?. z' F+ n% e+ U9 m3 }
        }
    " ]) J5 J1 q% \! D3 m0 N6 K}
    7 J9 f# U. W! ?) h& t再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中" q! ~3 r4 D; v1 k( D/ E
    , a0 [# Y+ Z+ D) u: T
    package com.javacode2018.lesson002.demo18.test2;# I: Z$ V7 o+ ]0 M8 X! ]: O
    9 O$ |1 H! J7 O  {! [; [7 J
    import java.util.HashMap;
    / r  ^! ^  r0 G( t/ zimport java.util.Map;
    2 v5 l9 C7 a$ ~/ |
    + {& L' _+ j* @# W/ r2 |, p7 U  Hpublic class DbUtil {5 N/ N4 \1 i5 c" z8 D# y' P
        /**5 U1 x: Q0 O- f& t- @
         * 模拟从db中获取邮件配置信息
    # R: N4 U  A, W     *6 Q5 F$ O, o- }1 @
         * @return' a% U6 h- z. a8 F7 v& H& i
         */
      J$ u  A! C1 V& @* K# h; J    public static Map<String, Object> getMailInfoFromDb() {
    5 c( Q) S( k- y) F' p        Map<String, Object> result = new HashMap<>();
    - m; O2 w% J& x) p( T        result.put("mail.host", "smtp.qq.com");
    9 N; u1 Z3 m6 C  |6 }' n        result.put("mail.username", "路人");& l5 [2 a# M: J6 S  d( \% H, y
            result.put("mail.password", "123");
    8 R5 b3 i$ m* ?7 |6 M        return result;& ]/ P1 B- y0 S8 l& r
        }1 x1 G" a: C- i$ @
    }9 ]. K# f+ B" ]7 k2 O5 l
    来个spring配置类
    / D/ i/ \# J' s% M7 [, D7 \8 _
    0 m! c% p( w% F+ s6 `package com.javacode2018.lesson002.demo18.test2;
    , `* t  T  A+ F2 `
    5 n6 I- ]) d0 `! r! Y: Fimport org.springframework.context.annotation.ComponentScan;
    * t, s* @: c7 w# I* |import org.springframework.context.annotation.Configuration;7 O$ U0 b$ ^1 I* H

    ) t1 l7 L! y5 P@Configuration
    : h0 \) J7 V  l1 z: J@ComponentScan
    8 _2 w- T6 a) J0 dpublic class MainConfig2 {; a' X' v% K5 w6 l* X
    }
    - K' R( W7 g  P8 Y, j& v; [* ?下面是重点代码% q9 l0 l! G4 X# R
    0 p. v) v$ I0 f/ |) l6 f
    @Test8 K4 K" N3 Y) Y. k! v
    public void test2() {! R+ G3 N4 d+ _
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();0 H( O4 H4 Y6 C% ^
    6 u  ~5 p9 x" P4 @: V
        /*下面这段是关键 start*/5 n3 n6 _, C0 S) l3 p
        //模拟从db中获取配置信息9 K. c3 k, `2 w3 W/ I
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();  R/ o' m/ E% f1 b) H4 u* k
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类), @* g! f! F/ \, P/ \& Q% R) S: J
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);! Z6 ?! \$ }( U, a0 h0 u% M$ ~
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ) v3 W8 l! D: z# a5 z" D    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    % X: C$ F) U; S( R$ _' E- e    /*上面这段是关键 end*/
    9 P! B& b! B) y$ a. J; o
    " V; n# m- f1 b2 \' J    context.register(MainConfig2.class);6 ?# O& |1 [5 U1 `6 x
        context.refresh();
    * A" h  n( ]  h1 X    MailConfig mailConfig = context.getBean(MailConfig.class);3 U4 K0 M1 e( C" d  k
        System.out.println(mailConfig);
    . x& Y; @+ d6 L0 @, o0 c, B& X}4 [0 X  s# S- A' }
    注释比较详细,就不详细解释了。! ^0 g4 j) A0 ?- k! R. e
    # d- x5 d. B" b9 z2 R% V) ]
    直接运行,看效果4 v+ z$ T1 a3 ^/ e$ G8 n$ y+ Q- {

    8 e5 K  I# ^5 A" C# _MailConfig{host='smtp.qq.com', username='路人', password='123'}( }' Q$ X3 X3 |4 `
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。& E* R0 Y# }; [) x7 A

    ) K2 Y. S! x3 L. c  s, B( _2 ^上面重点是下面这段代码,大家需要理解, |: s4 ?; Y, Y" J: P
    ! a& }, X8 O9 N) p9 ~+ L& i
    /*下面这段是关键 start*/2 O' G) T  n4 v$ \
    //模拟从db中获取配置信息
    ' A& {  R* h. N2 x# O3 ^; PMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ; _2 K$ {# G3 l; s5 f: G% a% y//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 x/ U6 a( v# P8 w' K" p6 t- L
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);' R9 f! h' _2 G8 ^) g
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ! ~4 U' Z( B$ ^% M3 L- d: C* N, dcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);' Y( p5 J8 ^- Z# M$ @( g
    /*上面这段是关键 end*/
    ' ?, _1 c: T& ~+ o咱们继续看下一个问题
    ) R0 x0 `  ^; [1 ?( }* [3 D) F6 F# Q- W+ o. N0 }
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    $ @7 Q5 _  o' z- [+ j
    9 a" D/ i% O/ P@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    + F- e3 N6 ?1 K; Y* G) _  d  m$ J. x* d
    4 D5 o: U# K- A7 I  P3 \实现@Value动态刷新
    ; q! f* b$ P# h! v" A/ `! ^5 {$ _- F7 D6 _! ^! ^1 V
    先了解一个知识点3 S8 h% H: l& e& t; g$ e& b

    ! q( Y* J/ x# I4 T  i1 f这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。6 o9 P  h- i! w) G

    " Q7 }  o# x% }% V( p/ M% Z这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解  e9 M) S  B$ F' l; k9 A# F

    0 a) n! O* K* _bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:0 c9 R6 |2 s0 D* I. e

    8 t! Q' @; b1 Y% \2 DScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;& w1 ~8 A. H, c3 X
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中, q% B: ]/ y* O
    0 j! V9 i# Y8 y/ P
    public enum ScopedProxyMode {
    # c* S: N' n" N    DEFAULT,) e# {/ ^* N" P4 t2 ^
        NO,
    8 p  b2 S* ?. I! `    INTERFACES,
    7 S, I2 l/ v2 U! V7 Y1 `. w    TARGET_CLASS;
    9 z/ O; Y% ^. [3 n: `& o; `5 }}" E1 l& ^8 j2 |
    前面3个,不讲了,直接讲最后一个值是干什么的。
    : t4 m- T( ?1 w& @" a" z/ _& s+ ^
    & B0 `! i2 _- J( k当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。. ]  o- c+ T+ R+ w, R2 S+ }

    / c1 m- G4 r4 \理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。+ c. ?: I. Y7 P' S  n

    : t6 U7 N/ r4 a5 a; s" E自定义一个bean作用域的注解. r' ]0 O4 L0 F" a4 l8 S0 a5 ?1 N+ g
    ; I: \. `' A/ {  U- _$ L
    package com.javacode2018.lesson002.demo18.test3;4 r7 U0 b2 K2 f; e4 Y$ G

    + c; a1 Y5 k! S! b6 D& i2 J( t& ximport org.springframework.context.annotation.Scope;
    $ R2 m2 M) B- J. nimport org.springframework.context.annotation.ScopedProxyMode;
    ' _  M" r5 p! |( s8 ~9 n/ |- ~3 T4 o& H* `* I# i
    import java.lang.annotation.*;
    0 A' J6 \$ B" `3 U, V  S7 O' W1 ?, n# F
    @Target({ElementType.TYPE, ElementType.METHOD})
    " u% ?$ w. U7 q1 T& k* _8 b! g@Retention(RetentionPolicy.RUNTIME)
    6 C! d2 g8 h% m4 h8 E; c@Documented
    , z% \) Z9 ~8 n/ g; B) N; {2 F9 o@Scope(BeanMyScope.SCOPE_MY) //@16 k* i: c, B( \4 U5 A
    public @interface MyScope {
    4 n1 ?7 I& p  `$ S" F: W    /**7 |: Q% v3 P9 S3 q! ]/ K
         * @see Scope#proxyMode()5 }% ^* \0 |9 G! n) Z1 u+ b( C2 }' X
         */* v' F8 {7 o6 `7 u5 m7 u7 w- H, a
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@23 Y- {  g# F1 \6 V3 |
    }, }  r! m5 x' \8 n1 y6 U" ~. r4 s( t5 H; k
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。6 Q6 T8 }" v& s! x9 `
    5 s0 Q- l5 `# S) L4 ?  o2 W" c
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS, O) U. Y9 z% G2 a: H" z! D% _- l
    . q9 d+ a( O! M& e# p9 H
    @MyScope注解对应的Scope实现如下
    8 D, d4 r! H6 w$ {4 {, V, A" t3 @) k7 Z. V4 F0 O
    package com.javacode2018.lesson002.demo18.test3;/ z- k- |$ K+ l0 I& y- b$ p
    8 q% Q. c' R2 q  S0 {
    import org.springframework.beans.factory.ObjectFactory;) o, B/ b8 z1 ^5 r& `6 @( _$ h
    import org.springframework.beans.factory.config.Scope;1 P/ O6 B5 h' l) w' H* r, e
    import org.springframework.lang.Nullable;0 H2 A8 s2 `6 ?( S2 g

    0 L8 G" r/ M2 A/**/ u* |8 L  u4 S. \! g3 c
    * @see MyScope 作用域的实现
    ; J1 @! K- h0 M# x: A */
    + I- W) V' C3 L' Q+ fpublic class BeanMyScope implements Scope {
    & ~) E/ k7 N  f0 W
    0 D% P; `  G2 d$ i& i( Z0 s; ~5 U    public static final String SCOPE_MY = "my"; //@1' {3 ]- x8 j+ t/ o

    & {% l1 z+ g# l! r$ [: ^    @Override
    , A, }& K  a  t1 E9 W    public Object get(String name, ObjectFactory<?> objectFactory) { & H" f+ D: e3 I9 N2 L
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2# i- y6 W! c1 o# Y& k  k
            return objectFactory.getObject(); //@37 \- E6 v1 b- q5 z+ y" G* r
        }8 f+ N8 a( a# Q. X

    - p% C5 Z* {3 g* V" `4 |' U    @Nullable7 k; ?9 I" b/ H7 C5 W! G' t
        @Override$ N" n& k; n! u
        public Object remove(String name) {+ H/ x+ S8 E! j5 }
            return null;
    1 u  Y; L/ t9 o/ Q9 H    }
      w4 p' f6 v1 Z! o# j
    3 Y- \! U+ O+ W- y3 d    @Override
    ' i% A, d8 B* W/ c! N! m    public void registerDestructionCallback(String name, Runnable callback) {
    3 A& k2 V  R) Q5 q/ W( S
    ; j! b; k% [. d& X# n% [    }
    " R  S3 u0 E. i6 C+ J- e
    % u$ s- G3 x  j* |    @Nullable6 t/ Q9 F- C$ n8 f
        @Override. w2 x) Z. x$ q& Y+ ~( O
        public Object resolveContextualObject(String key) {
    + ~% Y7 w3 ?5 H0 l6 B& e9 g+ {+ J        return null;, F. ]$ x/ j+ X1 P; F
        }
    ! R. p- T+ `# P$ Y7 R5 j2 J
    7 Q* t6 c9 {* O0 y    @Nullable
    ' F7 r2 v7 }7 `! m    @Override
    " @* O) d; y  E8 M/ |    public String getConversationId() {
    , g' S+ a; o! @9 T2 p4 Z& @        return null;6 c& S) x# W# _$ V
        }1 p6 t5 @% c: N& I4 u! w# U- F
    }% }9 \# V2 [2 S4 F9 z6 n3 a  ?
    @1:定义了一个常量,作为作用域的值
    # a/ {; v9 `; k/ \3 l, i& t% E4 w& i- \) E1 {5 G
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果, \: E# M6 g- a& F; t

    * G+ F4 X3 a/ W7 ^@3:通过objectFactory.getObject()获取bean实例返回。
    2 A! p* C; I* Q8 T$ E5 V
    8 ?4 z3 {8 S: K; o" f4 C3 ~7 q, }下面来创建个类,作用域为上面自定义的作用域5 i' M$ @/ {% }) p) `# F1 H
    : B  _( i1 h. z$ ]8 O
    package com.javacode2018.lesson002.demo18.test3;1 E4 R" r7 B8 Q) a+ n3 ^$ d
    # R7 e$ z3 l" p
    import org.springframework.stereotype.Component;, `8 O6 R9 l! Y1 O" L/ E! M# M0 L* R
    2 z6 G+ c! w7 p0 A' S) L* r) J
    import java.util.UUID;* H0 s8 ^! Q8 X- z" J! g9 i

    / q% M5 |% r1 P' Q@Component7 U! i* W$ x" _, j% k- t
    @MyScope //@1 & @. [4 N7 ]: z' c9 X& h, r
    public class User {# M' g" D/ F! s3 R% t
    & ~; U) f3 {: ]% ]6 U3 g# R9 z
        private String username;
    $ n, ]9 c" \% c, h8 p5 L9 x7 O3 |6 {8 R
        public User() { 3 j$ A" N: f0 c. X5 v8 t
            System.out.println("---------创建User对象" + this); //@2+ d+ I2 s# D4 Y( E6 f
            this.username = UUID.randomUUID().toString(); //@3+ M: V6 r' O! }. F' z5 l
        }
      ~* T' v7 {2 c+ B0 ]. z$ M8 h) n: @- G  c0 L
        public String getUsername() {, n! _+ X& k7 B! T' V
            return username;
    ' {  c" g1 O+ m/ P4 S7 H    }
    ' L$ ~+ ^. a9 ^
    ; x; X* k2 Q1 K7 g$ F" p/ [- V, X    public void setUsername(String username) {7 W# H% K( \5 u2 {5 K' ?
            this.username = username;2 _% @1 h0 l& e$ T; J/ |
        }0 E0 e# |; m0 Z8 O) Q2 M
      y/ T% r3 Q* O9 Y7 f% h4 [* x1 h, Z9 T
    }
    ( P8 D: L: E7 F& L2 }@1:使用了自定义的作用域@MyScope
    # c; j# k: y) w- J
    3 i2 \" S3 g: \4 M@2:构造函数中输出一行日志
    8 h6 x3 `. o0 Y1 t# J
    ) B7 W1 V! Z4 o6 J4 d, n$ b@3:给username赋值,通过uuid随机生成了一个# W4 Z$ A! A/ B% }, e, c4 e  f
    : I4 |% @! @- T& w4 h! @9 M
    来个spring配置类,加载上面@Compontent标注的组件
    5 d4 {0 X* I* d) H* _' _4 n" Z  M0 l9 v8 L
    package com.javacode2018.lesson002.demo18.test3;- u8 J. x( u0 h6 {6 S/ U$ r8 R

    : p5 Z6 F/ Z) i+ M. A4 v: timport org.springframework.context.annotation.ComponentScan;! K$ Y" ^7 E/ i# b. E
    import org.springframework.context.annotation.Configuration;# Y+ k+ a: Q. \7 x

    8 L* m' s3 n+ E: r' l/ H@ComponentScan
    + V" t5 ]1 w' X: t@Configuration
    ; K* m1 ~/ Z/ M, o! J. H2 l; l% [& npublic class MainConfig3 {" n: _/ {$ ~( O$ m- Y& [
    }
    ) f: B) N0 W7 L- Z! _下面重点来了,测试用例  a6 n- U. J( `

    6 r6 n- d$ ^" }' k5 ^) r@Test( G+ M/ u' J0 {- M! u" M  X
    public void test3() throws InterruptedException {" W  H6 D8 P/ S3 k( i/ }3 d9 C
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();9 {9 A& m; j( U) a9 V
        //将自定义作用域注册到spring容器中" a0 a' J/ M1 b2 W5 w0 t6 D; {
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    % z5 i. W7 F, ]4 ]2 D    context.register(MainConfig3.class);% }( z1 d% v, U0 j% O
        context.refresh();5 _' k# p% [2 w' {! L& n/ l! o
    / S: e3 q8 Z: I. n5 M! |- ?, H2 e/ L
        System.out.println("从容器中获取User对象");$ }7 m9 y3 y6 u
        User user = context.getBean(User.class); //@2( ]# |2 @* t' P4 F. v: x8 f, y' s
        System.out.println("user对象的class为:" + user.getClass()); //@3
    8 c4 l, J1 |3 f) B
    6 O$ a- T/ D5 b9 ?9 u( I! F( S    System.out.println("多次调用user的getUsername感受一下效果\n");
    5 V6 i) Q; F- N6 F% ?# I+ f    for (int i = 1; i <= 3; i++) {
    1 ?8 S- v3 O5 C. f; |        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    9 M. ]8 r( T: _% \! n: c        System.out.println(user.getUsername());
    & L, g( ~/ a6 k/ m$ K! T; H        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    + l5 D3 P& X# p1 s- r    }
    $ N8 F! S; j' V, f  S3 }* d}
    ; L/ X/ |3 X- [- [@1:将自定义作用域注册到spring容器中
    ) o8 B$ T' z0 J6 X/ g  ^: R1 ?3 i) x1 G
    @2:从容器中获取User对应的bean
    4 K/ E& G1 H+ N( N  d  A& W' y/ Y% q8 t
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    2 a) z* I3 ^1 m4 p2 ~) i
    # y8 N* r6 X7 y9 P! l$ S代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。1 J9 g7 S4 ^6 T; f! r
    * P0 n" a5 I6 f  U9 f
    见证奇迹的时候到了,运行输出' r  c6 T9 C$ W

    8 ^2 v2 t* p8 ?$ `" y6 ~从容器中获取User对象
    * |1 E, f; i% z3 ?* z( k9 Wuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331271 U# Z2 ~& f( X! B" A
    多次调用user的getUsername感受一下效果$ a9 y/ T7 n* t: d
    9 Z& G* o% R/ t" q" g5 a7 `
    ********
    , J7 h+ B0 b- b0 z/ ?  Z第1次开始调用getUsername3 y. M; t6 w6 I0 {0 w; h4 _. x& X
    BeanMyScope >>>>>>>>> get:scopedTarget.user% K$ [: t1 F8 W& v$ w% K2 t/ x
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4  z7 X+ d# M$ _* R4 Q* O, n! b
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    - D+ b- x/ f, ]) h$ H第1次调用getUsername结束, O2 f, w: G/ F
    ********$ J( r9 C9 G3 }9 l9 `

    0 X: u1 V8 C6 i  X+ _********) ?$ @9 t. K) C" t
    第2次开始调用getUsername- B* \9 M, E6 j+ d: F
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    + A" U  G. f2 u  ]; p---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    $ ]; H; `. }% S: `/ X( B01d67154-95f6-44bb-93ab-05a34abdf51f' v! `: H% S2 d) y( a7 w6 f
    第2次调用getUsername结束1 ^  W. {1 ]9 j4 R
    ********
    4 v' m, d. d7 |9 c4 k( [, g* _7 M# T, N, r( H
    ********; l1 M7 J) @! M. z; u
    第3次开始调用getUsername
    * v$ V2 Z4 p8 ^BeanMyScope >>>>>>>>> get:scopedTarget.user, ^, U) ?7 a' T: G! F
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d157 t/ |, Y# p8 m$ b. e" g4 F8 n
    76d0e86f-8331-4303-aac7-4acce0b258b81 E' v7 z. t; Z9 g
    第3次调用getUsername结束. F1 C: J+ [% L. y+ V( G' P1 D
    ********; O8 \+ p3 U/ E, A" B. E
    从输出的前2行可以看出:9 Y& N) t" `! G' K

    * i$ {/ }$ M! C# n/ f调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象7 c, H, ?+ i( x4 b

    ) ?5 ]% A) R# t! i0 z5 a. T第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。  y: }! b, ]6 U; Y) {3 @, r' Z( q
    & _, Q+ n& D- M0 p
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    4 t) Q4 g& V+ D7 }! [; Q+ j( O9 ?/ D) K0 f, U- a, s6 e
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
      y3 s; C# M* X6 I& V3 Y) q# I2 k$ F: m
    动态刷新@Value具体实现
    , W) g4 B8 K% t4 g# N
    9 M5 F% U4 c. K" l8 g9 f那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    8 P  A: @3 K& O( O) E
    8 E  A/ r7 @) g- r. Z4 s先来自定义一个Scope:RefreshScope: M1 z9 S# S9 j. q3 W' Z
    - j. k+ ?3 X8 z; t5 Q& b9 \
    package com.javacode2018.lesson002.demo18.test4;" X) p0 w" d) j  [  B: d, @" N6 g) h
    $ P6 f% {5 a2 c0 T! D" E5 i
    import org.springframework.context.annotation.Scope;
    , w4 m& m8 X; n8 b9 d: w: f* s" zimport org.springframework.context.annotation.ScopedProxyMode;
    " G7 k8 ~2 K, `' T$ P/ X5 l2 q/ N& {! q/ Z8 Y2 q1 ?  U% L2 L
    import java.lang.annotation.*;, h+ C; f; h7 \6 R4 ~+ l
    3 j/ w0 ]+ e$ i- h- L6 Y+ H( H" n
    @Target({ElementType.TYPE, ElementType.METHOD})
    - \9 C' o) a- i( k0 W@Retention(RetentionPolicy.RUNTIME)
    4 a0 [" M. P! ^, E@Scope(BeanRefreshScope.SCOPE_REFRESH); h. y+ M. J  n4 s- i7 k1 W
    @Documented
    / }1 x' f3 ]7 Y# o3 o, y4 b6 xpublic @interface RefreshScope {
    / ?6 Z0 v+ q: m4 R    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@17 `1 F: D, c$ e" I* P
    }9 D! ?/ s; J2 I7 t; k# j8 B5 [2 x
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    ! H# \/ F! f/ f9 `/ |9 z" {$ O; m4 G. ?) @6 X4 S/ Z
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS9 V3 Z! \0 n. H& i
    9 S8 \$ z& z7 Q) o7 [( Q9 b" `
    这个自定义Scope对应的解析类8 H; E# k+ \' j
    # S. I" l) n% [& Q# X2 X/ F
    下面类中有几个无关的方法去掉了,可以忽略8 b8 M% u- K5 z( R9 k' K

    " x" Z' O7 a. D- dpackage com.javacode2018.lesson002.demo18.test4;" `& w* _, b! z/ A3 k& B
    1 G( N0 A6 }  f  S2 G- E
    : x/ s/ j  u$ M+ X5 b% f
    import org.springframework.beans.factory.ObjectFactory;7 F8 {, I0 x4 q  p$ z! {
    import org.springframework.beans.factory.config.Scope;
    / w8 k% U, a% ximport org.springframework.lang.Nullable;$ ?) s! j- ?! l% L
    ( l  |/ _2 ^5 C- K; P
    import java.util.concurrent.ConcurrentHashMap;
    , h0 Y+ |) t2 f/ M
    0 y, i: h# P! Ypublic class BeanRefreshScope implements Scope {
    - D2 s! K) g* Y& x& H! k* s9 \! p" e" R
        public static final String SCOPE_REFRESH = "refresh";  K+ i5 f4 {; V/ S- K5 f* a. y

    4 M- p) z" ~' [) [    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();6 A; P( T7 Y1 L! ?9 ^! e7 D$ g! `

    & \7 Q" o2 Q4 B- M7 W6 A' W$ z    //来个map用来缓存bean9 g6 S# E0 A8 [5 K, f0 @: L) z
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@13 j5 p$ @3 [6 F* \; z. v5 V
    9 r9 f3 {1 e# e
        private BeanRefreshScope() {
    4 g* n9 }. i, x' V* h2 Q1 f6 C    }% w" g! x+ W1 I, T

    ) y" c9 ]; o7 C5 @    public static BeanRefreshScope getInstance() {
    9 q% S' m8 J2 B0 u        return INSTANCE;
    5 K4 E- ^7 [# r9 l! P  s7 O6 g    }  s6 A4 U; H; }+ _  N7 ?' p

    7 E5 x# M6 {  j# Q1 d    /**
    , F% h* A) b' L: A5 {3 m. B5 G- S$ ^$ G     * 清理当前& U7 L. t  x2 H/ X( C7 Y! }
         */, R+ U8 ]0 v; C" @7 @
        public static void clean() {3 t' {# o" D% W+ i- B
            INSTANCE.beanMap.clear();
    4 _, L) \* n) i) _) ~    }, J1 L9 u! v; T5 S* |: ~; }0 J8 m- t% O
    & f. |1 X" v7 ]2 G+ e; t" ?
        @Override0 W! p/ k' {# y% @
        public Object get(String name, ObjectFactory<?> objectFactory) {
    + y5 h1 |$ O; n  B3 ?( p4 W        Object bean = beanMap.get(name);% [  C+ u2 P; k
            if (bean == null) {
    * ~% R' D0 W/ ~" J1 Q# I            bean = objectFactory.getObject();. y1 s9 @% V" I; Q( X+ R
                beanMap.put(name, bean);
    & \1 r0 F5 e9 g# H* \. S6 |" R0 c' A        }
    7 k9 w: E% s5 X# T) @        return bean;' r- b8 ~. z. j( s" [6 c; C
        }
    ( ~  G9 `/ S+ W3 l* l# B
    2 a& `+ j5 ]/ [0 [  M2 |}- w3 k9 c$ r# @% A4 x
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    ' U* H9 c6 d- E: u" d, P8 W
    / ?$ @2 _. n9 _/ H. [8 {上面的clean方法用来清理beanMap中当前已缓存的所有bean- q, [. C1 \9 u( Y
    9 M* b" t! N! g; E0 Z/ V3 A
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    , u2 D5 d7 |% x- k( n6 P9 u9 i" [' h& m5 Z4 O- k
    package com.javacode2018.lesson002.demo18.test4;4 K9 i, M/ h# _. t' j4 v
    % A$ ^8 m8 \- X5 |( ]
    import org.springframework.beans.factory.annotation.Value;: ^! ]1 H" i6 t
    import org.springframework.stereotype.Component;
      ?" Q- h9 N; l' I6 a
    ! e2 e( ]' E  w& Y/**
    ! J5 |5 F0 h0 Q" E * 邮件配置信息9 k% Q- `/ {. v/ {- |" P
    */
    , R7 k- H% I0 N/ ]4 [: y& S+ }@Component5 W# D8 y$ y: k* f* @& [7 T0 |6 H
    @RefreshScope //@1/ _! n2 ^  J5 e# ^
    public class MailConfig {
    ' m! |  |! L" M5 r: b/ C+ m+ H- R" ^) w8 f* w( W( e
        @Value("${mail.username}") //@2
    6 x3 N( C$ s, L) E  @+ {    private String username;: {8 F9 T$ E; x; f' c

      g# R# w! b7 [- y- \% y    public String getUsername() {8 P0 z/ e$ J) B9 j8 K* W! z. X
            return username;6 |- j7 c8 j! ]$ Q/ w) |& j
        }8 n- }" \; y5 x8 }, n

    ' a' o* ~) u- d* s2 B  s- J    public void setUsername(String username) {
    & K+ F9 t. l/ v/ \        this.username = username;5 _  {7 P2 B9 l: ]* M8 X5 |
        }& S- `; n+ J% |- @4 X  E% B) W
    ( k- y& ~9 J! j, ?8 ?5 @. a
        @Override
    + X2 U$ I6 }( ~  w# ~    public String toString() {
    6 @& j" c* W. d        return "MailConfig{" +
    " b+ p+ ?' i: a( z( f                "username='" + username + '\'' +
    9 z2 x) I9 M% F                '}';" k0 n+ Y; h9 z0 Z. A
        }2 f: {4 y4 g; D5 p6 k6 b: ^
    }5 X2 a7 h( l' t' H) r; B
    @1:使用了自定义的作用域@RefreshScope
    ! \+ `# w8 D* ]6 ^
      u- H' I% r) O' O0 N& S5 ]6 M! Y4 b@2:通过@Value注入mail.username对一个的值
    / C4 O$ }$ X3 @2 e' a- h6 m
    $ v  s3 U3 U' F( t) `8 K  O8 z重写了toString方法,一会测试时候可以看效果。1 l( n* {5 V3 h! ]  ?5 a' ]
    * ~  @  B& ^( z7 R4 x% L
    再来个普通的bean,内部会注入MailConfig
    & u" M; H$ d$ b" O
    3 c( L, L3 t! Q5 @% _( \package com.javacode2018.lesson002.demo18.test4;
    + I) i, V9 ?, x* L; |, [
    ) Q# Z  r% \- F/ u3 Mimport org.springframework.beans.factory.annotation.Autowired;
    " L3 }4 N  L3 himport org.springframework.stereotype.Component;
    5 @& N0 n% L1 v! z0 G0 p8 g' U# @7 G7 q1 {1 T1 ]
    @Component
    : f/ n7 b) j7 L' \, u( Gpublic class MailService {8 o. L" n  I$ g& X
        @Autowired
    6 N9 L" P. Q7 |  w, I$ H    private MailConfig mailConfig;
    % M' k& m3 ?1 ~5 M& ?0 k! Z5 u+ y7 e$ y) v7 h5 ?* T, K
        @Override) i  G' [% M' X6 h6 a9 \& l
        public String toString() {
    / h' g) ]. K8 \9 L) o$ K        return "MailService{" +( H5 U: n5 M, @! c9 r5 m
                    "mailConfig=" + mailConfig +
    4 f! B2 U5 {8 _1 M# k9 M                '}';
    ! v' d! N" Z# K    }
    5 y" t  t9 Y; x& K5 g9 C}
    " @) }3 p& b6 J. m6 D代码比较简单,重写了toString方法,一会测试时候可以看效果。
    . U: E& d3 N1 F" V2 s' N& t: M* _- V
    3 h. D! s+ D1 b2 E' \5 v% j  f  I来个类,用来从db中获取邮件配置信息: r# Y% P3 s# D1 {" W3 O8 ?
    # b0 p' O/ t! W) Q" K. d$ o* M1 \
    package com.javacode2018.lesson002.demo18.test4;7 c, I: M  H. E& R1 z

    8 k9 Y- O1 p' g! i, v* a$ o% Cimport java.util.HashMap;$ z& A1 w9 {: a
    import java.util.Map;
    7 Q% R$ n# i* n8 P8 u* bimport java.util.UUID;
    2 }" }$ W# o' D& N. q) K# q
    * x2 v' P$ Z+ C, ~$ Z+ u2 @6 Zpublic class DbUtil {
    $ @$ _7 Q$ K& C: }    /**) G/ ?" D1 l6 X8 Q) J
         * 模拟从db中获取邮件配置信息
    - r- C  d; M/ A& u5 s* \     *6 z8 D# x3 ~, b3 ]9 o5 r; y# p9 e
         * @return9 c. b/ F/ n4 h3 }
         */
    3 ^9 m6 {" N2 X    public static Map<String, Object> getMailInfoFromDb() {
    , ]2 V0 Q3 O2 t: x% N( h3 z6 i& y5 Z1 _        Map<String, Object> result = new HashMap<>();0 g9 _1 p- A% L3 d# }. E# a
            result.put("mail.username", UUID.randomUUID().toString());! w, V; k/ o3 A, u* p' j3 _
            return result;
    5 [- \# f( R" g* k. p    }
    + M/ v) n; v1 l; g1 S}
    ' a6 v# B! P7 C* E* E- `) n# N: b来个spring配置类,扫描加载上面的组件$ V4 g& p( A3 J# }( i& V

    # ?! K8 j1 h/ J  f: Mpackage com.javacode2018.lesson002.demo18.test4;
    ! d) k3 R' y* C
    6 f' ?# N+ U# a% p' X- \import org.springframework.context.annotation.ComponentScan;1 ~8 ~5 u/ G2 E  x- n/ w9 j
    import org.springframework.context.annotation.Configuration;2 W5 }# q' j: l, W% Y8 y8 ~5 `$ E. S

    9 Q2 U$ Z; R" d) H) Q9 e@Configuration/ V+ ^6 U. ~; \2 @# y) h- C
    @ComponentScan/ S: A: R$ I* F# p) W/ V
    public class MainConfig4 {
    3 S7 [. Z6 }( C; i& ?3 t. s, ?}' ^* o  b* l3 t- N: v0 u* G- }
    来个工具类7 U8 u6 t7 n, `
    ! O5 Y5 W# q8 L# D; J4 f
    内部有2个方法,如下:
    / v( Y$ r5 J. ~+ E# n& [9 O* ?, U/ `$ B; ^
    package com.javacode2018.lesson002.demo18.test4;; s& @# ^2 y6 w5 v: Q. |3 a
    4 ^: g5 v$ r& a
    import org.springframework.context.support.AbstractApplicationContext;% F% G* L  \6 n$ e
    import org.springframework.core.env.MapPropertySource;3 ?8 X2 R1 X; ]; v" l9 V+ J9 K
    & y- w) z  _) b- j" b1 L! K
    import java.util.Map;
    / L; q9 Y% b4 `# B# {( S. P& A
    * J# H) {2 r1 p. F1 ?6 j1 [: Kpublic class RefreshConfigUtil {8 O, S! I# Y/ k" ]' h# C" H
        /**# O& q; M/ `0 u, w# E: h
         * 模拟改变数据库中都配置信息, P. P, f; [8 u& B
         */: t+ O* C8 Z" ^% c9 }' C2 U
        public static void updateDbConfig(AbstractApplicationContext context) {
    : D5 B1 f* S6 i        //更新context中的mailPropertySource配置信息
    - E& ?0 d4 f* d; x6 @, c. {+ b        refreshMailPropertySource(context);8 P7 s" P2 \; @* u" M
    9 Z: A! P; W9 ~4 ~1 P' I5 X7 `
            //清空BeanRefreshScope中所有bean的缓存
    3 g3 a" W+ ^, d& B. c- p        BeanRefreshScope.getInstance().clean();- ^7 u$ y1 x$ [4 r1 y8 `
        }
    + l$ X/ K, V! `% _1 q% I- d0 t7 }- ]" q% o+ m/ T& `& a* E
        public static void refreshMailPropertySource(AbstractApplicationContext context) {8 P3 k( J* c* u
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
      |) x- d- G, \5 M  I: i        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)8 L( u* x( i* a# R) W* D. `% l  }
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);( C5 l5 R5 t- y5 U: Z
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);# U8 `/ S  k" s( @6 Z7 F
        }: B: b5 a  l+ b  Q, B& u. g

    " A+ [, d- h0 u0 `" J0 ^}, Q) E6 U: T  E+ Q0 y1 Q+ o
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息1 c, c; c0 `/ ], o

    : c8 L5 p8 w2 FBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。% ?5 M7 q" D% k: Q. V4 P" X( q
    2 p$ Z4 ]8 `+ e" O
    来个测试用例" F/ M. v$ {# v9 H+ X3 Z5 M

    * }* T4 S% e* @! c@Test
    & ^: L  r' g6 W5 j% z$ ]* y0 N* Zpublic void test4() throws InterruptedException {5 @+ m7 t$ {0 ~$ Z0 n
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    - ~- ~6 \# x, Z    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    1 c0 s( @1 L& c$ F" |4 m9 L! y    context.register(MainConfig4.class);/ W6 Q' @0 w3 W0 A, W
        //刷新mail的配置到Environment
    1 h4 N3 s$ A3 ?6 \    RefreshConfigUtil.refreshMailPropertySource(context);$ g8 @3 ~: @  K. m
        context.refresh();
    * F  r" J% }, k/ M% W& j3 E, q0 ~) E: {; z
        MailService mailService = context.getBean(MailService.class);0 h2 _4 [( N9 r) X8 G: N  [# G
        System.out.println("配置未更新的情况下,输出3次");/ R- G! Z6 i: _; \# R
        for (int i = 0; i < 3; i++) { //@1
    * b/ i- E. ?6 l7 l) ]* b* y        System.out.println(mailService);  U, l8 b% w! r, c' T4 A) r8 a
            TimeUnit.MILLISECONDS.sleep(200);8 N% Q( D# F0 m
        }
    3 P; @# Z6 i: [% x4 ]+ F8 f' F5 U/ n3 ]% e( t6 H6 y
        System.out.println("模拟3次更新配置效果");( s# G* S( j3 ?. O
        for (int i = 0; i < 3; i++) { //@2
    - W$ o# \' c+ t        RefreshConfigUtil.updateDbConfig(context); //@32 Q; b7 p  K0 {  M. l# E
            System.out.println(mailService);
    : y' Q5 T% G* L        TimeUnit.MILLISECONDS.sleep(200);
    ; ?: S3 Z" U3 @- H! g$ ?9 ?" `9 n    }$ Q" o3 v/ n- _/ a, U
    }
    ; v# h7 |# {- K# i, m@1:循环3次,输出mailService的信息
    9 l/ e! T" a8 Y5 k
    0 F& D, U! l7 [  Y6 {2 v1 o@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息- U' B7 h2 U0 v" a: U5 i0 k! T
    & t2 }- ]* G; a- f; Q0 ^9 D
    见证奇迹的时刻,来看效果
    " n: u1 B# z7 T0 |" K
    9 |) q5 u8 l9 k$ B配置未更新的情况下,输出3次
    8 i0 T+ J( }* y- NMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    / j$ V0 a) Z( e1 E1 W6 N/ ?, {MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 Z4 q& @9 M' v* q4 \  ZMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ) u1 x/ p6 T' N& f6 G模拟3次更新配置效果8 _6 }$ g1 \# F1 q( }
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}6 s; s; B! a. p) D& b
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}+ e0 u3 z1 B( c7 Q4 R) t* n
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}+ |& ^) _+ N) T
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    5 {6 x. w2 s3 e; e8 |* M( [! F" F& f" V, |9 I9 D' {0 I4 B/ t: ?
    小结/ K! H( s, @9 K) O
    * U7 v5 v5 O0 W
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。7 B5 g) p4 W$ E1 B8 ?

    / W2 q. z. {' N* `4 l% M/ j有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。% I+ l1 i4 g7 \8 l
    ( Z* u5 q" j$ y( {
    总结
    4 H+ A0 _; ?1 a% g  t: A9 g6 U+ m1 ], v7 N, h
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!8 b/ |7 H5 M& L
    + G2 \. E% `9 y+ P
    案例源码6 b: s5 K: H, D) Y

    % d4 q$ e- ~( j0 ]: V, `9 ^! Whttps://gitee.com/javacode2018/spring-series5 Y+ v( L* ^. |& ~3 U
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    . G# J1 J7 m' w# {+ I: N0 C9 s! W
    & T7 E3 }! t- Q! @$ VSpring系列( @: ~  \: ~# U0 a
    ) g) w% F% Y' [/ b. I: O7 V
    Spring系列第1篇:为何要学spring?8 r9 a" t6 K2 ]( h; E6 S" O+ o/ H
    9 P' |# u$ }8 Z' |/ ^
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    $ F, D  b" r. n/ ~$ K
    : R$ \1 Q7 |# t5 o/ ?8 l1 uSpring系列第3篇:Spring容器基本使用及原理
    " v  x/ z' U$ s* C& S
    8 Y" f8 E) E5 K5 ASpring系列第4篇:xml中bean定义详解(-)$ \2 u" l7 s% [* L* B* a& z0 L$ y

    $ q. X* C5 b6 ~9 y: K8 l2 Z" eSpring系列第5篇:创建bean实例这些方式你们都知道?
    3 F/ K! q' P( L$ V" F4 g$ s. b) ~3 q9 B+ I: f" O8 E5 p3 l
    Spring系列第6篇:玩转bean scope,避免跳坑里!0 ?; C- _8 Y( y; Y7 {
    . V8 v( ~4 R+ N' G
    Spring系列第7篇:依赖注入之手动注入
    ' b$ h* ~# H1 C, n; h8 v+ j7 k# j- B0 Q4 x! X
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    $ _+ K: ~0 N0 v& {
    $ K2 A; N* o5 P  d9 tSpring系列第9篇:depend-on到底是干什么的?
    . h" U' s4 L5 Y8 O9 [" L& f9 u! I' w& ~+ Y
    Spring系列第10篇:primary可以解决什么问题?
    & i0 @2 H! `) V0 @( O" \$ ^
    3 S2 M2 S: U2 ]" ^7 aSpring系列第11篇:bean中的autowire-candidate又是干什么的?$ Z7 `. T+ f3 j0 o6 V; H2 V
    2 R' w. n, X6 A7 I
    Spring系列第12篇:lazy-init:bean延迟初始化
    . e* Z& r. o; u9 a9 K
    1 W2 X; b- s2 k. ^* YSpring系列第13篇:使用继承简化bean配置(abstract & parent)
      z7 `% S; w3 j1 ?' c( J# Z9 }0 ]2 f1 s* I
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    $ y. J: R5 O9 u( w1 {& k/ Y. L* [$ F' I8 a' m: ~: u
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    " `9 d: g* Q/ t
    6 w; o6 t$ \& O. E2 t- ^$ jSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)* e2 Z0 K! |* M' @* Y
    + Z: q. N  }. Q' F5 u" o
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ; X7 A' q- T2 b4 d' g# Q+ @
    6 n' u6 ~3 l0 U' h3 ASpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    ) O, L- M1 S4 }, S3 x  c" ~8 ]+ ]: Q1 F6 o1 m
    Spring系列第18篇:@import详解(bean批量注册)
    ' Q. O& d# p2 a0 f. a& s, `+ m4 \( p, U5 h
    Spring系列第20篇:@Conditional通过条件来控制bean的注册5 P5 C2 o1 g1 N6 M$ U

    . {% }- M  k, v3 d8 y& p. V2 ~Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)3 f+ x8 {$ s" C9 ]+ q( [1 y$ _

    " X" |' u4 i, g7 {+ r8 iSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解& h& V! v% _9 o# Y- s# W

    5 m# r1 m6 Z' @Spring系列第23篇:Bean生命周期详解
    # \$ x: R# x& r% f! P" ^0 d# d  D0 Z2 R! [0 S
    Spring系列第24篇:父子容器详解' ~2 U% M$ b0 |. {7 B5 r. M; ?' |

    # u& T8 N. {1 }. e5 _/ v2 N更多好文章
    + L5 W, D5 T; F5 ?; _) @: X  V+ j% l; _6 c$ t  c5 Z
    Java高并发系列(共34篇)
    4 `' D' A! [+ [& u# i# E' @( _9 z; B& a+ a) k1 e, t
    MySql高手系列(共27篇)
    1 E1 ~7 U; _! X4 B1 W& L* a2 B: ]2 W
    : o5 ~+ `& W+ B9 dMaven高手系列(共10篇)
    - A/ n' y3 `% x2 G6 I( q
    - n3 o) H- l: ZMybatis系列(共12篇)
      v* H! ~$ k. T5 x1 f4 R0 Q8 v+ H" k  d
    聊聊db和缓存一致性常见的实现方式
    - Q: _, C7 q4 q! I. I5 k
    $ a5 b* G3 n6 d- _接口幂等性这么重要,它是什么?怎么实现?: E  Q- D" x  k- T- `3 M, u: z* ^/ R

    / d/ \$ y3 ]0 l泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!' B. u/ a" _+ _* X$ _
    ————————————————8 n3 S( g: v; Z) Y, g  W+ f
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。. Q# m" W% ]3 n3 M6 W; q
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    1 W2 f4 Y0 X- t6 m: q! b& P
    : g4 R/ E* }+ g  G% `( @! \+ Y7 h, a, w1 }
    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 10:42 , Processed in 0.322971 second(s), 51 queries .

    回顶部