QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5396|回复: 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!
    % S( Z. Z2 F6 G疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    ) E3 p( h2 a8 j7 u
    ; Q& }7 g6 b& D. s面试官:Spring中的@Value用过么,介绍一下" f2 I% r0 n2 C+ B# C; c! e

    + E+ W, {& \0 |0 P! I; @7 ^我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    , o$ m2 a7 o* L' H! E* h# h. _3 q  u' y0 ?6 k
    面试官:那就是说@Value的数据来源于配置文件了?
    7 l. n( I/ E+ b' ^! {
    # i% K/ w; Y# I! R% `4 r& c0 e我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置) @5 e( q2 L. K4 g) `( E) o

    9 m% g4 C9 q3 \6 m+ o面试官:@Value数据来源还有其他方式么?, P" C! d: c+ B) Z3 m

    ( j4 ?# Q: t" A我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。6 R3 B7 R0 z* b0 p' k4 }- d
    6 B$ b6 Q  k) E: Z! G5 j
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?5 A- V0 D5 G0 ~

    # t3 k2 a* B; w  s, r7 E我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    7 L% @- G3 d7 f; i
    * x6 \1 t7 z5 O面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?0 Y1 `5 K  I  z
    , I% `9 {+ r7 p, x$ l
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能% s' z& s* t& p' G4 G, x: C
    ) l0 u% z6 s3 P1 M5 K; I
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    ! c) u0 w  u. o! q  K# t+ D1 [# V/ Y1 x! C) w- I$ H- a" b& T$ a7 r
    我:嗯。。。这个之前看过一点,不过没有看懂
    7 k9 s  A! l! J4 \5 n2 G
    : a. L+ x1 D" C/ ~2 q/ g面试官:没关系,你可以回去了再研究一下;你期望工资多少?/ v5 ?  G! {0 ^. k+ M( G' E5 t. N3 C0 T* h

      I4 y- H* R2 S# ~' Z我:3万吧9 U1 l3 j" U' {. c
    * i3 |6 P+ ]  V7 ^
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    ' k* m8 A  P- \  p7 ?9 {- r. f; k. t3 [5 j, A
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    4 F8 J) \) N$ o
    3 J6 J8 b7 @' T! ^* _面试官:那谢谢你,今天面试就到这里,出门右拐,不送!! [! z% W( P, ^$ b* e  O* C
    . Z2 I7 t8 [* w' E' f9 ]5 g
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    $ i, y: A9 [( b$ s
    " k# L3 b5 b& [8 r! j. V) x这次面试问题如下, d8 m. s7 e/ f
    5 Y3 O0 V, ~! B' Y$ a
    @Value的用法, k7 c) D& R. H6 u- d. K; \& L

    ; j/ T0 ~7 P0 \- o  X) V@Value数据来源
    5 h* p  z1 t& M( N% c4 R: E# x: g& \5 Q8 f' D+ P- X* W) _
    @Value动态刷新的问题
    9 `1 S" ^% @* j" L3 N- [' a; A4 m6 r% ]- z# p& }
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    * b* V+ x5 m0 m& v% u1 K1 [: U4 {# c" V
    @Value的用法! D' X+ q% ~, n, J. [) a. @

    * [" z$ x0 T* b0 y: K系统中需要连接db,连接db有很多配置信息。, R% J9 Z$ P9 K% `3 k, y6 ^
    : u% E# o3 u3 e6 m9 T
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    ( R2 k9 }5 t# P6 }" Q/ [, |" E- Y
    还有其他的一些配置信息。8 |- o! S) x) p

    ) X% X0 _5 }% L# O我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    ! l* O' i% g/ p; t2 }2 x4 w
    ; Z6 p% g# O# i3 k0 D$ ^  h那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    ! I1 p9 y5 N- ^3 S2 E& K
    4 h% V% g/ `& k4 }# ~通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    * A) s5 k& p9 p2 F' o0 d% G. Q7 f
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    ( n# F1 N# q2 X
      v% r2 L% R; _7 `' Q  G6 ^6 K@Value使用步骤. j1 |7 Y/ L# i- @  s

    ( r, y. B6 I' g: f步骤一:使用@PropertySource注解引入配置文件" J5 M) u& `9 [/ o/ ], b

    0 J# Q0 K6 \; R将@PropertySource放在类上面,如下* g5 A1 t/ z) u+ d

    6 ^- p% h0 D9 ^0 ?8 F@PropertySource({"配置文件路径1","配置文件路径2"...})6 a7 K& z, T1 u
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。( y$ ~5 z5 I6 ?

    6 a  S% A5 T7 s( ?8 ]" _& I- K; a6 B如:
      }0 |7 k: D4 |. V* d
    8 E& P" E" Y" K7 P9 G@Component( y5 D  L- ~) L7 y, k0 n3 l
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})7 _3 w' _$ u- d! [- f
    public class DbConfig {
    : _( i0 X/ d7 g+ a: H}% e9 j7 e* H. Q
    步骤二:使用@Value注解引用配置文件的值
    8 T( x2 l  g  V. U( T" T9 ]% E6 _- K( F1 Z, [
    通过@Value引用上面配置文件中的值:4 t4 v! S9 P3 F7 ~  m

    0 d( x$ B# C* E/ L# I! w8 [语法
    . o) d! _7 l0 a$ ?
    ( C* _* m0 i) p! ]/ y) _% h- K@Value("${配置文件中的key:默认值}")1 `! [$ Y& C! B1 W
    @Value("${配置文件中的key}")
    - K& U, L5 w# U. x; }' o如:
    * n9 y0 u. _9 m' s; Z7 x( _' R8 m, R: m, E, E; I; O, l+ t
    @Value("${password:123}")" M( |; v& o4 f$ R2 L
    上面如果password不存在,将123作为值
    ) ~; M) \9 d4 J& ~! s+ I/ M4 I2 {
    8 c: p! C, V/ y) K+ I, o2 O@Value("${password}")7 _3 b! d, z9 ^+ g3 K9 V' g" p
    上面如果password不存在,值为${password}- a7 @/ b* x0 y* r2 E
    ' x( j1 O+ S, t; m
    假如配置文件如下8 z: _9 Q8 f6 J
    6 D" o7 D" {, q# R) L: J1 o& \
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8" _; C* c0 ^5 P4 O  h7 P" x
    jdbc.username=javacode
    3 m: d* W% o" N) H% T: A' z+ ajdbc.password=javacode: l+ Q- }$ X- i' L# `
    使用方式如下:
    $ @" z' s* J, F- x2 r$ a. r2 v6 r( b- x6 J: `2 s* b
    @Value("${jdbc.url}")
    1 T3 e0 p8 m0 Z9 o* @private String url;* H5 |# G4 B6 w7 c+ ?% F' M+ B

    " e# i- p  i/ G1 {; ?@Value("${jdbc.username}")- I0 y* g. k# c0 ~- `
    private String username;
      A9 A% m) Z7 Q6 z. s" F: N$ J0 j2 k& g: v) o% S  u1 v- D
    @Value("${jdbc.password}")
    ( q0 t. }  i0 `0 @private String password;
    , {! q" d) v) v/ f下面来看案例( C. W' y3 W7 T9 ^, U, o

    0 ~0 C, S' C8 h9 _% C% l9 |* S案例
    ; \) V( E; U& c9 q% f2 @5 \& i: j
    $ y; @6 G& o( K. U4 r* L( }来个配置文件db.properties: H- b6 W! ^+ Z; |- D! W
    4 X8 }: Y- h, H" d& Y/ W) K7 j
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    , x; b6 m4 G% p4 ^+ qjdbc.username=javacode( _& W$ x. K% J2 ^
    jdbc.password=javacode
    , a3 f, F# ?( u9 s/ c( P来个配置类,使用@PropertySource引入上面的配置文件
    + o; Q5 P. \7 m
    ! p: h; E0 \3 Cpackage com.javacode2018.lesson002.demo18.test1;
    0 d& i: e3 R( ]# S& |) R2 k5 M  z; o$ X6 N2 N# G
    import org.springframework.beans.factory.annotation.Configurable;, [# y3 i- r" }1 _$ J7 N, I( H
    import org.springframework.context.annotation.ComponentScan;: ~( g: J& X& |* D0 {% }% L, j
    import org.springframework.context.annotation.PropertySource;
    / ~8 J* G/ L$ d. Q* D/ d- l
    , _1 c& f3 e; z& l( a@Configurable+ |" A. p- {+ T0 @& t, H# d
    @ComponentScan
    . t6 N' e1 e" g7 H@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    % p, l2 L" k7 l+ @6 n3 K+ Dpublic class MainConfig1 {7 _, L6 f; v% e/ R0 D5 h
    }' s1 A) J% f' `" g6 l! y9 o
    来个类,使用@Value来使用配置文件中的信息
    ! m5 [* c6 F" f; T2 _6 `5 a+ o* w
    * I- N4 C: Q: Rpackage com.javacode2018.lesson002.demo18.test1;
    3 q7 B- n& ~' o( {( n
      ?9 Q1 B) L( y! Himport org.springframework.beans.factory.annotation.Value;) C1 v8 t: W7 Y
    import org.springframework.stereotype.Component;' r$ x: o% q7 _

    : J( ]* L. j# S7 {( d$ O$ m5 l@Component' \* r0 L0 b: p7 T+ n( M; w! y
    public class DbConfig {
    , F- q9 @1 |$ b- E8 [+ D( n0 g& @1 c0 f7 `6 X) w7 D
        @Value("${jdbc.url}")
    " u9 y6 ~3 h/ G0 l. @5 M. P  {7 s    private String url;
    3 S% l6 E9 Y% z& F  A
    8 \; t. |1 ^& {  `/ j  b' r    @Value("${jdbc.username}")
    + Y9 E$ n5 K' K    private String username;( @9 O8 j; r- ?' j6 d
    - e/ p5 V2 ^4 x
        @Value("${jdbc.password}")6 r  u( Z1 G! D& M/ W7 F
        private String password;
    6 r+ ]9 P$ B3 P1 m$ T0 S' h
    . s2 b+ @% {  j6 z2 r( u    public String getUrl() {4 k) G- ]" w8 s& }: K
            return url;9 x  G1 Q. i+ T+ U% A9 _
        }
    1 w" e# n, R+ Z! ?) {
    3 X  F* N' c8 b! Z6 e' ~  f    public void setUrl(String url) {8 K- N& y3 N1 I* ~4 c/ H4 V" b0 j
            this.url = url;
    : R4 Z) B8 H1 l( g* n    }: S) o; ]$ g; L& V4 f

    , c% ?- ?" \' |/ @2 }- D    public String getUsername() {
    7 i9 W4 s9 z; i2 _6 o; z6 s        return username;
    2 ?7 P' U+ R2 \+ \5 V) b9 ~    }
    ) T2 [/ ~% B/ v1 k# E) d: b8 f/ m5 k6 u; j& q3 T
        public void setUsername(String username) {! o; `( _  y' @7 ]" k
            this.username = username;: q% c# a. c" a8 Q
        }
    / r1 Q5 ~7 i" I: z- F) O: @2 G2 t$ P2 q. u  M# k& ]8 a
        public String getPassword() {0 Q" M# w% {) @5 \
            return password;
    ) E9 D: A) Q4 p8 T" k5 a. ?4 \    }
    1 w: b' W+ i8 _  C1 O4 O+ ~( L" Q/ A! {, E& d! w4 h( m
        public void setPassword(String password) {
    : t( L: Y6 T+ W0 g1 F, Y) R! Y6 ?        this.password = password;
    * J9 @/ f1 c& Q7 {/ A) o, n) w    }
    " F2 `( S5 L* \( @. `( f$ o4 P4 d' `3 f* f0 w  _
        @Override  L# N& S) r5 ~; Q
        public String toString() {( O$ v% r6 c% I; D3 s0 \' x
            return "DbConfig{" +  o1 b5 x+ H4 a4 \: P
                    "url='" + url + '\'' +
    8 Q4 D8 M2 m! r8 X0 N& [                ", username='" + username + '\'' +
    ! l8 B* T* S3 [9 q                ", password='" + password + '\'' +
    / N( ]! j( X- }: M                '}';
    " L7 V, O! F6 y5 N4 O    }
    8 g" ^. Y* }4 F' o" m" z$ P. I}4 h6 `' I0 L. [& A$ D
    上面重点在于注解@Value注解,注意@Value注解中的9 i! ]* g. w( d) ?) _) T; F

    $ @% u) U: ^  P. r+ l7 G来个测试用例
    ( A9 R1 e( ]8 N  o. d
    $ p! O3 V1 N1 [6 `% B1 vpackage com.javacode2018.lesson002.demo18;1 Q" o; g3 e4 G7 a% p

    & B" D! g9 F/ ^' q3 Oimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    9 p( ]& k5 J; e" @5 F  m4 Jimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    * Q- g  W9 }# s6 q7 Bimport org.junit.Test;
    * j. h4 L8 {' b. k* |! Eimport org.springframework.context.annotation.AnnotationConfigApplicationContext;) ?7 |  o3 y+ j9 Y3 C4 l4 K
    ; F5 q- K7 G% Y. P, `: T. y
    public class ValueTest {, M/ D/ x( X2 U) s% Z
    / R5 a) l8 `- n: g1 P9 {* ^, \# X
        @Test# |$ K/ q, J3 M) u7 Q1 S
        public void test1() {
    0 s* N: J3 s8 v4 _) L# q4 r        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' t% t6 g8 M4 n2 p$ I3 U
            context.register(MainConfig1.class);. k" U: ^/ |1 J& t' m9 J/ Y
            context.refresh();# R) q6 L; O# \/ a+ W6 f0 e5 ]

    . j- S8 t! u! {" C+ L        DbConfig dbConfig = context.getBean(DbConfig.class);
    2 w& y1 |2 W% ~" \+ }        System.out.println(dbConfig);
    3 `1 e  i; F2 g4 |" ^/ H    }& y0 x" C% I1 t
    }7 q& e6 `; t2 {! U+ t+ _
    运行输出. Z" p! R* j; ]3 Z! [1 e
    6 J, c5 _" K, ?+ q7 D
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    " U5 H1 ?6 R8 A& J( Z3 w上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    , ?! P5 ~. e" a0 @  z3 z4 t6 Y" p3 v9 C6 Z+ D  ~0 }: m$ A% I
    @Value数据来源& J+ _9 K3 I# x1 Z' `' l, E

    - h& T" d3 b% Y# p4 D8 D通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    : O3 A7 L: y' r. _
    0 c/ ^/ o! V  K5 R1 k3 d8 a, T: \: h我们需要先了解一下@Value中数据来源于spring的什么地方。( B' ]" |4 y. d9 S4 F0 a8 a9 l

    ! }8 Y! ^& Z2 a" q, ~" yspring中有个类, ]8 _8 v8 s/ r+ G9 c' Y( c- Y

    7 L7 p' h  a/ j/ S3 `# Worg.springframework.core.env.PropertySource
    8 |8 P4 ^7 \6 q6 t! v可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    4 u' x" G( o: b+ h
    & a, `* U0 ]3 d1 t8 n3 t' H7 r/ R7 E$ T内部有个方法:6 k8 `! L) D, F4 L

    # S7 C8 t  O% D0 I4 ?public abstract Object getProperty(String name);
    : B! U! J* l6 \) D2 V通过name获取对应的配置信息。. h, k5 r7 \7 ?& d
    ; @0 B# i% x  m7 y) n
    系统有个比较重要的接口, `1 o5 K0 }/ ^# c2 ?, q) y7 c
    0 ?, R$ w# K. b  L& s
    org.springframework.core.env.Environment
    / z5 p$ D: }$ I5 ~: s用来表示环境配置信息,这个接口有几个方法比较重要+ @4 F  T7 j* N; s! a

    $ u6 ]9 o$ N( G4 D+ `4 a# sString resolvePlaceholders(String text);
    5 F) e, P& T# x, \: q5 S* s0 V# RMutablePropertySources getPropertySources();' O1 ]! y, Z+ ~
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。0 d: R% K7 z% Z; }, x
    & }. {. t! n* \& d( h' \
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    / ?7 n) }  V* M  O% c. Q( `: {3 i( m  `& W9 }; C) U, l
    public class MutablePropertySources implements PropertySources {' Q3 M/ G2 W& u7 v, c/ {7 v

    0 n9 |+ X* z7 D) w, [    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();9 ~6 Q9 c! G# j; ^; G, }$ v6 {

    . m# d# @9 o/ A5 }7 D}
    + Y8 }. I. c0 y: D5 d* ]0 {( c: y内部包含一个propertySourceList列表。
    ; N! r$ H) S) h6 n- y' X% ^! o& j7 m, u
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。  t! T9 ]; ?# R* Y
    ' F0 Y2 T2 f; c% N# \7 ]
    大家可以捋一下,最终解析@Value的过程:
    " }2 ?5 O& a6 a9 \# f4 b0 y+ h; s: j& C# x1 ?$ ^# [
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    " {2 v) N* j/ z( ^1 k2 `/ \2. Environment内部会访问MutablePropertySources来解析" G8 F- P& m. L5 F7 A2 k
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值8 n, C8 T; _6 k0 |$ W3 d
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。8 f2 }7 z6 r9 c* ~3 ]) Z( n% Z

    ) l5 H& r& d0 B2 {7 R' t: ]下面我们就按照这个思路来一个。
    0 g2 I( S; j5 F4 Q$ s% Q& w/ M9 G; k! J8 @% K/ Z3 k
    来个邮件配置信息类,内部使用@Value注入邮件配置信息8 S9 g$ t  T" k9 d0 k: K2 v: n, I

    , ]& G  K7 E7 N% V5 bpackage com.javacode2018.lesson002.demo18.test2;
    5 t+ p2 K9 D/ }) H9 r7 A8 H
    2 Z) I9 z& q9 s& Rimport org.springframework.beans.factory.annotation.Value;; M) Z0 _$ C: T9 c5 b
    import org.springframework.stereotype.Component;. q2 k: K1 Q3 [0 p& ^- n
    ! p4 Q- ?% h6 }
    /**( i- W: H8 `; |! E
    * 邮件配置信息
    ( @- u- d* m0 r" J' U */5 x2 J) e6 a0 }& A, ^/ x& R
    @Component
    , s0 W" R; L: l( M! h. f( i+ Rpublic class MailConfig {- k: l9 O: J, ~0 \
    ! ?/ s- Z9 e3 a, |( M! h# _
        @Value("${mail.host}")1 \+ S8 W" e! u; [, o
        private String host;) q! f+ c' E! b5 h
    7 ?  f: z' Q& d$ I" f
        @Value("${mail.username}")
    ! z0 ^8 j9 u/ H3 o2 d+ N6 Y    private String username;; V9 L, F7 O$ r; M

    : }6 U6 l. f! G0 a( B    @Value("${mail.password}")1 a5 J4 [* T, h# z3 x( {1 R- o
        private String password;
    * L7 Q" c8 z. X7 y
    / h$ T5 s. I+ r; M    public String getHost() {4 J  s* i3 K3 Z( _5 R& f7 R# w
            return host;
    ) {  c+ A! X5 d! W8 ?$ n    }
    ' ^$ L: j* d% ?) _3 I3 Q9 W* p1 n; g6 s( L$ O6 e* _% A- X
        public void setHost(String host) {! g- v$ V& A2 q" w
            this.host = host;' {$ W/ N7 _3 J- {1 o8 H# j! g8 q
        }
    $ u! e6 [# N8 d
    6 _  Z( H1 Z4 d  g, E+ C, h/ v" Y    public String getUsername() {
    ; {$ A8 h" y6 d  F, V        return username;4 ^- ]5 G8 p, p9 t4 P
        }  X+ B' {/ B, _* |3 Q
    ' E3 J+ N; f& z$ O
        public void setUsername(String username) {
    3 Q: y# k3 I2 t4 [8 m$ _/ M        this.username = username;  X4 r  w, ?, k$ @2 w
        }' `& Q& X1 Q& I' f

    & T  _- W2 k" E5 X6 R    public String getPassword() {
    9 G  O. Q! }6 }: T$ Q, b1 [        return password;1 k0 S& A# @% P2 Z
        }
    ( C0 H2 `2 z  B' H4 p# o/ x, i* r! g) z4 Z
        public void setPassword(String password) {2 |! B* ?* p0 ?! n
            this.password = password;- \7 e0 E, y5 C6 M0 h0 m. m# w
        }. {1 h( x  `9 H. \7 @/ C# a
    9 d: n( c# B$ T5 x/ k
        @Override
    & n5 p; J1 {* l" k, H# i. B    public String toString() {4 O8 h+ A; Y0 T
            return "MailConfig{" +& Z  r0 ^- t" w( |8 a0 ]( Y8 x) M
                    "host='" + host + '\'' +
    7 V' R: m6 N% D8 z7 _                ", username='" + username + '\'' +
    ( K- d8 p9 q7 I0 ~                ", password='" + password + '\'' +$ V/ x& _7 Y8 U/ M/ T2 @4 _* I
                    '}';
    . N- D% E  {+ p    }
    5 ?' l* Z2 d. j6 a}
    1 R% l# ]( A+ e  z: u再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中9 l) ]/ k  |! w
    6 d6 R( t+ m; `2 t9 d
    package com.javacode2018.lesson002.demo18.test2;
    , X1 q1 l' ^, I. J5 I. U
    8 V4 ]% a+ [! G* Pimport java.util.HashMap;; {' y. m+ Q  {8 ^& }
    import java.util.Map;* ]" E$ j( b- ^( d( P) A9 W
    % J. |7 c# {$ c
    public class DbUtil {
    ) M3 s- z+ ?' A. C    /**& r5 H0 w, C: s
         * 模拟从db中获取邮件配置信息! H0 a7 X5 D1 u5 F. e, f' ^7 ?" d
         *" k' e7 i9 P' G" b
         * @return
    ) a8 n( z6 z: h' R5 F3 N; x' v+ \     *// k+ L2 }1 n. u8 Y' J
        public static Map<String, Object> getMailInfoFromDb() {8 j  I" T3 _8 W0 z7 S! k$ Y4 I! d
            Map<String, Object> result = new HashMap<>();$ T% T$ Y, y8 {3 j( L: R" f( s
            result.put("mail.host", "smtp.qq.com");
    ) o, _% T+ a$ M8 H8 Q1 |        result.put("mail.username", "路人");
    * j# Y4 i( f: e/ O        result.put("mail.password", "123");3 j+ B9 H& E8 V& |2 x7 b
            return result;" H3 v) U5 z  ~/ Z$ q  H
        }
    - h  H) h9 Q8 b6 X, H3 _}
    ! \2 Z1 q1 Q/ j2 X/ G来个spring配置类
    ! v0 I0 g4 N" W! p& f* n; Z" I# s, y& C) n
    package com.javacode2018.lesson002.demo18.test2;* p$ p& j4 ]* }2 _0 ^+ u# M

    # j8 n( H: p6 [* L. i, C2 rimport org.springframework.context.annotation.ComponentScan;
    , h1 j3 g) O% o, }4 Uimport org.springframework.context.annotation.Configuration;& `' h0 E9 H7 a! @, p

    & z9 B; N) Q: \@Configuration
      o8 ?7 |4 B, c7 `@ComponentScan
    / N+ a* x$ ]& S7 d+ W% Upublic class MainConfig2 {5 k% }, v" N* V$ V9 G
    }
    6 u+ u5 \! u3 _/ w2 h下面是重点代码
    ; E9 k9 S6 i/ Y) E" R. o) X
    / N# {# z. w' T3 p, V4 ?@Test! T' B) ?! f" z% Y) v' m
    public void test2() {( |; Q# V, m6 B4 e) m, b: }
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ) i' Q( b- A9 z$ L/ p' Y% ]1 J
    4 K' ^9 O! v  V$ L6 e- W' f    /*下面这段是关键 start*/
    1 o# G4 c( V1 W( r! o3 o; h    //模拟从db中获取配置信息) F) q9 x4 X! y4 K3 Z
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
      l& {: X  O+ A" G    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    3 l0 S* j. ]9 f    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);7 _) k/ o' Y) ~8 R( d9 z' z! A
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高7 j6 m8 k3 m  Y
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ( p  e/ S! r' f, p; T% f    /*上面这段是关键 end*/
    ' b0 d& P$ x( w* r4 N0 v( T
    . a1 q( R+ ?, n    context.register(MainConfig2.class);& |9 @" S$ v' U; |  A$ ]4 d
        context.refresh();; ?" B; \' n0 f' Y6 S2 s
        MailConfig mailConfig = context.getBean(MailConfig.class);2 L2 x& F1 T- l3 `( |
        System.out.println(mailConfig);  r* ^0 {; p7 e7 k* }) p; w5 a
    }
    1 i2 i9 J; Z2 n0 W: A注释比较详细,就不详细解释了。8 J8 D: C% O6 \6 h) l# ]) R

    9 z/ e% Q2 v5 c. \, M直接运行,看效果2 ]: X+ g# T& C! a% Y2 K! V

    " @( f! s( ?' z4 s0 N9 IMailConfig{host='smtp.qq.com', username='路人', password='123'}& |- s4 v7 l8 d0 e2 v* g
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    6 |( z' E- p; l  T% {
    4 _$ H& h% H: Q: r* i' }上面重点是下面这段代码,大家需要理解4 e% U& [) c0 e2 u7 D; {
    , g- \( d9 j, q0 u8 I
    /*下面这段是关键 start*/
    # t$ ~' P! K9 I4 C' W: T9 B2 b//模拟从db中获取配置信息
    ' s1 E; o" V8 m# a. R1 lMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();# A3 F5 h' ^, U% i# b9 \
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)9 d) c3 W1 \$ w& N7 D$ g9 ~
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);6 y9 L* |, R. S$ ?0 ~, x- d
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高) `7 p" ]3 S/ d: S) P! t2 ?% J
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ; A' V3 s6 w& j# V+ |/*上面这段是关键 end*/
    6 T% k. T3 p2 ~, c# m" ?2 [/ J' x咱们继续看下一个问题
    7 v7 E5 f: G, M& S6 M( Z  T; J1 Q
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    7 }7 F1 a2 X8 F
    9 g) u$ k- L* u) _& G@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    8 Q. o" z; X5 D) p: _8 I9 S3 g6 o$ L+ W* x6 h: ^8 H* t/ \* O
    实现@Value动态刷新) S, M0 b& o- F& Y4 k2 t* l8 }" \

    $ I# [' j& E" v! r$ s先了解一个知识点
    9 c0 I% T- {, @; |4 L6 m( o* a
    + a; B+ d6 ?( ^6 o! l这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    " T5 {( \+ q# s) G$ L/ y+ |( i4 \, h$ K4 H% J
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    ! |2 ^* T3 e' R2 e) Q4 ], M; T! c  @! }
    - V) {4 }8 J" E7 Q; Cbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:, k' c- {9 n) H! T$ G
    2 N  |1 G" h0 U% r  \7 z" o) m
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;, y5 W2 ^: c' Z! z6 y4 r6 g! O
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    8 A+ j, s! {: ^" I( |# t3 P# b# Y5 ^2 z* t1 C! p9 y: P
    public enum ScopedProxyMode {8 H5 N9 w2 D2 |9 K' b
        DEFAULT,
    9 ]8 @" i4 c+ g1 P9 v& j    NO,
      `( n8 C5 G* g& g1 M    INTERFACES,: z& F) N+ v" ^2 G1 `
        TARGET_CLASS;- e; U5 ^8 t$ f6 ]0 c$ D
    }0 _) D+ r  k, \7 k
    前面3个,不讲了,直接讲最后一个值是干什么的。
    " w/ H( |6 J; X" U
    * K( m3 T; T, L( k当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    2 i' T3 G5 ]6 U- T! \
    . p: j) [  `- x* f- _理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。! L1 b3 y0 |6 K) @" ?

    % k8 D" \' N; r+ B$ Z/ b' E2 G自定义一个bean作用域的注解' ]! x* \4 x$ c7 i5 z

    * x% r9 e4 e; u9 X7 h7 c& mpackage com.javacode2018.lesson002.demo18.test3;
      m4 U; K, J' n8 o; y( {! D
    # x4 {, T' X$ j8 ~import org.springframework.context.annotation.Scope;
    * B, u# G' ]" e+ x) Eimport org.springframework.context.annotation.ScopedProxyMode;
    - Y" b5 z# ~* T" \9 M4 e' A
    # }( b% f$ x3 N' P) rimport java.lang.annotation.*;4 H9 ^0 t. }3 Z5 N7 `  j
    6 b/ }+ W8 k" j8 m: g7 T* ^
    @Target({ElementType.TYPE, ElementType.METHOD})1 `, R  A1 i; A- x) @$ g5 l
    @Retention(RetentionPolicy.RUNTIME)
    , }$ G  a; c1 D5 D: C@Documented3 \# j+ y  {( J8 k2 K1 {) U
    @Scope(BeanMyScope.SCOPE_MY) //@1
    " N) e- _) j8 Y7 S3 B+ S* ]public @interface MyScope {
    % K6 ^- T  J* N2 j. H    /**
    3 r  s/ ?* b2 \6 w- y     * @see Scope#proxyMode()
    * V' k) c  p* L3 ], _1 ~     */
    6 |: d7 ?8 e3 A' u    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    $ n& U: y0 A0 T0 N) p) R}
      U" p: F5 L* U+ W@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。( E  g/ K8 `1 N, W3 w+ ]+ m
    9 a- ?8 y2 |5 A. a; y
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS; C5 R0 v% A4 K, X9 g9 e  v% ]) c. @+ P

    / W: x6 Y8 R* Y; Z* E@MyScope注解对应的Scope实现如下
    ( C8 }1 T& V- ^& B. r: f. ]( i; u( F5 S$ h' K, D  w9 Y+ K) F# Q$ v  `
    package com.javacode2018.lesson002.demo18.test3;  B3 t) F4 B  {8 b8 p

    ' r+ m+ e; w8 T1 `import org.springframework.beans.factory.ObjectFactory;- x5 o4 k/ H  R4 v4 G: K
    import org.springframework.beans.factory.config.Scope;$ s% ?- I( A( M; `
    import org.springframework.lang.Nullable;- b: T/ E+ \0 {
    + F, H, r% Z2 V+ p/ R* r
    /**
    6 s5 E; Q# g# Z" C * @see MyScope 作用域的实现0 o% R1 W. L; Z& k* ]9 O
    */" g4 y$ p  j# K. r9 C8 j
    public class BeanMyScope implements Scope {* {% m/ d3 F- r7 F% ]% e  @

    5 H* X$ R) U* Z4 r, y    public static final String SCOPE_MY = "my"; //@1
    ) p" U8 M% h) t3 O* v, n( B0 P; V7 b# h* L$ L
        @Override  c3 A1 o9 N$ x( x3 J; |. l
        public Object get(String name, ObjectFactory<?> objectFactory) { # Z% P6 m- n/ u8 A: H/ B
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2/ {. @5 h1 e% q( b
            return objectFactory.getObject(); //@3
    , H; l# i! @5 T    }; K% L, l% z  T% n4 W
    8 p, N0 w/ y& B. D
        @Nullable
    6 G. j" A7 k# p# d! Z0 U$ d8 w- [    @Override4 \9 ^6 c  B; @
        public Object remove(String name) {
    : @8 b" D' f1 e6 R) ^$ M6 a        return null;
      E5 k+ @4 c0 y# |* P8 y    }- C0 b& X- {/ ^) d( n5 z

    , \( m9 v9 R5 {5 C5 B    @Override
    - u3 w. q# d  s% r7 M" q    public void registerDestructionCallback(String name, Runnable callback) {$ b9 @: ~2 r  I4 P- \) f! J0 [# n9 b
    - ?2 ^0 |$ d; O( q
        }) ]0 F! ~  v3 r. V. U  `  w' _3 F  u

    ! \6 ]% S, u, H0 y# E/ ~6 m% c$ E  S    @Nullable$ {0 v2 H+ x/ d/ L2 t0 W
        @Override
    5 j6 U& ]0 c" a) G" g    public Object resolveContextualObject(String key) {1 K9 R# ?5 [4 w4 N# [% ~/ C
            return null;
    ( P4 I9 j+ Y4 R, N$ E5 k    }
    # c9 N& V) W& V5 \: j5 W5 C$ j6 h% ?4 E& \
        @Nullable" P, W. v5 T2 ]9 }* f
        @Override
    3 p* a  A* T9 |5 U5 l+ U8 @7 Q    public String getConversationId() {
    0 @& O3 O/ R! a3 D9 A        return null;
    0 o/ @( Q% T# a- i  d- A! [5 A    }
    * D& R3 P7 _) X% {}
    * Y8 m# a7 [. Q( D: n8 s5 g, U0 J@1:定义了一个常量,作为作用域的值; U7 n, B- n+ Z0 \9 R' [! ?4 B

    : X  p- E9 V& E* _@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果; a  F* @$ b* }3 O' R. {+ [
    : u. ~! a/ P& C! N
    @3:通过objectFactory.getObject()获取bean实例返回。; \% i. u# {6 s( C

    - l8 r; l) H4 a下面来创建个类,作用域为上面自定义的作用域* G3 c' ?! w* `+ v
    8 N5 k, G6 R. |# _% u: O2 U
    package com.javacode2018.lesson002.demo18.test3;
    0 ~# y, L" |7 U+ j) B4 Q1 S4 L3 G3 x- x/ m$ p- _
    import org.springframework.stereotype.Component;
    # r7 v' L5 J. _7 {2 n
    3 a) X0 G7 `( V' k5 \/ Qimport java.util.UUID;
    ) K; K: J- H0 ^, e+ M( X& w; G1 y6 K0 _1 P8 K/ t
    @Component
    ! |8 e  F4 W7 h. C  V1 S- Z; L- K@MyScope //@1
    $ t# b+ i7 x7 vpublic class User {9 H5 @0 x! H! f, p3 G; K1 K
    - A$ d5 J  {  [  ?
        private String username;- T4 m5 k: I9 x7 S+ M& d2 B
    3 Q$ J) A( y' ^7 K7 c$ ~0 c
        public User() {
    2 i6 G7 k9 q6 Y( w, O1 M' |) i        System.out.println("---------创建User对象" + this); //@2) V8 x4 K/ ~$ L9 G& Q
            this.username = UUID.randomUUID().toString(); //@3
    - ?% S) I5 \6 Y4 v7 e% _' F- ?    }' q; A1 ?7 d; T) X
    / H5 i. s5 D! c4 F6 Q/ r
        public String getUsername() {
    ( x/ Y5 `# z3 N        return username;( j- d$ y, J- j; Z# |
        }
    6 g  G" v  k% G/ v% p2 W
    + z# W: W3 c( M! P0 `0 t9 M+ e# g    public void setUsername(String username) {
    8 j+ v* q0 [' U8 B        this.username = username;' \; w% \( Y+ e4 r9 E
        }6 y4 Y4 \; J+ f4 k. C
    ' t7 l4 Y; I) c4 q3 d( V; D7 g
    }: u8 ^5 `3 [& H+ c7 }' f0 k* R
    @1:使用了自定义的作用域@MyScope, e& X) B& D6 }  d9 E. Y7 i
    1 e1 |/ C& n. Z2 {6 v2 q
    @2:构造函数中输出一行日志" L$ l: U5 A* [6 ^' p+ x

    * e  o& O- D1 ]" ~& L% ~! a@3:给username赋值,通过uuid随机生成了一个4 `$ k( U' [+ S4 b' a* P
    # a+ k* T8 h4 t5 l+ G
    来个spring配置类,加载上面@Compontent标注的组件
    + g5 r5 q6 @; R/ j" e
    6 _  r2 S3 m9 t7 d3 ]( Upackage com.javacode2018.lesson002.demo18.test3;8 V7 Q4 ~5 m& b, g

    6 ^4 q2 A( k9 m1 C; ^import org.springframework.context.annotation.ComponentScan;
    " B& \. Y8 w) a5 wimport org.springframework.context.annotation.Configuration;& ~7 B) f$ D1 v. ?8 i

    ' K* t+ J% K5 M) U' |6 [' M& B@ComponentScan" H7 V, b- [! b) v
    @Configuration& T/ B3 m, w8 v7 ~
    public class MainConfig3 {
    9 O! u/ T/ d8 f9 L0 R; g$ x; e+ G}  j4 J, T' U; ^: n
    下面重点来了,测试用例
    * ]6 r% z/ p8 D% u; c* X' F' ?1 L! H1 p' T3 T
    @Test
    3 d- E& g9 K% M( y% Zpublic void test3() throws InterruptedException {
    ; H! m1 i, c) B5 h" \    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    7 f6 a$ `+ W$ b8 R$ J    //将自定义作用域注册到spring容器中* w& U9 d2 g( e$ s6 G  O6 f
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    . H( L# j- Q8 y; W    context.register(MainConfig3.class);
    ) G$ k) M5 r5 z" {" x    context.refresh();4 D; w) @' O9 s, U

    0 C0 ?: B* t+ j    System.out.println("从容器中获取User对象");) q2 |1 W8 t( Q
        User user = context.getBean(User.class); //@2
    : Z! C0 D  ]. k    System.out.println("user对象的class为:" + user.getClass()); //@3: [, Z: q4 j/ M8 N* c, z7 G
    / F4 y! Y3 j0 _. M
        System.out.println("多次调用user的getUsername感受一下效果\n");
    7 X; ^; p( j: i0 J    for (int i = 1; i <= 3; i++) {/ j: b4 u4 B; W! s/ {9 j
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));* w1 x( R3 B" P$ |0 I% D6 {$ v8 ?
            System.out.println(user.getUsername());
    & l# j( ^9 i9 P0 f5 x, J! O        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    1 G1 F6 M% X1 ^3 m8 G) @( \  E% U    }& T" ~2 j, `$ a( \
    }, `0 t1 q  c. U8 e% ]) l9 w
    @1:将自定义作用域注册到spring容器中
    * R6 p  B) T1 X, s5 e  }
    - h% n1 }8 y/ O9 C3 H@2:从容器中获取User对应的bean6 f. i+ S9 ]' Y$ X" T5 w4 z4 ^
    7 i/ D, y) h& n- o
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    / C9 y/ @9 T6 w# P: ~- ~0 X, d* U! E8 K8 [0 L2 q) }+ a
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。  f* n: L( i0 _$ ]! p/ n

    3 Q6 p% L+ e/ e% o3 ^) f3 I# J6 u见证奇迹的时候到了,运行输出; [% v& \, T% h* s
      W9 Z% i9 U& m4 j
    从容器中获取User对象
    3 ~4 f% h! t7 _( d5 c! {+ B9 yuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127& h6 |4 y3 V# P- A9 |6 u
    多次调用user的getUsername感受一下效果# m, B$ k& \# K. d: H6 _! ~; i
    ) z: b9 O- e9 t3 Y
    ********
    2 Z, p/ Z; V# X& M* P1 i第1次开始调用getUsername' B+ K9 b) h# ^" A9 \. r, S) [
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    6 u7 R% A, ?# I, i& v( `/ `! Q  f) D. s---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4$ z4 W9 H. O: {  ^* O
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    % K- y! i: `6 Z( v1 N6 Q第1次调用getUsername结束
    ' t/ U# @8 u- C. v6 r9 v********
    / b6 c2 f; h, \! s. y5 e
    & s, Z' e4 y3 b/ `********2 s' ^; ~2 _$ b0 Y3 m
    第2次开始调用getUsername
    4 _0 u/ W7 W, }, s9 Z& sBeanMyScope >>>>>>>>> get:scopedTarget.user
    / w: y# d( J5 q: n7 L  ?---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    , g) t1 f  ?& j/ b: |4 t01d67154-95f6-44bb-93ab-05a34abdf51f$ C% v/ r3 E, K+ X
    第2次调用getUsername结束/ g, C6 Z+ B$ u& O8 q8 M4 _
    ********2 h* s  x# f' v! z1 x  t
    7 d8 k- o, _/ z/ n( l
    ********
    : X1 l3 R; [$ s! |" d, \7 e/ d4 z第3次开始调用getUsername/ q! F; A7 Y* u( H* s, b
    BeanMyScope >>>>>>>>> get:scopedTarget.user* I+ F9 P2 v( e2 t( D0 f
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15( v" e5 k+ q/ v/ V" J
    76d0e86f-8331-4303-aac7-4acce0b258b81 N' J3 [2 h6 v( B! S" s  Y
    第3次调用getUsername结束
    2 y  U$ `; v" Z- f& \********
    7 Y2 G% {; Y8 F" w! P4 k6 j从输出的前2行可以看出:* c# B4 y2 v/ e) }
    " u) o3 r/ n) e' U- i2 D
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    4 z1 c" B8 j6 Q' H2 p$ l) E6 o
    ' U' F$ z4 K& U& N& z第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。. }0 }1 U7 n% t; A1 K3 T8 k# J
    3 J3 O. L5 @1 [+ ]
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    / R" u1 Z2 Z# D/ U' o7 R3 L- w% g- J; y5 `& T5 ?# a' q4 n
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    1 ~# c: E/ k/ a; L+ l
    . F4 o8 t) e8 F8 w- K, e动态刷新@Value具体实现
    ! b1 h6 _$ c  w, B% A" h
    ) W4 r8 `* h# H2 C  y3 q那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。+ c3 p) I/ w, y1 s

    6 V% w& o$ ?5 g6 X  d先来自定义一个Scope:RefreshScope- |' P2 i3 G/ i# C" h3 A6 W3 c
    8 u) Y  Q9 N& s
    package com.javacode2018.lesson002.demo18.test4;
    9 T/ f4 ^  B3 _, S% I; p
    3 x% A. E! v6 }7 rimport org.springframework.context.annotation.Scope;1 y; v* \0 u8 b0 \" a2 j
    import org.springframework.context.annotation.ScopedProxyMode;# [0 J' E  u3 g

    # d* B6 M7 m5 C0 B5 limport java.lang.annotation.*;. J* g8 Y1 w3 A5 R  N& S
    4 H- H/ u8 d2 q" U* t3 H
    @Target({ElementType.TYPE, ElementType.METHOD})  s+ Q0 ]( |7 f
    @Retention(RetentionPolicy.RUNTIME)
    , j, }2 t) g# v( }5 D7 H2 X@Scope(BeanRefreshScope.SCOPE_REFRESH)$ A4 l: {  b6 r% z
    @Documented3 c" H( t- P+ \; g+ ?: W
    public @interface RefreshScope {
    ' O* L& w* @$ o& z& E& F3 ?( {    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    $ e% o) W0 `! O7 z}
    9 X! @, H& h- E要求标注@RefreshScope注解的类支持动态刷新@Value的配置9 |. w) k9 K! F# T/ f

    / F% k: a/ ?5 O0 M* R  w@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    7 z! d# @: m/ ?9 H' X6 ~) S7 B1 c* v, l* L* v# Z% C. I; {$ J, k0 K. R/ ^
    这个自定义Scope对应的解析类- v2 F' ^+ j: z+ R( K: x5 @

    - n) {' l* B6 C  J% G! A5 }: a  V下面类中有几个无关的方法去掉了,可以忽略
    6 M1 u+ R/ ]9 Y/ a% \& L& @& {0 j7 D+ J" u0 ~! }6 E. v% t
    package com.javacode2018.lesson002.demo18.test4;4 q: R5 \: W6 a& t% [* R

    5 t+ v$ ^7 y0 L
    : J/ L9 U. d# c7 r+ Oimport org.springframework.beans.factory.ObjectFactory;) d4 k& Q6 n9 F- q0 C2 J
    import org.springframework.beans.factory.config.Scope;
    # P& A" L) C5 M& q9 G0 cimport org.springframework.lang.Nullable;
    : @' u' N2 H9 K. Y2 l4 t1 e) A; C$ W. k
    import java.util.concurrent.ConcurrentHashMap;
    0 |, M" a$ j7 C) m& B( `1 S
    1 j! [( g8 s7 @7 Npublic class BeanRefreshScope implements Scope {
    ( v, I4 @1 e0 a9 g8 Y& M  V. G; U
    2 e; \2 W* E: L- P    public static final String SCOPE_REFRESH = "refresh";% q4 R( {, s( D  r0 A

    : H+ w8 \- `; N- b" @% o2 j, i    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();8 o# D- k" j  p
    + U$ }' Z, H% B; t! O) U
        //来个map用来缓存bean2 b9 }4 u) d  U( w: M9 Y! ?
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1. ^7 ~3 q* n$ k$ Q: q+ C
    ( u. {. F* x% l( a
        private BeanRefreshScope() {
    ; ~" `7 t, b2 S    }
    ! E5 N2 e  `; a& k: O  p6 A& E0 ~" g4 U  W: I4 Y! {! l
        public static BeanRefreshScope getInstance() {
    7 j% z# i: t4 c* j7 L        return INSTANCE;
    : o' [; w1 g6 a    }# O; u0 l* ^3 R( t
    1 t5 K+ A2 |1 q: ]
        /**
    3 v1 V8 n( n, S2 r: J5 o     * 清理当前# f5 |6 O+ q. B: W2 o7 N9 L- K, m
         */
    8 E" K: l6 S2 t! S; {% S! R3 I+ d    public static void clean() {
    % ]' [# }/ @( q7 a( X% k        INSTANCE.beanMap.clear();% x8 c( H( w0 @7 I' T+ M0 c
        }
    ; Z! N5 |0 |" s  ]1 A
    : c" i/ S# c' c( x    @Override) S0 f1 Q6 E) `" J- s4 s4 s
        public Object get(String name, ObjectFactory<?> objectFactory) {* x  G2 p6 L- O" k& N
            Object bean = beanMap.get(name);
    & `. q) W. Q# S5 @% C7 H+ D        if (bean == null) {
    . g* g) ]5 Y3 j) i6 \3 G            bean = objectFactory.getObject();/ I5 G: I3 e2 F& g+ M
                beanMap.put(name, bean);
    - u6 E, C9 L$ t9 Y        }
      R1 V1 A9 U( V1 g% [4 _& n        return bean;
    : k. {4 `1 O9 G8 t' F2 @    }
    ) t+ ?' m* n7 e& V4 q) I5 B- g
    ! t0 E# }2 a5 \8 P  {+ ]}4 h% a+ {8 y1 S1 t+ B1 y
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    ; W* e: c4 T- x. a, f5 f3 J; T9 A& p1 }3 y
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    9 u7 l6 f# H7 `9 |, A' c% a7 d! l  l' J  h, C
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    ( a+ O6 {, L: v) M2 d1 ~
    3 \! N9 @( M6 B7 U. O5 ~( @9 hpackage com.javacode2018.lesson002.demo18.test4;0 r) w: u2 z5 d9 r% P5 j% e

    ) ?7 e( F/ w+ K, \import org.springframework.beans.factory.annotation.Value;; `- ]# V7 d+ M! {, @9 c! K
    import org.springframework.stereotype.Component;& y4 l3 a! j6 t$ I  s6 \+ j# e% Q3 I1 H

    6 z1 J  Z/ q! x! i# X# a8 ?/**3 g4 g: B2 \. c, ~  {/ J
    * 邮件配置信息
    & `( v( p4 L. M. } */
    9 y* J/ _+ p; }( j/ F4 C@Component* E" E) \: i# I9 ]: F
    @RefreshScope //@1! J: g6 _1 o7 l: l) [9 Z
    public class MailConfig {* V5 [, @2 h1 r! C$ |
    . }! P4 P% z  B4 p: T/ S
        @Value("${mail.username}") //@2
    ' a' L1 h5 q# L' Z0 w$ g) M    private String username;
    / |+ ^% k# L' w. d5 s
    , s3 z6 ~9 ?( k8 w    public String getUsername() {3 h( h1 C& K: t
            return username;; Y' z7 c( ]2 Q5 y
        }3 l$ d9 D4 ^" s5 ]' h4 J

    9 l" z1 z+ X( \  j8 x. g8 p    public void setUsername(String username) {. r& |8 }1 L, N, H
            this.username = username;$ D) u! u; Y) k
        }
    % b0 Y: d' o6 C9 H- c, W0 k- q4 d0 T! V
        @Override
    4 n/ \. g% ]  P    public String toString() {; g: @/ R1 |: F4 B  E6 d" d
            return "MailConfig{" +0 i9 K) |; B9 \+ n% S. @! b
                    "username='" + username + '\'' +) r6 L/ }% p% d! a5 t; L
                    '}';
    % \+ D% O% R. D+ p7 C    }
    , L$ d1 D( q0 P9 _# n}
    ! w. E* Q6 ?$ g4 C4 ~. D% y3 c@1:使用了自定义的作用域@RefreshScope! ?; ?7 J1 T2 `( B  s

    6 X) J3 F6 ?. i& ?$ q@2:通过@Value注入mail.username对一个的值
    3 j& M2 u6 o2 ^1 O
    , L+ l, @4 ~: Z: W% @; M0 R重写了toString方法,一会测试时候可以看效果。9 ?, e+ t% t8 [) _

    " s8 R) U+ j9 I+ I% [6 C% N再来个普通的bean,内部会注入MailConfig/ |% C. |$ {1 ~  w( w3 F
    # I8 W3 d: K" I: ]% \5 o
    package com.javacode2018.lesson002.demo18.test4;
    - `( I3 c) |7 O1 v  H$ n% B0 z; f* ?% k: k1 D6 _  u
    import org.springframework.beans.factory.annotation.Autowired;
    0 O. ]: e/ B" h% Oimport org.springframework.stereotype.Component;, r7 W1 `# o5 R$ K6 G- E- b3 A
    " B9 |+ ]; V1 x
    @Component
    * V2 S7 ?8 A% z  s8 Apublic class MailService {9 Z  @' d% Z8 x, V: i8 Y" ?: C2 `
        @Autowired0 B+ e- k6 s9 ]; Q
        private MailConfig mailConfig;
    : l; ?! Z, }4 e, B7 o  o7 D
    9 ^; [) d6 [) f3 H0 i' B    @Override' [' z; C1 M1 N5 q! R; _2 h
        public String toString() {
    , X) j; }3 ^7 Q% i1 i- g        return "MailService{" +
    : L, J& S' U' `  v                "mailConfig=" + mailConfig +- \* U1 P! f. ~, F5 A
                    '}';
    $ f) j* G" T8 s: Y& `3 a# c    }
    " }. g+ J2 v/ C- r' B3 c& `/ p}8 Y' D( Z# N9 `) f/ x
    代码比较简单,重写了toString方法,一会测试时候可以看效果。" Z) s" n3 S# R* W! Q
    2 w4 Y# F+ o1 S" _0 W
    来个类,用来从db中获取邮件配置信息# H8 {0 E( `5 {$ W. v
    9 j6 w/ F; W5 K* U3 A
    package com.javacode2018.lesson002.demo18.test4;
    8 x3 y+ i, O7 j* L2 l
    ' \: n# U  ~' v' @& e8 `& e: e  Dimport java.util.HashMap;6 V) u. m# Y5 h# U
    import java.util.Map;
    # N& ]# F9 ]( m/ uimport java.util.UUID;8 Y& c. j0 @. D& J2 u: i3 W$ @9 Q
    4 Y0 Z' Z, J* u" M. |
    public class DbUtil {/ ], Q0 d" o! I: j) r5 b7 \/ ]& z) C
        /**
    $ u& S3 z( d! v, q     * 模拟从db中获取邮件配置信息
      l- ?; p) p/ {6 @" q6 z8 h2 [' Q8 @     *; F3 @, M) U6 C' ?
         * @return$ n1 [: }* K3 {3 N( N/ p
         */) G, `1 j& s; ~3 h# c: [
        public static Map<String, Object> getMailInfoFromDb() {
    0 [6 R. P. g% `$ q' C        Map<String, Object> result = new HashMap<>();* ~. L3 U5 T3 _/ h# z" Z" m
            result.put("mail.username", UUID.randomUUID().toString());8 F; D+ p- `2 e8 F- y
            return result;
    2 a3 ~6 Y1 Q7 U0 y9 V    }
    : U8 K' V- }5 d7 o$ w; W}  d( j& I9 H3 n/ I0 z9 Z
    来个spring配置类,扫描加载上面的组件/ S) t5 c% D) O% l3 z
    % v5 ]( v' }# R( S9 p0 ?5 F( L; X" k
    package com.javacode2018.lesson002.demo18.test4;
    + q- d$ O: w( H# P" W* Y8 b/ Z# s* V: s  j" H
    import org.springframework.context.annotation.ComponentScan;( }; M" N- J5 v/ S  H; ~2 b
    import org.springframework.context.annotation.Configuration;( ?& R9 i$ _" l) {2 `/ H9 x& Q

    7 n  j4 @( q2 N0 C% z% ^$ S  \/ E9 w@Configuration
    * n% }. r/ y  c+ w6 h0 L@ComponentScan% s- b' Z8 J/ ]8 `
    public class MainConfig4 {
    / ?8 e# R; o/ `5 r9 Z! L0 i}- x' H" x- O  F0 e( I8 M  G6 |
    来个工具类
    * n% r7 [4 i! N1 o
    8 _: a+ u$ {! v8 f内部有2个方法,如下:
    ' {8 `, q+ k/ e. V6 q5 q
    ! m+ Y& v5 O& E$ G5 Z1 O( _% M5 Lpackage com.javacode2018.lesson002.demo18.test4;
    - R5 y. C5 v- Q5 R9 K; ]* ?7 [$ c; F/ V
    import org.springframework.context.support.AbstractApplicationContext;% M4 R, u1 E+ {! m
    import org.springframework.core.env.MapPropertySource;# E0 Z( W& J: p* @" W
    ! s" G' @0 Z9 [7 Q2 q+ _1 J
    import java.util.Map;) x# n3 c, o) |' k1 Y( w7 L5 G2 L, r
    $ m+ s- K# {8 @. j6 Y, _& R  k* C
    public class RefreshConfigUtil {
    2 N2 ^! e; C+ {0 [    /**  w( f2 c$ z. Y5 \* y" @& x  p
         * 模拟改变数据库中都配置信息
    0 j0 T& |. s: {8 h% ^( ]: J$ s$ _     */
    ) ]3 |: o/ a5 K2 z6 h: a    public static void updateDbConfig(AbstractApplicationContext context) {4 ~' o' g/ \7 D$ X1 b
            //更新context中的mailPropertySource配置信息
    , V7 C9 t: G8 M# Q% @        refreshMailPropertySource(context);
    + [3 S8 Q+ g$ X4 h4 h/ z, D; h6 M8 \: }
            //清空BeanRefreshScope中所有bean的缓存, G2 N2 [+ C$ j* f
            BeanRefreshScope.getInstance().clean();
    2 v% B7 ?; e: A9 x9 L4 G* d! C    }  o& j1 S2 D8 n7 n+ ]( x
    4 \4 o% E# ~$ p6 \( D6 h
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    , z0 a, A, \9 r; F, Z; m8 v+ y        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 d3 |6 z& a3 y1 E
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 U5 C' k# q, L# c6 G$ _
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    " ^* m. ~7 \0 ^+ W- ~        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);7 {7 a" e7 t' \  S: H2 [
        }
    6 R  k1 P2 Z  s% n8 P" j% [# @$ M
    ) n* U' r+ f( Y2 F' I& l}7 \  l! P5 q, U
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息) L  i$ l- ^: `' x' D% Q

      z) {2 t, g6 _; iBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    6 X7 D/ X6 ^: J1 n1 j; w9 b2 M. n6 X2 B
    来个测试用例
    " j' I& L) \2 @7 ~
    6 D4 y5 {1 h8 ^, {. x; A@Test
    # j# l- T% V! W4 J7 h9 zpublic void test4() throws InterruptedException {
    5 }% \# q% A- R: [- `+ A2 i& S    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();- \) H( z' y" \/ d5 |" F% W+ \7 X
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());; a- Y$ U' t; \. J+ w
        context.register(MainConfig4.class);
    7 w5 ?; U2 O+ |6 H- i* ^    //刷新mail的配置到Environment/ F6 w5 e9 s- D7 {& P
        RefreshConfigUtil.refreshMailPropertySource(context);
    ) Q5 j2 Z" U$ V) D* X    context.refresh();
    ' h( ~7 z' N7 R3 [/ X
    5 f9 X+ a* D, u9 o    MailService mailService = context.getBean(MailService.class);$ s# E8 r% t" _6 B" v6 y
        System.out.println("配置未更新的情况下,输出3次");
    # ^# f  Z$ l/ b$ i; s7 U    for (int i = 0; i < 3; i++) { //@10 u# g3 S/ B  n
            System.out.println(mailService);! Q! X* c3 L% ?* o. Y# t6 @: @5 I
            TimeUnit.MILLISECONDS.sleep(200);# v7 d) J/ o+ L* B
        }
    / k" p3 I9 P3 m& T/ \
    ' s) f7 j0 R  C7 L  w+ \    System.out.println("模拟3次更新配置效果");
    % d+ V+ j: N# j0 l: r    for (int i = 0; i < 3; i++) { //@2
    ! v/ t1 a7 @$ }. P0 @        RefreshConfigUtil.updateDbConfig(context); //@3
    . D6 W# Z3 b/ b& G, X6 G        System.out.println(mailService);
    $ P8 _* d9 _9 S! w% I+ @        TimeUnit.MILLISECONDS.sleep(200);
    4 x9 A7 S& T7 `. y; b    }+ H, d7 j5 k- ?0 M8 s" Z
    }  M8 a, C, T% V; v- a  G( U
    @1:循环3次,输出mailService的信息' _. n/ _- r4 D

    + |' P  Q$ v) z8 s; Z6 k@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息0 p, ~, T5 i1 {3 n' H( w
    9 D6 d/ r. m- X+ z; k* U* R3 s
    见证奇迹的时刻,来看效果
    - o% {; A4 b& W# V! m9 K* N% r) p% Q
    配置未更新的情况下,输出3次
    ) j. s# l+ d+ y/ nMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    / T9 U. J+ j7 Y* r  |% \1 ~MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}8 D  x+ Z; ~$ x$ G
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}- L$ e, `: X( C
    模拟3次更新配置效果# }9 h- s9 @+ }
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    - @% {) B5 e5 i1 iMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}$ W1 z( N7 x) z% v
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}# ^* s. @  N; p! E
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    3 D) {( y& S3 X- v% k1 _3 k: W7 L' W0 \1 d
    小结. z6 J  G9 O9 k" P. G' l

    % E3 o+ r1 Q- w5 W5 `8 d+ N动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。6 K6 k' s; e) T) `' z

    % W8 A  B& W9 ?有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    % q. v3 W; r' j2 j- T  ^% Z9 z" r% k8 D. D9 l$ I
    总结) P% q; I7 ^# ]  P- `( B
    2 |& P- \. ~% q) s  p+ P
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!3 \  ^  R" e8 j! A2 _5 c

    . l+ `) U9 J. Q' ^案例源码4 L6 s) P/ P4 H7 b7 w
    $ r& x( n, y5 W; w# L0 Q
    https://gitee.com/javacode2018/spring-series
    / V! {5 y2 K% [  }, {2 ?路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    5 m- [& [. D. b( C7 I! H+ i7 z' {/ F/ [( f) j
    Spring系列4 x5 `) r! z9 I( G' e! J

    ; V5 P0 w; ^) I5 g! u) d" TSpring系列第1篇:为何要学spring?! f! E# k: f1 X5 k0 W$ t7 A7 _

    - y; O4 x" ^% i8 tSpring系列第2篇:控制反转(IoC)与依赖注入(DI)# v" b% e7 j2 T) _& ?! Y
    7 T9 {0 B2 h. Z( V4 b
    Spring系列第3篇:Spring容器基本使用及原理& U$ a; h; m, d* c

    & ^& m" K8 e$ w# g+ xSpring系列第4篇:xml中bean定义详解(-)( o; x- b, p- _+ k
    2 N& z# ~$ b, z$ T$ h
    Spring系列第5篇:创建bean实例这些方式你们都知道?, B0 k& w4 D% |  o- c$ T( h
    $ x6 ^- d( U- s' w  G* {; F$ ?, E" J
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    7 ?) w0 ?5 @5 Q' d2 s1 R: Z# l5 q. A; }
    Spring系列第7篇:依赖注入之手动注入& f" K1 W/ J7 |6 I, A9 u
    2 r2 b' K4 M1 w& S
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持4 u; ^0 @8 ~$ |( m1 x6 u* S' Z1 P

    " p' G- i& o# W& ]Spring系列第9篇:depend-on到底是干什么的?8 }5 w& C$ \+ d  b2 L/ I( p0 V8 A
    ) b- a: J- f3 D: m* ?. E/ `
    Spring系列第10篇:primary可以解决什么问题?
    ( n6 {" N0 L$ V1 D( z! N! N* \8 w" e1 P) M% w. M8 `
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    # _$ }( O8 J7 y5 h! z' k: m. s& q5 m; I+ O2 m- g7 ]
    Spring系列第12篇:lazy-init:bean延迟初始化% T( a; O8 `+ a- G7 x

    : M" e" J/ p# H0 W" ?% t# d. QSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    ! M% j& T: @$ Q4 t% z; e
    + l; `# R+ @3 C4 }/ @Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    1 H- w5 Z/ d# k. s7 N7 w
    , T6 o- D' [3 R( R; GSpring系列第15篇:代理详解(Java动态代理&cglib代理)?! `+ N( F+ x! V- S" e
    ! |6 \" f: _# \
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)% k( @! Y- y5 q7 K/ J

    ! {( }" F: L5 F) m, Y2 D! QSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册), G+ d9 ?; X" t9 E$ X0 ]) U2 p4 c
    . I4 ~/ e+ _/ E
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)5 [, E+ k4 V* W1 V, Z4 h/ `& v1 l
    2 o) o/ V2 {3 g' I6 g
    Spring系列第18篇:@import详解(bean批量注册)) [3 y0 s+ s6 k* `8 A! \. ?3 Y' A

    + T8 ^$ p: R! ]# PSpring系列第20篇:@Conditional通过条件来控制bean的注册  j2 o# @& o$ J) ^
      I1 T/ I- J( h$ c) o
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    9 W: L6 T# f' n4 h
    9 L9 N5 u; t$ ^2 u! n) fSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解- G  p5 ^, R  y/ I- f
    - R6 C: T9 Z7 y
    Spring系列第23篇:Bean生命周期详解
    2 H  h. ^) _2 x/ ?+ ^6 P8 ]0 q' u+ z' {5 G
    Spring系列第24篇:父子容器详解
      {) p0 K/ H2 J! Q) ]! }. J5 ~, E( T! m2 z
    更多好文章2 g" |' x" E6 ?6 t- o

    ' A% F. s5 y# w& SJava高并发系列(共34篇)
    8 i1 L/ v7 X8 C% v& ]) c8 G* A& S) g/ m0 [6 Z8 ~
    MySql高手系列(共27篇)) G0 C  Q9 O0 p! O7 @8 n# m
    2 O5 i) v  B: ]" q* M
    Maven高手系列(共10篇)+ ]+ ]8 O# ]/ w* d* i

    6 ^, [! z$ s6 s9 B6 {# oMybatis系列(共12篇)1 V; Y2 L$ F" _3 ^

    0 J- _5 z: l9 r聊聊db和缓存一致性常见的实现方式
    5 S3 E$ S. I4 _) ^+ T0 g- v6 i8 v) d% ^
    接口幂等性这么重要,它是什么?怎么实现?
    7 o3 I8 |, {5 U; i0 A2 Y8 `: s1 L. o7 x
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ! A5 e: `- K( V$ c) n" q+ Z& K————————————————$ p2 R/ L2 p( l) C: M) E; X
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    $ g) i3 \1 b' Z$ Y原文链接:https://blog.csdn.net/likun557/article/details/105648757% x( t- |5 A4 {! o: ~
    9 m! x( N! I. t. a" n1 r
    ) v7 ~+ {/ P, x7 P' m: ~
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-4-20 11:01 , Processed in 1.254628 second(s), 51 queries .

    回顶部