QQ登录

只需要一步,快速开始

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

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

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

5273

主题

82

听众

17万

积分

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

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

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

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2020-5-23 11:01 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    太狠了,疫情期间面试,一个问题砍了我5000!
    4 j1 u: n, D8 f/ V8 y疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!$ U8 l7 x1 |; I/ z: V1 }% I$ r7 U

    ' t8 ^8 Z( d$ M/ N9 T' V面试官:Spring中的@Value用过么,介绍一下
    ' ]" @! M9 {7 ]2 N" v- u6 r+ K
    & g% k  k9 n$ m我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ' t8 @/ l/ P8 B8 A" O4 u- A
    & a2 ~! p' s+ y6 L面试官:那就是说@Value的数据来源于配置文件了?
    - E: [  U, F; d9 N' L1 v
    ; h& r; w3 V' D; L我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置2 Y) [% f; z1 F% M

    4 I% u* H" E- Q' k+ O& u  u* @/ \面试官:@Value数据来源还有其他方式么?; ?' n8 c8 {  F

    * f- a$ I- i, o) Y我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    / {+ b" O0 O# `7 k( X
    ) h( a- J1 X6 J' z  Z* q6 ]面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?4 p+ Z# H+ y  O7 ~/ M: g. m
    / ?$ \$ |0 o- ^6 J
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧% v) k, @6 C  c2 F: _9 Y

    + L% W4 @0 o+ ^2 ]0 X/ ~面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?6 t0 H1 n1 T7 k1 t
    - p, U# N9 A4 L/ p) q7 [
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    ' H) H3 v" d/ w) j
    ) P" @' h' y. x6 Z面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?9 S0 N6 C/ K% k
    + y# d* E- W+ t2 C' y# j# s6 B/ {
    我:嗯。。。这个之前看过一点,不过没有看懂
    9 s; @) r% }1 B) |" M  z$ ^. h5 e8 S6 Q& ]- X' M( l) j, B
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?* l, f6 n* l4 c$ P( V+ `% s
    ' [6 ?) G, Z& U9 K$ ]
    我:3万吧; E1 m* }! x. S$ n) c1 M9 S2 X; s/ X
    : P! W' b' o1 e5 i
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?' q' N; S7 U" }) [) _' t- B9 P
    / Z7 {+ m6 Q& `' s% t3 `, I0 ^
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    1 I; p! ?. }( s* p  t/ I1 }* O: ?# O* w) \# o( Y
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    . r3 a! F7 t. F( \. z) n0 R% o' W& [) w0 X7 T
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。4 D* M/ u. R  S7 d
    : p2 c1 T9 {& ~' x. ?3 H
    这次面试问题如下
    0 z/ Z  a* I& |  ?8 H; E8 z0 b( V, f, |6 }, r# b8 H$ t
    @Value的用法3 B+ s2 h8 m' d8 |* M7 Y1 f
    2 {, ?5 o6 B6 k+ J- N
    @Value数据来源6 C% _( \% y7 ?/ o7 r1 ?/ L6 o
    , s7 ~( }7 \; R' |
    @Value动态刷新的问题
    7 y6 B, L$ ~9 K6 n) {2 v
    $ j: m+ v8 M5 j0 W' ^# `( D7 J下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    5 z. l5 f, W8 }7 J. a
    7 g* C( x: G$ Q9 S: S7 [0 W@Value的用法0 D" p' R" g; M3 E

    . d" s! y7 T* l; R$ c- P系统中需要连接db,连接db有很多配置信息。
    % ?  I, b: Q! r8 D3 p5 J
    : q9 X, M. M! B/ q/ I# r6 ^' P( h系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    4 E, ?' C0 C0 [7 b8 `. C
    ( k  y: A0 w' K; z还有其他的一些配置信息。
    + S9 V9 e" S- P, G. p2 }# _1 u! K
    ! C% d: y. f& n! {6 U$ n4 P( D我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    $ G. K- R0 M* z2 |
    ' H5 L( T* @2 T2 J& ?# W' Z; M那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    ; c5 U" J0 [" Z+ S1 N8 b5 E! }7 g6 I  ^! s: \7 |
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    # a" r( Y4 P& m3 i: V. d
    $ B: i9 F5 h/ S7 U: c通过@Value("${配置文件中的key}")来引用指定的key对应的value。/ _* ?& Z! {/ S, `7 ~

    / K7 F! f2 k7 l  m@Value使用步骤
    7 H) M$ M0 c. b8 b  I- p$ x7 R
    : y1 d8 r' t; o6 {7 @步骤一:使用@PropertySource注解引入配置文件
    . B5 o/ i6 R7 e
    ! Q! b$ Z+ E6 R/ H将@PropertySource放在类上面,如下; K3 @! @0 Q1 p0 [: |) h% Y$ Z

    $ b, k5 M7 k! v& N( }@PropertySource({"配置文件路径1","配置文件路径2"...})
    5 f+ V- }* E; S1 U7 D+ ^+ q@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。1 H8 e5 a0 d/ L- z( Q
    . b/ I( a1 m1 K
    如:9 c6 a; u+ h. D& K# e& V, l- r  e
    0 N' [0 O- A. h
    @Component
    3 x" B# R" A4 M+ i@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    # |. Q% J2 F  x+ x4 spublic class DbConfig {
    ; g) a  B8 J" L$ u" o5 D# i# V}
    & d& X, l* B- |) `- i# b, a; k步骤二:使用@Value注解引用配置文件的值( C3 p% u) n' b( H3 |

    5 q- L. p! Y0 o0 }- a3 l$ x+ f通过@Value引用上面配置文件中的值:
    8 u$ ^1 p1 U( j
    4 H$ c8 G' W, Y4 k语法- X- K" U7 @" p" D) U' F

    ) M, _+ ?7 Q1 N$ W8 ]- l@Value("${配置文件中的key:默认值}")
    % r4 W$ F0 L: _  p4 d' \- \/ l! ]@Value("${配置文件中的key}")% ]" l% I3 a1 o  D5 _9 T- B
    如:
      Y" [' Z; K- I1 V" _6 y( b( b. `# N) H
    @Value("${password:123}")
    . S7 k6 ~5 O9 k+ [3 A3 G7 k上面如果password不存在,将123作为值
    - x9 [6 N# C9 U7 E
    ( n$ p6 E# ^3 \2 f2 B@Value("${password}")! }8 h  Y5 v" E  o" z
    上面如果password不存在,值为${password}9 J$ n5 G8 ?  G
    % D0 U: M  G% A- O0 }, o
    假如配置文件如下
    + F! ?9 f) @' b. C1 u1 i! q9 }! R! d6 B  [6 g) c2 c
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    3 [2 W5 }! U& q5 Cjdbc.username=javacode( s; ^+ m. h7 ^5 R) i' A, O0 l7 c
    jdbc.password=javacode6 a  `) B6 M0 s
    使用方式如下:% I' n( J9 X# V  N8 d

    5 j1 G3 I7 K  w1 V7 Q@Value("${jdbc.url}")
    / [% I6 w( A9 _+ U& N! wprivate String url;. f/ F9 h" D, V- l& H6 H' f

    * U2 j3 }2 o6 S3 m8 `7 k2 G@Value("${jdbc.username}")
    * H+ {, J* ]' z8 L( M* dprivate String username;" l; L& l/ t( F8 c2 J2 _  x  g

    - K7 D8 G3 S( {: `1 c@Value("${jdbc.password}")
    1 M, h( m) Y7 g# z4 S$ z" jprivate String password;9 v$ r8 D1 a; V6 W9 G$ \
    下面来看案例
    ) d' E" x" H: D! l& b1 Y2 x( u" q8 j5 {
    案例/ {, u+ X$ ~! C$ d
    / ]9 V0 a" _; j
    来个配置文件db.properties4 A5 y8 x6 d0 _% }1 `

      K9 g9 L, d) Hjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ! F7 b6 Y2 i, f0 z- G9 }; z* G7 ~jdbc.username=javacode
    0 d$ N  t7 N# N+ \! Ljdbc.password=javacode4 m% a" d* F1 m! N
    来个配置类,使用@PropertySource引入上面的配置文件0 b( _1 e, f0 L3 q; C: g# ]

    # [1 R" [7 g& X) J9 D- A' Ppackage com.javacode2018.lesson002.demo18.test1;
    3 E. c. t" ^# j0 ?/ h7 y! ?! @9 f6 r& N! S( f4 z+ B2 \' h
    import org.springframework.beans.factory.annotation.Configurable;- \5 W, |$ x! j9 n0 u6 p9 K
    import org.springframework.context.annotation.ComponentScan;
    ; s1 X' ]1 l3 ~  k* ~$ ]; iimport org.springframework.context.annotation.PropertySource;8 }9 f2 k8 P# M$ |. l' M5 _

    4 `% w7 n5 x; F" |/ m4 {@Configurable
    & Y7 @9 f2 n* B- \@ComponentScan
    2 b% W4 B9 Q0 A2 v3 h# T. P@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})$ n$ Q6 `% b4 g$ d8 Y  Y$ F/ X
    public class MainConfig1 {0 g/ u! z# y9 z7 ]& |$ O8 w5 {
    }7 `& P4 V/ e2 f! [, x' I6 `
    来个类,使用@Value来使用配置文件中的信息- `+ Z' z5 o4 _7 g
    6 h$ f( x9 Y, z# [1 p
    package com.javacode2018.lesson002.demo18.test1;
    % \/ ~/ {* Q+ R5 z; l
    . p% u6 M1 Z; N. gimport org.springframework.beans.factory.annotation.Value;
    1 z8 c: {6 i. X; j) @import org.springframework.stereotype.Component;8 V2 O9 j% B- t( l5 S

    8 i, L% @& `" ~5 C2 ^+ |@Component2 i  i4 S9 x" |: s
    public class DbConfig {' A7 i3 b2 g+ q4 b) z3 \
    1 e( p" T0 f, @3 x( S. l
        @Value("${jdbc.url}")6 @" d. V2 t7 X) k! _, P8 t2 x
        private String url;
    & t; e( W; C  G2 `3 {
    4 I% a! O5 Y% _  F2 ~    @Value("${jdbc.username}")
      D( \8 M+ z8 `2 M& g" z: A2 @    private String username;
    9 N8 c+ f2 K3 q# u" a$ W, e8 Y; B1 g' z
        @Value("${jdbc.password}")7 D0 F6 ~  _. X8 H( i0 A/ a
        private String password;8 V: _5 x; W/ Y! X& u& a; }

    4 E' P% G- u$ S4 X    public String getUrl() {
    ( _4 k1 c6 g6 Y: `        return url;
    ) x1 k2 [/ `0 |' m& [    }
    # y, a* \  F' A; m& f/ S3 c$ o! S" |# X$ g- L. |% \6 _
        public void setUrl(String url) {. d' o0 w3 a' o7 r) U9 R
            this.url = url;
    1 E" @( o9 }1 C    }
    , }3 b" [! @! m! D/ V* m$ v2 [/ a/ {# @
        public String getUsername() {8 K7 s$ ^, L: n4 U  l
            return username;
    3 d+ c" Y& |+ }9 z    }+ q: p0 O' j6 A9 i- v

    % Y3 V: b6 N4 B( r8 J    public void setUsername(String username) {
      f, ^( A% n- D- i3 U        this.username = username;
    ! G, W5 L  i% b- ]4 d5 R5 F    }0 Q: t1 H2 a% X" ]! O' T8 M) V

    % r* n4 |! C2 p. \4 m    public String getPassword() {
    - S) I9 t6 Q4 v% |. F) K2 X8 o+ V8 T        return password;' f: A2 i- Q$ y- O& j- c
        }  @6 M( W. `# K% S8 z* o. S

    ; L; w! D" s( `    public void setPassword(String password) {
    . z' m+ L( G* c1 k3 l        this.password = password;
    , ^/ e% u4 r+ E# k/ _9 C" S    }
    + K9 k0 w+ o* x$ i
    ) V( S  l- w) r2 B) C  ~0 I    @Override' j1 v" V7 A0 v  y# j. o6 L
        public String toString() {0 K0 v% R) k) k, C, k/ i" E3 W
            return "DbConfig{" +
    3 z- P3 f' ?2 s$ n3 e7 C                "url='" + url + '\'' +5 z0 M5 y& O% f  ~5 Z, J, q8 w
                    ", username='" + username + '\'' +- _* d- C4 d8 T; M. i
                    ", password='" + password + '\'' +
    # t0 g) K" ^! G& ~3 ~$ x9 o( R% s- G                '}';' S' e) y5 K- w( {7 i+ Y- Z# w
        }
    6 _& f+ s- b. g' n2 l% R* T: C. A}
    2 J1 F- v3 |9 G: m' g. R$ J* Z上面重点在于注解@Value注解,注意@Value注解中的* m( c  L7 |9 `5 R8 {

    : d0 H- k) e% j; y$ O; I# z7 ^1 x来个测试用例, F& w% h5 G# r* c
    6 k- O$ F4 K+ S: g# g0 l
    package com.javacode2018.lesson002.demo18;  ^4 \" P0 Q- _4 d: K. {
    6 {; ?+ L. V2 m' o( ~2 p7 X
    import com.javacode2018.lesson002.demo18.test1.DbConfig;$ X. h2 `+ q! N* s: ]4 L
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    " X2 Y! r% ~* O7 b' g5 ^import org.junit.Test;$ @" |! A# D; A0 v+ \. J
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;5 O6 C4 N6 h" x
    : L4 v4 f5 Q8 I+ S/ K/ W
    public class ValueTest {
    % i& \8 i- e6 n' L5 l  V" n; [' W, Y+ I, l1 W
        @Test
    # o( L2 x' r& u; N/ Y    public void test1() {
    ( S$ f4 |) p& ?        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ p- ^4 x' i& v, u. W9 g        context.register(MainConfig1.class);' ?! ]+ E0 p5 {+ p1 P" r( `
            context.refresh();
    3 S1 \  J  b& z8 ^" O& W. t2 e
    $ k* e# ]2 r/ z0 D; i        DbConfig dbConfig = context.getBean(DbConfig.class);1 p; D2 V8 \3 i% W7 o- g0 w
            System.out.println(dbConfig);7 X) c# r$ T" d9 h2 J7 t
        }
    9 X3 \/ y+ Z1 |& r* C}* t# V% C! X* {/ F3 Z
    运行输出# A/ |) d, r; }) {
    + j2 P2 M$ [# X( h; s7 ?
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}" M* q6 X, P8 P, x* c1 }5 B
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。$ T5 V6 M8 S: J, C% R. f

    2 d  y# A: y1 e! F  W$ j0 ]$ P@Value数据来源  B5 s' i, X4 K$ Y# l
    ) I) [1 Q- \+ Q
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。7 V. v. s  C8 I. p1 \4 I
    2 B2 T% E; U4 V5 r0 v0 r+ [
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    " a/ x& |: J! \+ p! |5 W0 Y0 [3 ]% Z  [" v. f! }% M) k
    spring中有个类& |; r1 `* Q0 Y0 j2 ~' X+ v1 B
    1 k- h! [, g, ?# {* v, G% V8 X3 ~
    org.springframework.core.env.PropertySource, x, t' T6 F' y# p. j& [' m8 Q
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息1 ^' C- h7 ?5 @5 p4 D! A  }/ t
    / q- O9 a; j- o' A  m# K$ \
    内部有个方法:
    ; ]$ I% |4 B7 f: w1 j3 U! a$ @
    5 @. R4 B% o' r3 Kpublic abstract Object getProperty(String name);2 F) ^. L2 G1 ?
    通过name获取对应的配置信息。% F) f4 P' R4 {8 T

    3 J7 Q1 C9 g8 E% i. d& f5 I系统有个比较重要的接口, o5 Y5 _- e6 j8 s

    ) R% W3 s, A, L8 Eorg.springframework.core.env.Environment' \$ w/ e0 r6 l# ~' g% c' ]0 L$ [
    用来表示环境配置信息,这个接口有几个方法比较重要
    . ]0 h3 G) i$ e# f! n9 P  L4 X+ `- s" W  ?$ e6 s4 p) G' n
    String resolvePlaceholders(String text);  z: n9 h1 U; Q9 D$ Y' O
    MutablePropertySources getPropertySources();5 E* j3 ?7 k' f7 I+ a. Z
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    3 L3 h1 W7 F3 O1 X6 i0 A  Y2 [9 P% w: L4 w7 m/ N
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    / l# \+ r8 g; z2 k- D2 o; p8 Q
    " e: Z7 E: b* _- w) K& U2 Q, rpublic class MutablePropertySources implements PropertySources {
    * G8 w+ U$ M: S; E: v6 P# ~7 ~- s- J- q! f, M. E& Y7 t- F+ y! u5 Q
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();$ k) W5 T0 M0 Y* k( f+ J! L

    : V1 b. ^4 \& Z' \! ^8 B}$ K2 n; H1 ]0 [5 j1 v
    内部包含一个propertySourceList列表。
      Y7 Z9 i$ |6 E  y6 x6 t4 S, p5 p1 z, c# m% P1 x8 V
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。+ S7 v  b8 L1 r

    - S% G  }' l+ `4 q. U大家可以捋一下,最终解析@Value的过程:
    4 X0 t' {* N* e* J6 d' O8 v' `6 H% d# N0 H
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    / ?0 w# I# L7 G! x2 Z9 \2. Environment内部会访问MutablePropertySources来解析" y7 a; J$ D2 H% M
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值' K8 e3 G7 w! a% T( U3 x; `
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。, ^+ x# _2 ]$ T+ V& Z& w
    0 Y3 x8 f  q5 N: w' a3 m& U
    下面我们就按照这个思路来一个。
    0 ~* a2 P7 E9 b* t) k$ Z
    3 U* r4 l" k- X  b来个邮件配置信息类,内部使用@Value注入邮件配置信息% G) u1 _" A& L
    4 j& \" |- g: M( N+ k2 z/ A( s3 V2 D
    package com.javacode2018.lesson002.demo18.test2;, }  ?7 ]! [, S( O" j7 V) v

    ; e3 r& f+ f: Kimport org.springframework.beans.factory.annotation.Value;
    5 Y  p% x5 |2 B& K0 W7 nimport org.springframework.stereotype.Component;# |# \# m/ Z( O) y. l+ O9 Y
    2 v- {% v! q; A9 P4 ^1 S4 Q
    /**7 {8 r6 N% [/ b/ i, l! U* L
    * 邮件配置信息' k, Q% F- w+ ~/ l# Y1 Q3 |( [
    */
    , j$ W- L5 C/ H* s@Component6 ^7 ~* i. r) w1 g) `
    public class MailConfig {
    ( v! {/ Y: r9 Z: z" w  l! D
      `  ]( C4 C5 N" l    @Value("${mail.host}")
    * |9 d1 j  C* J$ ]9 V    private String host;
    : L2 c# M) f# d0 [9 B& R& m/ n0 q2 M
    + S! i2 p  |7 V( ?. V    @Value("${mail.username}")
    0 {" S5 v& v. X  R5 Y# t3 ~: o7 F    private String username;- R" i) k- w! W* J/ ]9 T
    ' B$ P% g3 X3 s
        @Value("${mail.password}")6 G- d: B" R& s- r8 W9 ^) b! }9 s
        private String password;% N' G2 R. W, H/ l. M3 p% k
    # \% U5 y# o2 @% }' y
        public String getHost() {
    4 h& Y8 A0 f2 A  `$ d3 k        return host;; |) E8 a5 g( @: }7 I
        }2 J' Y: T4 L" }& D; f" x' L
    , s% |' j- |9 _
        public void setHost(String host) {5 G/ u7 m* x0 {0 E
            this.host = host;2 L9 O  R2 Y4 O1 ?) Q) K5 w9 u
        }
    3 d* ~7 q+ m) w: C: `) s
    $ B8 r- ?4 ?$ Y" ]    public String getUsername() {, }( s. Z/ M; P/ v
            return username;( _5 B0 u- ~% n; j1 r" D
        }9 k& W/ h! R; V! J
    6 e1 H8 k0 J$ H$ Y
        public void setUsername(String username) {/ \! L9 Y( j0 r; H2 n* O
            this.username = username;" r/ x$ a3 [+ m/ \4 n9 K& E
        }6 Y" S1 F' K" u$ J
    - u, r- n; V1 L
        public String getPassword() {
    & p6 `3 v3 c0 A1 Q/ n9 U: ^. Z        return password;% n3 V' `& x: W6 T8 x6 f
        }
    8 k1 T( y" {, {! P. d. L/ ^( L. ~. V+ z; F9 r- r6 [
        public void setPassword(String password) {( B( P: [7 h( _& E# N6 g
            this.password = password;- z" H& f7 A5 }( r
        }
    2 E1 D, G6 e: U. p" ]% s1 s" E2 ]! @' G8 J' u1 M1 I3 A5 L  J
        @Override
    1 m4 \; |. z4 k9 W    public String toString() {
    # }) g/ A+ r# \) A        return "MailConfig{" +. B, u, z& s. h1 ?6 j' m1 p' b$ ^
                    "host='" + host + '\'' +
    6 i7 v* V# T4 l' P                ", username='" + username + '\'' +
    9 Z. C4 x# A; C9 z. J0 w: ^9 U                ", password='" + password + '\'' +2 p% W' ~% ]5 T! Y! ?
                    '}';4 o% D0 `  P9 e" y
        }
    , O* r$ r9 I% K7 F" ^}' Y  i9 g$ M# L+ ^3 K. v
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中2 {( N; Y# a# Z0 [( o
    0 H2 l' H' x0 |2 M9 L$ L" N" i
    package com.javacode2018.lesson002.demo18.test2;2 s; m6 ~  L7 J, u# \0 V
    ; l3 d' |8 b; I% d+ }' ]5 R
    import java.util.HashMap;9 R8 H5 j& C5 Y2 w8 E9 `
    import java.util.Map;
    $ b# w# ^* S% V- T& I
    ( `- U5 ?% h  x; U8 E) {4 n$ jpublic class DbUtil {$ P9 {+ b% T2 b0 K  o; d9 v( W# G7 _7 |
        /**8 ?! v' k& V& R) g+ H
         * 模拟从db中获取邮件配置信息
    $ L2 A+ }( _' y' Y8 ^+ [/ @; m     *, f3 H$ {# T% N8 `5 ?' V( q6 v- M
         * @return# E! X8 r) _$ F, }
         */
    8 ?5 }1 s# ]3 Y5 e    public static Map<String, Object> getMailInfoFromDb() {: T! [8 |% |7 B+ [" _8 u  m
            Map<String, Object> result = new HashMap<>();- x  I" u1 O  w% T
            result.put("mail.host", "smtp.qq.com");
    * D5 E; T. m6 U        result.put("mail.username", "路人");+ G& h8 p9 v# W! F, B
            result.put("mail.password", "123");
      ?( H; |8 P2 Z0 Z( `        return result;
    . V& X2 Z7 c3 E3 E6 L    }
    7 H* N& m. W, x9 e: J+ ~1 A}2 A  n- F! H( M9 |1 ^- s" I4 Z* y0 z
    来个spring配置类3 @2 R2 S/ z% s/ T, U' T) l

    1 I2 p6 v) ]- s9 Q5 r& ?& D+ Wpackage com.javacode2018.lesson002.demo18.test2;3 F9 |. S" A2 Y4 i+ N
    - B( {2 P' v) _9 I
    import org.springframework.context.annotation.ComponentScan;
      x; |, E- M9 ~import org.springframework.context.annotation.Configuration;
    ) a7 h/ s: @0 l. \
    - R' `! |3 k8 J7 g9 l5 E@Configuration$ V! c/ W5 U8 n
    @ComponentScan
    % f% L" P. h- Z4 Jpublic class MainConfig2 {
    ( y5 ]) c. A! T1 S1 V}
    6 C2 C$ V8 {2 ?0 Y/ m$ U下面是重点代码5 @3 S5 F0 K, c& v& [7 A

    ( m1 h# J7 n2 r1 ?) h@Test; G" \/ a4 \% t! H2 P
    public void test2() {
    8 s  {8 a$ z- i5 m( r    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 ]  N1 E( `# @, U1 O, b+ g1 Z$ x3 U, [! j/ ~. r: S
        /*下面这段是关键 start*/
    # z0 ^  r$ J6 K! W    //模拟从db中获取配置信息
    / n" ~/ _5 k7 l5 g( [8 V6 f0 J" E( I" P    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();- @7 X% Q- H0 B' s2 i' H  o+ h
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)# Z/ G6 I! n: k9 b  N/ u- i
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);2 B# ^) I; s. p# L6 M9 R
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ' ^) ~: t$ b' }  A0 _8 R    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ; L2 q: ^  F( n# C3 p    /*上面这段是关键 end*/
    0 W. G: Q: H) f8 E6 F
    9 Q2 e5 H3 c+ j    context.register(MainConfig2.class);# B2 q, O1 P0 w7 m% Y( ^0 K1 j
        context.refresh();$ e" z0 U5 `# B% k" i
        MailConfig mailConfig = context.getBean(MailConfig.class);
    8 V+ k2 O  u) `/ z2 i- {7 b9 k    System.out.println(mailConfig);5 d- M0 @6 ]& b1 m- u! _' N5 s
    }: D& {* `# T4 N! C/ k% P
    注释比较详细,就不详细解释了。
    1 G5 B& C- X( W/ h3 w: o' ~, U7 X% \1 ], ]" n$ r. k9 j3 L: H
    直接运行,看效果
    3 r. W0 m" R* x$ E% `0 w! V
    4 {0 v6 H* D( _# R# H3 [6 gMailConfig{host='smtp.qq.com', username='路人', password='123'}
    2 U8 D1 s( F4 z- p; v有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    : ^% _2 T) u# e% C0 x
    : [. I% B6 d# d1 H4 L: O' R上面重点是下面这段代码,大家需要理解/ ?* \0 \5 H8 L  Z7 r# U

    3 @  A, C9 p6 O" G& h9 i/*下面这段是关键 start*/* M" ~7 j9 @, D' X) ^
    //模拟从db中获取配置信息
    , E" G6 Q% Y" c0 k; V/ ?3 |Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();, K4 k3 x' H( |, `- a
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)8 L4 ~/ {7 X4 z' E* @9 T
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # }: a* n) R+ H3 U2 z& \: c//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高# g9 x8 v! V# \# L2 D* u( L; |
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    8 s6 c! t0 d8 Z  j: f5 v1 h) O7 U/*上面这段是关键 end*/2 ]' S6 ~8 n% H! G  }. m5 M
    咱们继续看下一个问题" ^9 _. i) a  w! _
    % [$ N$ e7 `, o* X
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。! G6 G! X; ^: [1 ^

    3 W9 V+ C+ t" @7 p% J. V$ w0 `@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。5 C% P, R( U! @6 i# O
    , T1 n0 j$ \! U! p, m5 p; \
    实现@Value动态刷新/ p- ], U9 v  ?; d% Q7 E- ]

    * Y6 o6 T0 L* L! s+ x4 Z/ h$ m先了解一个知识点5 F0 x9 s1 s! {8 U3 O+ m' C

    # \) n& |5 U$ m: u3 r2 z这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    - h$ q7 o$ z/ H5 L: E
    ( j1 O9 |/ W3 }$ I* M* G: w; _这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    1 a& ]3 _, A3 ~1 x7 m- _6 x) I% s- q( W3 L6 }" X4 k& P
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:! k  I, S5 S8 m$ A2 R5 z

    9 r9 N1 H3 K5 F; I4 `& h( yScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    1 v6 U" r6 n' p! w* y( r这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中4 }; T8 I2 d$ q# o; f

    $ f. z& s  r8 K% W- vpublic enum ScopedProxyMode {
    + u4 Q) }' m, H4 Y2 Q    DEFAULT,
    . A" L1 X" f) s& }4 z0 ~    NO,5 ]2 K3 O/ _- x% X4 `0 `+ D
        INTERFACES,, s1 H: Q& D, r
        TARGET_CLASS;% L8 n6 }# ]# [# u. Q5 }
    }
    - P& L- h8 z- ?前面3个,不讲了,直接讲最后一个值是干什么的。
    # c& r$ p7 I2 o) Y; u9 N2 x, A: m- A0 F. p! q
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    / x6 h  \" l; p
    6 J" q2 }6 H. e. N% G+ U! w理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    / h; T8 B7 W* i4 w* H' o7 C3 p- b" [/ j) v$ Z) o2 D% Z
    自定义一个bean作用域的注解
    , {6 D, Y5 f) @. |5 ]) a  Z
    # y2 ^& O& K- s3 Vpackage com.javacode2018.lesson002.demo18.test3;# [. m) t  P: A4 r* V6 T, H- J

    1 C: }6 z7 H3 A& i; B1 Dimport org.springframework.context.annotation.Scope;
    ! l) ~; f  f+ X5 u( f% zimport org.springframework.context.annotation.ScopedProxyMode;, G8 A/ I+ m7 A1 `" d  V' P4 l0 o
    + d! `2 u! E4 v3 P
    import java.lang.annotation.*;
    3 l8 @( ~; Z; h$ Z' e# d6 c) i" G" m  e0 y0 g9 d
    @Target({ElementType.TYPE, ElementType.METHOD})
    0 t2 t3 n' f, ]# E@Retention(RetentionPolicy.RUNTIME)
    6 n6 y& P+ \, ^8 @- b@Documented
    : y- r  w6 b9 Q@Scope(BeanMyScope.SCOPE_MY) //@1
    " B' o( F4 e3 s) D- s; g9 _public @interface MyScope {$ k8 m" e8 Z; ~. I" _
        /**
    3 [# U2 o+ x7 \7 j     * @see Scope#proxyMode()
    0 N% G6 P  f: c( H% A8 y     */
    6 m6 K3 h' b( ]  {    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2! ~7 }. Y' s& d4 z  p! f
    }" A9 X2 g7 L- l9 E2 L/ E* }
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    0 e- }! ?( {8 C- U8 K( \9 ]) ^, P% b3 S' E% n
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    % ]! B  F+ w- S/ ^9 U) B5 E0 a3 \3 T% W7 q; x# o
    @MyScope注解对应的Scope实现如下
    5 _7 Z4 W) H* V: i: X0 ]  V( ?) \& X1 r1 X
    package com.javacode2018.lesson002.demo18.test3;
    # U9 z* @" u. v- e; M7 T
    + L5 T9 g: i9 r! L( S' P3 N! Kimport org.springframework.beans.factory.ObjectFactory;
    # M- T4 ?' G7 h( F1 Limport org.springframework.beans.factory.config.Scope;2 Y8 J% X! i* S+ J
    import org.springframework.lang.Nullable;
    % l7 _: N) K& g$ i1 q! R2 ?! i8 s: |" _/ ^! S0 i
    /**3 o3 s9 O) J' r
    * @see MyScope 作用域的实现
    5 b- m$ L) h  @0 S */; k+ Y" r  {( E; |' t  o
    public class BeanMyScope implements Scope {7 G: `8 j* Z7 L) U
    ; q8 ], L2 C! r' E) F7 C5 P& ^# e+ v
        public static final String SCOPE_MY = "my"; //@1/ m  B7 F" x; s* B

    & O; P7 a' v9 r    @Override6 ]9 i/ d1 v5 @/ c  c4 S
        public Object get(String name, ObjectFactory<?> objectFactory) {
    % G6 }5 ^8 N; F' V  J; Q        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2# f: Y+ a; \8 H6 J- ^" j" J
            return objectFactory.getObject(); //@3
    , V; T; c3 U) t8 H    }
    - H' I5 s6 ]5 q( N$ p( o
    1 ~6 S! R: W2 o    @Nullable
    ( O* e2 n: R5 x, D- R# t2 P    @Override
    ' r; K1 T; y2 H: Y& T    public Object remove(String name) {. t# m% w) P, w- W" ]4 G
            return null;
    ! S& O8 K" k% D    }; Z# P, l* f7 L' O( n
    * l9 S- g3 F+ f6 V' L* p8 j
        @Override! E4 G7 e  }* u
        public void registerDestructionCallback(String name, Runnable callback) {2 q# P- q/ W; Z; S. f

    6 n$ _# U/ j( z" P6 O4 n    }2 c5 {4 v/ W. m# m
    1 D! W" r8 i* k
        @Nullable8 ?/ f( o; ~! u+ M3 p3 h2 P% J  j% ?
        @Override5 B2 _  |2 V/ J
        public Object resolveContextualObject(String key) {
    " U" W; u& F2 T+ d        return null;
    " a. B2 @& s2 Y) d, O    }
    " D8 F& i: W" y. X) S$ P9 w- J' Q( s! ?; F; g' n8 l1 }
        @Nullable
    8 I7 n. G% H- o6 Q3 p    @Override4 \6 a# G' E, w4 w- t
        public String getConversationId() {( j7 n% s; y! O2 D% }3 Y$ ~" D
            return null;
    , R- _0 D6 b! {" }    }+ q# O* |! X4 e
    }, Y; W6 u1 D8 n7 k0 v* D
    @1:定义了一个常量,作为作用域的值7 l" I  j+ h) J% y/ q6 L3 R, m, ]

    : Q* t- l2 N: d9 u3 U! Q# w+ B@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    , b$ S; p* {3 _1 ^) L$ a3 Q
    0 M1 N$ M- Z$ y# a/ I  j$ ^; ^@3:通过objectFactory.getObject()获取bean实例返回。
    , f8 d1 f/ ~& J# S. n
    0 n4 F: u0 ]) |* C$ R8 u9 k+ ^下面来创建个类,作用域为上面自定义的作用域
    ' {9 \: N5 b. s
    4 m7 @$ U) y! r, j$ ^  Epackage com.javacode2018.lesson002.demo18.test3;4 S. @6 C  g! }) d9 n# p

    ' K6 V) A; ~( G  D5 }% g/ }import org.springframework.stereotype.Component;2 U+ U- c2 O& |+ b7 b

    $ T$ N, L6 W! _6 k' z: t. k! Timport java.util.UUID;% G3 c$ a' U! [

    : G. t7 m) c1 }@Component
    " ~$ E+ S7 w( [2 L6 `; n6 K8 g0 A@MyScope //@1
    7 Q- v* u0 i! p0 `. c0 G. Y6 dpublic class User {
    ! [0 `" d, R9 D# I, {
    $ X1 h+ V) ^/ u# Q0 c    private String username;
    ( C0 h. O6 X) C% a; p
    - I6 v8 Y% |6 r% a% r    public User() {
    4 W! E# H* i8 c9 z, v        System.out.println("---------创建User对象" + this); //@2
    8 C! z, {1 F$ c& Q- u$ P        this.username = UUID.randomUUID().toString(); //@3
    2 R3 p; e9 H/ n( s$ G7 u7 I    }( f' N/ V6 ~8 O7 e% M( ?

    + K+ t& {* Y3 Q9 z    public String getUsername() {' j/ L( J' M; H9 V1 t
            return username;
    1 s7 E1 k0 v6 C2 `    }
    / e5 w$ \0 ]3 M: f8 {' ?+ ?5 V8 K5 Y' I# Z# H
        public void setUsername(String username) {
    ) |* D5 l; H% e) r2 T2 e, ^        this.username = username;
    + Q/ ?  N7 l. l) \0 r9 m- |    }3 b) M# i" u# X1 j3 }2 V% c
    9 s: G& k) I" r  W, c. n$ F
    }$ A! M8 D# s5 s3 i
    @1:使用了自定义的作用域@MyScope
    3 z! G, K$ G! S6 J/ x7 A* v$ }1 G8 J4 D
    @2:构造函数中输出一行日志; C! K% J0 [0 |2 Z

    ( N) W/ I( d9 J% p@3:给username赋值,通过uuid随机生成了一个  S# g3 P! V. G+ ]$ F! w1 G
    . t/ h5 g: B+ o4 N4 u1 D1 w
    来个spring配置类,加载上面@Compontent标注的组件9 @, Y7 Z7 k2 i# }6 D

    0 B# p4 {8 I' [6 g, Ypackage com.javacode2018.lesson002.demo18.test3;7 f. ~$ w  z' h  ?1 O
    , O+ `5 z9 W. N( }  a
    import org.springframework.context.annotation.ComponentScan;
    / L7 Y+ F: \: p" a% vimport org.springframework.context.annotation.Configuration;
    ) |( {7 Y# z. W, Z0 G5 ?: A, \
    2 r" \& h9 A: T, A# a@ComponentScan
    ' y  u1 x+ h* X$ m" C@Configuration8 k, T6 D  s; k2 c; I6 E
    public class MainConfig3 {
    ' l  c8 |$ }5 f+ W( q- J; N% p, ^}( b  Z% q* f# ^
    下面重点来了,测试用例5 O6 ^$ g4 C9 n% Q

    . Y" O- K: f7 ~' V@Test$ F; Y1 P( o1 ^) m& T0 R( N  q
    public void test3() throws InterruptedException {
    ; F' v9 O( H4 r% t! q    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ( |6 b9 q* i5 h4 u    //将自定义作用域注册到spring容器中8 k4 @' q- K+ {: i+ Q" B1 s
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1. B& p% M: P( Z9 |+ d' z
        context.register(MainConfig3.class);5 t% @) V0 n2 }& t# k$ _
        context.refresh();
    ) p7 o3 L# |) U' [
    9 C- Z/ v8 t  |. {    System.out.println("从容器中获取User对象");+ R2 c% x; R3 n& Q; N9 }  ?' _
        User user = context.getBean(User.class); //@27 r! I* w+ Y- n& Z* F# H; G" T
        System.out.println("user对象的class为:" + user.getClass()); //@3
    3 i# K% A9 A2 G; N2 K5 V$ j
      Q' B- q8 v0 G& F8 v  ?    System.out.println("多次调用user的getUsername感受一下效果\n");. {+ ^2 {# i/ G+ ~3 U) C( k. M) L
        for (int i = 1; i <= 3; i++) {
    # b; @! H( z' x; D+ U1 ^        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    ; }  d6 P7 d2 k; j6 Z: }        System.out.println(user.getUsername());
    " ?3 V5 B9 T  P4 M' f% {; X9 q        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    2 d9 t: R+ t/ l8 R( A- y. M    }% Y; Z" B% j% s+ K
    }- o, A3 A( J! |
    @1:将自定义作用域注册到spring容器中1 a1 ^5 O. _; G8 ?3 f# q- l
    - U5 y; d$ B$ V" m- ?& }8 W
    @2:从容器中获取User对应的bean0 T- g3 ?6 W! v" N" o3 [
    : g. m! Y5 w( ~" F6 |
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的' q9 ^( _4 G% `# e

    5 E/ |! y# i3 r( Q4 ^% k代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。8 K4 `4 ?" @  p. [8 {8 J

    : Y$ k1 `# n- T: V  w1 a( r9 N见证奇迹的时候到了,运行输出
    9 f- k4 Z: z2 @: M: ?3 |) Q
    0 L1 k2 b) \5 `  n1 m3 K" L8 L从容器中获取User对象
      z) `+ \7 O' G5 Puser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127& i1 A9 N# A6 f# R; }0 r8 u% q
    多次调用user的getUsername感受一下效果- i' h6 z5 i9 S1 [; U& G) n
    ! {; k% c. E/ y7 K  }
    ********2 `' j0 G: V  t, s, x
    第1次开始调用getUsername2 s5 c/ I' E& X; R
    BeanMyScope >>>>>>>>> get:scopedTarget.user* P3 l$ f) A  K# I
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    : W6 f, b" B" C( Z% Y5 C7b41aa80-7569-4072-9d40-ec9bfb92f438
    3 `5 a+ d* \! e( I第1次调用getUsername结束
    9 ?  v' m& |& r$ n9 O5 ]' W# D* u********
    $ p( @/ w; O9 \4 T
    ! |& f% i  C  x. E- k********
    9 y1 N7 F, j- O; L0 B第2次开始调用getUsername6 t* u0 ~3 J4 F: n1 M* r/ |
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    * L' v; Z1 f. f* v' _  R, r# p---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b4 y( ~2 R# `8 s
    01d67154-95f6-44bb-93ab-05a34abdf51f
    # L" H6 E9 z; |8 ~第2次调用getUsername结束$ B& f- w8 o* c# q7 Y
    ********
      T( i& [5 ?9 `5 ~* x  N, _1 S" p& l: s. h$ X
    ********
    ' [& M( r( b1 m! T, u# s第3次开始调用getUsername, L4 e, k. M* L) U& ^3 Y
    BeanMyScope >>>>>>>>> get:scopedTarget.user$ q- [$ `! {" w$ t* q2 x/ ]* ?- @" S
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    % w1 U* F) X4 @8 }8 r76d0e86f-8331-4303-aac7-4acce0b258b8
    5 B/ n0 M( Q9 w* _  a第3次调用getUsername结束
    2 {9 ~! M! w- t& V4 x! E, {********2 }& f4 S; t8 t5 Q' w
    从输出的前2行可以看出:
    , ]2 T. ^( R' X4 m, k7 b9 Z2 t% M' }- }0 L1 n, Z3 ?6 Y: N
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    ( J0 H" [+ N( X, i3 Y. n# Y: S: _, P2 U0 _  a
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。, g# S3 `9 Y8 M( n% k

    + _! T& O& I( Q! _2 @后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。/ O3 {% S- G9 O) G$ Z1 ^7 a6 O

    ( c8 \  ]% l+ _2 \3 Q  Z9 l% T通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。: A& G' _& v+ n3 b9 ~

    $ g* ^! D7 Z& X4 X2 L/ Z. h动态刷新@Value具体实现% _1 c# Y1 o; k1 }
    # M, Q/ }+ [0 A' t
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    3 P: R# l+ @, A3 [) E  U  `" n' O4 y7 p' U2 p# i1 a% ~
    先来自定义一个Scope:RefreshScope
    5 A4 M$ v0 W; d* c) B; G) K; S
    8 U" I# U6 `1 L0 f" @package com.javacode2018.lesson002.demo18.test4;
    + J# D, d# e) B* z3 r
    9 U) p2 p% I% gimport org.springframework.context.annotation.Scope;8 y- S5 O4 Z3 r
    import org.springframework.context.annotation.ScopedProxyMode;
    . x4 U6 s( P) M7 @
    & {/ [" A) `" @2 i; }import java.lang.annotation.*;: V4 G2 @' y4 Q+ j- g+ \% {+ K
    2 @9 p$ V" J( X& W1 g  U' v* k
    @Target({ElementType.TYPE, ElementType.METHOD})" |8 _* R  u: n. `
    @Retention(RetentionPolicy.RUNTIME)
    ! i) b/ S9 t7 _& X9 E" h9 c@Scope(BeanRefreshScope.SCOPE_REFRESH)5 R) [2 D& x$ S5 j% N
    @Documented
    8 P7 `& e9 x$ Q4 r% Npublic @interface RefreshScope {
    / j5 K% P! u9 U5 ]" _    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@18 P$ w7 A. i  b+ E& _: O
    }  O0 q, }: E" q  X
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置5 B* u3 X: U; c4 o9 t
    # S5 F. i- c, g2 e( \
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    1 |* G5 a& q- U6 d2 Q/ A, _6 @8 f; L; [; M1 h$ v$ d' _7 N. [
    这个自定义Scope对应的解析类& D6 w. |, k; V

    + H6 `+ w+ E! ~" P  I! _# H' t- C# ?下面类中有几个无关的方法去掉了,可以忽略1 K* I8 @# f+ X$ Q

    3 L7 J) Q( o% E6 R' Vpackage com.javacode2018.lesson002.demo18.test4;
    & h( s: b5 h( C. \/ W
    3 Y% b2 D3 K' x3 v9 _
    - P3 k6 ~3 ?7 ^* R/ E+ Y. [import org.springframework.beans.factory.ObjectFactory;
    ! X$ d: k" e0 Fimport org.springframework.beans.factory.config.Scope;  \* Q7 U8 l# N: I7 X- F& O4 ~
    import org.springframework.lang.Nullable;
    - e. ~2 W3 {7 T0 q5 `8 E; v1 a9 V! V* |( o& z) `& y) s
    import java.util.concurrent.ConcurrentHashMap;& f/ T) i2 Z. x  W1 e" }
    ' E8 U: N6 x  \, j! [+ Q
    public class BeanRefreshScope implements Scope {/ E, I: _, M& D1 `9 E5 e! a! G# ?* k
    & F6 H7 Z& N4 f! W. p8 V- j
        public static final String SCOPE_REFRESH = "refresh";) j0 Z  b: k7 R* A. K! F' B0 \

    1 E" {& s1 e" ]8 ]6 R& J3 g    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    & x( p- i% H5 j1 v( E( A
    - r/ l  J1 A/ q, d    //来个map用来缓存bean
    . f8 @; _! x( \5 q& C0 {    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1% C7 Y9 ?; ?4 e( J" c, [, [

    ; U7 G  j7 R- a8 J" R0 t) ]/ j    private BeanRefreshScope() {
    & G( r- Z- a, I0 s$ N    }
    1 G/ a3 Z, S& z* W: E- g7 P' \" V" P
        public static BeanRefreshScope getInstance() {
    , h2 `1 f- K7 a5 p        return INSTANCE;
    0 R9 E$ L4 ^' i    }
    1 r! Z% N  b/ z, O' F9 U9 h
    2 y: k" G6 u/ `5 D* ?( C( C    /**
    5 h  `6 f- C+ Y% j     * 清理当前
    ' c5 p5 U: R6 l$ s: f     */2 \' i. f$ S" ?* q  p8 @# V. T: d2 T
        public static void clean() {" a* a8 r* V6 ]2 w& Y* R8 _
            INSTANCE.beanMap.clear();" e% u- Y( v0 K: f% @
        }' _# V( P# ]4 `. G9 U# H2 a- x

    # L8 f- h, t5 p& c  ?    @Override5 K2 n! A: p9 P
        public Object get(String name, ObjectFactory<?> objectFactory) {+ S6 y$ Q* L' v" E! V7 u
            Object bean = beanMap.get(name);
    3 n' k( j$ l2 B        if (bean == null) {
    2 ^' r4 l& A4 I            bean = objectFactory.getObject();
    / |) U. f7 J. p% _6 T            beanMap.put(name, bean);
    : ^  X, |) G% u" n) i9 z& S. M        }
    ! p8 \" g; ?. {( j9 @0 z6 {) f        return bean;; Z7 s' W+ V% `, J' P- I2 r- `
        }
    0 C8 d8 @; O& l+ P6 E/ R1 r! ~( i4 D) _
    }, E6 p  t1 P! n. M8 I/ q
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    $ L: L9 q$ ^4 T' R' O* ~* t8 c2 }3 L) Q, M5 ~+ c
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    * ]9 H+ Y. l2 y& J7 z" C# n6 r# @3 Q: P/ ~$ S+ E7 Y
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope' i$ t7 B; q" `1 I0 g  i
    $ k0 x0 b3 b0 k! L9 X
    package com.javacode2018.lesson002.demo18.test4;
    7 v  v# O( s  G6 l. ]! B/ P
    2 g' M/ h% I6 E  Vimport org.springframework.beans.factory.annotation.Value;& ^; j" H& M7 F% `' k9 y" G
    import org.springframework.stereotype.Component;7 e2 s: K9 a5 S6 m; l+ j, f% b
    3 b8 `% }/ T$ m5 @( w& d5 K
    /**; x5 G& H! |! q2 K) `0 h8 V
    * 邮件配置信息  o. Z2 |6 D& Z+ t% N
    */4 [" ~7 _* r5 f% k7 {3 f) L
    @Component
    1 z+ z- ~6 p) ~7 v9 s@RefreshScope //@1* {0 T0 z# ?5 [- f* C1 T9 x
    public class MailConfig {
    + f0 _! H9 l+ e/ }$ R
    5 G* [' t& y3 M* R& u1 X. G, p8 ?    @Value("${mail.username}") //@2
    / c: l( ]8 I! \1 A& R4 `9 S    private String username;
    ) i8 f7 Y" o$ N! r% S$ s0 R5 S" f  Y# i( Y. j; `
        public String getUsername() {7 u/ e. h/ u; U$ p
            return username;
    - _: l& s, c' i/ A+ N; _$ Y& a2 i    }
    / m5 i: c- `( l7 }7 O8 X/ t3 Z/ U& ^! J5 I
        public void setUsername(String username) {
    . {3 J, n9 P8 P* x        this.username = username;$ ~+ z! g$ @. r8 L7 e  i8 }
        }6 L6 F0 d/ N0 t, @

    % i& s' x: o+ n$ y    @Override
    / X* X; q) m& Q1 K- ^4 i7 k2 L    public String toString() {
    $ H! j4 P/ d% {6 a) R: M6 `        return "MailConfig{" +
    ( ]# y8 Q0 D' p2 k$ t, r  o                "username='" + username + '\'' +6 o/ a, j) k8 f3 l. ]
                    '}';! p) c( f! k! ^
        }, [) |- U% b1 i) p
    }
    / z3 a( T+ A5 }6 J2 l@1:使用了自定义的作用域@RefreshScope
    2 u1 ~, p& f$ G( Q* f& ?) B. H8 N' k2 A' Q
    @2:通过@Value注入mail.username对一个的值7 O& l' q8 i* u/ a

    , G4 v2 N$ A' w重写了toString方法,一会测试时候可以看效果。; j+ S$ R( F0 S( m$ B4 k$ _
    ; _" M* A, {# J) p+ L* ]* s$ ~
    再来个普通的bean,内部会注入MailConfig
    + Y0 G% Z  o  U: ~% y- @1 Z/ E7 g* ~8 T, z- }% q
    package com.javacode2018.lesson002.demo18.test4;
    : k  P) p8 J# W$ ^: y8 m# ?! @, l$ p! N. E) K) u7 L
    import org.springframework.beans.factory.annotation.Autowired;
    # L2 w% P2 J0 p) k0 O' \import org.springframework.stereotype.Component;1 _. T1 g. F8 x1 t- _# T
    6 @0 }$ ?; q0 E* w* j$ r, y
    @Component0 M- \9 M3 n& M' O
    public class MailService {
    0 B) U8 ^2 r- R1 j. H    @Autowired4 L6 L7 D& ^% j& Y, c9 ]
        private MailConfig mailConfig;  v* @# X  [2 e2 q0 M6 a

    . v! z7 w) Z; }" k, o! U# m    @Override
    ; l8 V) q6 L( C8 g    public String toString() {
    ) _" s  i4 |- b( P6 z        return "MailService{" +( O. e' A8 S) ~. k
                    "mailConfig=" + mailConfig +) g6 Q5 `+ B) `4 `! y) |7 O
                    '}';
    6 d8 k; }+ i0 x& ?; d    }2 W! f3 u$ j5 v- I7 \3 M: b
    }
    9 ~- G) _4 y; n% P3 a+ V3 _代码比较简单,重写了toString方法,一会测试时候可以看效果。
    % O! _' y2 j$ \+ O& }! \
    8 f/ Q' l; T( @/ C- R0 T  d来个类,用来从db中获取邮件配置信息' G  [! G2 _9 X* }7 s! N
    " `+ d6 g+ ]  t
    package com.javacode2018.lesson002.demo18.test4;# M2 j: d+ [) g+ R( s" O1 R, q) w- M

    % W! O: r7 T5 ]  yimport java.util.HashMap;1 A! }) s; O0 w; p3 c3 j; j: f: f
    import java.util.Map;, @* Z* g* m5 y9 g. ~) y. g
    import java.util.UUID;
    5 r$ j8 M( `! j. g  v! ~3 q
    3 Y9 }: p# |6 ^public class DbUtil {
    % ?# J  H# n' \( V    /**
    " c/ V) E4 ~! `0 F3 N" N     * 模拟从db中获取邮件配置信息) R/ Z' G7 I! q" Q; k9 H- l: ]
         *' ^. c  ~6 h' G3 n; x
         * @return- A( N  i& S7 p& F% `" e- X
         */2 k* Z7 |: f) c1 A; [- Y. x" j
        public static Map<String, Object> getMailInfoFromDb() {
    6 _3 `. a" ?; W  F. N+ s- N        Map<String, Object> result = new HashMap<>();% q* L; Y; Q( I
            result.put("mail.username", UUID.randomUUID().toString());
    $ O; s. `$ T5 u/ n6 R        return result;$ D$ M0 B- w  K0 }( ]9 j# A6 G6 _4 u8 t" y
        }; d/ B: _+ E3 N
    }
    1 b& H" \2 }  U1 Q来个spring配置类,扫描加载上面的组件
    % P: h! d/ G7 d8 X  V
    1 L& ~; Z4 H: E% `0 Y; c9 Apackage com.javacode2018.lesson002.demo18.test4;; D$ r( D, n% Y) A5 k9 n

    1 x1 h' W  |; s: F1 k9 g" U1 f, Uimport org.springframework.context.annotation.ComponentScan;" \. _& z% \* v! J/ `
    import org.springframework.context.annotation.Configuration;
    $ M* Q- F' K4 Z5 H2 e: L% L+ e4 w7 A
    * q8 l, C: B0 \7 H) I4 _@Configuration
    $ [0 s  ~, Q1 G: Z$ [7 E@ComponentScan
    4 E0 S, v, M! z& n- wpublic class MainConfig4 {' U. u% }' n; M
    }
    , t/ ]3 b: N& U3 p& c, x/ r来个工具类
    / b( n: }0 S9 t% h/ ]
    ) F2 I9 }* ~1 r7 |内部有2个方法,如下:
    7 ~/ S: g1 M6 O. ^% I9 A
    ' j8 B( j0 o& c4 |package com.javacode2018.lesson002.demo18.test4;
    + i2 T/ I1 Q+ x- |# L: c) [  j" z' e/ H2 f
    import org.springframework.context.support.AbstractApplicationContext;
    ( X! i$ D! o& v# r% S; Pimport org.springframework.core.env.MapPropertySource;6 a, K; ]9 {7 U9 G! S

    1 T4 u  u! U# ^6 w' [) P# {3 `$ Nimport java.util.Map;
    ( w- `) y/ _7 t8 l7 o" l' r$ b/ Z0 A8 d
    public class RefreshConfigUtil {
    ; F' N& s. |! V8 P' K( R    /**
    * `/ B4 \. h1 V- u% N. p0 H     * 模拟改变数据库中都配置信息+ s" k" N# u# \) r, X% p" O" @8 f9 o
         */
    2 L& s8 Z3 {- [% u7 f) }    public static void updateDbConfig(AbstractApplicationContext context) {
    - C& P" q+ h# q& |* d% p% Y" _        //更新context中的mailPropertySource配置信息
    ( h' o7 z" Q; z, n2 y& e" T) J        refreshMailPropertySource(context);
    1 n7 p6 I/ d# e& E8 _; C: {: e8 W1 o: T0 ]- T  M
            //清空BeanRefreshScope中所有bean的缓存
    ' r% P6 C( O# R6 B% q        BeanRefreshScope.getInstance().clean();
    & p) f, H7 G0 s2 Z    }3 I- N1 n+ [9 F0 R
    & C. J( ~1 y' \+ ^* ]; `- C' n! t) {
        public static void refreshMailPropertySource(AbstractApplicationContext context) {; h$ [. o2 x5 t' O6 U8 G5 t
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();2 E( ^3 q4 s: Q+ a* q
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)" }( j  D2 m' ?' K  T
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    5 }. H  n8 B& n* n  |3 |        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);" g( S5 t0 \/ o& T4 t
        }
    * f1 ~8 B# k8 Q$ z0 P3 P5 v4 r- r: N
    ; F  w2 X1 n! T' k7 [}; I$ P) E, `% e: A) j
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    5 T  M# P; g8 ^: n; L' w& W* r1 A7 W# l
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    ; c- g8 Y; Y' _; p# k. j9 C! \- G6 D6 J
    来个测试用例
    ! G6 B# e! v0 X0 V! @9 w
    8 k2 x' ?6 L% i: ^& e@Test- g. }: M9 Y9 \5 V' _' @. N# I
    public void test4() throws InterruptedException {0 I6 i( |+ C; H1 n
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' e) L4 C: J+ L* H8 |9 k
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    ( e) {3 ^- H: ^  q3 \. R    context.register(MainConfig4.class);
    . W6 E7 a! w& m3 Q( q3 o    //刷新mail的配置到Environment$ `- D% s7 n$ e, K
        RefreshConfigUtil.refreshMailPropertySource(context);% c  `9 a( R5 y7 m) l+ ?
        context.refresh();+ h) y7 k4 E# Y7 }; g) C: f

    ; i+ h3 H3 ?( b9 J0 S    MailService mailService = context.getBean(MailService.class);7 v* W; t) t* d. G
        System.out.println("配置未更新的情况下,输出3次");& F) x. I3 e  M" k$ f, \0 A' v
        for (int i = 0; i < 3; i++) { //@1  y8 p3 Q2 V9 {4 D$ ?) g1 `) v, }
            System.out.println(mailService);: a4 a% ]: U; B% |1 r$ I
            TimeUnit.MILLISECONDS.sleep(200);: M& I! N) t- ]8 N& }" q5 |. g' z
        }
    / m( g, d$ k8 V: B# c1 [; N8 s! n( _9 }3 J$ ^7 @& }% G0 w
        System.out.println("模拟3次更新配置效果");
    " G4 i, \% M) j    for (int i = 0; i < 3; i++) { //@2
    : t/ }; {" V- _& O; P! s        RefreshConfigUtil.updateDbConfig(context); //@3' u& l8 ]& c" t' _
            System.out.println(mailService);
    ( T) h) Z) \! R9 q6 Q: i' t        TimeUnit.MILLISECONDS.sleep(200);  ?9 t; r& |4 k# |  c
        }  |7 G& v1 q$ ]
    }7 V8 x  a1 E2 `& V. h$ ?6 m1 T) M
    @1:循环3次,输出mailService的信息# B+ D7 V; p5 G7 I

    3 _) J+ L# y! J3 _@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    + x" g- c3 n0 @3 {4 _& p
    2 v; B; J6 l, p- p9 Z见证奇迹的时刻,来看效果4 S2 K- m( p! [6 M. _

    : g# Q, Z6 p3 {配置未更新的情况下,输出3次, e; w: H& W1 g/ t
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ! S% x- u$ w- X6 J5 hMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}! Y$ ^# m! A5 P+ \! M, B3 f
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    * `6 b3 }. x$ N模拟3次更新配置效果
    * e' [) j. G2 j: j( W. gMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    % P, I; W. ^9 @5 mMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}$ q- t) A% V& H, ]4 l
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    ) A- M1 o  z/ I0 c% k; b! L" g+ T2 q上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    + ?0 u6 g4 N1 o2 H  q1 L4 O3 Q; p) ]
    小结
    - X; x' l' d7 c1 B& k) r' M& T4 ], `/ q7 L4 R  t$ B0 _& `) |& q
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    0 S$ Q1 E7 m% `1 z( V) L" l7 A+ m  Q& Z
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    " q- r$ U2 H" P/ ~9 y  {. \  ~" c1 e$ ?& u
    总结
    8 e9 ~  _" ]' l* y$ F8 h
    ' v$ {$ v" _* {5 q本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    2 k% |1 u2 |) _; ?
    / y6 o4 F* f4 g9 i! B案例源码; Q9 J0 Q" i; d) a$ @

    4 L7 a. `# L8 [# |$ G1 uhttps://gitee.com/javacode2018/spring-series5 `( F4 f" E" n6 _* ]
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。* Q: @4 Q8 P" u' J) V1 a  A

    - ?" f) [7 Z7 I7 LSpring系列
    7 t0 Z$ I$ W3 R) v2 l  l& P4 Z4 Q4 i* ^) L& H2 G# ?$ w+ Q6 n
    Spring系列第1篇:为何要学spring?
    , ^* v5 n' w  r5 y+ F6 b8 F' q7 C- @) y
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    ! Z4 \4 K6 V, E" G+ D4 B& }$ n5 J" S' M* h3 B* H1 |& |
    Spring系列第3篇:Spring容器基本使用及原理
    4 L8 R, U$ R5 f
    5 r& n1 p0 w/ [, Q8 dSpring系列第4篇:xml中bean定义详解(-)
    5 D6 ~! r3 [6 }: h2 p  ~2 M8 ]
    $ [" S% `2 v2 o; ^' HSpring系列第5篇:创建bean实例这些方式你们都知道?% G: r2 W& `$ l2 i  d4 C7 P

    - T0 l; A. l0 V1 l; dSpring系列第6篇:玩转bean scope,避免跳坑里!, w1 f6 q! ?, F( }9 L9 Y
    . H+ u! W/ t) @7 v$ e
    Spring系列第7篇:依赖注入之手动注入
    " C' z3 f9 _2 L- I1 e- u* w: q+ g1 c9 O: W
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    ! j! Q  a, [4 u0 a6 E0 N1 _5 v0 K) f7 V
    Spring系列第9篇:depend-on到底是干什么的?# k  }0 `5 i8 t

    ' v6 s7 s* n2 H$ e& X5 W' x& _5 wSpring系列第10篇:primary可以解决什么问题?
    8 }; S! ]) r5 [; j
    , _, N- F9 ^% }Spring系列第11篇:bean中的autowire-candidate又是干什么的?9 E9 q; m8 A% `' E, @* `

    3 Z! |' E3 d5 ~. u0 V" lSpring系列第12篇:lazy-init:bean延迟初始化
      Y4 X9 C- n4 I. t
    * T; I5 Y' f0 i7 O$ v7 L8 f  e/ v! qSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    - o* ]! ?3 a5 ~. Y
    3 @: z/ x0 k* A* d8 {% |Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?( \2 |  ~% [2 a- m

    0 j  c7 y0 m  O( t/ i$ I: SSpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    4 M! d7 S, Q! H3 {) o  V) K- ^+ [6 m0 C# F
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)( U) t4 O, T, e. ^9 @- h6 c3 B  ?) i) M
    : ]2 h  h* e& i2 i  W
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    2 c$ ~& z1 q* ^% x7 w6 z1 Q) E( N0 g8 E' C7 A0 g8 d$ e2 ]
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册). g$ X, E" o+ X: M' J' T
    6 p6 ]; e) n0 J; {/ O2 B1 ~
    Spring系列第18篇:@import详解(bean批量注册)
    3 `! z8 ]( _8 u8 ?  `* y; [& D8 J7 ~( u; X0 A9 [
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    & _  v, F) }, z+ R1 c0 `
    8 C) s3 h2 x" Z# g% J0 s( a- DSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)0 w% Q0 \. f0 ]( \% ~

      d6 \$ a6 b: M0 ?Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    1 k$ H7 p1 ]8 E5 d1 M( E
    . x3 m5 T0 E, [* ?- XSpring系列第23篇:Bean生命周期详解
    " n2 o$ O2 h6 p3 h) z2 E0 F) p' }4 k8 \3 m7 S1 G
    Spring系列第24篇:父子容器详解
    ( ~  ]$ N' u* p% C. h( x8 f
    ) x% W) t" w7 K4 F' {' C更多好文章% F! d4 J; R5 w
    * h4 n0 b+ K% w
    Java高并发系列(共34篇)! Z5 C+ p" W3 h: m$ Z. i
      o. d2 _# o; D3 b3 O
    MySql高手系列(共27篇)
    ! l7 K& R) A8 L" t6 l0 l( D5 V; E/ b! e4 ?0 C8 m/ p: y; B" K8 J
    Maven高手系列(共10篇)
    ) p3 }; _! N4 {- F$ E: P: p/ B6 z0 Q. r- Y) S( C+ N# ^
    Mybatis系列(共12篇)
    / h! B) o, ?! K9 D3 B
    $ S. u) o: p0 J3 `聊聊db和缓存一致性常见的实现方式
    ) G0 B/ |! ?8 g9 `# ^
    4 i) E5 y5 t6 Z% z+ h接口幂等性这么重要,它是什么?怎么实现?, }* q5 D6 R: d# ~5 q9 _

    2 F4 H6 ?" b; l9 y7 B泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    - n7 E' M6 N+ w. Z. d" _————————————————
    6 `1 p. X: m% J5 L8 K版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。4 M# W4 y) Q% h" _) P9 h& B
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    : G: D8 L# Q- I9 k" c
    + G/ Z! `0 W/ }5 y3 z9 ?4 ^" l: Y- o8 E
    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-22 08:34 , Processed in 0.756540 second(s), 51 queries .

    回顶部