QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5393|回复: 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 e0 F! ~: X! ^* |7 t6 p5 w( h
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!+ w' n0 @6 R2 e0 U
    6 [5 ~- [' H) V5 e+ X
    面试官:Spring中的@Value用过么,介绍一下4 M; g8 ^8 |: `1 k
    7 }0 v& a  r0 [# ?  v$ X6 Y
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    " t- r% B# o8 p+ s% t2 `4 ~9 G. S) ]8 D+ ^# `0 N/ k6 c, j& V8 v; i
    面试官:那就是说@Value的数据来源于配置文件了?
    : k  Z/ N" |# @' x, T- V& `7 R, V% C2 l, M' w1 L2 v% `
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置4 g, k9 d2 m; w
    % J% z( c2 ~5 _; I4 j9 I
    面试官:@Value数据来源还有其他方式么?) V9 s7 x, A7 l, E" R

    / h. H! h' y4 q0 |. i我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    4 l) e, m. W# v6 I7 u- m; d, [( S* O
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?( G6 B$ \* n9 ], L
    ' i% S$ x' L. K+ I* i) p& `3 [: j
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    9 k. ^; Q3 b! j- O' p) r) c
      j6 J% T' u, }$ k面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?6 \8 j1 R% [9 [6 O9 l9 R

    2 P( t0 q$ p6 o& l9 z我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    4 U4 W6 H7 v; M) G( e! v4 q/ E+ M5 F) v
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    ! [. i) I- B! e( ^
    * Q# \7 H& h2 m我:嗯。。。这个之前看过一点,不过没有看懂
    ( _3 [) n7 f, ~( [3 k& U6 ~  B
    ! P4 n9 D7 W% I面试官:没关系,你可以回去了再研究一下;你期望工资多少?% [6 K' E( k# a9 G. T4 {$ V! O
    * Q0 O6 N  Y* a0 S2 I/ W7 i# s
    我:3万吧
    . `, R' R8 i' F6 H$ a) w# e
    % p; g# _6 I5 M7 b; ~# h4 l' r面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    1 K1 J$ C/ R# @; R) D' V* w& u$ I; M+ N
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万, t' Z3 Y: A* r6 K

    * ^+ [0 k  t$ N1 i3 {; E面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    + L2 i, t' K2 R8 O+ i4 {
    - |" |9 V" D3 p+ e/ i- z) j0 p( Q7 `我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。* y) \5 B% i; ^  ^; L9 D
    5 }3 @* @7 B7 f7 r9 j) H3 V# U
    这次面试问题如下
    . C/ \  a3 K; V9 V  R, v/ {
    ' m7 r) [) D/ A7 g9 ]9 ]- `@Value的用法) S4 F8 W& \5 l: b% n; L4 U- o

    $ x. Z6 A/ h2 A; y) K6 i7 b) t@Value数据来源9 C4 Q0 {& y: m. M
    9 b3 H: ~. S$ O/ ~1 l; D
    @Value动态刷新的问题1 i. n) j! V: U( T2 E

    # b1 s) y8 K' G4 [2 o下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    4 \# _  ~& K6 h  U8 f' ~: n. A- w& ~/ m# b' k
    @Value的用法
    8 R* A4 F, ^8 A! x/ c2 ^5 a3 b0 F, l7 B
    系统中需要连接db,连接db有很多配置信息。4 f2 H- ~  T7 {* c5 k/ `

    ! W: p& F% _8 Q$ R系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。  }" M/ L6 l" F! y! ^% ~

    1 M) G1 u+ B' ?$ t( z- E还有其他的一些配置信息。
    , v3 w0 K/ l( i6 n2 I0 ?5 {% E5 @9 a/ H1 i+ ^( _
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    9 c, Q; J& ]1 h" x! @& {
    : k9 }9 {7 E- Q  D) z+ i那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。, F! @4 |  p4 Z/ e
    3 \9 k* U! c. K. ?  U
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。/ a- U! x+ v" d( H5 a. J
    & F9 \$ C# O6 f5 q  q" t- i
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。! t, Q, ~/ l9 A0 t8 q9 j
    0 k" z4 p- n) {4 l8 x$ B
    @Value使用步骤0 T/ e! i4 z4 ^/ L: E) w2 I

    ( q0 r! Q2 g* ?7 a步骤一:使用@PropertySource注解引入配置文件! Y' \9 J: F+ u
    + D; Z* b/ `4 r$ Q  Z
    将@PropertySource放在类上面,如下/ a2 I2 o  _9 c6 M" R, t0 G/ L
    ( A; W9 _. X9 N3 ?) ], }& L* f
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    5 c% x" ~0 ]5 v9 I% D4 |- U@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    : t( g. \/ W) \& O" \
    : o5 A. t9 G. K3 p+ N如:; B& l; q5 z" u: s

    ( m4 f' o! M! W. X@Component/ a5 x; z! `& W% b- ^0 e2 L$ L
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    : Z7 N3 q6 C8 z5 Jpublic class DbConfig {
    * _0 z: }! ]  i% g$ E}
    ! y, M; F3 ^; P6 d; I步骤二:使用@Value注解引用配置文件的值  J# I7 u8 Z' _. D* u5 x
    9 T4 A( H8 J: \6 M( v% {5 `" b
    通过@Value引用上面配置文件中的值:* j3 X& I, _3 N# I9 |
    % u! \2 Y. R, |  D) f" D6 a
    语法! ?6 B7 R* g  h5 `
    / C+ z7 b) g# r8 D+ U- o* m. `
    @Value("${配置文件中的key:默认值}"): j+ ?1 D$ a& X8 ]3 v
    @Value("${配置文件中的key}")  e* }  R( l4 r9 {
    如:
    ; `- ~& Z9 T6 M3 b
    - \: Q) J' _+ y4 B, ?& W@Value("${password:123}")
    4 L+ r: A% O8 D. d( t8 b; W- k# o5 y4 H上面如果password不存在,将123作为值
    / e  e7 D8 {3 b2 b$ g0 h6 `, P8 M) q" H/ k5 V% o
    @Value("${password}")
    $ ^6 T1 f/ E0 F. \; t( J3 p上面如果password不存在,值为${password}
    0 S7 v* R) p  c
    8 _+ X7 ]1 [& y9 \( E假如配置文件如下
    6 l0 P  T9 D5 ?) q0 W: x7 F) |% H+ f0 G0 d) O- X$ C
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    6 D" F; `9 U* [' }jdbc.username=javacode
    3 K; g$ P* m9 {* r. hjdbc.password=javacode1 W* n4 ?; x; v: _* S' i# p
    使用方式如下:) G1 a: h% @$ j! g) G/ x1 L0 j8 R

    9 T7 m) }4 w; d- _& @9 b@Value("${jdbc.url}")/ N& r& Z3 H. B2 _& V  w; _$ i7 i
    private String url;/ R: a% B1 U3 o  i5 @+ H

    ( x4 |3 n! ?" R  y# u@Value("${jdbc.username}")/ j* l% `- K) V
    private String username;
    - G7 B: b1 e% p% n& @6 s) ?3 z  S1 n7 }, X0 [, C" c7 g' [
    @Value("${jdbc.password}")
    / B8 X- {. v" ~2 T1 l$ a  t' Gprivate String password;
    ) Q) c7 V& t5 ^- ]& S+ V5 Y2 x下面来看案例
    # U$ Q: T# J- X! \
    , h& L- o3 `- {3 n案例
    ' R  {" s. G, ~5 ~9 T( J, A" a1 [
    ) n( O% |! b  r来个配置文件db.properties
    . M" Y0 D  F2 X- Z' \
    ! @* I# i/ k" ^5 }6 }' djdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    - M  J3 L5 s. t+ ^; W4 A: i/ jjdbc.username=javacode) Y' t4 H+ s! f3 C7 H: y
    jdbc.password=javacode
    % P) N& a- w( ~! L$ @% c+ K( g% W来个配置类,使用@PropertySource引入上面的配置文件+ }% u( u$ ^1 ~) L5 `

    1 M7 g, Z& R$ K7 J5 Tpackage com.javacode2018.lesson002.demo18.test1;
      j3 W# U) _. r
    - d7 k3 a4 F6 @import org.springframework.beans.factory.annotation.Configurable;
      k2 I; j" B. S$ oimport org.springframework.context.annotation.ComponentScan;
    , V9 @% p! ^, n( ximport org.springframework.context.annotation.PropertySource;
    0 g3 w2 P9 E/ [/ G) s6 I; C# O: s8 R3 P9 Z" [+ X$ I
    @Configurable
    - c1 T- W$ j  r) s3 u( y* a& a@ComponentScan' ?$ T5 e6 _! j- D3 g
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    / @% m" }$ |6 Y) V, |public class MainConfig1 {# A5 q- I# {7 |
    }# m. _4 |  {8 N8 T
    来个类,使用@Value来使用配置文件中的信息- L, U% x  A' o1 Z: ~2 b3 ~

    / _) K4 ?$ N( ]! t4 Kpackage com.javacode2018.lesson002.demo18.test1;
    % N5 }. [/ [. S( N/ g+ ^
    & w  c$ \9 d- dimport org.springframework.beans.factory.annotation.Value;
    $ x, B7 K6 z  H) }; pimport org.springframework.stereotype.Component;
    $ x* S0 ^, _9 v  K6 R7 r* |8 T- b- i1 C; e6 I
    @Component1 F  n( Y2 k6 J: n, i
    public class DbConfig {
    " n% L! l) V$ }: l5 U! i- b, N; U* X$ ~/ C& r7 S1 E0 P
        @Value("${jdbc.url}")
    1 K4 l$ j' ~; O2 ^8 `    private String url;2 F! |4 H- r4 Q+ [) B
    & A' A- h4 _7 O) }) V( s
        @Value("${jdbc.username}")
    & J" E7 z' f& C9 O4 ?    private String username;4 ]7 l' m9 k. `% @8 e9 k, ~4 f. x

    8 j' g4 e/ E2 k! g    @Value("${jdbc.password}")
    ! T! n7 D( V, r$ _    private String password;) H8 l  w; ?: U/ [$ c+ [( n/ t
      N% t$ S' e/ x
        public String getUrl() {
    * L% s, ~( b2 h, b        return url;
    & z( l1 X7 X# B: d    }
    ) m* B* s: {2 ?" T* }/ \( I+ \) \  A, Y* z& n& h! n9 c
        public void setUrl(String url) {
    ' [- ]$ M  ^7 W7 ~        this.url = url;, L$ y* v$ N$ S
        }* f' S% W9 m& ?: j( a6 y; S3 K

    . _5 K* v' A6 G8 D    public String getUsername() {
    + `  b& B1 U0 M) m        return username;
    , N5 c# t8 \5 ~. [; ^* z; ~" P( o    }
      Z$ E( K* q5 }( z: P" q+ T. w. q0 ]# {' D
        public void setUsername(String username) {+ {4 r  L8 O3 T7 H- O
            this.username = username;7 ~% c1 r, y3 U6 v+ G3 p5 b
        }
    0 c6 u2 n( H" h- q
    ! T. j; s, r  K! n/ i    public String getPassword() {
    ' _+ C+ B9 y1 E8 q! o        return password;% y% a; z2 W: E- r6 }
        }
    2 v" E# a2 r/ a8 S
    " ]; j8 W( m" l    public void setPassword(String password) {5 y$ o) t  }8 T4 L
            this.password = password;/ N" X' E) u! D! d  ]9 j! R* L
        }
    , t3 M: z/ X# v/ a, s' L+ W3 _' n7 Z
        @Override
    6 d2 c7 e( _- I* H% {9 m    public String toString() {
    . i1 S, `) {& f' s2 @, S, ?0 a3 |        return "DbConfig{" +
    : Y  A5 V" ~' W) n2 c                "url='" + url + '\'' +  ~" [& t3 I3 H; p' O9 m2 H* X
                    ", username='" + username + '\'' +' b' A! j& Y7 A7 ~! P% \, H
                    ", password='" + password + '\'' +. i5 u3 C  q6 ?
                    '}';) u( W( }8 m- c% w
        }; H# p6 V/ s  k- g
    }! G5 e' r$ V/ [
    上面重点在于注解@Value注解,注意@Value注解中的! Y$ j5 R0 {4 L- m( i

    : B3 J' N; K4 U7 [来个测试用例
    ( m+ v. w1 a1 p3 g  r7 I7 G4 y: h& M6 q. ~( ]
    package com.javacode2018.lesson002.demo18;# x: t* t/ C" S9 u

    # n1 r9 {' n7 o1 r; @import com.javacode2018.lesson002.demo18.test1.DbConfig;
    * l& v/ o2 ^& himport com.javacode2018.lesson002.demo18.test1.MainConfig1;1 c0 R/ H% ]. f: y# v0 [1 e& w5 M
    import org.junit.Test;" _# A% O& q# a$ \0 g: a
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;5 ?5 d  s' c2 y/ V
    1 {, x7 g9 I. Q% R" B& ^
    public class ValueTest {3 n3 n1 i: v) J" O4 k! d

    & `3 e+ n" |5 Z2 U' f* o9 K    @Test
    6 `% M7 A# }0 T. n    public void test1() {- R2 }5 k* o! ]
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();8 L1 L1 I+ X2 m, n; {0 B0 v
            context.register(MainConfig1.class);
    4 _' I& }5 \1 D/ l* J) X        context.refresh();
    ' t+ P+ _5 J8 c8 w" V/ f" H  a3 z1 b( t/ S3 M, a( [
            DbConfig dbConfig = context.getBean(DbConfig.class);
    ! \5 Y1 U* y6 B, P        System.out.println(dbConfig);
    % f* c( A+ q2 W: W4 x    }6 i* R; T2 M* F/ v3 t
    }$ E: D+ M7 J6 P, W( B1 P: h
    运行输出
    4 a! A9 L' p, M" x
    7 \9 D: P1 v1 Z5 X4 Z2 yDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}1 {& r$ Q: y2 g* ?* g0 R
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    9 E1 N8 v; }0 `. C/ M/ m" U+ d6 K9 f5 B5 o2 Z9 {# b6 P
    @Value数据来源, u. ~$ A8 h2 o

    1 }: M4 N7 z4 M7 f通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    0 V8 A9 z" W, E0 G- Y- G' h  O; I7 \/ H" V
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    9 i; V% p4 d7 ]. e
    ! B3 \1 H  ~4 K! ?7 l/ g- mspring中有个类! @0 h8 w( }+ P7 P0 x. X0 Y

    ! r" G1 H: \" o5 E& Jorg.springframework.core.env.PropertySource, X. B' y, _$ W! L1 {' P
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息5 F' q6 Z; E9 L$ Y9 W
    # I, L# E7 Q6 i6 `* }6 a* u
    内部有个方法:
    $ P' _7 O5 l: G8 n9 {% Q( [7 z$ `# k7 \. a7 W% r& j- N4 X+ H
    public abstract Object getProperty(String name);
    ( Y4 @: q3 n7 j" J1 E! n% g通过name获取对应的配置信息。
    - x4 }6 ^6 B% O& n
    3 R3 t+ N* M4 S6 j; l系统有个比较重要的接口' G- B6 B& D! q' p) b# q
    ! T$ r* j: w# G0 f6 n2 o( s* w
    org.springframework.core.env.Environment
    ; f% ]& K) X, O: _" }: g& u) A用来表示环境配置信息,这个接口有几个方法比较重要
    ( f) o3 \- x" o0 V
    0 J: N6 [& m+ [/ Q" }! o/ o5 Z! zString resolvePlaceholders(String text);
    + W9 |+ {/ A: pMutablePropertySources getPropertySources();
    3 q9 w% c+ Y0 zresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。& V2 M$ [( g. {" d
    6 e* |# j$ Y; q, X% ?: i
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    5 @* w& X; y* Y: F4 c8 W1 `% m8 w- x) P7 `. L$ Y& h" ~
    public class MutablePropertySources implements PropertySources {
    ! R3 N9 r0 a( _& d* p
    : M- O1 r1 Y9 m# V1 a% w  z# ~5 R7 e0 G    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();- Q4 \& v7 z0 y% ]5 k

    3 X6 t9 c2 C. N4 [- K5 ^}
    , t. o" K( r& g内部包含一个propertySourceList列表。+ x7 u9 w/ q% V. n0 m
    - y* n+ E6 G( G* m
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。; [/ H, l! b9 S  @( t
    . a; N) D, H, S$ [, G! j  X( i
    大家可以捋一下,最终解析@Value的过程:4 S! H  B5 `+ o7 u( T- z2 M- G$ v
    6 Z* k7 \5 P3 W) K* s
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
      V& A9 B  j  g" a& E0 g) e- f* g2. Environment内部会访问MutablePropertySources来解析' ~4 z! b6 V& M. h" k- L) x
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    # ^1 @. l7 N% l, d' ]通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。5 x& F8 V! c* x2 a% v7 b

    8 D; Q9 `7 d) n. d$ M, X下面我们就按照这个思路来一个。" P7 R, G4 Q* o- ~8 X' r  k1 d5 I

    7 ^+ s9 m, P, n: a  g4 k! z来个邮件配置信息类,内部使用@Value注入邮件配置信息
    & D! O6 C6 P# i4 Y: B% q( H
    2 O; W& r. z7 E' e- D, Gpackage com.javacode2018.lesson002.demo18.test2;' Y: h$ A( ^( L0 u" C
    ' c8 k; l! N( B; {
    import org.springframework.beans.factory.annotation.Value;
    6 N# M, R/ L& V" r' Iimport org.springframework.stereotype.Component;
    ( M. h/ ~& v% d2 d/ C  C, l2 U6 q9 {& m8 U- U2 }
    /**
    8 V' g$ F/ g" S  c * 邮件配置信息
    7 v/ N6 q& _" B: b */
    9 [$ v5 n. C1 Q. c1 H4 `" E" I* d@Component6 a" Z4 M$ L0 D
    public class MailConfig {, ~5 |) N5 x0 m, \7 u7 r  S/ G
    ) x9 P( G0 X' [. _' Y
        @Value("${mail.host}")
    3 J( y1 c0 u" l( p  ~0 z    private String host;* o; I9 m3 Z$ Z7 A/ P( z/ y

    ' s) {( ?. p- H# G    @Value("${mail.username}")
    7 j, k- k" Z) z, z: D% @- z% }/ z* y0 T    private String username;+ p; @( F0 V9 w6 x1 S& u+ y8 ?

    0 l; r5 x* _3 @/ V0 G4 W    @Value("${mail.password}")3 [$ n/ Y8 R. ^" C
        private String password;9 ?% J! b; p* `# w) U. F* u; z" D

    ( ~4 j- R* _4 _    public String getHost() {
    - s7 o: ~/ P% w$ V$ y- w6 x! `        return host;: r/ P6 g8 q& J& @
        }- m* U7 y8 U. X0 [& D) |
    " u# a: R4 x4 N/ j( |# ~. O
        public void setHost(String host) {
    & ~: y0 ?: @3 k) t        this.host = host;+ O1 u; t) {9 d9 c
        }7 {; ^6 f; C# ]0 _3 F
    & k" _' X# F& Z1 H1 P" {& U
        public String getUsername() {
    , a4 t( M# c* ?) f! J7 r# N        return username;
    8 o: o- ]( _/ _/ U9 U    }
    - r* p; h. I( V: J* I7 O2 A! s6 m
        public void setUsername(String username) {/ V) e) d& \% I" _. [; ]) h
            this.username = username;
    3 v; }% A1 U3 Y9 ?9 \. n    }: ~1 F: e& A. `: ~
    ( r3 G# N( O8 ]0 g/ Z  [
        public String getPassword() {
    . M2 N% e! \1 f8 W2 _/ o- O# @4 q        return password;
    + f- C6 C* f: R, I4 S7 Y    }
    ( O1 ?/ W8 N* u, d9 p. [. l) N3 ?# N! |5 y2 f2 B
        public void setPassword(String password) {) m( w9 T( S+ f% o% ]# J+ b! F4 F
            this.password = password;
    - c$ K* o: \% e6 P7 }+ q) D    }; \! ~5 \8 L5 t1 |* K

    . l2 j8 G* n# v1 ]+ P    @Override+ D+ z- i$ r9 G; T4 G" s, Z7 }
        public String toString() {
    , A# X$ H5 X6 t" u5 w  s' D        return "MailConfig{" +8 K: Y) r! ?% I+ [2 l" ]- M
                    "host='" + host + '\'' +
    3 l! E. J+ i" `                ", username='" + username + '\'' +0 C9 a) u% \0 z/ D! ?
                    ", password='" + password + '\'' +
    " r% M/ J8 T. `& H. P+ K                '}';) A+ N; h8 m/ r7 t" u
        }' i3 [3 [! O+ I* h* O
    }) K! P* q6 N5 I* ~4 N
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中' I% W! {: y0 u' @- X

    : P: w5 P. A. F  s; e  z; N( m4 e) rpackage com.javacode2018.lesson002.demo18.test2;
    $ h$ e  Y# G9 y* S; v/ u7 L& x9 o) ~( K. \6 t; ^
    import java.util.HashMap;9 C" @- J! Y6 M9 c4 Q
    import java.util.Map;1 X2 Q8 C5 C3 f$ g( c
    ' y: }4 ^. @4 M  [& T1 d
    public class DbUtil {$ z4 B2 s2 o& ]
        /**
    0 J2 [# P, A5 @# Y  D# b     * 模拟从db中获取邮件配置信息9 {3 ^& m$ R+ r0 P5 D: p. v" A* I
         *
    1 w: u! a! q! v! E- i     * @return5 F4 q' H5 h# h8 i: Q# W' p% y
         */" f0 |( H( g- }) Z; M
        public static Map<String, Object> getMailInfoFromDb() {
    8 y% x% t6 \6 f1 ^        Map<String, Object> result = new HashMap<>();
    3 B8 ]0 {4 w/ a        result.put("mail.host", "smtp.qq.com");" K3 _" t* {1 q$ V' M
            result.put("mail.username", "路人");7 s& J/ p! i- g0 @& ]) f
            result.put("mail.password", "123");$ j  @- w; z6 ?
            return result;
    , ~- u9 W! v4 W% `' P    }, D) i* X8 ~! D
    }
    - ]2 O* p9 _9 L) q$ R来个spring配置类
    ) }& r. D% F# F% S1 d* v% X
    1 q% h4 I, h6 i4 B' a+ w* W$ upackage com.javacode2018.lesson002.demo18.test2;
    7 {3 }  B$ a& N; U$ S; h9 G6 y2 K* J/ D  I/ D0 U) q) n
    import org.springframework.context.annotation.ComponentScan;  W+ T' O3 c& |$ M$ ^
    import org.springframework.context.annotation.Configuration;8 a0 t# A% ~' m1 W* b0 k
    6 ]) O. h+ \1 |6 E& \8 t
    @Configuration
    $ [" ~  o. J  c, ^, I( e@ComponentScan
    * G3 y* i4 d% r' \* g8 }2 L, bpublic class MainConfig2 {
    0 J& q) C5 w. f0 T}
    ' u! Q6 i( S5 O2 n下面是重点代码# S( @$ d2 A- O

    + M( L9 x( q  [, T0 f) q) \7 o. D@Test, e9 j) ?4 l  c' Z
    public void test2() {- t8 y& {4 ^8 J* x
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 G* b, Y' ]% w0 {" W+ M
    , E# D# ]. b* v8 C' |6 |  @3 j    /*下面这段是关键 start*/
    4 C: D! Y7 w$ x2 o: V: \/ |    //模拟从db中获取配置信息4 q( I  p+ k) Q! H
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    . |. m! i% }7 E9 P6 ~- `$ g* {! [    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)% B$ ~: A7 c/ `2 V) l
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # q& u" X) Y/ H) W5 C    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高( L8 Q. r4 }) v8 Z8 d/ B
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);: s" |: D. p8 C5 Q
        /*上面这段是关键 end*/
    $ ?; S2 Z+ u6 t. U8 a1 c
    % s+ J8 c. y6 p6 o! x' b$ N7 i    context.register(MainConfig2.class);
    & f0 `- V/ i* @, f    context.refresh();; L: E; z+ M) x% X* n6 f+ n9 J
        MailConfig mailConfig = context.getBean(MailConfig.class);
      \8 `7 n& y9 l6 }    System.out.println(mailConfig);, A/ E/ Q# \: h" j* U) m  e  h
    }
    ' h* C5 u$ n5 B. |9 @注释比较详细,就不详细解释了。
    ' o8 X, c; s3 F
    7 F% P' f6 u+ m& r2 w& c直接运行,看效果8 F3 u6 Y0 O( B# N* V0 O
    9 J1 g* \$ V9 _4 c+ j6 U" _
    MailConfig{host='smtp.qq.com', username='路人', password='123'}& ?6 E6 u) i3 X# I5 _/ J3 {, g
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    9 s6 Z+ V  |5 m3 T) ^; a
    ( t: K% t0 _+ K; U上面重点是下面这段代码,大家需要理解
    3 U" w# W6 ~4 m2 o$ v+ f
    0 D% F3 }' J0 d9 m1 h/*下面这段是关键 start*/, w% B+ }1 m, G/ ]# R% t
    //模拟从db中获取配置信息
    ' j; }  O* W+ w, A2 w( C0 p% W$ r7 u9 yMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();3 L0 G) l9 g# G
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)( F3 ^- e2 R0 I* K% R/ j
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ; |) v# }$ r7 `, Y# r: N4 J//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ; y1 C$ W, g& ycontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);5 R: m; N! g2 v; R# S
    /*上面这段是关键 end*/
    3 v9 F% K% C6 e! M! u咱们继续看下一个问题
      d. q( }1 q# p  t( o6 Z0 u1 O  F- ?2 A) E
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    ! [: F$ }# n( m+ C9 A6 _% P% e  N: c* B( L5 i
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。5 `$ v0 y6 F; I! G; z3 g7 F$ m
      w6 p% |3 C- d, C3 G6 ^
    实现@Value动态刷新
    + W0 V' t  y/ }) H
    / z) i9 _" H8 ]/ x( W) O先了解一个知识点
    % L5 i$ Z% D0 l. t/ j" ]( ]
    # G7 y& [* ~* d' Y! g这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。% J$ S* V8 \" N  L
    . I! F* S% s& }' \
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解5 Y. a& V! P! N

    4 w% _6 f; q$ `- s- g4 Dbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    9 `: {- M9 E6 ]7 v) R% a
    2 Q" e3 [0 V# H! |$ JScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;" G" X# w' O! q0 g6 c
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中3 n4 c3 v; m1 L6 a9 I
    ) p3 C9 j6 o6 A; A; g( ]
    public enum ScopedProxyMode {
    1 H1 H4 z: `* w    DEFAULT,/ a) y" ~( ^4 E6 ?
        NO,
    2 r! o; T; a$ {    INTERFACES,) C; c3 ]5 S" l4 ?" {# y
        TARGET_CLASS;" O( U0 v1 Y8 k6 D% n
    }3 Q( x, w/ U9 J6 I* y& D
    前面3个,不讲了,直接讲最后一个值是干什么的。. [1 p/ q/ K  M, b8 L- x$ R6 `

    8 S' D' {+ f$ J6 r当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    ( ]0 {( p/ J& _( J, U$ g
    / g: ?0 ^+ ?0 [& s7 g4 Y0 K6 T( W理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。  V  e9 |4 n1 J, R1 R: o8 P$ _, @
    . o& a/ `2 x% b% K$ V
    自定义一个bean作用域的注解
    & q* K$ v3 F* s- m6 h, E% B8 [2 m" A7 @8 m
    package com.javacode2018.lesson002.demo18.test3;
    * u2 v$ J* n* e* f( ]$ O- K6 u" l' A  Y- S7 p+ x
    import org.springframework.context.annotation.Scope;
    4 I% R; d! ~1 }& nimport org.springframework.context.annotation.ScopedProxyMode;
    . d5 A8 c2 a! l! [6 [9 c6 Y: i- c4 M* Q2 r! D2 H! d
    import java.lang.annotation.*;2 U: O- g3 |+ \3 N/ O. r+ ]
    + n8 p7 k! h% `3 M# m
    @Target({ElementType.TYPE, ElementType.METHOD})& u7 Z2 P) X, \; Y. S
    @Retention(RetentionPolicy.RUNTIME)) r, E) {( h1 e. C- p" G7 T
    @Documented8 j8 ?1 [" [: A0 q
    @Scope(BeanMyScope.SCOPE_MY) //@1- d; G7 j$ T/ O- @. t
    public @interface MyScope {
    3 O. }. t8 l1 Z: l  q. o9 l' }    /**
    7 D; R  ^" j# i( Y; i& w( A     * @see Scope#proxyMode()
    9 Y, M; K. F9 h     */( }# m4 g! `9 u" N; M! [
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    / E. o$ Z- J, ^5 b}7 X4 @9 W+ R! @
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。! X+ B# o7 M# N
    0 B; D  R' Z: q7 I/ _1 J% M$ ^
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS8 Z# ~/ u- S: ~) r
    : K5 a; |" N! y8 v7 c: u
    @MyScope注解对应的Scope实现如下5 f- h9 t+ B/ `7 s& B
    / E- i9 a# d. r6 f
    package com.javacode2018.lesson002.demo18.test3;. D0 ^7 {( U; j. }9 Z7 X- F( n3 B
    0 z& X: T/ G# ~- F" m# L
    import org.springframework.beans.factory.ObjectFactory;0 k$ ^0 D) t" |) x
    import org.springframework.beans.factory.config.Scope;
    + V' W' O* b) f8 j- w$ Jimport org.springframework.lang.Nullable;
    # F4 y4 y& t' L8 \3 t/ F
    0 O/ w, l# k+ I2 i( h4 ~- q' c/**
    3 M' x; n1 M1 q! }7 Q, }4 D" ~4 A& k * @see MyScope 作用域的实现; b4 C  G& i4 y9 R$ I8 V$ Y
    */( P- h. j9 u% @/ @- Y4 S
    public class BeanMyScope implements Scope {
    ( o* f: `% M  T! v
    5 W: j, O# x; W4 F5 u    public static final String SCOPE_MY = "my"; //@1
    2 w& c! D8 h" S: @& A1 k5 E. m0 X
      L) x# ?3 P( T% |) C5 E  f  `    @Override
    ) w- |6 u, {* B' J8 Z    public Object get(String name, ObjectFactory<?> objectFactory) { 2 r- u8 i  g, E
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    9 u2 R$ ~$ y4 H* U        return objectFactory.getObject(); //@32 I- d$ z7 m9 V. P1 S5 h
        }
    $ N/ Y1 G/ u5 {# a
    : \  M" d% s- m  l3 a9 t9 [4 h6 i    @Nullable2 O+ s! h* f7 k. @( w
        @Override
    - C' N" X1 X9 B  @$ F7 v    public Object remove(String name) {
    5 t! t5 W9 N0 t. ?. [( ~; C( @        return null;
    , s5 Q6 M8 b8 w8 z/ ]) L    }- y! B- j( k: @; C- {' J& U
    ) c8 q4 s9 P( Y4 @7 [! i* X
        @Override
    5 K" }+ u; V6 \; ~    public void registerDestructionCallback(String name, Runnable callback) {
    0 u+ j, d* J7 s' N) J
    $ e) U( W6 c: ~2 B) E( _! y0 ?    }4 \% j+ C& F3 R& R: f6 D
    " W1 {3 k3 W/ B2 Z/ O. k' v1 d
        @Nullable
    + o3 M" u4 R% q+ ]    @Override4 j2 E# i" W/ C  Y
        public Object resolveContextualObject(String key) {; p( f& @% r) w
            return null;* G& J! v: w& E2 S/ O
        }
    " \- r: c; O! V. c
    ' g$ g7 u+ k8 d# X    @Nullable* D# ?1 L( K/ ]% [" o% ~% N" S  N
        @Override# Y8 L; C' r% o* ~. j! n) v
        public String getConversationId() {
    # v6 @$ ?1 G; E) \        return null;0 S* y1 Z% w* E! g: @- f- {, w
        }$ Y5 q& U  H7 s' R
    }
    5 F% Q% n$ P, z+ M* d7 o@1:定义了一个常量,作为作用域的值0 Z& z- S; j6 J* H# M7 P& M9 c

    + n4 y' m7 e- u2 F  G- w% q# Y@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    % Z- q# b7 Y3 e3 c: I4 C0 i; u* r5 p$ o+ N3 q! [& f
    @3:通过objectFactory.getObject()获取bean实例返回。
    - Q  A, l& ~% V# P% l% c
    ' J) L7 {# ^- ^! B; D9 D2 L下面来创建个类,作用域为上面自定义的作用域2 [( d0 e% F' a5 l. `, a7 }) o. E

    ; Q4 ~; X  |) ^) P9 qpackage com.javacode2018.lesson002.demo18.test3;$ }' `$ m- {3 C8 E$ H6 u$ \: ~
    # X& c5 I% G* t) Q( F$ y
    import org.springframework.stereotype.Component;
    ) ^2 w9 G! U$ e! w2 X7 S9 }1 u3 x9 M' @/ w) |
    import java.util.UUID;
    9 Z9 u7 w  w- ~3 I% n
    & p( R- Z6 P5 ]@Component
    ; c1 k1 ]& `' f0 t7 m@MyScope //@1
    0 q" v) i5 |0 ?7 T$ x; {public class User {
    " A* E1 {4 o* ~3 a
    1 N8 t8 k& R6 i    private String username;
    0 D: ^7 j, |! c8 t3 w" |
    & Z/ X/ a  Q: s    public User() {
    8 t/ f; L# C8 r# B7 W) N- g8 l        System.out.println("---------创建User对象" + this); //@2
    8 p& m& _+ N0 c7 C  `  z7 ?        this.username = UUID.randomUUID().toString(); //@3
    1 F  V& v% O- S0 b( @# T$ g    }
    ! @: a/ V5 D* I; y( q# V5 ?! H- a' ], f; S7 x, \. J
        public String getUsername() {
    ; v3 f2 J5 L3 U8 f+ M        return username;
    # d: F2 |7 o3 y2 e8 u    }
    ! v7 G! @. n5 ]) n
      B' P3 l- z" C    public void setUsername(String username) {
    , x" J9 F2 x% @0 o9 U5 O        this.username = username;2 p9 h$ O6 W3 X3 E
        }  }+ p% S0 ]+ o( R( f4 O, R

    . J' N# l# Q% Q" y! Z+ H. G/ J& G}
    7 F5 r6 Y# d5 H+ \. q5 g+ O@1:使用了自定义的作用域@MyScope
    + U( e$ p5 |' z+ {3 v5 W3 O: \* l
    1 r$ C1 N( O" N; O@2:构造函数中输出一行日志3 G5 o: v- z3 L9 K) [/ [
    ) p# }9 c) I. s
    @3:给username赋值,通过uuid随机生成了一个# h; e! [; {( i
    9 B* j! h; A/ B+ T7 j
    来个spring配置类,加载上面@Compontent标注的组件
    # C! J+ t3 f& y1 ~9 [! q6 m5 j4 A& z- e4 T9 [1 E; U- l
    package com.javacode2018.lesson002.demo18.test3;9 |6 F  o! p1 w6 V0 [; }# v
    + a, w9 E7 e( |
    import org.springframework.context.annotation.ComponentScan;
    6 J& ^# u! q& S& simport org.springframework.context.annotation.Configuration;/ V2 b# Z$ @6 L
    3 c9 k/ H+ u5 j1 F
    @ComponentScan
    ) H0 H& Z8 i6 n/ s1 L( o@Configuration
    , o1 [# U; I* x4 ~+ J, d- ypublic class MainConfig3 {0 }' a' m( \! |) Y: M
    }/ f* b; [! _% _5 Q. m7 }* a
    下面重点来了,测试用例
    * l1 |5 s. ~0 p) B& M$ i1 ]( x! T0 e5 G2 |$ [- o  z- p8 @* Y
    @Test
    9 M, D( J3 p' V* t% C2 _public void test3() throws InterruptedException {! k4 U# h( t3 Z/ x8 K5 c* p( h7 @! r
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();& e: G3 r; a, |( Z- S7 I
        //将自定义作用域注册到spring容器中8 l) c; {- y2 ^) v3 N! b& O5 `
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    0 [9 ]5 Q+ Z0 x! k) M    context.register(MainConfig3.class);
    * n3 f* z+ v& R    context.refresh();# Z7 b/ G1 H( q' z- Q, P4 P' m
    ! @% a$ a3 v4 Y3 E2 m0 s
        System.out.println("从容器中获取User对象");! j4 ~. v; N# d+ s. ?; {) a
        User user = context.getBean(User.class); //@2) {9 n4 U4 n( S0 i: s
        System.out.println("user对象的class为:" + user.getClass()); //@34 y. \% l. T& _% y

      {& u4 y% u' [8 |% X    System.out.println("多次调用user的getUsername感受一下效果\n");
    - I) f* P- D* I    for (int i = 1; i <= 3; i++) {$ ?) `2 P- ?/ N# ?
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    ; v8 c( n  V: s0 m" f2 t1 k        System.out.println(user.getUsername());
    & q4 |' J$ d6 |4 n) v& S        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));, }9 h5 Z, f( c
        }
    - f" V+ {" s6 B4 t+ ?3 E}( t! Z& c& |% ]3 X1 E" C  w- a
    @1:将自定义作用域注册到spring容器中
    4 D1 _' Y6 M) p# S
    " Q$ g" s! x4 B* o@2:从容器中获取User对应的bean
    ( q4 M+ |4 d$ b, k
    5 {5 X$ C: X0 N@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    - H  Y8 s8 [4 C" L  r$ d
    3 q9 Z/ l) o! O6 N代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。! l) w* ~4 }& I7 P$ [. m

    $ r* ?! N+ e! v. E见证奇迹的时候到了,运行输出0 y! I) J" U+ a
    ; c# W9 G4 Z* K5 V/ Y
    从容器中获取User对象
    , V( u$ O2 B$ W8 \( muser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    . G. f& a2 q# }4 K" w& F多次调用user的getUsername感受一下效果6 Z& r) x' s! I" D% `3 e
    8 J; V# A- F( Y2 O" ]( G8 p
    ********# J! _5 t& t+ H, G& [3 Z* }$ r
    第1次开始调用getUsername$ D# r7 x/ q# o7 D; J. {
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    : x4 o7 \" Z' }: h---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    0 t" L7 y7 ~' W9 T6 I( G7b41aa80-7569-4072-9d40-ec9bfb92f4387 R$ C# {  D  W' S
    第1次调用getUsername结束
    ! s& B% c5 j4 s+ S2 H********7 n9 u' u- E6 k. k
    3 N2 j9 q/ C( k* g: |6 S& t
    ********6 E6 X$ ^* p: s
    第2次开始调用getUsername
    % N0 W6 I3 L9 _! u: _$ \BeanMyScope >>>>>>>>> get:scopedTarget.user$ W5 @" M# ]9 y" `/ G* T# c- \: [
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b$ @# y, ]2 b7 n' u& u3 s, d
    01d67154-95f6-44bb-93ab-05a34abdf51f  }/ j5 f% S) v4 c
    第2次调用getUsername结束. Q" H9 Z* x6 t3 }4 C" L4 Q5 Z
    ********0 U  _) a  i% S2 D+ J: t
    ( l, }- y* w- ^; {
    ********
    / p. g% a3 _# l# r2 V' g0 u4 K  s第3次开始调用getUsername
    + Q/ W' d4 }4 h+ ^, i  |  qBeanMyScope >>>>>>>>> get:scopedTarget.user
    " O5 J4 A3 m  l" s$ a  H2 t---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d151 j4 ~- z# {4 y
    76d0e86f-8331-4303-aac7-4acce0b258b8
    7 i, u5 \6 i- l; Z1 ?第3次调用getUsername结束
    ' \* Q! }/ x+ o2 G********. ?6 p, @2 d* l  w, ^
    从输出的前2行可以看出:8 b, p+ A' P+ p- W& n

    9 c  A+ Z( P+ D3 E调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    1 X+ I( z3 O. V, g; a# |
    9 U* u+ {: F6 Z  L5 f/ j! Z% I1 s第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    3 K' I2 J7 X# o3 a1 z- W
    & n/ C2 d+ C- R! K; E- V0 g9 O后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    7 ^9 K9 y& Y( c( A2 T0 E( a# b9 X% ?; \5 R% p1 P1 @9 n: k
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    , G* j' [: P* ~& M1 [" B# E" P9 T/ j0 ]) }* Z8 g5 |, ^, O/ l
    动态刷新@Value具体实现
    7 J2 y& C; }: b+ w  h4 @: l0 V: c+ P- H, O1 j, Y
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。. Q' I4 A& I% \  b9 p* [- N8 n' d

    " t/ U/ P: V- |2 W* R先来自定义一个Scope:RefreshScope
    + S5 d$ @! a9 {) p7 @8 l- v
    2 s$ s8 Z3 {- r7 h( D- g8 o7 opackage com.javacode2018.lesson002.demo18.test4;
    7 j/ L- y8 f8 |  s3 Y
    : p9 S( m, G# N/ J) \* ximport org.springframework.context.annotation.Scope;
    ' k, ?2 _8 |3 X  Zimport org.springframework.context.annotation.ScopedProxyMode;  J& f+ s1 R3 L  [0 p( l; B
    * w3 d6 P7 `; }! T
    import java.lang.annotation.*;% a# O) U" J; y5 S9 z5 y/ O$ @: r

    : t% c- s# Y/ s' r( M$ }@Target({ElementType.TYPE, ElementType.METHOD})
    5 u( I* V3 r  R" w) |@Retention(RetentionPolicy.RUNTIME): j' ~* t1 T; q) C
    @Scope(BeanRefreshScope.SCOPE_REFRESH)4 }  M$ ^) c: Q1 g9 @
    @Documented
    0 J  R5 U3 L3 g4 m/ f# S* Tpublic @interface RefreshScope {) Y6 G8 t# b, \. o8 D* x) {+ ~
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1, ~  S& ?. o/ i* Y
    }1 p& e/ c* M( }) K4 O! [
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置& B' B# T! y. Z2 M7 b
    7 H* D( B3 |  O5 W" q
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    ) d( `: Y8 Q/ ?+ j% h
    $ K0 _; X6 U8 l* Q( f$ ?这个自定义Scope对应的解析类* R3 ^0 u9 i0 x& @

    ( t1 L1 [8 P$ Q; s3 j下面类中有几个无关的方法去掉了,可以忽略- l* q5 _, t/ {  J
    0 O0 r* o+ r6 Q7 S2 h" M) o& [
    package com.javacode2018.lesson002.demo18.test4;
      e/ @( ?+ t  h5 k8 N% ]! X
    7 g2 v1 _4 D- X9 x6 K, |8 C3 B" f( R! v1 u6 \+ b. a$ z
    import org.springframework.beans.factory.ObjectFactory;& Q" T1 l0 C$ \: Y) k6 ~; c( B9 P
    import org.springframework.beans.factory.config.Scope;5 ], q% @# Q2 H- F( r; s
    import org.springframework.lang.Nullable;3 x  }' M2 ~5 L8 n& ?
    ' T) T/ p$ {# F
    import java.util.concurrent.ConcurrentHashMap;5 G' T( h! v" f' {+ J! B1 f
    3 W3 b1 T+ j: S% w& a# \+ ]' S* A1 u
    public class BeanRefreshScope implements Scope {! ?2 W% p' P! _" v/ j2 d
    : s3 M0 X, D6 f3 x% f) w
        public static final String SCOPE_REFRESH = "refresh";( D: C9 x, X$ B5 B

    ' i# i4 E, s0 J5 G  n0 Z! i    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();7 e3 z( u  t; _

    * ?; _" E1 h) q" n" h    //来个map用来缓存bean$ \" O6 {! r2 W, a5 _+ P" |. `
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    ' D" [" k; N5 V6 E3 c. |( N0 e& \, E4 i1 P9 s
        private BeanRefreshScope() {
    3 \* [6 V, X' N$ }' t% v) z    }
    ) R) X5 A! ?8 s- s/ `3 b# d
    * u$ b5 P2 S1 n% w    public static BeanRefreshScope getInstance() {4 ?; X0 j- D. |( y; ~
            return INSTANCE;# k' U, D6 p9 O, i+ j
        }
    4 S! @8 T) y( F+ t: H$ o! t& L; Q# k% T6 c: x& |: r  C/ M
        /**
    1 z2 ]7 P6 m5 w% u7 R& ?     * 清理当前
    6 ~2 ~' D0 i: l     */
    $ F8 Y0 R2 o0 D) Z# V" o    public static void clean() {3 h' K# l0 e, K9 S0 t! k2 j
            INSTANCE.beanMap.clear();9 E9 Z- p1 D  V  a
        }) C' z! l+ v7 |$ s* V. b

    : w- K0 H1 e6 v' b    @Override
    3 k+ [) i- a  z; Q9 B8 \" I( s1 S/ H/ h    public Object get(String name, ObjectFactory<?> objectFactory) {
    - d# c, F9 s' z& E2 N  Z        Object bean = beanMap.get(name);. V, h7 F$ g- ^, [; Y8 A/ _) U  g/ n
            if (bean == null) {% G- I9 L/ b5 G% ~4 K* j
                bean = objectFactory.getObject();
    7 J# ~4 g3 ^1 m3 k# _9 K3 A            beanMap.put(name, bean);
    * Z2 T& R( f3 A/ O8 L        }
    / D) F# V$ C; J$ _+ w) J/ h7 t        return bean;* ^8 V, ]) |/ q/ A
        }
      z1 X& l1 w. B4 i  I. c+ v1 K& S+ l: t
    }' ~7 [9 C1 N; H% r; R% ]' ^
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    6 [9 \7 X1 w" m& w: U* C& ]
    3 e- I5 X. Y4 y7 [0 R) V+ y* g上面的clean方法用来清理beanMap中当前已缓存的所有bean
    : m( @; M$ R/ @2 T! Y- S( r; E
    2 g5 T: A: \% c+ Q来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope% g. {, a  B5 ?! u0 l8 L

    , \" a: V2 x1 L% E6 Jpackage com.javacode2018.lesson002.demo18.test4;
    2 c6 s. r  n. q! {: ?, I7 F* X" B. @% G  n3 X; F
    import org.springframework.beans.factory.annotation.Value;
    ' w' R7 c# d; Y  F4 B, }9 limport org.springframework.stereotype.Component;1 {- D7 g) F$ X8 u, u0 j1 J
    6 f* k$ k/ |* s- ?- Q  f  O
    /**' N/ l) C/ E  O( i5 O
    * 邮件配置信息
    / {% b  H! f2 v5 F) j3 ?$ [ */
      V/ t; Q7 M* d- \@Component
    5 R4 I) Y+ ^7 X7 d+ W" L7 l@RefreshScope //@1
    ) G9 N7 _2 A$ e# L; J( r6 K1 Jpublic class MailConfig {# S( I) f2 s: F4 o; E- g6 l' Q

    , o7 ]: r5 p0 w9 K& L/ S    @Value("${mail.username}") //@2
    / x8 B, Q" v+ l) s: w: {: ?1 m    private String username;2 ~* f% F+ L& v; k
    - G& p# [" l1 I. {2 {0 M/ X6 L: o
        public String getUsername() {' x4 n3 H7 v; K' f
            return username;
    ) Y# S7 K; `; b7 c    }* O. u8 t" ~* e! Q9 h8 j
    # h6 H" N" K+ c0 m# W! Q8 D
        public void setUsername(String username) {. u6 ~9 d- \! G
            this.username = username;
    ) x3 }+ O7 k9 B/ }) K    }
    ; O! T" G: J% b3 W/ a* i( X4 I+ E3 P( _
        @Override
    . d: |" n$ h5 n* V* V; X    public String toString() {7 K5 ]4 Y9 Y+ {1 G- p& k' ]& H
            return "MailConfig{" +- f3 O& @& \' r6 j1 }
                    "username='" + username + '\'' +  u; v$ ]! ?1 X* |" `8 ~
                    '}';
    0 F& T' ?& l- T4 l    }
    ) Y* m% D$ e( o# m}& H% s0 j. T  [) P+ V5 Z, n
    @1:使用了自定义的作用域@RefreshScope
    . K! P7 B1 `5 ~" z% S  g/ p5 N: p2 u  |$ z7 e$ N+ e" O
    @2:通过@Value注入mail.username对一个的值2 P: j1 m& h8 Q7 p# A( k

    ! X8 c- Y; Q9 d$ C重写了toString方法,一会测试时候可以看效果。' ?/ r; b' e5 e
    : h" m3 ~9 R+ z- n1 x' e+ C
    再来个普通的bean,内部会注入MailConfig
    . j) a- U( K* b4 M* I
    / J; w: i' m1 K5 o  @2 Apackage com.javacode2018.lesson002.demo18.test4;
    0 O/ I: X# b% u$ P- y' [6 r. \$ a$ d
    + L- K3 X7 C7 w& [! iimport org.springframework.beans.factory.annotation.Autowired;
    & H; O$ c/ l! f+ {. M7 b" _: Uimport org.springframework.stereotype.Component;
    0 O( Z8 F  a3 R, `% t3 F/ O# y! A# I
    @Component
      p2 ?* T- P' ^1 W. ], npublic class MailService {) \5 Z0 T1 G9 @1 L. R3 r0 {; r2 H
        @Autowired6 r7 F1 t1 A2 a$ |  o
        private MailConfig mailConfig;# C6 Y" _; p+ K3 i! i* Z: k
    1 t/ l, n# A9 B- c
        @Override
    ; N& j  W% r4 G2 F: ]$ e# F    public String toString() {, b: f! {9 i$ C9 B& [* @
            return "MailService{" +
    , q* d' p! u1 V5 \$ N. y# f( K' K                "mailConfig=" + mailConfig +
    ; [! h6 z) R, E  c                '}';* y1 U; f3 y) K: W6 t
        }
    & a* O, j! ~+ q  A: p* G}. k. g! N, H* |1 o' c
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    * W) |. n9 A9 E, h9 G, }# K/ {2 i* {" Z$ t( q: G3 j9 n( q
    来个类,用来从db中获取邮件配置信息
    8 x7 m6 u8 o6 O2 C& [# P! F+ f9 V5 A" ^+ w& ]/ _  M4 A& h5 m, w- [$ K' \
    package com.javacode2018.lesson002.demo18.test4;
    " y" E3 j4 Q' M3 T6 y* k
    2 Z: f! d9 Q0 b( V9 [import java.util.HashMap;
    6 U1 w9 R1 R: A' {- vimport java.util.Map;
    9 C3 K6 ]3 l+ a/ W0 K/ iimport java.util.UUID;
      _! [* g1 J# ?2 ?2 U" \4 d. s7 f& x
    2 H+ j2 K9 Z0 _( G+ L6 tpublic class DbUtil {
    2 O+ w' T+ h$ F- r( {    /**! L; W! p( g4 |1 Y- K
         * 模拟从db中获取邮件配置信息% ^! z( d$ G, D) V: Q1 K
         *
    ) S; F' y9 w+ b* ~; X7 K& P     * @return! n8 C/ L9 l. N+ l  B: {
         */
    2 `5 K( |1 i; t4 k( d    public static Map<String, Object> getMailInfoFromDb() {$ g* S! ~2 u: p$ w. W
            Map<String, Object> result = new HashMap<>();2 z: B  j) E( }& x" U  n# H
            result.put("mail.username", UUID.randomUUID().toString());
    0 [8 V8 A5 R# r# H4 w  J        return result;
    ! k* v+ X) v5 W6 i. J* t6 H$ v    }* p/ {# }( u' _6 }( W: C3 E( y: C7 `
    }
    1 t% r8 b/ u+ V7 a( P9 N% V来个spring配置类,扫描加载上面的组件5 x+ f( v. C/ B$ X5 x* V
    0 a/ a0 j! c" T$ Z4 E9 P
    package com.javacode2018.lesson002.demo18.test4;
    6 L, r4 H/ I5 J2 `
    9 A! k2 R2 i8 _3 {import org.springframework.context.annotation.ComponentScan;5 q) m9 k- u9 {4 ^
    import org.springframework.context.annotation.Configuration;
      r# I1 e* S$ n) @7 C( [1 O7 f0 b
    9 p* [2 R/ V: c& k, |/ G; [& n( u@Configuration8 ^3 }! q0 b( p$ \! ]
    @ComponentScan6 W7 Z7 l; }( u! ]1 d5 {
    public class MainConfig4 {
    - x  j7 Z# y) l! {& N}# a, c; s1 r- y( d1 F
    来个工具类
    & b& e1 o: T/ Q5 a0 s" @0 J& ]) c' A5 @0 y/ p; D
    内部有2个方法,如下:% H$ R' Z. m0 j! h) H: R
    9 |$ O/ |9 \9 \' U* _
    package com.javacode2018.lesson002.demo18.test4;
    2 U/ k3 i  a* x' k$ X$ g/ l$ E( I: r- i. l) t
    import org.springframework.context.support.AbstractApplicationContext;3 d" K  d' Z+ |9 v6 _6 U( j9 d
    import org.springframework.core.env.MapPropertySource;
    ! p  j) \+ q8 \: g8 f; S+ V7 c' t6 [1 T  [
    import java.util.Map;3 X& K/ O2 \3 A6 c" g  {
    ( ~( b7 z$ ?9 V% L: c7 x
    public class RefreshConfigUtil {! ]6 l* a6 h0 _# j
        /**  d! I' Z; r0 F
         * 模拟改变数据库中都配置信息
    " P% r2 _! u7 S     */: N$ W8 d4 Y# ?$ }, P% c- J  U
        public static void updateDbConfig(AbstractApplicationContext context) {, D: o4 y& t8 z* A
            //更新context中的mailPropertySource配置信息
    3 U# Y' b  n  B  e        refreshMailPropertySource(context);
    4 G) `' ^4 X0 U- |& O+ E$ J; ^9 V# k. `; R/ }) v
            //清空BeanRefreshScope中所有bean的缓存
    $ z1 G5 ?5 P! z9 d! p        BeanRefreshScope.getInstance().clean();" H5 |- i/ S0 v4 ?% L/ z" d) B
        }
    2 C" g; O5 u0 A$ D+ U$ f$ {# i  S
    5 ~1 c6 u, t# y    public static void refreshMailPropertySource(AbstractApplicationContext context) {6 G( g0 `; i& B0 N
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();6 `. q, r- i0 U; a. j' }( s
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ' I! w5 u$ z  F$ i2 U) v( G        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    % P  i8 B, H7 F! ~& S" y        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    4 `* A; B4 B1 c6 j, N  k4 `, o    }! U! [! h8 ?* T

    * a) s7 V% E% V0 G9 \3 W% \}
    / U* Q9 ~7 E# U5 B: ~9 N) PupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息$ W3 y4 j8 n# ?* `  H3 d6 r4 Y
    ( Z( M- s' h" r+ Y$ M1 E
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。7 Z: ]/ r% M; W
    / T$ J4 `! n6 Z& Q7 S
    来个测试用例, T' P& V$ R9 _5 z$ k0 s
    6 ~8 U7 e6 D- S/ u- [) ?
    @Test
    ; f' T7 o% z, O: v2 Zpublic void test4() throws InterruptedException {4 I9 A, z% p( Y- N  H+ B
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    5 o& W/ u; l+ e  M1 |, q- P    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    * e6 V# D( ]7 U9 p    context.register(MainConfig4.class);6 w. J0 B" G" @+ t" S& T/ r
        //刷新mail的配置到Environment0 D# S3 p7 f9 a8 I
        RefreshConfigUtil.refreshMailPropertySource(context);
    7 X1 K% M- x3 |3 a3 @8 h    context.refresh();  {4 P- N4 u* B9 `! |) C
    3 I( ?7 K: B7 O/ ^: Y# m- A0 |
        MailService mailService = context.getBean(MailService.class);+ [3 g; t9 k0 i6 N
        System.out.println("配置未更新的情况下,输出3次");
    8 _, z9 Z9 o( Y9 R& B9 [$ M    for (int i = 0; i < 3; i++) { //@1
    8 R9 y& f/ C( D( W' P% y        System.out.println(mailService);4 e: ^/ y+ n* i2 j
            TimeUnit.MILLISECONDS.sleep(200);
    5 `. m& H1 U9 ?1 a+ d    }, z+ a4 w: [/ O0 g

    ' j7 o" p1 o8 T; p  X2 Y+ W  ^6 r/ G    System.out.println("模拟3次更新配置效果");
    + W' c* h7 G0 `' ^5 V6 |    for (int i = 0; i < 3; i++) { //@2
    5 K+ g& t1 @& g2 M; F! o- _        RefreshConfigUtil.updateDbConfig(context); //@3
    ) U- ^* j% I6 M6 B        System.out.println(mailService);: z4 N2 F' i8 V- ~
            TimeUnit.MILLISECONDS.sleep(200);9 l* c1 s! v2 K+ R
        }- }* P0 I2 b. F
    }! b' N% H! p* w. X% W: J
    @1:循环3次,输出mailService的信息& S4 c$ B( a, Q; e; R' b1 D9 ~/ w

    2 h& x8 w& M) p' Y3 u@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    + J9 k1 L/ G% \' c. n
    + [) ]; v  f" v& z* D见证奇迹的时刻,来看效果
    0 |- b% {$ E9 p4 e) ]& g+ E: J2 w7 @' A
    配置未更新的情况下,输出3次
    , r+ ^* ~0 M  z5 r3 V! R  H2 ?/ _MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}8 \% n6 ]8 w0 K( h' A1 p) i
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    1 [, c. w2 q, j0 k* ?+ DMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    4 O* R* M/ |$ Y* S模拟3次更新配置效果2 _1 U& C& C, E
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    ) t1 o. H% R2 g% @MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    3 U- k* ?1 w1 uMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    . |8 a  \* u0 \2 S* [' {* |& c上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    3 V; V4 g  b( o# t
    + X( ]2 Q- _1 E7 N) |" G( b小结! Z, X; Y& t+ f/ C

    5 b  B  F" l. ^2 t) |) b6 S动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。7 \  a; z7 u+ W2 m% T5 @

    8 a. n, e) S# Z4 L, s9 r( G有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    , Q6 U; H8 R& h& o" P7 ]5 |5 U$ ?/ A/ q" _* x
    总结
    $ d" m0 |' Q% S7 Y4 D8 G2 U$ Z9 v" k! j& q
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!4 f  @) y. ~4 N" y

    % Y' X1 B+ U  ]! C1 ]! n  B案例源码1 e) |; ^7 A& K# \  C
    & Y2 l* Q, T6 z3 h; U( q/ l
    https://gitee.com/javacode2018/spring-series9 z2 Q3 J" Q9 r* y/ e/ k
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    4 k2 L! \9 g8 l# K* I
    8 U. X. G' E# B* FSpring系列# X9 G6 e0 b1 ~% J* P

    3 Y+ V$ L3 i$ Q! O1 R" E+ oSpring系列第1篇:为何要学spring?) m" ?7 }( u$ D: O& b$ X. \
    : i& X- b8 P6 \' K
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    9 T, ^, x3 t, ^
    # ]* ]. \, S* P5 ISpring系列第3篇:Spring容器基本使用及原理; @# N: v1 A. F4 a+ D
    $ N5 T: Y) Z& h; |5 }; [
    Spring系列第4篇:xml中bean定义详解(-)
    , T8 K8 ?9 J( [- |& m, Y# q) ~. q
    % G& v7 P6 b* J, b2 h  D/ nSpring系列第5篇:创建bean实例这些方式你们都知道?8 f* l% h% Z; j% l' e

    ; p6 k. t5 Z( i3 \Spring系列第6篇:玩转bean scope,避免跳坑里!
    2 e" T2 e: t' G
    2 e6 k7 B0 \6 rSpring系列第7篇:依赖注入之手动注入: Z4 a6 B8 I. z# o; U" O
    7 _  x) `. W6 }$ n
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
      @$ e6 J0 j- u8 D
    , {! T2 S3 M* C# M8 K* H+ KSpring系列第9篇:depend-on到底是干什么的?
    1 a: W5 e, T1 }
    - r9 k2 s. l- \# x; MSpring系列第10篇:primary可以解决什么问题?7 \3 V. x3 Z. m$ D- h- c4 l& n1 R
      t, l3 c# X. q2 ]) k& u
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?% K# c3 q. I2 q0 B% p0 y

    * s9 P. n! z1 i" q: qSpring系列第12篇:lazy-init:bean延迟初始化
    6 k% `) r3 M  j
    ; [% j! u2 \& n. \* @7 C3 hSpring系列第13篇:使用继承简化bean配置(abstract & parent)
    $ }9 s5 d* Z: Z# z+ Z5 H6 D
    " k: [% X. t; x8 z1 Z3 i# qSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?3 e1 r$ O( W4 z4 a& B' W0 _" W; \, g
    8 ?# `$ ?) T( p. d$ D% C; u
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    . M" _: g1 k" h& {7 e9 s3 b- O" }5 [) U$ C. _# L
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    2 K0 r  O# p9 U# N) f2 ^! G) X% r0 U4 X' J
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    6 E1 \9 T. g- C, c) {+ u% [) h  \2 G# H0 b- Z6 B) A
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    ( W9 b2 L  R5 g  \6 m; Y" f6 S$ ^4 U7 d8 G9 W
    Spring系列第18篇:@import详解(bean批量注册)
    1 P+ z# Z3 X* }' V$ M6 P
    5 w2 }% _2 K4 K* U) pSpring系列第20篇:@Conditional通过条件来控制bean的注册
    6 |4 |! ]1 e. i/ L# U+ u5 \
    # e! ]8 m& W( J9 iSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    9 Z) l% b( @4 C, U. \
    ) ~3 S' a4 H1 bSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解  M9 ]* j+ h8 D( j

    . v' d. U+ w: _* E' h6 x7 F  a5 mSpring系列第23篇:Bean生命周期详解& T5 }4 T+ t" _: w, y2 |0 a

    1 W0 A8 N3 T( e9 L0 v' USpring系列第24篇:父子容器详解
    : t. r/ x, g: T4 j, g
    $ D1 M# C; a' i) c更多好文章
    & H7 }2 q9 X1 v/ v2 [5 S8 T3 ^" _1 P% J2 }1 ~$ E4 A
    Java高并发系列(共34篇): j6 l1 @7 r3 }1 ^' y2 g1 ^
    1 _0 P% X# J" ~3 @  u
    MySql高手系列(共27篇)+ W, Y& T: e* ]5 ]8 O1 g

    ; q2 F: \& ?, Y+ b) o( u. bMaven高手系列(共10篇)
    ; Y& ~0 K+ t: j& S
    1 e9 B/ B3 X) b) G6 S, lMybatis系列(共12篇)+ G* n. Q  w4 F2 Z
    / i  y( P$ D+ k  I( w
    聊聊db和缓存一致性常见的实现方式$ a; I" c& i6 ?' @  ~

    # m" a! L3 G$ F3 _2 U$ H6 d% }+ h接口幂等性这么重要,它是什么?怎么实现?  _, A7 B/ {8 d/ j: v/ e2 G- y

    ! }$ V+ R: W. w4 {, H- G0 r. O1 a0 N泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!- U6 Z  A5 g8 ^+ y! c/ K# f0 g/ {) z$ E
    ————————————————
    9 d! o: q* L+ |' m( i# `' {) s版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    & ]+ @% ?2 K5 ?* H0 G) g3 s/ E原文链接:https://blog.csdn.net/likun557/article/details/105648757! ]3 R. ^6 x1 G' g* y6 Y
    $ s/ I( j3 [1 b7 J. {7 P
    6 b8 q7 V4 ?' Z, {$ L
    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-19 12:52 , Processed in 0.444232 second(s), 50 queries .

    回顶部