QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5409|回复: 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!
    ' ?2 p) K8 u( q! y疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!: d# ?1 ]0 J6 @/ `+ i% I# T

      m, H% M3 {3 J面试官:Spring中的@Value用过么,介绍一下) H1 _# F, Q, E% {. `! h. l
    7 M1 G- }% O& [3 n& g" d" j
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    8 w2 ~1 r3 V3 d* f* G
    " F2 G. e8 Y7 L$ ]面试官:那就是说@Value的数据来源于配置文件了?: O6 h$ M% ?% R( b
    1 l. r: b! ?2 z; i- A' R$ p7 \9 S: A
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置3 K) S* ^# |3 r, N0 ]: E1 I6 g% p
    / n3 I7 [" K6 `& s; O/ u
    面试官:@Value数据来源还有其他方式么?: s" C: }, C) F! X" B# {  k

    $ c0 \4 r% K+ R) z$ U我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    8 N! w/ u% G& `- A/ P" H  H
    ! w0 H& R0 V) N4 J3 c0 ~面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?. T# H: x% @) B2 F

    3 c( N5 C7 E2 G7 l# x% s我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    . Q; v% [' h$ V/ y5 L2 U3 V
    7 i" s7 l7 Y, y- G面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?3 ]; k& b- W* e- B' A4 ~' b

    # ?# ~4 a/ \0 I' j# _我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能$ N& }4 @# m' f& P) j
    ' p/ F9 J0 N$ M
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?/ `+ S) \9 A5 [/ L- H6 @# Q  A

    + L# \3 c5 q. ?3 P我:嗯。。。这个之前看过一点,不过没有看懂, ]5 W; W. G& q# H' D( G6 b

    0 J7 I% M0 l( `7 r) ^# Z面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    * A' U" k0 q  j
    + G  }( z7 t1 y) R/ p我:3万吧/ ~* D1 X4 c9 T! U  D7 a! x
    / {4 ^7 r& M" [9 z+ D" p! k
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?+ a9 V- Z7 i7 A) r

    6 u* U% f$ C3 ^0 Z; n我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    : m% R" v6 L, R" X1 N" Z9 }' `3 s7 }' ^: M# u
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    ; L5 G! G6 S( V9 r
      i- V0 _/ }, z! _" q我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    9 m: \9 v: b4 U8 K" W% L, _' [8 f3 q
    这次面试问题如下
    - f, M) o$ Y4 |0 C, |$ D+ f  F$ K# H( A' Q) C- m3 g
    @Value的用法9 S+ y, H+ b4 }: c9 g

    * L; U) g; r$ l@Value数据来源
    2 \+ f5 a# A" W7 P# U: U, K- b
    : ^8 W2 ^8 O% w  X* G; }@Value动态刷新的问题
    7 M1 R2 w+ K; o% F. r0 E
    ) f# G$ T& F; ]4 K/ r/ W$ k下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    - A. M5 m) I4 S7 i* y5 y
    & Q  ?( _. C! M! {. t& ]: C  J@Value的用法
    ! {+ D. L) G4 E( l, q2 ~
    # i: ?, q: O- o5 E5 `8 v系统中需要连接db,连接db有很多配置信息。
    ' w; g7 i" T% o( M( B) }, @' Q2 L' I: w+ o0 R4 P8 |& @* u
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。* O4 Y2 v: V/ o7 f
    * W' ]1 E4 l6 M5 @$ L- i* g
    还有其他的一些配置信息。& A% |& L( K5 q! N" @& r9 ?1 Z
    % U7 j2 b4 M# u
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    ( l  O  J. R  a7 {0 M, D3 @- _- t7 ?5 p5 Y1 X/ x2 Y
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。6 X" R+ P% W( X6 g
    : j7 f! ?2 d, H0 M+ o& E
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ' u0 n7 T% w/ i1 [
    , E& ^/ \3 F, Q' ^通过@Value("${配置文件中的key}")来引用指定的key对应的value。) l8 N% C% K( A0 Q6 {5 Y1 P

    $ R4 x  v* W) k( ]( G@Value使用步骤
    * e  A1 l$ s+ j/ e/ [8 Z; i& y' ]& X+ u
    步骤一:使用@PropertySource注解引入配置文件
    - K! g* k4 V: _8 a$ P; S$ p
    + i2 j) z* m+ X* p  H" f将@PropertySource放在类上面,如下8 W; `% a; w( U; `; {$ q

    6 _4 y* w" j- _+ I@PropertySource({"配置文件路径1","配置文件路径2"...})4 `, n* F  Y7 \& H$ g
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。$ L. i& w5 m  i* \! s* q
      f/ E+ E8 }6 r- e% k5 e
    如:
    9 f. }( s! a. O, p4 u1 b, M4 W8 b% I+ a: M8 d
    @Component- b! S+ H) H6 j/ T2 K7 H, Q
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    + A& _( A) A2 P" _6 a) m" }( ypublic class DbConfig {: D4 J+ _6 c# R( E5 ~( a
    }+ u/ ]; L1 [: J- G2 z
    步骤二:使用@Value注解引用配置文件的值
      s: f/ c( r% |
    # n- a8 C6 M. z. H" C7 N通过@Value引用上面配置文件中的值:
    , \  M5 D% r* E9 y1 [6 _# Z$ h5 q3 [4 `0 c
    语法
    3 B8 U1 g# B% n; ~" T% J' [7 j  }) @% R8 g
    @Value("${配置文件中的key:默认值}")6 e  H/ Y' Z8 u$ l
    @Value("${配置文件中的key}")7 ~. r6 e1 @$ z- g
    如:
    " T& j( a5 i# i: @; R; p
    ' ]5 ?' s! ~8 Z* X! H  |- ^@Value("${password:123}")
    , e/ [  E) n( c% ~/ Q) ^上面如果password不存在,将123作为值
    * m' o  @' V( r, b. J+ e$ d+ R! V* w4 n* T& J/ x# `5 Q
    @Value("${password}")
    0 ^( s/ J% B) j# \上面如果password不存在,值为${password}
    ! S; n- ^& z$ b7 l2 J# f" C/ M/ f1 c9 y6 r  G+ ~0 B% _( p3 |: I5 ^
    假如配置文件如下
    0 }, H& N! }; _+ a: [+ ?
    ! ~1 V; q5 {  n+ ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    8 i% s/ U7 S- X4 Ojdbc.username=javacode$ |! E  j; I/ D" s% A
    jdbc.password=javacode
    ' x9 s  d- i5 D- Z使用方式如下:2 a/ {% B$ ~  f5 L
    2 X! _4 Y! _) F' B+ z( s" H5 o
    @Value("${jdbc.url}")
    4 u# j8 |) q  d+ w: gprivate String url;
    ' e: B/ m: p% h4 l
    & a) V4 x% f+ K4 C7 l) q3 ^5 g@Value("${jdbc.username}")4 |9 L0 n, j6 J! I; Y, N0 u
    private String username;# ?% t4 I: L- t+ P/ e$ \! m

    - T. I1 ?: {; ^5 k# g6 [, D8 t: }0 F@Value("${jdbc.password}")& g) Y' k, E. B+ O7 |
    private String password;
    $ {- i6 m# E, J. ~  E! ]下面来看案例* ^4 s+ i+ ?4 E3 E& r+ ?# S6 N
    0 I" w' ]& t" t0 T; h
    案例
    , e, T) @, |5 z- G: n2 x3 W5 F/ n& U0 `
    来个配置文件db.properties
    9 k/ n& [3 V9 I* Y) l& f
    / Y4 |9 a' |. E  x+ E1 A) Rjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8# r, T, \1 f6 I  [. Q
    jdbc.username=javacode& [/ S7 a2 Y) E3 I
    jdbc.password=javacode4 V. B8 a: {0 Q$ F
    来个配置类,使用@PropertySource引入上面的配置文件
    : D: P  f5 o* f# O; i6 e$ P1 Z
    ; p) M7 H$ v! _5 j* {package com.javacode2018.lesson002.demo18.test1;9 {; E* _% z2 |4 D3 |

    . z0 O) h* v" |( Qimport org.springframework.beans.factory.annotation.Configurable;0 _; [, q5 ?. \3 p4 J9 p6 @
    import org.springframework.context.annotation.ComponentScan;& k) T8 w/ V1 e' o
    import org.springframework.context.annotation.PropertySource;
    8 {) C! ~5 N/ {& g$ s) h$ Q, B% ?& |+ W1 Y9 `; P: ]
    @Configurable
    * H& p7 V- j# }* {8 U! }@ComponentScan
    3 y, T/ U. A0 C: S. W# ]1 {, U@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})1 S8 z7 [  q; z# Q) s! k' M$ R
    public class MainConfig1 {
    3 u! s; J4 x- N* I}: D5 y& u, I) V; Y& L" V. a* o
    来个类,使用@Value来使用配置文件中的信息
    7 ^$ r9 B- R; h8 H" E" ]
    & }* |# s7 `; ?2 v* B6 h1 D: Tpackage com.javacode2018.lesson002.demo18.test1;: |% z( D% E  l. @( J
    . d: Q- c" i. a- [9 D
    import org.springframework.beans.factory.annotation.Value;. k$ c' j- _! w& H! i7 n' X" M
    import org.springframework.stereotype.Component;
    * Q% F' ]4 b9 N! ]7 |0 b5 s0 u  k5 k, ~
    @Component
    . d& q7 c9 ^, f1 lpublic class DbConfig {
    4 C# x% d4 s9 J% i9 i
    " q* ~/ _: Y! `* w    @Value("${jdbc.url}"): L! d; S- Z% w$ r. S* |
        private String url;6 P' H% b& X2 j0 t+ ^6 g# _
    ) H% ^- j9 |/ M9 t$ e- f+ S
        @Value("${jdbc.username}")
    ! k, ~! ^, t4 C5 `9 j- u    private String username;
    % ^8 E# L8 Y* b6 ^' I$ X7 U# e5 x5 x6 R6 g
        @Value("${jdbc.password}"). Y) Q7 W8 e& r4 ?! {" t
        private String password;# k/ S  R7 s7 u" @

    6 v, y6 {2 ?, D# ~0 o& O" b! B/ ~5 L    public String getUrl() {% C" _! f7 y+ T  g' O4 P, x
            return url;$ D& Y: t* e5 U
        }
    ) N/ P+ S' S1 h, `$ j( _1 @" o6 @0 p8 {$ f( S' M. K
        public void setUrl(String url) {4 X( @" A$ q- ^) p0 W6 s, ?9 |8 v
            this.url = url;
    ) y& B5 z" P8 r, [* x    }9 d  g; ~5 r6 ]# K5 }& f
      |/ K* I( ^2 i& N: v  o% J
        public String getUsername() {
    + j2 T' p4 c/ I+ j7 Y        return username;" C- t5 E9 l6 w& a. `# m
        }4 t4 |; k" T3 \# v
    3 G3 y- c1 S- F6 j: G
        public void setUsername(String username) {% q0 j& t5 e- X- {7 a. D& Y
            this.username = username;
    # N7 W: ]0 a: u# m" A1 v5 a    }
    . f( N# Z! U# H+ n9 o) U) ~. y3 j9 Y) {9 {4 q9 w' k. E
        public String getPassword() {
    2 C5 P  R+ `% I        return password;
    3 N% D4 z. i+ I4 {. x# e9 o: K    }: N1 c0 ]% S' w% t# s
    + g: T5 f' ?' h- E2 j6 `% @+ T! t
        public void setPassword(String password) {+ o# {0 Z4 F' h* H/ }! k( P+ ^
            this.password = password;
    0 i: f9 t& i) S- h! }$ r6 e. _    }$ ]6 g$ D5 |  c0 f6 X8 k0 y
    3 e* Q- O9 L( l* I" Z
        @Override
    * p4 [) U! Q; ^+ x    public String toString() {+ Y3 [9 M0 k$ n' R! F. z
            return "DbConfig{" +$ c) E2 H9 k9 r" x
                    "url='" + url + '\'' +, ?7 r& U) |* q' w3 N
                    ", username='" + username + '\'' +
    1 @  L* ^; x- h9 e                ", password='" + password + '\'' +% _, E5 z: B6 X
                    '}';% z& K, H5 s5 G$ d% z; Z
        }
    2 w% t9 r: o* x" G! L}) |9 \# F' m( G2 D8 h9 a0 e
    上面重点在于注解@Value注解,注意@Value注解中的
    1 }) H9 r6 Z9 g% E: r/ a
    2 s6 u& C* }' n+ T- W; m0 \. i+ }. d来个测试用例7 x6 b* B8 h9 n2 g5 D' W
      C/ [  s% s" H( n! D, y* p& L
    package com.javacode2018.lesson002.demo18;+ T. U8 ?" X$ ~

    5 X6 j/ n* T2 Kimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    / b4 O, Y% e3 h7 o1 cimport com.javacode2018.lesson002.demo18.test1.MainConfig1;7 W% o9 e7 q" ?" B' P% @( a! r6 g9 P' ~
    import org.junit.Test;
    " K% ]. U) F% g) c% Aimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    2 ^* b8 B: w7 M$ }; }- z
    ' [# H; _) z  Q8 w% a5 l: W" ppublic class ValueTest {1 q9 ?- d2 a' z* g2 L* l

    6 y$ T0 k' T( P/ _1 v1 E7 P1 I    @Test) V8 k. T) @6 Q5 ?# O) A. g) g
        public void test1() {
    ( M2 u& A: B( {3 O0 t' V        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();; x( Y5 C5 u0 q. E  Z; K( C
            context.register(MainConfig1.class);
    ' q4 h  ~+ N) E# Z1 J1 ?% I        context.refresh();
    4 g" {) t3 w2 z) l# a: j
    ; X' W# v4 G4 l+ ^$ r        DbConfig dbConfig = context.getBean(DbConfig.class);
    - \7 k: i+ }1 F% |* l% ?7 C        System.out.println(dbConfig);
    # x7 ]% O) g8 Z0 N    }( d8 Z* i( D! g
    }
    ! r0 B9 h1 a* \  E, v: {运行输出
    1 c( ]2 Y8 ?4 n  @6 f
    9 j- v$ U+ ~2 t7 EDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    , R' ]7 J( C- Y9 J( {5 r! k3 X上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    9 m6 D/ b( [+ U% Q5 w: {2 h" Y& D' a8 G
    @Value数据来源
    - r" ^0 a+ Q4 E( Y! e9 v1 H' ?0 `6 ~: U6 O) n! {% g
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。( I5 v7 J- B# `* N4 W) l, n: F
    ! n- s! M8 u: G, D. d  K6 ~( q
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    6 B# }% v( ?; b( o, d# W3 p
    9 |; s6 C6 f0 y. q3 T1 {spring中有个类
    ! j# Y3 L5 A; g  b; r
    4 t" s) t% Z2 Q4 l4 U7 P  iorg.springframework.core.env.PropertySource
    6 m+ w# ]. C8 L1 Z# [8 V$ B8 @2 B可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息; Z" b8 d0 N& R  p2 y8 l
    # c8 ?+ }) X8 ]* `$ E0 w, v
    内部有个方法:
    8 R  g0 V( V1 e8 E8 w% W( \! X3 f( {& ^# e
    public abstract Object getProperty(String name);
    " b. B  Y0 [* m7 J5 u通过name获取对应的配置信息。
    5 O' ^/ }% N1 D" `" ]' y  ^  z$ m; q+ s- ]9 `- }
    系统有个比较重要的接口
    3 R7 I$ i/ D1 ~+ d; Y# A9 o
    5 e- Q) k; U$ Iorg.springframework.core.env.Environment% K4 E0 _& M: t- T  }
    用来表示环境配置信息,这个接口有几个方法比较重要+ c! r/ [, M* S& h: z4 V* o
    . w, y, ?+ x  K
    String resolvePlaceholders(String text);
    : P, a2 c+ w; R; `& I2 bMutablePropertySources getPropertySources();
    7 j4 H/ y; ^% wresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    * M/ C% o9 E/ U0 n' @* D
    # d+ o# E. X5 ~1 ?! O* @+ |getPropertySources返回MutablePropertySources对象,来看一下这个类. Q7 r' o  Z% k. k3 C! M2 c

    3 }" l/ h" U8 b8 b! Ipublic class MutablePropertySources implements PropertySources {
    ' I+ i- I. m5 P2 a5 G% g
    + X/ L- P9 O8 p5 ~, d: P    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();, d) j6 Q! U/ B; g
    ! n: N( ]# v1 t, F3 L8 q
    }+ q0 e8 j7 C( W
    内部包含一个propertySourceList列表。- M0 c# Q, C& y1 q) v8 ?* [

    , ?" J% ~3 Q& N4 l1 mspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。" V2 v7 L( h1 q9 G/ K

    ; L8 O3 I8 g, ]3 z( `+ m! c大家可以捋一下,最终解析@Value的过程:" v; I& \( c/ |- {! W6 s8 ]

    1 B. p, K  x( S1 ?  D. I8 F% }1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析: U3 w# q* l. k& G3 H6 l6 E
    2. Environment内部会访问MutablePropertySources来解析
    7 {# |: x. O! ^3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
      f- w/ g) e. @5 X* b通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    + f: R' U: ?8 j. j, }* J
    1 B- n/ s( v6 [- z9 T下面我们就按照这个思路来一个。
    # s5 Z; W7 {! N% B+ m% `
    7 X4 X# m/ Y% T来个邮件配置信息类,内部使用@Value注入邮件配置信息; @6 n- z6 J$ Q9 s/ `+ m
    * \  w8 U9 Q2 \* D  b9 O3 b( o
    package com.javacode2018.lesson002.demo18.test2;7 {% d, c- F- @  b2 p+ y

      D2 {  ]3 d: Y$ |" ~- f) simport org.springframework.beans.factory.annotation.Value;
    ! D8 u+ X7 l1 N: @import org.springframework.stereotype.Component;& n* R* [( I7 `+ ?* Y# t  C% T2 {# A

    5 M) X/ k0 M0 v2 M. g/**
      x$ e; }$ |3 i * 邮件配置信息9 N9 N4 j5 T: l/ u% o. U( x9 w
    */
    / r" g7 {% n0 M8 ^@Component) K6 m+ U3 l) ^7 Y0 A) F
    public class MailConfig {
    ( O" H# i' w9 ~. m2 _2 G% }1 W, T1 v
        @Value("${mail.host}")
    3 A# f# f" j" C4 R$ `4 i# G4 H    private String host;
    % G4 h3 I" [& g/ V9 j$ X+ T$ K, G: g" m3 [5 t
        @Value("${mail.username}")5 i: Q- J- e& ~+ l& @0 `
        private String username;
    / H  I5 ~% G5 ]2 ?1 r4 o* V; `, b3 {4 A: {
        @Value("${mail.password}")
    , a: @8 m* f& U$ S+ r    private String password;" h. b7 A6 h& C2 h+ c% O1 S2 K4 n
    4 F9 y) j* v. f; R3 R/ p
        public String getHost() {! Y1 k' \2 L& {& o! Z+ L
            return host;/ f4 ~; F3 l9 o/ O
        }
    2 x. ]$ t2 g( H) r
    9 |1 _9 o+ s' s! D  n  w" o+ ^2 Q    public void setHost(String host) {
    " w4 R/ u0 P( h! m8 P1 A, Q8 h: R5 ^/ X        this.host = host;% k. D0 u9 l, ]7 r+ v8 _7 y! M
        }, q6 U8 I/ H+ b' ?7 k

    9 @1 |" m5 [" ~" Y$ ~    public String getUsername() {2 c: Z' k' S) L. q  n
            return username;( h( o3 b/ G3 Q3 E8 J) F) {- W
        }
    " g( D1 t% T: v' j: k4 Y- \1 x8 }8 Y$ z) S9 U/ r
        public void setUsername(String username) {0 o. S) u1 d4 o  [  Q- k
            this.username = username;" l8 Y* _! t( \* N
        }
    0 E* H3 w) L2 \+ c4 p/ ]
    ; l- F3 y7 f6 U! f8 x' q6 e    public String getPassword() {" t& Y  m7 N( u$ z# e& r2 w$ o
            return password;2 [( `& l* w& {! D3 G: z: k
        }2 u+ {& W! N2 Y1 A6 J
    # F# Q( e6 h, r. f0 v
        public void setPassword(String password) {% L+ z7 Y" C7 b/ y4 T# ~8 H6 z. A
            this.password = password;
    / d2 j8 F/ j. g+ A; D2 \8 F    }
    / \& b, f; O; t9 E+ V' K1 u
    4 t0 i- j9 I4 J0 I    @Override' ~( N( K4 d9 f4 S- x# K) h+ P7 S6 M
        public String toString() {
    / k$ R$ t! h6 e* F% |9 |" G0 `        return "MailConfig{" +
    ) }3 w& w# I6 o4 c- _  W                "host='" + host + '\'' +; R' Z, ^! I  Q
                    ", username='" + username + '\'' +
    ( _6 ?4 z2 |' j7 ?2 j' M. w                ", password='" + password + '\'' +0 R% d, r# h" l9 j# C
                    '}';' i; G$ X8 `, y. T& m9 v1 }. a& m
        }& E2 ~' ?' b+ c
    }
    - C: `# f1 N" e- q2 c; a再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    $ Z- t7 B$ l) z3 v% Q: h, O  S! W# ~8 i" C
    package com.javacode2018.lesson002.demo18.test2;; S- _0 t6 Z$ ^& Q+ _/ s# L

    0 Y2 }! h. b1 M2 Z/ v4 Z$ qimport java.util.HashMap;/ E0 V, ]6 X) n! n/ C
    import java.util.Map;5 T5 _- q3 i& Q* \1 t
      M& j$ M. {( ^$ V# }
    public class DbUtil {* j& m5 ]# a- g- E* L
        /**
    1 }+ W! A8 {+ D/ R, H     * 模拟从db中获取邮件配置信息
    ! W. g9 U, w% M. z. G  n+ n0 I     *
    0 I; m, E9 O9 T. k     * @return" N" q, \1 u" X# g$ W
         */" \; |! z" [. I) D+ ^
        public static Map<String, Object> getMailInfoFromDb() {
    " K4 M* b  a: l( o& p        Map<String, Object> result = new HashMap<>();
    . x- _4 b7 p5 [) N  O        result.put("mail.host", "smtp.qq.com");9 g7 |5 x" e  G
            result.put("mail.username", "路人");9 R9 H- e6 \4 ^' z+ c+ |# T/ z
            result.put("mail.password", "123");( w- b) O8 B' U) N7 z
            return result;
    : c/ Z/ B) |3 g/ G& [) l: e% O    }7 |7 F. h! A5 e- d' x- Z7 O0 `3 e
    }
    ' Z. M* C# @9 P/ A& A来个spring配置类
    - C0 c8 Y8 T) x6 N. j$ q2 X0 }9 n) e% k0 `9 D6 R; g
    package com.javacode2018.lesson002.demo18.test2;) n+ T2 f0 Y+ u8 p/ J
    ( R1 G$ A1 R! I2 Q
    import org.springframework.context.annotation.ComponentScan;
    0 p7 w$ N/ ]1 ~9 P9 P) Oimport org.springframework.context.annotation.Configuration;
    & f' r  ]3 ^5 K; w" T2 Y+ e' E1 b- v5 X3 @, V
    @Configuration* x9 f. O  O7 t! l- R0 E  q! ]
    @ComponentScan8 \$ \* \3 D7 O3 B! k
    public class MainConfig2 {
    ) a6 l, J! D+ i. k; Y0 x0 l  Q}
    % m: R  y3 u, I) {下面是重点代码
    2 x+ K* o& q6 G$ @1 ?! a" U7 k5 j  z5 }0 Q! h, t. g7 Z
    @Test
    8 }  v2 @! p$ _9 r+ ^7 u1 M3 cpublic void test2() {
    / E# Z$ e: W7 B1 S+ q    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();4 g% `# `, z  O( X: g# ^0 t
    ( ?1 T8 B0 V2 v' v% k: @# h
        /*下面这段是关键 start*/4 }- Z2 s* C' Q: V
        //模拟从db中获取配置信息% {& J1 H7 Q5 U0 V
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();) Y- v  `9 r2 A% I  J' g
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    9 D. w0 B1 {3 m! y& P$ |    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);. ]# q; L% e( r4 h
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高5 R; {1 Q& g: h! G9 Y( d  V' D6 N
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);. A" W+ g0 w  G
        /*上面这段是关键 end*/
    " N- k7 D8 j+ d. }' f& T4 l9 H  h+ H# v3 K' p, L
        context.register(MainConfig2.class);1 Q) k+ Z- x% X3 o6 V
        context.refresh();
    1 t7 S- d& h* ]. Z: t    MailConfig mailConfig = context.getBean(MailConfig.class);
    & D+ n# r1 k9 N1 w3 P0 |    System.out.println(mailConfig);
    ' e- o! T  X* k( n}
    1 s2 p6 @, Y% _) W' L注释比较详细,就不详细解释了。3 u8 M, E/ S7 N$ B4 S2 ~5 G; U4 c$ t
    5 ]" x, t" w6 _1 C  m
    直接运行,看效果
    ( z$ C* t# p! M" q/ H
      j; ~& |% H1 z& r: F" r3 \% yMailConfig{host='smtp.qq.com', username='路人', password='123'}
    * Q3 a. T" V8 X有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    8 C4 [9 r' R6 K; W4 o' k' h9 I# t- |) k3 F+ S9 v' A- t
    上面重点是下面这段代码,大家需要理解7 U0 ?8 ~0 ^1 `

    . `: ^6 [4 C: V, E/ f/*下面这段是关键 start*/
    ! z7 x2 C. n; d8 d! v//模拟从db中获取配置信息1 q+ B, W+ r* g7 F% k
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();8 V* a* k. J6 a
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类); E' c' n8 n3 y
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    " C* K# f; R5 G+ Q5 v- g2 o//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高  _& }: v# R2 E+ V9 X  b% F
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);  [) j) p$ y# \8 W
    /*上面这段是关键 end*/7 U/ p/ v% _& z' i
    咱们继续看下一个问题, N- f9 [. X7 u
    : `0 p0 \" ]5 r( }5 ^- x! i. a3 O
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    5 [- ]2 E$ t& I1 b- q" @8 R$ B+ ~: ^  Y! U0 l
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ; `  h* z* m2 a. e3 G6 B
    9 p0 L- r2 [! D& E& g. j9 s实现@Value动态刷新
    7 t! [& ^$ ~7 |  m( F
    ' w: C# E# R0 o先了解一个知识点
    " @2 q  G5 Z5 t; Z+ A4 \" k8 v
    - a$ f& L1 q3 y+ K7 V  Z/ k这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    ! F1 A9 |/ E; j6 Y5 ~
    - n) w. S# S; _$ M% t2 k; s这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    ; y: E/ [2 L. o6 x7 I# F4 e* @$ Y3 [  n: U- x5 L8 S
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    9 K* B7 G( t5 L
    ( W) w+ R! h  L" k" jScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;/ Y( r6 v! t" p( y* j, T
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中  I8 V* k& f7 _* b/ ]

    $ u1 P3 C" s! @public enum ScopedProxyMode {
    ) {# C! @+ X2 Z2 ~9 O7 c9 C! k    DEFAULT,3 `+ D* k5 e" Z; Q2 N; M$ _
        NO,- o  s) A( ~2 `% t1 d9 p
        INTERFACES,
    9 [$ l9 j+ A2 I- P8 F+ u" {8 E    TARGET_CLASS;0 g9 y  E2 h5 z3 [
    }
    " @2 q0 [, W5 e% a  M  j前面3个,不讲了,直接讲最后一个值是干什么的。) K- n  D0 i; E# N, @: N3 [: \
    0 z0 p- f5 X9 N5 w7 }
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。/ G4 K2 A  C, D4 s

    2 g, P/ ]' w9 c  Z; W理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。5 t  X# _6 V5 B* g% L6 A) i

    , `6 G9 t8 g# }0 b8 l自定义一个bean作用域的注解' n+ x" m1 F8 L4 j
    2 ]$ z+ z6 L7 R4 f; a7 [
    package com.javacode2018.lesson002.demo18.test3;
    - n: [" R& v% Y" W# f3 K4 |. C- ^+ q+ J6 X- R
    import org.springframework.context.annotation.Scope;
    5 s! q/ d; ]1 H$ o/ simport org.springframework.context.annotation.ScopedProxyMode;& ~4 u7 v8 |- t

    $ v* _9 O$ j0 p3 k; |6 _7 Q3 ^import java.lang.annotation.*;
      M. K6 n2 O) f6 i, ~, `) U) h6 g5 [% {' z
    @Target({ElementType.TYPE, ElementType.METHOD})" b- r3 n' }9 Q2 m2 _
    @Retention(RetentionPolicy.RUNTIME)
    # j& t5 F/ k; [9 E. k@Documented
    % d8 ]+ D) [$ w0 }. J& s8 b- ^@Scope(BeanMyScope.SCOPE_MY) //@19 S' O' f: p7 i, ?+ G9 j4 I9 F
    public @interface MyScope {0 @* d3 t: t9 W
        /**0 Y7 e7 L4 l/ e7 q9 F2 C: ^! Y9 i% k
         * @see Scope#proxyMode()
    ( ?. W: S5 j+ s     */
    - B6 F$ v" F: q5 q$ v1 p    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2! T: j, d2 ^% e
    }. m; l! K# j! \; {* N' B
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。2 M6 [, C* V5 M+ ]7 O0 ?5 P. R% X

    ) z6 `8 J+ ?- x' o7 \; y4 x@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ! v2 i" g; z3 v* P: }
    / j" n  {: `, Y  @5 [@MyScope注解对应的Scope实现如下6 R: H) v$ u( }" _4 G, r  w
    " O/ p$ a. l4 j" y
    package com.javacode2018.lesson002.demo18.test3;* q7 u2 u: z" {; b$ w( i7 u

    ! U1 k" p* }3 V5 O8 X6 J9 Nimport org.springframework.beans.factory.ObjectFactory;) k* _# A+ f+ _
    import org.springframework.beans.factory.config.Scope;( [! o3 E! U# j  n+ N! I( L- W$ Q0 J
    import org.springframework.lang.Nullable;
    . A& x; d8 W8 h- v, {/ t/ r" q7 g: m0 H% O4 u8 q
    /**
      g( f7 F6 P$ U( F) D" c * @see MyScope 作用域的实现6 L+ V. u0 i+ O6 f3 Q7 X
    */
    % D( J0 _5 W. r, k6 o' j1 ipublic class BeanMyScope implements Scope {
    + s, |8 e( e1 X1 c8 W# \- H4 W: n( a/ t5 F+ y$ |. z
        public static final String SCOPE_MY = "my"; //@1
    - v; E3 Y  I8 a* f) v* r
    4 F; t7 P& g3 \" _1 `    @Override
    9 M# g1 x9 Y9 S, z! X" U: V- [2 W    public Object get(String name, ObjectFactory<?> objectFactory) {
    / i  j9 b; D5 ]8 {& H        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@29 N& H7 y6 k" {, E
            return objectFactory.getObject(); //@3
    " P" L  l) r' W; j4 H. O7 O    }
    0 O7 O# p& _( D1 K2 q% X8 a
    ; L# u+ h  t: }9 B4 _! u    @Nullable7 T% z$ N) A7 Y" m) L8 @
        @Override
    6 g7 X0 K- U3 j2 J" z6 b4 F# |    public Object remove(String name) {
    # `. E( G3 Y; C$ \" l7 e1 e        return null;) n) I) s8 w% X/ ]) M
        }' o; {* ]4 d. W& \! }# Q7 A7 K

    " R; c. n* ^+ {' P! A+ m    @Override
    # [% }% D% p  t/ T$ \+ M    public void registerDestructionCallback(String name, Runnable callback) {" T# G1 G  `5 Y- r6 |5 ~
    , ^) Q6 \/ S# J/ L' ?  f5 @
        }
    8 {/ J3 q' j% K) G0 @  e. j8 `: @1 X2 d8 E- i( Y8 P; U
        @Nullable% t, c& \6 J  g" ]$ [
        @Override' X4 N& a' y7 X0 [+ T- h8 \
        public Object resolveContextualObject(String key) {
    ' S6 q) p& F( L        return null;
    $ v* w" L3 V4 q    }+ o( P5 k3 m  F% Y, W& ?

    ( H$ `% R( Z  d) }; N  L/ q* @" G    @Nullable
    . a. @& T  \, t, m1 U  e2 I$ j* X) W    @Override1 \5 c" ^- u2 f- j1 b: `4 H+ Z
        public String getConversationId() {  z  ?2 i8 B  `' w1 N( S8 |
            return null;
    ' t6 C+ V1 T1 H  w  g' e5 @6 c' j  E' ~    }
    + b) |8 L. K  m+ G, t- S% }4 X}9 i2 s: \. h6 p4 h: {
    @1:定义了一个常量,作为作用域的值- B% G# s! W, n) @- ^% U. L

    2 i& }/ ^+ S2 C2 f5 i@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
      x4 ]  G0 F( a: t
    : C$ a, P3 R# X9 c' I@3:通过objectFactory.getObject()获取bean实例返回。
    * y: w# [/ R; y% U( Q' Y# I$ n/ T0 Z
    下面来创建个类,作用域为上面自定义的作用域0 @0 i- J% L3 Y' t8 {' c. b" O
    : f* H* t0 D; }" t
    package com.javacode2018.lesson002.demo18.test3;/ e$ v& }  o8 H; y* r8 {5 E  |

    : O) B5 f7 q9 himport org.springframework.stereotype.Component;: [: r. U# R/ P* Z
    $ `9 Z- C! k/ c7 A0 s; K
    import java.util.UUID;
    5 [+ r7 ^5 N$ `' I5 I+ ~
    3 T# C9 j* U* }. \. ]. [5 U@Component% i. C# ^! x2 u3 t5 h# @
    @MyScope //@1
    ) X7 J0 j, d9 j0 J7 `- ]public class User {
    . Q8 r  _: a# ?4 f7 [
    # I9 \1 B1 e* B5 d5 w9 {    private String username;1 w6 i5 x2 \! z6 q$ X
    % x5 X2 B2 M  i
        public User() { 5 N& M2 x$ G, X7 ^3 U
            System.out.println("---------创建User对象" + this); //@2; y' h3 V! S8 Q
            this.username = UUID.randomUUID().toString(); //@3
    0 h6 C: q0 X1 D    }
      o/ T4 s; {1 k: t. L% w
    5 P0 x- E5 w  ~, ?4 U) L# j    public String getUsername() {
    ! V! Z) n9 @8 V9 I        return username;! w9 S3 i$ b; l% B0 n3 M+ N
        }
    4 {" x; `0 ]7 X9 }1 X+ F$ T
    ( x4 P4 t/ q. Q3 i6 v    public void setUsername(String username) {' F$ i, i8 a1 _7 T: h
            this.username = username;
    6 a8 _1 k9 ~7 `; U+ b* O    }; ^8 e7 ?! H* {$ t2 L+ V
    - |: E' i3 \8 Q' Q
    }
    3 A4 k+ k5 L* y  y" i@1:使用了自定义的作用域@MyScope& q: t  y4 m; ]) N" W( @! T
    6 v. M2 d) q2 J& _) a* X
    @2:构造函数中输出一行日志" @7 y2 o  ]- G& x1 ~3 K
    . {9 @/ q; H9 g! Z5 ]& d+ O' |
    @3:给username赋值,通过uuid随机生成了一个
    & E% p& O; ^: O8 Z; X% h2 Z
    / }5 K  x3 w' @/ C7 J7 Q来个spring配置类,加载上面@Compontent标注的组件% i& Z' ~7 z! Y% r6 I
    ; ^# g4 v/ K; T+ O' ]3 D
    package com.javacode2018.lesson002.demo18.test3;
    ! W. E2 c5 o: m# q- k1 H" I# C" S; C' {6 j& I% b% }! |* O# g
    import org.springframework.context.annotation.ComponentScan;
    8 f  e, p+ a: D  L2 E# U' Eimport org.springframework.context.annotation.Configuration;8 }2 e0 a5 n% d/ H
    % }8 d! }' }3 v
    @ComponentScan8 u# s* _% t+ |3 m3 M
    @Configuration
    ' S7 Q' ], o/ m! Q8 V1 C7 ?public class MainConfig3 {4 u& ]% }) O0 z. o6 B( \; X1 p
    }
    8 t6 ?' }+ ?& ]1 {下面重点来了,测试用例1 B% ^. S" U) i2 _/ D% N
    . \' |% ], ?4 W2 B/ I
    @Test2 J! m' P* Q7 V. U) k
    public void test3() throws InterruptedException {- i3 L% `7 \$ }7 [& I* y
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();3 k" e( V& Y2 M
        //将自定义作用域注册到spring容器中
    ! k  I& {; g$ f8 @/ C9 a  t    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    ) V: h" d' }/ b" D" s    context.register(MainConfig3.class);
    8 q+ c  V: n) E7 R    context.refresh();7 u  z  a% S" J' I$ ]

      i( f" R' h6 W. N- j9 W: H$ ^    System.out.println("从容器中获取User对象");
    9 v) @6 u. x2 i+ Q6 A6 t' A& d1 b1 c9 Z    User user = context.getBean(User.class); //@2
    4 f' E* O; \) x+ @7 ~    System.out.println("user对象的class为:" + user.getClass()); //@3
    ; v/ ~* X9 Y5 u' M
    6 m2 b. ]+ j; r, Q    System.out.println("多次调用user的getUsername感受一下效果\n");
    ! r* L4 \1 n$ H$ F# o3 {    for (int i = 1; i <= 3; i++) {
    : G' \7 x0 O- i' H/ w; n0 O        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    5 o; `7 t! J( g, i3 B        System.out.println(user.getUsername());3 ?6 F: m/ x' t, O3 e
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    8 t' T" O6 Q" M6 `3 ]9 \) c7 d    }
    $ O, e; ?% E' {, Y7 Y}/ i' _# Q3 i1 W; e- j
    @1:将自定义作用域注册到spring容器中
    / H3 p2 x- M& Y
    7 E' N! z: k6 {) i@2:从容器中获取User对应的bean
    & G! o$ W. R- {/ v6 U4 F4 R$ G* X; Z+ \# k4 K
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    " k  H9 j2 X; U1 {8 x6 m/ L7 N/ n  X( n* r: f- E+ S! H- n
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。- r" J+ k9 S/ T9 Q2 ~5 `) b8 o1 D

    8 T7 p% y0 d# p( O见证奇迹的时候到了,运行输出
    * X3 V& x' I) i2 k8 D  s8 u) U5 H0 G3 R6 H' e
    从容器中获取User对象
      h" ^* L$ j4 R& i9 o% Z& ?- L. l" Euser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331272 I- y% k% f% V  q4 u% z# b
    多次调用user的getUsername感受一下效果- [. x% h0 N6 K% T+ U& T: f+ Z
    $ }8 Q8 G, x. J1 a  r/ L
    ********
    0 e5 i& Y# `9 A3 Y第1次开始调用getUsername& I- P% n9 W* v2 ?/ `
    BeanMyScope >>>>>>>>> get:scopedTarget.user4 B4 s. k' h; ?
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    / r7 e; a+ ?9 B& h7b41aa80-7569-4072-9d40-ec9bfb92f438
    : Y0 k+ ?1 _% M- j1 A: M* k" U第1次调用getUsername结束9 L+ F) B) V) m" E- @1 h
    ********
    ; P( V- h9 k) ?, ~. O6 c/ D* P$ @/ y7 v& v1 k5 x
    ********
    , ?4 w6 Y  Q1 R第2次开始调用getUsername: p  k, Y9 _; G; F+ E0 y6 B
    BeanMyScope >>>>>>>>> get:scopedTarget.user; ?. e4 M6 @  a  T3 g5 Q' Z5 ~
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    1 U+ l1 y! {1 o& E1 o  y01d67154-95f6-44bb-93ab-05a34abdf51f) S" M. ^, h/ F& f2 w
    第2次调用getUsername结束
    8 t9 }: |" s5 C2 j- G( R) f6 H********- U, g/ K4 h- `/ b. ?
    6 f) F' a, Y. p0 {& W& y# W
    ********
    : K( }" |* W" a0 H& k9 U第3次开始调用getUsername6 a3 X5 q* r. d! g( m5 B7 b3 p4 u  l. ~& {
    BeanMyScope >>>>>>>>> get:scopedTarget.user3 P9 K+ a1 G* \* u8 x
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    " }4 k) T% Z" g* W+ }76d0e86f-8331-4303-aac7-4acce0b258b8
    * h5 x; M; ]1 P第3次调用getUsername结束
    9 U- Z4 h& \8 k- k& W4 @4 k2 @, [8 b5 K********
    3 J7 V: u1 P$ @7 D9 V从输出的前2行可以看出:
    % t; i4 e& ~3 I6 x6 v3 z& a8 L/ r0 h/ c2 N
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    % |+ q! D5 q  t$ ]+ I& |
    2 v3 H+ ^" ]# ~* i1 `第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。9 V1 C& Z- X: M* B

    , |' U( A) o* w后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。. z+ X0 m: V& z7 m

    6 D5 U0 P; W  m0 |3 N通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。6 p7 a! a3 R2 M6 R; o1 I. j

    ) o% B1 r& U. O  P$ v- `动态刷新@Value具体实现
    . G2 v4 d0 s; K& m
    * ?) C  ~7 E  ]6 X2 w/ @那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。: `# p/ {5 x7 t! V) ^
    8 e% }, x- Q6 s* P& g6 a. F9 ]: g
    先来自定义一个Scope:RefreshScope
    + n' F0 L8 T# R- J- u" d9 C' g9 x# v. C$ l( ]/ ?/ `
    package com.javacode2018.lesson002.demo18.test4;
    7 J. V' {  O# R! Q; w" R! U
    & F" J  c* e% f9 v% f, t2 eimport org.springframework.context.annotation.Scope;! o* q1 R+ x* I) v* m6 }" z6 Q5 e
    import org.springframework.context.annotation.ScopedProxyMode;
    % f% v2 |8 r9 m+ b: W3 j% o& L  W% S( r) Q  @
    import java.lang.annotation.*;
    " j' o) n& p% J
    3 S# X9 a: n9 M$ q2 D$ u@Target({ElementType.TYPE, ElementType.METHOD}), m5 A; i0 |/ ~: l, l( q4 h
    @Retention(RetentionPolicy.RUNTIME)# n; p8 a3 W0 `& ~: l
    @Scope(BeanRefreshScope.SCOPE_REFRESH)( h, p5 B" o1 Y) S$ H8 s* L
    @Documented
    2 H* z/ Q* N' }$ M" _public @interface RefreshScope {
    : ^4 e6 g8 {7 g6 k. t* g* j    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    , T& j$ I; a6 s/ E9 S' @: F, D; R}
    & u; Z  x* B# V6 V# E/ c4 l* F1 f* \要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    - e) }! z0 R$ t/ V
    7 z9 g& O6 @  e: X& I@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS( P9 y0 a. d  j5 M% \
    % {- P$ f2 e! a' W2 X4 H
    这个自定义Scope对应的解析类, Y' n. N6 a6 ]2 b- A

    / m; M/ v8 A9 ]5 s4 y' a' T4 M下面类中有几个无关的方法去掉了,可以忽略
    . w1 k0 z' s6 W" E5 J* ^- Q* S/ O; \+ Z
    package com.javacode2018.lesson002.demo18.test4;/ b) r* ]# j4 n% _' t

    / v( d9 _% W: _. b- Z2 X- w9 o+ W" {+ U3 J' ]2 y
    import org.springframework.beans.factory.ObjectFactory;
    9 u3 \8 e5 o  M4 ?% Cimport org.springframework.beans.factory.config.Scope;) c+ Y1 R* i5 ~
    import org.springframework.lang.Nullable;
    : ^/ ^9 J  G% W' m+ |9 `
    3 }6 Q; C+ D( nimport java.util.concurrent.ConcurrentHashMap;
    / o) A: D5 ^, K. H6 K6 ], X, D& M  E( I# x" x9 I& S
    public class BeanRefreshScope implements Scope {( v( _2 }0 q1 ~
    4 v/ w0 i6 P$ u2 K/ A7 n
        public static final String SCOPE_REFRESH = "refresh";, k. C1 X4 J& t5 m- m* ?* h
    8 d' Q/ r- \9 z0 C. T% i
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    - ^4 i6 t! N* q2 y' K! o2 t& J7 ?# q& H
        //来个map用来缓存bean
    % k, c5 v% i" G% I! M2 c    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    # E: C8 P7 |9 u- t6 L& j& Z
    5 q1 r! u! z# \  J    private BeanRefreshScope() {" G* b9 N* R9 Y
        }1 e$ L+ O2 R) @; A/ Z9 C( a

    ( M4 @; R, j2 `  q! z  u6 E    public static BeanRefreshScope getInstance() {! S- j$ m3 k9 o' Z9 C* O
            return INSTANCE;2 i' X" L! \, [1 g' ]/ |
        }2 k. l9 w! I% T' e2 B0 S" O: \
    ' ]; N" E& B1 ]4 {
        /**
    ; K# K9 C: W: t# U4 f, v7 D     * 清理当前
    + E* c$ _3 H. E( u     */
    / ]1 F! j/ g. v7 s0 @  Z5 x! R% G0 U    public static void clean() {" O( ]+ J# o" u) ?! C
            INSTANCE.beanMap.clear();
    ; w7 D6 O: |6 F1 r2 N    }
    + \" b2 C  A% u# e3 @, o! p6 D; A  C( [5 B9 t# q
        @Override/ L9 z) ~- T6 A7 |0 D1 v
        public Object get(String name, ObjectFactory<?> objectFactory) {9 }7 A4 E' c  F% y4 L: S
            Object bean = beanMap.get(name);. A1 b# A. ~# k8 ~; x, I
            if (bean == null) {
    $ `" X8 X# O6 U+ Y# E# z7 {  W% G            bean = objectFactory.getObject();9 }, q9 l6 U' q+ ?6 G' ]6 V" _$ B. D
                beanMap.put(name, bean);
    8 G* {8 m# \2 R, r        }
    0 v' h5 ~; J. _; ^* j: F: x4 B) n        return bean;9 i8 h! ?7 h6 D# y; F6 u6 o: o2 N
        }8 o: _" r- A2 e9 Z/ Y* L$ ^

    3 e, p! @5 C3 y& m! x+ Q# o}
    4 d& N" V8 \" _' K上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    . C4 s) ^- x4 |2 |% _" n7 u5 [3 a( @4 Y$ C2 k; v" ~
    上面的clean方法用来清理beanMap中当前已缓存的所有bean1 p: a  w* f. `6 p- X, ]

    ; g( Z% R# c6 O- {来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope- Z! y; ]# ^3 t/ X, B& u- v% `

    & O& b& u, X& [( }6 \, {" upackage com.javacode2018.lesson002.demo18.test4;7 x. }; W; K2 O" l
    - f6 A( s( g; |5 L: P- i8 F
    import org.springframework.beans.factory.annotation.Value;
    ! N: R. @! p% L" P" p7 bimport org.springframework.stereotype.Component;2 e- s3 d) |( Q5 J) d( C

    " X+ ]7 c( n8 f' V# s* c5 |, R9 ]/**
    8 @) _7 v  h- b+ \- v: F * 邮件配置信息( d8 V4 R) n! o/ j# Y
    */
    4 J# k; O, d6 t& a2 O@Component
    ; ~" H4 I# s# d8 j@RefreshScope //@1" y  R( ^5 F' _! {& ^) K1 g
    public class MailConfig {
    # P( X( f, _6 c- ?' V
    1 M; _: x; ^" N/ i2 J    @Value("${mail.username}") //@2& x1 {) ~8 `' E6 t3 k0 T
        private String username;
    ( _( ~( Z% z5 O* o# O- ]/ l1 k! A* W7 t2 T
        public String getUsername() {
    8 v( `, s7 |) ^5 }/ |. S# l        return username;
    $ d/ z" Z- V: m4 d2 ~    }8 i7 E- i& o& U$ a
    - F: C& n. ~$ r/ k9 s: w
        public void setUsername(String username) {
    ) s1 z. T. \) i5 c1 p9 G        this.username = username;$ [8 q6 e. r6 ]" ]3 i8 h0 i6 |! v
        }( D) Z6 b* h4 \9 t7 _, j
    * ^8 ]7 _  \5 Y6 X5 z
        @Override. J9 `& U9 A6 o. k5 f- ]0 k+ w; b
        public String toString() {; m0 \% z8 _0 Q
            return "MailConfig{" +
    4 p8 s8 e3 p) s                "username='" + username + '\'' +
    6 c8 [' k1 l( ?/ E/ ]1 G: `1 B                '}';) E2 [+ ~4 Q7 j% o4 f
        }
    ' S/ `3 {7 P, n1 }}: F# _/ H" l% Y+ a
    @1:使用了自定义的作用域@RefreshScope3 v# f: J$ \) K- m( W% ]# d( M

    : t0 B6 A$ s6 q2 ~+ f@2:通过@Value注入mail.username对一个的值
    1 K8 G+ e' D5 J+ @& m! N" ?5 l: o! C/ m" v
    重写了toString方法,一会测试时候可以看效果。, y  R  S, E9 u8 L8 v( ]
    ( |; R/ p* N, L4 X& O
    再来个普通的bean,内部会注入MailConfig2 V% y4 [6 x# |( R* C
    9 k' y$ k9 n9 X# D* C5 v
    package com.javacode2018.lesson002.demo18.test4;* V1 ~( e+ d. k1 A& {; y# _

    1 Y. w; G9 V. x4 ^* q( {) nimport org.springframework.beans.factory.annotation.Autowired;
      d& R7 V7 C! U  b5 A: Y9 b; gimport org.springframework.stereotype.Component;
    % @% `8 C7 ^! n+ R
    ! D' p' j; t+ ?1 f' C, T, \$ v@Component: c* e& l2 ?0 Y% S( B
    public class MailService {
    9 l6 T; B7 c& {# Q, L. p' [) |    @Autowired
    ; N2 k& a, B/ ]: Z" i    private MailConfig mailConfig;
    ( v4 X8 [8 f% W" a# P, t
    8 P  e. L' I7 v( y+ H    @Override
    - }1 l6 N# v8 V+ S$ Y" s0 R    public String toString() {% d$ [2 }2 \" }! ]/ A7 p
            return "MailService{" +" ?' S1 B2 i$ q7 j
                    "mailConfig=" + mailConfig +
    ( F: e1 L  X" B1 I                '}';5 {! b6 i, G% q  ]+ L# Q8 {
        }& O7 G: Z" P! ^4 z
    }6 y9 O9 b9 P  A) V! V. i( v
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    - L" o: O# l' i- _' d: o* z7 P7 y$ I( G' E5 m0 H; [& q0 a0 [
    来个类,用来从db中获取邮件配置信息! W; w, h1 k) g

    / p6 [5 `. t4 P$ ^package com.javacode2018.lesson002.demo18.test4;
    0 a8 d1 V: Q1 R+ V( y: D
    4 }" r; n. L1 D5 j; o) t4 rimport java.util.HashMap;/ E$ g8 E( Q. A  ]. c
    import java.util.Map;  w% ~4 Y0 ]( o
    import java.util.UUID;
    7 |! N$ J1 ?6 ?7 }3 c2 K4 l6 }& g1 |) m7 e  O
    public class DbUtil {4 q( t$ J6 k% _1 y* r7 U
        /**
    & A* h) z' G, ~) s4 t# ~     * 模拟从db中获取邮件配置信息& [% a( K; m0 J# D" e, K
         *
      f& R- k2 [# g! }1 L7 u  b- _     * @return
    0 E  n( h6 z3 a( c1 T; h' h     */
    ) x1 U  t0 j  q$ V/ Z! G! q    public static Map<String, Object> getMailInfoFromDb() {
    9 n& }, X9 j- }" ?        Map<String, Object> result = new HashMap<>();+ y7 L* b% p. ^2 y. ?  `0 l5 u
            result.put("mail.username", UUID.randomUUID().toString());
    0 A4 @7 S- r% _! n' y, n        return result;5 K$ W. M+ ^5 I' N
        }
    . G- B/ [% _3 h+ I3 |) r}
    ( }* p, t' b- I- T来个spring配置类,扫描加载上面的组件8 v1 d; P0 r2 n5 k; Z/ N' g# p
    5 ?1 h+ H# ~: @' ^2 f
    package com.javacode2018.lesson002.demo18.test4;
    7 d( G2 b3 d" g$ b4 |2 e8 c# q$ q" f5 L2 l
    import org.springframework.context.annotation.ComponentScan;
    ' q  E' d3 G  N3 I" V( U' Uimport org.springframework.context.annotation.Configuration;1 ^) r$ x2 N) r+ \2 `& O" k! H
    ( t3 @7 q) w; @1 t4 y
    @Configuration7 r% x! P/ m$ a3 L1 m3 w& J
    @ComponentScan
    $ W; G8 }* z# H8 fpublic class MainConfig4 {
    # ^; w/ m2 V% c! C) p- i" D}9 \  }  X9 B! P4 ~0 h7 Y' v: {2 H
    来个工具类
    - C* Q0 C( |& y
    8 a9 }% A  S6 T* K" \1 U8 a% T' L! P内部有2个方法,如下:1 K) R7 _6 d! D% M5 o2 |/ j% b$ G1 ^

      L$ j; T" I% C' h3 ?package com.javacode2018.lesson002.demo18.test4;; Z# P  l0 Q1 ]3 |
    & `" h2 m( b9 d1 P1 |) t
    import org.springframework.context.support.AbstractApplicationContext;
    % I, p/ p0 R5 l3 v9 ~: i% Z1 Y3 Iimport org.springframework.core.env.MapPropertySource;0 U/ t0 h7 i: K0 A
    3 J& X$ n4 D. }
    import java.util.Map;
    0 x: t. r8 p4 n" ^: }8 K- q; c0 |; L2 u" x0 S3 H6 u/ r
    public class RefreshConfigUtil {- ?$ x# t1 `) z7 |! o" n
        /**
    1 d8 [% f; e6 P- M! T     * 模拟改变数据库中都配置信息/ w* t( ~( X) ?
         */
    2 \% k, i+ Y' K5 ?- Q    public static void updateDbConfig(AbstractApplicationContext context) {
      b) I8 n$ O$ A/ V        //更新context中的mailPropertySource配置信息
    , G/ c# H) I% c( T7 u8 x8 d        refreshMailPropertySource(context);2 Y; w8 F7 R% `4 `" t. D
    : [+ Q& d* N" O4 t1 A( P. N7 k
            //清空BeanRefreshScope中所有bean的缓存
    # G& Q# X* c. u8 C8 Q        BeanRefreshScope.getInstance().clean();
    2 v- O7 o' q" F0 x( I# v1 W& f$ U    }- {/ J- E0 X$ L8 Z# ?/ F

    * O9 U% _! _6 A  ~; A% n7 ?- u    public static void refreshMailPropertySource(AbstractApplicationContext context) {" ~$ p  j( a4 I) ?
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();& \! W$ F* o2 i2 d
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& {2 B/ i( N* K! c1 z+ S* L
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);5 O5 s9 S* |, P
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    4 Y0 [( T; H9 a2 a! \+ d7 W    }: b7 F2 {0 b  m! W! T5 }/ B, |
    , b- O  g$ d7 t, z) l
    }
    2 T! i1 F0 L2 u3 C/ lupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
      _/ n  b4 O: v1 @3 w* h& @) _3 F! [, `: c+ {4 {+ q
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。+ ]7 C+ ~' a, z; H$ c

      _' E1 K! ?8 H$ t来个测试用例
    ; Z% A9 M' l  q0 d9 D, @# k* {: y* H$ g. ]
    @Test- ?0 h+ X2 m( Y/ d' q6 x
    public void test4() throws InterruptedException {" g+ T2 Q' m5 S
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      V3 Q4 ]: k% _, n( Q+ y    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    / h: G1 A6 p7 j/ I    context.register(MainConfig4.class);0 q$ r. q1 T. s, p: Y: I
        //刷新mail的配置到Environment, d% m5 X4 p. a5 N2 Y$ ?. D9 K
        RefreshConfigUtil.refreshMailPropertySource(context);
    5 w. e' ^0 e) F4 P: N$ |" S    context.refresh();5 T% |* ]$ H) Q8 W

    ( v. K* m: u* V' P7 _1 T    MailService mailService = context.getBean(MailService.class);
    , `5 l$ G, J- @$ i+ H5 F    System.out.println("配置未更新的情况下,输出3次");) X3 g! V4 I. y6 B- l! v
        for (int i = 0; i < 3; i++) { //@1
    7 Y9 X8 S* ], x; n# r        System.out.println(mailService);
    3 Q) e1 r8 e( B1 u' T        TimeUnit.MILLISECONDS.sleep(200);% G; B; u/ k- M
        }; V3 @2 A9 i0 j' Z3 Y$ f
    / y% O+ k$ [7 s. D; J
        System.out.println("模拟3次更新配置效果");
    7 n* r2 b, z5 b. l9 _& c    for (int i = 0; i < 3; i++) { //@2
    ! w6 g; P3 s7 q+ x/ C        RefreshConfigUtil.updateDbConfig(context); //@3
    - y9 {4 o- b/ _3 C        System.out.println(mailService);
    ) u/ z; v# g' W& |        TimeUnit.MILLISECONDS.sleep(200);
    - u1 e' Q$ @0 M4 d* _    }
    0 e) Z- T! B# c& o}0 `' }3 y! G! u' B: P" C6 d
    @1:循环3次,输出mailService的信息
    & k2 P* @2 r2 X- d* l
    5 N# J4 y* Y+ r' e@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息, F. z" }* R6 c' U2 x
    * b' \- s8 H3 \) _" R' a; Q$ D
    见证奇迹的时刻,来看效果6 _! y- k  ^6 \8 T2 _7 M6 T0 s

    3 q7 h1 q/ n; \+ E, \配置未更新的情况下,输出3次
    9 G' x1 Q  D1 x$ H5 }1 `MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    # O( b% p/ L; `5 _' IMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}: W2 A: c  k( b9 h/ _- g+ g( i& ~
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    , x* K& n3 _/ ?8 Q& z# o模拟3次更新配置效果% }$ g: \7 G3 b( h
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}2 q& F+ m- \! @1 r' g
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}$ m9 |) _) @. |1 {6 X6 ~; d+ t2 C
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}3 g' w) E# v' A; N4 w
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    0 i5 s# |" H8 y  f, f! d) a/ s0 C2 \
    ( G( z, ]3 B5 N- [! f小结
    * u8 B) M7 u' o2 Z) x: r1 S
    + @9 L( G; U9 ]" P) I动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    4 P9 q) F7 m) f8 l# t$ u- w* O
    * f3 |: {$ p* d0 C有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。3 O; t# _1 ~, B: f$ j5 a
    # P  |8 h7 }6 S8 T9 I. n
    总结
    ' D9 \' e( G$ U, W. j$ f1 ]1 z4 G0 ^" ^1 N! v$ r( J$ T
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    7 t% A* R$ O, o( y5 I
    0 c( f7 m9 c5 b  j& T案例源码
    # ^3 \6 w$ ]# F5 }4 j( Y8 n9 D
    $ {/ Z2 ?! W0 ^$ ?+ N3 h: H# mhttps://gitee.com/javacode2018/spring-series
    4 d' N# d8 A8 G0 H6 Y, [路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。  ~8 ?8 C3 T# @+ z( g

    2 r7 u: Y) H. B! a8 P; P1 R0 ISpring系列
      e- r; ]$ |; U  k4 b: o+ R& j8 T% y' n. t6 P; Q, b1 k
    Spring系列第1篇:为何要学spring?7 R  W8 d) U' Q) l. |

    7 \  I/ o; y7 G$ @Spring系列第2篇:控制反转(IoC)与依赖注入(DI)! N; N6 P6 }1 q: A6 d
    1 F1 _. x& y  A# G1 w( f: ~
    Spring系列第3篇:Spring容器基本使用及原理: E8 ?0 e& x7 i* e* @2 l; \* C$ @

    + W$ U; k6 K, r5 X: \: K" gSpring系列第4篇:xml中bean定义详解(-); ~7 P# Y9 W+ K0 g  g/ ^2 V

    . c1 e. r( H* l: ~4 d7 R4 TSpring系列第5篇:创建bean实例这些方式你们都知道?
    - |- q& l( f. L3 w. s3 `
    " s2 E- j  y6 ~* X8 @8 |Spring系列第6篇:玩转bean scope,避免跳坑里!6 B; r, O" Q3 z  J3 t. D9 b

    # N- y6 Q! D7 \7 ?" eSpring系列第7篇:依赖注入之手动注入
      v* z; A5 }( ?1 t' _4 g( ~; c+ P" ?' `1 w; a: l5 J) ^- _! ~
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持3 j. V# ^* }6 [
    ' A/ K' J* c! c' l: T
    Spring系列第9篇:depend-on到底是干什么的?
    7 f) k) F) ?8 Q- H) C5 z1 D7 J: B2 M- P
    Spring系列第10篇:primary可以解决什么问题?
    ( ]: [8 P0 `0 w, V% ^0 ?, j% W, P$ B5 {7 x  q& V: `
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?, {& Y, `! V0 x. O$ _- z

    0 R- S& j7 [' DSpring系列第12篇:lazy-init:bean延迟初始化" s2 `& Z3 y9 w% c2 Z( a

    ! x4 b- r$ N* x5 USpring系列第13篇:使用继承简化bean配置(abstract & parent)4 u& _6 t$ S, ~( m) `. q5 p
    2 T9 f( b4 A$ G" G0 h4 Z
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    ! _; J( b& g4 G8 A" p( p$ @9 S
    ! b) ?4 G3 g* g# y$ cSpring系列第15篇:代理详解(Java动态代理&cglib代理)?4 Z! V3 c6 w7 m! f% ?# E
    ) v+ f, I' K' V$ p5 k
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    + o: V& K$ e3 S! V/ F; ^
    ( b4 u3 n9 ?9 ], [- e5 r* k/ K$ OSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    . v; u  z, _! B
    / J8 x$ _; X8 o' F8 ~5 x) USpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    4 E8 ?$ x1 A6 y7 `8 K) t% g3 D/ Q5 Q( I
    Spring系列第18篇:@import详解(bean批量注册)
    & f8 u  X5 J1 [1 F
    2 w3 w% d8 N: a( LSpring系列第20篇:@Conditional通过条件来控制bean的注册
    8 n" v* j4 j) h7 E8 E. T
    % g5 s& `2 q$ F. R. Q4 l& WSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    0 T9 P, v4 d# w( Q2 i. s% \- \7 p! {) q* T$ G" x) \! @3 `& U
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
      j/ P! m8 h; R. p! z+ B& x
    $ z$ p4 E$ P* q: rSpring系列第23篇:Bean生命周期详解0 {3 V% Q3 H3 k1 m; B( _
    $ d8 p5 ?  e: K" ]5 s# i2 c% G
    Spring系列第24篇:父子容器详解
    " A8 R1 ^* |  Q4 N; i7 y* U% m" D- k; t0 e/ n$ |' m
    更多好文章5 P0 n; k# c3 `: Q

    / V- ^* a* D& L* c% e+ ]% b. _Java高并发系列(共34篇)" _) U& Q# v4 J, [' g

    4 Y2 N, ]( d9 }4 kMySql高手系列(共27篇)
    6 l$ u% p& A, m
    3 m6 T+ ~: |- Z, D5 hMaven高手系列(共10篇)
      y4 `$ C( @" Q1 q0 [3 t
    2 b  j3 S8 l( X" x  wMybatis系列(共12篇)
    + S' q5 t9 x( e2 k' S, P3 @) s! d9 `. v1 U4 E3 a
    聊聊db和缓存一致性常见的实现方式
    8 G* `9 o# H2 T: s, ^; q) n" }6 n7 u
    接口幂等性这么重要,它是什么?怎么实现?
    " u$ ~! A  ]. H: }8 o  i( k# ]+ }9 |0 ?" o
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!( K0 `) t; m. }& v8 n# T/ u
    ————————————————! _/ e! v" M5 _; v6 s; ]
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    0 o8 v) w* J/ M8 q. J+ J2 |原文链接:https://blog.csdn.net/likun557/article/details/1056487579 {" ^: U9 {) E  F

    + c! i5 I0 i/ O; [# M. N" B
    , U! N$ L9 F+ f2 D3 w
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-21 19:11 , Processed in 0.376489 second(s), 50 queries .

    回顶部