QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5086|回复: 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!
      t0 [- n" D7 \( f5 g疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!4 N+ j8 y1 _. U6 h, l6 p1 T

    * p# {/ p4 C/ G! p% m9 I面试官:Spring中的@Value用过么,介绍一下) ~  _5 {$ v) n7 S1 E2 \* K/ b; L2 u# i

      A7 z7 H( P8 R- t" \/ O我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中( ^4 W& ~5 ]( j, \  B" i# {# Z. M1 G( F

    ! Y9 Q% _5 o9 M) |0 d面试官:那就是说@Value的数据来源于配置文件了?
    " {* V/ h) D- J. T; T) a8 Y& [* `7 N; `+ o8 V7 m
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置2 `$ x9 m% ?2 T' Q: {* g

    4 c2 N. y4 _/ A1 g" ?面试官:@Value数据来源还有其他方式么?
    9 c& r. m1 p( _. }$ L3 N. v  |+ l/ P- T6 s1 E/ i& P
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    + h8 O4 a2 @6 r2 Q2 M, r* E4 b1 a3 j8 ^) q& H6 @/ F
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?: _" n, x8 f1 z7 H/ _, r
    # p: K2 R/ [) D9 J0 o
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧8 D9 D0 {0 ^7 D$ e+ b1 K$ i# p; n

    % d, i3 x6 c$ u  Y5 U' T( r+ [面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?5 Q0 n+ O' Q3 \% [  |) U
    * a6 b3 z9 ~1 Q/ t, u& U  \/ z
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    - c8 ~1 g4 x+ X! H/ p) Z6 p2 }+ E1 A
    1 _9 ]: f7 z1 S! B# d# M& j, W面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    9 H0 v4 H! x4 t
    ' U5 m: {6 `  p8 Z我:嗯。。。这个之前看过一点,不过没有看懂
    + B! a7 b4 a" g7 k- q* N7 H8 D; [8 w3 s0 b: e0 B# L% J5 u  K
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?  [- P. P5 d; r3 Y
    5 p( O& s; F; {$ A+ V, f0 p  j
    我:3万吧0 a( w+ B9 l. z

    9 Q4 K# w* H, |" w+ G' a. J" B1 t: Q面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    ! P- s6 g" n3 ~! u3 b8 \8 M8 Y" w
    - q2 V# ]+ T3 K: a9 s& {; K  F我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    7 b$ Q- S0 Q; ?+ F9 ^3 ^
    ) ~. P5 v3 Y2 d5 D, O/ E; ?( p面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    ; P8 ]' s3 m1 U  S3 V: }, {  Y0 A: N# y
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。) u3 _1 ]7 v. J# Q8 g& u& N
    % N& ^6 A7 P- N4 o" s
    这次面试问题如下
    # R8 E$ j4 |7 O& p* ~  v; E; Y0 j! }1 L7 q0 p1 x
    @Value的用法
      l6 H1 k3 ?6 l9 e# @5 b4 _2 Q1 k( e# O& Z
    @Value数据来源. D. \$ w. P7 B
    : w* z2 h4 l  g0 d  E- ]
    @Value动态刷新的问题
    " u. e9 p, M6 D( j6 L$ O8 c- V# @6 K0 A  {1 w/ [8 S
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。+ K4 a7 B, s% Z$ u6 p1 S2 F% L
    " g9 N$ W+ i: |$ d1 B& C4 |
    @Value的用法
      z1 l0 G  |# @: ]" W+ ]$ x5 t, @  |& F# r- \1 [3 C
    系统中需要连接db,连接db有很多配置信息。
    ; q9 K0 o! s( _
    0 K2 L4 E% {/ J' ?系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。+ v9 Z: |, j4 F( V  @2 C% s

    * V. c6 U; a( S+ c# T# W: a还有其他的一些配置信息。1 H$ h, d& v; W3 T. j3 X

    4 o+ k5 [( A( M9 R8 G6 r. S" Y我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。$ f3 D1 W' l' R% e, i! g/ o8 k! v

    2 I( e8 r, b5 W0 i, z. u$ }) G: [( b那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。) k/ V" Z! v, l) K
    . q9 P& z, W& o3 R
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。- v, f& Y4 e) H/ e

    - h8 o. i! ]# |0 X% R通过@Value("${配置文件中的key}")来引用指定的key对应的value。3 V- V+ D+ r; j8 s
    . a: k. C5 h7 B
    @Value使用步骤8 s) l, q  Q1 v. e$ y+ O7 ~

    + ?6 Q& g1 Z  T* T  t步骤一:使用@PropertySource注解引入配置文件
    ! X- _1 L9 P# C$ q5 o" d$ V8 k4 I" y0 g
    将@PropertySource放在类上面,如下
    " \; g, K, u. M+ R/ o
    ; H( Z5 Y8 K/ h! M+ T) O7 P; H@PropertySource({"配置文件路径1","配置文件路径2"...})5 A8 g% l& m& e( A& [, U5 l. @/ a
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    % a8 ~% c4 {$ o. v* j" l" @9 Q2 i7 X3 D7 {( s$ t  W0 H
    如:% L6 _# Q4 }) s. t
    & {5 C  B/ R, B
    @Component  p! k/ \# N7 d8 c4 c; ?- u
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    7 x( l/ h" N" r  bpublic class DbConfig {
    + u+ \9 a  d: O! ^}+ w# I& Z6 f! R+ U8 Y1 F! T
    步骤二:使用@Value注解引用配置文件的值
    ( X! m  E6 S' f: |2 ?9 t# U6 w. f9 [  L
    通过@Value引用上面配置文件中的值:
    5 q. s( T; e; k  |" `8 V
    / a7 U* M- g$ }语法
    ) r& q# e& Z' N5 `5 y  t8 ^( q
    @Value("${配置文件中的key:默认值}")
      @! ]! k( m" k: }7 U@Value("${配置文件中的key}")
    $ i0 H" p# [- g6 F! _如:, u  z; g; A7 [" @( ^: g

    & A2 p3 Y/ \% a+ f@Value("${password:123}")
    ; ?; ^6 V3 u7 x9 J上面如果password不存在,将123作为值
    ) H! q: K; p5 ~8 ^! g
      _; M: J1 s) {  S9 C: l@Value("${password}")
    7 j  ~4 _2 }' s* X上面如果password不存在,值为${password}. o& f5 I# P. \( d! j
    " Q- M: ?' H) ]/ V: z5 U6 p* Z
    假如配置文件如下
    & d7 N4 w9 t, O* n& n. S* l* P" s4 l0 C0 j& c
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8$ `& l, [) g+ G/ Y8 Y: Z) q
    jdbc.username=javacode
    # C0 I, z* u# F! xjdbc.password=javacode
    6 o5 n( A+ z' V0 J6 z3 c使用方式如下:- t  ]" b+ Z7 a

    ; j; ~! I. F( u) F@Value("${jdbc.url}"), [4 ]9 f+ ^% [4 g  _% H
    private String url;
    6 c& h, z2 X3 r2 {2 E  v$ ]/ b. y+ t0 {3 u7 Q
    @Value("${jdbc.username}"); K  y8 G) a0 d9 Z: r2 |3 O
    private String username;
    3 C  B- N; N/ v
    ( Y/ U' }! V( K, @- P) A@Value("${jdbc.password}")
    9 P$ Y+ ?* P* J8 E$ V7 I: nprivate String password;
    " U6 C( a: Q2 m1 w5 }! {5 i下面来看案例
    6 S8 w2 \0 k! N- ]9 t1 N
    ( i4 P; a! U  y; O3 s案例
    7 O( p6 ^# e" V/ Q7 f1 X
    ; R) L2 N% L( V/ h: E6 r8 ^$ E来个配置文件db.properties5 O  L, m9 A  u
    5 Z( K) |0 Y' c2 j+ `. @0 |
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8( v4 c! C+ m: G1 R; E
    jdbc.username=javacode
      Y# K0 A+ i4 {- P( `0 J( yjdbc.password=javacode
    # s. `0 g. @, D4 W# z) a来个配置类,使用@PropertySource引入上面的配置文件* w0 s  }* s* d" a' L% L
    , @1 F  B: J: ^( n
    package com.javacode2018.lesson002.demo18.test1;
    % f# y7 c2 p0 T% B0 p1 ?4 k2 d' ^& v( Z/ X' n( p0 v4 j; j# n
    import org.springframework.beans.factory.annotation.Configurable;
    2 x3 N& c! r2 p) Fimport org.springframework.context.annotation.ComponentScan;
    / ]4 v/ \: G0 F% D. Simport org.springframework.context.annotation.PropertySource;
    / R. L$ H7 q2 o6 S0 R0 i% i$ Q+ `% j& Z( T' X* n2 `6 K% E+ y
    @Configurable6 [. @+ e: B5 b7 g( ^( f' ~- W
    @ComponentScan: C* N, O2 [. p
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & Y+ h4 P3 ?( \9 L, Z7 L9 \9 apublic class MainConfig1 {* a+ V; h% \$ I( [' J- S
    }
    * B; Y. |$ V6 s, v+ D( }来个类,使用@Value来使用配置文件中的信息; ]4 b9 H* v, o% |
    ! Q; X8 l& V! j
    package com.javacode2018.lesson002.demo18.test1;1 @" i! I5 ?, e/ A! o
    0 d, i6 b9 w/ N0 I+ y4 P
    import org.springframework.beans.factory.annotation.Value;
    & r' v: r2 `  p. k% Bimport org.springframework.stereotype.Component;+ B: C8 S4 T  f& r0 F! Y% L

    ! u, ^) @& p! z  I: x@Component5 d4 b# u& V1 t4 v9 y; |9 D
    public class DbConfig {
    ; i' u+ g# h" [7 i; Q$ ]
    ! C: f8 R# Z2 Q6 G1 e    @Value("${jdbc.url}")2 q2 f" _4 w4 y
        private String url;1 v* p" S% Z& \# g" D- n7 P
    - k7 Z( B+ Y7 v" S2 m2 y/ d
        @Value("${jdbc.username}")8 f: O  }6 D" V" q
        private String username;% ?3 a9 V0 V! P0 G& s1 g

    6 S3 Q% Z; T8 W4 p3 m8 |6 O! q' Q    @Value("${jdbc.password}")! V2 b5 `5 }8 m& _, O7 a6 a
        private String password;
    / |( v! d: A4 O. g. G; M& p9 {! f; d; T7 G' n7 h/ \
        public String getUrl() {  o$ N5 T9 W: r" r$ D
            return url;
    8 s: x  J. @8 b, X! X9 V, e    }
    : h/ T6 I: j* M! T. B. {. a" D; b
    7 A$ G+ k) Z& Z. l8 J* d* z    public void setUrl(String url) {1 ^) }! n$ C& L9 w5 e3 @' ?0 C
            this.url = url;* C6 Y' E& z" h5 r# H( f
        }
    6 h% l, {9 [- }0 v, b2 r5 h3 S, O' V1 s$ S
        public String getUsername() {
    % h0 |: y6 C- G0 G9 y, c% S" o2 T9 x        return username;
    ; K# H- V; E2 g" X. f+ s    }# h( ?' X% j, y- g( I

    : D9 ?- T, E4 Q    public void setUsername(String username) {
    # S0 k4 I! X2 p        this.username = username;
    ' n4 i# \* p* s+ @! L8 p7 ^: d. e" Y    }
    ; A1 M( V* S/ i0 _' z7 c& M% p. Z2 }' k& g# N$ \& h
        public String getPassword() {
      {% G1 l! h! m  q9 X  v        return password;
    4 C6 U" i+ T* U" H    }' v. I3 S# L. t' r+ }9 U  E2 r; K
    4 J/ t- s6 G& N/ m1 w* N
        public void setPassword(String password) {
      z, n" ?! B$ x, |% W9 a' ^$ T        this.password = password;' Y0 e% U8 b; N& Y+ q6 _
        }
    ; N) ^& r6 M& c4 \9 x$ ^/ z6 N* U& z# M" B  y& t$ B
        @Override% p3 O, z) N. b; w3 L
        public String toString() {
    . O! N. k$ V4 [; O& c& s" [        return "DbConfig{" +
    + z: W2 G% M- M' M9 ~                "url='" + url + '\'' +7 E: t) p4 F( M0 ~& V6 b
                    ", username='" + username + '\'' +3 e- A5 f, {- ]  e6 U7 W+ u
                    ", password='" + password + '\'' +
    5 D- t3 p( N7 A* I                '}';
    2 Q5 n3 I- R# w  `$ \+ I    }( Y% T+ p8 w8 |" K% |6 P
    }
    , `; E; y! M" ~. V3 H( c, u上面重点在于注解@Value注解,注意@Value注解中的
    4 x* t* w/ h' \9 ^  V6 H
    1 A# E" {2 Z2 W& z来个测试用例. t" M/ ]. z1 t- o' t0 Z

    5 O: Y  d1 \) A& Ipackage com.javacode2018.lesson002.demo18;) I" e5 W8 P) K" D
    * W& N9 {* ]4 x& c3 I
    import com.javacode2018.lesson002.demo18.test1.DbConfig;+ Z$ a$ o5 ^, j  h
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ! e0 m& ]4 d7 o3 D* Y: w" ^import org.junit.Test;
    + J  z4 ]. \3 V7 W) j; c2 A6 N* uimport org.springframework.context.annotation.AnnotationConfigApplicationContext;- x8 w. X- E, i+ ]( v
    & x5 W/ p* L1 I' B- [4 i% F
    public class ValueTest {( B. \. t; f* z! Q

    & c4 q/ e( r* S( G    @Test
    6 Q' o9 _& z; ?7 {' v$ G    public void test1() {
    8 O/ [; Z5 f1 M& G6 w5 ]( l        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();# y, }, D" i; }
            context.register(MainConfig1.class);
    ! R" y$ V5 h% h: J* _' b        context.refresh();( W  B  k9 C$ w/ l, C8 k6 `
    - a$ E; o( p, ]% [8 e
            DbConfig dbConfig = context.getBean(DbConfig.class);
    $ p% g) t2 v1 a* \/ i% m        System.out.println(dbConfig);
    ( y, X6 k$ R! E8 @8 b4 J    }) j$ Q6 X, y* o; r+ k2 }  Q
    }
    ! Y; h% _( [  E3 O' F4 p- n运行输出2 u6 C! O% @. F: h# e. f4 E( w3 T

    $ L1 N& _( u% g! j5 _% H$ pDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    6 \' o/ w/ `4 k上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    # p9 l* n0 k0 X2 ^( U1 |8 I6 Y
    0 m4 U' i! [- L+ `( E! T: k' p@Value数据来源6 I$ l! X1 z0 K& V

    5 s  _% y9 f* M8 m7 m" o8 L+ a通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。( G* g1 W* G( @) S* Q& f7 ?& w: |
      N3 K% B; \. d! K
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    " l" o. y8 D+ i* ]8 j- f# R+ |1 \3 a( d) N% I+ o
    spring中有个类' C1 d  J) O$ w) Z7 B  \
    . H  z9 U$ ^- ?4 x' B) }5 C: c
    org.springframework.core.env.PropertySource
    : v% @, K6 l8 X/ U/ k可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    9 W) e. N* M8 B' Z7 f% R
    # n+ v+ O: G0 \7 l0 {& J" e内部有个方法:" ^' _* F' K( ?. h

    - ?" ?1 [) ?( S; Opublic abstract Object getProperty(String name);
    5 S+ |. Q3 c. R8 n1 k' U% n通过name获取对应的配置信息。- g# F* p( j/ k. }
    & ?0 h: {5 ~/ ^
    系统有个比较重要的接口
    : o- J0 T$ d; i/ a: H1 }  K9 A$ C3 _
    org.springframework.core.env.Environment
    + i3 Z& J; V* @用来表示环境配置信息,这个接口有几个方法比较重要" v5 }! N% c+ B' c4 ?- N- Y

    5 s6 v. c9 n. M' yString resolvePlaceholders(String text);
    : t6 c4 K$ g2 [MutablePropertySources getPropertySources();
    1 X+ M7 j3 Q/ p/ C2 ^4 F# F( K" jresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    " }/ ^) g$ f% l$ Z. x) `7 ]- Y
    & p! E* H  R7 C- |8 S% Q$ p" B8 dgetPropertySources返回MutablePropertySources对象,来看一下这个类
    + `5 U( d1 I+ s0 o9 J% a" ^+ G+ {' h3 h8 c0 Y
    public class MutablePropertySources implements PropertySources {8 N, v8 v  z, W. W- Q; S5 x2 j1 E
    . q# h( p. H8 m
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    + s# Z6 q! i6 w- A- Q$ h% f
    5 }1 z# u' v, t/ M; E) |) T+ z}
    % Q' Q2 P9 J" J; w( D. ?内部包含一个propertySourceList列表。
    / @$ _7 \: t2 o
    1 v) y4 F, C; z  n( v' Gspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。' D6 S$ N5 a. `

    1 k2 E8 E$ U+ e1 o* \& t4 ]大家可以捋一下,最终解析@Value的过程:9 q' |! Z6 \' ]" Z$ `; a, i( E" E( k

    6 `- o, i; g( A; x8 z1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    ) Q' B$ J, v( a' G2. Environment内部会访问MutablePropertySources来解析
    + r  E. N2 l; \( D# I3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值3 K; d+ x1 u, E7 Z2 ~
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。1 ]- Y6 [! k1 g! P) B

    / v1 n6 m! s4 Z7 k下面我们就按照这个思路来一个。
    6 r$ J5 B7 ]/ d8 ^4 _# u1 C* A8 H6 C" `
    来个邮件配置信息类,内部使用@Value注入邮件配置信息: k, F1 F6 U( I9 X7 h) s- l! ~
    & i5 K# ]; d# Y- U( i
    package com.javacode2018.lesson002.demo18.test2;
    * Q% ^( C, u  M
    " O; {. T1 `. Y+ U3 K* m; \2 \import org.springframework.beans.factory.annotation.Value;8 f+ I% N/ A% B# F
    import org.springframework.stereotype.Component;
      G) W! u9 W) p2 x' q; y5 V3 F7 o. i
    /**
    2 F6 {& u% L- c0 V3 H6 I * 邮件配置信息
    4 c% U) D7 ?) |* R* j8 m */
    4 C" r1 w' u" {: i' l6 Z@Component
    0 y8 m7 K* {9 Q$ e  Epublic class MailConfig {; T( y% M, y9 D& p3 j/ K

    % n& p- o# v6 R/ s    @Value("${mail.host}")3 J5 M/ B3 Q- A$ m
        private String host;
    6 b! P: T7 \8 ?8 w
    - J  c% Y  o1 I" A/ V, @6 W" C8 G3 e    @Value("${mail.username}")
    0 H. y' Z; ]: }4 X+ v' j# d    private String username;2 f; y' }& o: V! T

    $ H6 Q# p9 I& C0 U' w    @Value("${mail.password}")
    $ ?" g2 i4 h" g9 Y    private String password;
    . a% U; I+ j$ x7 G, U, |. I. D
    3 M$ u# S3 n8 h5 `: T5 w5 [" F    public String getHost() {
    7 e& D) n. T+ V( z$ @) t        return host;
    2 \* @4 G4 B7 D: A    }. A% c. f& l2 R. D, L+ A0 i
    6 @5 J0 W0 s4 x2 R1 ^% v
        public void setHost(String host) {) j- a" N" I9 J: |7 B
            this.host = host;' I8 I3 n% D3 ^! a4 D3 Y
        }
    ' M! F, D7 T. U0 A
    ( _0 ^0 R8 e' W) u# K) n+ S    public String getUsername() {7 O/ ?& p: z: M
            return username;0 H5 d* L. b+ k8 [
        }
    0 t( q  ?* H6 O& \! T9 b7 o9 \0 g8 a* V
        public void setUsername(String username) {
    + m8 k1 g( o4 g; \        this.username = username;: E0 {" e" n& |* a
        }7 Q+ L3 T: |( x
    * T' N. \  S. ~$ v  G* Z
        public String getPassword() {7 U0 k- Y! _/ `% B
            return password;
    ! z! M0 {4 G- ~* K3 p1 Q/ ^: M0 j    }
    7 r. J& i, I+ o8 I/ S$ I8 `, E6 o0 o$ m
        public void setPassword(String password) {% W9 k% R( a! z# r
            this.password = password;/ \6 L1 J0 w4 F2 `; D  e  C
        }
    " t6 r, J# W9 G$ I/ J0 [( Y* ^! T1 b! f& U2 ]' _, b
        @Override/ X- \& s% T& Z  _* f! |0 l7 E
        public String toString() {7 g" U5 n' `1 f+ W% M9 x) N: B; E& ~
            return "MailConfig{" +
    7 r( _+ G% {: C9 E5 ~# p                "host='" + host + '\'' +
    0 b* l- D3 {# }                ", username='" + username + '\'' +
    4 m1 x$ A8 S! I" o5 d                ", password='" + password + '\'' +
    1 j! ~! l  u' t( t, C                '}';% j7 D/ K8 a4 z
        }
    - V+ D* s( m4 A}
    ) }/ x( B8 [  Z" q再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    4 n( U% d9 U- o0 W5 q7 V2 y. P% W, g4 K: V' o& F& z5 J  I* z
    package com.javacode2018.lesson002.demo18.test2;* |8 r: P+ A) ]
    4 }4 f# Q& D; o5 C
    import java.util.HashMap;5 m3 ?6 E9 z6 j0 M6 r4 W' C
    import java.util.Map;& g+ F! }& T+ s/ p& S

    - w8 ~; h2 F6 k7 o* H. m& Mpublic class DbUtil {
    6 x1 a# G* p1 e* W" o* M5 |! d    /**
    4 T8 {& N* M9 Q- R0 y     * 模拟从db中获取邮件配置信息# [7 l9 _* r6 n( }; }/ ?
         *( r5 z$ {5 J: Z; G+ I9 U+ ]
         * @return
    9 D/ V/ Y# O/ |# t     */
    7 p: Q" _. r; h# V! d, c2 G    public static Map<String, Object> getMailInfoFromDb() {
    # N& K  `, G( F8 J+ M        Map<String, Object> result = new HashMap<>();
    / Z8 ^. o. [1 g) ]        result.put("mail.host", "smtp.qq.com");& L7 z( U. f7 s; B9 t
            result.put("mail.username", "路人");
    * C2 `6 n4 B+ O' x$ r* [8 L        result.put("mail.password", "123");4 H" _$ K  H4 L9 d, ^6 A
            return result;
    4 j8 I2 b) _8 I) o* s  R    }1 h# \: m, y1 T  }' i
    }# Y( m$ V9 F7 ]5 H
    来个spring配置类
    7 k' c7 T$ h! O7 {0 T8 o
    " J- h2 ?& ?' ppackage com.javacode2018.lesson002.demo18.test2;9 U, u+ E$ E6 s) {! t) P
    ! l' F2 d3 P1 K$ p  A( c
    import org.springframework.context.annotation.ComponentScan;! }3 K, U8 u( O, e# C
    import org.springframework.context.annotation.Configuration;, `' W" H( V( ^* |8 m
    $ x" ?: b) s. T3 b/ B
    @Configuration, t  \8 w" X- A# y  H" D7 H
    @ComponentScan
    ! a% `, o7 O1 L) e) ]public class MainConfig2 {! n# ]- c/ o8 l3 ]# B( |
    }/ ^. k3 s8 }4 Q( d% \) a
    下面是重点代码
    5 X1 u% ]- L3 C  k" Z, d9 Y# b% a* s, \! h
    @Test/ u. d) W+ H# ?* i( D% C
    public void test2() {' q3 C& `7 o$ z$ x) G
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();- |" @8 W% s, T5 _
    + D3 B: z. M0 c( T1 G8 f4 X/ o8 W
        /*下面这段是关键 start*/
    1 |$ g8 T( q. a' B2 B7 s4 r* j    //模拟从db中获取配置信息
    7 ^; D) K4 E* J: m; h9 ~9 j  W    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();5 E- A0 O- ^6 |- f
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ' n9 a$ ?4 o5 \: g6 E+ T) }0 r    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);6 ^5 O( y: g8 o7 o8 P$ s7 E5 s
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ' G' E# T2 F% T  C( s: r- R    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! I! b0 D" ~! c; g  q0 L    /*上面这段是关键 end*/6 ?: O/ t' b- t+ O+ n; W* p( }
    * c  y+ I- i: k, z, b
        context.register(MainConfig2.class);
    % S- y: p4 P" K    context.refresh();2 e* W7 p' p' ?/ \" R- h
        MailConfig mailConfig = context.getBean(MailConfig.class);0 q; @3 Q  ]) s4 q
        System.out.println(mailConfig);
    5 e/ _+ g4 \+ l, r* w}( q% Y4 N& F6 A+ W  l
    注释比较详细,就不详细解释了。
    2 c. _+ s) N! Z0 ]1 l6 G) f# }7 w: U3 r7 J
    直接运行,看效果
    " S  H- g+ T" C) n' k* }3 T( \
    2 |; ?4 a3 W$ ?8 f& S- q/ s) [MailConfig{host='smtp.qq.com', username='路人', password='123'}
    5 s; c; P5 `& P有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    8 b6 T' w8 ?8 b8 ]; j
    ! a5 J, k0 \: c" `. O( C上面重点是下面这段代码,大家需要理解" V, Y6 s/ O6 f6 k0 f0 f' d8 Z

    0 }& g; G, d8 Z/*下面这段是关键 start*/0 P( J9 o" i7 V, n
    //模拟从db中获取配置信息+ V! P  m; w* s
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();/ B0 Y: @( t4 C( e, V$ a) O  G
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)( f; A0 K* {8 n1 C3 S- y
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    : ]* O" {- r# F4 @//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    + n# Q# P$ ~9 K0 B1 d9 Ncontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ( `0 U3 I  p, F; ]  S/*上面这段是关键 end*/
    ) r' S3 \" H$ n0 m# Q咱们继续看下一个问题
    ' E2 `) p" @  r; O; y. K0 m# _9 e; T1 X) ?' u
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。) ?: Q/ f1 p9 ~( m; W" z
    ; z* n  o! [  Y) ]
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    : ~3 c( P. \$ E" `1 G3 T& V9 l
    7 ^7 l4 i. P5 t实现@Value动态刷新
    & p1 x" K' J, g+ V" K% M
    ) l4 f1 Q: w" W, |) h先了解一个知识点
    - @: `* d+ R8 k7 Z" P: Y5 j
    9 s8 L7 K8 {& v1 P9 Z这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    3 Z& @8 V8 Q* P( N* o$ n5 f
    , w1 ]+ d% c( `这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    8 V& r0 @% U7 z' X
    ) a4 Z  z, c7 Y, tbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    0 O3 ?3 ?% j* J# u3 y$ W) u+ f$ X5 X+ u7 N( }# i& L
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;4 b/ M, G. W3 ?& r
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中. j" O8 `! L2 d1 h
    + M# X$ I0 ^2 w) u2 d6 J: }
    public enum ScopedProxyMode {$ G( J) m  o! }
        DEFAULT,
    . C" i% O- W) T' H3 \    NO,
    ! ~9 Q4 ?; F7 W    INTERFACES,
    : G+ a* k5 b) p# Z7 D' r7 D    TARGET_CLASS;
    + N3 m4 C6 C! w" q4 Z}
    9 ]0 _  ~& {& R$ g前面3个,不讲了,直接讲最后一个值是干什么的。
    3 ~$ e  Y' w9 Q" t6 e" w( \
    6 V% a8 U" C" H. p) l- |  J当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。( c+ L2 M0 H" w1 w" w! b  |, H( w
      z8 }4 n  Z& r) i/ o' W) d
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    & [0 a( {3 I" e2 u4 U( d( D  h: t: ~% f
    自定义一个bean作用域的注解) n( D. k: j  s" f) u+ G

    9 G. m5 v: _+ A! O/ K0 w* Npackage com.javacode2018.lesson002.demo18.test3;
    * J4 i/ O9 ~6 F1 b, w" C5 P. J# m; X# F/ P
    import org.springframework.context.annotation.Scope;
    # o0 Z' H% S& c3 M5 u! rimport org.springframework.context.annotation.ScopedProxyMode;& M9 e2 w2 N& d; |
    4 {) d* w% R$ o8 w9 z: d/ v
    import java.lang.annotation.*;7 f5 d8 N! {; y

    0 l+ m( g) [0 `4 i6 l5 X1 y# t& n@Target({ElementType.TYPE, ElementType.METHOD})
    ! D4 X) z2 e& h, i@Retention(RetentionPolicy.RUNTIME)
    6 N4 B" w0 u+ L# {@Documented* F- G6 A" O6 R1 @. l
    @Scope(BeanMyScope.SCOPE_MY) //@1' k- \$ d* D* h( j! L
    public @interface MyScope {
    & ?  J- C% I2 p- x5 u# a1 k$ N8 D: r1 r    /**/ I8 {, v0 Z1 L
         * @see Scope#proxyMode()6 W3 b- C3 ^: L7 t6 \
         */
    ) I2 g0 q  f$ [# d8 ^. c, C# X    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    + D3 y- e0 m8 V, W( D) n- E2 j}' D" ?& l+ T) c; f) r
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ! y# c3 [6 z  z5 J5 E! J6 w9 c
    $ }$ f7 j9 G4 r) I6 V3 M@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    . y1 h/ j& L& Y- Y* e% q' q  b+ h! Q7 R. D: N8 B0 j9 `; j( N
    @MyScope注解对应的Scope实现如下( j2 F6 k* H$ d5 k# i8 `
    " m; n9 U4 {  ^5 h, T, [: A
    package com.javacode2018.lesson002.demo18.test3;, @7 [6 e8 E. u1 Z! [

    8 w* @* W  I: Ximport org.springframework.beans.factory.ObjectFactory;
    " w/ u1 y- E1 X& N6 O: Timport org.springframework.beans.factory.config.Scope;0 w6 j0 u( v$ {
    import org.springframework.lang.Nullable;9 h. J2 J, H6 u9 |7 y# n
    7 m* Z4 F* S* `
    /**$ X  [6 {) k/ u; S& @8 E0 A
    * @see MyScope 作用域的实现1 `5 o" w& G% b" e; ]2 x
    */2 i0 k! ~2 E5 f1 i4 O
    public class BeanMyScope implements Scope {
    " B: b1 g- W* H: q0 Z% {
    5 E; r0 W& I+ c9 O  c' a9 a    public static final String SCOPE_MY = "my"; //@1
    0 G* j$ B( q, t8 l0 w: ^5 R6 t; V7 v( @$ I& m/ M# B5 w
        @Override5 `5 [4 f* c0 k5 S% f0 D3 G
        public Object get(String name, ObjectFactory<?> objectFactory) {
    0 _4 Y3 |: a" u* ?( j5 o: S        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    # `! l7 j' Q% b1 X        return objectFactory.getObject(); //@3
    7 J) r  g2 {) D* ?+ `) ^2 D    }& q* r' S3 v% G* |8 t+ |! ]1 d

    / S; n  e1 N: X* G$ E) e    @Nullable1 U# ~; _7 L3 N
        @Override9 l% W' q1 k7 D* z$ D; e1 f5 O
        public Object remove(String name) {* F, W% d* `& Y3 z# \5 m5 B! _9 b
            return null;
    % }+ J: x+ @) w- f8 }7 Z6 _    }$ S9 N* U8 x; L
    . r7 p: A! j9 W$ k; k5 s7 D. z: _
        @Override
    + l$ k% d6 g* f" X/ d/ }    public void registerDestructionCallback(String name, Runnable callback) {; b2 k* r" g/ Q2 K4 H+ X# ?

    3 W( M$ o4 ]. k: S! C; @- Q/ k    }
    / j0 H6 w( B2 J8 T1 @0 O# G
    7 b! ~+ z+ _; @+ C* Q: d3 x    @Nullable: e7 U3 A: {+ u
        @Override9 \3 F4 q# k' }/ p% h
        public Object resolveContextualObject(String key) {( p0 }3 t  Y2 K7 w
            return null;6 [  P6 @& x" k! I! }! I+ {4 }5 W
        }
    , @) ]0 I# q+ z8 J* ]. Q6 _/ d2 P& D3 x7 K7 W, G6 P
        @Nullable
    - G1 k) q. `  t' d6 m8 a    @Override
    & U) X0 m2 s# J8 Z    public String getConversationId() {
    6 p2 P. h2 f' V+ K, X& W  D        return null;, [, q! t, L$ ~. O
        }9 c: \- D0 `. |5 j
    }6 w1 c5 J; H5 ~6 S8 }
    @1:定义了一个常量,作为作用域的值# i* e' d  f. \2 i
    " o+ r! T" C* W! w  _) m
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    & `/ o: e- l) @7 j
    9 |: Y' P' _+ c+ O# l- g@3:通过objectFactory.getObject()获取bean实例返回。! L! m+ \/ Y0 a& X( y; i

    ) i9 K" c, Z, f+ f0 f下面来创建个类,作用域为上面自定义的作用域
    9 l* O. `. L( E8 \8 B! W1 C9 h# }) v& }) g
    package com.javacode2018.lesson002.demo18.test3;! j  ], q, k2 ]7 x
    * W1 z; r1 q) \& ^' Y
    import org.springframework.stereotype.Component;% `  j: B0 ~* v' F

    $ P$ k+ {. C1 c2 F0 J2 f9 limport java.util.UUID;
    / P" J* y0 [8 G
    + V( y4 S% C7 O2 |  B9 X@Component  u# R% @, N" n& i/ w
    @MyScope //@1
    . O$ C. [" J+ Apublic class User {
    $ J6 `$ ~! C" ~( `
    " e3 @; m& @& b# T  I8 t* L    private String username;
    - ^8 I0 J2 I* r5 n5 I! Y, r6 F
    / y/ o- h& U( R: a! D+ u    public User() { & a6 e! H: Z+ l0 u/ @' U$ {3 H) M
            System.out.println("---------创建User对象" + this); //@2
    : x$ A/ u" V: ~3 b; `3 e        this.username = UUID.randomUUID().toString(); //@3
    + A6 F* Y% E  F6 O$ r    }
    7 e% u; R( G1 v% o  k# N0 u" E7 j/ h$ y% W: L
        public String getUsername() {) Z9 h; n/ i$ o! O
            return username;& Q4 f9 m8 m; k
        }
    % w2 b% X) G! J5 n0 m5 ]" \0 P% x6 d
        public void setUsername(String username) {0 q+ m' O# l1 G  s
            this.username = username;$ C7 @' X. }5 _2 V0 I) ^
        }; T8 z5 V" d8 }7 ~9 @% {
    + s1 g7 x. A& w, Y/ I/ W( q
    }
    2 h; s0 C0 _. @& ~4 ?9 j@1:使用了自定义的作用域@MyScope
    : }, F  y' f( F' M* R" l6 E+ X" \  N1 y
    @2:构造函数中输出一行日志
    # c& t- n/ ?( _) l  w+ L1 x
    " t- O1 q* ^' K7 o- w@3:给username赋值,通过uuid随机生成了一个5 G" M. |! N8 {4 O& ?
    " H8 M1 b: Y" ^! L: s1 v
    来个spring配置类,加载上面@Compontent标注的组件! k7 O$ |2 m3 ]. B& m5 P
    * T! b( Y2 `6 e2 R( j; N1 o
    package com.javacode2018.lesson002.demo18.test3;
    , z3 u! [* T( {2 L6 ^0 v0 P" l8 d3 b1 j9 r0 E7 }5 }
    import org.springframework.context.annotation.ComponentScan;
    " t) s! d0 l  q! g( Kimport org.springframework.context.annotation.Configuration;0 p3 s5 z+ D; j. u2 D* Z& T

    7 t5 q& z- E7 o( x@ComponentScan
    ! f7 p& s& [0 v+ z" [* W@Configuration6 P( L6 t# R/ }! K
    public class MainConfig3 {
    ) q4 l: G0 n+ f}
    ( X5 S9 L  T5 J2 P; x% P下面重点来了,测试用例2 U* s; m. g3 O- Z5 k1 U

    1 C, p; W( ^+ g7 C@Test. @5 K; K2 z+ z$ ^- B
    public void test3() throws InterruptedException {
    ) o; o+ M4 p% l; [: d, L    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    6 C/ j0 @4 T  K9 R  l    //将自定义作用域注册到spring容器中
    & h, x$ U" O) [2 d" ?; L# P) \    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@10 A9 v+ v% N% ]  c# L1 B* ]
        context.register(MainConfig3.class);% H0 u4 y; @5 A8 h5 X( I% p  l( V
        context.refresh();
    2 F- L4 j* U8 e9 Q1 Y4 P
    & a- ^4 H  P9 p: d3 l6 O1 v    System.out.println("从容器中获取User对象");3 \6 b! M$ A9 e* {
        User user = context.getBean(User.class); //@2
      H) x- n2 I/ O7 S- Y- t2 s7 V' A    System.out.println("user对象的class为:" + user.getClass()); //@3
    9 u! Y) e" X2 v# ~6 _& t% _! |" S" H( @6 ^2 W# r. [
        System.out.println("多次调用user的getUsername感受一下效果\n");& u& Q% N  \' {3 f( @6 r
        for (int i = 1; i <= 3; i++) {
    / B/ a3 b" c2 z; a4 g& @        System.out.println(String.format("********\n第%d次开始调用getUsername", i));) q, c, p5 n6 p/ e) k% {0 K3 B
            System.out.println(user.getUsername());
    * R! `% R* Y& W; I. K$ ]/ g4 s        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    4 t# H$ z7 x2 w( g9 ]    }$ G+ |: `$ ^. `. |5 c  G
    }
    : r6 w2 e' G, ~@1:将自定义作用域注册到spring容器中' {- e0 M* A- ?2 r; D8 K8 O: ^: P9 i
    ( r% `6 u- b1 M* Z
    @2:从容器中获取User对应的bean1 G. j: v2 w/ a
    " Z( N" U7 b+ w9 [4 `
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    & \' C  L3 c  D" b' [4 A5 Q# i8 b: i
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。5 Z1 L+ U" o  N/ ]4 L

    % P& W1 W; ^' ], [% N* F8 d见证奇迹的时候到了,运行输出/ H% V& g; N+ k5 p- K- S& Q

    2 P. I/ C  T* ~  m7 o- P从容器中获取User对象8 b8 @: `  x9 Z$ `% p
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127% C. F" o8 p& k0 x4 }" U. h
    多次调用user的getUsername感受一下效果( _3 b( t8 d* ]6 D, O5 H! X1 o# s0 r

    1 W* Z. S; t) i/ _. |* v********
    8 {+ o4 k+ R  N8 E1 m: c第1次开始调用getUsername
    ! v; u/ o- o. Y" ?! o) fBeanMyScope >>>>>>>>> get:scopedTarget.user* G  `" l! O' O" q6 e5 [
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4& S$ E' C- s: E. D: N; F, y
    7b41aa80-7569-4072-9d40-ec9bfb92f438' _2 E3 X" M' i; n* u& F% x) T4 W2 W
    第1次调用getUsername结束! g/ A0 \, P2 I6 _' S  i! X4 F( T
    ********
    9 t" o9 m+ I5 P) T( o
    " |* \& T! I9 ]9 [* s4 l2 e********
    " U2 v9 N) d1 o' j* E7 D: B4 ?第2次开始调用getUsername4 \/ d& s; w" F' ~& t. I" d
    BeanMyScope >>>>>>>>> get:scopedTarget.user3 E* B0 d% p$ X# f/ \  k/ Y
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b& E$ A7 C1 ~$ V& C
    01d67154-95f6-44bb-93ab-05a34abdf51f
    ! n( ~* `/ f. v; d" ]9 u* [第2次调用getUsername结束
    " l: K7 L. R9 d) k0 e********2 ]( u. O: z+ p* i. t+ X

    6 g- B8 p" j8 f7 N3 @5 P. k********
    - Y! T) D+ z  P. w7 d$ V第3次开始调用getUsername$ P6 W' k+ ~3 G5 {; F6 e/ g- Y. W0 m
    BeanMyScope >>>>>>>>> get:scopedTarget.user6 L4 K3 x. K- [: G  @( r8 }
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    * y# h! k9 b. \. b, z8 Z76d0e86f-8331-4303-aac7-4acce0b258b8* H4 E, o8 t8 m
    第3次调用getUsername结束
    0 {' W: h1 O6 C8 G********7 ~7 A* c6 c8 u! \# j% B3 z
    从输出的前2行可以看出:
    ! j, e, j  v2 C6 M+ s7 }; H% p1 m4 b0 d9 N
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    7 r, Q/ D6 x/ ?) E
    + M# ~4 I3 B  h# R" v6 r第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。1 P( n2 K9 M0 p4 @! B6 S) ?

    6 q7 _, d' A1 L' {( T8 M7 _7 m! W后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。: E) E3 B9 \2 \4 a/ _
    4 U% y- ]+ S1 s! g; ~6 o1 [- r
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。; N+ j' L% S/ R0 z

    7 l% p9 t4 @% w# d5 j/ H动态刷新@Value具体实现" p; x  U4 r* g9 H% c+ t5 c8 l% b; `
    2 P# f3 y' W. |0 Z4 n& r
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    0 X" B0 M/ S6 M5 U
    ; P- y# Q5 [' S/ I( q1 e4 H先来自定义一个Scope:RefreshScope% j& I, Z, h) [# H: N# X9 _5 Y

    8 z, f+ n4 s+ [# {4 [  ]( `; ypackage com.javacode2018.lesson002.demo18.test4;
    + k, r1 q6 G$ @& u; f, d) Z. y
    9 ~! R5 K5 Q. ]" e( f% \4 Dimport org.springframework.context.annotation.Scope;
    9 D2 }/ m. Z  A, |import org.springframework.context.annotation.ScopedProxyMode;9 l+ z! j3 ]  l$ y8 c
    , ?$ S; a  S1 j
    import java.lang.annotation.*;' X  m/ P- {* W  h( q' T- D# n
    & K* P# z0 k7 L
    @Target({ElementType.TYPE, ElementType.METHOD})* U8 u7 n. g( \
    @Retention(RetentionPolicy.RUNTIME)( j; {" Y  A; O( M4 L9 X' i
    @Scope(BeanRefreshScope.SCOPE_REFRESH)! s0 p5 X1 ]3 g, \' k! k7 R% O7 F9 J
    @Documented
    ) T7 H% z) |) h+ j) Bpublic @interface RefreshScope {
    & a+ t; o$ k$ S+ N: F: d    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    + r& y/ T) w+ f}. I3 l; g2 E) D
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    - I9 e2 @! J9 X! n' W5 W- ]% y0 b$ ^! T, ]/ {
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    ( ~9 M( G- x  e' D9 @' v4 E- |6 l8 T. K2 q7 r) Y
    这个自定义Scope对应的解析类
    - E- x# ?& i* ~0 S
    + Q; V" X8 x1 A0 S2 n, ^5 l下面类中有几个无关的方法去掉了,可以忽略
    % F0 n2 O: r/ A% ^( A
    3 p+ u$ ]: K: K$ h. Hpackage com.javacode2018.lesson002.demo18.test4;& g3 E4 v; z. C, X

    5 L5 w8 B7 O' [
    1 T4 r; L7 p4 w2 b0 d) `import org.springframework.beans.factory.ObjectFactory;9 z. U3 z8 _, \2 ~4 T' L
    import org.springframework.beans.factory.config.Scope;8 f  u: j, f* _% k" z
    import org.springframework.lang.Nullable;0 L8 ^0 z6 F! o- x( j
    8 Z. n2 y* X. s' z
    import java.util.concurrent.ConcurrentHashMap;8 {& T" Q! m+ f, G6 j/ K1 m
    $ @7 \7 n( J& _3 }/ a5 _
    public class BeanRefreshScope implements Scope {" f* \% f* O0 D. A* U8 ^6 `7 s
    0 n" i4 C- h  Z1 `  `! U
        public static final String SCOPE_REFRESH = "refresh";
    & d: Q2 u6 s" h4 U
    $ W# p3 k/ y! K( i$ `" C% z    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    ( g' J: Y! U+ t! ^  w& }* g2 j4 {; D0 O& r; a' @9 L
        //来个map用来缓存bean0 }. D; `$ b8 M  _  I3 _( L
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    0 K, R5 D- _2 f4 z
    ) M; L" x, G' X1 E, }1 p. e    private BeanRefreshScope() {  m+ Q# y+ W: Q  d  U$ Q/ t9 n
        }/ t/ S% }" N. d- c
    6 u& a0 E' f3 j+ ]# ]* a
        public static BeanRefreshScope getInstance() {% h/ h: v) e" k& A
            return INSTANCE;
    + K* I- k+ i# Q- @  z) v    }, M3 b! i" V. w# Z, A! h

    & M  u, W+ E  L9 I: K) Z    /**( t2 E( d) g6 N1 c
         * 清理当前  v0 i( Q) S% f! Q$ Z$ l
         */
    2 j# V# c7 R, x8 c    public static void clean() {! z5 X! f4 o2 t4 e; q: i8 }7 p1 @
            INSTANCE.beanMap.clear();7 @; Q8 r# C( J+ U
        }
    2 E1 X. @! D( t1 ^3 }. B- ~7 i$ }* S4 t
        @Override
    9 h8 [( U! Y. |5 a0 J. [    public Object get(String name, ObjectFactory<?> objectFactory) {, y0 u, h$ }/ R, L; S/ q
            Object bean = beanMap.get(name);/ _4 r" E' d/ I$ d
            if (bean == null) {! Y6 o! K1 y0 t# o: R, j
                bean = objectFactory.getObject();; E* c' V' n" t$ U' o
                beanMap.put(name, bean);
    ' `! i- e7 ?) Y  }$ x8 u0 g9 t7 Y        }# D0 x. p" q& I( l% P3 d1 i- K# D
            return bean;. l/ I1 D, N% }- o# [
        }
    - x. u* H, C% n( L  a# y! d. I: u+ Z' [9 `( B2 s4 N, E
    }4 A! b  a' y! e+ x5 s9 ]
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    - ^* c+ [+ n8 v# D* T1 c+ X* X6 p5 V4 n6 d7 t
    上面的clean方法用来清理beanMap中当前已缓存的所有bean0 M4 r, i) Q4 I8 b

    & ^8 f. `5 \! j+ S来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope7 D2 N% Y! z8 j/ Z

    , m' C' t' H$ E+ U9 Tpackage com.javacode2018.lesson002.demo18.test4;3 N$ e  I& q/ h; z! a: D

    - d' j- w$ y# I  @1 Nimport org.springframework.beans.factory.annotation.Value;5 }' A( j# L2 ]+ x
    import org.springframework.stereotype.Component;
    ( G6 G- |2 R: i/ A2 P" N# ^. ~
    # a& O. I# M+ Y/**- R8 O# S- P0 T( S4 B8 R: E) m
    * 邮件配置信息
    ! V* ]) v) B# K; y7 I */
    & e! b( m+ e" ^; R+ o@Component) J2 T' N4 \  f* y4 V$ P1 c2 L, f
    @RefreshScope //@1
    3 C- i( y- S9 s2 k# s; w8 Fpublic class MailConfig {1 ?/ Q7 \! t/ y; w2 ~

    ( S' ?  X4 l& _, ^+ {# ~# q% M    @Value("${mail.username}") //@2
    / }) R1 K. B2 d+ \- F$ j  N    private String username;
    0 e  V5 e$ ^6 G3 F  a
    : }& N  O0 q; x2 U0 w! i" ~    public String getUsername() {0 b; C' N* q1 Q2 ^+ p: |. X
            return username;
    % g1 C  L6 O7 a+ ~( c/ D8 `    }- C& L! I) z, h! j7 J, y
    8 _. Y- e3 G$ d  X# B+ ]5 `' f
        public void setUsername(String username) {
    : Q8 x8 i, Q& P0 M6 x+ N8 J% E* m' N        this.username = username;
    9 z8 o& J4 h0 s* g. X2 a, J    }' _- H5 j$ L4 t. z- \8 I
    & B( K( j* ?2 g: U
        @Override7 |+ ^! z, b, m4 {- l2 u9 p. ?) R
        public String toString() {1 x+ Y' S7 S+ _. o1 K4 F+ s1 K
            return "MailConfig{" +
    + V5 d0 N4 u6 v2 ^/ I                "username='" + username + '\'' +
    4 p. a1 K9 x, A6 t! v% ?                '}';7 ^3 @- k9 Q3 ^4 `( Y
        }  d5 X! ?% f& A4 E
    }
    # ^' P% |; i/ I6 w, V1 q- @@1:使用了自定义的作用域@RefreshScope' e! [6 s& n9 L1 {; h

    % P- G: N+ z" `- l/ H@2:通过@Value注入mail.username对一个的值
    5 M! f1 g1 k" E. M  R* ?+ H  }& \' W$ O$ g$ F0 ~. @
    重写了toString方法,一会测试时候可以看效果。
    * N, \$ \8 u& S& s  x( T- h
    . z% [& Q" P7 N再来个普通的bean,内部会注入MailConfig( U5 h; [6 H" L7 M5 L$ P
    1 i7 b+ h4 B5 V6 ~1 ]3 j" a% ~4 k
    package com.javacode2018.lesson002.demo18.test4;' M, ^+ l' m, i# X7 {4 k8 g1 c

    6 }, x. j' j$ r9 w" @: b# _5 _" Rimport org.springframework.beans.factory.annotation.Autowired;8 I3 ^" o% q. A! e  V" a
    import org.springframework.stereotype.Component;7 `3 e0 g  Z& i$ |2 Y
    ' {- F3 H/ ~! j: g2 e
    @Component$ @' S7 F+ u; [
    public class MailService {7 N4 _+ o: C( Z; h0 p/ O" D; ]
        @Autowired
    5 M. x% ]7 ?% S2 V    private MailConfig mailConfig;, b- R; t+ N0 x( t% i

    1 o5 v  t# |( u$ Q! u2 }4 C; G  }    @Override
    1 U4 }/ Y% @& c7 J5 ?    public String toString() {
    1 Q  q' u7 x; g' g) e        return "MailService{" +1 ]8 ?' e! C8 m
                    "mailConfig=" + mailConfig +
    ; Z. i; l* a& }( Q" p                '}';- @6 ^. Q* f9 L1 A5 ]
        }  V. y1 p4 K8 o* j' [: M4 V
    }
    % o1 A  B1 m2 J* S( V: x代码比较简单,重写了toString方法,一会测试时候可以看效果。' I9 G8 ~0 T5 F* M7 i
    ( D. U- z4 `) D. E8 Q
    来个类,用来从db中获取邮件配置信息
    ! |' w2 t' b+ s6 ]
      c) H# X6 T6 a2 Zpackage com.javacode2018.lesson002.demo18.test4;# y3 a& L( D, w2 |' U! |6 z3 h

    4 h4 v3 X$ ^# u# t' ]7 Q" b9 jimport java.util.HashMap;
    4 u9 x9 _: Q) [: \* ^6 {import java.util.Map;! x$ Q6 m# W& e! M/ @. \" m' B. H, n3 |
    import java.util.UUID;2 e; Q% C3 q" @# [1 v% @

    - J8 E  @  z  upublic class DbUtil {
    6 a2 u* E! U% ?3 ?/ o' A7 \6 x    /**
    5 g2 }, m5 X, I+ U6 R6 L     * 模拟从db中获取邮件配置信息
    * N+ R0 Q5 i$ a6 \) K, U4 ^     *
    9 @+ p+ H+ X# F) I+ }# V) t8 H     * @return) m2 J! z+ X! e
         */
    1 W+ i4 W% B0 e/ S8 U    public static Map<String, Object> getMailInfoFromDb() {. x! v. Q, ^- u
            Map<String, Object> result = new HashMap<>();
    8 y8 |: o7 `4 N$ |4 [; Z1 v        result.put("mail.username", UUID.randomUUID().toString());
    - W1 m, \  d5 ]0 z) B        return result;
    " p$ I0 i# R: i! D    }# w) x4 S* o' n" ^7 @
    }
    2 q# w2 a( P1 b. B, Y, H1 W/ C来个spring配置类,扫描加载上面的组件
    ; J/ V- D! e9 L; @- Z) W8 n! ?" c
    $ h% u! o0 y0 I! j) Spackage com.javacode2018.lesson002.demo18.test4;+ A$ `5 s: S$ h1 y9 d$ V& X
    4 M) q! K+ d8 d7 X9 t4 r8 @
    import org.springframework.context.annotation.ComponentScan;5 R- }1 K9 o) P
    import org.springframework.context.annotation.Configuration;5 L3 m& \) P4 B& ~& a0 h' a" f2 M

    5 a& c  E8 A$ h  W/ u@Configuration
    & U0 C" g+ U1 d' c- G& [6 l7 [@ComponentScan
    / `- u0 I0 |6 Z& b: j, h' ~public class MainConfig4 {6 f4 c2 ~7 `9 Z
    }
    5 x( D4 P1 @! o来个工具类
    ) [6 I/ s( S: h! b0 F' W$ q4 _! p6 M. _6 i- {9 ~/ e% p2 A+ Z+ R
    内部有2个方法,如下:! C$ O* u0 ?6 s4 c$ x
    ; G! X! W) Y9 b4 t' ]
    package com.javacode2018.lesson002.demo18.test4;4 f" i& Q1 H. u/ K" z* B! g- \& H
    - B( k2 V0 R& P: ^- s( V
    import org.springframework.context.support.AbstractApplicationContext;6 ~) }; s) d6 u0 o6 `& R6 U
    import org.springframework.core.env.MapPropertySource;
    : \3 h0 [8 a2 s9 ^! d4 e
    # O1 o4 T* o7 _4 r( Eimport java.util.Map;9 V8 J6 r7 W# K
    " ?+ a2 v1 d0 {  T' F% j
    public class RefreshConfigUtil {
    % U* @9 l7 d0 O5 r    /**. X# Q2 d- s- s
         * 模拟改变数据库中都配置信息
    2 k# }$ k, d0 V% }     */
    + V7 d9 }' J0 z  X( E- z3 h    public static void updateDbConfig(AbstractApplicationContext context) {
    8 v: n) Q: d+ {        //更新context中的mailPropertySource配置信息! z$ ~) F, e5 X7 ]' f
            refreshMailPropertySource(context);
    4 E: i1 F6 g/ J! `3 i( ]/ @5 g) `, h2 i: r& ]. [
            //清空BeanRefreshScope中所有bean的缓存% D- p( o$ t8 S! i& x2 r# l3 {
            BeanRefreshScope.getInstance().clean();% l+ B) _3 }0 S$ B2 m' D( d  \1 e
        }
    * m7 d; j1 ^, _. v3 J3 H. a+ g0 X) i1 a7 p7 s) K
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    * k. E* o. v* c" K9 k- g        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();" V2 z( R# J* h0 V1 H) V
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    2 Q, }. M4 B+ H) u3 {/ _) P        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    + G2 _7 x& U$ d5 F) I( W2 m0 b& }4 X2 u& {        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! j& A( u  [0 k6 }    }, V* x) V8 Q6 a) e

    " {6 i( {- |: y; D# C: D5 v  Y}
    , Q! j0 d& `8 S8 p* k* R( M$ LupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    $ x: d: `* t5 c: _' Q. F1 _8 _$ l( H
    8 S/ O# h) B9 _  O7 JBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。$ r) T& g2 \! E. M8 ~. |

    % k3 k. g5 r9 t. f3 L来个测试用例; l2 c7 u) {& k2 l7 q! B1 J1 ]/ b
    ; F+ ]- F; u- @! O/ d6 V
    @Test
    # y; H7 V# {" h0 R* o* J6 A! jpublic void test4() throws InterruptedException {
    * R, S- I7 G" m* l7 _3 D$ C. e3 }    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    6 ^( e, T) G4 Y3 K: l% [    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());1 z: S8 \- l9 q3 y1 c
        context.register(MainConfig4.class);! A* _- z9 C& s1 L2 }' N: G2 t% b
        //刷新mail的配置到Environment; N8 t- H# Q! S/ A
        RefreshConfigUtil.refreshMailPropertySource(context);$ n4 c) @* M/ _+ Y
        context.refresh();
    : v, @) F, j8 ^# `: I
    * M4 I/ H8 X0 k& o3 [9 }( K    MailService mailService = context.getBean(MailService.class);: j* [) e7 Y, W- X  p0 b
        System.out.println("配置未更新的情况下,输出3次");
    ; T2 E% L! y) K+ n5 {    for (int i = 0; i < 3; i++) { //@1. V( h8 V. a/ @3 A7 E; A
            System.out.println(mailService);
    # q# S% R0 l2 P* O        TimeUnit.MILLISECONDS.sleep(200);  \$ t& m4 r$ c! n
        }
    ! y; W- H- S$ [! N- ^' w' y5 j( ?& k- r$ ^
        System.out.println("模拟3次更新配置效果");" W7 M# N+ F& d( @# U
        for (int i = 0; i < 3; i++) { //@2: \: D5 n" G; j3 b2 S
            RefreshConfigUtil.updateDbConfig(context); //@3
    * y$ u* x) ~! }' L        System.out.println(mailService);* g. O3 r! k. ^4 {6 F6 h
            TimeUnit.MILLISECONDS.sleep(200);
    0 }' T' o( V' r0 D    }
    4 ?$ R+ ~; _0 z}2 z7 O+ z! K& [) M/ H# R
    @1:循环3次,输出mailService的信息
    1 |# Z" X" `' F/ w( f$ b' t
    + C' l- _" I4 Z! B& q3 ?@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    - U, N" |  v  [6 t3 [3 X
    5 c8 H; h3 w( w. t# H; x见证奇迹的时刻,来看效果3 X: n4 X0 v+ @( ]* ^* t
    ' R& J( l, K& t$ |2 L
    配置未更新的情况下,输出3次) e$ `9 g. z" ~: |  T5 v4 G
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 z, w$ v1 u) O4 u3 CMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}9 N$ \* J  g5 m6 K2 d5 l8 P
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}% K6 g. w2 h5 n  v9 P# h0 J7 |0 k0 }
    模拟3次更新配置效果1 l$ Y7 t: f0 J- F
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    0 ^8 Q9 R5 {4 K7 E- yMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}4 f. o# j' t; }- r, V  X* E
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}/ j, o. H, B" J( z# I. i# G6 u
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    2 s7 j# e' L! b$ Y. g- U
    5 @* W2 |; U- y+ A0 t% j小结
    $ ]1 e/ f9 U* L# w( o8 I/ T6 @5 b  f+ f% \5 H9 r% Y; c
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    9 n7 G- c# O2 J; a- i4 A/ ^- N! o/ y# c1 r, h1 r" H5 W
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    5 S. r$ Y5 m# p: L
    3 }9 y# I, P: `4 |6 d总结' F. P* Z8 I4 N& U& u/ l7 s+ z6 i
    % k6 D, C, R& W" r* t2 Q
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    ) Y  s$ [- A' i, n( m' X9 l8 [2 U* L0 R: }1 \5 h+ y
    案例源码3 W( ]; k! V# d" q& i

    & f+ U1 ?  h- m5 g( |" Qhttps://gitee.com/javacode2018/spring-series
    1 d2 x- ~! l3 R# S- X/ ^路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。, O0 [! R# ?, S

    + L8 {: v7 F% |; bSpring系列
    8 z- t; t7 a- F- Q" W& y
    $ H( p- W* I9 Z0 H! _6 dSpring系列第1篇:为何要学spring?
    ) ~) H1 @- g* T6 r" k+ s- M- m( H$ V7 X3 x6 I" @6 ~
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    : T+ ?1 d  ^$ U. E2 k6 n5 P* ?6 }
    Spring系列第3篇:Spring容器基本使用及原理6 k( c; w8 r* J+ M- }, ^9 [

    7 A. g- P1 V4 \* YSpring系列第4篇:xml中bean定义详解(-)) A0 f& U9 f1 Y3 r
    0 b* y, E( i9 O" z- x6 {
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    ( G1 h+ [8 K# f
    ; u" f1 _/ ]3 |  u# X  V& F$ fSpring系列第6篇:玩转bean scope,避免跳坑里!  V& d, t. M0 W0 ?* E
    7 ^* z5 O3 T  }! o4 \
    Spring系列第7篇:依赖注入之手动注入
    4 `7 q  Y; Q* m. ]  K. d( t" }
    8 k. d: ^; j0 V( }; ]+ _: _6 qSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    " o% I+ s0 o* V1 R
    9 o! y- ^# Y& ?1 C- fSpring系列第9篇:depend-on到底是干什么的?- n  {- O- @2 Q; n5 q' J

    0 i) ^3 i. V: k- t3 BSpring系列第10篇:primary可以解决什么问题?1 }, c7 d: J2 ^: W4 s
    . S" O% P9 }% x5 q' h, K) }' ?( l
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    $ E4 `0 J9 `# x- i$ k
    " {2 M% t0 I4 z/ O0 p3 USpring系列第12篇:lazy-init:bean延迟初始化
    & i, V+ s; Q8 l' [& ^
    , z8 c: G' T0 v! a; LSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    - t" b' C+ F' M2 O1 q( i- g; x  W! I8 J( \
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?9 H( V# Y8 i3 C( z/ z. ?
    - p& t$ r1 Z* k7 R7 X, W9 r4 j
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    % |$ X9 t" ]  Q/ ]4 ~0 z
    # O. U/ T% [8 B" T; O, Z& eSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)1 @5 A! L) V* m/ @

    6 e2 Z* E2 `: e. K3 [& SSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)! ^6 _& G: K+ B  b% u, R

    ; t7 A; r1 ^; c1 \' ESpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    % y1 t5 A- P' g/ [( p% x0 D( _9 ~* l' i7 W" W) y; X
    Spring系列第18篇:@import详解(bean批量注册)
    / X, }) b, t" Y) I
    9 q& S' U4 K0 @; M1 jSpring系列第20篇:@Conditional通过条件来控制bean的注册
    5 B0 l& V# N# i+ ]6 A% L9 @! M& P) i' p+ K8 r4 c4 ?
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier). x$ z; i  J& L6 N

    , T+ {' j6 J- N5 OSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解1 b$ t) J: U9 O) V

    $ ~; E/ {2 X& t* [' G$ C! {Spring系列第23篇:Bean生命周期详解& g  j4 A" T4 o# G  k+ v
    7 x% g. t+ D; c) @
    Spring系列第24篇:父子容器详解
    & e) u  }+ ?& ?% r3 z3 u0 j& Y1 l' `6 V6 b3 c3 @
    更多好文章0 b" |: B+ G/ x' J! q7 f. m: `
    $ C4 V% O* E; v% D4 _9 |+ d6 U4 e
    Java高并发系列(共34篇)! e! ~9 r) i, R9 D
    # b# ?( u: G, M% J- F, h7 w$ E3 }
    MySql高手系列(共27篇)) e: X/ Q, {% A+ A9 f
    % _$ ~9 C, {8 }* b5 K+ j- s- U
    Maven高手系列(共10篇)
    . f( Y9 m$ Y3 [8 i* A7 c9 s5 t
    % z8 a; ]/ v" Y. C# V2 eMybatis系列(共12篇)3 a3 p! _4 L* Q
    7 L3 D0 o% r% ]- f2 Y2 U
    聊聊db和缓存一致性常见的实现方式
    & P) N/ Z6 ]+ z7 o# G9 h" T! ^0 ~0 L" y" R- l
    接口幂等性这么重要,它是什么?怎么实现?
    : [0 m3 @& ~) s
    . V2 w! ^0 i" @6 L5 m泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    $ z" k8 Y. U+ ~2 O7 ?0 J& C6 Z————————————————9 M5 ^% ]7 G: ~
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    * d$ m0 J- a" c2 l/ k原文链接:https://blog.csdn.net/likun557/article/details/1056487571 |- G; {1 A! V* S0 S! u" t6 \
    & ^" Q; Y' M: U" t6 Z* w4 J4 i

    1 X0 R) T: Y5 D5 n
    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, 2025-7-13 19:04 , Processed in 0.296132 second(s), 50 queries .

    回顶部