QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4319|回复: 0
打印 上一主题 下一主题

太狠了,疫情期间面试,一个问题砍了我5000!

[复制链接]
字体大小: 正常 放大
杨利霞        

5250

主题

81

听众

16万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2020-5-23 11:01 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    太狠了,疫情期间面试,一个问题砍了我5000!
    8 [2 w/ C" u2 w$ s疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!. V2 ^' b5 @' Z9 ^0 {& l, M
    ' B- y4 {+ X! \( Q
    面试官:Spring中的@Value用过么,介绍一下
    1 E4 s  G/ D* V- P) [
    - G; \% P( U' x1 E/ e9 n+ F我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中* ^& G; b( m% Y, b: L6 o
    % x$ X8 `% T, S3 L( |  }, U3 w( h
    面试官:那就是说@Value的数据来源于配置文件了?
    ! m# V6 c2 e# m8 {; X
    & P  m! \2 _. K' {, k我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置5 ~, V7 C% c7 {$ ~# a3 Y9 L

    + O$ R/ Q0 t* J) L) \面试官:@Value数据来源还有其他方式么?
    + E# Y/ l0 y- u/ {) i1 u4 z/ A
    5 {+ f- A1 F( ^我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。0 c/ v3 h6 \' y$ E
    * ]5 w# p5 ?# i8 u7 t, E: ^# _
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?! y- x3 u$ K) o

    - |& {/ i0 ~+ ]我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧. B: T. E& y0 ^+ F0 l# S! O
    : q* Z# ]1 k( P7 A. ~/ x' c
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?; k2 i, t: l5 W6 d* W

    0 k2 i$ S) x4 C, Y; W5 N我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能9 J- E( r% c4 }8 R1 g$ R6 B0 |

    ' @! ?7 y0 p% P/ M& s0 G面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?. X$ I' D+ p9 a

    2 E+ b* R1 d9 J2 q我:嗯。。。这个之前看过一点,不过没有看懂
    / U5 _1 w; }% l: w, o
    % l6 |* l$ z  O面试官:没关系,你可以回去了再研究一下;你期望工资多少?3 d3 y+ r4 Z: ~" ~3 j8 D

    # i8 t+ j. s% K( [1 o' Q我:3万吧$ e: O% A" z2 I1 I- C, \9 G& d
    : `7 \6 }9 j) x1 r, d; S2 d1 O/ _) F
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?+ }8 i9 \9 ~' T7 [5 |

    ; l7 b3 q- q" O) g$ k. ?我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    ) D+ F% `1 ^1 U# I" A3 ^( g% A* x! v0 u: f" h; }
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!  n, g6 o# K8 B" h: A' ?
    : q7 R" k# j5 N. M: Y- k6 m4 S
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    3 A$ _1 i# p4 v2 A, j# i8 J: y4 }. \4 A. x8 _) a
    这次面试问题如下/ c5 F1 l+ M5 U( F6 ~6 ^4 y# t
    # U% X1 ?) u3 t7 |
    @Value的用法2 T% A4 o0 ^/ @4 s) b/ [
    . [' A5 I6 r9 I0 J$ U
    @Value数据来源7 [1 g* p2 U/ o0 `7 R/ X) ~

    " x# E: ?. d  R) \0 c5 R@Value动态刷新的问题% @9 O. t4 F! J

    6 s! _& T# N- q( X* N; q下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    , ~5 ^) i' O4 M
    - q/ w3 A5 R6 Y7 z9 s@Value的用法6 Y; T, k9 g) d' @/ \
    * f" |7 ?! }& r8 E% L6 K
    系统中需要连接db,连接db有很多配置信息。) u' ^: C* }$ Z# c2 l
    + f% H! `( y$ V3 n0 y* u9 n
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    5 n; `/ D6 k! K1 S" D% V* i( U
    , H* k' A4 ~' y还有其他的一些配置信息。: e( K% Q4 X3 e" t
    " A, B0 z$ m( w1 i. Q7 r3 b
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。3 i: z8 u" Q$ q, C

    9 d/ k! O/ K. U那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    ! ~5 V3 l3 H6 G0 ~! B7 u
    7 L3 _1 v  v9 T0 M通常我们会将配置信息以key=value的形式存储在properties配置文件中。* @5 _- a  ]. Y4 U. O( S  M+ J

    + z, w( F* @  P& B, h通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    * n  p% `* l+ W4 t9 y2 B
    ! {0 _) N6 ~' m" v5 B  o@Value使用步骤
    * N& y5 B8 y: L+ f- T& u
    % |; }! n: N3 G" Q% Y步骤一:使用@PropertySource注解引入配置文件
    # ^0 o2 F/ S/ B: v/ O$ `( j( H
    2 U0 V& F+ c: K6 j; n将@PropertySource放在类上面,如下5 ~1 l! V, o) R9 b8 b5 J4 k

    ! B& N. C, b7 ~# b1 x@PropertySource({"配置文件路径1","配置文件路径2"...})" Y. p7 J1 W! G, p9 }7 a% A% N
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。1 u9 O  v+ A. ?

    ' D  g% O( F# b1 s1 E3 w: `" _$ w如:/ i% I* ^1 X, l- G) N

    ' b: i9 U9 b' G" ?5 b; J: b5 E@Component
    - C1 ]  O4 v/ t4 m$ m) b@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    % R7 n  w1 x$ U) jpublic class DbConfig {3 Q. x7 o) M7 v5 J
    }1 l+ V( U* B& _) V
    步骤二:使用@Value注解引用配置文件的值( O/ ]% C  L+ V. B: j4 Y

    / \& O* t* R$ ~; W1 [2 W通过@Value引用上面配置文件中的值:
    8 L7 x: O8 L: S3 `7 o' E% G. T; P( c1 H0 Q% s0 b5 K. J  A7 S
    语法
    0 d$ i# ]' k  U- ~
    4 B7 |. X  o# _% v: v! K2 d@Value("${配置文件中的key:默认值}")
    ( I! N; ?+ y$ p8 b% K( K5 `. k@Value("${配置文件中的key}"); \( u9 l3 w4 u) B( e- X
    如:
    9 Z; R; @0 n" |/ }$ _- [5 }
    1 n4 a2 G$ z" Y0 L! N@Value("${password:123}")
    % ]3 {1 h2 m* A6 J3 d6 h上面如果password不存在,将123作为值2 B" u) y* H: w

    6 P( X# l, ^9 K( S& N1 }@Value("${password}")
    ' J$ s3 b' n4 O3 n上面如果password不存在,值为${password}
    # m/ G; p$ c6 q& n& z
    # n8 O  a) S: m0 ]: w" Z; _& ]! f假如配置文件如下
    , p6 u' u; O) ^* F( Q* ]" C1 y5 u3 ?1 X  p7 U6 ~9 ~
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8, `  U( C! t% K+ f/ Z3 h
    jdbc.username=javacode
    ' ?# ~# }  l( Z& g4 Rjdbc.password=javacode
    # i' B8 `$ _; M, P" C使用方式如下:% J+ C( Z+ [6 c7 m

    4 M  m8 N* V( k6 |@Value("${jdbc.url}")' a0 q5 `; N% e8 U
    private String url;
    & ]% A" J5 s- d, I4 v) P& g! V  H- h" o
    @Value("${jdbc.username}")6 n8 `6 o/ T$ G& R. B5 M1 _
    private String username;
    " s( O& y* i- _' ~7 J. W8 v+ d* W- }% L
    @Value("${jdbc.password}"). C0 M2 W4 @8 @6 I
    private String password;
    6 X7 x* _8 `) [6 h, i7 Q下面来看案例
    9 p( g3 `$ a" {0 K8 j4 c! Q
    % f$ v- V8 N' u8 K, n+ h6 _案例" h  T/ @, Y& f. l; W% ^/ z- r' W

    , d, E0 E. t5 W: O来个配置文件db.properties4 J; m+ h* j/ w1 i, c: `; U
    $ K# H/ N) i1 h7 r0 y+ u  h) M
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    , y+ j1 B& I) X: u4 Y  Bjdbc.username=javacode; M6 M6 T# A0 z% m/ S4 v. ^, L$ w
    jdbc.password=javacode
    * E% \) p2 S) m& g* d! T! T" q来个配置类,使用@PropertySource引入上面的配置文件
    ) i: H& d6 a! |4 h* K% R
    3 Z+ V+ K% N. J( Kpackage com.javacode2018.lesson002.demo18.test1;
    : `( I* ]/ T, q+ S* b$ o7 R  [, l0 q+ I
    import org.springframework.beans.factory.annotation.Configurable;
    - c( ?$ _4 |& ]) M, ?; Qimport org.springframework.context.annotation.ComponentScan;6 v* ~0 l0 X/ A
    import org.springframework.context.annotation.PropertySource;3 H& u- [; k! G- x; M
    + P5 i$ R* K- j0 C6 W0 o/ t) L
    @Configurable
    * x. R7 D0 z& `7 I4 w9 J+ |@ComponentScan
    ! T: Z' Y3 W) }* k. R: x" K- O@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    $ }/ H' r' a$ L+ D5 S( n( Wpublic class MainConfig1 {
    + k; r7 f0 ]! @+ |& F3 S}
    ) i8 n$ a+ h; c: _/ Y来个类,使用@Value来使用配置文件中的信息9 P/ u0 J3 D! t6 @. z3 v) _
    ) u( h. W2 B0 o& j
    package com.javacode2018.lesson002.demo18.test1;! j( F- X4 h0 C

    2 @  U5 s' K# R9 Yimport org.springframework.beans.factory.annotation.Value;& G0 j: c# \; ]6 S  B0 P
    import org.springframework.stereotype.Component;
    ; y4 F8 `3 w; m! H  n3 O3 T" `9 n! S3 q# a
    @Component
    ; p; ^/ g8 E1 B/ npublic class DbConfig {* q' B, T0 t6 T; Z, s8 E/ o' H

    1 ?6 ?, q1 w4 {6 l, s    @Value("${jdbc.url}")
    % F! c) y9 x7 J9 ^; j/ ~    private String url;# U; y0 i$ K2 |. v& g
    % u% ?5 i, r" G
        @Value("${jdbc.username}")$ k3 _! W" _+ ?9 p, `
        private String username;
    / Y% e( ^7 E- O8 z2 b+ U
    ' S" P) f0 J+ n* O. W, o3 N    @Value("${jdbc.password}")
    , R0 B& T# J8 P+ k# C    private String password;
    2 h: a% j- f* M, H& c2 C0 D7 T6 d9 X! H2 b
        public String getUrl() {
    5 Z3 I4 J: T" g) `( D/ F        return url;6 W) ?0 C6 i, \
        }* }% l1 m2 ?5 s$ V- N  X
    : v9 \: m3 L% ~  S8 ^& b
        public void setUrl(String url) {
    & e" f0 v: h  Y0 Q; l        this.url = url;. {* l4 G7 I* y6 r
        }6 z) C- o4 l/ A6 y$ ?

    5 k$ l" ~" i* \3 N; Z    public String getUsername() {6 S! @/ v( s) k1 X; p
            return username;4 z$ X) X9 S2 E5 X1 U1 o
        }
    # N% ^  z9 O. o( d0 m- ~2 x% w' f. m' M" F& e* F( i
        public void setUsername(String username) {
    $ V) x  s) `6 x. M7 ]        this.username = username;' w; b. H" [; e( T7 V4 I( P  Q
        }' P& h+ H1 K8 g' @/ m! N3 p  `
    ( E% @7 ~6 c# c% W4 ?) Z
        public String getPassword() {
    % k. I; l0 \2 @6 m3 Q+ p        return password;
    " j! }+ ~/ K6 s7 f. o. A1 q    }" h/ L0 x$ f0 ]+ A! V, w
    ) [! q! Y4 }+ o' e1 _; r6 z
        public void setPassword(String password) {
    $ b/ H# i/ z% D# M) S$ _! d        this.password = password;6 P8 i0 i7 w# ]& u- b9 k6 g  x
        }1 s2 s) Y# ]% ~* Q8 ]3 H
    ! O- z  l3 c: L! s) U+ [. {
        @Override) q8 G) l3 ^3 G6 }' V
        public String toString() {) S! K; ?3 m$ o
            return "DbConfig{" +
    ! O: q7 q& D, k0 l+ Q* G                "url='" + url + '\'' +/ z& g8 M2 s: d6 i' D7 b
                    ", username='" + username + '\'' +
    . q' b& q* C6 P( l  X                ", password='" + password + '\'' +' D! {" K8 W" w/ n
                    '}';6 o+ w  X% ~- N  s. {# y) y
        }
    $ `: L+ J6 ^. T/ D}6 |( q/ A1 M) a
    上面重点在于注解@Value注解,注意@Value注解中的
      z* L  D: J3 g$ Y4 l* f* M7 v; i& e- l  e& ~
    来个测试用例8 V. ^% H, W9 \5 c1 Y9 w: }7 r/ z

    7 _% M$ E9 e  s( m  I: G6 [package com.javacode2018.lesson002.demo18;# P" `' k  w2 v# Q  Y& s% q
    4 m5 k" ]: `. ~- m
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    - `$ V" N  q6 P, }import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    # C3 z) g1 R. ]% y- Rimport org.junit.Test;
    2 r& p( h& ?7 a+ S1 u. @import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    , g  t7 O2 C2 G4 ~0 Z' {: z3 X/ t2 {7 S
    " X# j: {8 K8 a, y6 g' Lpublic class ValueTest {
    4 i- A0 T! c: M: B, M  ]1 J; W; J4 K
        @Test
    $ e! a/ K) B+ l' b. ^  q' }) n    public void test1() {- I$ T" T% g( S" B
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    # l* R2 x8 `) G$ U9 W* N- \  }& ]        context.register(MainConfig1.class);% v1 E  L4 x& z
            context.refresh();! V* t! `1 y9 [, i! e7 J

    . Y+ {" n; L7 d        DbConfig dbConfig = context.getBean(DbConfig.class);
    9 u7 }# f. {9 c* m0 r/ o  ]        System.out.println(dbConfig);- h. @5 }+ q3 N+ J
        }
    / g. p% D! j/ A% D, u. u}
      S' R- q1 U3 z  C" Q2 x3 I' ]运行输出
    3 A4 s- h9 c1 Y0 m/ B; S/ e8 \( T4 O
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    6 A) u7 h8 C5 t上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    6 N6 Z5 @+ S$ n7 P. X  @/ Z  R- W- ]0 b) z- E% T* i
    @Value数据来源
    * y1 L, _' ]/ P* B5 N+ S7 j
    : X; o! h1 U/ n1 n通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。+ P9 v) ~4 H1 `
    2 l# g& q5 Q0 }- O# H
    我们需要先了解一下@Value中数据来源于spring的什么地方。1 |6 n/ l. ]* _1 f$ u! u
    5 O' x  Z8 ^) l3 X+ c; @5 E- K+ u/ H
    spring中有个类$ r/ t- C/ a8 M5 M8 w

    , [* U; e! z: y* Y$ ]& z# M7 d, @org.springframework.core.env.PropertySource
    0 }2 V! D# X, _( S0 ?1 Y" |5 K可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    ! e% _" t  C+ x2 I: c( \7 Q
    7 K8 H2 Y* l9 a8 r, p6 o  t内部有个方法:
    % C: K7 v2 s2 k/ J4 ?, o8 s0 j
    / Z# G* U$ O, _1 ?2 jpublic abstract Object getProperty(String name);5 }6 G9 t5 W9 Q8 T8 s
    通过name获取对应的配置信息。. m1 g7 T* }/ Z9 i

    8 m- O" W  `! N* ?/ ~& e系统有个比较重要的接口
    & F+ d2 E" R3 ^, `& Y% e$ r  ?: R! K3 W- \$ n6 |" M$ h
    org.springframework.core.env.Environment3 U( y/ p& p6 ]* q  s. h
    用来表示环境配置信息,这个接口有几个方法比较重要
    ' _) P& g: F- {7 z' I: n
    . u% M- a2 N! o  @String resolvePlaceholders(String text);  \) {- s8 B; ~
    MutablePropertySources getPropertySources();( M" T0 N& L7 n% N; G$ N
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    8 L5 f) Q6 d5 E9 f% c: z: _% u
    & n2 T& p  P: \! v3 S7 egetPropertySources返回MutablePropertySources对象,来看一下这个类
    6 s. Q( c7 {, C4 V9 o) f0 b" |4 S0 w) v/ M6 W- l; r
    public class MutablePropertySources implements PropertySources {
      d" t. T) f5 H5 ^4 c
    . d$ q( u' r) Q4 j+ E    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    9 R% _0 _' U; v4 Z5 H, b2 w) t0 ^, }, Y
    }6 H4 f0 `: ?8 D% I
    内部包含一个propertySourceList列表。
    " `* a. [$ L0 U! T8 j9 h& q- N2 W: V) ?4 b5 {/ Z% S1 E
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。+ [" e/ l# z) e: ^' o

    $ l3 F  w& r" l% f1 d- D& G大家可以捋一下,最终解析@Value的过程:1 W/ [8 y! A' d: m0 p& X

    1 b) |/ t9 I( l8 t" Q% |3 l3 J1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    3 J; j( R; r: G1 y0 Z6 d2. Environment内部会访问MutablePropertySources来解析
    + m: q& X4 r; j& m3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    8 s0 t# o* D/ O+ x通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。& P/ w: i$ @! ~- e1 S, d
    - @9 B& b1 Q4 u4 o% ?
    下面我们就按照这个思路来一个。
    : C/ J% {3 f0 D' L. z: |+ a1 Z0 n3 T3 @' F% K! y) s
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    . h3 N6 i" {+ U8 P- c. J8 v' S
    # X: [. s+ s( a, Cpackage com.javacode2018.lesson002.demo18.test2;# g: i" u  J0 b- y) n

    5 Z' \  J1 S7 N; V3 B1 ]" Wimport org.springframework.beans.factory.annotation.Value;! f. z3 d; C) }6 e/ O2 H3 m
    import org.springframework.stereotype.Component;
    ! V0 V) `+ P  \' ?
      X. h" h( `1 l! y6 @% g& ?9 ~/**  A- r* Q+ Y5 H7 J% J
    * 邮件配置信息
    ; U0 {5 ^8 i. k0 _9 @ */0 E# D5 B) o) {
    @Component  ~: e/ b0 A6 r% y' z$ D3 r
    public class MailConfig {
    6 E3 c6 b9 {( f; d+ ^  u& `8 ]& i9 S- N" T9 U& N% n! B! R$ h
        @Value("${mail.host}")8 h1 [& I" ^; \; l
        private String host;
    ( d3 `$ B( ]* U; h4 V. w1 V& C  X% i8 q. V: a- D# J- m5 F, @
        @Value("${mail.username}")
    - m' Z9 {% ~7 _) {; |: [    private String username;
    ' w7 j# u- u* r5 j, i% Z5 `. ~/ c3 P" }* t) J9 _  Y
        @Value("${mail.password}")
    : d2 u! ^) M) W1 Y) K    private String password;
    ( V" w. u3 Z) z2 j# c; L) N9 @. y& I/ P8 z' h/ K6 f/ U" m
        public String getHost() {/ F1 O1 {/ n# [, E& b, L
            return host;
    7 z! @6 V: f" [* G3 O# a# \    }) u# @9 Y- R) g1 l! Q0 j$ ~/ a
    1 d4 n4 q* R* E! v/ e: B. D
        public void setHost(String host) {
    + T5 w: P, ], e- d# D0 Z        this.host = host;  Z2 O5 \  j- F1 `0 x3 ~
        }; S, i$ E9 e. S# ~
    + d+ R9 [2 e; ~- [- O, J6 l
        public String getUsername() {7 g, I" F6 g2 j+ ^: Z1 Z$ c' e; P. K3 R8 \
            return username;! q+ c2 z$ j* i$ [- |
        }0 Y+ w+ Y) }0 ?- Z: }, g6 [9 _+ c' n

    . l( r) m5 o& F5 h2 a8 A    public void setUsername(String username) {
      ~' E* U" O. s" s% i        this.username = username;$ T4 B) z1 J( B+ Z2 \0 G1 g, y, d
        }
    ' J& f6 X0 Y' F: Z* y6 }1 z9 q) L0 s% S7 q1 `1 E) ?, i) Y1 H7 B
        public String getPassword() {7 d* A! x' ]% z
            return password;. F4 P0 p, C% d# l+ q8 p
        }, s  S' K7 c& A" ~

    1 P9 q2 i  k: E9 U    public void setPassword(String password) {
    & w' \; x: i' R3 P# {2 F( c4 D: i        this.password = password;; ]2 _# `. W  D! \1 Q
        }
    8 Z( n0 n) b/ S9 T5 ~5 [8 p& V4 C! U" A# a2 I  @
        @Override
    , M; N" m5 }4 ]# v7 T, {    public String toString() {
    . a+ w1 U; j% d: U        return "MailConfig{" +1 \  E4 Y# S$ u5 b) `  P' R
                    "host='" + host + '\'' +
      V  Q) r/ q7 K4 V& W4 A$ p: s                ", username='" + username + '\'' +# q/ h1 N4 _" p+ z
                    ", password='" + password + '\'' +3 Z( [2 P8 I+ D5 r
                    '}';' L6 X7 p4 u( F
        }' ~" W( k5 v% ~6 n  s! A
    }
    6 A! z* g% z. X" Y* \% k% s+ R+ R+ I再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中; A3 f6 v, R7 D4 `: [
    ( ?, q0 t* W% {! T4 [9 @
    package com.javacode2018.lesson002.demo18.test2;
    7 \* N' Y1 c+ V3 Y: @( }- _5 B
    * |$ A8 [" }% u3 X! ^import java.util.HashMap;5 d( K$ ]' ^. s& w
    import java.util.Map;
    8 l0 ~+ W& n* u* S* X( g" e" H# F- _/ h2 O+ a$ K/ G
    public class DbUtil {0 y* N3 v$ H- G2 @: j9 C/ X
        /**
    ; e' n: e- S6 {2 \5 {     * 模拟从db中获取邮件配置信息5 B2 J8 U+ h) w! [/ m
         *
    2 c* H8 |9 F- i* y, z9 ?     * @return
    % H3 p4 @( C* H8 ?- V0 z     */5 T9 g- H: {  r6 p6 d9 C- ?. d
        public static Map<String, Object> getMailInfoFromDb() {5 b! x! @9 [+ T; v, R! E: j5 Z1 r! V: O
            Map<String, Object> result = new HashMap<>();6 P9 O/ b8 C! q- y- @# {& V
            result.put("mail.host", "smtp.qq.com");" @, y# P8 {  w" ]/ o) E
            result.put("mail.username", "路人");
    5 R, E# @2 R8 m, L- U( v        result.put("mail.password", "123");2 N: ^2 ^! ?( e
            return result;
    4 T, B( f9 `" Q7 y- m# p6 E; B% I    }; O# ^1 ~/ \) G4 B9 R8 X' d3 J
    }- P7 _" t4 e: n, ]4 \: f) _
    来个spring配置类
    5 c- [8 o+ g- [/ T* A  P& U, l0 ^# |" Y* c
    package com.javacode2018.lesson002.demo18.test2;
    ' z9 _5 y, I! n" F
    0 {1 Z. @8 d6 _- X) }1 o0 Aimport org.springframework.context.annotation.ComponentScan;
    5 a2 }4 V/ p, A: E6 w: b8 D, A0 `import org.springframework.context.annotation.Configuration;
    ; g  s8 g+ `  p1 p6 E7 s; t2 m  k" }
    @Configuration# t0 J# Y) L2 B% t4 N
    @ComponentScan
    ! R+ S" s: w1 b+ q( N5 Dpublic class MainConfig2 {; q/ V  N/ V- g9 k2 s
    }' i1 e5 E' ]! d* j& z/ S
    下面是重点代码8 F: r' g2 o7 z: T* D2 x
    6 L0 m3 c, I$ ]* `, Y7 w
    @Test
    * [, r- ?; k# L1 P- T) Opublic void test2() {3 W0 o. \8 m+ }+ z
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      i1 D8 h* f/ k- Z2 @
    . m6 P" k  z, Z    /*下面这段是关键 start*/6 r% N5 @  ?4 ]7 q3 h, R$ w; ?; r' H
        //模拟从db中获取配置信息0 C2 a" Q; r7 i; v( Y* T" @/ M! V
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    0 v/ U# l! _2 o    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ' |( h4 X; m: Y! e/ |3 K6 j2 R+ ?    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ) d+ f! a) X# _" A: y    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    $ U1 ~5 B$ Z' N! v' v  h' N    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);) V4 ?) S$ T2 m7 T$ R" ?5 M+ l6 k6 n
        /*上面这段是关键 end*/7 z% T! K( e' \! M
    1 E7 n. R5 c" X, d3 X. N( |
        context.register(MainConfig2.class);
    0 |5 Q  O9 S' y. r1 e# j    context.refresh();
    2 g% g9 p9 H$ u    MailConfig mailConfig = context.getBean(MailConfig.class);4 c6 _# l4 e9 I, i
        System.out.println(mailConfig);
    6 V2 C) Q6 l2 P, ~8 U( O}) ~( W7 Q% L& K% |' v, |3 c
    注释比较详细,就不详细解释了。" R  V" ?; E! R# o; J- }' h5 I( |6 y3 ?
    3 `* @8 n4 A, ]+ g
    直接运行,看效果
    & A  L8 m! [% C: u" W; `9 `" J% D9 ~5 u
    MailConfig{host='smtp.qq.com', username='路人', password='123'}/ i( G; c) A' F- v, e
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    6 }" l' n8 Q3 @, Q! t% A
    ) `+ g/ H6 ~) o3 b# ^上面重点是下面这段代码,大家需要理解
    * g9 {7 u; t8 x7 B
    + Y+ P" }0 T: C( B/ t  D/*下面这段是关键 start*// d5 P& r8 Q! [0 A+ @
    //模拟从db中获取配置信息
    & G, f9 a2 u' x( OMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    8 k% p" Y/ i# j' h% z+ X//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    % f* }# @  f$ S, A" C4 J) \MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ! B4 a# d( N: l- S' y//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高% y0 x* X7 _' B& n+ [7 I; N6 o0 P
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    & @& l& k) Z! @$ ^7 S% F8 P  E/*上面这段是关键 end*/% G7 o& W! \) j- ?8 d! l; q: M
    咱们继续看下一个问题  C2 @0 a7 ~3 P6 D, l2 {
    2 u1 F4 Z- h$ r, J6 c: q
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。7 q0 {% ]. i( h/ Q3 i

    2 @  L* r+ i6 r5 W, @@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    : ?4 A1 ]# E" |1 j6 Q
    ' M) s' w- i  t" a9 k实现@Value动态刷新  f: |8 e. t: [! ^" U

    ; }0 U- @1 o' Y2 b% Z# ]3 Y. @7 b先了解一个知识点
    0 v1 @2 H# {% r- I; R9 r9 C' M% c+ w2 h+ |' o: q' {. \
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    7 ?0 \4 b) O. Z* N2 |- W6 B1 y$ B
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    ( _" `; C; a* q$ `2 `8 ?
    2 g* S, d1 e1 ]; X- ~8 V" t! obean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:) X  i# ~7 u4 W8 D/ M0 K) v! H- b
    , a) C1 m5 L- u/ H
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;! P. x0 A6 h& N) j
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    2 T/ p9 t4 ~5 ]: V, I. c& I1 M) L
    public enum ScopedProxyMode {+ B  E' X5 D8 d' t# D% N: x' {
        DEFAULT,' Z- w8 z, y2 k# E& Q
        NO,
    9 i0 N* I+ p* p9 Y3 y    INTERFACES,
    " m+ t5 I# x+ ^$ z+ R' j/ N+ \# J; m    TARGET_CLASS;
    , j  ]% Y' C; X' D, o4 B1 f) B}
    " w* Z) w$ p; b. d* M( ]. W; B前面3个,不讲了,直接讲最后一个值是干什么的。% C) ^. k& n# j) a5 D

    * J8 g! f3 K% I) P! A当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    " w6 K" T$ t/ o% q6 H0 n7 |  `; Z1 ^! _/ A$ n) A3 ~. X
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。) X/ S& \+ E6 T

    * x; l5 o  R- K2 p5 y3 `; t自定义一个bean作用域的注解
    . c+ Y& k& g! r% i* |8 v3 w& P8 P5 o* e+ ?3 a7 [# w
    package com.javacode2018.lesson002.demo18.test3;5 j; y5 j  n, O) D: V- \

    ) p# H9 ^* t& Z* ?' `import org.springframework.context.annotation.Scope;( i9 `( Z0 P( F& _* g
    import org.springframework.context.annotation.ScopedProxyMode;
    ( N8 \0 n6 C  K' N5 k% T) {" g4 I5 {3 s# o
    import java.lang.annotation.*;
    * ]- J% ]% s. H/ [% b2 m0 L+ @5 `* a3 J
    @Target({ElementType.TYPE, ElementType.METHOD})
    ; g" ~8 W4 |# p/ ]7 K, \7 u@Retention(RetentionPolicy.RUNTIME)
      r8 C- V, i& t3 Y/ C* v9 F@Documented
    6 j5 ]  h9 L$ _, b+ X@Scope(BeanMyScope.SCOPE_MY) //@1
    4 A+ Y( u! X1 j! n/ ]3 rpublic @interface MyScope {* ?1 I/ l1 n. Y# w- h/ s
        /**
    : o! h" ?& C3 D: W! G4 z% E  B     * @see Scope#proxyMode()
    3 Z  {& T% @' M     */' J% H2 y9 |. D2 [: C2 z1 w& \
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2* p: `7 z, s" X, i% O! K. W
    }
    / Y6 @6 n6 A) ^' J5 H  u; T@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。/ Q% n: i& Q0 H2 d2 M
    / _, L2 X; p' q5 d
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS0 Z8 L& r6 y& s7 Z  d& v8 T

    ! h5 s$ c; l( r1 T8 Q: i# }@MyScope注解对应的Scope实现如下( a1 L1 F  b" K
    : V1 E" R3 X+ T6 W' m/ o( E7 k
    package com.javacode2018.lesson002.demo18.test3;/ E0 K4 R; x; V* {7 B: Z# u! i& K& X
    6 O+ q2 {' _! s7 }! {. m" V4 [, T
    import org.springframework.beans.factory.ObjectFactory;4 p- R" V# T/ u" [
    import org.springframework.beans.factory.config.Scope;
    5 U1 g# r! m  x0 A$ uimport org.springframework.lang.Nullable;
    # d$ Z9 ?& g) N; `) F
      x! p6 B/ t" p. f9 f8 @* h: Q# O/**8 F+ M5 R6 t5 `3 l2 c/ w+ ^
    * @see MyScope 作用域的实现9 f6 [6 N' p( R: d  b% _
    */
    0 G+ R8 ?0 `% f; h' p3 @public class BeanMyScope implements Scope {6 A: Y" u% _+ ~/ J2 ]

    * M0 Y  u. l5 \) i  F    public static final String SCOPE_MY = "my"; //@1
    * _% p, h; R$ w2 M+ c
    % F. l& _1 T& b/ c" K: q1 z    @Override/ t# m  D' z  Q( e8 R  m  b
        public Object get(String name, ObjectFactory<?> objectFactory) { 3 }0 z: H, o$ [
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    & t5 J$ E6 ^8 e, C  C6 S        return objectFactory.getObject(); //@3& x. L. z9 {" b: J7 Z$ B) [6 w
        }) H& n9 g4 v6 Y) p- S9 h; Z4 h1 b
    & {4 I; q* T; @* e. M
        @Nullable% X: k5 b6 [* e+ p3 G! K
        @Override6 X. o- I7 m, |9 R2 h* |! \
        public Object remove(String name) {
    9 e* n' I7 O& k  O# h        return null;/ s9 y2 ^# p8 O+ S2 ]
        }
    * ]; {& {7 `, B5 Z2 R
    % }" `1 g! L& x- J6 M/ E    @Override* U  y7 s, ?& m# q$ o- [0 j
        public void registerDestructionCallback(String name, Runnable callback) {
    & v/ e: D& X. N( w5 T9 ^% d+ G0 R' j. r
        }
    , B# `- i4 Y, x; T& W4 V& Y, K) E$ @
        @Nullable
      Q1 d' C& N$ Y7 N$ M7 n    @Override' E+ Y3 h2 c+ z( N& O
        public Object resolveContextualObject(String key) {
    ' e* |0 {& s/ l1 L  }. b# |2 K        return null;+ s' o. C2 p+ v+ V3 j
        }
    6 Y& H: C4 k) @% ^' k
    & n: P0 r" @& ^( f    @Nullable
    / l: [& |4 K/ i    @Override
    4 p+ t" z# H9 G- S& |2 E. J3 D/ c    public String getConversationId() {4 C: I5 C+ z. i9 U
            return null;, d9 o: I# M' |3 R
        }9 Z8 u- a: d; T. X5 c1 I1 o5 x
    }. c& b1 y; {% t" A9 }: Z
    @1:定义了一个常量,作为作用域的值2 A2 k2 k8 n, K
    & h8 H9 x6 d8 w: _" J' R3 k
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    . c( P( S/ z1 O/ [' U3 B
    ) L* |' s* D/ N* G0 G@3:通过objectFactory.getObject()获取bean实例返回。
    5 u/ v2 y+ P8 K4 [" A/ t0 S
    $ r: x9 E: l0 m下面来创建个类,作用域为上面自定义的作用域
    # x: x6 _8 \, k% T* \5 ]1 D& }5 ~! D" r" f" N
    package com.javacode2018.lesson002.demo18.test3;
    8 d+ W0 N/ R7 Z% c. A
      r9 J+ h6 c% V" himport org.springframework.stereotype.Component;
    - y# J" p( q. e3 B) t3 ?$ O/ h' f+ ^  o' f3 L/ X
    import java.util.UUID;
    4 c, i$ Q: j/ w2 q5 S2 Y0 h$ ^" u! a# x9 m) J0 S6 @3 o+ P
    @Component! f- m9 [8 J. \& ]; y  |
    @MyScope //@1 0 r0 z+ v/ t6 O8 {
    public class User {
    ) u) S- s6 q3 r1 w" o* c% T, i0 l+ H) X  s, Z, j" B
        private String username;! s4 v8 \. E7 f0 _) h- |
    , S/ q8 e9 D2 r; m! w6 t% f  Q0 w% J
        public User() { 0 r2 f7 R2 s# r7 m
            System.out.println("---------创建User对象" + this); //@2, ?$ ?. J/ Y3 v: U# M( `
            this.username = UUID.randomUUID().toString(); //@3
    , |% d1 z1 x& x+ x3 C    }/ S' u+ U, B/ ?  p" H" Z6 S
    $ }, \5 U7 U2 W" W9 B
        public String getUsername() {
    8 f% b5 W& W5 B+ x: x        return username;
    - u$ X& }( t# ^# a5 T    }. Y6 I& M: _& V" w( W
    5 R) d( G# z2 Z5 ~0 `
        public void setUsername(String username) {6 v( q% W; p, \0 d% n7 l
            this.username = username;9 q; a, h  l  X, I' J# I7 h  F
        }) \+ H. q/ A2 j; Z
    + o* h  b  T, Y5 Z
    }# o( ^( d* m$ k* _5 K; E
    @1:使用了自定义的作用域@MyScope/ F7 s7 v4 v: a: v: E: O0 K
    + c& ~% X0 x7 u' `
    @2:构造函数中输出一行日志
    ' a. R% H! z7 f, D
    , h' T% G" Y& H9 b. f  r@3:给username赋值,通过uuid随机生成了一个
    7 G4 C  M0 O% @" Y
    - _, e2 a+ `3 B. |来个spring配置类,加载上面@Compontent标注的组件; g! W5 E$ O% m: t# D
    : i2 k( u6 a, [
    package com.javacode2018.lesson002.demo18.test3;) J0 L5 Z6 [/ r6 w- Q& K

    / ^- d- ~: |' Cimport org.springframework.context.annotation.ComponentScan;4 }1 Y/ H3 _6 A( [" Z; e
    import org.springframework.context.annotation.Configuration;* m* ^0 d/ \1 }3 G

    ' S4 m/ A/ x' R8 j3 k" ~" }@ComponentScan
    0 p( W  o5 D9 M7 n: A" E@Configuration6 y8 D  X/ }# a/ O! o
    public class MainConfig3 {
    - x4 s* i8 ?' r}( \, C% K1 R3 p) Y
    下面重点来了,测试用例0 H7 a2 K0 M- v5 H1 I
    ) o% P# ?: k6 t$ L) H8 h4 v  `( m
    @Test
    ! i& s% Y; T' g, \7 Mpublic void test3() throws InterruptedException {5 S! P( \# R/ N' ?" w' J
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    / @, ], S+ T. D3 P2 K4 Q$ ^    //将自定义作用域注册到spring容器中2 S& o7 C# h8 U" B
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@15 H7 t# w/ G! D1 r' H& d
        context.register(MainConfig3.class);
    / A6 W3 ]* F% D# N2 M" l& `. S6 l    context.refresh();( T6 ]: N# x6 R' A
    ( b, V; B' i3 d& ~3 x
        System.out.println("从容器中获取User对象");
    0 |9 X! I, a. C( d    User user = context.getBean(User.class); //@2
    # Z% C. B7 }. X    System.out.println("user对象的class为:" + user.getClass()); //@3  D3 I) Y& w3 J" G

    : N9 E* @8 E/ B/ n% a$ M    System.out.println("多次调用user的getUsername感受一下效果\n");
    . b6 Q' y9 q/ c& s& B$ m3 x/ t    for (int i = 1; i <= 3; i++) {6 D5 _$ f- q) m! Z" U9 y) }! J
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    / v( x( |8 M( F2 y        System.out.println(user.getUsername());
    ( a. `1 M  r' t( J5 ?( U: D; Q        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));4 z  C- \" g/ V/ w) _% D& }
        }
    / ], X. A4 d+ [0 n5 P2 ^5 w4 K9 v' I}* H! D( ]5 V* g/ J
    @1:将自定义作用域注册到spring容器中% Y5 r/ l# `5 m) }* J+ h
    6 m* U6 s- @9 S
    @2:从容器中获取User对应的bean
    # X$ V! Z3 v) w. h8 r6 U# y6 K7 p& @7 z. X
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ' T. W9 o) E- X" m9 i# y6 h7 T2 K! [2 @* W! u7 i% F# E) t% N9 M
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。( R" a3 w* o6 i# i5 W9 F3 `- |  h
    ! N' [$ _1 d0 h( V# d- z8 l
    见证奇迹的时候到了,运行输出. y' W0 m9 k4 i, r

    0 r3 Z! k5 \- E* W从容器中获取User对象
    6 p1 ~& W. p+ yuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127+ G/ x3 j8 {: B
    多次调用user的getUsername感受一下效果8 y9 Y- l4 d2 q4 M# W- i+ I
    3 W" d' b9 c! @9 c" ~
    ********
    5 \) n5 e" T: q5 U$ q第1次开始调用getUsername
    ! T' q# l. {3 k8 e6 D2 GBeanMyScope >>>>>>>>> get:scopedTarget.user
    1 U, W; L0 E: j6 r7 R! N* A3 P3 b3 u---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f44 M: A1 N9 J- B
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    ' B1 B( j1 q* b第1次调用getUsername结束
    8 |8 M% t7 A7 j. D3 J' s* y+ B********
    # S  A! N2 X" Q# b& ?4 z; h' Q5 k& @7 ^7 P/ B5 i
    ********- [5 A4 \% K+ q; @8 A9 `. l
    第2次开始调用getUsername
    - o0 U9 H/ i; s- d6 y% W1 SBeanMyScope >>>>>>>>> get:scopedTarget.user
    & p/ v1 D% ?. |5 N---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    - g( z- x% ~3 J8 [6 K; |+ ~. ^01d67154-95f6-44bb-93ab-05a34abdf51f
    / R& x7 X" R0 l+ o" T: g- |5 [' W第2次调用getUsername结束& f+ V) i/ G7 x6 l  T. `
    ********7 j* C0 X* ^+ N1 p. P! K$ j3 Q
    : N2 R/ R: X2 ]& x
    ********
    9 M% X+ |6 n- N+ D第3次开始调用getUsername
    " @" j  `, u9 s8 G7 RBeanMyScope >>>>>>>>> get:scopedTarget.user2 W2 X+ W% ^; |# `
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d156 w& b# q0 W7 ]( R
    76d0e86f-8331-4303-aac7-4acce0b258b82 U! Y) t9 z" A3 Y6 P/ m
    第3次调用getUsername结束
    & S2 y2 u9 y& T7 `# t1 b********
    5 Q' F6 a3 V- p9 p  w) X7 k从输出的前2行可以看出:
    # B  {3 H7 B+ N, Q2 \- l) d4 H" \$ R
    ; ^6 n" Z0 w# a) F) E调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
      _8 w$ o$ K& b7 Z1 q/ }( r# D
    % u% D$ I) K- T, j/ |; x3 Y第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。" j/ C+ D% Q0 |7 W1 P
    , _% O* J6 E4 d% p) s: D: z( J# h4 P
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。6 E! M8 N  u( O' _8 R" B

    ' j4 p# G5 e# [( i' y* K通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    ( ^9 r" B- x. V% y7 T, L+ p; m/ _& O% k
    动态刷新@Value具体实现% F: Y) M  ^# O: C8 g4 H

    & ^8 h5 Y% Y2 u0 N0 ~6 h7 @) j# j那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    ) B: l3 O6 O+ S6 t+ [$ H
    6 [* o# ~% k! M" v" `0 q0 G先来自定义一个Scope:RefreshScope" h2 U, x0 e# I0 l' D

    & P' e% N6 k7 M% }; X/ j* [- Cpackage com.javacode2018.lesson002.demo18.test4;
    6 N5 h( X5 N. F  g1 w) K8 w
    & H* L0 J1 |) c. ~" L/ himport org.springframework.context.annotation.Scope;1 L# g! a6 P8 ~) ~% u% K" n/ O
    import org.springframework.context.annotation.ScopedProxyMode;
    ) J( s6 B) O. K
    9 v; E; ]. P+ w4 G4 eimport java.lang.annotation.*;
    ! `/ |* O' {7 X8 n. Y* P# v  A$ m/ F) N# S- y7 u
    @Target({ElementType.TYPE, ElementType.METHOD})0 {' A2 t/ u( \
    @Retention(RetentionPolicy.RUNTIME)5 X6 q# K1 v3 N7 t) ^. g
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    . [; D& G# r* i4 u" J" O0 F0 @@Documented
    * o& j" o3 y  \: v. [0 ~8 V0 dpublic @interface RefreshScope {
    8 e1 W, w2 y; N$ t  a: ^" Q0 f, h& d. [    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1  g  x0 o% l5 J
    }
    ; v7 n7 H' w$ R0 p' b要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    4 N" d& _6 ^$ r' ]- B& s- L) H2 S" M
    6 l7 K  \9 z! H2 Q@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS8 |. S) z1 j/ Z" m, |' c* L2 C
    9 ?' J' D" q8 K# x3 p
    这个自定义Scope对应的解析类
    ) a/ u( ~% o9 e( c* i% S9 M
    . T- U: ^2 Q* w4 m下面类中有几个无关的方法去掉了,可以忽略4 i( H7 p. W6 ~4 x- V6 F# M

    4 p. U4 P4 h: U0 }package com.javacode2018.lesson002.demo18.test4;
    & _: M2 J; W# _8 Y9 m  x  V) }) i  J- F6 y2 u8 t# i4 K1 }, F

    - E3 W5 y8 {/ a* ?import org.springframework.beans.factory.ObjectFactory;( J6 G8 X" |  e* p6 s7 X2 }
    import org.springframework.beans.factory.config.Scope;* Z  r/ F6 `) N4 @$ e
    import org.springframework.lang.Nullable;
      f: ]. V) T/ w5 O
    $ l+ t  S: Z( o  Rimport java.util.concurrent.ConcurrentHashMap;. u% I3 I3 M+ S0 l

    + S7 _4 ^! c( a/ S* ]/ D) Spublic class BeanRefreshScope implements Scope {
    6 f# d  X  B6 l  y0 V( M: Q4 v
        public static final String SCOPE_REFRESH = "refresh";/ ^1 V8 `6 d! y1 G: b7 X

    . H* G4 b" y# E, z' `. l" \% i    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    ( O, T7 J5 {5 M3 A' H) \  p
    / q8 x; j- ]& p% I( H; ?    //来个map用来缓存bean
    ! G' j" O$ I! w+ a* o* f    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    . o2 Q3 c) F: e9 A7 L3 B1 Q% b& R' G& C
        private BeanRefreshScope() {
    ' a1 x6 f3 k0 q) E, r/ \    }) K3 l/ |2 l$ x' F/ t5 M3 Y
    / M4 n7 S' u) \! b3 u$ s/ V; w8 H' [
        public static BeanRefreshScope getInstance() {; C, i" [3 s& d, h
            return INSTANCE;$ R  u+ F$ ~: K
        }
    ( D! L0 u. d% }0 l2 `7 N4 ~
    6 _4 l2 a4 v. `    /**
    ( i( z5 Z+ Q& I. m$ v5 g3 [     * 清理当前
    1 J; P$ Y3 e" y( {& b& y" a     */
    $ x6 s6 Z3 [: G' b    public static void clean() {
    - [- I) n' G  [        INSTANCE.beanMap.clear();8 S8 p4 y$ u. v) s- {" B
        }4 ~4 }5 @- D1 J; G  u5 [
    7 p2 o& J( Z8 z; N% p
        @Override3 Z  C# C; W' V) M
        public Object get(String name, ObjectFactory<?> objectFactory) {. w0 C& n5 F0 r" _
            Object bean = beanMap.get(name);
    . h9 p2 v! q% k, |        if (bean == null) {
    2 E/ l8 ~/ R) U            bean = objectFactory.getObject();, L7 s: c6 _# S' c
                beanMap.put(name, bean);
    ( z$ C4 J. ?$ C( k9 Y7 ?        }
    / P6 h/ u4 B9 b2 d        return bean;
    6 w' d3 K0 W9 }7 s" \! ?    }1 N" @0 M6 N; _  f5 ?9 J
    ' w. I6 F; ~4 ]8 E
    }
    ( r. \6 V, V' O4 }上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    7 F7 H2 r& F3 X2 l+ P$ l) E3 A# e# c2 [3 N5 p; v- b' y
    上面的clean方法用来清理beanMap中当前已缓存的所有bean: v- H, L$ O3 o! Y
    5 x" v$ g! b  s, q
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    # ~+ ~" |% l* X1 m6 }7 V4 f/ c& P5 k3 M5 U; z# g
    package com.javacode2018.lesson002.demo18.test4;5 ?0 i  T. Y) v4 ^$ |% G. h' i- ]
    2 g; S, k6 K! w0 {' }. p
    import org.springframework.beans.factory.annotation.Value;
    4 G' _7 s/ h( E" l% Q. K1 G) x: Jimport org.springframework.stereotype.Component;% ^& k, U' d( H# H3 b& k2 Y
    % u5 P) ~& W7 F' I5 P0 M
    /**
    . W# m! y5 D+ U* P: p8 O. b7 i9 B * 邮件配置信息; L" Y1 T7 V) W0 T. b
    */2 i# y! S8 h. ?# h; O( n7 x
    @Component8 e$ Y: U% V+ p2 v( E& }# t3 @
    @RefreshScope //@1
    * Z% L$ b" d5 C9 Ppublic class MailConfig {
    1 p& z5 i! A% w- H) _6 a$ z4 Y- E  U; c2 k. [4 W' ~
        @Value("${mail.username}") //@2; i; z& U" g6 ^1 i
        private String username;& n% O# H  s+ G. ]

    ( M# j+ B( g' m! h1 H    public String getUsername() {
    ( L6 l7 g/ E0 V8 }- l1 p        return username;0 G' ^4 A+ _" w
        }
    & [8 o3 B3 ~5 i* t- F. N2 x( y; o$ ?1 `
        public void setUsername(String username) {
    7 `" o7 b) t& U4 T0 b2 ^4 X& q% M        this.username = username;
    ' |+ E" t5 M# U    }5 k9 a6 M. y! g% w7 a' Y

    ' |- T' l0 w7 w5 K" ^    @Override
    4 E3 P3 }$ c0 q: m: p    public String toString() {
    5 S0 E) M9 c! o1 f0 P+ i2 k. C        return "MailConfig{" +6 ?9 x" E, a6 ?) d' r% O7 J1 P  x
                    "username='" + username + '\'' +6 s' Y+ M$ M7 d7 G) b$ ^
                    '}';5 P7 f" ^( b  F# V0 Y* e/ O
        }
    & K6 w5 p3 \  }0 J& p# Z}
    - ^% W) E/ R3 d/ U4 o! ?8 v@1:使用了自定义的作用域@RefreshScope
    " A# w6 ]- K  t: M/ u1 g0 `* z+ L( Y7 f- b
    @2:通过@Value注入mail.username对一个的值
    ( E6 _  ~6 V2 B3 X. C0 }# h
    * h1 H0 h2 [( K2 a/ @( `3 ?重写了toString方法,一会测试时候可以看效果。3 m+ D, Z$ ?2 j* _0 a& q- _  K

    ( w* z4 ^, E" k! Y再来个普通的bean,内部会注入MailConfig2 I/ t) S6 ~( U5 ]6 A

      O: g8 v. _; w- k) \% u" Hpackage com.javacode2018.lesson002.demo18.test4;  n. P0 w2 y" D  i7 l: S2 z2 S9 v# ~

    3 b; P. K1 Y& _import org.springframework.beans.factory.annotation.Autowired;- s: {  ]! L( e) _
    import org.springframework.stereotype.Component;
    ! |) \, D, n7 Y3 X) M
    , ?% l. L4 n' ?7 U/ l' V@Component( [5 ~! q, }7 E
    public class MailService {# t# j) k( F4 {  E
        @Autowired7 M8 S9 @( q7 k7 m. c6 s
        private MailConfig mailConfig;4 \( J: T  j; M: [3 U+ S

    ' G: }0 H1 g% t  A$ ~    @Override; l! ^" |/ g4 \7 V) D
        public String toString() {
    / W, L' i. N$ l9 O        return "MailService{" +  {; G: O" W; I
                    "mailConfig=" + mailConfig +
    + t6 f; I9 X) u3 C4 P% `( E% {8 O6 A                '}';, J8 w1 d: ^5 b& {
        }
    + E* f$ E2 f8 e7 p5 L}. X6 V$ `0 Z7 F5 `  A7 S
    代码比较简单,重写了toString方法,一会测试时候可以看效果。9 \, ?0 h, }+ U  F/ D1 U1 U
    * j# X+ p% H+ N3 }7 Y
    来个类,用来从db中获取邮件配置信息3 P& o/ S6 v( j' t
    2 b! j; k  c* N* a& D
    package com.javacode2018.lesson002.demo18.test4;
    . V, ]9 ~# e! x% n) R+ s
    7 K+ B$ Q( u2 N, Q6 e  v. i1 k; _import java.util.HashMap;! Q4 h8 X8 [. O/ W3 B
    import java.util.Map;9 r+ C- |: V: F+ l' _7 |' f
    import java.util.UUID;4 A( m: F+ _! W5 M
    7 G& m1 V" c0 u; w5 i
    public class DbUtil {$ F) Z- u  u% l  b$ N
        /**
    " b* |% a2 M( I6 G2 g& K1 @     * 模拟从db中获取邮件配置信息
    7 k- K7 \' U8 c. ?     *
    " v1 g% E5 ?( \7 c( E* x9 J     * @return
    % y! Y0 j6 p1 B3 X! _( l     */+ r' M- _2 |9 p! a( m2 s
        public static Map<String, Object> getMailInfoFromDb() {
    $ |! N+ X2 {; o8 F        Map<String, Object> result = new HashMap<>();
    5 `0 G/ l9 Q5 o; X8 A        result.put("mail.username", UUID.randomUUID().toString());
    ! W0 O' a& {: p2 ~        return result;
    6 T; Y* h. |& V$ J5 M4 A    }
    1 O: H) M4 J. ]0 p7 o. B' Q0 D, z}
    % r6 w" a4 f$ O$ P; k* ^来个spring配置类,扫描加载上面的组件3 m. |5 I' S. j" x& {4 c0 H
    " V$ l# g) i1 \) m1 R
    package com.javacode2018.lesson002.demo18.test4;( D/ y! I9 f: y7 d; [, L$ L
    , n0 }5 q9 [7 L0 D* c, J/ T8 \
    import org.springframework.context.annotation.ComponentScan;) T+ @6 j9 {0 ^% ?! A  w9 E
    import org.springframework.context.annotation.Configuration;8 L! x& A% Z9 Q- i" B- P5 G& Z8 g

    2 c4 y8 L( `2 f# q& u@Configuration
    $ v; K! F8 A; E. _5 }' I@ComponentScan
      G( y) ?3 z3 y8 k8 J! w1 p5 {6 X" Lpublic class MainConfig4 {3 j5 U: {( ?0 ~+ N" ~
    }8 K0 }+ o7 n# ?" l4 l
    来个工具类
    6 _' P2 ]0 Y. a1 l* m& V/ W
    - F; W+ G+ t, ]% I% c! H0 P/ g内部有2个方法,如下:
    : g8 ]$ q' A- \! V9 K: F0 D; N: o4 }& E/ q3 i& G5 a
    package com.javacode2018.lesson002.demo18.test4;
    $ W/ S% g+ e6 H+ W5 E! b* Z
    & F* x+ C* x2 @! c) o, Mimport org.springframework.context.support.AbstractApplicationContext;
    ! M: b  f, b, G  U' qimport org.springframework.core.env.MapPropertySource;2 h. U9 l% i* X7 e% \

    3 q' G# @  E5 m  d  ~5 Uimport java.util.Map;
    $ v  {. S$ L$ C
    % E5 M, N3 x  B; q5 d/ Mpublic class RefreshConfigUtil {
    2 U- J- Z0 e' _# j7 L7 B- u    /**
    0 A: Y0 u2 w* S9 D  M7 q# M     * 模拟改变数据库中都配置信息
    2 {- ?  P- t) G9 j     */
    * `4 V  U0 M1 O6 q    public static void updateDbConfig(AbstractApplicationContext context) {
    ( T% e) H# v8 y* q        //更新context中的mailPropertySource配置信息( {% t9 `: E+ w6 D
            refreshMailPropertySource(context);
    * H6 p4 W+ E# q4 S+ b; t! W! w- [- y  a5 k- `
            //清空BeanRefreshScope中所有bean的缓存$ i8 i2 F7 U7 J* P, _
            BeanRefreshScope.getInstance().clean();
    ' n$ q  }7 w' J/ W/ q* F9 H# I    }0 a% L  C7 n# m! K
    * x3 W2 C* E, F, \* e6 I. l
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    2 c: ]& o6 M$ H8 o/ E1 s7 ]        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    : S4 x+ l; ?; h4 T& q        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)" p7 S! T9 [# \  E2 n* G2 g
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    % K; j$ T0 L  S0 c  k        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    2 [% q7 c. K+ `# L3 S    }. \, A2 @1 [- V

    7 D4 |4 A1 c$ ~, D4 {, T' D& b9 m}+ Q& j) q1 p& d, K9 L" U4 u& J
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ) i. B5 d$ ?: R1 b  B: h8 X' j7 e  c0 j4 F" r; Z
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    % i  I! |9 z# P% r& U
    ' C. |, T. b( g' D0 l6 _来个测试用例
    . i/ u, v% |5 d  D. |' W# N! [$ K$ v0 f! k' Y- i
    @Test: z7 z0 I. i; x6 s' K
    public void test4() throws InterruptedException {
    4 |; _2 R0 A2 M. X- ]    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    % r: d' K7 z( y3 d. t7 Z3 `    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());1 ?, O  o' j# _
        context.register(MainConfig4.class);+ L5 F( j" z; Q
        //刷新mail的配置到Environment
    ; {( {& s1 p' h# I4 b7 i    RefreshConfigUtil.refreshMailPropertySource(context);
    ! k5 u5 j! V; q: n    context.refresh();
    , {; V/ {4 u  m! o7 `# {5 C3 M1 z- j9 f4 A) b3 c" j
        MailService mailService = context.getBean(MailService.class);
    6 L  M( D% A+ _0 ]- s$ Y    System.out.println("配置未更新的情况下,输出3次");3 ^- A7 z  F3 ^9 T
        for (int i = 0; i < 3; i++) { //@1
    # }- U" F. t" S  m$ J        System.out.println(mailService);: A) F. s8 C: f2 u
            TimeUnit.MILLISECONDS.sleep(200);* r# B$ W% s  M1 K- Y2 p' f
        }
    0 l+ c! g( N- u$ s( Z% M  N$ c# L! L
        System.out.println("模拟3次更新配置效果");
    5 _% U0 W  \' M; {    for (int i = 0; i < 3; i++) { //@2
    ) E! M$ {  c( h8 w6 G' ~, u        RefreshConfigUtil.updateDbConfig(context); //@3) L' O; _7 O7 X7 F9 u: e- p
            System.out.println(mailService);: k* x$ C5 I& J8 ^% E# D9 Q
            TimeUnit.MILLISECONDS.sleep(200);: u1 T' S5 ~& m* q
        }
    0 c! }3 O% ~( u$ M) u! e}1 x, P. @9 J$ @$ K; C
    @1:循环3次,输出mailService的信息
    0 }+ a9 [) N3 c8 k5 I& W4 ~" X$ v6 c' n4 o$ {
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息  y. h  N/ a5 s0 R( w! f

    , D" V1 d2 G2 ^# \. k4 V见证奇迹的时刻,来看效果
    7 [+ U5 `, ]9 c# t0 G6 e5 T5 k1 l) J, }  z3 D. g  a
    配置未更新的情况下,输出3次
    ) a  H2 c( z' d1 y% X9 p5 m. zMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    % j6 d/ @& H; U3 cMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}$ v& x* V4 U! h- O/ {0 C9 U
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 Y( e$ y4 `6 f. @模拟3次更新配置效果0 m" [' u1 F  `5 x9 U: _7 h
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}' m; c, U+ A- |6 k2 s
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    3 z0 c8 u8 C- A. u6 h( t4 ]( H8 oMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    2 }; a8 N1 R% I. E! [. V" {, h, V上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。* f& l9 D+ }* d6 ?- F* H
    3 Y+ C  y- |- A4 |) T' r
    小结
    ) _9 N! P, B0 z2 ^! g6 U
    - V+ O3 X- X8 R9 `6 X& a* R) B动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。0 Z6 i  r; g" g0 `
    , I- p  q, |  M5 |1 G. R- T
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    8 @9 v% Y& U$ v& Z0 u' v" x- K9 ]: i# E! m& _  T! x
    总结+ i; K, ^0 k2 L
    3 @7 W' l7 J3 ~: Y( d
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!( z: p% D/ J5 N- g7 \

      V5 i! _5 X# X7 N" k: S案例源码
    / K, U7 U" G1 w* {, F  i2 X7 b% s+ z. F8 A
    https://gitee.com/javacode2018/spring-series
    8 ?% O* B9 h& Q  l3 P0 Y5 ^路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。$ u8 x. H" I) q# t
    # Q" v( A! s4 S( \, p1 O4 U
    Spring系列
    : S3 V$ R- _. h8 V' H7 X: Y$ z! _6 A( a) t5 G+ r6 d
    Spring系列第1篇:为何要学spring?1 x* s# O( G: J, M+ y

    * p0 z" [, [4 T* q* gSpring系列第2篇:控制反转(IoC)与依赖注入(DI)0 L9 U# N: ?3 D% N0 a

    . ^/ L1 e4 C* J2 b1 T9 Y6 XSpring系列第3篇:Spring容器基本使用及原理
    6 D, @( s' J4 ]1 o- b& c0 q5 h5 _* q
    : L9 y5 f( H7 F# v- dSpring系列第4篇:xml中bean定义详解(-)
    5 p" k- s& h* ~9 Y  T" t& c6 ^: Y- B- \4 c! q& n
    Spring系列第5篇:创建bean实例这些方式你们都知道?1 @' [* {7 D- |+ i

    $ |7 h5 p. N; Y7 U8 Z  oSpring系列第6篇:玩转bean scope,避免跳坑里!/ U3 I3 s1 e0 y9 o/ Z! [

      b9 B2 ^# ]8 K8 FSpring系列第7篇:依赖注入之手动注入$ m! Y0 S) J- x
    / w1 x6 y& S2 J" e9 Z3 E
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    # A; `; e0 h6 w3 F8 m- l0 ]8 v& d; W1 T- ^! G# r7 `
    Spring系列第9篇:depend-on到底是干什么的?
    $ i! M; P) R- N+ t7 s' H
    9 }2 q' X& @8 P7 _Spring系列第10篇:primary可以解决什么问题?+ ~5 U3 _3 K: c( U9 [/ j

    0 I/ A9 ?0 h; O) x- l7 lSpring系列第11篇:bean中的autowire-candidate又是干什么的?
    9 j3 Z9 G; t8 n6 W; @* ?1 O: m# O8 M! U) |1 \- I4 x  ?
    Spring系列第12篇:lazy-init:bean延迟初始化
    " o# [, _$ C/ j/ X, H1 S# a# x
    ) [1 Y  Z* ~" p( \. O, A& x+ P2 DSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    . _2 A; Q, F7 I7 N% U. q' J
    ' b- a  _4 l0 i# GSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?' X& Q7 F' Y* q8 W$ |
    , m, ?5 V3 c8 {1 E5 R! y$ ~4 C# @
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?, E! Q: p! K' t. l' l/ m) M+ j# X

    ! q6 p. L" L) L) I9 k7 ESpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ( G+ h1 ]$ c' U7 `3 p
    : _6 v: Y/ s+ YSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)* i) R+ w2 {0 Z5 i
    $ \4 T! E! \! C* x/ S, n
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)( M" |4 z6 a  z# `+ {4 e
    6 T% k/ P" o5 [4 `% l+ T
    Spring系列第18篇:@import详解(bean批量注册)
    . [  V) S. J( G( c, [
    ; ?5 V4 m2 N& T% q) hSpring系列第20篇:@Conditional通过条件来控制bean的注册
    0 A; \0 A7 A; x4 f. t! U3 [- X
    - o# |0 S0 O0 ]Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)  v& Z$ ^6 T- d0 ^9 _& M
    . F# k0 O4 J1 w1 A
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    ) C) A0 `+ I6 o6 `  T4 L, ?3 f- r/ v: C
    Spring系列第23篇:Bean生命周期详解9 h! H4 j7 c" `. W/ l4 F
    ) ^# n. {0 g2 V7 |
    Spring系列第24篇:父子容器详解
    . G2 Z  C+ Z' j* `( Y" \$ E5 Q6 U/ Z# u$ a7 m- [9 H, B
    更多好文章' g$ N- e' z& x! m* p! M$ e
    " l& g3 G5 B6 k: K
    Java高并发系列(共34篇)
    + h( @- d3 c8 ]' f& B) F1 P3 J% o  n8 e
    MySql高手系列(共27篇)
    . `& {" H2 `4 j$ ~. {( v# i& Q+ |' r# u8 M8 n
    Maven高手系列(共10篇)
      v9 e( W( w& ]6 O% G* J- @) P/ [6 C, [) F- ^% n! `
    Mybatis系列(共12篇)
    ) H' V5 f% t% u
    : X9 Q$ A& q/ d: n8 m) P) K聊聊db和缓存一致性常见的实现方式
    % ~2 s% @& x" }6 J
    & |$ h2 y! P! T4 l& J接口幂等性这么重要,它是什么?怎么实现?
    ) C5 U" j1 P8 B" h$ m1 s, ~6 `5 m" G' T/ H4 s
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    1 R( N& l, q+ s* v8 p9 P————————————————
    ' [# k6 c6 }; y+ T7 \版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    , g' m# Q. R& v原文链接:https://blog.csdn.net/likun557/article/details/1056487579 f  p. m' x# {! f. n  S
    $ J8 @* d5 [, H. Q6 x
    " z* m  a  i6 F: i+ f  |
    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, 2024-4-25 07:55 , Processed in 0.294028 second(s), 50 queries .

    回顶部