QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5522|回复: 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!4 G; u, C& p2 b  f
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    6 p, ?* \  B  B! [/ H& y: T) i; F+ Y1 |  k5 o# k
    面试官:Spring中的@Value用过么,介绍一下
    . W; l5 J, g3 O" `; f$ `5 l
    1 ]" N/ t" P1 W9 k/ O* E5 ^* y我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ' m2 t2 G4 k8 c: w! M+ a
    + v6 _! u3 S8 z) |" U面试官:那就是说@Value的数据来源于配置文件了?$ d8 t6 @( N1 ?' I
    ( y9 ~1 U( g1 V
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ( Q4 k3 P  N$ `  ]
    - }% }4 V2 \8 z+ q4 i. l' g$ Y面试官:@Value数据来源还有其他方式么?& M4 t! {4 H: p- H$ P
    # @7 a) Z5 N" C2 w( w% u- R/ ^1 t
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。4 @- m- u+ F9 L; G$ p

    - J: q/ E5 K( {3 O3 H+ u  h面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    6 T' P4 J; h" q& _7 u# J. G* |) s( I& C) F
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧0 Q+ o$ W' U5 e% V* p+ h& r
    9 p' S0 p" j$ M) D
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?% e4 i! |/ S: L! }8 b
    # F, B' J' H( n" s0 d2 Q4 T. l
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能8 W2 P  \1 _3 Z: z, ^) {% r

    ; P# A* y3 `1 N' X" v7 {6 p面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?% J2 z* B; Y+ I% X
    ( H3 j% S5 G, x  U
    我:嗯。。。这个之前看过一点,不过没有看懂
    3 R( A  s: k% f. w; J# R9 O; b
    " R! r( Y7 y( ]' _. J1 s+ b% k面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    3 Y8 k/ M9 K1 K8 A0 G% w9 \- b( T, k6 q
    我:3万吧
    1 g; A5 }& @! q4 x
    $ S, {2 S! h; ?面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?* E- w* \- J3 n5 [
    8 f, r1 B4 z5 m- W0 r4 T: I1 S
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    9 i) g$ Y1 `, L  M" g# M; b! z6 o9 }# s2 p6 p, P
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    . V7 b& s6 R  S+ }5 r( H' Q/ Z( i/ K/ D
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。! ~+ X: a4 j# W3 B

    , h# b! ]4 a: O8 h2 s! {6 M这次面试问题如下
    1 g2 g, o# X" L1 q! m! t
    5 r/ q( g3 v% m! M& }, O' ^@Value的用法
    4 T+ ?- |% g* j  C) i/ V" K! r. ^- t/ H
    @Value数据来源  \/ D" x' X8 T( P( O1 j$ F9 o
    . x& D% X! J5 y1 d& u" y
    @Value动态刷新的问题
    % l, Q" H' l6 R! N( {
    1 X0 h6 e7 c& O0 `, E7 x! u3 f下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    2 L) n: P/ c' w- k
    - Y: y% [  p4 b1 O7 K; _@Value的用法
    $ B9 D0 u8 `2 L9 j7 ]+ j5 E+ G0 m# B/ f5 W( ?
    系统中需要连接db,连接db有很多配置信息。
    3 K/ j6 }  A- p9 {* V! S  P  Y+ G9 Y7 M0 j$ @: z
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。$ Z; `7 i# N  r8 p2 Z% G
    $ T3 O6 [0 M* T& @$ n$ _5 M" }! d
    还有其他的一些配置信息。4 n$ q7 v: o  R6 Z$ r

    7 @) z( A% I$ D0 o; j6 V) M! V  I我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。1 n. Q+ U, |9 g5 k+ S) _. [6 j2 j
    ' w: h! S0 f: R" J2 I
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。1 l9 k7 e, [  Y4 w4 z0 K

    & v! ~+ k: N$ j  h4 o0 b: Z通常我们会将配置信息以key=value的形式存储在properties配置文件中。7 R8 z$ ?: g" Q" E1 S

    7 @0 _, }3 m& }7 x通过@Value("${配置文件中的key}")来引用指定的key对应的value。
      z* f6 g$ m; h" r% H0 E' r" g+ \7 |
    @Value使用步骤
    1 z* o; Z/ W. q: j: N8 M# Q& K. S9 t' I( c7 @
    步骤一:使用@PropertySource注解引入配置文件
    , l( m% m% u% D9 e& a* d, x7 C, l0 p4 b/ L/ g! Z& _# r/ D
    将@PropertySource放在类上面,如下8 S7 C9 k6 w+ G. W

    $ E5 O3 U- x7 Z2 W3 Q7 N) ~@PropertySource({"配置文件路径1","配置文件路径2"...})
    3 a; P# t1 r0 C6 z@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    ) c5 q0 ?, h6 j  i- ~9 s3 b% P5 e4 q
    如:
    9 Y+ L6 e9 j9 i6 ~  z7 e; E" P! d4 j# e% e$ ]
    @Component
    / b" O9 t7 d0 q7 I( |@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}); H6 g6 H6 V7 W  V3 z  b8 e# i
    public class DbConfig {. T5 y2 l7 ?4 F, m& o* F
    }$ W1 m4 R' ~- n9 e8 w( L2 t8 x
    步骤二:使用@Value注解引用配置文件的值  }/ w; z9 I7 ?+ A, \% S

    8 ~7 S& K0 ]7 Z& d) }3 u通过@Value引用上面配置文件中的值:
    " i' i2 `" G1 G: ~3 P
    * e% |- w2 K3 M& L+ ]  B语法! y" N) C, S/ z/ q: x  G
    2 @  F# K2 h3 o( D
    @Value("${配置文件中的key:默认值}")
    . D8 u9 H/ r. h- b: t8 B. V@Value("${配置文件中的key}")
    6 L& _  [% D) ?- W0 B3 y, S如:
    ! b, m8 m, |3 _, m
    7 x+ e0 U! q( h, E' r4 k+ P" a@Value("${password:123}")
    0 U2 x$ T7 _, Q$ A; B上面如果password不存在,将123作为值  x% ~! N2 ^( B" i4 }0 C' M" Q

    ( j0 I6 ?7 Y5 l( S# _@Value("${password}")3 d6 o0 t! S, K) A
    上面如果password不存在,值为${password}
    , C+ V8 ~% ?! v1 T9 J
    9 W! J) p: T1 o& ~# i假如配置文件如下/ }. U( w( A3 Z$ w- b. l- \

    1 ?% [1 N. m) j- B* @* Ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    . a* o* n% O3 a1 Ljdbc.username=javacode, L1 o; _3 n+ Y3 ^, x/ ~
    jdbc.password=javacode
    ; D# c- g3 r& p使用方式如下:- D: X4 ~# m" L$ s7 m+ z8 \+ T7 a
    6 D* o7 a2 E2 s, @+ e/ A5 H
    @Value("${jdbc.url}")! h9 z5 ^" W5 Y$ Z. K$ U) L* S* S
    private String url;
    " O' d$ r/ R! N: I4 U8 O
    ) H  K* V; O) o" A3 @@Value("${jdbc.username}")7 z: Z7 j1 `6 M+ t! O: k
    private String username;
      o+ Z" k6 q' {$ \1 @! ]% |" Y- {9 ^# J% v
    @Value("${jdbc.password}")
    9 w3 [2 {& P5 n, `; Zprivate String password;" R: m/ w. D, I! r
    下面来看案例/ a1 O% ]- ]& J7 w
    3 @8 D0 s4 T7 k
    案例
    2 `; Z2 h$ p4 q( D6 w" K
    % p& o8 l" X8 D* h; r来个配置文件db.properties0 y7 `4 ], P% [6 i% F, U
    0 k" S- G2 a8 g
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ! Q9 i2 m$ H7 F. |9 Y2 k7 h2 Vjdbc.username=javacode) r% K6 v! v3 j* v  G$ F
    jdbc.password=javacode& u' {5 h9 t9 z! ^
    来个配置类,使用@PropertySource引入上面的配置文件
    + r- Q0 t+ [7 v  }# Q, B0 Z! N
    ; l1 D; E9 v; R  D* Gpackage com.javacode2018.lesson002.demo18.test1;# h. ~6 u! ~! \
    ( c' _+ [& ~! q3 K8 o9 }
    import org.springframework.beans.factory.annotation.Configurable;
    ( a% W7 N+ ]# x+ D8 x  Mimport org.springframework.context.annotation.ComponentScan;* @2 }: k' L+ F" r; \* k- x& `- {
    import org.springframework.context.annotation.PropertySource;; m1 H; U3 J& }0 j1 a2 p) A" R
    $ G; @4 E) U% J6 S
    @Configurable% N8 T! O/ x4 Y% {3 |8 ]- e8 J
    @ComponentScan( T6 k( t: X, c3 o% Z& y# J
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    3 ^( Z, M# x: ^6 R: Lpublic class MainConfig1 {
    ; y- ~3 g; p5 S8 w# S$ A9 S}9 X9 c+ R3 M4 Y
    来个类,使用@Value来使用配置文件中的信息
    ; C! `6 o1 o  w  |* m7 Q% r
    , i5 g: u" O* I7 I$ m, Tpackage com.javacode2018.lesson002.demo18.test1;
    " i+ Q$ C3 I6 M% M  q/ x/ b7 T% ]  A% u+ ]& R- @
    import org.springframework.beans.factory.annotation.Value;
    4 h" C2 Y9 b) P8 d) Yimport org.springframework.stereotype.Component;2 a; e7 N' ^5 {. S
    , K5 T& a, X+ ]: z5 P
    @Component9 D# ?2 S2 R) V
    public class DbConfig {
    , M/ P+ ?* A7 j' I5 z
    3 ~' X, o+ o1 N    @Value("${jdbc.url}")4 O5 p2 _' U; _# f
        private String url;
    0 F. K/ b4 a3 W! M1 N6 {$ D8 Z* _1 I! a! ~! q- \' ?$ n
        @Value("${jdbc.username}"). B5 J  Q' o5 ?4 G0 d! n; K" V
        private String username;; F3 l$ j; n6 ?, X# t; [

    : B+ r, h- T5 D; T* _+ P! D% o( ~- a" w    @Value("${jdbc.password}")0 ?* u) G  k6 m! G3 I- A
        private String password;2 x; V. d- J$ @4 j) i

    # B7 N% s" Y9 `    public String getUrl() {
    " q+ r/ j; i0 ^. P        return url;
    : d+ |5 _% D3 I6 A, \0 L    }* k9 c; y+ p3 f6 Y) W

    % i- z  E5 a' ^; B    public void setUrl(String url) {
      K# h7 |, _7 }4 G- @        this.url = url;& @9 N3 |# s! f. q
        }; I$ C- {. c3 _

    $ x1 y; _, s+ I3 S$ v    public String getUsername() {3 Y% |7 a6 x( ^/ |- G
            return username;
    ( l5 |6 P7 t! _8 y4 q' C    }, {" v0 i/ _) {" \
    1 V- x7 G6 n3 \+ X3 q
        public void setUsername(String username) {2 `5 a1 v- e. N  Y2 J
            this.username = username;5 ?% `3 I6 I% k, C2 b3 q: O/ T( F+ N
        }0 ~: E7 I/ D" g4 n9 V/ D
    , C, y* l8 N  b# `3 f! }8 V! m
        public String getPassword() {
    0 K8 b+ H- P5 V: p" p4 k& E7 E        return password;6 l6 t( H: y2 D6 G* A3 I
        }
    ' h! R1 @8 }. ~8 A3 [) P- v5 V# y
    ( E6 l1 r* W! V    public void setPassword(String password) {7 a  f. G9 U: q" }% f1 A3 ]; x
            this.password = password;
    $ k( @2 O" \7 x! S$ Q    }; a/ k& U( g& I2 C$ x4 {
    & p0 w/ ?! F3 p* D$ z
        @Override* c: P) W' I& z7 z
        public String toString() {% h4 n8 ^, O- M) i. d# v% w, s
            return "DbConfig{" +* ?. q% O! {8 r" Q/ q3 D
                    "url='" + url + '\'' +/ \6 z0 }, G% h2 b  I; K- c9 W
                    ", username='" + username + '\'' +1 f# ?  x5 Z# Z7 ]
                    ", password='" + password + '\'' +
      f3 h# c' R* [0 R3 G4 f' A  u4 A$ h                '}';
    5 F$ F: l) C$ ], X' g# _6 _    }
    / d1 a  {2 Z" c}
    8 \# \1 x9 Q$ ^$ v. l6 Q1 |. {上面重点在于注解@Value注解,注意@Value注解中的
    ( K$ x4 M* |( B' ^4 L: e! Z! v) l5 D1 I% U' h; O' N7 a. ?( @
    来个测试用例5 ?# \* b0 |- Q8 X1 Q3 @! j3 @! L

    $ @& y( B5 T- g1 Apackage com.javacode2018.lesson002.demo18;
    3 P# b/ n0 F2 n; a, ]! F
    * V  ]& k! f$ J+ A4 |import com.javacode2018.lesson002.demo18.test1.DbConfig;
    ( C3 g9 x1 O8 S' F( S6 Fimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    / A( R* m! ^" f/ w% @& S+ Iimport org.junit.Test;8 j. K9 N. E& Z8 e: v
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    6 _  p& J$ Y' F3 M+ G/ W/ Y3 f% d/ R
    public class ValueTest {
    - D& B1 o) M) D  e$ Y2 W# R+ N3 Z, y9 l! ^( q. E0 `4 P' S$ o
        @Test% @1 m- A. H2 z+ r, T* O; Y
        public void test1() {
    8 W& B9 y$ z2 y  m        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    9 j+ R% X) R, V' d        context.register(MainConfig1.class);
    6 a" w. R5 ]  j$ F% z/ m        context.refresh();! y2 y2 h6 r1 m5 @# s

    , H/ |( \0 X9 M6 A1 ]        DbConfig dbConfig = context.getBean(DbConfig.class);! d# W# {. ]% B) o; N3 A$ F
            System.out.println(dbConfig);4 \) [: b6 Q( m/ e  ~
        }2 U6 p& n; Y0 t
    }
    ! W( m) i( o$ s9 b  h6 }运行输出* r" X3 \6 o: Y  [

    # Y! ^: Z: Y. {- nDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}8 s$ C6 C3 m( |$ t& y: h& @3 U) e
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。" ]# J! T" P* E+ i. s2 H7 |; o
    2 z- {* w( J" P% a7 P
    @Value数据来源! v' x4 p4 S, |2 s, n
    2 H4 K& W: g: O. o
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。2 W1 ?4 G$ c4 y7 ]. t3 g

      E- G; w9 P2 \4 t# {% Y# d  \/ j我们需要先了解一下@Value中数据来源于spring的什么地方。* h7 v2 a  x# J9 ]5 n0 R# b; _
    * O4 t0 @: {7 T8 x: c- H0 [
    spring中有个类& [4 t. `! E; f4 l. t( g+ R+ i: q
    & E$ P: A5 R6 k7 ]3 {( x/ l
    org.springframework.core.env.PropertySource
    , P9 v1 h7 e* I4 G* w8 l. E可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息) l2 b0 @# n2 L2 W9 N# @2 n9 B' L6 U
    % n# {- o& ?" ^3 s
    内部有个方法:" p$ N+ Z/ E- [; c
    ' L- R3 }9 @/ W
    public abstract Object getProperty(String name);
    * P) W% j% r; E- n1 z通过name获取对应的配置信息。
    / c; _0 |" T4 \- F2 M4 p
    . K/ Q- B0 y1 j: N1 s系统有个比较重要的接口
    " y5 S. }5 G6 y/ [/ _: T. L0 h- R0 p; U) S( z
    org.springframework.core.env.Environment
    " y# B6 R6 M3 \# |用来表示环境配置信息,这个接口有几个方法比较重要
    8 u+ d2 {$ _# }* H$ p* E$ b9 T) p$ F7 i, B# j! G
    String resolvePlaceholders(String text);
    + m9 k  l, P! N5 SMutablePropertySources getPropertySources();% y, r! H& ^7 t4 R
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    ) v5 X0 L( h6 j/ L' D! Z  t2 k
    ; i! O6 Y: @2 X7 v+ v& f& U9 Y( fgetPropertySources返回MutablePropertySources对象,来看一下这个类
    4 i8 T. k9 `" B# T, h3 Z( I! i5 i) x2 q/ D, [0 y' a- m6 r  s
    public class MutablePropertySources implements PropertySources {: F! }& ?5 ?- Z0 o8 |

    3 n% \  f* e3 \: ~$ _+ q# U3 G% I/ _    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    / p+ s) k  p% D
    2 U2 ]6 i7 b$ X" p}
    1 k$ K# x4 b/ o- w* O6 j内部包含一个propertySourceList列表。
    3 Y  B8 n) W" W) z& I. T2 o# D' D5 K( y) y) U) r# K
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。; Y# y; R  g" p( ^
    : e, e2 n" a! \0 w$ b
    大家可以捋一下,最终解析@Value的过程:) J+ R/ g2 H/ v/ ?
    : ~3 l. Q1 ]( c8 V  V5 {
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    , a1 y) }9 o/ E$ `  S, u5 H4 B2. Environment内部会访问MutablePropertySources来解析$ |# I: i$ A3 ]  l& d5 j
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值) k" o- Q$ l3 z: s0 ^3 S; M' k( f" g
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    9 P) e# f4 g" U* F/ o8 q
    ) h6 X3 i( f" I$ h4 w* I下面我们就按照这个思路来一个。7 u$ r" T* r% \+ y7 S% K

    - x- S1 A  [+ C# ^9 d- r% Q来个邮件配置信息类,内部使用@Value注入邮件配置信息
    - a" c3 j1 v9 e) P& i1 a5 s
    2 P  F" v  z# Tpackage com.javacode2018.lesson002.demo18.test2;9 j4 o: N; z& y1 @

    . [; L& d- o* j  z, G' U2 E, Gimport org.springframework.beans.factory.annotation.Value;
    ) T& w* ~  c1 \) ~2 himport org.springframework.stereotype.Component;# j: c. D( e2 z! A( Z
    . u: N' G% j' D" m; L5 O
    /**
    7 p3 e/ t: d7 R9 P( W" ] * 邮件配置信息- u0 |& N0 ?0 H% |  t$ k4 s
    */  K. g- H& q3 _) S0 ?+ }
    @Component# n" P3 [7 K0 @- Z  E! Q  D
    public class MailConfig {6 ]7 m+ w) Z& ]! j! \& [

    8 b( o5 m7 a4 N5 a( T: q* x    @Value("${mail.host}")
    0 r6 K2 P& k. e* `    private String host;0 B$ v# d* t5 N3 p/ ~9 w" E7 |( D0 W
    9 w& g9 x% N- I- O& b; t3 j$ {
        @Value("${mail.username}")
    3 d) {: s% [1 J- j9 G' W    private String username;
    ' E* ^8 @* h2 N, M6 k- I2 @4 S" w
    7 c8 q7 n" e. |0 X5 M    @Value("${mail.password}")
    , g, l8 y$ t$ K    private String password;
    4 ^3 @' J2 k- g
    ; B0 s* ?" Y' H* t3 D8 e9 X    public String getHost() {( W  y3 s7 ~5 z! ]
            return host;/ y. i6 K( ?# Q& w
        }& q  x& P3 M! x4 u/ Y6 Y, J

    / x- v& k! z8 M( Z6 n5 Y- E0 W( }# w    public void setHost(String host) {6 W1 e+ ^" V. _) T) s% c
            this.host = host;+ w' ^0 h- w& p8 m' x0 \
        }
    4 P5 [- F. l' s+ i1 Y
    $ |0 v7 z' g- [! @/ ~9 X    public String getUsername() {8 z8 }: L3 D4 E  X$ z' u
            return username;; G1 y8 V. l: l; a, W" b
        }
    * r  g# E, I& A0 M( h
    - E. |& U: U) u" v& V/ o. |& x+ y    public void setUsername(String username) {' T6 M3 x: p' I6 Z1 @5 [6 l
            this.username = username;* `9 n5 ?( _! ?( l& Z  d9 \
        }( z6 z9 c# u( s% u4 L

    / a7 i8 A0 \' L2 F9 E0 d7 P! p    public String getPassword() {/ q' o" T4 _; q
            return password;, _" N0 N% O1 J2 t7 m
        }) }, A) ^1 X( r7 X/ J. I

    $ e/ e$ E% [; z" X. @  h8 J- f% Y    public void setPassword(String password) {
    # T0 f) p1 B% m% g3 I0 c        this.password = password;
    " S6 f" W1 q& t  `3 D    }
    $ d$ r  R6 j9 o5 o  L+ D" t( E+ P  F6 o
    # q& C7 W, t; k  b1 z( V7 U    @Override
    2 c. E. b  f/ t' U9 }/ |: J    public String toString() {9 m) ~$ s. R) o4 q4 u$ ^/ R' \. s
            return "MailConfig{" +' t9 S$ E0 ^6 o7 m
                    "host='" + host + '\'' +( u+ \6 m- V6 K
                    ", username='" + username + '\'' +
    ( z' Q+ c! T3 v7 T/ i1 Q3 X. D3 B                ", password='" + password + '\'' +* F! t: {" S$ i
                    '}';  L% n  ~. u( s5 G! R
        }
    - E1 \$ J- J! W) z+ a}' L6 N9 T% ~" S7 O- _1 {
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中+ O7 C1 V& n# c# v6 Q

    , `& t' f% {. \7 o$ }package com.javacode2018.lesson002.demo18.test2;: ~) E: `$ H( x7 p: S
    2 W7 _2 ?( }5 r, `+ X  G+ l2 J
    import java.util.HashMap;% D: g% l0 V9 h( K# Q& B( |
    import java.util.Map;
    " i- P/ d' b. v; w2 e$ {
    " T0 Q9 S3 k; j! H+ d; _public class DbUtil {5 `) d( h7 V: Z3 d- ?
        /**) ~  g% ]: G% K9 }" P1 V
         * 模拟从db中获取邮件配置信息
    & n0 }0 y$ a0 h& {     *# ]) a8 p8 I, D( W- T. A
         * @return$ o0 V  b9 P5 `5 o; L& J0 K# r: G
         */2 f+ r! [- H  B7 f! p# ^) h
        public static Map<String, Object> getMailInfoFromDb() {7 N" `' w" L1 s  ^& J: y
            Map<String, Object> result = new HashMap<>();' E! j% G6 G2 ?
            result.put("mail.host", "smtp.qq.com");+ |. n; q- P, b# E9 o
            result.put("mail.username", "路人");% F) e# C' Y6 {
            result.put("mail.password", "123");
    ! ?" p+ K! n3 c2 R5 i        return result;
    ! i1 W5 a. z* Y. E$ d( c' M& W    }
    6 g* @4 ?; u0 U( K" r2 H}
    - ?2 S% _1 n1 q+ H8 i来个spring配置类
    8 d  q- r- x: {
    ( t, E+ I8 L7 q5 o0 hpackage com.javacode2018.lesson002.demo18.test2;
    : `/ k. {" n3 z
    % Y$ g5 W! P# himport org.springframework.context.annotation.ComponentScan;
    3 \/ I; _$ }4 O+ wimport org.springframework.context.annotation.Configuration;: T+ N0 n, x0 [: G7 R% O. Q
    + f8 R4 I4 ]& @5 V$ r2 @
    @Configuration( o# G5 I+ H) U& T
    @ComponentScan
    * A. F, k6 s" Fpublic class MainConfig2 {! T9 F  |& p% E) ~2 K
    }) K: _: |; A: \6 x  p, p4 t! ]& @( Y4 B
    下面是重点代码
    8 q6 W+ y: }- i! w6 u" k# ~& o6 A, i' Y
    @Test1 u3 m4 R. L& L7 b1 E( |# l6 e* d% i
    public void test2() {- b+ L% |) z; o! L7 ?5 B: A: Q
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();% U, ?8 Y* H6 m8 r5 K7 r! ^1 r
    + r) u7 x6 z+ J) ~" v( s3 e# ?, w1 |" b
        /*下面这段是关键 start*/, I; ~" H& W  j  G& R5 y, S
        //模拟从db中获取配置信息
    $ b8 v- G2 ?% c2 b8 Z    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    2 W! {4 B2 L* R: O# X    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 f4 U5 A, Q: s6 i9 N6 o
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);0 \1 o% k2 o$ r
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ( y: k5 b, Z% c$ O& ~2 t  a    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);; e8 ]9 @% d: {! P. D
        /*上面这段是关键 end*/! B: T. n2 ~- X. w& c1 y0 l/ g
    3 y1 C7 o" h2 S8 D
        context.register(MainConfig2.class);
    6 ^3 R. ]' B5 |  n    context.refresh();7 B5 \1 X; x1 R  ~: r- P/ z
        MailConfig mailConfig = context.getBean(MailConfig.class);% V( a5 k- w( m% u2 x0 X
        System.out.println(mailConfig);
    9 d2 ~( b" r% A. Z}
    , F5 Y: m9 C' O6 [+ {注释比较详细,就不详细解释了。. ~1 h0 J$ J0 f; R
    ! Y% a$ E8 y: a; ~/ L
    直接运行,看效果
    0 s* x1 O8 Y* S4 Z2 C1 G9 g8 f5 e! H# u- O
    MailConfig{host='smtp.qq.com', username='路人', password='123'}, S8 p% l; s9 |% c7 d
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    1 V+ c( U9 w5 v$ {) x: c; M: Y: Y, K% D7 Z+ ~. o! L" ~
    上面重点是下面这段代码,大家需要理解* q/ \# ]3 ]/ {% r/ n% I5 o

    ' F# _4 N' I( m0 Z  O. \/*下面这段是关键 start*/. y, x5 m+ l& ~( m
    //模拟从db中获取配置信息5 m! g$ A1 N4 j# q3 a. U+ P' [
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    8 ?/ F" g6 b+ w6 x2 |4 s0 b//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)9 P0 ^$ F# [2 S1 E: k6 z) N6 N' ]
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* m4 @& m. W8 O5 v6 s) w" I1 s
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高' N$ ?) }6 Q% }* N" ^5 P, I9 x
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);  s2 Z/ n( `3 x( o6 g
    /*上面这段是关键 end*/( Z6 n& B3 P& W  }$ W8 s# z
    咱们继续看下一个问题2 y; j5 Z* h! ~+ S
    , f" \, i* h: _" a+ r' `6 f
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    0 B1 w. e) w+ q# p  n# ]' \* _6 k* ]: V
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    6 f' b; Y, ^" r* z+ L1 }5 V' D9 X+ @1 P: x% x) |) G
    实现@Value动态刷新
    , K+ e0 N+ ^8 d2 g
    ! N/ {7 D3 j) I/ r- s# z; v1 f先了解一个知识点
    ' j2 Y/ p, c" s0 c( B
      h# k3 D" w5 s/ E  k/ I/ |' ~这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    0 S8 G7 {5 Q  a/ V
    2 H3 \0 j1 K+ Y这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解$ _  R- o* o9 w1 @. I, S
    7 e( Y; ?. c2 Y% {4 P# O) t& n. L: D
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:. p# m5 U6 G) ]7 h
    3 W! T) ?& P6 O* @
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    3 N& d! z1 W7 d5 \2 A9 n, A# Y4 p& v这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    4 Y4 g& d0 g. _. }( I3 D
    4 w6 B+ ?# a8 @' bpublic enum ScopedProxyMode {
    , w3 d# K& s$ E/ x  U1 D# u    DEFAULT,! R! O; g4 j* m4 q; g6 S6 w' L
        NO,* @4 v3 G  T0 r  Q' [, \
        INTERFACES,
    8 Z$ Y3 b* W% a0 L2 @# v1 B    TARGET_CLASS;
    ! r# `* b1 o) K}
    9 `5 w; ?7 c. J* n+ W! ?前面3个,不讲了,直接讲最后一个值是干什么的。0 \; T4 L$ w* K( G. R

    * ]- Z; M/ E2 L/ k& ?当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。# c  r5 j, F% Z7 S9 H; T% s

      X8 p- s5 ]7 v理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。8 @/ S4 a3 W. q* N4 k" E% c/ D
    " c6 r5 h- b9 y2 ?9 s
    自定义一个bean作用域的注解: L! D, Y# ^5 u' D: ~( v4 n
    % y1 P& U' p8 x8 m' S
    package com.javacode2018.lesson002.demo18.test3;
    * j+ v  N- Y5 M+ w% [$ I$ K8 U
    ' ~. O" T2 @. a/ N1 A; l% \import org.springframework.context.annotation.Scope;" }$ v( U# _+ ?5 @3 s" K- g
    import org.springframework.context.annotation.ScopedProxyMode;
    . V1 p2 T/ s8 ]3 O7 j, O. T& ~; P( n* U6 h) O9 q9 M
    import java.lang.annotation.*;8 W- C5 X2 i* X2 N

    1 J& o! Z' S) p/ d9 v@Target({ElementType.TYPE, ElementType.METHOD})4 j) c1 O) H/ g3 G
    @Retention(RetentionPolicy.RUNTIME)
    ! q$ Z3 K4 N0 N, @9 t9 W@Documented
    . G0 o6 C9 {4 w" c+ ^* H5 M7 ]@Scope(BeanMyScope.SCOPE_MY) //@13 N  p  G& f8 [  J
    public @interface MyScope {
    ) X* }8 G6 Z0 H    /**# B- ]. A+ _# Q2 x+ J4 q
         * @see Scope#proxyMode()' h( H( ?$ J4 q7 R- e% U- X* K
         */3 Y6 Y- J! Y& ]- _3 r5 T, M5 O$ s
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    ; w+ Q4 R* i4 i- O}
    - L" @% `0 u) |1 v6 P@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。  q& n0 W9 }* N: T8 z! |9 n
    . Y$ K. ^- d6 k% g/ X
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS0 {0 W+ g0 U) M' O

    5 z" p  k: |) n@MyScope注解对应的Scope实现如下
    ! s! W4 ]# Q# P
    # {& t" E4 ^, r! L" c' X3 z4 D' dpackage com.javacode2018.lesson002.demo18.test3;: M$ R! e+ {3 o2 z1 l: A, }- L

    ( S7 h- M" I9 x" yimport org.springframework.beans.factory.ObjectFactory;/ h& \) F# ^% S
    import org.springframework.beans.factory.config.Scope;5 U' B" V/ q8 L- n" Z: U- X9 b
    import org.springframework.lang.Nullable;) _1 n0 i+ I# F

    9 r4 l& u- x8 [7 S( r( |/**
      Y; e3 z2 b2 z * @see MyScope 作用域的实现
    % L$ `3 R+ A0 G; c1 S3 W */" Y; ?2 d) }$ b5 I4 [. `' w
    public class BeanMyScope implements Scope {
    4 l5 x  R/ ?- R
    ( m# k  n# a: X    public static final String SCOPE_MY = "my"; //@1
    ' L2 t  V, @" ^. M" \5 c" t& M3 b9 x: E$ x' [
        @Override
    ! u( w% v7 H; \0 ?# D! u    public Object get(String name, ObjectFactory<?> objectFactory) { 1 U. ?% T( k1 Q, _! b. {1 S
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2) d2 {7 A! w% p5 Q  l" {( \
            return objectFactory.getObject(); //@3* Y  Z# P  i9 g4 h7 J
        }/ ^" I7 C( C$ |' ]9 L

    ) R0 H* s" A' x3 b: `" m" h    @Nullable
    ; x2 z. Y( i7 m1 P/ ~( \# _    @Override
    " A, p8 Y" a1 s# R. j, u) t, r, v    public Object remove(String name) {
    0 O* _* S3 T4 k        return null;
    / R" Q2 i+ {5 e8 F8 Q    }, T* V- {2 ]4 q- `* F4 c* D
    2 \( \+ I% m, D
        @Override
    $ Y" o( |0 E* I+ Y& B1 E) H& b    public void registerDestructionCallback(String name, Runnable callback) {1 @9 h9 o/ _7 v9 y, o1 V

    6 P; w6 l& r+ q: b& n: j7 ~    }
    & j/ b; z) y# x4 m! s; N( Z0 s. o  t# L2 Z9 \8 B% k+ O- E8 x  D3 R* s& g
        @Nullable
    ) ]" [" `* N- n% j- t" k5 \9 D    @Override3 T2 K" j) [4 C) n
        public Object resolveContextualObject(String key) {
    4 N1 \! ^* W  |0 l! b        return null;7 @+ l! G+ S9 Y) U1 @4 {" @
        }' a4 X; p- O& e( b( B
    ( w. P" f* z: `$ u
        @Nullable
    + x: f; A2 h7 m% n- b2 v. p    @Override$ R6 x7 g4 v+ G  @; q( Q
        public String getConversationId() {0 }$ ^& o! }# V: z9 {, S" D
            return null;) o9 o: B' y: r: Y
        }' P/ N. Q7 N7 i
    }* Y, T: o0 ~) h; W5 Q; A
    @1:定义了一个常量,作为作用域的值
    6 [: S3 t2 l2 P6 R; b7 e1 b' V7 W. o8 m9 r. w( ^5 a
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果% B! [; T+ R6 M) i% i

    " n5 A. |7 q/ U) U@3:通过objectFactory.getObject()获取bean实例返回。
    , j7 @2 Q" C0 r2 [& b3 V
    : `/ D$ ?- I9 `2 H; H* h下面来创建个类,作用域为上面自定义的作用域
    , \' V" \+ Q3 b! |1 P* t/ i- t# }% R. P
    package com.javacode2018.lesson002.demo18.test3;9 D9 c! X2 F+ B8 T
    $ o) o% l$ A* z6 E8 v5 n
    import org.springframework.stereotype.Component;
    5 z+ H/ J6 p! h  f0 ^
    : w  U7 f) Q" F. Aimport java.util.UUID;
    ' V5 ?* N- P% D" d
    . g5 s0 F9 j8 ^3 _/ E3 ]@Component
    3 d9 Y; }, l" p@MyScope //@1
    & V3 H$ }  }- i$ Qpublic class User {
    % x" o, U6 s$ X* |6 |( `$ n
    " o  b9 H1 z* P( t    private String username;' z' e: P( C( _9 _4 X3 z
    6 ]6 I7 r/ Y- d* z, m( m. f
        public User() {
    ! {0 A3 p; b3 u# H3 k5 _. m: y2 h        System.out.println("---------创建User对象" + this); //@2/ ^& \& k0 b8 h/ n9 C
            this.username = UUID.randomUUID().toString(); //@3
    - q: n" i& F  n5 s( D3 I: h    }8 z  c3 }' G; c: Q% S4 s& M

    & Y( {$ ]/ Y* X. q8 L    public String getUsername() {0 T- m7 |; Y: k& z  ^
            return username;
    ' p$ ^  u0 F- f: H7 {    }
    % o& R& O' Z! P9 v) ^' p0 H' P5 b4 p3 s+ u
        public void setUsername(String username) {
    $ U# l* e- w6 f. P) h& O" ]. z! v        this.username = username;- ]6 S2 u7 ^; `8 C& i% W% ?
        }
    / n! _$ Z! p4 p1 O5 ~, u; Z- n: j! r% }- a
    }8 U8 ~8 v6 ~6 F- \! E( H8 h
    @1:使用了自定义的作用域@MyScope
    , |  d' T5 h) ]  ~( h& y& `1 ]% q. D6 q* `; y- ?! Q' ?0 E# X4 `9 f
    @2:构造函数中输出一行日志
    0 h! x, j0 v# D1 l' s4 S
    7 Y3 t# T3 P  a) i0 n$ M( u@3:给username赋值,通过uuid随机生成了一个9 e( O" |/ M4 D# C
    , M9 Y8 b; h. P. f7 Q7 B$ X+ C) L
    来个spring配置类,加载上面@Compontent标注的组件8 t8 C/ p6 F( U2 x# p# n( X% U
    0 t  S3 L& h8 a' s, ]
    package com.javacode2018.lesson002.demo18.test3;' @" m5 {2 \4 ?

    2 J* c6 _- K2 R: _import org.springframework.context.annotation.ComponentScan;
    2 J5 e3 K7 l  x2 _1 S+ G7 M  J- w, Cimport org.springframework.context.annotation.Configuration;1 }: ^' Q8 K8 u) m' c, d. |. X5 I
    7 @. i3 n) |/ \# q, r% I
    @ComponentScan% K1 a& M( f) {2 S$ n5 e
    @Configuration2 g  p" r; X" }* C
    public class MainConfig3 {- t% G0 J- b) H4 {; j2 ?
    }
    1 p7 c! z/ H" ?下面重点来了,测试用例
    ; k; L; o! T5 d5 S. d" `% }3 S# J+ I0 @2 _
    @Test
    ; `% L' P$ q! Y' Q- m& ]public void test3() throws InterruptedException {& _2 F$ }  j2 p
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();$ ?5 [8 O- T% m) @  v9 ]
        //将自定义作用域注册到spring容器中: a4 S: x5 ~0 i/ V. m2 C$ o
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@14 l, \. D, X4 A2 E4 Z6 g, G0 [
        context.register(MainConfig3.class);: X' ]) D$ A& x# @  N6 {, a
        context.refresh();
    0 `, q7 \' m- V$ s! m& A
    " Y; K- s5 V3 Q8 _4 M" k0 v2 T    System.out.println("从容器中获取User对象");
    $ Q5 h# F4 H- m' V# K$ S1 Q5 g4 k8 _    User user = context.getBean(User.class); //@2
    : j( P- x3 b( i4 v7 d/ W    System.out.println("user对象的class为:" + user.getClass()); //@3) ]- p6 n& p0 J* C3 a

    9 D2 q$ j5 A! G% P    System.out.println("多次调用user的getUsername感受一下效果\n");0 _$ i+ m% x; L% G% H
        for (int i = 1; i <= 3; i++) {% u/ ]) H- p2 W+ m8 E3 Q: n6 K3 r! ^
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    ! e$ W2 W: m7 }2 i% E        System.out.println(user.getUsername());
    0 C( a- y' I7 ~        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    . N. d1 ^  P% e0 I    }
    ) x2 R) l+ b) [& h+ ^' Z}2 g0 U7 _" E) e& v$ m) v* l8 p
    @1:将自定义作用域注册到spring容器中
    4 K" n& ~7 Z' J. m( ?
    8 V* s6 l( \; A@2:从容器中获取User对应的bean
    6 x! d5 T. E! T1 a- l$ [) ~- y% o" m( {+ ]) V' }" \
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    9 v4 A( ^9 A! S; D9 k' P/ _# a$ V1 E  U6 M  `! _
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
      D5 j% |1 i5 V
    : y- n! [7 a0 ?0 y  a4 i7 u见证奇迹的时候到了,运行输出. d( c  k" U# y1 O- d. h

    $ `5 e2 i: H9 s: X7 ^' c4 _从容器中获取User对象
    4 s- M- V* @" K" a7 X/ euser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127  i! W4 i) d  Z9 n
    多次调用user的getUsername感受一下效果' K& R5 n6 ~  g3 s* U. k6 Y, a

    6 N. s1 v. i, h8 p! z0 Z. P********
    / |' ~0 Y7 i5 i4 @第1次开始调用getUsername
    6 l7 k+ o5 z( `$ y/ ]4 K* k% mBeanMyScope >>>>>>>>> get:scopedTarget.user
    " V& d# n- W3 \3 _8 q---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    6 k. D: m0 q$ c7b41aa80-7569-4072-9d40-ec9bfb92f4382 B% J: V. |; X) L9 @
    第1次调用getUsername结束) W: f3 g. i$ ]! \$ Y7 [
    ********
    , y* N2 q- S" E( Q; f/ C. a9 V7 K# V9 _: o) W- H+ w
    ********
    + Q- D+ R: ^% G$ L, w3 R第2次开始调用getUsername
    4 R5 B* x7 B  R$ q+ B# r& eBeanMyScope >>>>>>>>> get:scopedTarget.user
    + i+ @) |; L1 I' c. Q1 ~( y; z$ F---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    ) X3 X/ p  x" t' |2 d. j1 P01d67154-95f6-44bb-93ab-05a34abdf51f
      ?" ^4 ^8 D! M: _! b- D第2次调用getUsername结束3 ~" O0 I% S0 H: c4 E, f" p
    ********
    2 q) B2 \/ w0 V! F) y, H4 L: K2 k: v# h8 C1 G
    ********
    2 e7 F3 C6 _3 T第3次开始调用getUsername
    5 w/ S& R. F; l3 e  K* g' hBeanMyScope >>>>>>>>> get:scopedTarget.user
    : h& z( |! F6 e. U---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15, J! ~" W$ M# f1 G7 i
    76d0e86f-8331-4303-aac7-4acce0b258b8+ m; P" b, F0 s+ s
    第3次调用getUsername结束. ?  g$ _8 `! d: R' _
    ********
    1 P  o8 }2 o* Q- W- A* k2 h$ j, Y7 M从输出的前2行可以看出:6 d' F" s0 h3 n  e6 }- M
    6 [% u# d+ R* q# N: s* M
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象' Y! q' k' u. a/ l2 D/ b
    9 b6 W* n$ r% c1 |
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    ! H4 A- X3 [# j9 `  y& D' F1 N' O7 S
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。* B: w1 X7 \' a% L* b
    $ ^+ n/ r/ y  C* J, L' t' F
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    # \# E# p- Q; i- m* |  D9 m' o$ n. H4 A
    动态刷新@Value具体实现8 X/ {3 Z" l6 X5 ]% w1 I8 ~$ t
    $ O: r: A5 R. G
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。! y% C$ }: C' g/ J1 }
    ( @8 p" Y/ H1 {. D2 G) G- z+ b
    先来自定义一个Scope:RefreshScope
    * r# r0 \7 r% j$ O* g8 F) R6 n
      g+ Z  l& E# ]- ]$ \. Opackage com.javacode2018.lesson002.demo18.test4;
    1 j: H- b% c6 _' p! j8 i0 M# V: R3 i
    import org.springframework.context.annotation.Scope;: L8 O) J3 `; `% Z) t/ s
    import org.springframework.context.annotation.ScopedProxyMode;# d* T7 z% d- y# V6 I) J: D
    * P5 [2 G+ q5 c2 F  J7 f% {
    import java.lang.annotation.*;' H& N, k3 ~, ]( @; u3 n( j4 k

      g! c7 G- ^/ J@Target({ElementType.TYPE, ElementType.METHOD})
    / ~. g1 Q8 E8 u. u4 W@Retention(RetentionPolicy.RUNTIME)
    ) n& ~3 U: }- Q2 k+ K# s@Scope(BeanRefreshScope.SCOPE_REFRESH)
    7 i9 f$ j( M4 z1 i@Documented
    6 Z7 W4 ~1 ?2 S2 F5 @/ qpublic @interface RefreshScope {5 {8 B( O( Z2 N) a0 w/ u
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@12 M: T" j1 u& V& \7 g. L
    }: {# n% v( X# O
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    * C' I: x  @* ?9 U+ A
    ; `7 v3 ]* |$ r+ C@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS5 W# B2 `3 u- P/ Z+ d

    ! a" m( [* ?0 }0 {( a7 ?+ W0 v这个自定义Scope对应的解析类
    ( R9 ^+ p; s3 g4 I" M( e, N% L4 N- v$ N; F9 u% \
    下面类中有几个无关的方法去掉了,可以忽略7 I$ q$ N: x5 `1 J8 r

    & k) e8 p7 h2 _  Kpackage com.javacode2018.lesson002.demo18.test4;
    , K3 {% g5 d, r3 A
    8 d3 B$ f  R9 M! H: B0 C/ J) B+ [; k3 n
    import org.springframework.beans.factory.ObjectFactory;
    ( B% j; T+ S; \" b$ f& u. Gimport org.springframework.beans.factory.config.Scope;: ~% j9 p' m2 P! }
    import org.springframework.lang.Nullable;
    % o  k4 W6 N4 D- p( ^/ a* |
    2 j, ~, M2 `  H. s. Q' ~$ gimport java.util.concurrent.ConcurrentHashMap;% c2 C( W1 }2 b7 }) D
    , A/ g3 ~* \& K
    public class BeanRefreshScope implements Scope {# o  y  }5 Y* @7 F
    5 D' {+ G0 s6 K, l" }, e- ]
        public static final String SCOPE_REFRESH = "refresh";
    7 H2 I& T6 N" b9 L, q/ T- A. O  k9 h  L5 d4 W0 y. q! }
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();) g3 \& F. k( t& W* Z/ k

    ) `4 r1 S2 d9 [" x3 E    //来个map用来缓存bean
    4 I* b- p/ f: }, W7 G+ y    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1" u/ i# |" r; i4 Q6 f5 x% w

    ; l# R( V7 @6 q0 m' l    private BeanRefreshScope() {
    , Z$ k8 K' D" Z# p+ Y    }
    ; U+ a% y/ t' M3 n8 s3 Z1 v
    & R7 x9 Z6 |& [% a    public static BeanRefreshScope getInstance() {
    0 Z9 B2 |8 R2 R) _* ]- B+ z# O        return INSTANCE;
    5 e' ^& e) P4 c* T7 N6 U  U4 q: S# ?/ }    }8 V& p5 v! F8 c: G! z7 x" J
    # g. Z4 Y9 U6 s2 l3 n
        /**; m% P  C8 g7 ]' s8 g
         * 清理当前
    ' Y& L7 H+ k4 Z  p; y1 U5 }     */
    " W* i0 Q+ _$ `    public static void clean() {: s8 i' s; h4 J0 y1 m! l
            INSTANCE.beanMap.clear();, {! H, @7 [2 I8 q4 k- K; p- t
        }
    ! r% C5 ^  I3 u- S3 I2 _! m" G2 R; Q3 o( E- X
        @Override
    3 o% c. ?: ^0 m) A" C    public Object get(String name, ObjectFactory<?> objectFactory) {
    , l9 c8 O2 B9 c6 E4 K2 S        Object bean = beanMap.get(name);
    ( M$ o, i( p& q3 C! o; s6 ?        if (bean == null) {
    3 x6 \+ ?0 I& A, E            bean = objectFactory.getObject();
    : x9 t* h; ]' `9 P6 z  @            beanMap.put(name, bean);  m0 q7 h. D  ]& Y% I0 o
            }
      J' }( M# j6 Y+ Y        return bean;# V' _; r: @) _+ Y4 ^
        }' a' ^8 A7 N* Q8 O0 V1 g+ e
    4 q+ ~; G3 X& i# G4 U9 r+ {/ l; S
    }& m  O1 h0 Y) ~1 \  U+ {
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    % ]2 T# n; @# |2 _3 ~% Y7 B* _8 E& i- j  g+ x
    上面的clean方法用来清理beanMap中当前已缓存的所有bean5 C( e: Z  I% Z, H( u9 C
    3 V. K$ _. Q4 W9 B; S
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    7 P% }1 a7 ?1 ^0 o
    4 v3 Z" _( j& A6 J7 x4 Vpackage com.javacode2018.lesson002.demo18.test4;
    / z; _5 I  [4 P  K: h8 y( E( T+ M" u( [. H' O" }2 w
    import org.springframework.beans.factory.annotation.Value;' d9 Q0 C9 \+ d
    import org.springframework.stereotype.Component;( a: J# J! q9 H5 ?  ]9 ~' k

    9 d: m% ~! U6 \1 v/**4 |$ r& B6 I6 q* K
    * 邮件配置信息& F! @9 R! l3 j1 {
    */0 G" b/ K+ z' a# Y# z0 H
    @Component
    $ h5 n) o9 p  N! n5 x6 u@RefreshScope //@10 t# w+ j5 J* `! x. ?
    public class MailConfig {
    / I6 k7 m- ]" a2 R, U7 q
    7 j& u$ G; I/ r4 G0 L- w, u. l% F    @Value("${mail.username}") //@2
    $ e9 O! q. A4 `) M* K4 \    private String username;% P' |2 V9 Q4 E- U$ {3 M
    # m% X/ O: z' c, `3 v
        public String getUsername() {! I8 D1 s% b4 c, i5 F
            return username;, @8 a6 J$ a4 Q, t# r# a
        }
    : F; |; G, F/ \9 h# i+ U- l! C) h! Y
    8 C! D1 i! x9 s+ H0 J    public void setUsername(String username) {8 b- t) j+ T' b
            this.username = username;" j: q7 {# I1 j* ~8 i0 }. w0 Q
        }
    3 H; W6 [2 m) d3 Q# R. h5 G8 P
    * e$ ~0 z9 [# ^& s, ?    @Override5 U; v' F9 P/ [# Q: n: X" j6 n' z
        public String toString() {
    ! z7 l8 \2 p) u5 g, g) O        return "MailConfig{" +" e" Z% e; I: i
                    "username='" + username + '\'' +
    7 F, c9 A* U* m. ^7 I- v$ S! w                '}';  \) B$ G' W* r2 U6 ]6 O- w" z; t7 n
        }! H# I9 f3 v  ?( o1 h
    }, O$ M" U6 ?+ c. C8 r6 `  ^* N
    @1:使用了自定义的作用域@RefreshScope2 g* i) A2 U  Z6 F: M8 J

    6 m+ n; N9 s/ W@2:通过@Value注入mail.username对一个的值
    & Q: T; A! S+ n
    : F: E# E' {& U' c  v重写了toString方法,一会测试时候可以看效果。. X) q) A; d1 ~

    / s8 J7 ^5 r3 ]  o- Q再来个普通的bean,内部会注入MailConfig
    ' [' }7 y" J9 i: n, R! o7 P9 I* b( i7 g0 @2 C
    package com.javacode2018.lesson002.demo18.test4;
    6 o/ g" D) u4 v8 ?# ]/ c3 O% _; Q  a
    import org.springframework.beans.factory.annotation.Autowired;4 H% v& M# \1 d9 t" u
    import org.springframework.stereotype.Component;
    6 v4 u) H! M+ {
    + ^( c$ J, Y' n( W# T% U6 S@Component
    5 ]1 r) I% @& l. H5 F; J7 e  Ipublic class MailService {/ f3 h: O+ [$ j
        @Autowired
    # p( W3 z- F) b5 G    private MailConfig mailConfig;! I2 Q0 @0 R3 M

    : M. g$ z6 D- t$ G0 p& }( B5 s" a    @Override/ T! {6 N6 a; {, {. S! E# o
        public String toString() {
    ; T# u! ^* I2 r9 m6 G9 Y+ {, I- D        return "MailService{" +
    . t* U' {& a% B                "mailConfig=" + mailConfig +
    % Q5 q+ Y; ~; u* ?$ M( y1 B                '}';
    5 [8 s- w9 ?4 n/ v    }
    / w/ L, d! h) X; f. p}
    & b* K4 Y: X8 u代码比较简单,重写了toString方法,一会测试时候可以看效果。
    6 I9 H: s( e. ^; q/ v) w2 g1 X, Q: c- M; {3 r: K& |2 d1 H
    来个类,用来从db中获取邮件配置信息
    ' ^0 r/ C4 w/ F" b
    + C8 h$ I: r1 P7 m$ rpackage com.javacode2018.lesson002.demo18.test4;
    7 Z6 g* d* G+ f" t& T! d# |
    % E7 D" x4 ]0 P+ X5 S0 y. o$ simport java.util.HashMap;/ m! T& ~: _- E! E
    import java.util.Map;
    1 m- k: L* D; |9 w; H4 o0 s% w2 _% Kimport java.util.UUID;# ^2 w. R% |5 l, c9 E

    " s1 @# }* g- {& J4 Bpublic class DbUtil {
    : p# e( Y3 a' u7 G    /**
    6 N; k9 I3 |) Q( k     * 模拟从db中获取邮件配置信息) C! s' O+ d+ V
         *
    ( G/ c8 d' W2 I' |     * @return9 Z. C8 z2 s& E" u" w4 z1 m1 N+ z7 @
         */8 s- N$ l% m/ X  ~, u# B0 g' p
        public static Map<String, Object> getMailInfoFromDb() {
    ) |% K( X$ ~: h! S# w4 U: h        Map<String, Object> result = new HashMap<>();
    8 i5 _3 Y0 n0 A* t. |. m3 N! {- o        result.put("mail.username", UUID.randomUUID().toString());4 C& g, ^0 h& \9 X0 S3 V0 o# N
            return result;. V# S; V2 I9 s0 H, c% H
        }5 U& d/ z0 ^7 F$ F4 ~
    }
    4 |- |: C# L3 v: Q! a来个spring配置类,扫描加载上面的组件
    ) k- v7 B8 u2 K$ f+ g% Q$ [. R7 p) Q$ m3 ^
    package com.javacode2018.lesson002.demo18.test4;
    * S, ~  _& E" A  b( ~" `9 z! m
    " Q) B* U- C& Uimport org.springframework.context.annotation.ComponentScan;/ m; ^1 @3 M1 V* {# H
    import org.springframework.context.annotation.Configuration;
    , t1 K3 F: B& x1 @) r; g6 b
      v1 l+ ~# L, Y0 Y@Configuration; q: [4 f- S+ O8 U0 R: r/ i6 o
    @ComponentScan* p- Z/ ?& @! V3 s6 W
    public class MainConfig4 {0 n! w- ?* m. U& W4 U
    }  d; H0 ^- z+ O+ r
    来个工具类+ ^8 p/ E: ^" l, N6 C7 L6 l
    : a" W* d/ i4 ~
    内部有2个方法,如下:
    1 R" Q2 y" s6 k6 D4 X9 v- h
    . _/ h( U  S3 ?( e! N0 T) ^+ d6 t6 P2 qpackage com.javacode2018.lesson002.demo18.test4;3 A5 U0 r/ |& Z% F

    ) N* `' ^5 s- s% Fimport org.springframework.context.support.AbstractApplicationContext;& m& L! m2 L. m1 n( |* D2 b2 ?. o
    import org.springframework.core.env.MapPropertySource;
    - q0 f- T7 M  Y7 Q- R' u5 U* }7 i9 M/ B6 ^
    import java.util.Map;
    % X  g3 M9 F( O8 ~! }/ Q3 ~, F6 f% q' |/ Y# `$ r$ S
    public class RefreshConfigUtil {, I0 o3 |: t4 r% K
        /**
    ; v% y* V% p" r; m1 C+ i) {     * 模拟改变数据库中都配置信息( s" z$ K: ~( B' f' {' I; G
         */
    / s% \4 i$ c5 m6 h3 _7 {    public static void updateDbConfig(AbstractApplicationContext context) {
    8 t: I/ \6 Y0 [4 j, F0 ^0 M5 I        //更新context中的mailPropertySource配置信息
    $ U; V8 h# \" \9 l+ l        refreshMailPropertySource(context);" V' O- K* k( Y, z2 G7 J/ z

    * s# r8 c8 q7 @4 F+ q9 e2 `7 g        //清空BeanRefreshScope中所有bean的缓存% R0 H& A- ~/ C/ c+ N
            BeanRefreshScope.getInstance().clean();
    - }; d& t' q, ~. _* v- D3 ~    }
    9 X6 p+ x6 F" O: h! ~1 J
    : \- }, I8 q) ~7 h# t0 c% J* W    public static void refreshMailPropertySource(AbstractApplicationContext context) {8 f5 j: e, Z: Q$ o/ k
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
      U& M/ R) q0 P1 o; }# u- v1 A        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)4 }9 M3 f% e, L/ O% E
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);, L3 A& A. h# _5 l0 ?
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    6 x% }+ V9 M# L& i    }
    8 a( z! f$ A' q! t# c9 |: Z, p$ S9 V* R0 r# _& A
    }9 _& o' ^# ]  B. o4 d  a
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息: r. V4 g% e5 X4 n2 G

    1 d& u! D# d. U9 eBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    ( k' Y1 B% Q) L/ Z% z. t3 m2 v
    5 [- p* S8 g! O/ ~6 v来个测试用例- I1 p2 A& _% ~! q% x
    1 ?# U: j) o. Q, a
    @Test
    / D$ X) w4 z5 w* kpublic void test4() throws InterruptedException {
    % f5 f: \2 R1 A+ _9 ~2 a    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; J5 h1 s0 o( |, j$ P    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    3 J; E5 B$ B3 y" m. u! c    context.register(MainConfig4.class);
      v! s$ m8 H0 }8 ^    //刷新mail的配置到Environment
    5 s- y- J4 M1 v$ u" {    RefreshConfigUtil.refreshMailPropertySource(context);/ ]( ~  S$ K+ f: }( V. ~0 j% D
        context.refresh();
    . p* D" d& f% b* U; ~5 |7 u/ v( x5 X6 k/ P
        MailService mailService = context.getBean(MailService.class);$ S5 Y! J6 z- J
        System.out.println("配置未更新的情况下,输出3次");
    : z) `1 p- h3 f2 K    for (int i = 0; i < 3; i++) { //@15 Q4 t/ a2 }6 @% u
            System.out.println(mailService);; R8 v) k8 n- E# e0 w1 M) R. M
            TimeUnit.MILLISECONDS.sleep(200);8 i8 u6 R  p0 c1 q0 y3 e
        }
    + V" p3 Q1 o& z1 t1 w
    * K5 W  i+ u& W    System.out.println("模拟3次更新配置效果");0 _+ g5 a4 L+ L
        for (int i = 0; i < 3; i++) { //@28 `- w/ v+ `' ~1 S% U/ p. Q
            RefreshConfigUtil.updateDbConfig(context); //@3% F- S8 N+ b# K5 E
            System.out.println(mailService);
    ' C( p- u( D7 U6 J6 F. B        TimeUnit.MILLISECONDS.sleep(200);
    2 I# j* P2 J6 j" D# \0 @% f4 M: G    }% d/ j; A6 l. j1 b" ~+ F
    }* l( P( V2 P, K+ f8 N
    @1:循环3次,输出mailService的信息" ]( \6 M4 l. {* H! x

    0 f# F+ }! _9 K/ Q$ q@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息$ B! y( O& |* ]' D$ R7 p2 y
    5 S6 Z2 f. c. ?8 i
    见证奇迹的时刻,来看效果
      N; v2 l# W7 Y6 o
    8 ^# ^+ K( n' z# g7 M5 I配置未更新的情况下,输出3次
    # K7 A* i. o7 F; v! g: k1 g7 H  b# ~MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}- Z( X. i% s* ?: W
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    5 w) B8 i3 o3 ^+ I' Z0 ?+ O+ d0 a; {MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}% W0 f( w& k% Y4 H8 e
    模拟3次更新配置效果
    ) i0 B: g% U' d9 W1 q+ M1 v, TMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    , [* c- c2 c8 s6 ]9 X1 c" _, CMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
      n2 H8 o* q# n' o, O: {MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}/ P1 Z5 [1 U! p) {
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    / @0 N  t6 [7 D  q. ^0 g8 c# }
    , @2 y! `' R# Y1 n" S; b小结
    0 h! {6 w+ i$ U" A2 e
      w2 P6 W2 i. M8 ^4 z# Q( T+ ?动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
      K! F% J+ _9 ~* }- C2 `) u! v
    % B) V9 v, Q- z. L1 T6 P. R有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。. u! v" X  f8 f, b& ^

    , z( A; j& M+ K总结3 g* O9 E- y  J( W+ d
    7 O/ ?4 ]9 I( F. H0 z9 n2 T
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    , O9 L8 L* H7 P$ h2 A* n$ g; s; V: v! d: p% s$ K, M
    案例源码; i8 W' m7 a( T4 V
    % J" |) B$ p# c7 a
    https://gitee.com/javacode2018/spring-series) ?+ U, T, \- j+ Q! Q
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。  ?/ S0 L* R8 S8 n* O) G0 O
    7 f) p. l+ E5 H6 C
    Spring系列
    / a/ \' F6 `& \9 s5 ~, T0 r. n5 L1 @9 c, W
    Spring系列第1篇:为何要学spring?
      l; \6 ~/ P6 D5 x+ b1 r
    # h7 a$ l+ @6 l) s2 ^# v4 eSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    3 e+ \' L. Z; w4 R5 q& D* r* K" ?* q" @7 a
    Spring系列第3篇:Spring容器基本使用及原理+ C4 O3 j% D% Y0 O7 D

    % |) t! ]6 O3 O2 t9 R, zSpring系列第4篇:xml中bean定义详解(-)9 \+ o( Z" x  y0 U

    $ g. |# w  X$ CSpring系列第5篇:创建bean实例这些方式你们都知道?
    / i9 @2 K# i# O8 |( U: x4 c$ [; q
    # v8 f- |" x. @9 n/ u4 n7 Y) a$ HSpring系列第6篇:玩转bean scope,避免跳坑里!
    & G) @% ]8 u  U; n9 ~) ^
    . o) T5 x2 ^2 O9 |- W8 I) k7 wSpring系列第7篇:依赖注入之手动注入
    ! e- Z4 P! G# Z8 f
    8 J3 E6 _. R, z: I- sSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    1 I5 G' f4 |. u1 f7 F5 P3 F  O+ p0 `# T4 ?
    Spring系列第9篇:depend-on到底是干什么的?
    ; w& d3 e9 x! g2 w' M4 I1 a
    . n1 Z: P4 |1 i* y4 L9 ?Spring系列第10篇:primary可以解决什么问题?, d. c2 ]5 b  Q
    ) @/ {$ G! f7 U% M
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?+ m" W) G# Y2 [! M
    6 S+ _9 ~6 p' q; v0 q
    Spring系列第12篇:lazy-init:bean延迟初始化
    9 r& D& [% K  E4 f% R% s( _3 S! ~. D( x' W
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)$ \* x* x' x9 {) w' B

      _! `! t6 y0 @4 X" V2 L5 W* F/ d+ BSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?* H8 z6 P1 @1 M* ^; U4 M
    $ ~2 C) W' A- f9 j7 }" e
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    ' K! U; Z  A9 d, n
      C6 j6 R1 p/ x3 u. @7 [Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)  H: T% `, L( s" J$ M
    8 h+ L4 f+ b3 @: a1 z' p4 O) T
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ' G: R& j  V7 Y0 G; {3 u" F% x1 K6 @: M5 N. y
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)4 G. ~( u! E3 p4 A) {1 F

    $ a" v; w4 }  u$ Y* O5 qSpring系列第18篇:@import详解(bean批量注册)
    4 F! K% D  F. S+ @5 z# s/ P# c  [' K9 ]3 r& q2 J
    Spring系列第20篇:@Conditional通过条件来控制bean的注册  z- ^( u- D; l8 E+ y
    ; B) m+ }0 s1 s* I4 c) z, t
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)- P2 w: P/ t0 J6 m
    / T$ O/ N! t! k" @! v$ F3 G9 o$ ~5 c1 r
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解, M8 i( H3 {0 O" J; M

    6 L$ k% ?4 }0 S$ k4 M4 E& A6 TSpring系列第23篇:Bean生命周期详解
    # P4 v; M) f5 s+ ~
    * g2 D) z7 m' b* m! ^Spring系列第24篇:父子容器详解$ Z, E: F, M9 K0 w+ {

    5 A0 g1 N- |8 O更多好文章; f( ~' L2 j, Y/ U
    5 |: ]- p, q- {7 v* o) K7 ^* T8 ~
    Java高并发系列(共34篇)
    2 }3 `' I& C) M6 Y' ~1 N% ~( t" ]9 _0 L" x* Z" ?
    MySql高手系列(共27篇)
    & R2 t% k2 J+ I! [- V" T, J* X( \* v+ u* P
    Maven高手系列(共10篇)
    8 h- E. F( n7 l9 k) ]
    $ ?3 N; k' i5 h9 mMybatis系列(共12篇)
    $ z' I1 {- U% e8 p; ^; ^1 Z9 e' @8 A& v3 x/ P5 _% M* O
    聊聊db和缓存一致性常见的实现方式  F& V0 ~! R' P3 r
    9 v3 t# @6 y0 z1 f
    接口幂等性这么重要,它是什么?怎么实现?7 l7 ]1 I; r: f7 w( G7 o4 J* v& o

    % [9 [8 [* b" A( z7 l+ A- E" e- W* @2 v泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    + \2 n$ J, i7 Z————————————————2 R0 W( h1 L; x) F  [8 a
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    & E( z- @% W, R原文链接:https://blog.csdn.net/likun557/article/details/1056487575 ]. {1 C( h; N% _2 P" A+ q
    , h' h6 m9 \7 w" `' N+ N

    # W3 T$ F$ j, p% a/ a( M3 H5 n* T
    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-7-26 22:06 , Processed in 0.457410 second(s), 51 queries .

    回顶部