QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5127|回复: 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!
    " y, o8 J+ \& n4 p# v7 {疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    & H4 G( M7 P4 n# i1 \9 S( J8 F: H: e9 b
    面试官:Spring中的@Value用过么,介绍一下
    " A5 o& p4 o& F' i/ P4 W; I& _* `/ m9 c+ X& A8 s6 i
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中5 S) h1 P2 o) b2 M" x# }
    ' C4 q, r2 r. A& T7 V# }( V
    面试官:那就是说@Value的数据来源于配置文件了?
    8 O0 Y& R; g8 O; B
    / E/ Z$ n" W8 ^2 s6 T我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置8 ~& J4 K* ^/ m; X& L9 m

    4 j9 a0 `  B9 R6 [面试官:@Value数据来源还有其他方式么?+ @2 O7 s. F$ [2 P' D6 ^
    0 g( G5 O. _" D( m% _
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    2 G. V& U- o, z4 D7 k/ a1 I2 @2 y- ?9 y: S" c7 m
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    7 _+ w8 V6 a2 y6 h7 s  |2 F  J" d. E& j; I. [
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧/ p& [/ a; ^. c" O4 `

    5 X/ }: Q0 J, s' S. L& G7 S面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?$ L3 b1 C4 k, }1 }1 l. J7 M  I5 |
    ! R/ H! w9 m# o6 E# d
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    - T$ Z* c. U& ]" m6 t+ L+ J% B4 m4 o, J+ S
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    8 M6 w7 |$ v3 I1 t+ g# F
    6 i1 k+ X; g  s* H/ q  T我:嗯。。。这个之前看过一点,不过没有看懂
    - N# W8 K% o$ v; P5 w' Z7 [# e+ G( l
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?# n) A- u% H" {' }, s( Q8 R
    8 L. [! v4 B( {
    我:3万吧
    # ~1 H! \  c3 N& f2 Y
    $ S3 Y# F; a. o% t1 U$ ?6 I2 ?面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    - {) p. G4 x  g0 _
    2 M) @0 R5 y3 \# r! P我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    9 o8 x$ P, n4 {
    0 ~4 @7 f$ c, T" q1 Q面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    # P" j' V  B$ v/ f+ L. A! x
    5 x! W3 [: K/ ]我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。7 A. G# j" D1 e0 d8 W1 c

    " V0 G8 u7 h, ?这次面试问题如下
    & L. |$ A7 n' ]: X. a1 o9 y7 c/ Y0 Y4 a. {! x7 j
    @Value的用法
    # ]9 g4 R5 g  V& d6 F# H; I3 Y- c+ `# V& z
    @Value数据来源
    0 h, {' Z3 i2 N/ I+ q
    + _. [$ V. s# ]@Value动态刷新的问题# ?4 r) p# P& g  h( q& p
    8 U" C) P$ K+ j7 N: @7 q
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。' f. P/ E, E0 j& U2 p1 K( s

    ) ]3 h; j/ q7 [" ]4 U) [5 W@Value的用法& V+ j; t& |9 W( X

    ( _! b# x2 |8 ~% Y系统中需要连接db,连接db有很多配置信息。
    - f) v* \, i1 H( v4 Z0 j/ Z# q, ~
    1 o+ E. G* M4 |1 p2 S2 P系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    ) j- t$ r' G8 o' @( ?+ T3 X/ k" t4 J3 J. _( w+ D
    还有其他的一些配置信息。
    2 b7 u% ]& N+ G0 @8 ^4 N. n; {& {& X" @9 ?+ n
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。* M4 l; }% f4 M$ k5 u

    . G/ k5 \# w) x8 W. {那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    " w$ w7 I% L' p+ O6 b' }: `
    3 p! B; |7 ~# s通常我们会将配置信息以key=value的形式存储在properties配置文件中。( C5 e! R+ H% b+ ]- s0 @

    * l  i; N( _* G* R( O  M3 b通过@Value("${配置文件中的key}")来引用指定的key对应的value。( T# w2 _$ a4 N' G

    9 f' D: `# s2 _9 c@Value使用步骤& u' s9 b! ]9 u+ y

    & u4 ^. t9 b$ V; D5 ?/ ?. \步骤一:使用@PropertySource注解引入配置文件: {# y/ v2 j$ m9 [
    ' e7 p* c# {* M6 m. ~
    将@PropertySource放在类上面,如下- u4 N2 l! ]+ O; z" w3 G: ~
    " C+ \! N  k7 J
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    / k+ p& }% {( b( u@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    # w6 u7 O7 M+ c6 k0 w8 S
    4 J5 ^* t2 q) p# `如:
    6 u8 s" P& W9 G: o( J4 l& a7 a0 K4 H6 y3 W' L, a1 o
    @Component
    2 M1 U2 c* S9 o6 q3 o& ^4 s$ x@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    " S) [, T# t4 ]( Rpublic class DbConfig {0 h1 v4 D5 A8 S1 ^0 p
    }& Z. w2 m0 l0 y2 {1 m% K& I9 D
    步骤二:使用@Value注解引用配置文件的值
    , ]/ `9 O8 E1 o% _% u# ~& T8 S+ W: k: M
    通过@Value引用上面配置文件中的值:
    2 G, Q7 c0 p, h1 v
    0 @$ n7 A% U3 s! s- I: f. ^语法1 J6 z" x6 u+ l+ j' A3 E
    # c9 q; q) Q2 m& z* s3 Q4 J
    @Value("${配置文件中的key:默认值}")& d7 e. _( q: a% U4 j
    @Value("${配置文件中的key}")
    # G$ i4 X! Y6 v. t5 K4 B" {& |, r如:
    7 r& T  a' {$ o: a# ]% f0 f1 G: x) T' m3 \% S4 T( [6 v
    @Value("${password:123}")
    - H" K' a: r2 z( t) o$ W7 L) t3 j上面如果password不存在,将123作为值; s7 B* _) f, K* b
    & s( J& _3 A$ v, U
    @Value("${password}")
    ! m* l9 ]0 P7 U  n9 ^上面如果password不存在,值为${password}
    + b' ~$ N! z6 u
    * x+ _1 w% i' R( A假如配置文件如下3 `, u+ L& t( k, D7 r! r

    : S. f1 |- A( V8 {9 tjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-86 i0 x+ z% O# ?0 |6 I7 c6 o# K
    jdbc.username=javacode& }7 s" i7 V9 z- b1 N' l
    jdbc.password=javacode' f2 E: c8 x5 Y: N
    使用方式如下:
    - i" v" R2 B: e' E" @$ \
    . n8 c# a9 N$ U, \7 U) A@Value("${jdbc.url}")9 s1 T5 z1 V: U; z8 x) L9 }
    private String url;* e7 d+ J( T3 W6 D" I9 s

    7 v+ j5 c/ U: {4 @! m1 V@Value("${jdbc.username}")
    6 g; ]! S; Y/ D* B" |1 o0 M( Lprivate String username;
    : M: I  S( c- G/ A* S( c3 w: D; j5 {9 y# W# ^9 ^
    @Value("${jdbc.password}")9 q) s* b# [$ @
    private String password;
    % B1 c& b0 K2 ]1 U2 ^' o5 u下面来看案例
    - `) U) o, y: y5 e5 G! Z' _5 x8 ]2 d) s' X, n. B4 {
    案例
    . f) w; z0 `2 ]1 w0 w8 c1 V8 z- ?) l$ x
    来个配置文件db.properties- ^+ C4 M, B5 z9 u* {
    , x( b, v) S$ @( }
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    3 [. f2 _5 T) _( G+ H6 ojdbc.username=javacode
    ! Z$ M- q/ Z' f+ |jdbc.password=javacode
    , H: l. G$ r4 ~  k来个配置类,使用@PropertySource引入上面的配置文件! l* _/ s  o7 b# B7 b- w; H) a
    - {7 I- U, [6 m4 s5 W1 s
    package com.javacode2018.lesson002.demo18.test1;
    - T: c$ O% z$ b$ V" S/ p
    # Z' v& g, N4 H* v6 O" }import org.springframework.beans.factory.annotation.Configurable;
    / n- U# W9 f$ j( {# `import org.springframework.context.annotation.ComponentScan;
    8 q) ?$ P+ ?) p' F9 Iimport org.springframework.context.annotation.PropertySource;, V- M" j3 ]: Q1 C% S! g- K
    ' H3 t; a2 ^. X1 N5 _5 U" l& c
    @Configurable: I% {- @5 o, O
    @ComponentScan6 @# ^4 x5 y5 I9 d& T; Q" p
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    1 ]2 {& i/ Y, Z7 B; ~public class MainConfig1 {
      p7 W+ @; K7 I- v" E! R+ e% p& X7 \}! S; E6 T/ O9 f! n3 ^9 o
    来个类,使用@Value来使用配置文件中的信息
    0 F/ O+ W( W+ G, Y
    $ U8 J3 K6 `" dpackage com.javacode2018.lesson002.demo18.test1;
    3 v) p6 _2 k+ n& U" u; Z' Z, Z% ?2 E- ?. Q# j
    import org.springframework.beans.factory.annotation.Value;$ r# `% g' P( \8 E% v: W4 O5 F
    import org.springframework.stereotype.Component;
    ; M' A( B& a6 n& v# g) X. `$ [4 ^! ^( M5 v* S* ^
    @Component
    ( X3 n% m2 f: y; Z( }8 v$ t1 M  lpublic class DbConfig {
    % u0 y6 X$ L" o3 N, B9 M
    ) H9 T6 W, p4 p1 U2 F, X    @Value("${jdbc.url}")1 [* U: |6 X6 [6 ~
        private String url;  s2 |% f* D& Z8 y; u

    # @5 d. n) B3 s- b* a! W    @Value("${jdbc.username}")6 e% }, J/ I) e0 ?% w. F
        private String username;
    ; {- L0 S  L; o1 U- R: ]) s% O, H, H. @) p
        @Value("${jdbc.password}")
    ( g/ j/ Q2 g: B9 X% L4 V    private String password;, |* i* a! d+ q( D* l
    . D. @, `. L" K
        public String getUrl() {+ A; V6 R) I5 `
            return url;
    2 j/ E2 _+ I- T. u/ [    }: o. I; \6 K/ n  _% ]8 W
    " K) g. S- Y+ ]. Z: u  z
        public void setUrl(String url) {
    ) z5 Y  L  A5 H7 |$ Z        this.url = url;7 i7 C0 Q$ {* v+ }0 C- `; ^
        }
    - h) C: R. t. c1 R5 Z9 g# ?' H2 q1 X6 Y2 a1 k/ K# j, B" `
        public String getUsername() {
    . s' N& |. W# Y" R$ W5 D* f        return username;
    0 o, T0 B( w, X1 z* ^' S) I2 o+ \& b    }
    : O9 X: ^' ~8 @; [3 L& c6 I# s: \: x6 ]5 q+ D7 [
        public void setUsername(String username) {
    1 r+ d  w1 |% E* s! v        this.username = username;- E8 E0 `9 W0 y
        }' C9 Q2 ?' Q( L( I4 W
    + O4 r( U9 h! X; [" v7 a$ ?
        public String getPassword() {' {  ~  A! ?7 m6 L6 @: u
            return password;: O% n4 r3 e( V  W
        }% u" h# J; l& k! N( U' m
    - k; d% C" R2 N7 `8 o- b
        public void setPassword(String password) {
    0 |: l3 @& M% w: g6 |' X        this.password = password;
    ! x! ~! `+ t7 O! q    }
    + l( B1 S6 b1 s2 @9 [' R
    7 F# R& c) I! a5 @3 M    @Override
    2 [' `7 s$ H; J- L8 O+ N) C7 i; c    public String toString() {
    ; X) j+ u5 x- K2 m. ?        return "DbConfig{" +3 Y- r, K5 k+ [1 z; F) |
                    "url='" + url + '\'' +3 ?! E2 W' ^/ d# [9 j
                    ", username='" + username + '\'' +
    4 T( m+ R! h9 R                ", password='" + password + '\'' +' E3 d% ]: t( ~& ^" j1 I5 ]" d  `& _
                    '}';
    - r4 _8 \, g/ w, ~' }    }7 b5 b  ^- ^* }2 _6 y* g- \
    }! J* J' y+ d$ V1 [3 M  U1 S- N: I
    上面重点在于注解@Value注解,注意@Value注解中的
    8 V9 D+ q$ F8 I5 H
    ' A+ y3 ]  @1 m# o( [% N来个测试用例5 F& d8 F  o) P/ k. ~, O$ w: g
    $ \5 a) `' }" Z( B2 K7 O& j/ A
    package com.javacode2018.lesson002.demo18;  T' a; m7 F) [5 F. B
    : C$ |" e# W+ q$ G2 g5 c
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    1 R5 G, p* A5 o6 A, i% W1 ~4 iimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ; p4 K( E' g9 s+ }& @# X) \import org.junit.Test;
    8 e) O& X' a' k! Fimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    7 k5 d  A0 i: M! i3 V. q0 L0 \: l8 M3 `) J
    public class ValueTest {
    7 y+ b' y8 {2 N6 j. j8 Z5 Z
    ' {# y# s; E1 A) w, n" E8 m    @Test
    % \8 d3 ~* W7 |) Y  A    public void test1() {0 j) G# d9 I, s* I* w/ P
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();/ r& W7 ]3 p8 g# N& @
            context.register(MainConfig1.class);3 k3 N, ]1 C/ F: I+ e5 o% Z2 e' b
            context.refresh();
    & X5 d0 |0 f" |% u- t6 i% ~) ~- p4 b, Y6 W, d/ {: N
            DbConfig dbConfig = context.getBean(DbConfig.class);
    # [( U  E% L; Y2 I- t! O! J7 [        System.out.println(dbConfig);2 R/ ^0 ~* F  N/ T+ P$ |
        }
    . k$ r- X6 k( l# N6 P}
    / j# l; R- P; o. Z+ ]  t/ Q运行输出% t9 ~- p$ u8 n$ h; m/ K# O9 t

    & p5 {: s9 d( B6 G- t5 i. r  eDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}# [8 {( Q+ j, U1 d  |
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。  M5 w0 a* u4 U4 w$ @- v* o+ F

    1 G! q6 `8 T# j& H! S@Value数据来源2 b6 \* v$ T% L

    ; x" O' A) E; y' b: d6 ?( {通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。4 q# Q5 N) B0 @
      c- J8 C' j9 f
    我们需要先了解一下@Value中数据来源于spring的什么地方。  Q. B; d& M6 @1 f# B% F; q5 H3 O

    # m& E* f  X1 r( R' ^* kspring中有个类
    + A, `7 M3 A, s% v, [* d3 P9 ?7 Z3 B+ X" \7 {
    org.springframework.core.env.PropertySource) N; i! j2 Y" S- O
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息* z2 l1 a/ h0 j. l" @+ X4 q
    / P! E, [. r, C; ~
    内部有个方法:
    # O! u4 _, ?& a, W
    . \: M1 k& y; \* D0 y; S: i* }public abstract Object getProperty(String name);
    ; X3 x6 J' f1 |( b2 y5 U2 I4 W通过name获取对应的配置信息。9 e" U1 h8 p% \! ]

    6 N  z7 m! I& s" V) Y/ r- P3 X. U系统有个比较重要的接口" t7 J5 w$ ?4 |$ I5 X
    ; d, T" @  f! j) X! g
    org.springframework.core.env.Environment& n* @! z' O3 ?# B
    用来表示环境配置信息,这个接口有几个方法比较重要
    ; O0 k7 x% u' D" {
    2 Z1 O" L  G1 U  k8 m! tString resolvePlaceholders(String text);
    + E* h3 j* A' P* J. N  m/ HMutablePropertySources getPropertySources();
    ) l' [1 z4 Y1 X* k0 i% t3 i9 A. presolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
      H. h3 }# B- l) i, G: p7 F9 y
    / C7 F" }# k* ?6 m0 R" zgetPropertySources返回MutablePropertySources对象,来看一下这个类
    4 J# L. u! D1 X) D3 b3 v0 g5 s$ |, P( _1 W% P
    public class MutablePropertySources implements PropertySources {
    $ {  t4 S0 X3 m" L! m& \) W, |; l0 M! {8 s, m; ]& e! ~, Y8 ^% e, v; `
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    / f9 x5 ?9 x( \. J4 `' d4 K5 G* I( ?& b3 {9 m4 j
    }8 x. w) i$ y0 C3 i
    内部包含一个propertySourceList列表。
    / P- D3 z) c) a5 y! D8 q, J5 h
    $ ~, T9 _' `/ K( v% Y/ Jspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    5 o) R3 z- s: h+ D* d/ t0 h: [' r* e" G/ \% ]
    大家可以捋一下,最终解析@Value的过程:
    ; a8 R* }- Q+ ~8 Q% b& t
    ; J3 Y$ b8 V8 f. r2 G. ^! b. c1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析3 `8 G/ f+ {! y: l2 v5 d, u9 T6 Z8 _' U
    2. Environment内部会访问MutablePropertySources来解析1 P6 a1 F: A: s! \# ^3 P% ~
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    # c1 `& H9 K  i0 I通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    # C/ N  S+ G# }/ e. @( h4 k: G. L
    ; }" j1 w. `! X, _  h- k下面我们就按照这个思路来一个。1 _" h  i4 {; ?% M5 ?) a

    + e1 c6 Q- C' t3 i- n. _来个邮件配置信息类,内部使用@Value注入邮件配置信息5 x+ V0 c8 \/ Q0 P

    * n; m' L  B5 X6 B' l6 apackage com.javacode2018.lesson002.demo18.test2;
    7 t$ w, D3 p# q& ]5 j' U, j: J) [. x3 h1 N9 v5 y& N
    import org.springframework.beans.factory.annotation.Value;5 V5 ?7 k* e! w) A
    import org.springframework.stereotype.Component;, a8 X% X1 X( @0 C! z1 @. F
    & y$ \5 `& `" Q& m& ~
    /**, x3 M1 u7 T+ C+ u
    * 邮件配置信息
    - Z3 C, L0 ]% f7 h: r* u7 x/ Y */
    0 b  y0 {$ Z! ]" y" e. b@Component
      p) A' B( q8 P' l0 m2 Zpublic class MailConfig {
    ; `8 s; g% X8 B% B! J2 S2 x7 @
    : Q+ X( g. \) B    @Value("${mail.host}")
    * n" L; B; S5 X) s8 ?5 a    private String host;" o2 f* a8 q8 B* \. n! Z& d
    ) f4 `: ^& T( Z" _( }( t
        @Value("${mail.username}")
    1 {2 \5 k8 k- `  i    private String username;
    9 }6 R# s) o1 o4 J( m% W9 \, ]0 F) h+ e+ ?  ]% H
        @Value("${mail.password}")
    , E( ]) Z( c8 X- V  g: S- t& r; e. t    private String password;8 o6 L7 @' B/ J: D6 Z3 {; f

    5 \/ X, H$ n5 A- K; ]9 a* H    public String getHost() {9 c' s1 t6 Z/ F7 Z, Z
            return host;
    5 U1 G3 j& t: e1 h5 m  T    }
    + }) m) C9 b8 q. J% V% t. U5 z1 J4 s6 |  T- N7 q+ w8 b7 d
        public void setHost(String host) {; p4 |; r4 J$ h4 V! o
            this.host = host;
    0 Q, J0 ?; s8 a" ]5 Y  E    }' f# V+ X5 E& O" L& W, a: k
    " v2 v$ U  C0 @& a9 O
        public String getUsername() {
    8 k5 G7 E: n# Z0 S$ q8 E        return username;/ g# m, u. S2 p* V! Y3 F
        }
    # \* ~, z0 w6 Z8 E; C% Y; {
    0 D- y% \( {7 D    public void setUsername(String username) {1 V9 J# A0 m) E
            this.username = username;+ n" i9 Y, Q. y1 p) q
        }: J' L/ P/ \8 r2 \8 H* m$ }6 F

    ! Z$ n& U0 R4 |' j& ^4 z    public String getPassword() {% ^! i9 ?: h7 F" P. {
            return password;( C. x( K. Z7 i
        }
    ! h9 k# K! J6 \2 S' M2 p
    : Q6 Q/ f3 U8 M7 a! \    public void setPassword(String password) {8 {9 W" i% ~, n# r* m. z* s' l
            this.password = password;
    ) R- D8 e+ \3 e    }
    0 Z1 |' v3 B9 ^$ C6 A7 s
    $ m" e! {0 x) r    @Override  o" I  {4 |6 ]2 s) Y
        public String toString() {. J6 X- {6 ^" S+ g$ A
            return "MailConfig{" +
    . A- J9 \1 E/ A0 S4 S8 e                "host='" + host + '\'' +# |; c  K8 O. j, D
                    ", username='" + username + '\'' +# I3 ^8 Y( Z# q4 a$ H% r9 z# p: a
                    ", password='" + password + '\'' +
    + ^8 o( U* R" H3 A! x$ d; K3 ]                '}';; _/ m0 Q8 \7 N1 f% i3 q, s. R* `
        }& V* r; q" J; }
    }
    * Q3 n9 ]! @- N$ a再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中" p7 ~$ w( ?! a* U
    - P- z$ l  p& c" S* A/ v2 N% `& ~
    package com.javacode2018.lesson002.demo18.test2;: w6 S1 U9 T1 D% q/ I
    7 G! ~: q$ V- F$ `
    import java.util.HashMap;# H" b+ j+ }3 d+ J
    import java.util.Map;
    2 i+ d, g6 z' w# r9 g7 n
    , f6 h; D# Z! q! j/ bpublic class DbUtil {5 K" G/ F  a* k# E( U+ b
        /**0 y( G) [( P2 G0 `
         * 模拟从db中获取邮件配置信息5 |5 u, l" @3 d' D2 T. J
         *
    2 S' i( j5 u4 H     * @return: F7 G& U# X- ^
         */) v7 N3 H% N+ J/ ?0 v
        public static Map<String, Object> getMailInfoFromDb() {" Q0 @# i7 A$ D: I/ j- G3 ?- ]5 O
            Map<String, Object> result = new HashMap<>();
    9 U! s/ V/ E/ g. q+ P" ~        result.put("mail.host", "smtp.qq.com");
    6 B9 e; H" S: {  u; ?0 Z, J        result.put("mail.username", "路人");
    : q# [3 }. i6 r        result.put("mail.password", "123");
      ?: r' @! H. \        return result;
    ) c( p6 }& ?' ?% V5 l    }) A8 |% _) N  j4 K# `$ v7 y3 A
    }
    : R( _5 X7 v. a, Z$ I% f3 o来个spring配置类
    8 t! M! _+ O; e  I& x
    2 B1 _8 H) o) d+ h6 N; Y# D, spackage com.javacode2018.lesson002.demo18.test2;# J$ H4 v  Y+ S" w

    5 Z' K6 L& ]! L# himport org.springframework.context.annotation.ComponentScan;9 a) R4 S9 P) a, N/ D7 ?
    import org.springframework.context.annotation.Configuration;
    $ O& q( [+ o' @' ^/ s. |" H
    % v1 u$ B+ |: U6 c0 H& j@Configuration  z& X$ o# }; s; [8 D
    @ComponentScan
    $ c( s  \- @6 j- j6 h& Qpublic class MainConfig2 {
    - r8 \* k8 H0 M}5 r2 }) |8 i( c5 o* t6 ~
    下面是重点代码
    + f, y. |3 V7 g$ L8 A( e) d4 D: `6 ], j9 j; f. w. R2 r3 E
    @Test
    % w$ O7 C0 B3 ^public void test2() {: h& c$ l* M; R" y2 z
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();: [, B! k7 u9 o5 }! C
    * U' {2 U8 m+ ]; E6 Z$ `8 v
        /*下面这段是关键 start*/
    ' A0 Z  G( R) c& o, s! H- v$ u! k7 C% D    //模拟从db中获取配置信息
    % f& y/ o6 ?, |) _9 C    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();- u# F& J. K* P  N& i/ f
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    - v3 p' u) _1 I, P) l    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    4 r& @! O! H8 s, W9 d) H1 C! C5 J    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高3 y1 c7 ~& {& Y5 O; s
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);- `/ b0 Q6 N, r$ A+ s+ B' c
        /*上面这段是关键 end*/2 y0 O' T  @4 k. w9 O1 q, u2 V( E2 U

    ( w0 c* y) W( B    context.register(MainConfig2.class);2 c) R  e& E5 ~
        context.refresh();
    ' V5 Y* ?! M& @. W9 m4 C, N+ M    MailConfig mailConfig = context.getBean(MailConfig.class);
    : |9 ?4 O) t$ Y7 i; ~% C    System.out.println(mailConfig);# j1 @% B+ r% Q
    }$ W9 a" \1 c8 ]) P  [7 P
    注释比较详细,就不详细解释了。
    0 W& s. m6 |0 k* }
    4 ?- Q3 J. p5 J直接运行,看效果1 [; @6 Q* T" `/ R
    $ C0 B6 a+ ^5 ~; R1 V- _" m
    MailConfig{host='smtp.qq.com', username='路人', password='123'}1 }8 U3 J: v3 a1 n( Z
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。4 ~/ A/ l. v5 c! U

    . W8 h/ \4 r9 t+ O# `2 g8 ^: j上面重点是下面这段代码,大家需要理解
    + y# P* H% u3 Q+ b! `3 y0 p# p6 ^% l4 r" y- j/ M- G
    /*下面这段是关键 start*/
    7 w. `7 i5 V# g) l//模拟从db中获取配置信息0 z* M6 `* k0 a  n7 V
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();: i7 g6 F6 f- I. M7 e7 q
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)  f( H  O2 I8 g
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    / f& h- F! ^. H! c& |3 M//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    7 X, I% F8 h/ D! y* kcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);  u) R6 e/ O. [+ W5 i
    /*上面这段是关键 end*/
    * N! E# y, u' g; m6 V咱们继续看下一个问题
    ; o# T9 @) d1 b
    8 ^: q( P5 L- l3 ?. F' b如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    " g, U4 u- w3 O* n' }$ ?' x9 v0 f2 j5 x6 W
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    : ]0 F. n+ N& ?5 S9 M' e4 F
    1 M4 ]' M/ Z4 n$ B7 ~实现@Value动态刷新4 d9 ^9 w. ~+ d% Y! Q
    4 Q* c6 N4 [- o6 O" c1 U2 c
    先了解一个知识点
    0 G3 a# v2 S7 ^7 V+ X) N! J. G8 d6 o# \" S
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    * Q# A1 x& V$ v4 Q( s# R/ P- Z9 A. y0 j) @
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
      M/ o' q4 F8 R3 o  C
    * H7 h" {3 H$ x! v/ {bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:$ D+ V2 V' f0 {$ {

    6 h! ?' }' X4 y( N% V4 gScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    ; C3 g* V) b6 Z" m/ u这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中0 c, O! [- i# B6 o2 v" q; C
    * @- E+ y/ W% a1 E* K# M
    public enum ScopedProxyMode {3 u+ O' K" k( p
        DEFAULT,
    2 e  g* Q) d5 G    NO,
    7 P2 a& b% y- h    INTERFACES," \5 ]4 \0 r$ z
        TARGET_CLASS;
    - ?- A3 ^  ]. b4 r5 i7 [# ?}
    # _' V: ]# }* v% G" @- _$ i* v前面3个,不讲了,直接讲最后一个值是干什么的。
    " h5 \- k# ^5 G& G2 p9 n3 H- r0 K% y
    ) R3 \3 A0 U; r% {4 T0 s& a. R当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。" b* `% [( D. m

    7 w7 }  c$ G5 K" s2 w1 a( o* L! A. u理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。6 d) h/ i7 @7 u8 Z5 ]1 S

    3 h& @% B1 N( D5 V: u% r自定义一个bean作用域的注解- f  H( C. a# a% Q
    9 }. I- e# G+ S7 N7 E
    package com.javacode2018.lesson002.demo18.test3;
    7 H: M2 ^% g8 \
    : e" x  \1 }: K. {( J0 ?& g& d2 C. t3 Bimport org.springframework.context.annotation.Scope;" g8 o5 y8 T4 y6 ^, G7 a, W  s
    import org.springframework.context.annotation.ScopedProxyMode;+ |  j1 U! ~, v5 ?

    . I) Q. k6 V, {- J" I* Simport java.lang.annotation.*;
    * P( J  @* Y: J* ]! U8 h
    ! V! p/ k9 S) D- i/ F' ], \@Target({ElementType.TYPE, ElementType.METHOD})
    6 l% [4 e/ M; }) x( Z: M' X@Retention(RetentionPolicy.RUNTIME)! g" c5 D" W5 T0 Y5 h$ p) x
    @Documented* U1 S! ?" ~  ]8 v, W  _& d
    @Scope(BeanMyScope.SCOPE_MY) //@1  K  @* H4 [6 T5 C
    public @interface MyScope {5 B  Q: }" s8 _- f7 B+ _, w/ _
        /**  l) J5 P. v3 p1 a+ @9 y
         * @see Scope#proxyMode()' h* W: T$ d3 @$ X
         */+ j$ U0 e$ z) q/ N  g
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    . T* b* u! @! c& Z1 ^$ W}
    - c  T4 K8 e+ ~6 v@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ; m# [$ w' j$ P- }3 O) h) z" r( e" _: V4 ~: t9 Q" h- F
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    2 @8 }- o0 [5 m( @& Y$ z3 l* V
    # w5 ^6 U$ B! {) A@MyScope注解对应的Scope实现如下
    , ^: k+ P; N9 O. u, z  \9 B
    1 ]! Z# e- D& r; }package com.javacode2018.lesson002.demo18.test3;  b" T1 l. Q, S

    $ m4 Y4 B5 B4 }5 R: j. t; q0 `. k) |import org.springframework.beans.factory.ObjectFactory;
    9 K4 H- |! n1 D. C" {% Z" pimport org.springframework.beans.factory.config.Scope;
    7 g' V* k. [  w/ \$ b& fimport org.springframework.lang.Nullable;
    9 _2 l2 @7 W6 A5 J% j; F2 W
    . E, E$ O4 V: D8 J& a9 s9 ?9 }* M/**9 r) w% f) d. _3 k
    * @see MyScope 作用域的实现
    3 F9 H2 p4 C$ \4 l */
    0 Z7 Y5 a0 w2 N& f3 epublic class BeanMyScope implements Scope {
    ) a* O" m2 L  _5 ?
    ( S4 W; S. y3 G8 Z    public static final String SCOPE_MY = "my"; //@1
    6 h3 n) J- B7 x: D. e8 w7 \: E
        @Override
    : O* c* B8 R: v    public Object get(String name, ObjectFactory<?> objectFactory) {
    ! K; l! V5 r2 _, a9 z1 v, l        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2( {! M2 H# ^6 j9 W+ @, V! Y5 W) ]
            return objectFactory.getObject(); //@3* s. B4 M6 \9 |3 D
        }
    ; r4 f- |0 x  w! p5 B
    ' a: w8 Z  ]1 `3 B% ]0 b    @Nullable$ [5 q" x% Q% Q: Q$ C
        @Override" t% Z) w5 b3 v9 U  o$ X
        public Object remove(String name) {
    % [, Y4 w7 Q. a* s2 X# k        return null;- n$ M! D/ ?# _1 v* S! K
        }
      c- {. h1 q* a) P, G* O- e
    ! f' Y6 r5 u4 ]/ q: X. b. {0 x    @Override+ B  V2 A; T1 T
        public void registerDestructionCallback(String name, Runnable callback) {) P2 S/ x2 B+ h. j, o' C

    3 B) W, s8 I+ e( O2 s& V) `    }
    . m9 y9 I; L; D/ Q/ r( t6 J! V2 W. @, z3 B( o1 i2 u
        @Nullable  |' X9 w9 ?! _' t. l+ j5 I
        @Override8 |, ?" _1 e. Y0 q6 W, k
        public Object resolveContextualObject(String key) {
    ' ]0 e6 s- N# e# ?- n' @4 Z: Q        return null;( e% v( N4 p# w9 v
        }
    + Q$ w$ `3 N! S
    * X* a2 g4 T6 [7 a) n1 Q    @Nullable" m2 F0 z& W/ R3 m, [( c; E
        @Override
    ) l+ k3 k% H9 B    public String getConversationId() {
    3 ]# P9 w$ m; K* s8 y9 w        return null;
    - A+ t: @+ l* f* E    }: Z, L' p! z' t2 T; T
    }# _' E! m7 m7 {
    @1:定义了一个常量,作为作用域的值
    9 Q( V3 e& w6 l6 x4 V; v" ^, G4 X) S5 a* [: P8 E
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果4 U6 U3 x) {+ w1 v$ d4 P% N% l- Q

    / J* A8 L6 ^' j7 M@3:通过objectFactory.getObject()获取bean实例返回。" Y/ y1 }3 |' [; J. v  ~, c

    * [$ Z: Y2 J5 H5 g4 t下面来创建个类,作用域为上面自定义的作用域5 s3 E: @1 h7 j2 Q5 n1 P
    , p/ H2 q1 O% `! h) }
    package com.javacode2018.lesson002.demo18.test3;
    1 @, K* V: D: E! M0 k' ]; R
    % l- H: w& V/ c- Zimport org.springframework.stereotype.Component;% C5 b& M0 c2 i/ n# p0 ^: ^
    " s. k: v3 b8 `/ ^9 ~' L6 v) r
    import java.util.UUID;& D5 G( k8 b/ y# V$ ?$ @" A' M& P
    - w: e. t$ f( B6 `- i3 q
    @Component6 S7 X6 Z4 x" w  N
    @MyScope //@1
    ' _* }9 q% ?/ w) y# l+ l" Tpublic class User {! L+ S/ e4 _5 u: C

    7 N; ~) L+ R& Y: Q" S  n/ K    private String username;
    - g3 s/ c0 ?( s/ v) i9 ]
    / ]  K1 \- ~+ y& Y4 N. H    public User() { 2 s# ], f/ J& ?4 y- }
            System.out.println("---------创建User对象" + this); //@2
    - w$ D7 T5 C& B        this.username = UUID.randomUUID().toString(); //@3
    8 D$ `! P7 l" M    }3 X8 M( G+ o# f5 r' i/ {, ^; H

    7 ~# }( ]) K) M6 `& f    public String getUsername() {2 D/ E$ a& \, Q0 r
            return username;
    . A, {4 F- d3 @: u    }. F7 q# N0 n  O! N4 C8 u; ]' o% Z

    8 B+ {* G& i1 H: c) E* k$ [    public void setUsername(String username) {
    6 k/ ]) k  H3 Z1 p2 g. ?        this.username = username;8 K2 a/ S6 U4 K; g: m
        }; {# @4 F1 l: I1 R& N. W
    ! e, L- p# {7 t
    }
    , `0 `# v$ M7 D@1:使用了自定义的作用域@MyScope
    0 _2 x& y8 L5 `+ `/ d7 h
    & F- q) P7 t7 x7 u& k@2:构造函数中输出一行日志! m( U9 Q6 N, y9 x3 R7 {& ~

    ! v0 Z6 ], w& n3 v) [+ x4 G7 W@3:给username赋值,通过uuid随机生成了一个
    ( G5 x( m/ r; t# q0 W# ]- b3 I, s' ^# y
    来个spring配置类,加载上面@Compontent标注的组件1 `5 O( {! X$ s/ w
    4 l0 y' `$ a4 Q7 y1 C: n. p# e
    package com.javacode2018.lesson002.demo18.test3;
    " m: _1 |1 j6 \. f7 y) l# ^3 U5 Q( W9 u6 T
    import org.springframework.context.annotation.ComponentScan;7 _+ ?- g& q5 i
    import org.springframework.context.annotation.Configuration;
    8 B: I1 J* t9 k" E9 g% |
    " o5 w( p8 p# |. L; N/ V@ComponentScan
    : V1 I7 ]. L6 U9 d$ a@Configuration
    3 N8 v$ j, s$ g3 a) A* b( opublic class MainConfig3 {; L( I/ b/ u0 @7 x( d7 g+ \3 I
    }
    . o1 R- M! v" b9 e2 |. I下面重点来了,测试用例
    0 ~7 D& t2 b0 `7 M7 N! t, K3 {" Y. R$ t3 _- O  q" E
    @Test
    + X  s3 W( p- e8 ~/ }0 C! Zpublic void test3() throws InterruptedException {
    : [& D$ p% R& r+ X4 Y' c5 w# V( [    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    5 k: ~, B9 B! F    //将自定义作用域注册到spring容器中
    # O4 @+ }% U9 ?    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1( n4 b. F* k$ a9 t
        context.register(MainConfig3.class);& N. M5 z8 w& e, ^  l1 y
        context.refresh();  ?# j: t( H9 d" ^, B
    : G, T3 z) K  E  w; m
        System.out.println("从容器中获取User对象");
    , t3 A- h! l: s) G    User user = context.getBean(User.class); //@2) c; h: I8 q4 u+ h. P& i" M7 O
        System.out.println("user对象的class为:" + user.getClass()); //@3
    ! H. h/ e  P& z/ u9 K4 u
    8 Q, W+ z2 R/ k5 G    System.out.println("多次调用user的getUsername感受一下效果\n");3 z6 Z" y3 w' f5 K
        for (int i = 1; i <= 3; i++) {9 H; G: z* s+ v4 W
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    ! l0 \3 `+ ^+ y* n7 I8 G& o4 y2 ~        System.out.println(user.getUsername());' F! i' ^) V$ q0 Y9 l
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));1 H; H9 F% r0 B) P. ?& x  I
        }* r$ t# c- H, x* s
    }
    0 I! C# G; |6 D8 a8 L% A@1:将自定义作用域注册到spring容器中! E9 h: M' e2 ^* d4 `

    4 K! l$ U6 H5 y8 \1 K@2:从容器中获取User对应的bean
    9 j  @$ \8 W. j( b0 C: w; h% o0 W; i4 x
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    + n( k2 @3 x5 Y
    ( y0 K" w- B0 X; n* L4 L代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    ! k% Z" e9 ?& c2 u% ?. ]2 m) ?8 t3 k) I) N4 P
    见证奇迹的时候到了,运行输出
    4 Q, N2 ^( a3 T( A: B, `6 ?+ ]) A: N
    从容器中获取User对象7 X6 N2 @% G8 b7 b; M
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127* i, n9 M. L9 d8 t6 e4 O
    多次调用user的getUsername感受一下效果
    8 L+ B; q& R; |1 i" A- x
    * T% S, `3 @* D********
    ; R4 w! r# i' n2 k第1次开始调用getUsername
    : ^. F0 K, C2 N! ]BeanMyScope >>>>>>>>> get:scopedTarget.user0 a5 g* z9 {* P8 v' a6 V2 R
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4; S3 P" a9 O: G0 k* x. o: j
    7b41aa80-7569-4072-9d40-ec9bfb92f438  K" y8 N3 c" c5 `$ h
    第1次调用getUsername结束
    " T: S9 a/ J3 w! h6 V* r: E********
    6 n/ @3 I3 y0 D$ |% K
    . Y9 ~" V+ T8 n0 T! q* l********$ Q. R* A. a7 W8 j: ~. \: {1 X& v
    第2次开始调用getUsername% o; n- v9 x5 _
    BeanMyScope >>>>>>>>> get:scopedTarget.user9 f6 L5 [( m+ W7 U" }( q4 i
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b) z- |' Z* r3 s/ p8 N* @
    01d67154-95f6-44bb-93ab-05a34abdf51f. U/ \- o6 W' Y4 j) W" d* y
    第2次调用getUsername结束
    # Y! E5 |- x- ^9 M, b* B. q********, F' s$ A+ K# @( A: P" c

    $ u8 G, g6 E! ~% R9 a' e' E. X********
    " J& E" R3 x% U: c3 [, T1 A第3次开始调用getUsername3 @' A4 E, v6 Z2 E
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    5 _( ]$ K7 G, d7 \1 ^1 l  D& Y4 q---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15- K# \3 M$ w& ^" B
    76d0e86f-8331-4303-aac7-4acce0b258b83 Y9 M3 B% o0 w1 I
    第3次调用getUsername结束
    ) d! i$ x, h5 j0 E; w4 b( K********
    * L3 l" P7 j6 H; @/ w  E, u. F从输出的前2行可以看出:' `3 L9 k6 f+ f9 J
    % z$ s% ?/ J  g: H, L1 k+ ^: O* ]
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    4 V( l% ]+ ?0 ?8 r5 F8 ^1 I
    + R. d- \; ~  Z* t3 `第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    . C9 C4 Q/ T% k! S6 J* M( o1 {4 ]
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    ) U6 @& T5 n$ N1 @8 ]  i4 X2 F$ W9 e5 M1 x
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。1 F* ^, T- U0 y& E6 w( y: }

    & a- f1 k7 g2 u: \5 c# e动态刷新@Value具体实现7 d4 t; W# m: s8 g0 U
    2 H  |8 m. B+ O' @
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。& H4 b  O; J7 Q5 h7 E

    ' R7 N6 H- ]- W' M先来自定义一个Scope:RefreshScope
    , C5 W3 x) n  G, c  k) x+ o, f- p9 A6 r" f
    package com.javacode2018.lesson002.demo18.test4;
    " J3 e& ^: W) |) m6 b6 ^. F  t  E2 v3 X$ R2 s; u4 Q8 L* u1 I
    import org.springframework.context.annotation.Scope;
    2 w( ]4 O: i7 W: _8 [8 W$ {9 p0 B$ q# Jimport org.springframework.context.annotation.ScopedProxyMode;8 ~, Q5 c/ k8 a7 H. y* D6 E

    ' P2 w( c( o# U* U& C/ B) vimport java.lang.annotation.*;+ x5 {# B0 ^( K; R

    3 X7 w. R4 y) q; R% Z@Target({ElementType.TYPE, ElementType.METHOD})- A' k6 z$ u- G; w3 b
    @Retention(RetentionPolicy.RUNTIME)4 f* \) S( i% K/ V
    @Scope(BeanRefreshScope.SCOPE_REFRESH)0 ~3 @; }* F0 {* i$ k" Y# o: S
    @Documented  `- v3 w* O# w9 v
    public @interface RefreshScope {* n! `0 \# e% S; v2 T/ z" i
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1) {) @) W- E5 ^' w5 F- P" _9 x6 l
    }
    + U7 g: ]# i3 R# _+ \" n要求标注@RefreshScope注解的类支持动态刷新@Value的配置% |# `& t! i3 W0 k

    3 ^' H2 Y+ D$ \9 _1 |0 ~& P* S1 ?@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS. Y& v" W- M4 v3 h! w0 c2 b

    & d0 m. F6 H9 [. a& s+ l, G/ u$ u这个自定义Scope对应的解析类
    - y' J% L  r- W$ d4 o6 O7 [: h% M& \+ O: K/ d) a
    下面类中有几个无关的方法去掉了,可以忽略
    + C. v7 O' L3 [, w: y! v% ^6 ?5 U5 H) c2 ^  S( `* U) f5 _# E: D
    package com.javacode2018.lesson002.demo18.test4;9 b1 ]9 g6 D0 }* Y

    - I& M3 K( N, H: R! @: U+ W1 O) E; l6 C
    import org.springframework.beans.factory.ObjectFactory;+ E* e/ c# ~; q* o6 T6 `
    import org.springframework.beans.factory.config.Scope;
      g! o. l6 s% a( q; Qimport org.springframework.lang.Nullable;
    5 a" E$ x) r0 ^' C1 _* K/ I9 U) e! r( G& _# L5 C: R7 H) s6 r& K# f, \
    import java.util.concurrent.ConcurrentHashMap;
    6 Q4 i3 f, \/ O9 v. ~: U* A  @2 V8 R
    " Z1 @$ S! T$ \7 L& ~public class BeanRefreshScope implements Scope {
    + `' e' i+ M% _1 |) v  z' f2 K
    1 h* U+ B# F) K    public static final String SCOPE_REFRESH = "refresh";
    6 u6 a; K% @: f0 f5 t. {& a: w7 \4 f4 _  ^" o6 X
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    7 F7 l7 X% O  |8 k
    " {$ u( N" Q7 G% V, F    //来个map用来缓存bean& K0 g0 ?. V* z. |% ~1 N
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@11 ]% o8 }) {4 h: a$ B3 q
    + a$ y; g+ ]# e4 c- W9 x
        private BeanRefreshScope() {  [; h# d" Q* r' I5 Z9 A
        }1 i' W3 Q! M5 w$ D7 I# M
    0 `% Z2 S5 i: f5 p- C
        public static BeanRefreshScope getInstance() {
    . E3 x) e( l- D- N- J& p3 b2 o1 U+ E* b        return INSTANCE;; l, R9 |+ w# _4 c
        }
    ! v/ a$ K1 ^6 w2 Z8 ?+ Z. U9 j0 D% E' |! R
        /**
    . C+ U( a0 r0 y; ?     * 清理当前
      F: T  Z" e% A5 |6 n     */1 s, O. b3 S$ ]6 s
        public static void clean() {
    8 j. E7 N# I# P" d# S' L        INSTANCE.beanMap.clear();
    : e% @5 O& f, N: x" t" B; F    }
    2 Y: P* Z% v' W# W8 G
    # s  X9 Z3 \2 ^- i* x$ b% O: n    @Override2 W! |. u, R( I6 G
        public Object get(String name, ObjectFactory<?> objectFactory) {: a3 ~9 K% r8 `9 g6 K; C
            Object bean = beanMap.get(name);% W1 e& G6 M4 X$ K5 \) s
            if (bean == null) {
    ' y# U+ }! |; c+ e/ p# ?/ A            bean = objectFactory.getObject();1 e5 F! B6 P0 `8 s
                beanMap.put(name, bean);+ n3 c2 X: M# t* [
            }- G& {7 C. M7 L( r
            return bean;
    8 m2 Z& m( M0 k" A    }
      x0 w4 u7 N! d: r! s- I/ ^) k8 Z7 ~+ d$ O* p# V$ Y4 o
    }8 \- k5 I+ t2 `: \8 j
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    ; U0 n: u% x" C+ o9 ?
    * _2 C  n. V7 J6 l: d" G. g& ^$ l上面的clean方法用来清理beanMap中当前已缓存的所有bean
    + C7 i2 y. L1 H" f% P7 h' |
    , s6 n" D8 n* V5 S' P来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope* d, H0 L- x: M! {
    ; \: z0 \' ^7 Q9 E8 G/ b
    package com.javacode2018.lesson002.demo18.test4;
    ' I! ~# s; h6 k2 q: |4 h' J! i+ _+ R4 N$ v: N
    import org.springframework.beans.factory.annotation.Value;* Q( j( A: Z% F( }; @9 W: x; O
    import org.springframework.stereotype.Component;
    + T( X& ]: q, m( g: W$ M
    - X0 W" X( d3 \, J% _; t/**% l$ T& \' k. G! v- y; P
    * 邮件配置信息4 i. B- ~# ~- g. k! H
    */4 g& B! \9 j) L4 y5 d
    @Component
    6 m  r7 W3 f  |9 g@RefreshScope //@1  m2 X& X8 C# r% ?, m
    public class MailConfig {
    % E1 [6 ~" z0 L# X. ]7 v
    ; h' k$ h5 m' W% ^% W4 e    @Value("${mail.username}") //@2# |0 H2 e, s) N5 O
        private String username;
    ( j8 V: X3 O. [! \* q. V. W/ [4 _, i/ x1 l  o0 C& p; m7 @5 E  q
        public String getUsername() {; M5 T+ x# G8 {" e: ]) ]
            return username;/ N1 V" J  z, t/ U
        }
    % N; G4 ~4 B8 X# q
    % S/ `5 V3 n& R- Y* C! l+ s    public void setUsername(String username) {
    ' H. d% E- D- p8 j' K        this.username = username;
    # W# J5 _! ~- @$ h+ b    }
    1 N5 Q6 z) r4 R- M+ f- g7 ?' M! A4 W2 y
        @Override
    ; I& W3 x% a) ]8 W5 T* e$ O: v& i, j    public String toString() {
    + O# v5 E8 f" |        return "MailConfig{" +5 e* A* G  B: K" m) A
                    "username='" + username + '\'' +
    ' c+ o; ^4 x) w$ e  b! d# N                '}';: P( o: }# P& H: R0 _$ e8 l
        }* T; k, X3 N, F+ |. r! b9 e" G* i' K- e
    }
    " z  R% x! I5 O  r% ?2 G@1:使用了自定义的作用域@RefreshScope
    5 ]: U1 W$ `1 F( l3 o
    - u- d2 s) Z9 z0 j% `9 J@2:通过@Value注入mail.username对一个的值
    : ]3 J# o! \9 e- e9 U$ F0 P/ F+ k3 K1 r0 p3 b$ N* i% o
    重写了toString方法,一会测试时候可以看效果。
    9 K8 X; e& n. v7 R- Q. |; w  @& f! B$ o* J
    再来个普通的bean,内部会注入MailConfig2 v4 i3 S( G6 F$ T8 r& y2 g7 H
    : a6 R2 j0 U! L; v
    package com.javacode2018.lesson002.demo18.test4;7 h" r' T! B  B' J0 m
    . ^* P5 s; C# A; C2 c- `1 I
    import org.springframework.beans.factory.annotation.Autowired;
    ) W. g6 N6 u. M$ I( ^8 Q; N8 vimport org.springframework.stereotype.Component;3 P  m/ \) l8 Y$ g# ]  u

    * W2 N; \% U" n% @2 V. p9 S@Component" R5 D: p- O' Z) U; K1 q8 G
    public class MailService {
    0 [4 G- d. O5 p" U$ `5 Q% i    @Autowired
    2 U( n; u2 a& C    private MailConfig mailConfig;
    4 n( c, Y" x( B9 K
    3 H- I# V* h9 A7 a8 Y    @Override
    : P7 K9 ~% ^+ R! c4 H& s    public String toString() {3 s- P# @& v, l. f9 s
            return "MailService{" +" e6 G6 Z7 k- X2 T6 j
                    "mailConfig=" + mailConfig +
      p* G4 |# B7 j$ o: C7 Q                '}';
    1 b5 i: I( K/ q8 y    }
    * j- B+ {: W+ F2 R( q1 L$ k& w7 d}
    ( Z# O7 X# l/ u; R: c9 H代码比较简单,重写了toString方法,一会测试时候可以看效果。
    , S; w, ?' }. r1 l2 h; r& U  {  D, \' ~& `" l- g1 X
    来个类,用来从db中获取邮件配置信息/ U! [( f; Z5 ?5 c

    9 ]1 }; n4 L. E! ~, L/ n, q% Gpackage com.javacode2018.lesson002.demo18.test4;
    / a7 a& T) T! B8 y3 [& h, G
    0 \/ C8 }0 g. r& E) oimport java.util.HashMap;" d& M! F! f1 R% P
    import java.util.Map;
    2 |3 W6 k$ \9 H5 qimport java.util.UUID;4 D5 d4 o9 ~/ a6 W
    . G, S7 H- I  W, `4 H
    public class DbUtil {4 T* ~6 ^, K& o2 ^8 S" a
        /**7 \" c6 l, ]9 P) a% x: ^
         * 模拟从db中获取邮件配置信息- p7 _& h/ |0 Z
         *1 Z' c  ?- i2 S7 m4 i1 b
         * @return
    # L  y! o5 g+ L4 ]8 F     */
    4 t. u4 |  `. |4 X8 a# ?" |    public static Map<String, Object> getMailInfoFromDb() {
    & s' F9 Q* M+ m0 e0 d/ I0 D. W        Map<String, Object> result = new HashMap<>();
    5 R. h. c2 c) E- g. {- q: t        result.put("mail.username", UUID.randomUUID().toString());
    3 x& m% t6 |+ o% m& G% \( s        return result;
    ; m3 g9 F$ V- q/ x) w: \. |) \8 \    }
    7 f7 w) N$ d* M}5 a  A: B6 u% p4 [2 y
    来个spring配置类,扫描加载上面的组件# d( S% ^, [( J0 @# s

    3 n% A  _1 Y7 A+ Opackage com.javacode2018.lesson002.demo18.test4;& G. N  {4 q' W& g) Q
    $ ~7 i/ K; x; e5 ^6 k+ m8 R0 R
    import org.springframework.context.annotation.ComponentScan;- N+ g- w; ~* a# {+ Q7 D
    import org.springframework.context.annotation.Configuration;
    1 y4 m! x) ~) j# @- C/ a4 k9 A5 T% C3 X. ?
    @Configuration- K( i. s6 g( \- O
    @ComponentScan2 e1 H1 c. Q! M7 j
    public class MainConfig4 {7 B* Y" i* v. t
    }
    : F" |4 R; x4 W1 c来个工具类
    7 A/ |- ^" H. t* e4 G7 o. B$ k* R% W# ?9 c
    内部有2个方法,如下:
    1 T* b$ Y% o7 p( U. H& M$ N
    ' ]9 D3 c+ ?3 ]5 H  N, Hpackage com.javacode2018.lesson002.demo18.test4;
    : P, H" v0 q& g3 D1 R" y9 r
    # Z) n: E" m% W' |" fimport org.springframework.context.support.AbstractApplicationContext;; b# A) @+ c, q8 M, Q$ y
    import org.springframework.core.env.MapPropertySource;
    ! v- N3 y1 `0 W/ Y$ J$ H
    2 O" p) G' F3 o& o7 y, f7 ~) @import java.util.Map;
    2 T8 `' X# E; ~5 t  i: z1 w* w, i: ~9 V
    public class RefreshConfigUtil {
    : d9 O* B5 J; |    /**
    2 C: t) L' E& q/ I) ~6 K9 R- i7 L     * 模拟改变数据库中都配置信息
    % C+ ]2 s. g0 p/ _     */8 [3 j8 n5 U) Y1 l" D8 {& h8 R' ]. g* q
        public static void updateDbConfig(AbstractApplicationContext context) {
    , F  w- ^1 c$ k$ [: e* A4 S" q        //更新context中的mailPropertySource配置信息
    " [% ]3 o8 `3 g        refreshMailPropertySource(context);3 c# L: d2 k9 K. b3 u

    7 \' a: v2 _3 t( c* o        //清空BeanRefreshScope中所有bean的缓存# A/ t; v7 C% m1 @
            BeanRefreshScope.getInstance().clean();" }9 u4 `) e1 q' o7 _8 r# l" D
        }
    2 V. X1 w- z  V# [- `$ ^9 f  P4 s2 d' ?. ]) L- p3 p
        public static void refreshMailPropertySource(AbstractApplicationContext context) {" D" r" U, J, q$ w' N3 r
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();: }  P1 s8 R3 h7 b3 M9 G
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ; e( D, h9 z. ~9 a" r        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    / d# g5 O+ V. ^3 K: f3 \        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);5 ?. O7 h- @3 i$ q
        }( f; |9 r/ R+ l! o! i

    / e  k9 m( O0 \}  B$ S" J+ {' E
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息& @& P( H- Q( |# @- o7 N# U

    , t- k" f, ?# ~0 X& lBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    * P1 E% W' _* i, c
    2 f, |0 c: b" n0 r* |9 I3 }) Q来个测试用例. t& E8 }, h; T0 a+ B/ q
      n8 p  j& {. M3 P7 V& o1 _
    @Test! X7 d4 E3 l( Q6 n4 r
    public void test4() throws InterruptedException {, u9 ^7 C4 M! U4 G5 t" J! X
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" a$ \% ^5 V% z/ `: y9 }9 e3 j7 d
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    % Y( P5 u: @, z3 K0 s1 e    context.register(MainConfig4.class);+ n  y" {/ d- U
        //刷新mail的配置到Environment1 V, B" l7 m$ A1 a& M4 S! {* I
        RefreshConfigUtil.refreshMailPropertySource(context);* |0 Y/ |! q0 d, D
        context.refresh();
    6 L( U% l* K5 D  }2 V6 j7 f3 m) j8 B) n( P: `+ e/ w. Y. P# u' X
        MailService mailService = context.getBean(MailService.class);
    . s# R! Y7 P- U( E    System.out.println("配置未更新的情况下,输出3次");
    - \& A9 C$ u* C. u  u( h! d& h    for (int i = 0; i < 3; i++) { //@1
    3 |* b7 u4 M+ j+ k4 d9 Q. ]        System.out.println(mailService);
      A8 R4 a& |# A, L3 p% n0 v        TimeUnit.MILLISECONDS.sleep(200);
    , @7 j5 |5 T6 z, p    }
    $ D6 ?( ?. {5 Z! A) h/ _% ~0 f: T. G, x
        System.out.println("模拟3次更新配置效果");
    6 _) X$ L& C' S    for (int i = 0; i < 3; i++) { //@20 l" W, h. t1 r+ m( M# J4 o3 C; E
            RefreshConfigUtil.updateDbConfig(context); //@30 q- |: _; F5 Q7 k; i+ i
            System.out.println(mailService);, V# o0 v$ P- X3 L( M' F) L
            TimeUnit.MILLISECONDS.sleep(200);& L1 ^7 u" M# h3 O# L# Y7 K7 B
        }8 T/ M( c9 c$ n  e) y
    }* N6 |$ S* v$ B# f& t4 ~7 J% N, e
    @1:循环3次,输出mailService的信息
    # d( Y( D6 Y0 V7 ]6 C$ p+ M2 X7 a0 ^
    $ @, |  B7 b4 _) g  n@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息5 y. m. _/ y4 Q

    + ]2 p3 J+ y% a0 v见证奇迹的时刻,来看效果1 E6 e1 @# g2 {. [  {6 j. u. a
    8 _* J! g" R3 l# H
    配置未更新的情况下,输出3次
    1 {# l3 x/ o( ZMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    - {; T1 W4 Q7 ?7 @4 e( @$ SMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}# P" G6 v! B0 n/ |- `
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}; G+ N8 k1 G' v0 R; l' ?- _
    模拟3次更新配置效果7 o6 G! }' C- D3 x  g$ H: u
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}1 u, v+ N0 j4 a5 ]  O2 m$ C
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    / G; `; S% \* }MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}# L2 @3 {6 X: y+ q. ?0 `
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。6 c8 J- s" ^: {3 y( t- P

    7 l; O; x( L7 {/ u2 M+ ^, }小结
    ! c- ?# N$ U% B) O8 ^9 S/ W! G. N8 [5 I0 R" {$ g
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    # D( y$ a, N2 a: M& u! U; g: ]3 e; j' [& f  B
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。) E/ ?3 ]% s" ~; Q5 v) t; B

    ; j" `. q3 s2 b总结* ]2 v( t* Y' s5 z' T& W

    ( C' d' q1 a& h; w本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!6 M. Q3 L. {' O' L
    ! Q9 C1 S% u- n3 v
    案例源码/ h1 u2 z7 j& z- e$ b

    % D5 r" S0 h4 \5 hhttps://gitee.com/javacode2018/spring-series
    # X3 J( Z6 \6 Q" j( e6 ~! S- p路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    5 t1 j' w, ]. R" A' {% q6 x1 i5 J& l, P6 O& \
    Spring系列9 P& H. r+ A! {* C% X

    ' j; w5 Y* u7 M: G/ a  n* pSpring系列第1篇:为何要学spring?
    + c" Z$ l4 {* C7 u+ S. G9 j  k! G; L& _" ~2 P
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)' l$ l2 u) m: f- T
    & n6 O( t7 z+ t* h; E
    Spring系列第3篇:Spring容器基本使用及原理4 M) D5 C8 G9 b9 i, u/ P( r1 m" V

    : H' i. w/ [& A9 i5 A2 TSpring系列第4篇:xml中bean定义详解(-)% i( G; c% L* i9 v3 S: V' B
    , X% w+ Y- l$ S
    Spring系列第5篇:创建bean实例这些方式你们都知道?; Z$ C. A% C. S! l  n

    0 q% a1 R- K0 A* ~1 R4 @* TSpring系列第6篇:玩转bean scope,避免跳坑里!
    0 s; v7 ]+ {: H5 R
    # r0 W1 ~* f* Z' eSpring系列第7篇:依赖注入之手动注入- I  e/ Y+ G! O  I

    : s1 `( n9 s: H; h, H' X: E- nSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    % K) L( U7 Z9 x7 }: o  \% V1 e$ s& ^8 g+ V' n, R
    Spring系列第9篇:depend-on到底是干什么的?
    . K+ f% I, T- i3 G0 t% s# g
    * Q- g3 x- t# O# O4 ?# t9 eSpring系列第10篇:primary可以解决什么问题?6 ~2 J: V3 ~1 `

    ) I" T' I: E5 |  l4 |Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    ( G/ Y: _9 _3 s/ T; |# U; E3 c) Z7 B0 L  h
    Spring系列第12篇:lazy-init:bean延迟初始化
    : |0 w# D! s: e% {
    " I4 B8 |+ ?" s# V- Q/ V/ X/ T1 [Spring系列第13篇:使用继承简化bean配置(abstract & parent). \7 }( t% L' u- a" T
    7 T9 u7 ^' y  l# o8 f( V- ]
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    ! t- d9 I3 N/ C9 N, H- I2 T4 P+ }+ o6 E. W
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?$ _8 Z4 W( o+ P( \) e/ `* s6 S( v
    ! K" Y3 E* R& @5 E
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识). u9 B' C' N# Y

    9 w+ a3 h& c7 g9 b7 nSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ( ?$ n5 e% U+ Z0 @- T' W$ C, F; C9 p( o4 e
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册). |. D, L0 w6 V, G. w7 ^
    : K2 o4 u/ \' ?/ v) h
    Spring系列第18篇:@import详解(bean批量注册)
    8 L3 z; F) z. M' R
    # B1 t5 V# F; @9 o) Y/ i3 PSpring系列第20篇:@Conditional通过条件来控制bean的注册* l# c& ?7 |% K% F7 R( @( T- D3 \

    8 k, J& A' v* L! f3 mSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)4 }. F' ?3 P  `5 w
    & x4 e/ @2 T, P: ~
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    + n( |5 {/ P( @" ?+ e& p6 a& p" k3 N' p+ I1 y7 v; m; w) \+ P9 {
    Spring系列第23篇:Bean生命周期详解
    * X4 M! m  e% j1 |8 J7 m7 k2 v. z! E- ^+ d% o+ }* G" H1 {
    Spring系列第24篇:父子容器详解
    - w( m  _6 Y5 ^1 b2 T$ o
    0 o1 @9 P! R& ]" B更多好文章  O; p/ d+ R5 B* F( z

    % q" `( Z, [) x1 ~4 tJava高并发系列(共34篇). p, T$ R- m- v8 ^8 C
    # }- |9 H9 A& ~
    MySql高手系列(共27篇)
    2 d) j" ^) ^5 O
    . `! Q# O5 V. t* V& c& X5 J- ^Maven高手系列(共10篇)& Y  E3 t' M: N* ?

    3 |( G) f, R3 Q/ i( m+ j3 XMybatis系列(共12篇)
    4 s0 A' k" b3 P' C2 Z8 }- J. l, M( ^: b% Z) f9 t
    聊聊db和缓存一致性常见的实现方式
    ' q8 f0 l- m) {/ v7 A! P# j% y+ `
    % ~/ p6 i8 q. W8 C接口幂等性这么重要,它是什么?怎么实现?0 \1 z, k& j" F/ O% r* h

    5 T4 H5 k, o, z! J泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ! i6 y$ ]: `9 `: p* [————————————————: N+ q0 y: B, M8 X
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    - f1 H* P% Y3 L: a# L, J原文链接:https://blog.csdn.net/likun557/article/details/1056487571 L- e' |1 ?- r
    1 a( d* [0 R  R0 M/ B

    & n' C" x, {# d$ H3 v
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2025-8-4 01:59 , Processed in 0.417614 second(s), 50 queries .

    回顶部