QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5391|回复: 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!+ j2 r6 r/ P3 {5 {+ O
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!6 m6 |3 V' s% v! a" P$ d

    $ c% _* }2 U* L; X, d面试官:Spring中的@Value用过么,介绍一下0 e; X3 g) ?+ v# E
    $ [, A8 y8 d0 |
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    7 b5 M  ?9 D& u" P; W
    . z) _0 V7 x( R$ d) q面试官:那就是说@Value的数据来源于配置文件了?# t6 \6 N2 U1 V- \$ U8 d( w2 S
    / ]1 C# |# I  r' z+ ~5 M
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    $ n( d" z! Y/ P0 H2 h# f# o) J/ ~- f: l
    面试官:@Value数据来源还有其他方式么?
    4 S! w, t9 ?" `
    . `" J; m- E% V. k% H: O3 ~我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    0 g* o, w) a& u" n- M$ Y3 c  d6 s
    + K3 ?' i5 \5 q4 q2 q, j面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    : t' V6 D. D. N
    8 t: f/ Y" s! |" N4 ~# N我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    + W* j+ i& y! q; n( `& g+ c
    # q2 ~6 g, v. N7 ?- I面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    8 ?' ~: X  h1 L. _# f: o( K8 q
      A! l5 N+ g% A我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能1 A  l; v: I- s" L* _7 I9 q8 B

    9 e, b. W$ U( p) }3 I面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    3 E3 e3 k, U6 E- l( l$ ^; h5 O* T% U3 m
    我:嗯。。。这个之前看过一点,不过没有看懂5 T+ L+ [! c) b% f

    % |( I$ C: ~2 e$ R% Q1 i- ?; ~7 y: T' U) d面试官:没关系,你可以回去了再研究一下;你期望工资多少?, U7 O1 c: k" R: l2 N% U- q+ }+ T5 v
    " {- {' N$ L/ f3 Y" Z
    我:3万吧! y0 S6 m2 V9 ~: b2 a. I
    & s0 G: ]5 F& w
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    6 e  K3 l$ Z' e
    5 a1 t* v6 r1 L5 a# s" y我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万& w4 ~/ p) a4 a& O* q

    ' C& q9 Q2 e2 n! W; ?  z面试官:那谢谢你,今天面试就到这里,出门右拐,不送!" b5 o! t6 O1 D9 i2 q% y3 W
    ! w+ H7 d" x& [# b& m0 H+ a% D5 K' x) l
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。  Q( I  J% n: X) C* A3 V
    % s9 h- l4 }; I" n$ B
    这次面试问题如下8 S" a/ [# c9 u/ y. h

    , T! P  m+ N0 `5 X. X@Value的用法
    5 Y1 Q# {! A2 ?# F1 G) k3 S. ?+ i/ Z' G2 u
    @Value数据来源& u4 r% }* O6 \( n$ |. x+ d+ V
    . V- C6 j. _0 H8 R
    @Value动态刷新的问题
    4 p! K7 ^* ?# \) Z! \& U3 g# A: _* K2 O
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。7 l; U  e7 x6 |
    % ]/ n' H4 c9 x/ F% w# U
    @Value的用法' W1 e0 ]. ^9 @" P

    ( n# j9 A" O+ ~; o# Y4 K  O' \系统中需要连接db,连接db有很多配置信息。% K, z: q( e; L7 }9 R1 P, t
    ; w' f6 L% z: q
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    8 [5 `; \# l8 t; p  g
    7 Q! U/ I, v# p8 N3 f4 a8 x还有其他的一些配置信息。
    , z5 }2 |! p+ j7 E7 t' C, N( D9 y' {, S% x5 E0 Z( O
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    5 @2 m% r0 C6 k  L  O4 n/ s; f1 H
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    # M/ T. ^+ E/ x& E3 z6 U7 ?3 v/ D5 |( @, D7 i. l; g$ N, M6 F' m
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。4 y) N4 h0 V( \0 F. d

    0 Q* M6 V- r, n- b通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    9 Y& ]) Y: ^0 h/ o4 H' E- e  \" |; K9 }8 V! T* ^
    @Value使用步骤
    3 H+ m/ \4 v% ^0 |
    1 F" b0 {1 X8 B4 L- O, ^步骤一:使用@PropertySource注解引入配置文件
    . j" y! z& r6 D, D8 Y5 A( f+ k% v$ L9 m) p
    将@PropertySource放在类上面,如下
    & e, J7 u7 {# t  D9 d, D7 `' c/ r
    # ^+ {/ y) f0 z1 Z. h+ ~, N  b5 Y@PropertySource({"配置文件路径1","配置文件路径2"...})0 B  o4 P4 P+ Z$ c  y( ^  n2 K  [
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    1 P2 t: }) @# M9 t9 |" [' c
    . D: y; u1 H4 s5 H# K) ?如:' h% b- C- h) e# y/ v

    4 u1 _3 c( I  E5 k  H@Component; O1 \( T7 ]5 ^, l
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    9 s) H; |5 c/ Z" m2 [8 ]+ Kpublic class DbConfig {
    " x5 A2 p% O- g( F6 n% Z}
    8 }7 @' A$ B- t步骤二:使用@Value注解引用配置文件的值4 J; u5 u. U& _% B

    2 B1 s" P- S) E1 N+ f/ _通过@Value引用上面配置文件中的值:
    0 V2 f! y2 Q, x8 U4 v# t4 t
    - ~6 K: K2 _% c' {% V* w7 l3 k语法/ h( d0 M% l" x1 t( u: r5 J
    9 u  [' B3 S, v+ D- Y: _
    @Value("${配置文件中的key:默认值}")
    , M$ m" h; a  @  O) P9 [4 R@Value("${配置文件中的key}")4 r. u: E  w7 T6 J
    如:
    0 D% p* k' w# o2 X3 c9 ?0 R, I  `+ Q& V9 X0 |* y( v& y
    @Value("${password:123}")* {7 }: E' g  R9 V+ u& s# g  @# `
    上面如果password不存在,将123作为值
    * R* i. F; n7 e0 E( U% i! L) P' F" y) h( x, B% B- e
    @Value("${password}")) @2 w) b1 F8 o$ [, L% a
    上面如果password不存在,值为${password}; h! }6 O! {- j* q! U- Y; M
    1 ~! {& [. C7 J
    假如配置文件如下# J7 D1 d# b# x

    8 }: T/ g+ V$ P0 rjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8. V) L) K8 u) `) p  t
    jdbc.username=javacode
    " a3 r( e4 _. H. z0 c* J" ?0 Ojdbc.password=javacode
    6 {. m: L4 z+ ?3 U( g8 w! o使用方式如下:
    5 z) P4 _2 U/ {2 n0 G1 t9 Q3 ^5 c# a2 Y
    @Value("${jdbc.url}"), @$ y0 M% ~; i3 B3 ~
    private String url;
    % [% o4 j9 o5 v; d" |. K( x4 o5 u
    @Value("${jdbc.username}")& A  r/ w  C/ L2 `; U
    private String username;4 P& Z! I; Y& S
    4 L9 ~+ y0 w4 I
    @Value("${jdbc.password}")
    . N9 E/ X( F2 o: v7 R+ bprivate String password;3 G/ I; Y+ ?) ?0 o$ U6 l4 a' z
    下面来看案例
    ( v# Z$ j7 M) ~3 J  y; p: N* Y% F# i( w. ^8 K; J: p+ ]1 _
    案例0 m4 ?* h0 {9 Y9 g' I
    * L/ B& w* W' s2 o9 p
    来个配置文件db.properties
    ; ~0 ]  J. J9 K4 n$ b; P# r& b1 I/ M( B$ X4 @& Y9 }
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8# Q  [1 b& m5 i7 f* v# O
    jdbc.username=javacode
    5 P2 F$ _- Z$ g# X7 ~jdbc.password=javacode
    9 R* F' l) k! q+ W" ]$ ?来个配置类,使用@PropertySource引入上面的配置文件
    % n. m8 X# @% O) g. X5 d# |' v; V
    package com.javacode2018.lesson002.demo18.test1;+ A0 X8 k! J" ]" M/ ^' R

    9 u3 e  }/ y% A/ d( Yimport org.springframework.beans.factory.annotation.Configurable;
    : U; h/ ~7 ~0 _! P# R4 B% M  t. bimport org.springframework.context.annotation.ComponentScan;  v$ F9 m$ D' C  }/ s/ N+ R
    import org.springframework.context.annotation.PropertySource;
    1 W7 @4 p' V/ |+ g) p2 o& t* ?, b0 c- c
    @Configurable
    0 l& i: l+ f  i' t# x@ComponentScan) O6 `2 d0 {! [  |  ^0 B& o
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})9 g2 R5 ~8 e0 \
    public class MainConfig1 {
    / ~* [1 [/ }2 ~/ |! k}# j1 _% [$ Z+ I6 n4 P" W8 F
    来个类,使用@Value来使用配置文件中的信息
    3 t3 P/ Q. D; D* R8 c7 |+ x% n: G# K2 L
    package com.javacode2018.lesson002.demo18.test1;
    1 X2 W. X" A! l7 l/ Q/ `2 Q) {
    + x9 M1 \9 W$ J6 pimport org.springframework.beans.factory.annotation.Value;
      e- r/ g7 U& j# w) {6 Simport org.springframework.stereotype.Component;6 X* V) o2 l9 K; e% i
    6 y' }% ?' c  e1 g$ s9 P( i4 Q
    @Component' `/ C0 {8 J- y4 Y* L' m
    public class DbConfig {
    2 I- E. U$ f* ]% h5 o
    ' Y2 D% \; N. v6 V8 [, g    @Value("${jdbc.url}"). Y; B# C7 t6 E2 P8 @' ^' S- k
        private String url;4 j$ B: a% a4 n- H

    ) u" i; ?5 C8 z7 ?3 q) u  f  T    @Value("${jdbc.username}")
    * ^  c$ b4 P9 T    private String username;  T; |( J% H9 X( j( @

      v) D+ j" ]* n/ x% k    @Value("${jdbc.password}")2 O/ y7 Q3 V2 v9 \% W1 Q( t
        private String password;* `% ]( i( A8 a+ R( V5 M+ C
    3 z9 a/ S2 O: h4 f# h
        public String getUrl() {
    ' d! ?$ K" ?4 ]& `! L2 W        return url;
    # J, O) y8 I4 X% [) Y0 s  p    }
    9 u3 k5 k9 x( b! S& X. A! z9 O6 m% u% c5 [+ u6 \0 x7 y
        public void setUrl(String url) {4 D! f$ g: ?8 m" _) b4 K6 v+ C
            this.url = url;, S# u/ z( g  ^" u8 z/ ]
        }
    / K% }. d* E  Q; w/ B; C: |9 F/ W7 H6 Y) g. ^
        public String getUsername() {
    7 ]& N* O1 j$ ?& u+ D# A8 {        return username;+ t6 J# r3 i9 c% z- f. H
        }) p3 t- \3 I- {& Z
    5 n( W% ?) p8 f% i7 Z. L
        public void setUsername(String username) {. s' }% y! e% t* t+ l. M+ ~* t0 B) I9 x
            this.username = username;6 O/ _6 v/ T/ Y% g
        }. G* O  q( r) g# ]+ u$ F
      Q5 P% [( a# ^% b
        public String getPassword() {
    ( `% l& p& i/ L; s' s4 D' _        return password;
    + T! z1 _2 ?, }$ c7 U5 V1 V0 d    }
    ; f1 \5 L) R! S  t# S9 Y3 p# _. }$ \0 M8 ]9 t  M
        public void setPassword(String password) {
    4 G2 v, M3 ~; g7 \        this.password = password;7 z  e/ ^6 }9 ~. N
        }
    ! n' n7 U! i  R4 @9 Q& g6 E$ w# U- M& _1 X5 q( e# ~
        @Override' t4 ]+ \, Z  P' ]/ c6 M3 _
        public String toString() {; _8 Y" o$ u8 N, w( o1 A
            return "DbConfig{" +$ `8 h: \2 W. I  [6 Z) m
                    "url='" + url + '\'' +
    # H. Z2 B, V5 k( i) _                ", username='" + username + '\'' +
    " w; ]$ T! U4 e6 @% t2 ^                ", password='" + password + '\'' +# l# @1 W5 f; o6 {% J
                    '}';
    $ A! i: F2 Q) _/ o$ {3 z    }* q' ^0 _$ I2 I/ ]( T' W; G. l9 d
    }/ _9 k* Z6 o2 h# k# }% d
    上面重点在于注解@Value注解,注意@Value注解中的: Y3 m; V) c$ J, q  D' \
    9 }% s0 e1 }" `+ X" T# S$ d! g
    来个测试用例5 n) E& ]* W8 M5 G  B+ T4 `# i
    9 {4 D0 T8 C* H: L
    package com.javacode2018.lesson002.demo18;
    1 h8 d! w& T" }4 o$ H0 w
    7 X& _  M/ k* S7 O1 M2 n$ vimport com.javacode2018.lesson002.demo18.test1.DbConfig;! v; U  ]( y1 H
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;9 y2 [( b/ L' S; w0 @! A2 a
    import org.junit.Test;
    2 l8 H" b8 Y" P5 Cimport org.springframework.context.annotation.AnnotationConfigApplicationContext;* u" R. Z- C( x1 P4 I  C9 x" R7 P; G
    5 W* G: L' T1 H  d
    public class ValueTest {6 Y8 Y8 l3 N7 v, c, P! t
    ; w# [- N  B; V* Z5 E
        @Test8 u5 `% a4 y# A/ Z
        public void test1() {
    3 K4 Y5 }! C* A, Z6 r        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ! R, Y0 D7 B5 g2 H% r, @' p0 S) b        context.register(MainConfig1.class);3 A- X( P: h9 {" C
            context.refresh();, D4 D1 g$ }3 F" F6 s

    6 D% b, w6 ^- d* f4 H        DbConfig dbConfig = context.getBean(DbConfig.class);+ V7 V4 U# g+ c; U
            System.out.println(dbConfig);  H! }# v5 I5 y' d4 L* |. u
        }
    + W4 x9 f1 N* d9 D' o: k4 F- Q}& G! [0 H' Q& s% q  p# ^
    运行输出. k0 M9 j2 s+ }/ {5 b

    / x6 L6 G) Z3 i" t4 ODbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    4 D3 ^" K6 w5 F) g% E0 e% Q. w上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。" v# [$ s% j, {! u7 {
    % p8 R' v9 m' a: v" d! S
    @Value数据来源/ G+ F* B! }8 y  z& }( J. p! x) l# h! k
    . h1 q6 s! t6 M' K4 K  T
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。* |! U0 q! q( U/ u2 \. `

    5 s+ `7 |4 H2 V5 }我们需要先了解一下@Value中数据来源于spring的什么地方。
    7 o& O  w* D/ C/ Q4 p' f! Z$ N5 D5 ~! ~5 Q) s# Z
    spring中有个类
    8 z6 W: m0 g9 |' K" c
    ; U9 h# `, q" }% D& gorg.springframework.core.env.PropertySource% S7 K& j% l% g& P3 S& I6 a
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    - m" G/ z) |% W2 J; |& p+ O6 B" d$ [5 _4 s
    内部有个方法:
      v- G8 r' u) C3 J  L- r
    4 w; E3 C% o: D% z$ Npublic abstract Object getProperty(String name);
    + }& j6 h, b) s0 `8 {# E/ `9 k通过name获取对应的配置信息。
    % V! b1 m# J9 c( {( E+ C6 y
    3 G  \" g/ V& n, ~4 C系统有个比较重要的接口
    . Q  \: s( Q" Z% t4 k+ N/ F& b7 Z* z3 m4 g  v; y, \- q+ s
    org.springframework.core.env.Environment$ n0 i) Y3 c( |' R+ R# E7 U4 p
    用来表示环境配置信息,这个接口有几个方法比较重要
    " g3 u: e( A; p8 Z# Y
    ) L3 }' e( e$ n6 W3 o) |String resolvePlaceholders(String text);$ |4 A: Q/ g; w, |$ H  \2 Q+ ]: D& E1 R
    MutablePropertySources getPropertySources();( F6 d# m: h8 t
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。* R6 t: @9 D/ ?8 S' U- f
    * `: e2 A3 k& o+ v, Z. c" m
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    / \8 w7 u/ B  o5 H. ]' p( Z  i' g! p2 @2 _& Q: z; B: ~
    public class MutablePropertySources implements PropertySources {
    0 L* Z. P5 I" u% }4 u% {3 t* x! @% }- ~
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    : T, ^! P- L- J. Y/ O, S5 S3 }  q8 A/ d
    }% a( O: t- m" n, |
    内部包含一个propertySourceList列表。1 Q+ l, k+ g, X5 t9 \
    + r: Y  ]- A8 ~$ ?
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。' Z) F" g% e/ R- M5 z0 M4 |2 f7 U+ p

      [+ K8 z0 L. a) n7 U大家可以捋一下,最终解析@Value的过程:
      c) f: X4 H$ L" b) g
    ' L5 ~- |1 o' W1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析- Q$ m2 n# c6 O1 [. b; _
    2. Environment内部会访问MutablePropertySources来解析* w; l7 O: u' a: n7 c  ]( O
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    ( W! |8 N: \/ j& m通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。3 H! c1 ]: h. \+ I8 b$ f7 w
    * a9 Y8 ?1 F! w9 o
    下面我们就按照这个思路来一个。
    5 G# A" F+ S+ S/ W, [+ ~6 J( n8 T/ i& X6 |5 m! r9 ]6 ?6 V
    来个邮件配置信息类,内部使用@Value注入邮件配置信息3 I  i+ H, C# b* X6 k+ |; p
    1 H6 |/ M% f( [, |( a3 T$ u1 w
    package com.javacode2018.lesson002.demo18.test2;
    4 T8 Q- P; p/ d
    - l! f( ~/ Z# yimport org.springframework.beans.factory.annotation.Value;
    8 }5 R/ @. y+ ?4 ~) vimport org.springframework.stereotype.Component;4 k2 h0 b8 R% H4 m1 T: A: O
    6 A/ w/ b( y0 z0 u% x" [2 |/ Z" a
    /**
    . E+ j6 f6 o3 n: v) ] * 邮件配置信息
    8 n! Q9 @2 {" j" Z  @! K */
    ( F/ K# z5 p: ]: f3 y@Component6 t8 |& I# ]. E1 S( G7 W  c1 t
    public class MailConfig {" k, ~& S. {% o) [5 n

    7 F1 L2 Z* g1 R) ^    @Value("${mail.host}")
    1 Y0 |+ T5 y0 d7 S4 j, g7 D    private String host;
    ! ~7 a6 J6 z% E" j* q3 h) {5 ^
    6 f6 N3 P* K" Z6 n    @Value("${mail.username}")1 [& `' ]: |  m/ K
        private String username;
    5 J9 ?- d0 K. E3 \0 S8 O9 w3 _2 Z4 ^/ C5 x7 U. @. T
        @Value("${mail.password}")2 F& F0 r( a- h' N  l% q6 B( [% U
        private String password;7 i6 p$ I+ W6 ^2 G( z
    0 T3 U5 j$ }. [; \
        public String getHost() {
      A: D' h. E4 e! v        return host;& Z* _+ G$ w9 |& O6 ^. X. m
        }7 b- L* i: u" v; m; K! J) k$ q5 p4 \
    5 E6 V, w8 c/ ~' `3 ~- A8 J& _
        public void setHost(String host) {
    1 \0 l- C# E) F        this.host = host;% w- Q5 X" k  f3 Q: U8 m. D% Q+ f/ T
        }
    4 f5 O( }/ O/ Z1 r( @: ^
    ' T$ @& D, m( o% P0 S    public String getUsername() {. R2 @; {$ E- y+ H( T
            return username;
    / z* a: O$ U  ~6 B9 c" p0 m, M    }+ u. B  q; [, o+ ~2 L% g( v

    ( s6 c. D, i; G    public void setUsername(String username) {
    7 ?5 q- w$ p0 ?  i% p0 q        this.username = username;
    + a. Q3 |: c- p    }
    8 d! E$ i" \5 e* X5 H& v3 a: w8 @* p( g: R5 B
        public String getPassword() {0 V# w7 k& v' ~! g* x- f  P! ~  t, O
            return password;+ G$ Z' J0 S  Q
        }4 k4 ^* E& c! X
    3 M3 k- G  M! E
        public void setPassword(String password) {
    6 Q% {. G* |; U3 U, T) o8 p/ C        this.password = password;
    6 k) X$ L  C" O! b  V' q& H    }. f3 Z# J4 ~1 U. E- c0 D5 n0 d, \
    - L/ X' h+ o7 X  |
        @Override% I) [" i6 K( H& X
        public String toString() {
      b; Z, \6 z6 ^7 `: r% s) C% l        return "MailConfig{" +
    ' Y6 F* m9 z6 Z# \                "host='" + host + '\'' +
    % i( R" ]- Q5 J! b4 X: o: _! A  v                ", username='" + username + '\'' +3 r, j" G: I( X/ N$ a" P' {
                    ", password='" + password + '\'' +
    # d* ~$ u8 j9 u, K0 [: C/ v! [                '}';- ^0 Z" o$ W2 g
        }3 m4 @; c/ ^+ ~3 W: \" t
    }8 r5 z: N, x, O* @8 \. U9 _- r0 m; |% G
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    , w& e, [5 q2 a9 S. x% d2 t4 Z8 a. O/ y7 B' f: C: f
    package com.javacode2018.lesson002.demo18.test2;
    % {* v3 S: Q8 N5 h) M9 _' f- L2 @5 a1 t4 @  e; K
    import java.util.HashMap;+ S' X( Y; e; G. g2 Y
    import java.util.Map;
    & j6 ^/ v  D1 v- a; A( _# D/ h- i7 q+ [( @' w
    public class DbUtil {
    7 v6 g3 _! p& u- }7 I) z% n    /**
    ! Q  h9 p% X6 ?" e( l) }: _) g( z     * 模拟从db中获取邮件配置信息, D: E# T$ e* w4 l+ o
         *
    . U* [- A# e4 B- h. _! U     * @return
    * q- e+ X0 I( a  }" E6 N     */5 z; a, e" _0 l' ?
        public static Map<String, Object> getMailInfoFromDb() {/ F0 ^: |0 m; n6 Y' I9 T  A
            Map<String, Object> result = new HashMap<>();
    5 M7 ?- E# \1 b  I        result.put("mail.host", "smtp.qq.com");  `" `9 {9 k7 D4 n: W
            result.put("mail.username", "路人");
    : p6 |4 B! w  g        result.put("mail.password", "123");# H- V$ a9 [6 M7 j: E1 T3 B$ _
            return result;& x( s& q0 D" u6 q: t- g* F
        }0 K* e, s3 u! P8 v7 W9 J1 x/ [( [
    }
    5 o* }$ _) `- v; \, E( c& v( e" ?来个spring配置类
    ; j$ Z7 I- d9 K) [6 X4 d4 t8 L( k* @' e
    package com.javacode2018.lesson002.demo18.test2;
    8 l' Z/ `# x/ ]/ T( o
    : J8 U; q4 g+ H3 Kimport org.springframework.context.annotation.ComponentScan;
    . @* ?$ z4 S& G7 k' limport org.springframework.context.annotation.Configuration;& C% Q. Q3 u% A- v4 Y/ S) ^. B
    2 E( V+ R) \: s
    @Configuration0 I$ W/ Z3 Z$ O. |0 q
    @ComponentScan, V# S" C5 ^3 J/ A& G6 Y, ?- y
    public class MainConfig2 {" W5 F3 \( i" c
    }
    6 h8 T% ?# m; [% |+ B$ A9 E/ H下面是重点代码
    ; X- A" D) s' z4 X( N7 b% i
    8 j' f: g  o% s- k6 h# G+ ?6 z" b@Test
    2 C. h; V6 D& T0 b0 F' u5 gpublic void test2() {
    * n4 n8 ]! u, O    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ) m9 I8 R$ l: q" k* z; x9 g% T# d* Q" g- c4 G" ~
        /*下面这段是关键 start*/
    / s( J1 m2 R/ E; D" b0 }    //模拟从db中获取配置信息
    * b2 i0 {( ]  Y3 s9 p9 ]    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    7 O- a6 F! C( \7 k) b% G    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)7 q* C' `' L1 Y6 N7 Q1 N& i
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);. `6 g3 {% C- w+ l2 E' C$ p
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    9 C4 C1 e, ]3 i& J  J    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);9 Z) f- f  a0 T# s( m4 m. o/ \
        /*上面这段是关键 end*/7 ]& m7 B, M# \6 @

    : t! N  G: s( E* D( E5 j    context.register(MainConfig2.class);) j  v  p+ D9 `. @/ k
        context.refresh();) t1 P6 O1 K5 s+ d1 g; u
        MailConfig mailConfig = context.getBean(MailConfig.class);" p/ P' C8 {6 Q6 ^4 I
        System.out.println(mailConfig);
    . r' \8 _- f1 b4 t! K* Y}' U8 I  M0 y8 n9 ]; R
    注释比较详细,就不详细解释了。
    6 F) n" C/ |% _, u
    7 N7 |0 N/ ]) N! i  z直接运行,看效果
    3 N+ v  u4 c: Y# l) U, C& n1 x$ `% ?; D4 J* N4 W
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    ) `% S; h' x5 f$ u2 g有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。$ G8 e5 |; Q, m! |7 u, l" x1 h: s
    ' U; O  X" [- o2 b1 v) }
    上面重点是下面这段代码,大家需要理解  n/ b8 ]) R0 I# l

    4 S3 M  q; Z5 `9 E# ?& [' v/*下面这段是关键 start*/$ ~" b$ A) |( s2 D( C
    //模拟从db中获取配置信息+ j0 w; E. P4 E9 n$ B; P
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    6 y, S' U* I- \; Q, \//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)% z8 i0 X7 u# _4 r( {/ H
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);; p- n, L0 \6 W& o$ S5 w/ U
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高: w) ^" n! x* }3 a" ~6 r0 h
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);1 v" X2 L0 o1 g3 m
    /*上面这段是关键 end*/. f! k6 Z+ C% L0 K: U6 U8 O; t
    咱们继续看下一个问题
    % F; Y$ S$ Y( ]# e$ t6 t
    $ b: H* N* @: f6 Q如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。& v, ^; c; d- i

    ( x$ p9 E9 \+ e% \@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    4 i& X7 c, D2 k. K* g& t  ?/ b/ ^' x+ L
    实现@Value动态刷新8 ~: Y$ Q7 y  S2 g
    0 E) g# g8 k" v* \  v( C
    先了解一个知识点
    ) U3 o. X# L8 R! D
    " n( y7 S  q$ h5 w. N, C+ Z' q( q: D这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。4 s$ K. }8 Q7 |4 q' ^8 Y( e$ _

    $ _: _2 C; J6 R  \这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    . _$ ~8 w/ o% G7 i' _/ G! A, }
    ! E4 H: f* b- o# k2 l- [# ebean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:% D4 Q0 S4 B) V/ L$ f

    / ]$ r) G7 X( q. [4 r. ZScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;0 x8 F; s6 A" H+ x; H5 D
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中$ k$ y1 U3 A4 X3 S" s* j

    ' u2 q1 ]8 i0 a% M* h5 ?) Fpublic enum ScopedProxyMode {
    1 ]; \: {3 ]  D3 r7 u3 s& a& d    DEFAULT,
    2 ^4 [# P% I8 C. E- Y, S2 ^' r    NO,: F9 d. E* y% l7 j& m
        INTERFACES,. C' Z) i2 I$ @7 P+ N: H
        TARGET_CLASS;- M& A$ r) Q' A6 s" i
    }
    0 P$ k/ S( j+ o; Y5 ^前面3个,不讲了,直接讲最后一个值是干什么的。
    - Y* |  }  t( X* K) G7 T5 A) I4 h6 ?" p' K6 c  T7 ~
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。# [+ c* U$ _5 \8 r
    0 m$ O$ t+ F! K* V
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。  v* e! x) h; Z# Z. ~, ~4 N- u; k  o

    ) k! x/ y1 Q( b7 z4 W0 ^自定义一个bean作用域的注解' g' y: k0 M& w# L( @. q
    $ U, U8 |1 v; ?
    package com.javacode2018.lesson002.demo18.test3;
    * Z& Z1 w, w4 F/ a0 T
    ) D" }8 L3 F9 B  wimport org.springframework.context.annotation.Scope;
    ' U: D1 P: A& l6 T6 I" _' s, Eimport org.springframework.context.annotation.ScopedProxyMode;( }5 Y# ?* H5 `) c- t

    5 F2 }  }! d- }import java.lang.annotation.*;
    1 W& L3 K: m3 H
    # j- }. o: V9 y1 X: e5 n" R' I@Target({ElementType.TYPE, ElementType.METHOD})7 q  q8 ~4 G. z5 f$ U& J
    @Retention(RetentionPolicy.RUNTIME)
    6 O. b2 \% s9 i# u1 V# Z7 q@Documented+ O; n: o6 [- ?  L
    @Scope(BeanMyScope.SCOPE_MY) //@1
    9 T* r7 {- E" r# spublic @interface MyScope {7 I/ |# d2 ]. w1 V
        /**
    & l& U% c1 E, l' w# ^     * @see Scope#proxyMode()4 y9 A) O9 Y( K1 Y( ?2 F
         */
      i4 y* }% f% y2 x' b' c6 U( v3 g    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    3 Q) S5 K% i# Z3 B}$ g1 A* O; l0 u8 K8 @3 @  x8 g
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ) |8 ]/ `' o! f5 E5 g+ K: t8 }7 O" f
    : k+ w! S8 `8 j# M+ I/ w@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS& @/ \) K, f) q$ L; B: M

    ) w6 z0 s: V  H. L. R4 D; M5 c, K@MyScope注解对应的Scope实现如下
    " s  Z9 \3 ?! O2 J) s" M& D' k  s  V
    package com.javacode2018.lesson002.demo18.test3;* s& |& R9 |2 e' F) @% d$ ]
    0 ?" t" v  W3 c' u  s
    import org.springframework.beans.factory.ObjectFactory;
    6 c% J' |! K+ O- q* z( gimport org.springframework.beans.factory.config.Scope;" D7 q! O6 {1 ~3 K  M2 p! c+ b
    import org.springframework.lang.Nullable;" ?0 D! ]! s) d# f# {, N9 Z( {. S0 N

    ; y  d% u( k8 o5 @; \: d/**8 H3 Y* g0 m! Y1 X5 t* B8 q9 Y
    * @see MyScope 作用域的实现' B6 |8 B2 u! ^- ]
    */! u5 s" {9 N7 P2 _8 j
    public class BeanMyScope implements Scope {7 A6 \7 q. h; ?5 u0 }/ l9 m7 X+ d
    5 R/ m, j5 V, c
        public static final String SCOPE_MY = "my"; //@1
    2 P4 j+ ?; A' f7 q+ g' x: f
    ! l1 Z$ G: W: ?  i) d& n) y    @Override: E6 k0 H" X8 G0 x. ]; g9 ^9 \1 m
        public Object get(String name, ObjectFactory<?> objectFactory) {
    + Y% D' x, D. i8 b7 s$ g  p" u        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2% u9 |. ?- K7 `! N; S# u# I5 B
            return objectFactory.getObject(); //@34 b  |6 Q% `+ X/ y# i6 @4 L/ n5 i
        }, o6 X9 ]$ W: y, k1 O4 A
    * W# G& F1 S5 J7 V# J1 @
        @Nullable# K/ k) n  q& a
        @Override
    5 }# d* W# i$ w8 z, M5 F& e    public Object remove(String name) {3 E8 V9 D8 Q3 W4 ?8 B2 `' H
            return null;& [( ~9 T7 [, j8 ?7 M, m
        }9 N& V; g* ~. w: H7 r/ s% m
    5 R$ F& H8 v9 l( v
        @Override
    * H; ~8 E+ ^5 s) m! l    public void registerDestructionCallback(String name, Runnable callback) {
    # [) ?4 M  f8 }# k$ a& c9 u9 N4 `( c( |9 C! H/ f  z' f
        }
    * }( I" y: G# I$ S! v' x3 m" p( u- _
    ! x" E8 ?; a. w4 K/ s    @Nullable
    5 F3 z7 ?( h- K1 S) o& i1 q" ?* Z    @Override
      e( r" U+ Q/ ^9 t0 b* w    public Object resolveContextualObject(String key) {% z' W! N6 u. F/ J! n
            return null;  O; E8 m0 ]5 N- a
        }8 N( s$ X9 C% B: ~5 j5 B

    9 @. [" O# W2 d) \    @Nullable. j/ R9 C- [3 d
        @Override! Z$ v6 F" R+ G$ \- Z
        public String getConversationId() {1 j0 l0 g4 j. O
            return null;
    0 b# U$ @" S* Y$ @2 b    }4 v$ p$ j) F+ d0 P. k$ B$ n
    }8 U. F% Y) O. R+ q1 C* q1 H3 T
    @1:定义了一个常量,作为作用域的值
    # ?8 D# [# B; a. N& i/ z/ n4 S: C0 D9 x- I6 w/ l9 k
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    2 L% @: W. K( Y1 \4 b  M( P) ~8 [& C% V! k
    @3:通过objectFactory.getObject()获取bean实例返回。
    6 |* N* ~6 N# i# t- B$ m( G7 }- V, n7 w. ]% Y+ y1 D
    下面来创建个类,作用域为上面自定义的作用域
    & T6 \! M% a$ v0 L+ r. q) c; e  Q
    5 o7 B- D1 g9 [$ ]+ m, t# C& Dpackage com.javacode2018.lesson002.demo18.test3;
    $ j( x9 U, I  T# A9 \; g1 i6 z+ A- D0 r: Z, T6 E2 F/ b
    import org.springframework.stereotype.Component;
    $ X+ _# U" K. X7 n- A- }4 L
    6 X3 [$ K# x3 W7 {import java.util.UUID;3 R6 E$ d8 Y( A( r! M* ^

    " ~& E0 \, A4 x" q$ o@Component; }) U# v' D1 W) d2 o# k8 [
    @MyScope //@1 8 J: ]+ ]" B: I6 G+ X2 R9 [
    public class User {
    - N  J$ O% [. ?0 }& x$ z$ p9 u' C* q$ h
        private String username;
    ( |' c) u! x4 \4 r* w1 I3 [+ }7 Q5 Q' i
        public User() { $ [+ x& x7 ?; d! L( F6 C
            System.out.println("---------创建User对象" + this); //@2
    / b) e- g. ~! F1 l2 \# H        this.username = UUID.randomUUID().toString(); //@33 e  t! P( P8 O2 ^8 `5 j
        }
    , M0 y4 p7 R" L- v: _9 \7 P2 G! K. v7 S  j/ V2 X
        public String getUsername() {
    " m  N3 z6 s: D; V. P' ^( Y        return username;
    : B- h4 N  c1 U    }
    : W8 N2 ]+ S8 X5 e2 X! g+ a+ @4 C* r0 q5 S+ y6 [
        public void setUsername(String username) {  f  d- c" J( l. g; i( r, E7 e7 ^
            this.username = username;
      \$ R- L/ Z+ n* m; L4 j2 V% B    }5 ^2 F- ?" d$ u2 G  ~3 t

    ' a# e$ F' n7 @* m$ Q% E, p% k9 a}
    $ w4 p3 T* @- S' A" u- p  s3 ^3 H$ G@1:使用了自定义的作用域@MyScope
    3 |& B' P! S& j$ |4 y9 ^0 m* M0 r- R1 S6 y' v5 ^( U# A
    @2:构造函数中输出一行日志
    5 m7 [6 ]8 ~6 p% ?
    " F4 B: h" E$ M6 X/ e  R6 K! u@3:给username赋值,通过uuid随机生成了一个7 j, |; w6 T9 [6 x
    * P4 I0 d- {8 l5 E! l
    来个spring配置类,加载上面@Compontent标注的组件
    / |& u2 W( I: P$ c7 P$ J! B% s, U4 K
    package com.javacode2018.lesson002.demo18.test3;  G2 S# @+ \1 q3 k& Y7 i
    / j/ e4 I; w6 o, `4 J4 S
    import org.springframework.context.annotation.ComponentScan;7 U+ C0 ?3 Q1 M9 P  {- d
    import org.springframework.context.annotation.Configuration;
    ; h/ }' k% ?1 b7 V* W) L+ ]
    5 f6 q8 T8 ]% Z$ `* t3 d@ComponentScan
    & }& d" S# F& O1 i* o1 l@Configuration
    . E, O8 e1 g* F! N2 ?+ m8 Cpublic class MainConfig3 {
    2 C4 E+ b9 W* t( C* O& y( L  ]. g, E}
    ) X3 a3 H8 b8 M# N! @7 S3 C下面重点来了,测试用例$ s- K" Y; J$ p9 y
    ) ?, @) B' T: q7 M( M( @
    @Test* i! z* e! x8 x9 v0 y& Y
    public void test3() throws InterruptedException {
    * j6 g# h8 q' v4 a& w# M8 u    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' a& r' \( B0 c/ x5 F( Y
        //将自定义作用域注册到spring容器中8 e; s& p- F& Q0 U7 q# u! y
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    0 z$ W) I# ~  B( ?    context.register(MainConfig3.class);
    5 C  P: b4 t( n$ b9 R" w' ~    context.refresh();. W% `% Z2 w5 X5 l- P* ], g

    6 ?3 l8 Y: s# @- G! z: j    System.out.println("从容器中获取User对象");6 F7 h6 s: l. L
        User user = context.getBean(User.class); //@2
    , ^- r! J3 j, v! e' X9 Q    System.out.println("user对象的class为:" + user.getClass()); //@3/ A5 P9 ?6 _0 |6 ]% V

    " a' B1 O4 e( P" O- L% p* T    System.out.println("多次调用user的getUsername感受一下效果\n");
    * q" }1 u& o- w; |    for (int i = 1; i <= 3; i++) {
    " _7 L8 U2 U& [  j        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    6 \8 D: W- l. ^  u+ \% k        System.out.println(user.getUsername());1 M  K5 G6 m4 W9 v/ G" M. D+ C* C6 P5 ?
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));" [9 K: D: M6 t  v$ Q- H
        }' f  b0 l9 k$ ~( d) g
    }
    4 Y* _0 `/ O9 h8 E9 G6 K@1:将自定义作用域注册到spring容器中
    , b5 I3 o- H: U( X& q2 {$ L
    + r, H: K' z0 M4 Z. y@2:从容器中获取User对应的bean
    1 |2 q5 B+ X. P- t3 r$ v/ }
    " p0 \( ]$ l/ I% G  ^; B@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的5 z0 N; X8 ~/ f2 A+ p1 b
    - ]" r+ ]+ d% X: T) M
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    3 V0 k  v- ?* L) D
    9 b" u7 d' z) L6 t" ~" h$ z9 C9 w! H见证奇迹的时候到了,运行输出& x, d' v  N* B2 J7 s) P& z7 Y
    4 l% B* y2 V  e7 b( h4 k
    从容器中获取User对象
    3 w3 \$ U. u' ^user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    7 Q* f. q7 b0 S5 v* m1 x* d" F多次调用user的getUsername感受一下效果& X0 N$ @4 c; [8 M
    4 L% C6 n5 o) G) ]# g
    ********
    ! C' G, B$ \' Z第1次开始调用getUsername3 O. f/ w# s( D* y2 z! N" L4 [/ ~4 P& x
    BeanMyScope >>>>>>>>> get:scopedTarget.user4 ?7 n; s. c" f# I5 _
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4; V) o0 ?! o2 g9 Y( a) ~& X' ]
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    8 z) f$ E. W+ Y9 p0 Y5 h第1次调用getUsername结束
    ! A7 ]+ D) w8 [, t********
    + ?! P7 W4 P) C# T" [  ]
    ; z! u) l" q8 T  }7 _% W  ^/ H; L********
    4 d) x+ Y1 h  j' P1 m3 K$ }6 s. q第2次开始调用getUsername
    5 {, H# h2 l& Q) ]9 N9 T5 uBeanMyScope >>>>>>>>> get:scopedTarget.user
    2 P- ?' z9 I3 ]% @% ~---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b- m8 \8 }% [2 J; ]; H  v
    01d67154-95f6-44bb-93ab-05a34abdf51f' O  Z  G- r9 m, N1 ^
    第2次调用getUsername结束
    : T: M& V7 a* k3 F1 X4 G: ?********& F! N. y& L7 F8 d* y3 ^
    & |3 Q3 `9 n' S% n; P
    ********
    $ T+ M7 X- d3 |2 E! j+ M- _. j第3次开始调用getUsername
    & o% u/ r; A2 {BeanMyScope >>>>>>>>> get:scopedTarget.user
      s- `  L9 v- S+ p0 f---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    3 S4 T- d  m0 l7 G76d0e86f-8331-4303-aac7-4acce0b258b8
    : d% x" b5 n3 U  c第3次调用getUsername结束
    7 j; K* R5 Q1 Y+ j5 Z, G********, Q2 u5 X) H2 d% g
    从输出的前2行可以看出:6 e! O9 q/ P4 Q
    / T1 z* g' N5 M( ^1 _, x# t3 [
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象3 x9 `# h+ M( K. C" X- k
    & G" C  k, Y& u: V0 h# q
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    4 m( L7 \/ q4 @$ L0 s* _  k4 q( w
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。4 X% F: e% Z" s& H* z! u; M

    / W$ L5 s" R0 Q; G; M( w2 o通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。6 x# \' x, M1 {) y$ }: ~

    ' i  C5 a" t2 g9 Z  i2 H动态刷新@Value具体实现- V/ S; n3 u* U

    ( }- Z' Z9 b) {( x7 u- ~那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。8 |" A. Y( ^- M

    8 }( [0 L" ?) g7 |3 s8 |4 n9 w8 i0 T先来自定义一个Scope:RefreshScope
    : W0 l: n) m  Z: @( w; J7 [% ?1 g+ g; D( ~4 {" l5 r9 [
    package com.javacode2018.lesson002.demo18.test4;
    ! C* K% m8 A! }% W- F
    ! ^) r% ^* E: {: X1 {7 v6 ~4 Q- {import org.springframework.context.annotation.Scope;
    " G4 ~* n4 h# F' nimport org.springframework.context.annotation.ScopedProxyMode;1 f9 `! \9 C0 D0 P# `. w
    % ]7 M% H& r+ O7 |9 |& w
    import java.lang.annotation.*;
    + i% r+ r1 {( q9 g7 y& F' c, k; P& O5 [  v* t7 X1 K& U  v
    @Target({ElementType.TYPE, ElementType.METHOD})9 z+ m& Q. t# D" t& T4 O. i
    @Retention(RetentionPolicy.RUNTIME)/ j1 a' ^5 C, E, z! g$ F3 Z3 }
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    : b8 \8 o) [# T. {  G@Documented
    ' a! l& x/ u. D- upublic @interface RefreshScope {
    - Y) |: v7 z$ C5 A# ]/ S, S6 z    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1- {: q- A& Q+ K( b# O! x" R* u
    }: E' z) L" e% U6 o( R% v  M) B
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置, A9 b% h! L, t3 I6 @- x; f
    0 e! ^3 ^& T# i* G. I, @1 D
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    ( C! S3 p1 ^: L+ G* x* \- c+ U. o6 t3 f8 T4 \" Y4 ?! D
    这个自定义Scope对应的解析类
    * d5 f- E. H# }, {$ Z6 x' s
    1 f+ r( X/ I( L  R下面类中有几个无关的方法去掉了,可以忽略. S9 ?3 s% K; O0 Y4 n

    + A; D7 H) }, R( npackage com.javacode2018.lesson002.demo18.test4;
    & E" m& \' R, ~
    7 n' j1 I% J6 D* x* E# D. V
    4 |. H9 [$ G6 W6 a- @# A5 D$ }9 i8 Vimport org.springframework.beans.factory.ObjectFactory;
    9 X5 t8 ?; }  }9 Q( ximport org.springframework.beans.factory.config.Scope;
    / M+ s* V8 f  Y9 w/ \import org.springframework.lang.Nullable;
    / C! o' z0 G, Y% A& ]0 a! R8 P
    ! y# |+ u" ?* T! Qimport java.util.concurrent.ConcurrentHashMap;
    ( G# |% k4 }% l" p7 n' q  B, ^. _- c/ j
    public class BeanRefreshScope implements Scope {
    * y2 {' [8 X" y6 ~5 ?6 A1 c3 V1 \/ O# c" `; P! }* X
        public static final String SCOPE_REFRESH = "refresh";
    1 c0 R: z2 u, p7 K- i: f! z. H  p. h6 A* p& ~, k; ~# P+ t
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();) B. |  b' b# k  Y4 R" [

    2 E6 p8 J/ P3 D; E    //来个map用来缓存bean, R( r$ N: L# F* H* d8 y4 O
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1; b5 H: S* `- ]8 F8 t  r5 ?4 s3 }
    8 \) c" Y) \9 e9 W( |! I/ c8 Z, B
        private BeanRefreshScope() {
    9 n( a3 Y" g1 M    }
    : w) X4 h+ q3 J7 ?) r( [+ \1 _0 |$ M, R/ [  Z
        public static BeanRefreshScope getInstance() {4 i0 ]! K% j% a0 I
            return INSTANCE;
    - X  g* I! J5 X" [$ i5 w    }
      o: G3 [4 F& e
    2 F  \: e  R1 O: S    /**6 G% I% E6 H9 l
         * 清理当前
    ' v: k6 b4 X% u4 }% f     */
    7 j: Q$ Y9 E7 O6 [, ~, {4 _    public static void clean() {
    . w3 U7 H7 e: C5 J9 j6 z1 M        INSTANCE.beanMap.clear();
    6 \2 s& |/ w2 v' K4 O    }
    " f  Y$ g9 S4 _: W% l3 y1 U5 {  x* ?9 r( N6 m, L
        @Override. Z7 ?1 R* t3 L- _8 q
        public Object get(String name, ObjectFactory<?> objectFactory) {
    4 S  `6 m2 p! e4 d        Object bean = beanMap.get(name);; Z6 S9 `% @! T: h
            if (bean == null) {- C, }2 R2 Y% g+ c9 B! g: X: M& m2 @
                bean = objectFactory.getObject();
    % E/ Q* l* N7 P1 P% C3 C            beanMap.put(name, bean);
    8 d) r* Y: `0 ^        }
    7 l! ?6 e( Y9 n8 u$ m9 u* ~1 y        return bean;( g# m6 y2 F! M# n
        }
    ; B6 l$ P5 }* q8 C+ ?# V5 C; T1 j& f, o
    }
    6 y  ]3 l: X2 z9 R- P9 z/ A& {上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    " N- w% r* J/ Y. k" p4 K9 H4 r7 M8 r5 M' i6 T, Q2 g
    上面的clean方法用来清理beanMap中当前已缓存的所有bean, v0 F1 n$ ]- E+ \% j; x5 W/ b

    5 x! }- K! C" A3 B) G8 x; s7 G! F% N来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope- g. n# l8 C, \: [3 A% z# }+ J, y+ N
    $ {* l6 d" c; \5 S
    package com.javacode2018.lesson002.demo18.test4;0 L* G5 s# }4 B) n& c( K2 x" i

    * D* h) D1 U2 U! timport org.springframework.beans.factory.annotation.Value;
    % d! M( r3 o, y  `+ uimport org.springframework.stereotype.Component;# L( A- [. M% d, W& c- d7 J

    9 m* w! l+ J' w: J. E+ l/**1 T8 B6 z3 y, L; \4 Y+ u1 \
    * 邮件配置信息
    # ^8 s( _2 h9 z, J( T& f */
    4 ], V+ q: P$ C: M8 b@Component" Y' c) n& I9 H  r) K) s: @* M
    @RefreshScope //@1: p) K0 y4 T- q$ J6 H
    public class MailConfig {5 }9 r7 t& G' i! q& e  g
    & n( `4 i2 J/ }8 A! V6 O: t
        @Value("${mail.username}") //@2# ^& N. r" S- ]! Z! \# c3 q* x2 q
        private String username;
    7 w" B2 U% b5 E
    3 z- {* ]$ R  L, X    public String getUsername() {0 g  R: l/ U+ o! c% v, j2 A/ Z2 J
            return username;
    9 I8 s4 p8 ^7 D* }& p- m    }
    # w$ B) f' W) H5 B7 J2 m4 M1 ?
    2 K3 r, M1 H8 J5 j( J  l* D    public void setUsername(String username) {; y# Q* r# d* X9 T0 @" U# E
            this.username = username;: R" D: i+ {& z$ u- N/ b# x3 M
        }
    " ~* |" c  {; K% I; n; |
    " w7 \+ \2 V$ `% T" M, d    @Override0 V0 \6 d5 w$ r6 f; _+ [1 @
        public String toString() {
    : p( L6 y, D+ k' D4 |3 t        return "MailConfig{" ++ C9 c) s% ^$ A( Q: N- d
                    "username='" + username + '\'' +; w$ `) N  }7 z! `/ r$ J- c
                    '}';- A0 C' X* m$ L& {5 E' R8 L
        }( Q* c1 }9 k5 j8 l
    }
    7 T1 o0 ?: z4 l@1:使用了自定义的作用域@RefreshScope! ~6 a" w" v+ U2 u8 G8 _+ Z
    : h4 H# X7 |" `7 ?, z2 o  s1 \7 L( X
    @2:通过@Value注入mail.username对一个的值
    8 J8 |2 r& ^9 K$ T
    ) r  [3 B  i) o2 a' C% c重写了toString方法,一会测试时候可以看效果。
    - |! k, a8 G8 V, h/ s. Q& r  C
    再来个普通的bean,内部会注入MailConfig; z! [$ H: H7 m3 }0 f4 ?
    7 r; {; j' H) D, D: U- p$ v0 b
    package com.javacode2018.lesson002.demo18.test4;7 s( w* G' A+ h# U6 h% {
    % j1 T( g- ~% Z- Q1 B
    import org.springframework.beans.factory.annotation.Autowired;/ f8 n- _3 Z6 w* G& L
    import org.springframework.stereotype.Component;
    7 A; L2 b2 l2 N
    : J1 m* d  Y+ z# r) D@Component
    # e: c, z% q( tpublic class MailService {( |% h8 B3 ^" V) s# {4 @% C
        @Autowired
    % j6 F+ c- r+ A  A  Q5 i, _+ J    private MailConfig mailConfig;
    $ V# f; V/ o* U' t, ^
    " M; `% U2 T. v/ ^9 r4 S5 G. F1 s    @Override, D& F2 h- m0 S( E) x7 M
        public String toString() {* Q: }3 C8 q% C0 h
            return "MailService{" +% U* d9 c  m& S& F8 a: B
                    "mailConfig=" + mailConfig +0 k" O: {5 }8 M
                    '}';7 d/ Y; P* M6 t4 N4 m
        }2 [3 F2 w% B& G+ D
    }# D1 K+ F$ g" W# [
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    4 P& i, g# I! e
    / E6 e0 ]8 s, i) d7 G# ^4 P( c来个类,用来从db中获取邮件配置信息: v+ l3 d% T7 b7 j
    0 o, q; x4 W1 |4 Q6 c# `; }/ S
    package com.javacode2018.lesson002.demo18.test4;* T% N8 G3 C. d" Q' s" Y

    # A1 P6 b6 b' V% F8 kimport java.util.HashMap;; N' n1 A# h3 Q3 q( Z
    import java.util.Map;; f# d! K0 V1 m& I" ~- E
    import java.util.UUID;
    3 m/ o5 A/ W, K, i4 P/ G, e% l
    : W: y; G; J2 C' r4 jpublic class DbUtil {
    6 U, x4 V& z/ E* V. c    /**8 ^7 ~! y# ~7 i; W: r
         * 模拟从db中获取邮件配置信息
    ( C: R6 Z" r/ [2 @) Q5 M- {) \1 c, m     *
    ) X7 ?4 H9 U4 g! G     * @return) A" f% O+ s% B& [! S0 C" |
         */
    5 J* v8 c- J5 ~! F0 U$ O    public static Map<String, Object> getMailInfoFromDb() {
    5 H* g  r8 P6 t$ v9 m* B/ e        Map<String, Object> result = new HashMap<>();+ A( D* i/ C  i) o1 ^6 a
            result.put("mail.username", UUID.randomUUID().toString());
      h5 x- a/ p  y8 Q( z        return result;  B. D$ t1 O. G
        }# P5 t0 W2 B- g) l4 ?" i4 n, e
    }4 U$ A' d/ s2 [
    来个spring配置类,扫描加载上面的组件
    6 k, }1 A- L2 A* ]3 l8 t3 J- v- `% c$ F6 T! A, g
    package com.javacode2018.lesson002.demo18.test4;
    * h0 q& z) E5 y4 U# ~6 T2 k  g$ P* G% b; ^6 y
    import org.springframework.context.annotation.ComponentScan;
    8 G9 O! N, D, u% m' y6 d* mimport org.springframework.context.annotation.Configuration;: S1 ~/ l" Q- ?# p- z' R7 ?
    ' r. `7 C  R, y: U9 j1 _7 W: }# @
    @Configuration
    5 o+ B, ~/ C1 c! ~  G@ComponentScan
    - ^/ B7 Q5 v, A! r! p6 h. O4 Opublic class MainConfig4 {* S  O& r# S3 D- G2 H( |, q
    }
    4 D6 J( [0 s: s, q  M来个工具类
    * {  y+ ]8 M. `* _' ?
    3 K9 C. T* k5 L5 o内部有2个方法,如下:0 c  `3 X' j, f

    2 m' |; M; T* ?3 _5 [package com.javacode2018.lesson002.demo18.test4;6 s6 y/ K* b! a4 b! x

    ; E  x# i  L- V" Q2 Dimport org.springframework.context.support.AbstractApplicationContext;( E" M' Q' N: E+ Z7 Q+ G* V" I  }! i$ A
    import org.springframework.core.env.MapPropertySource;4 ^! J7 o4 q& c. O/ f0 S3 \/ j

      o: y4 V0 s+ X' T/ Oimport java.util.Map;8 k) {4 C6 w* G7 D% J- q

    + c( \$ [$ O& Bpublic class RefreshConfigUtil {! O' A7 E" n0 ~! I0 w9 X2 z
        /**$ w- j% B4 ^& Z' [3 U
         * 模拟改变数据库中都配置信息
    $ E1 s! P; z6 S8 S6 ?     */
    # q$ C, E4 b) ?% M8 K, O    public static void updateDbConfig(AbstractApplicationContext context) {
    ! t4 V9 Z: d: N( ~        //更新context中的mailPropertySource配置信息8 X5 J) Z- r. z/ |2 c1 X
            refreshMailPropertySource(context);
    + D3 F. x0 m4 X* ~8 T
    / _  E+ I+ ~+ M0 m: a0 c4 q        //清空BeanRefreshScope中所有bean的缓存
    $ I0 t& I: H; B) ?        BeanRefreshScope.getInstance().clean();1 ~! W6 m5 Q+ a! }( c9 b8 p
        }
    1 k; q( f2 Z; R* Z3 s$ t
    - }9 w, s( ~+ t    public static void refreshMailPropertySource(AbstractApplicationContext context) {& r+ r' K: G5 G' m  I  j
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();3 L9 k5 |0 H. Z$ @$ r, N7 J
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)* h% F/ b6 n8 t, d
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    3 J  v5 {" I3 h, a        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);7 K# w% O! _0 T4 B
        }
    1 _. h" a  M$ b3 s: @  s' n) @8 d* Q: O' N: B+ U! [: {
    }
    + G3 b5 U: L: y' |, [updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息+ }8 e5 `/ y* S2 D( }1 K. |' b

    - D, D" e% K8 a4 Z( j% SBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    6 w8 b5 Q% K* j0 s. Y& o& l& X  l4 G- g* j$ c1 X  H* x' B
    来个测试用例. P$ G* ^) c; P
    ' C: ]9 ?% F# E' U* L2 G
    @Test7 j0 C. Q) o: o& E- P. e
    public void test4() throws InterruptedException {
    ) Y; q' l0 |, K6 S/ v2 N, Y    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    7 O' n! `2 w# g% D( u    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
      |' W/ `; T9 Z  [    context.register(MainConfig4.class);
    ; G, z/ T4 m7 q5 W5 q8 }. g    //刷新mail的配置到Environment9 A6 G0 z  M! x- D9 C1 C- x
        RefreshConfigUtil.refreshMailPropertySource(context);
    9 L% F# @: ?, P    context.refresh();4 Z' ^9 B" J6 q" M5 D: L7 a
    9 W3 h; Y8 b' c+ x  G2 I5 x7 k8 ~1 ^
        MailService mailService = context.getBean(MailService.class);! o9 ^7 o& x7 S# `4 O5 g0 X' R
        System.out.println("配置未更新的情况下,输出3次");
    $ N1 P; q( i7 y; C9 n    for (int i = 0; i < 3; i++) { //@11 M5 I! h6 A- d& B4 k: E  ]' {
            System.out.println(mailService);2 V" t* P" j8 I& v
            TimeUnit.MILLISECONDS.sleep(200);
    ! a+ y* }* _% k    }
    & H& ~$ [7 i* z7 x2 G8 b( O) U
    - g" R. Y' C* R2 x1 c    System.out.println("模拟3次更新配置效果");
    : t2 Q  ]* V. J$ h    for (int i = 0; i < 3; i++) { //@25 L. m; m& [% c9 t1 E
            RefreshConfigUtil.updateDbConfig(context); //@32 q( t+ ^6 a  y
            System.out.println(mailService);' [0 Q1 Y1 W) R3 W4 x
            TimeUnit.MILLISECONDS.sleep(200);, q* [4 V* ]) ~, ], N" Q0 o
        }
    2 k/ b4 G; P8 [) z+ G/ ~0 F: }}: ?4 p7 R8 ~# M9 o! N! m, f1 t
    @1:循环3次,输出mailService的信息
    ! Q% J, k- Q$ q; H
    3 \$ \/ V: e* r" S9 t@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    9 j' \- w8 H) e  Y- h
    * H1 J$ l4 y8 @见证奇迹的时刻,来看效果3 w. F# s" y% i1 v

    : n5 H  X8 S* x2 [8 S0 I配置未更新的情况下,输出3次: ]# g4 R& c8 O' @9 }
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}* o. h) {! j5 v3 ]  Z" O9 G6 ^
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}; j) ?% [2 i  t& W- H" d2 f6 ~# u
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    9 Y7 B  O9 b% U+ R* e$ z, x7 ]" z6 z+ }$ b模拟3次更新配置效果
    / Y6 F( [) P2 b. {" g2 hMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}9 o5 O% s) g# L
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    2 H0 B- K: G4 d) lMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}( i! [8 ?3 u0 p
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。- u) K0 Z0 r! T
    ( O& m( |  V7 N; z* ]/ w/ h9 l! V8 |
    小结" [. d/ t  A" G. ^; S8 b
    + E" L* T" V0 I+ H
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。4 u3 P# X: J1 e, _
    0 w6 u. h; r$ z; S& o6 U
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    & A' }& `; x, w0 C5 }# y9 n! M7 {3 W1 c! x8 Q
    总结
    ; i/ N# T! U! C
    ' ?, H7 u- ]: z# `9 \& S, [: ]本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!: g/ H$ q& @" f0 B$ T; ^1 E% X
    3 X5 ?2 i8 e( i% W# k
    案例源码* y3 r" _. t& ?% {8 M. @; f) w

    7 D7 [# P4 [/ L5 Z5 @9 i* W$ l+ whttps://gitee.com/javacode2018/spring-series
    ) b6 x3 A. V% |  T1 W( n- @6 c路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    0 H# Y8 G8 Y5 p8 A+ P# T! `5 Y7 n$ T8 T' R5 N7 y  d2 B3 ]
    Spring系列
    6 w7 z0 x$ \5 b$ d' Y0 F& i! l& c
    Spring系列第1篇:为何要学spring?% e) E  ~) m" _9 n# @3 q

    . j. d4 ]4 G. f% J/ p% Y. YSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    % L" a6 p5 k6 c/ x- B- i9 S9 s( T; K1 D0 `0 d
    Spring系列第3篇:Spring容器基本使用及原理) [# w0 y: `% J& A

    : K6 p! L6 X# d2 TSpring系列第4篇:xml中bean定义详解(-)
    7 B9 ^$ T6 r$ y' F" h! |
    $ g$ R$ S' {' R% {0 g; mSpring系列第5篇:创建bean实例这些方式你们都知道?
    $ `/ O8 k5 x8 d" T4 f: |( C: r
    # Q- i( y! v) ~" r- Z5 ]( n0 S' ?Spring系列第6篇:玩转bean scope,避免跳坑里!
    . }) n: d6 n$ Q
    & w3 D: r( X% sSpring系列第7篇:依赖注入之手动注入
    5 l- b- L; S  F" `
    5 H. m( X# {. F2 g1 qSpring系列第8篇:自动注入(autowire)详解,高手在于坚持; U7 ~# P/ ]$ k0 N3 X0 B

    . f" k1 q/ X, i+ }Spring系列第9篇:depend-on到底是干什么的?: ?$ ?  B5 T8 z7 r! q* y
    1 W- B0 j; K" b
    Spring系列第10篇:primary可以解决什么问题?
    ' |( C( _4 U4 s' P. C1 n0 I; E" e* s
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?& o  i7 p& q0 i4 j

    2 M9 ^& T7 U6 J3 x2 fSpring系列第12篇:lazy-init:bean延迟初始化
    $ ], _$ U4 d9 R* m7 A8 r  C: O/ W* _2 y2 S% k" F* b: F
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)  g7 F8 V3 L5 y
    % m/ x% N3 b  y1 J2 }
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    9 S! y- Y# p+ P# b7 b& b1 c  Z7 {8 @* M
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    . d- U/ R. _0 y1 ?
    3 c& w+ `6 `' O. CSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)  ?; E' k/ z) T# F$ Q
    8 _( }, R- T: N" D* \% d/ a
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)0 o1 ?9 F- P- j: F

    ' c% \9 Q& x) ?- @5 {4 RSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册). G( r4 w- F# K' g
    5 c4 ^# o$ ?% F
    Spring系列第18篇:@import详解(bean批量注册)( z' t: ]" i8 L; V/ P6 Y
    1 X) x4 H0 C) A
    Spring系列第20篇:@Conditional通过条件来控制bean的注册; L! e2 ^6 G: Q; w2 C" t
    : i+ J' R- @/ K9 [6 h  s
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)# ~4 F, g' r' S6 d( R" X/ w
    4 _! S5 R9 Q6 F0 f" j) G
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解& n: B, ]9 T% b/ h6 K4 M

    4 ]3 `2 [2 v6 u& n: @5 Y* G  WSpring系列第23篇:Bean生命周期详解
    4 b6 n! y; m9 o* p: R* l
    ( R2 V$ q% a$ ySpring系列第24篇:父子容器详解! t1 Z9 A2 G; W" z
    9 T' u( N1 i0 G, D' T: h& D! R
    更多好文章2 F& H4 j- {4 ?$ l

    8 @5 E+ u0 C2 d# i/ A. uJava高并发系列(共34篇)
    ' D. d0 ~) ]/ S% d' ]/ t& z. ]) s7 F: s
    MySql高手系列(共27篇)% w$ `, Q4 {2 E
    : J! q' i6 i# ?+ L7 r: q/ X( w
    Maven高手系列(共10篇)
    6 k6 i1 |& K( X+ n" ~2 @4 ]: e$ g  H
    Mybatis系列(共12篇)" C2 X3 y8 g* B* m9 w( d% \

    ! J3 ]9 ~. D6 e4 w聊聊db和缓存一致性常见的实现方式# d$ n6 r  u: e! _! M
    / f5 P! L1 ?3 I. A. B
    接口幂等性这么重要,它是什么?怎么实现?
    2 e8 f# B7 L8 m# W/ ?4 W
    7 N$ a: P% ]# b2 _泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    1 @2 X: I, O5 {- A4 t0 F————————————————# r4 M& O. C0 p
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。/ w+ w9 ^( u) C" g
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    2 C* L% e, }* `) m1 N; G# y2 m4 W
    " U0 o1 G( Q) c' i% Y4 F. A3 ]2 @6 e% Y$ M5 I% x
    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-18 16:01 , Processed in 0.491606 second(s), 50 queries .

    回顶部