QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5619|回复: 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!" t3 F% n9 S3 E* T: G2 I; r$ D
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    8 A; `% s+ ?- N: c2 o( i5 W& O( w& u. @  m* I' X4 q
    面试官:Spring中的@Value用过么,介绍一下
    ! [, p( Y2 p- r8 a6 G
    8 ~8 \* v6 a) \我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ' A' S3 S9 R- r. O; X- [( q. x3 ?: N9 E) M1 U
    面试官:那就是说@Value的数据来源于配置文件了?
    ( {1 r" c: W  k% `& F; N) g) G
    6 Z3 s4 h  j- a  }我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    5 n" d! z* G! Q2 t0 S8 r+ k" B$ Z4 }6 d
    面试官:@Value数据来源还有其他方式么?  S) E. c1 p$ P5 u% t9 o/ y( O

    + \  G  j, C/ _( T9 |: Q% w我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。% G% o& t  B+ o; P! D$ I6 u0 ^) T
    8 @3 O" X3 t5 U5 y3 I* ]
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    : r4 P4 J) t/ k( t# j$ w. T8 n1 \6 k. h# L' q/ t0 q! c
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧' ?, q; Y! m% c7 \, L$ |9 h6 I

    ; N9 W; h$ a3 p) ^. f, e  i面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?* X: p8 ~+ v0 t, Y

    : T, [1 z3 F5 U4 D3 I我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    3 }. S3 |) ?3 I7 j. N
    - x$ I0 g% s" A. E% R面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?1 z0 _4 F" f& n; S; h) M

    9 h2 K' H; n" ]: V8 W$ l* {7 S我:嗯。。。这个之前看过一点,不过没有看懂$ l. m! P- N) N" a# Q
    ) I4 C; R0 F% \' X# h
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    * A( L$ N3 W) K4 b+ K  N/ w
    * f8 H* ]3 r4 m- G/ B% q% Y- I我:3万吧! A+ i2 o# [8 @, d: L( y' }2 W7 p
    ) y+ N4 C1 B+ y" g
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    # x) v0 j# G" N3 r$ H- S( |5 i% L6 d( \  t2 `! M# J
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万  l& D& l) e, L' Y0 x
    , N% T. X2 e+ M2 f" D
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!+ z6 a6 `- q) c/ q
    ( s0 T  n& ?0 K) ?6 _3 c( N
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。. d, Y5 e. P0 t! [' f
    , R: K& V/ V3 ^) u
    这次面试问题如下. o2 B2 L2 V9 A; i- A
    ; v) C3 Y1 m# s' y, d
    @Value的用法
    7 L% Z, `% X" D( i
    5 t6 [8 E, p- l3 I6 j0 h' ]; n@Value数据来源
    7 ?8 f% P" e1 R8 c& J1 y1 o  P; g  M1 {' ~( Q
    @Value动态刷新的问题
    ' {# D; W- Y$ ~3 I, I' t3 @3 y/ K% y# g6 m$ G; j
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    7 Y! U- g2 Y! n' Q2 W0 C9 s2 {: ]8 s
    @Value的用法+ P" B7 |. k# L; I# |
    2 g3 q6 y3 b. z
    系统中需要连接db,连接db有很多配置信息。  a% m( m7 ~& l, U4 ]- |

    2 j& A7 v" b: R系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。% s! Y/ K/ P, I& o0 a

    9 {) c, o6 L0 O9 X还有其他的一些配置信息。. A0 X; T1 \) l
    8 v& d7 Y! ?( V5 B& K, e0 ^) s- X
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    - ]6 [1 ~- C+ Y' L0 q
    & G, J7 k5 m# z" B那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    ' c, J& L. [  i6 y6 h2 O+ f6 x" R" m6 L  A! b
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ; E/ k4 f5 J, g* `4 I4 h* w& ?" `, F: |$ ?8 w
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    + g$ b/ {3 g  C$ ?) W# m
    / T# I- ~8 y# [" `* |@Value使用步骤% T) E  c7 g9 o& m% d% R6 z

      h. r+ ?# m) O4 C: C0 l步骤一:使用@PropertySource注解引入配置文件
    2 U2 @8 s" \2 w4 C$ |" [2 ?( V) W8 I1 d' K6 P" `+ ~9 V
    将@PropertySource放在类上面,如下
    5 C  h5 V, ~+ A; I" y3 o$ t! l
    0 h/ |1 |  c6 |@PropertySource({"配置文件路径1","配置文件路径2"...})2 w5 L: `: M+ C% J( x
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。: t* w* P! L! H& t
    4 Y: G  B' H9 r  D+ e. g% h
    如:' g* z  H& K  J

    3 v. P" K3 l- W( Z@Component
    3 P9 {1 {# B8 N: ]- D# J@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    / d. U3 l# z) j: q  Hpublic class DbConfig {
    4 r0 l' E- C6 \+ b% t0 p}# b! Q( S2 K) r' ~
    步骤二:使用@Value注解引用配置文件的值  R1 _6 d  ]- c( u! S! C
    4 M1 H( v7 _$ u1 g! F) N0 l
    通过@Value引用上面配置文件中的值:
    ) I! b7 B" H3 r6 W2 P0 ^! R  ]5 `+ u& [! P+ m- R7 R$ V
    语法
    & W' y9 r/ q' A8 A2 y; H' i: x' h& k! @- p. [' b( g, t
    @Value("${配置文件中的key:默认值}")1 }+ o% u2 z4 w. _
    @Value("${配置文件中的key}")
    ' q  Z, s# i4 P5 P9 b如:2 |! d9 M$ q. P, M/ c# p2 ]9 f
    8 N6 d5 [& z: p/ P4 b
    @Value("${password:123}")" K5 y0 y6 d# i7 a$ N% Z
    上面如果password不存在,将123作为值
    : Q/ [  I" j- _( Y9 B; v
      ?' {" s: |+ S4 C; c8 F7 Q@Value("${password}")
    2 z/ V7 b+ h* m2 R上面如果password不存在,值为${password}
    5 b3 l9 U& @' `# `6 O. N2 y2 o, E) ]! X" e, g, I' r2 E
    假如配置文件如下
    . {7 ^3 V; t, j7 d7 y5 w* k7 A! [$ V
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8% M5 e9 F, x5 [/ H" |  x
    jdbc.username=javacode/ @5 P% T& h/ m2 h; v6 k; F" i
    jdbc.password=javacode3 X; ~, E: m8 \: j; C+ E+ X  U
    使用方式如下:7 i" I# j% k9 V- t- O0 ]

    ! P5 ?* o  x* @/ F& ]: }@Value("${jdbc.url}")( w: Y: V. m$ Z: [
    private String url;
    ' a( y* a' T, @( }  g; t  I) N9 h! p) E3 w
    @Value("${jdbc.username}")
    8 ]  h8 p: L1 {7 X! q& K: eprivate String username;
    6 J# W  L$ j4 D" J
    ! [9 @4 `  u7 y0 W, [( T7 T& _@Value("${jdbc.password}")" l2 Q4 z" }9 g# t
    private String password;# e& D+ \  O( c( r& B1 F
    下面来看案例
    7 q* x7 Z3 G0 J2 a
    # Y5 N& x' _9 }6 S8 N案例
    + o$ o% P5 r4 _0 W
    % o3 X: `8 C3 j! u& S来个配置文件db.properties
    1 O2 f% E* o% C6 Y" j/ U
    % K' e) I8 R0 J. {jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    0 \0 T  Q' t' zjdbc.username=javacode5 s6 I$ P3 Z% F7 ^9 l' J
    jdbc.password=javacode
    * f& O; u( f. p6 R# p来个配置类,使用@PropertySource引入上面的配置文件
    ; P7 x6 L' g( }: h' z# U- s8 V/ A+ W3 X7 c1 P' A1 Q
    package com.javacode2018.lesson002.demo18.test1;
    ( h5 N+ V' Z/ N& d
    . }" _' E# p. O2 I" j, Pimport org.springframework.beans.factory.annotation.Configurable;+ z+ W0 J, d* i3 i
    import org.springframework.context.annotation.ComponentScan;$ n5 b0 ^$ C) m- J5 ?6 V
    import org.springframework.context.annotation.PropertySource;& c9 e1 ^( L2 E+ d( K) |
    7 J, {. M& k+ D9 W$ M, i
    @Configurable, P( v: p6 ^% _* S& O: B1 y( `
    @ComponentScan# l5 M7 z% L$ b4 ]: i: U9 u
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})4 s7 \" b! M9 A2 R( Y1 y- S
    public class MainConfig1 {
    ! P$ d: k# {: M" U}3 F' X* o3 h$ J, O; v1 g1 l
    来个类,使用@Value来使用配置文件中的信息
    ; L: \6 ~/ U- b$ R3 V+ f3 r( w3 C1 k  e+ s0 q; w
    package com.javacode2018.lesson002.demo18.test1;, N$ v* [6 \" T3 f# S3 U- q! h

    3 }% Z. D2 G# C$ o7 Yimport org.springframework.beans.factory.annotation.Value;" C! K6 Y+ d! y6 B0 D! y
    import org.springframework.stereotype.Component;) t. y1 L4 g$ e( i

    ( M: x6 G* |2 ]% V. P# T@Component  s8 ~8 X1 p, d2 u
    public class DbConfig {
    9 a2 \: o. c/ k" _0 o8 W% a$ R, R  o1 M3 D! S- H9 f) C8 I$ x
        @Value("${jdbc.url}")% N5 K$ g9 }# g, [6 n/ d3 R# Y
        private String url;
    ' ~/ h2 v/ B2 s  a4 ~5 R: |$ \
    & Q/ p) M- V+ z* j* {* w0 O    @Value("${jdbc.username}")' ]9 Z/ ~, f0 O
        private String username;) e7 Y+ m, h' x8 r8 W7 m
    . h2 C# _; Q/ t& q8 m+ {
        @Value("${jdbc.password}")
    + C% u, Y# u6 K  @    private String password;! b# R  w; ~% b* e: J) o. a% |. n# B

    : {2 t. J  N' }; y" g- k$ o    public String getUrl() {! I9 B5 E$ z. D. `( P/ \- _
            return url;0 h1 W# @8 T: ^2 i4 V
        }
    / a) N/ M$ i8 S$ b: K. d( T  M8 m! y/ x& z8 L. A
        public void setUrl(String url) {
    1 A0 w0 D) I9 ^9 Q; D* c/ ]3 }        this.url = url;1 v6 l- P$ d: h3 L: _
        }
    : E  B& |6 p3 |2 C2 `4 m1 g3 I) Z1 E; b
        public String getUsername() {
    " X+ r( q) E3 q        return username;
    0 p5 n; j' {$ {! {    }
    . [( Y8 D( b$ O1 ~; d8 L1 `! S9 y$ b  Z( h7 f' i
        public void setUsername(String username) {. Z- q# r7 l# ^
            this.username = username;
    4 ~  j- K  P$ ]4 m* h    }
    9 i2 g% `% F% }* R& `
    : }; q8 w7 j+ b' w" \& g/ q  R    public String getPassword() {
    5 A0 r' i' O% Q; W! z* `1 o4 e        return password;
    , M& o( j" S; }" k. {& y    }  Z$ Z% i; M- p. P4 G0 ]

    ! O! k. w5 A* O6 f2 u    public void setPassword(String password) {
    8 y- s  G1 N0 U5 |* F2 V        this.password = password;6 u8 P; U7 D$ |& ]( S" d
        }
    9 V$ U7 T" ]6 d  `% q/ K
    . m) I* J2 D4 x$ r    @Override
    3 P  o2 D9 _$ `$ d& x    public String toString() {5 P' K! m: j, i' {+ y9 S% ]
            return "DbConfig{" +- Q  u  P1 l$ U$ ?7 p* k
                    "url='" + url + '\'' +; B& l! M7 ~5 M  ~( G: a8 {
                    ", username='" + username + '\'' ++ ~/ Y* U6 D) E/ a" M1 f
                    ", password='" + password + '\'' +, d$ [( n0 I+ s0 X! y
                    '}';
    , ]2 R: f5 j: f, K' i8 ]- X    }8 N- |! J0 Y4 o8 F4 A8 n
    }
    3 k2 Q! n/ i. w8 s) D- P: n: O& T上面重点在于注解@Value注解,注意@Value注解中的
      x8 A0 F3 P! i
    7 P% k( i6 c7 w% N: P, _. Y来个测试用例  U. D, F6 _% C6 L1 R0 [

    ) d# Q/ m: K0 ]package com.javacode2018.lesson002.demo18;
    6 I) J5 F1 M) [" ~7 f! ~$ x
    3 E1 L, @  z& r, _; Y+ |7 qimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    . [$ T3 D: H1 Oimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    $ ?! z' t7 h( a/ a* S4 T* Qimport org.junit.Test;
    1 f6 ?0 s0 y3 N- mimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    6 x# D. j3 b5 l
    : B% _* V$ r* s/ y& F  F5 e4 C* gpublic class ValueTest {/ Q) U" G7 \- T. |$ l7 u

    . n. `% Z& v+ q    @Test% ]: ?( c+ Y3 H4 P
        public void test1() {0 O9 Z$ c7 A; V
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. U- k2 |7 v% U
            context.register(MainConfig1.class);9 g/ J  a6 A0 N
            context.refresh();6 g6 z0 j% J! p" n% W

    & O! T" `0 P0 }! H9 D        DbConfig dbConfig = context.getBean(DbConfig.class);5 {" e5 B: ^" R0 I' c8 b+ ~
            System.out.println(dbConfig);- ~' ?4 r: m: E7 u4 c3 w. X  _
        }
    2 q5 Z: s. K4 x}
    5 _& X: Z8 w) Y# u9 ^8 z运行输出& s, \5 k. K) J5 V5 r* ]/ {& c% h6 F

    5 @) [. v$ N' p. bDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}# x' `) I& w' a; P9 m+ O
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    & V1 c$ ]8 P- J, ^- g0 y* z
    ( ]6 h8 L# w8 F- P  H@Value数据来源
    ) c* ~4 y* ~% H1 \9 j
    ' U2 ^( {, `# \2 k( R7 h通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    ' [8 j. |9 k5 h; U/ P* O5 j5 `) e' Y% G6 s  G% T- u0 X# C# r, n1 k
    我们需要先了解一下@Value中数据来源于spring的什么地方。% x# O, h' |0 J7 l
    ' K& j3 Q6 s# P7 f
    spring中有个类3 _; W$ K$ ?0 I2 C
    . ^$ B) W) T  q1 s
    org.springframework.core.env.PropertySource! S1 G9 t( Z" a" @$ l: E
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    ) F, O5 _; l% |" `" x1 e. g' Z  k/ @
    内部有个方法:. T# x% S6 O# v

    , h: }9 s0 S) l. ?1 z6 xpublic abstract Object getProperty(String name);1 E( p/ ?8 r. m: I7 r/ x
    通过name获取对应的配置信息。
    2 A" g: Q- X+ k6 L/ D
    6 K( {9 S  K; S4 }$ j2 @. k系统有个比较重要的接口  `; Q2 M6 f6 \" d# w
    ) z$ o2 L/ `5 c- @& I
    org.springframework.core.env.Environment$ r( f+ J' U) r' t% I/ |
    用来表示环境配置信息,这个接口有几个方法比较重要
    3 U! f/ l; h5 E! [8 h
    $ k9 S3 g! h: @7 A  t6 I. H1 nString resolvePlaceholders(String text);) d$ v+ J- k$ R* _6 C
    MutablePropertySources getPropertySources();0 |8 e; Y) K% o; x
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。$ N. N7 S/ u5 Z+ t* _: b2 x

    2 e  A3 m3 X5 S" V5 E  `$ \7 N- p3 `getPropertySources返回MutablePropertySources对象,来看一下这个类( D# x+ h, ~# `7 @# U

      s5 y! v6 {  P! B( opublic class MutablePropertySources implements PropertySources {
    0 K- }- b7 }! m8 i. r6 S
    ) J. B( N1 [3 e4 |, S: o/ }    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ! V; h2 V5 q+ g" H  s$ ]2 g: F5 j- V/ s# [) u7 t# h  `
    }  E1 U, L4 ], M2 f
    内部包含一个propertySourceList列表。
    & f6 I+ j0 E' e/ M$ K9 j; d8 d1 V- S& \0 e6 c0 S" B$ v  L3 [
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    7 v" Z' D3 T4 ?- G: ]' y2 Y3 k$ `3 I# G  X# W% w. l" @  K6 z
    大家可以捋一下,最终解析@Value的过程:$ @* `& _5 Y; h- C

    5 i* v- L2 D+ ~& W1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    ; ~- S5 N4 g% I2 H+ ?9 P2. Environment内部会访问MutablePropertySources来解析
    7 B' _% S0 J% u3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值$ ^) u& ]/ }" \' W- X
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。8 u# o) [9 a4 B  o' L
    5 L2 b. V% D' n- H
    下面我们就按照这个思路来一个。
    8 ~0 j8 |$ }. T( j) d, W
    . w4 e' y0 u1 ~& q来个邮件配置信息类,内部使用@Value注入邮件配置信息5 n3 l7 Y3 x4 |

    , m. ~5 u  w' d& Z- }; r7 J8 g% lpackage com.javacode2018.lesson002.demo18.test2;4 N' p- P& U7 C7 _

    . l! O/ j# \! H+ ^" r7 J" z: vimport org.springframework.beans.factory.annotation.Value;
    1 _. E) h7 p: f* Iimport org.springframework.stereotype.Component;( e0 M: M9 _* x: ?. u

      ?3 x* `1 S- [: U' v( R8 W* |" }. a/**$ N8 v% i9 M" `$ b* X5 D5 o: m& [
    * 邮件配置信息
    3 N# Q* A. K. c4 P) | */- s! X8 n! ?% \+ z
    @Component
    . }7 J+ m4 F- [. D! o2 ]public class MailConfig {
    - O& y& N9 r0 T; {( k: q) M. m% C7 h1 ]7 _, W; i
        @Value("${mail.host}")
    1 F1 c3 v" ~  _8 o: M. S( s/ }    private String host;
    3 P: ?' K: X7 J* Y+ N
    " X% ^9 `% d; u3 d2 T: n    @Value("${mail.username}")
    9 D6 }3 D# @# e, R) r/ u, R) ?    private String username;
      b* ?& {. E; y( s9 D  C: Q3 |) S3 A( L; ~2 b$ N3 H$ X( }4 R
        @Value("${mail.password}")
    9 ~1 c1 V+ I! X    private String password;# @. ^) J( E) p- g" N/ h! C
    2 c/ F  i/ t( v6 Q
        public String getHost() {( t: J* i! y8 |7 {  n
            return host;
    / f1 }) o$ M) w, h" J    }
    4 D# A* g9 V  B# ~
    1 e- q: r3 k& o3 c8 |/ b! B3 t    public void setHost(String host) {
    9 [; b( d" ^6 e' h" x5 G8 U8 I3 L        this.host = host;# C3 {* i/ ~* L- w! _; N  F
        }5 P$ {8 B% m7 U
    . D: ^6 Y5 e- K& O" R2 E; M
        public String getUsername() {5 d8 }1 [( r2 N# }0 C  e) j
            return username;' Z* |, ?3 G. w- O
        }# h" J  H6 W+ x( `0 q1 A/ A

    " X5 [4 J4 q  |+ W$ `+ b5 D7 S    public void setUsername(String username) {
    / Y" |' f: u& T/ |. H" `( Z) Q3 c        this.username = username;! k7 l4 _: y# ?. N3 e5 i' E* ~  I
        }
    # J+ n( a. b' Q0 r( r% Z4 g, W1 m$ i2 i1 D0 B( [; M
        public String getPassword() {
    / r; R* \2 `8 S0 S6 v8 l        return password;- F4 W- K8 b. Z/ i+ \
        }
    5 c0 i5 `' j: m* T( \. E
    1 `$ c2 F$ I; b7 k3 p: j    public void setPassword(String password) {. b0 |7 c6 l5 n, e8 y6 |) @
            this.password = password;# D0 M5 `" @3 C; Z
        }6 a, k2 K9 _7 n! w
    3 @8 c$ i7 m5 x0 Y: ]" U
        @Override
    3 T) o* z& R' g9 J" V1 f2 A! e    public String toString() {
    ; N" v8 s; d! B* S- j5 l* K        return "MailConfig{" +
    : j6 J/ z5 h3 S                "host='" + host + '\'' +
    6 K- F4 }  B+ {                ", username='" + username + '\'' +- M2 P1 R- T, y. ?7 p/ \- `
                    ", password='" + password + '\'' +* \' J! l: d4 K3 F7 H) ^; `
                    '}';4 z- o0 R8 N- q3 j. w
        }
    % E5 u$ _& H) h* U}5 j% E& b9 _4 g6 {
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    9 @  `  U9 d. z
    + j+ P$ c& l. P+ L; a6 w! zpackage com.javacode2018.lesson002.demo18.test2;  G% ~6 i# w- W" ]' @* g

    ! M  d5 c: K3 l2 y$ c/ Bimport java.util.HashMap;( P0 ^4 b9 h! F- ^; }+ f
    import java.util.Map;& D+ N0 r; s9 a4 e3 ~% s
    5 ^. M( t( i1 \& U4 f
    public class DbUtil {
    ( k1 s. g5 E- q$ D3 s" o% }  o+ Z$ \    /**
    # R1 T5 `/ n6 [- |% z     * 模拟从db中获取邮件配置信息5 _: |0 a& F4 ^; ^5 [* Z  z
         *
    4 N9 y/ o3 }! f0 u4 h     * @return9 r' Y3 x* D2 E; S" h- ~
         */9 q" O' M' j, x( [0 d0 E/ N3 H
        public static Map<String, Object> getMailInfoFromDb() {
    + ?! ]2 ]# v4 C3 S- L        Map<String, Object> result = new HashMap<>();
    7 q+ n5 o" J8 ~& T" n6 F' }        result.put("mail.host", "smtp.qq.com");8 k6 b6 c( j8 r
            result.put("mail.username", "路人");3 q$ p& E2 d) T, D
            result.put("mail.password", "123");
    5 s6 q* C4 ^3 x/ v% r4 Y        return result;& d9 f* K9 \& W* C
        }+ s) e% y3 }" V6 N0 ?0 y
    }
    ' T7 I* s6 y& @! v来个spring配置类
    2 R' E3 F& \/ y/ i; U7 y& |0 R9 n6 L& N- ~& o: c
    package com.javacode2018.lesson002.demo18.test2;
    " ]9 C9 h% j/ o2 d5 I- m4 ~6 D$ ~3 s# _% o# w; G: o9 K
    import org.springframework.context.annotation.ComponentScan;) E( Q; F, v1 c& w
    import org.springframework.context.annotation.Configuration;
    & i" F- ~( N. l- S4 N* @$ Z
    # v8 D7 h- |; m6 d@Configuration( N# p& ?1 m, E
    @ComponentScan
    0 T6 i4 }1 n7 Cpublic class MainConfig2 {2 u* d5 {3 H: S5 g/ y
    }
    + [$ s0 l3 b$ ^下面是重点代码) o; V' u5 A3 Z
    $ [3 R; Q4 d( F" C
    @Test" ?" j- ?2 P" w; B
    public void test2() {% Y2 _# S* Q2 \( p* b1 P5 P3 p  X# O
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    5 w) K. ]1 V& ^; g  _% A! ~8 d9 I! h  o2 t% x" h% W
        /*下面这段是关键 start*/
    ( y5 A9 f3 ~; K' x5 K: m- J  e    //模拟从db中获取配置信息3 F1 |/ x; a% B/ @2 Z
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();) V8 i) V0 R) R7 Q9 e
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    $ C: k& z; E  S; G* T7 h  `% Y* U5 w; ?    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);# O0 V5 a) _$ g, U( ]: Z8 z
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高) a/ X3 `' O, s6 u
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    * i$ b: P0 d5 \8 u6 ~$ q: X    /*上面这段是关键 end*/
    7 {, f5 o4 ^$ W8 ~
    . d% B7 \" ~$ O) U    context.register(MainConfig2.class);/ n+ O( S9 D& ?
        context.refresh();
    * c0 I& _6 w+ ^) Z    MailConfig mailConfig = context.getBean(MailConfig.class);
    " k4 r3 w# ^: Y% H* O    System.out.println(mailConfig);& L$ h5 t: E5 \
    }3 _( [. z8 X& c2 \" \
    注释比较详细,就不详细解释了。
    ! z6 a( U' g4 v2 s. Y7 q8 y  N
    9 Z9 q/ @7 Q. F! s7 o- A直接运行,看效果2 }" m7 P. s, b( ]5 m
    8 o; P& A4 n) b9 D3 [
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    , e0 t* u9 P  T: q' u有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。3 X. g/ m) e8 V# W6 L

    * c5 L* P0 i* V* l$ v) `上面重点是下面这段代码,大家需要理解
    * y& i3 f5 T1 P- k
    3 J$ B/ ^" K' }8 q7 X/*下面这段是关键 start*/
    ' b# @, x: ]) [3 D# Y, P//模拟从db中获取配置信息
    ! S5 u6 H  j  y& E7 ~7 U, d) F' yMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    : {5 @  [( J' w3 V# r* V+ ^//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    - ~5 F, f5 Y5 f7 n- O9 w# c! xMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);: o# D+ n9 ^& `# v9 d: Y% d
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高8 O1 W% ~* t4 S. i" v  V
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    / `2 I6 ^7 K4 ~, `/*上面这段是关键 end*/
    " ^4 I4 A- T, _& |5 s咱们继续看下一个问题* v6 v2 x) J" [1 \. d% p
    ' t4 h3 `- S: N& X4 s* W: l( A, I
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。5 ?! }) x3 o8 Q1 C1 j! ?- k/ Y
    : k9 \1 C- z4 H
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    2 Y3 K3 \% c8 F7 A% e  o* w0 A7 l9 D: Z+ _5 @  S
    实现@Value动态刷新
    : h- f! A) [; D' s0 t
    . N5 H. k2 c" q  g2 {7 z先了解一个知识点
    4 `3 C7 X" P/ O/ X! q  x
    - @' \) o- D( a- L这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。) _7 u# V4 }) l9 W6 q4 @! j

    6 i$ Y& G: A8 `6 N这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    & G9 i7 ~! N# |6 q/ h# N: l! j
    + }  p. }7 U0 L7 R' W$ }bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    - z; S; C$ e3 [8 A$ D, u' r/ ]
    2 g. A3 D4 a( T# ?: pScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    # V  u+ o4 {) r4 ]- {6 E这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中; X8 g* R" H9 h) f1 |1 J) {$ u

    , w2 R4 Q$ [; K0 ?( s# vpublic enum ScopedProxyMode {
    ' r7 J: r( T% [    DEFAULT,. b9 b  a- `. q- x
        NO,
    0 Y( Q' g  U  h2 s    INTERFACES,$ i/ |! B; T# k( f4 |
        TARGET_CLASS;0 t+ M" i' k7 I( b% F0 s' P
    }
    $ ~# X6 q+ M. I; ^1 q9 n. }% F前面3个,不讲了,直接讲最后一个值是干什么的。
    7 i. L# E& f- `+ }; ]& x1 O* L
    & K3 {2 ?- {/ @' X9 j( t当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    4 F, l( F: c7 B$ }  o6 g
    & b( F( w/ P2 D3 Y9 ^& d理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。- u7 B, K$ `3 y7 V
    : ^' m* I) g2 O/ m' n
    自定义一个bean作用域的注解0 Z" Y3 |4 k" I4 [

    * k/ ]" x2 k0 ]6 X& D: tpackage com.javacode2018.lesson002.demo18.test3;4 V  [# @& h% P$ V! {; a
    * W( }3 Z: k! b2 i% L
    import org.springframework.context.annotation.Scope;
    5 z* }0 m& S+ E. n6 }import org.springframework.context.annotation.ScopedProxyMode;
    ' |" \8 T" ^: K- F/ A7 f1 C0 b( \/ @) M4 p/ s
    import java.lang.annotation.*;. M: W8 ]5 N1 K1 k, N
    : j: L: G4 ^& o* ?/ }, Z
    @Target({ElementType.TYPE, ElementType.METHOD})$ g! K' S4 q6 c& o
    @Retention(RetentionPolicy.RUNTIME)7 Y9 ^1 l# W( I. y
    @Documented
    + X5 k2 U6 k+ u3 p@Scope(BeanMyScope.SCOPE_MY) //@1
    $ b  m8 C" H& ?! \public @interface MyScope {1 q9 F& x6 K' [% o4 D: D: V
        /**
    & S/ u& v" V6 _+ g( E5 Q4 B     * @see Scope#proxyMode()
    2 G2 i8 d# a- o# A; e+ |     */
    ) E& w! p# z- \    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2+ y- ?" E! j" Q, B
    }
    8 J/ z" ?' A( C. t  I; F& W: T@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    $ _3 ]& g% `0 M; T
    7 \/ s) \: h$ I  h7 y9 v6 k8 P@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    5 A6 \+ ?/ F- \% K6 y
    $ A4 d' G( ?6 _; `+ k@MyScope注解对应的Scope实现如下
    ) X! s; W. Z: b
    4 t9 G; c5 n3 `" m7 v1 npackage com.javacode2018.lesson002.demo18.test3;
    , b1 ]2 K4 W+ ~' T- D; ^7 k" ~. X9 ]" [0 E+ q
    import org.springframework.beans.factory.ObjectFactory;1 `/ k; C- Z# m
    import org.springframework.beans.factory.config.Scope;
    , Q2 Q# h7 M: G1 [: y/ _) q6 Iimport org.springframework.lang.Nullable;
    1 }+ [( b- D$ D2 G1 H! O$ C" E6 A( }6 q* n2 B' y
    /**
    # D2 X5 V3 D1 T% N4 S2 Z: a * @see MyScope 作用域的实现
    : @$ \. Y" W5 _! n% z1 g */
    2 n: `; r# i5 ]% w  Q" v  G! hpublic class BeanMyScope implements Scope {* B. u, V. R& b

    , j! Q+ j/ t* d7 D. U    public static final String SCOPE_MY = "my"; //@1
    1 Q' ?* y5 y% j; \2 V3 C7 x! t( ^- H* {0 ^) W& ~0 E
        @Override
    6 U! w) k( T1 `3 @    public Object get(String name, ObjectFactory<?> objectFactory) {
    . ~8 A% Z2 j& e3 h0 v6 B9 }6 H. }9 Y        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@29 j$ F, `, W# N1 x
            return objectFactory.getObject(); //@3
    - P9 `* q" s+ z    }
    . Y; O5 j+ |& y  o& P" h
    1 O( F% I- Y$ i4 n" Q7 b    @Nullable6 B' j7 g% c' O1 {) i- ^7 a
        @Override
    * I) Z: m( I8 Y: W. ]5 Q    public Object remove(String name) {
    ( h/ d1 E/ B1 b; `        return null;( V. Z# i" ~: Q- [
        }' g, p& r5 K. |2 u' a; {) e8 X
    - F% t: X: E  u, J; J
        @Override
    3 p7 A) ~+ C7 O2 h, f* G    public void registerDestructionCallback(String name, Runnable callback) {( p& J( r8 e8 [1 f) n* ]
    . v2 B; r# y- ?
        }
    : l0 _1 l% J$ A- z# s$ Y" l; t. k' u! a' A% B5 o2 `) o
        @Nullable5 c3 l% }2 X  Z+ O7 D- S
        @Override- s) I7 t. g! h( N, J3 r
        public Object resolveContextualObject(String key) {: V# ~/ s- n7 N7 M7 m3 \6 G
            return null;
    1 v: ]  x0 b6 ]$ d) W    }7 K6 V8 N5 p2 U8 I- ?( g
    / u: Q$ N, H0 Z
        @Nullable
    - _- A: [* C9 _- ]  j7 J2 B3 B& N, Z    @Override
    1 L2 i* ?, T0 D    public String getConversationId() {
    % X0 H# \& J! V' x        return null;, W! E& C7 x; e/ g
        }; {: {. ~' L, L0 L
    }
    * v" x+ f$ W9 ], o9 v@1:定义了一个常量,作为作用域的值- `9 h8 }7 _; M* N

    4 R0 {& r6 i% I0 {; Y  i' @! F@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果: f  |7 p- s4 o

    " @+ U' [9 h" _- E3 F- q@3:通过objectFactory.getObject()获取bean实例返回。
    8 i, q0 f6 z4 l) O5 t8 Z$ e( O7 k9 T% B+ y4 g3 Y! ]- ]
    下面来创建个类,作用域为上面自定义的作用域
    : L% y/ @2 j9 G' Z) M3 Z8 T1 C
    " a$ |5 c1 l' O9 q, Rpackage com.javacode2018.lesson002.demo18.test3;* V; s# @0 X, E
    ( H2 W- f  `$ d" z* w' I
    import org.springframework.stereotype.Component;) i6 j2 X1 i9 f+ H
    - b5 w# C- }  C
    import java.util.UUID;; f7 I& ?5 c+ X( H9 W
    2 C3 _+ T# N1 j- J! w/ ?
    @Component$ S' o) x4 X3 H2 Y3 ~6 U, q
    @MyScope //@1
    2 M% c9 I6 Q# |+ l  H) spublic class User {! t+ x2 ~) e8 f7 d9 P  W4 F# V

    + Q* K4 s/ G; I+ b3 H( t3 a    private String username;
      S( G3 f' D( w( ^# f0 N* h2 S* G
        public User() {
    : B" y, @8 @& X9 F& ?        System.out.println("---------创建User对象" + this); //@2
    - Y7 ^) _2 c3 Z- @/ c0 N        this.username = UUID.randomUUID().toString(); //@3
    1 \2 P1 a* }/ t+ @* v    }1 c/ g7 ~, B8 R- O4 V. c
    2 ?0 z6 ?: z6 n$ g, W7 s& \0 n
        public String getUsername() {
    + \" D  k" l6 n8 m6 l! v2 [        return username;+ m5 H6 f" j" w- f: z" A7 Y
        }4 I; o% ]% A/ G" V* |

      n# y' t+ P/ h6 d    public void setUsername(String username) {
    ; w: E5 g- H9 {5 @3 T$ X; P        this.username = username;6 Z+ W' ]% d& c4 r* g1 `0 B
        }
    # U. G0 j# x! d0 q% s" ?0 ~5 G2 r) H4 K! s; K  ^/ d; |# u9 P/ d, r) c
    }; M4 g0 A2 G2 ^0 Q3 u
    @1:使用了自定义的作用域@MyScope: C) O2 ?9 T3 E

    % e7 D, {* a% c( g3 D% R8 A@2:构造函数中输出一行日志
    0 Q) `) z1 @; X+ F/ Q+ T% X$ V. ^1 h/ f2 O
    @3:给username赋值,通过uuid随机生成了一个- Z/ S  A4 Z" V! W
    2 X' M- f: t: C/ u
    来个spring配置类,加载上面@Compontent标注的组件
    5 q! o: d% k, j4 n  b8 y
    : Y5 V; s, I+ ?package com.javacode2018.lesson002.demo18.test3;) M9 v0 t  Z& b+ W: e3 t% p% @

    7 S* l% `8 E  w8 Y. Qimport org.springframework.context.annotation.ComponentScan;+ n0 o3 `! W9 j' d  R1 q
    import org.springframework.context.annotation.Configuration;
    , v9 {  x8 J, l5 Q2 {
    " G3 \$ D, k5 {& O- G' |+ T@ComponentScan
    2 R8 k  E8 @4 q, D# q& i0 c@Configuration5 M7 o, \% E; ^) j- O& ^
    public class MainConfig3 {
    " v/ N/ M1 ]* x9 Y6 f}. N" J, m9 P: `3 I- `  a
    下面重点来了,测试用例% M3 g4 v' A. E- X# `  c- A
    " M6 ~: _& y& W
    @Test% O& n: d7 k$ j3 \% n
    public void test3() throws InterruptedException {
    9 ]# C9 f* T' p5 f1 Y4 c    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    * z0 ?( v& G7 C+ h, {4 |( p1 s    //将自定义作用域注册到spring容器中
    4 F' v( ]8 t5 j% o- F3 i    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1+ }, V( I5 ?( S" e: }- Z
        context.register(MainConfig3.class);" P; r( N- H/ f& V- D6 {7 [
        context.refresh();" d- |" T5 K( A# F, f( t9 r* L; G

    0 @, T+ _. C% [- d1 ^& D% W    System.out.println("从容器中获取User对象");) |6 y1 _- b1 x/ U8 ?5 z
        User user = context.getBean(User.class); //@23 a5 V; G. \# C( s
        System.out.println("user对象的class为:" + user.getClass()); //@3  A1 o" ^. @4 h7 }  W; V  \
    & c' I5 h6 V+ X  ?
        System.out.println("多次调用user的getUsername感受一下效果\n");
    ; y* e, Q6 t8 T* ?  X    for (int i = 1; i <= 3; i++) {9 p  E/ K- `' j) O  W: U% H
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    & B! R* Y9 V6 f1 {' ?& [6 `        System.out.println(user.getUsername());
    2 |# @* h3 D! ?% f, r* Z, M        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));4 J7 n* ], K# Q% P* X& ^2 g7 M# t
        }# H$ W: o2 h1 N9 k6 S2 D0 y9 z
    }
    + ?% X: X( W) w* ^4 \/ e@1:将自定义作用域注册到spring容器中
    ( K* E4 K! M- c/ b* L. B" T0 f- V3 o" x1 c+ N3 V
    @2:从容器中获取User对应的bean6 T) a) j- u( `" r0 m' Q

    3 W: K0 C+ W  U4 ~@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的% o4 d# a/ K6 P, @7 l( X

    1 m2 }& a8 I0 S代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    % s, }$ D3 m+ u) c: w& }* ?- T/ G# m2 u! S* ?8 T6 z8 U
    见证奇迹的时候到了,运行输出
    # `1 k& w$ r: i0 ?7 i4 J3 [" V! I4 n/ O! }3 Y# i4 b
    从容器中获取User对象
    2 G# ~- C( ~" W7 e0 z# l" i+ q) guser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127, a1 N% j% j3 Q2 p% _+ h# F6 b
    多次调用user的getUsername感受一下效果2 e! V6 K. u9 K. C! y5 G

    , ~$ w; T8 o* b1 A********# Y* @3 x% t- W  O
    第1次开始调用getUsername6 w6 v7 q! j9 ]4 q
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    " _7 h+ s$ e6 Z  Y6 _---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4  o) d+ f2 c' b' |4 z3 Q- o
    7b41aa80-7569-4072-9d40-ec9bfb92f438% D# L) }1 S; }
    第1次调用getUsername结束: N3 _. v8 ]# z( \3 A" v8 s
    ********
    ; a2 B* i1 i: h1 A3 u
    0 Q; i' H7 _( J  f" w/ V********
    - y* e( |8 r0 i! T' b5 x1 }& P3 w$ F第2次开始调用getUsername. l) n2 p0 w# V6 f, o1 x0 p+ g% i
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    ; M; r4 m2 ]! {0 ]% k---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    2 d4 c! W9 ], e2 G4 A- a5 E01d67154-95f6-44bb-93ab-05a34abdf51f, V$ U3 @7 u9 V) t7 e3 i  m
    第2次调用getUsername结束" @* H+ ~7 a4 e1 m% `8 m1 I" |
    ********
    - L* K2 m' }+ V1 J% x! k  }7 J5 C8 d% r: o' m8 [' E
    ********- ?; R) p" G/ q% X* n' c
    第3次开始调用getUsername
    ) h$ @/ ]" O0 X" T* eBeanMyScope >>>>>>>>> get:scopedTarget.user
    / [8 A5 R# [' x: t( g% M* b8 p* f---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15/ P  S' ?+ P/ q
    76d0e86f-8331-4303-aac7-4acce0b258b8
    1 a1 Z* f" o3 y% h第3次调用getUsername结束3 C' R  g& D8 {  j1 O
    ********& n6 l& V( k! i# V% @7 _
    从输出的前2行可以看出:+ A" ]  e  S( F: T* h0 x* W+ |5 m
    9 c) u+ w" M3 ]* b8 [5 T
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象2 H# v5 F+ C: F. [

    % Z* g7 ^2 Y& n# O' g, D; S+ o第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。# ~: i/ i  E7 w; x3 O6 ?: {) k
    ' w" G% r7 E% X$ O) f( Q
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    5 C3 u; O! T9 @/ [; u8 N
    + N3 b2 D8 j1 x' w通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    ' o5 |% l9 ^5 R$ X  o( j8 p9 w
    ; K" M  `, r! p: {4 g动态刷新@Value具体实现9 S0 \" _4 R; N$ f
    4 J& D% m( p* }+ r: }" @4 v
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
      v. J% Y+ B; a8 }  s( T
    4 R  H! s) X- p# j' d先来自定义一个Scope:RefreshScope* q) u" w9 \2 x2 P9 L
    / v; Q; Y: m# b# t3 L/ E; ?7 U" W
    package com.javacode2018.lesson002.demo18.test4;
    ) q' w3 g( B/ V( _5 P9 D3 e
    : E5 D. C" B1 B  x: u9 ~8 oimport org.springframework.context.annotation.Scope;
    6 x, e) e2 u8 ^import org.springframework.context.annotation.ScopedProxyMode;
    ! j7 F1 u* x* M( m% p  v
    ' }( C5 i2 k8 ?6 ximport java.lang.annotation.*;0 V( o$ [  ]& w' |& o4 Q  z. T
    4 ], n' T8 u5 I! ]6 t' C: z
    @Target({ElementType.TYPE, ElementType.METHOD})5 D$ O- L1 X8 T5 n- H4 j+ S/ F
    @Retention(RetentionPolicy.RUNTIME)- c' }- u7 {# P0 ~- _( L4 ]* R$ d& l
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    7 D- j  W% G( \$ l' J( L' S@Documented
    2 |% P# n9 X/ W- O6 Zpublic @interface RefreshScope {
    $ n8 u! _  T$ z9 O& W& ?3 D+ U" o# H$ q    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    % B- t' N/ _! B3 D: I2 m}& m2 X( u1 L# Q1 E
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    * J1 E/ z. ^1 q! F' p. W. C  n  D: r# |. r" j- n0 F
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    ( m0 o% Z: B5 [* q
    # A% _* W. [  N1 w1 N这个自定义Scope对应的解析类
    ! H0 y" p* Y, u$ O( T, F- Z2 o; z; e+ a: F. r3 C: g8 W: e! c
    下面类中有几个无关的方法去掉了,可以忽略
    ; U* ?4 W  ?% I; P2 c* E& u9 q* M5 p, ^' ~) u' r6 c: b1 y6 o' ]
    package com.javacode2018.lesson002.demo18.test4;7 b& X* Y/ x4 ]1 e! b$ x" Q0 i

    ! }$ t3 o4 T3 X; \3 L. T+ U& F  b% U8 w  M: T# N  @
    import org.springframework.beans.factory.ObjectFactory;
    * B1 q* P( I. |8 F6 }5 aimport org.springframework.beans.factory.config.Scope;
    ( k5 l, s1 M$ Dimport org.springframework.lang.Nullable;4 P" Z9 I( z. E8 H$ t* s
      X* K3 I; H' s5 A  O
    import java.util.concurrent.ConcurrentHashMap;! N  O, H$ z) v- U% `1 i

    ( ?. K/ k( s) P. J) T9 Y; }public class BeanRefreshScope implements Scope {& A. `# E. B5 X

    4 A6 T' }# S+ ^    public static final String SCOPE_REFRESH = "refresh";1 K, h- e2 A, {. F, B# \
      |! @8 ^$ P, u# @* Y  V- y
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    % X) l- _. i2 ]$ G/ I
      _, O: X* h3 I. H) K; h/ Z, g    //来个map用来缓存bean* M3 C& B9 [: b/ @# g" n4 p. I" G
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    ! t' e$ h2 X/ f7 }9 J
    0 H2 i9 z- n! E6 O8 F: A% j( }( ~    private BeanRefreshScope() {' `% }& K, ?% ~6 y% Y/ Q) R3 Y
        }8 _+ B+ H, R: \  v

    & s' n2 J$ G6 p9 [$ t' \    public static BeanRefreshScope getInstance() {
    . I+ s, F- Y% W4 Y" i1 S        return INSTANCE;
    : X) ]% T+ o; h2 I4 T0 H9 U    }8 L" R& S" q/ ^( O, x5 a

    2 _: M6 ~6 q" I& E! i    /**
    ' Y9 I: r) P5 D# N. t# p2 V     * 清理当前
    1 x5 P# o* t3 W" S     */7 T/ ~& g( c! X+ A) y  [1 k8 H
        public static void clean() {& L- u( a& b' q: E% `, j7 q
            INSTANCE.beanMap.clear();
    % m% ?! K2 e+ D6 o* v- t, P  _6 B    }
    ; e" f4 {2 c& c+ \, a0 X* E% D* n" w  W. T/ t: V+ H, R+ P+ l
        @Override9 I8 ~7 o5 O( i6 ?, {6 Z
        public Object get(String name, ObjectFactory<?> objectFactory) {
    5 H8 n9 o7 t, B- U6 e4 V        Object bean = beanMap.get(name);
    0 v8 V6 h1 m! z  h2 s+ z7 H        if (bean == null) {
    8 c. k  b# A. f% ?& D" ?, y5 r7 b1 w            bean = objectFactory.getObject();; R& l8 R4 s5 G% l: \
                beanMap.put(name, bean);4 g5 [+ k9 B9 W; |
            }
    4 ?" F0 A1 e6 {) f( ]' n) f% c        return bean;
    / H( }  S: d- R$ ]    }
    7 @& Z7 K2 p5 V# V9 k
    0 t6 O: \( R, N' i9 e) J}
    $ C0 x7 [# Q  ]' m0 y上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    : K+ Z3 _! @8 E. k; q$ V2 N/ L* h( r+ ^) }6 D: c( q. s
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ) O. [# V; L3 S+ y( j' W8 m& i7 d4 G
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    - K8 c3 F  [0 q0 L/ P5 m3 A
    ; O& h% w. K' p4 ?9 Jpackage com.javacode2018.lesson002.demo18.test4;5 b3 p3 y, k' g6 `
    1 K* t9 H2 E: [
    import org.springframework.beans.factory.annotation.Value;
    % E- E$ s: y3 G: W2 ~3 m' g% gimport org.springframework.stereotype.Component;% O9 @0 t; v4 Z) y1 z

    / Z$ Q$ V# N: L) r/**6 r/ g4 A+ ?; j: \2 n
    * 邮件配置信息* b$ K" L3 X' v$ U
    */+ S! N+ d! w' b6 n3 R# F4 V# H: T3 o
    @Component
    / \0 C' `8 C9 r& d$ p$ e@RefreshScope //@1
    & l0 Q, r3 g2 S# N0 xpublic class MailConfig {6 K; y; ]* N* W- }
    2 ^5 u6 m5 [  e5 }6 m* i9 g8 W$ v
        @Value("${mail.username}") //@2& F/ f% y7 W! Z+ P3 @1 R
        private String username;
    $ i2 Z/ X; ?3 @# q# f# c) U* [
    0 I) K/ F9 \9 _    public String getUsername() {
    ! u7 n6 X, r8 O' F7 L+ ^        return username;- o% L2 ?& X* Z6 r
        }
    + @0 j' C2 j- n' ?" P! v5 }1 X% }. B
        public void setUsername(String username) {
    ( a4 b8 u. Z9 D/ ]/ y& ?& w" T        this.username = username;6 s4 Q- u# R8 s# E% Y
        }
    0 [& j: A3 K! D2 Y, t' I; M4 K, ~! n- z8 T# \$ [3 J' {, p
        @Override
    2 R0 r/ k( {! ~( [: ~0 z    public String toString() {
    % M, S; W- h0 \        return "MailConfig{" +
    ; ?* Y) I7 G! D7 d! j                "username='" + username + '\'' +
    9 ]% k% g8 {; D3 ~                '}';6 L# D+ ~' w% P1 o) k% M+ G4 B  x
        }
    $ g8 I. F, s7 e}
    6 H- X: \- q8 s/ q4 a@1:使用了自定义的作用域@RefreshScope, O" Z' I: d. u+ n! p/ M
    2 t( V& B- [- `/ Z
    @2:通过@Value注入mail.username对一个的值
    ; M( t( R4 r* t9 H6 O6 m6 H( F
    . q+ }- c* w$ z! Z/ y) ^重写了toString方法,一会测试时候可以看效果。
      O5 w. W/ X& T- z8 P) _: G6 c1 g7 a+ H1 V) L3 H
    再来个普通的bean,内部会注入MailConfig
    $ x; `3 F- A% a& k
    $ A8 W8 ^# {4 N+ [6 u$ s/ Gpackage com.javacode2018.lesson002.demo18.test4;5 z: |+ E& f* B: G2 K: }/ I7 Q
    6 y) w8 o- @8 H, m9 V" B
    import org.springframework.beans.factory.annotation.Autowired;
    % f/ X  ?4 E$ g) G$ e0 C; Oimport org.springframework.stereotype.Component;
    % `. M1 e6 b7 h) \6 T* b
    0 j* C- N; G) w/ r7 A3 M6 a. E@Component
    - f, v+ @- k! Q( Ypublic class MailService {
    0 U& G% P# o* `    @Autowired/ p8 ?- Y9 \; o* U! O0 z! w
        private MailConfig mailConfig;6 W! P' a) s6 `/ ?. e) i! p. D
    5 c0 I" c1 F8 C, n
        @Override9 F% {3 P5 R& t
        public String toString() {
    ! m7 D2 \3 J& k, R% H7 j% |$ o! I) H        return "MailService{" +
    & D0 Y( l, B6 i+ H( r% z                "mailConfig=" + mailConfig +9 M- ^7 v6 ~" Z& B6 P0 r
                    '}';
    8 X) G' F3 a5 x1 h. M6 K    }0 m: D3 j& B% R4 h, _2 ]4 w
    }
    , j7 V1 p) q" x/ E8 C2 T0 d, V代码比较简单,重写了toString方法,一会测试时候可以看效果。
    - m0 T8 S+ {/ v% c8 F6 N! L
    ( g" Z5 ]# K7 D- o( y3 ~来个类,用来从db中获取邮件配置信息# ]' J; @  i8 |* g: e% U% ]- ?
    7 y% N; T. N) n# b( i
    package com.javacode2018.lesson002.demo18.test4;
    0 r& }3 `; o7 j" o- ]9 F3 |8 }2 X, z5 n1 X- ]  `
    import java.util.HashMap;
    0 W+ m4 Q1 p: Z! i9 m* k' S) aimport java.util.Map;
    , |6 n7 y5 v$ b9 Yimport java.util.UUID;
    ! D3 s1 I+ u% [( o1 Y' C8 o: X, p4 s3 g0 o) Q4 h- k7 [
    public class DbUtil {# E4 P5 D# Y9 t' m
        /**
    ' V. V! P! z: B, B     * 模拟从db中获取邮件配置信息
    ' o! h7 ]7 F6 Y5 c! Z. y$ r1 W( y     *
    % {- A- v6 m1 r. z; n- M# k4 b; A1 h     * @return8 e" R! [& [  d$ A* J
         */. j0 J2 o  }) ^6 q. a
        public static Map<String, Object> getMailInfoFromDb() {
    ' m  @- @, f" A5 B        Map<String, Object> result = new HashMap<>();6 p/ d  d% G' Q; r6 s( g
            result.put("mail.username", UUID.randomUUID().toString());
    - {& V+ p, O( @3 c; M! s! [- V2 \+ u; X        return result;4 Z, C4 g1 R, Z6 B3 M) o
        }
    / @# A( ^' s8 I4 h1 h/ `& F3 i}
    : }; y0 c& S3 `" e, c来个spring配置类,扫描加载上面的组件7 ]) y. W$ n2 m& r' a8 w# U! a. Q8 r

    7 B5 h' g2 }. Cpackage com.javacode2018.lesson002.demo18.test4;9 F  H" g# T5 u7 j+ O  s
    - k1 ]: o1 ?) J& G9 D, q2 _4 x
    import org.springframework.context.annotation.ComponentScan;
    % N, U6 [" R, C% g2 h7 A2 e" zimport org.springframework.context.annotation.Configuration;
    8 `1 S* ~7 |3 l+ K% v6 F/ h1 a& q9 y0 S0 t- W* a( S3 i
    @Configuration
    8 r! `: B4 {4 W( x) d* Y' Y0 _) c8 R@ComponentScan4 k) k/ K( p' Q7 f
    public class MainConfig4 {# w8 t9 Y7 G1 O# G6 r
    }
    5 L8 ?3 T. T7 L2 \来个工具类8 Z$ \+ k; k" U: v. I9 P+ @( x7 v

    + v8 x3 i( L2 j1 |$ ~0 n( R内部有2个方法,如下:
    , s) v  o; p  D4 Y( l
    8 V+ v/ q1 O$ c: E, Zpackage com.javacode2018.lesson002.demo18.test4;( Y8 [1 D. U, _/ z) Q: R# V% E# |

    & C4 s& b9 O) y# b" C* Gimport org.springframework.context.support.AbstractApplicationContext;
    1 n3 N% ]% G8 E- l6 d$ d& Yimport org.springframework.core.env.MapPropertySource;/ f: U8 a1 E4 H( @( H3 l0 O
    / U, M% Q: s9 n  w3 z  F, w
    import java.util.Map;% X5 H0 E: H) K. T. {" ?
    ! a. }3 |, J% \1 `/ d& {
    public class RefreshConfigUtil {7 v1 y6 T4 {3 I$ {( i& H( P
        /**3 N  l8 `: W4 v* g2 e% D
         * 模拟改变数据库中都配置信息
    5 q5 V  N( j% K- B6 b+ k     */
    2 o$ V- A( n- j9 s/ G7 ]    public static void updateDbConfig(AbstractApplicationContext context) {9 P/ W7 W! e6 M/ r
            //更新context中的mailPropertySource配置信息& i- W1 t1 o% y7 g3 O5 C5 J8 X; n7 u
            refreshMailPropertySource(context);
    / }$ _4 I% W# B/ E) N& ^( j" r% M& Y; j! x3 i/ I  E; a% `. f( C% B2 m
            //清空BeanRefreshScope中所有bean的缓存
    , f2 S9 ?: g& ?5 L        BeanRefreshScope.getInstance().clean();
    " X- S' H+ j( ]" H2 y8 o( v2 N    }) o) n) Z; l8 z0 z0 v' _

    - W) M) [& |" ~    public static void refreshMailPropertySource(AbstractApplicationContext context) {8 ?3 A% r& m& u! ]% Y4 M: E" Z
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();8 l1 }4 J3 H: t: ^$ G
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    * S/ K  p* F4 [8 a9 s        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    5 Y$ E+ l+ s5 l# H        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ; p% l9 ?3 k9 Q1 l5 R1 }    }. s, f, _% u8 E. }# B

    6 m- C, H* e/ ]: ?6 r% h. Z}
    . [' T6 A5 r- q3 F4 |  [) FupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息: J. ?, U5 R9 L' B, T& m

    0 D0 ^: a4 z$ b8 w$ d0 jBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。  \5 j; H0 l; v. q: r
    % i) o; I& Z8 m. _' o" T/ Y
    来个测试用例
    3 |) C0 \, ]8 H( _) C: E% j' C2 a5 z# J
    @Test
    3 S% I, ]9 p9 v# Rpublic void test4() throws InterruptedException {
    & j  U# Q# g7 a8 l    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();2 g+ H& r* ?" T2 b( I- y0 H
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    " i1 k: U7 w: J; I    context.register(MainConfig4.class);
    2 ~9 P- W: x% K+ Y  D! g    //刷新mail的配置到Environment
    0 J3 N6 c6 c3 |7 T    RefreshConfigUtil.refreshMailPropertySource(context);0 V( n% h- W; |# o% F& U
        context.refresh();! S, ~. U4 W: ]5 \( p  X  o

    . a% N- L6 N* k6 z- R3 s- O    MailService mailService = context.getBean(MailService.class);
    " B6 H; @- P7 @* s$ K: i    System.out.println("配置未更新的情况下,输出3次");
    ' o5 P% X; {4 J    for (int i = 0; i < 3; i++) { //@1
    : |% Q. v; ~! @* q4 {$ K        System.out.println(mailService);. H  m- B# V+ e
            TimeUnit.MILLISECONDS.sleep(200);$ \) u0 X" H& G/ K
        }
    5 B. A7 S, J/ P2 q; D% I0 d2 G6 t* Z9 a8 m
        System.out.println("模拟3次更新配置效果");
    + z1 T4 a* J* S% ^, T    for (int i = 0; i < 3; i++) { //@2' y+ x; p, K! m* C; J) q
            RefreshConfigUtil.updateDbConfig(context); //@34 D' B" R- H: ]7 I
            System.out.println(mailService);: t7 Y# D1 R9 G; p0 @6 D' ]; m
            TimeUnit.MILLISECONDS.sleep(200);
    " W. D& r  t) L+ Z' [    }) \! P2 R! B1 ~$ J
    }
    9 S: i* Q7 V# c. [7 Q@1:循环3次,输出mailService的信息
      Z) q) \: D4 R$ i
    1 q8 s; T7 U4 x! s& [@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息$ q1 j% y! {- Y
    9 t$ [' W) m% x* d+ q; W
    见证奇迹的时刻,来看效果
    3 _( B6 n" F# q! @' I  F& o& t6 R1 C# N
    配置未更新的情况下,输出3次3 G" M, l+ C( t
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}+ U) v7 C& X# Y
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    5 @5 }) l4 ~7 u0 I+ q+ Y0 l6 k0 KMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
      Q, x1 `) L' j' E5 L  R4 I, @  @模拟3次更新配置效果2 r6 L' N% G# t5 y$ r
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    1 r# Z& g$ m. y6 EMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    " z3 Y6 r5 z. J" y+ [  @MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    " M" s9 a5 @3 A) Q* n上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    # _# e0 H6 [: b9 X  L  ]: g/ a! P4 r
    小结; u* D+ |% o1 M5 X) L
    & p3 L& G# D. X) [
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    3 w; T8 C5 S' N. Z
    ) x+ H; e$ D. o: C7 b& q$ y8 v( s有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。: F! b$ \+ Y5 _- Y7 ]) k
    7 M+ l3 P# v6 H7 `; Y1 f
    总结% M& o* G# C- R' Q7 g8 H7 K& N

    9 _2 m* N- h  C% o; u; o- q6 ~本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    : _, a* `. L% C4 Q6 h$ Q$ K) F+ Y; ~0 n  X. z! [/ d8 s  u6 v/ r. q( t
    案例源码% J% a- `; d4 F* y" x
    . D/ k0 `- G- P! O
    https://gitee.com/javacode2018/spring-series. v9 ?! T0 v0 K; n+ ^% M0 a! K$ P
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    " K9 w8 [  }. |% I8 |
    6 R7 r+ ?3 t+ fSpring系列
    8 G8 }6 u: ]8 r9 {% s( P, h
    ; l0 c: u) L. X8 z# C3 h+ iSpring系列第1篇:为何要学spring?
    % G$ E+ m" j8 z- ~5 a1 l4 [4 I
    * B2 k  N; @) a( ^4 [/ X& B5 nSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    ( Z; t# B7 s, \, r$ x1 ]/ N& r  I* N4 W3 U! M+ c6 m4 Q7 r% K5 n
    Spring系列第3篇:Spring容器基本使用及原理
    1 c; T% e% ~. u+ U. Z5 V, j% m0 t
    Spring系列第4篇:xml中bean定义详解(-)
    , z3 \- F- q4 r, C& f2 ?+ w
    . E8 ~2 T! }: L. j" _/ ~Spring系列第5篇:创建bean实例这些方式你们都知道?  W6 ^5 z: O) |& l( A

    / N0 N4 F" h- Y- Q2 c2 eSpring系列第6篇:玩转bean scope,避免跳坑里!
    3 W9 D3 I& R% v
    + E6 e  L, L+ _0 a' \Spring系列第7篇:依赖注入之手动注入
    3 \2 |6 V& r1 y4 i) b2 t' D
    ( T1 w6 f" F/ x4 USpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    & p- s8 z7 B( J* @; S3 O' C" t0 Y1 Q! _/ b
    Spring系列第9篇:depend-on到底是干什么的?
    & D' _- n1 o4 G5 Y8 z5 m/ o! Y9 g) g
    Spring系列第10篇:primary可以解决什么问题?9 J% ^% C& H1 i% p
    7 ]% _  _" r- f9 u8 d: a$ s
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    ! {- A0 k' [: G) T
    & q& t9 L' S3 z3 G( oSpring系列第12篇:lazy-init:bean延迟初始化
    6 p* _" M' O7 W8 l8 _: y6 _: [7 l/ w
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)4 B4 n. A3 Y5 n0 A4 ^: {
    ) C1 N, a9 [0 \/ q7 J5 `5 D1 F
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    . H' F( i# G) l/ T* }3 U. ?0 o, H8 c5 i) M
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?5 t# q% V) V0 W9 D
    % U% d$ N0 O* q) G0 ]
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    . x" s& \/ J/ U! |0 n
    $ s5 v8 m2 g2 i6 p0 xSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)+ G/ i9 W- o  O* K6 Z( T6 H
      Q  s" [" r  I* Y; q+ U( z
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)0 G; e7 ?* r# e$ P

    ) W8 W; t' A2 n& s7 z( qSpring系列第18篇:@import详解(bean批量注册)$ i; w( O9 t1 G4 J2 Q9 s& r6 C

    2 Z. i0 j" L  K: BSpring系列第20篇:@Conditional通过条件来控制bean的注册
    " C, S1 ^4 G1 y4 o$ ~, n* S: t4 C( q" r2 Z6 z. ]1 [7 g& B
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    ; S0 q) W4 K/ N% c) r
    2 C: u  _" }: X9 KSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解3 \' n' F, L0 a& r9 u
    5 N' r& y7 U8 u( a; a
    Spring系列第23篇:Bean生命周期详解+ b5 M2 }/ c3 J$ m8 F4 `! S

    9 I0 w" |, F; t" ?Spring系列第24篇:父子容器详解7 ]: t! M- a4 L; B  p$ Q
    . S1 p" s4 R/ p; `
    更多好文章
    9 ?( m4 d8 x0 y
      y9 _+ L' S; U7 ]5 TJava高并发系列(共34篇)" ?" r5 D7 l8 G. c/ z, B

    ! c# y0 Y7 u* n: LMySql高手系列(共27篇)
      T7 |& B) v% I5 E5 y, ~2 N4 ]* P% A! @
    Maven高手系列(共10篇)
    4 p+ z7 \6 Y; ^  O# ^2 ~0 h- h8 u. z+ S) v! h+ Y- ^+ m% u
    Mybatis系列(共12篇)
    ( Z( T# Z. G! z+ J) P" i$ Q* m/ R- Q. Y
    聊聊db和缓存一致性常见的实现方式
    4 T5 A! A5 a( n1 m; M$ O6 i
    , B: z# f0 F' p6 U接口幂等性这么重要,它是什么?怎么实现?
    0 }4 J) x2 Y( i4 A6 x# m) H$ u" ?! p6 Z8 y2 m  z
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!4 B. s5 R# j5 N* l# e  t: |
    ————————————————. t- e% }4 D  w3 ~. f
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。) f4 R7 Z6 }# g  D3 W% \& E( D
    原文链接:https://blog.csdn.net/likun557/article/details/105648757, Q. Q7 y- f1 l, @
    4 q! Y# C/ t! ]9 E% c+ E
    - V9 N' j; z) f" Y
    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-9-24 01:27 , Processed in 2.881103 second(s), 51 queries .

    回顶部