QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5534|回复: 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!
    + {9 \, M) t6 g) m4 T: e/ w7 t疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    ; {" d/ T# i! t! y# R8 l, J* c; n( X+ l% c$ w  ?/ N' ~* Q
    面试官:Spring中的@Value用过么,介绍一下* c( i! y* N+ D. x8 @9 M3 Y9 y6 p

    + V5 n- k9 T, B& U- Y: R, ~! ]# v+ d我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    1 o" ~/ J2 C6 W, B0 ?
    : F7 [7 `3 e4 o* v面试官:那就是说@Value的数据来源于配置文件了?: Y" L: o+ l4 \( n) I
    ) D: @: {) f0 H
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    " E( q7 `+ `5 C) v! L) b7 i+ W# u$ m
    面试官:@Value数据来源还有其他方式么?4 ^7 c9 C' c/ N
    . x8 u0 \, L; j' z+ O9 {# C/ N9 V
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    ( i7 v# \4 d- K& L8 p7 [0 L
    7 m' V7 h& u( t. q4 o- ~面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?0 c5 A) p7 }& T
    " O+ x  D* R2 F/ V
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    6 J/ i, d" C: `. ~; g1 i. a) D! }7 I* w6 C
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    ! ^" F, s" E; l+ q" i' V$ V) ^) G4 h8 B8 f) s
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能5 H5 ^4 T0 T/ Z( U, ]; Q

    : k) a5 q5 W. u9 @: H. Y+ m0 K面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?: V/ R0 n+ k8 E/ }- j4 J! V
    : ^1 `( S) c, x) n# e
    我:嗯。。。这个之前看过一点,不过没有看懂
    ) @  Q- V" h8 Z7 _7 Z! J
    4 I) k% e! Z0 ?- z. o面试官:没关系,你可以回去了再研究一下;你期望工资多少?& ?1 E5 ^: U( J5 `9 R. X3 {# g
    , O; [% l1 k2 a: E" T& o* K
    我:3万吧# S$ l2 K5 J$ o" O

    1 H* a/ j. F' M4 m面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    " P5 t+ v6 O, O' X( q6 g7 B# e( O4 |# T9 [/ s, b7 V' S
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    ) S6 y2 D/ d5 V! H$ J- a, M, F
    + u; H/ j$ s" x; ^* d面试官:那谢谢你,今天面试就到这里,出门右拐,不送!& H0 p* D2 e8 Y
    9 x5 S/ [( ?2 p# i
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    9 X/ s. f% `" B0 B* N3 i" ~$ o! y5 E" ]$ w* ?: ]+ Y
    这次面试问题如下
    ; _$ b$ I! A5 P4 R+ Y/ _  B9 f, C3 `$ Y2 J
    @Value的用法- P/ w4 i# S- O+ R$ C6 V' @4 _0 x
      b8 G1 ?+ \3 U) z0 H, e" w& A
    @Value数据来源2 t# Q5 C2 i+ q' ]  c0 c

    , {3 N$ i! B& P+ e+ ~@Value动态刷新的问题# [5 c* E0 B# m- D* W

    / Y3 e. A1 J: }/ Q* k  X9 w下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    9 _7 P4 \' L, d7 D; {
    ' x' c0 q4 e. `9 p. o  u@Value的用法5 L5 E* q% X+ D  g+ z7 `- ~% p
    2 w6 m5 K- u8 S- h  w" `( p1 t
    系统中需要连接db,连接db有很多配置信息。
    4 {; e: V, e! {1 ^, a. J
    # d/ c. ?8 j! @% [- v系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    . Y2 I0 I3 Q9 Y0 f; v7 o: ]3 n' L6 D3 K$ _5 F4 C; C) D& ^
    还有其他的一些配置信息。
    : [( F! r$ k% u/ K/ I# |& }7 T3 P) G8 J, O: r4 j4 O% U
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。0 z3 @8 T# @& _; X" P. ~
    / B+ w* J3 w0 A
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。8 H/ G2 U9 c7 ^( n9 A$ f

    - ^+ Q7 M/ _/ ?$ O3 U! M1 d# n$ l通常我们会将配置信息以key=value的形式存储在properties配置文件中。! n' K& p1 R! I

      J! b5 C4 g9 T+ Z通过@Value("${配置文件中的key}")来引用指定的key对应的value。. y! K# N, `) [+ M( V3 f) \
      D4 m1 j6 d- y1 O  O1 ^, _) P; P
    @Value使用步骤
    ! M( _9 _- b& x6 y% n  P% h- m* F. I% [& h9 v6 E+ s
    步骤一:使用@PropertySource注解引入配置文件) C1 V* h* t: r  U( y

    % h1 Q* O% s( U# K) H将@PropertySource放在类上面,如下
      n& `" c4 w, W6 |- s1 J; i/ s  B2 [" q  K; ]
    @PropertySource({"配置文件路径1","配置文件路径2"...})% a  m* @5 s; g0 ?7 u0 V
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    ! D9 ^, n& ]) m& I
    ; _3 n( M7 |0 A: t6 V如:0 m/ ^: p6 P2 D' ]0 E" @$ R

    " o6 s+ X7 Y8 L/ z  U@Component
    8 J# \* ^1 l( E7 L@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})4 Z! g7 y9 e. D
    public class DbConfig {8 M, n8 N" k: W# ?. B
    }
    : X- k9 z  N2 B步骤二:使用@Value注解引用配置文件的值
    ' d- q  d, o  K$ f. f/ `% A+ p0 H( `2 F+ ^" l/ w+ ~
    通过@Value引用上面配置文件中的值:1 o# W4 i. V6 P! d1 }

    . T8 q7 t, R5 L0 W3 A$ k语法
    & D" L# ~0 A, @: ^1 b
    8 \8 {6 C! q  R1 X7 H. ~, u; j@Value("${配置文件中的key:默认值}")4 j! s0 G5 {. ?$ i9 n0 E- x8 J
    @Value("${配置文件中的key}")
    ' s2 e" C# N6 O7 h如:* z# q8 m. w# a0 J% [2 u
    2 u! K. v1 a. ~+ n; p
    @Value("${password:123}")
    8 C$ H! P$ F, R  K% p上面如果password不存在,将123作为值
    , A# D* b9 v0 V/ g) s3 t  `
    + h6 t3 _* h% u4 S6 [@Value("${password}")
      G& |* g5 r7 s2 o9 D# W; h上面如果password不存在,值为${password}
    , p, A# F: J6 M
    * k3 ?7 s& J( I7 _假如配置文件如下+ g7 b2 ~) X1 F& F+ E6 _9 t) U
    ' i* `$ N4 M/ _% k" x
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8  [$ H5 v: f' i2 A& `) L) ]' @, r
    jdbc.username=javacode" T( D5 a4 R2 ~! _
    jdbc.password=javacode
    4 n- H7 s" Q0 i0 ?使用方式如下:( \; f% M. Y+ t- H2 c

    / h( g) L( g& s3 I6 l8 v@Value("${jdbc.url}")
    / k, ^$ J% Z9 Fprivate String url;
    5 O% i" E9 {7 V. O& z; E1 _# e2 K$ Y% o6 I& V4 K2 \( B* o. `8 `9 H) @
    @Value("${jdbc.username}")1 ~$ j) N$ U% i9 H
    private String username;* _' g- W. s' c$ D9 b% Y

    ! P# t$ M' ~1 G@Value("${jdbc.password}")$ t6 x& A8 T4 Z3 ]' }$ s2 A' o8 m2 R
    private String password;: d, ^3 o5 G2 g0 }' |2 l. C; `7 l/ R
    下面来看案例
    * C$ ?5 m7 U, C1 Z$ b6 w
    & g! Z: g2 }( @) b) f案例' O' m6 U  @7 A' @8 w$ n

    : ~8 x$ P9 V( u+ U6 Q来个配置文件db.properties& E1 a+ }' o. `6 f

    0 o! {8 q: D6 U2 u4 f2 Q  ujdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    0 z$ H0 R# M4 mjdbc.username=javacode
    3 I' `$ t* a: Ijdbc.password=javacode
    0 ]! ?8 ]) v$ Y% `) T; D来个配置类,使用@PropertySource引入上面的配置文件+ ]5 ~; s5 r8 j5 o* ?

    / C" P: x+ x: h5 I5 Rpackage com.javacode2018.lesson002.demo18.test1;
    / O4 D6 m* ~: G" q# U2 |6 y5 x& m0 r/ w1 v" D
    import org.springframework.beans.factory.annotation.Configurable;  I) ~( `5 \6 F" ?
    import org.springframework.context.annotation.ComponentScan;' Q* L; w7 w6 a% z. c9 D" U1 M
    import org.springframework.context.annotation.PropertySource;
    : ]2 g& H) z' J! W
    & t% {& i7 m9 B$ ?0 h: O@Configurable
    4 w; H! N9 X" I' ?! `, [@ComponentScan$ ?  I8 u9 Q/ |# g: ]/ _
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})  N/ b' Y6 \" f. {: }' N: S9 u7 @1 J
    public class MainConfig1 {
    3 m- c$ B, b8 c; t! S}
    ; {  A. D. v, A: C* k来个类,使用@Value来使用配置文件中的信息
    0 V; R: D: E% y" _3 l, |4 P  Y- {9 K3 i+ Q. t" B5 o. d6 k
    package com.javacode2018.lesson002.demo18.test1;
    8 b; z: k$ r1 ~; O5 u8 q; |0 d( L5 n. x, h6 o
    import org.springframework.beans.factory.annotation.Value;* S; ~+ G) S! y2 u0 K9 L2 A: Q
    import org.springframework.stereotype.Component;
    - X' [6 @  N* j& \! y6 b! ~2 a9 Y* ^. H- q3 w# Q
    @Component( N' o+ w- f5 m4 R& a
    public class DbConfig {
    0 u4 C$ x* M: z0 }, C* _- m4 u" t: B3 g) m/ S2 `) q! {
        @Value("${jdbc.url}")+ p1 H* p& X/ T* t$ |1 u$ a% j1 V
        private String url;
    0 \$ J1 q/ J  t) f7 G# [
    ; B+ T: h' Z- t4 q; Q    @Value("${jdbc.username}")! c$ J5 n* S! {# B( P# ?/ d+ }
        private String username;
    4 X1 `" i0 p/ c( Z# x  [: F
    4 u4 u% G# e1 N; Y    @Value("${jdbc.password}")3 U# u0 v9 }3 B/ d/ O
        private String password;
    & V/ K0 w9 [! o9 B0 v: x0 H% y" q! w" `/ ^0 `& |1 S0 g$ O5 g
        public String getUrl() {
    9 x' s0 f3 K! ]2 q4 T        return url;
      C& x' }* D0 @/ X; Q9 s* W    }
    " t) {* ]5 o1 X% B+ s8 Y' L# d# C6 D9 J
        public void setUrl(String url) {& ~% `& _) N) M5 D* O
            this.url = url;) `! g2 a/ O5 W4 h9 K
        }
    3 ]5 T5 O# U2 C9 M) q% ~. \& `( X
        public String getUsername() {
    3 X% K' C9 ]9 Q+ f+ r        return username;2 P- ?! p$ u9 l$ P2 E7 s
        }$ s1 p: f( y* x

    8 N2 R' {8 F1 Q. v/ k( D    public void setUsername(String username) {
    7 ~4 J  n# t8 \% O4 C        this.username = username;
    & }2 b: D9 i4 @! y; {2 q# K& [$ y    }4 V$ n7 T8 ]1 q: F

    % `6 I* U4 L* n$ e9 A    public String getPassword() {  F) t* ^6 y" ^# ]
            return password;
    4 _% p6 R* X4 A5 x6 e    }
    0 k* m- x8 ]6 ~; B! E4 x) \& u5 C! N1 t  Z4 p' e; e7 R
        public void setPassword(String password) {
    ' `* t# S. @& x! ^$ ~" ^5 [+ r        this.password = password;
    ( B' Z1 k0 {/ @    }
    8 S& z% [6 _* D/ S% b
    ( _# t! q" o( H6 t; J1 D' ^" v: c& x1 M1 V    @Override  A" `/ n) \! X; X
        public String toString() {9 i( w7 B" E* `8 s/ l
            return "DbConfig{" +/ D) m% d0 O* o+ H% L* [
                    "url='" + url + '\'' +
    9 N% e7 n. b  [) _; f# q                ", username='" + username + '\'' +- C6 B: a) L' f% u. n- N1 L
                    ", password='" + password + '\'' +; |1 {5 X1 H4 X/ ~' L1 `
                    '}';( u$ \7 W: }3 G' e$ r  J1 ~4 [# P
        }
    5 C, N# q; u7 Z9 k8 B: e, Y; i}+ Z  t. l/ K& t/ l  H2 h5 o
    上面重点在于注解@Value注解,注意@Value注解中的! D! @3 y1 l/ x8 \
    : v6 i# E, {# [; E, M
    来个测试用例) b: A( y  ^# z2 k; t7 ]% d$ [0 U
    # X8 N% `; |* C( ?
    package com.javacode2018.lesson002.demo18;4 `- T* t7 B$ ~& N8 O1 y2 _
    7 }. f4 o. i( z1 D
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    : s7 L3 k5 ~' q; ^1 x! k. H! Oimport com.javacode2018.lesson002.demo18.test1.MainConfig1;3 @" m: Y* E3 o  @; J: P
    import org.junit.Test;
    . p( f' p3 S. Z7 ]0 v( W0 J7 cimport org.springframework.context.annotation.AnnotationConfigApplicationContext;0 W! |# a0 n6 x! [" q4 n) b/ e  i4 r) P

    ) K" F; |# v6 C6 Bpublic class ValueTest {6 L. n8 {4 z0 O

    % |- i+ s# B  j    @Test
    - j1 Z! L9 K1 k3 j5 u# }* n    public void test1() {
    6 L7 d  O# k+ M4 b  C        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();- F0 N* Z: G' o
            context.register(MainConfig1.class);
    % I# U: u- m6 x        context.refresh();
    ' x. F( d/ ]2 q
    2 x0 {) a; f8 r  n# l        DbConfig dbConfig = context.getBean(DbConfig.class);/ B3 U7 w& n8 Y, u
            System.out.println(dbConfig);
    : \8 G# Z6 A7 g    }
    & t+ X6 b( D6 I; D9 V}( T7 w6 O/ v, U; m
    运行输出+ U& T+ B, m! c
    1 x- J' N' s+ z& g  W- N' w
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}; s8 F2 V1 }8 i/ e/ q& r8 @- W
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    4 v, S- G6 G& i/ ^
    0 N% L" o; \: e# E@Value数据来源
    # p* g: [, }' G  z/ u' i0 N+ f: ^$ K8 y) c+ h& M# Z/ d: R
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    1 Q: @4 U' K8 ^! U0 z/ Z6 G7 N* _4 P
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    9 p! ~+ O7 s( P' t3 w; J5 ~: j; x1 ~
    7 ~9 S! _+ a9 f# `spring中有个类# `% h' P% }/ _1 J) |6 h- w
    0 o# e" J& G+ P) }$ t' a0 \
    org.springframework.core.env.PropertySource( E& U& }" s! R0 |9 M. ^: P+ O, n
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息/ U- r5 o3 E8 C, L5 ]* n

    8 H% u! p  e* Y' O; f# j内部有个方法:4 }' j! S% X8 j9 b% A, e
    , P, I" ^- H# V- u
    public abstract Object getProperty(String name);% N0 Q% {$ b$ E' W
    通过name获取对应的配置信息。! N: y* G0 Q( P( `/ ?
    # o+ d8 W3 {7 a7 N+ m$ R
    系统有个比较重要的接口6 J0 q% m- ^. M# `& u: U( P4 k# V
    ) C3 V5 a6 t7 m" ]8 z
    org.springframework.core.env.Environment; U3 a3 K7 E2 Z8 \3 y
    用来表示环境配置信息,这个接口有几个方法比较重要' b% _3 Z* p: o% V0 s8 z

    ) o0 W4 E7 R; ^  iString resolvePlaceholders(String text);
    0 A: L" [  D# C3 C$ b( |! `: e* A! FMutablePropertySources getPropertySources();
    ' u  _/ J6 C3 \( b" L; X& kresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。( G( Z  L% P7 ?- O6 o6 V( d, z
    # w6 U! q0 u" M; W0 i5 N$ f
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    " K  k$ q# i! E5 o# ^
    # J! m& j, J2 g' \7 N& N: @public class MutablePropertySources implements PropertySources {: N! W- T; K3 k% _; h! d& M

    5 _/ h' c( s5 {" [    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    7 G. \. \! _) o. y. S6 n: [# I- y7 }) ^, Z, h0 ~& G" ~5 v* C1 ^
    }' x; K! j+ d1 |$ i
    内部包含一个propertySourceList列表。" y, V% h- q: S, D
    8 ~+ @. E/ a' k/ I, s) v* S
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。3 u# F7 p; l; `" G4 `
    7 a" [& V9 N; j! E* m$ P% u# o
    大家可以捋一下,最终解析@Value的过程:
    # c/ [7 c9 n; H6 z) N, f, m& X, ^9 }+ a
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析) l; }8 n; A7 N. p
    2. Environment内部会访问MutablePropertySources来解析
    " \' {& V9 g8 v9 ?0 A1 i3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值6 O0 l/ ?7 _# O, x
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    - U8 ~( ^( j9 n2 J! a
    2 T) R( L) p* S/ y) x- d下面我们就按照这个思路来一个。( @5 r) {  S& D; w+ d

    3 G# [' x) Q! {1 f* v# V来个邮件配置信息类,内部使用@Value注入邮件配置信息, B* }  n; h& C9 {' \1 S8 `

    3 P' ~) U$ W' f) o1 Q, d" bpackage com.javacode2018.lesson002.demo18.test2;
    - i5 w" Y. |! _0 x" ^
    # T# ?$ Y8 a6 G7 D: G; w6 Z  Qimport org.springframework.beans.factory.annotation.Value;
    $ [% z, o6 {2 |3 b4 \import org.springframework.stereotype.Component;
    . T, O4 c8 D! }/ P8 s, i7 I" J  J5 Q7 `& a. M
    /**7 q" t8 A, ]+ G( s
    * 邮件配置信息# L- Y' ?4 J, A, J
    */7 x7 r7 o3 v8 ?$ o3 j: L
    @Component7 i$ }9 E/ J3 Y' w' @9 X0 m
    public class MailConfig {
    9 ~, k! f# d+ ~+ S. Q
    / v( j$ w6 Z* x- d) T7 }+ s. Z    @Value("${mail.host}")
    1 J4 @: H* I5 @0 S( ~    private String host;
    ; @! x3 S$ o& ]. Z5 s" n5 L, ]1 Q- d) @; u/ k7 E( i7 I1 C* Y
        @Value("${mail.username}")
    ) A5 D+ n( q2 n0 @+ z3 K    private String username;, n3 i: I0 `6 {/ L; C

    1 A, C" `7 L' q2 n! b. s& X    @Value("${mail.password}")& G9 b' ]) m  T5 C+ d! R
        private String password;2 r( ]; ]7 m0 i6 K7 y! j, y
    & v! M5 d* k) |0 K- z
        public String getHost() {
    ( |; A0 O3 E! f6 \; O( P, g5 m. G- F        return host;/ N% }. d& q" H
        }1 p9 x; k1 v7 Q! ^! x$ h: i
    0 Y" G. D& O1 v/ e5 G5 ^& K  B; u
        public void setHost(String host) {$ |* J4 D8 }7 s" S1 I0 c
            this.host = host;6 D4 s+ o2 c" x: @. c6 X( ?/ A
        }
    5 Z7 q+ S3 e7 ^3 y/ E7 T% j  W% K, z; ?- F) b% H2 j
        public String getUsername() {
    * J9 Y/ ~- r/ B; p' U" V: z        return username;
    0 ^% b( x7 t+ a7 J# E4 f    }0 s+ U. f+ t% I
    0 w; E7 s0 M5 [2 v
        public void setUsername(String username) {5 b5 \* r4 h4 J3 s7 P& t
            this.username = username;7 x. _( ^( P& x+ j. A' y. l
        }
    $ Q) F- y; T$ P0 t: m) y4 L$ M6 o
        public String getPassword() {
    8 k# a! n8 M) ~' x" o9 o+ L( T        return password;- ^$ W9 m' O$ l/ O" P6 u. k7 O3 h
        }# f2 H- Y+ P! F! j1 D

    . f5 I" `: o, D0 V# e2 B7 ?- C    public void setPassword(String password) {# C' l' Z2 [' @! e4 R- C( g
            this.password = password;
    ' y9 }! m* T: m2 b    }
    5 c, H% _  w" o! H, v: \, ]& g
    ! t; n0 v, Q0 i# k( }7 |5 T8 O    @Override, P: z1 M' k; A4 q. I
        public String toString() {  x7 C3 u0 B  F
            return "MailConfig{" +5 s7 g% Y) Z. ?4 H" Z* A$ ]8 `
                    "host='" + host + '\'' +( D/ |! `/ B5 Y+ `8 @
                    ", username='" + username + '\'' +( P; N3 p5 _/ g. D6 p
                    ", password='" + password + '\'' +
    + i' ?3 m, Q& _3 n                '}';2 _4 T% v: O1 l5 Z
        }
    6 |. E- F/ a% `- y8 a}3 C& V6 |+ `9 u$ Y+ i9 Q* h
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中2 z" T4 f$ w1 `7 q3 `( y+ p
    ( ~( X8 b0 K$ c, }' w+ f$ K
    package com.javacode2018.lesson002.demo18.test2;
    ! Q' f, r. _5 i7 |+ o6 `8 S$ m, N
    import java.util.HashMap;
    + I8 ^) d3 S9 u# K& f# I7 E6 `import java.util.Map;
    8 ^2 y' @( }5 o7 O$ P% m, O" a' g4 p" E
    public class DbUtil {
    8 m! b+ A4 D; A5 ?9 j2 ^- I# b    /**
    ! _$ I: B# ^. l: Q     * 模拟从db中获取邮件配置信息
    : }5 p4 i! J7 ~2 `& j     *
      u; J7 Z1 s6 e     * @return1 ?( c/ l1 G9 ^3 q- k5 f
         */
    ! D$ k% D! }5 ~; L; z. L" b/ P    public static Map<String, Object> getMailInfoFromDb() {) B5 M  Q2 R5 c, l, B
            Map<String, Object> result = new HashMap<>();' B& ?% g6 ?5 U9 G+ [1 ~9 c
            result.put("mail.host", "smtp.qq.com");5 }; G8 s& d9 l* Q: y- ~
            result.put("mail.username", "路人");
    0 Y6 h2 E  s5 ]* R, j) y3 p        result.put("mail.password", "123");$ m3 y4 D4 G# u" ]* p* a( `, Q6 c; X2 h
            return result;
    ) I7 f$ m' a- A1 ]    }
    1 {7 A' P4 x3 q6 }1 x7 U}9 z" H" |) p0 S
    来个spring配置类
    6 ]! _6 G0 o. w# X& T
    4 U, I% m# a+ kpackage com.javacode2018.lesson002.demo18.test2;
    0 {* s. x: Y: Z  a7 ]8 L
    6 h# g9 W7 o8 u! ?import org.springframework.context.annotation.ComponentScan;
    & V' e4 T2 o7 U8 `6 himport org.springframework.context.annotation.Configuration;( z/ N) z: m5 h
    . b& A8 p4 I: v; q$ ?# u
    @Configuration2 F7 G/ y8 A7 d  d* G. i
    @ComponentScan
      v* ?. y; i8 C- ]public class MainConfig2 {  I( f$ {4 U# u- F) @/ z8 Z2 |, g
    }
    / K- t5 J% T4 c9 z2 n7 d% A8 ]下面是重点代码
    2 u7 @5 w7 D! n) E8 R  i; o
    ; j- N# X+ ~3 N@Test
    : s( C: H4 e( u: B$ Fpublic void test2() {( x3 o. {) l9 b' _* w! E( P  e! ^3 {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    5 ]1 K$ @/ f6 U; h7 B+ Z/ b
    & D; M. c& F9 z: R4 S    /*下面这段是关键 start*/
    . ^$ F7 }( d4 J* e; |    //模拟从db中获取配置信息
    4 N: I% I6 J( B' @9 Y9 P    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    3 K$ y9 @  h6 ~1 q; m1 t* Y& F    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    6 u8 n# g2 M5 |5 H2 u! N! H    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ' b& I1 d  c$ f- J1 X    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高8 `2 H8 ?4 K5 b- a: @7 ^
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    4 S) b7 E3 [4 h8 N" P' r    /*上面这段是关键 end*/
    * ~2 K* |7 D* i/ M
    2 ]9 P5 a3 ?" e* S    context.register(MainConfig2.class);
    $ N0 ~1 c+ W) }: \2 l$ U    context.refresh();
    - |& i' |' R* g$ h# h& z$ X    MailConfig mailConfig = context.getBean(MailConfig.class);& ^( Z2 A7 H' t' Y
        System.out.println(mailConfig);+ a, i4 i4 Q2 u6 ^  l
    }
    ) j& @* k: _8 \, O; ]' y注释比较详细,就不详细解释了。
    5 b8 L! m% x5 p, K7 t- q! `% V5 v
    直接运行,看效果
    % {& P, T) _- _! k1 K, r2 k
    8 i0 I7 B7 L) A8 k( OMailConfig{host='smtp.qq.com', username='路人', password='123'}& t3 t" f) T' d2 Z$ r8 G
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    & H, H6 Z1 H& C" m* M4 A& Q6 ?" z; R5 r7 ^: n
    上面重点是下面这段代码,大家需要理解
    ) p4 F8 p" w) y7 i* }' t; P5 }6 |: C8 u4 }8 P5 \4 K1 @9 v0 k
    /*下面这段是关键 start*/
    + v8 q- M! N- c5 ?5 \: ?; V3 k//模拟从db中获取配置信息8 J' C, c+ m$ e& ?- T/ j) R
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
      }, k/ ^. ]/ [- J+ S6 X//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类), Q3 S' ]$ R8 f8 K( F( B
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
      \  i: F9 v+ ]+ y+ U//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高6 p; T0 M2 p3 W' H- p1 n
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    1 k1 h$ s- |; K) Z/*上面这段是关键 end*/- W/ l# k. R# g3 Z* \- L& c  B
    咱们继续看下一个问题# E* W- i  g! q3 C) L
    / O/ r7 p% c3 |" C
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。7 e4 P, V8 G# T: `

    7 u( v2 |6 b2 G& j. p@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    5 t) V9 u& D) M' k: P; ?8 a7 a% l: @: B
    实现@Value动态刷新
    ( W, {' K- R. e+ @) x( Q1 d& Q8 \
    先了解一个知识点
    & {3 F# {- F9 N; M( o  h: @2 n/ ?3 l; {) y9 s& K
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    / h! J; v' j) X% {8 U
    , V3 s4 Q& C! |& N5 W. \这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    3 B4 @( l% K& M% [& k  N3 F: n" L# k9 c1 |
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:! |# C; {( C; _1 h4 s2 q
    , P5 V9 B, N: k
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;! h6 n# f: M2 W# k5 [+ o
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    5 f. u, w2 e! R0 }* v$ k; U$ U" M0 T, [; B
    public enum ScopedProxyMode {5 t" I, C  d  G- x
        DEFAULT,2 _0 p: A. O) ]  {5 p
        NO,
    7 g/ K) q* h' N7 w    INTERFACES,
    1 n7 G/ g  w+ i8 }; k. A( l    TARGET_CLASS;: n7 P5 q- K! B" R5 R
    }$ }$ w7 e( A( W! P
    前面3个,不讲了,直接讲最后一个值是干什么的。
    ' H) z# b- C, w3 ^# C# N2 `! \2 s0 T8 J/ k8 q' ?7 k3 b6 Y
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。0 z" I; R$ X1 m  [$ E: D

    2 J% p# D: m9 Z理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    + Y# b! L1 q" d2 j# N- |, Y% d0 b  X4 Z2 Z
    自定义一个bean作用域的注解& E4 `" e  l; A" C! P0 A

    " R# g* g* X% g  G1 n) G/ X( W' ypackage com.javacode2018.lesson002.demo18.test3;: Q- S" ~, }: Y* q: v9 n
    - L* S( f% U5 O) e0 x& m( ]
    import org.springframework.context.annotation.Scope;; T- e. H6 o3 n. m0 T! J
    import org.springframework.context.annotation.ScopedProxyMode;+ A- g+ V4 e+ h# c5 h% C9 s+ _* `
    / ^) H! A( P$ U
    import java.lang.annotation.*;
    ' E  {/ f( L& t4 x5 H' z5 V3 n5 a. Z) b5 T; |7 A, z" @
    @Target({ElementType.TYPE, ElementType.METHOD})" {2 l- n; x- s" S2 _. n; r
    @Retention(RetentionPolicy.RUNTIME)
    0 a3 {" i' L- I& Q9 M, V2 Q1 c@Documented! D( _) b: ~1 [7 k7 S
    @Scope(BeanMyScope.SCOPE_MY) //@1
    9 J% K. R3 t# z5 \* d& Z6 k' K$ ~) Xpublic @interface MyScope {- o/ Q" G* a% g% ~7 F* \
        /**
    7 K. H& v* P( J4 P) X; Z     * @see Scope#proxyMode()5 A; F" W( f2 @2 n+ ~  C% O
         */6 m% Z& B( G! E% P) \) s
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    4 J) D% V* \  V- W# X4 w) u8 q% z}; X8 G/ L1 I/ u+ X; w, m
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    : J( ^: K- S2 p4 j
    0 @4 Z" v8 g; `4 @. ]7 I& B1 R3 f$ v" I" c@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    6 W1 L/ `8 b/ j0 h! ~! V# R& `1 P0 N3 o% G7 j1 a( U
    @MyScope注解对应的Scope实现如下/ Z' I8 F# N% S+ K1 ?

    % @! \6 `) `. h& a7 tpackage com.javacode2018.lesson002.demo18.test3;
    ) }# q% E, N" ]8 ^9 K1 r' B8 ~  r0 B5 C1 J: N% c0 T8 U2 ]2 q
    import org.springframework.beans.factory.ObjectFactory;) D/ Z; q8 C/ d5 W; [( [
    import org.springframework.beans.factory.config.Scope;" j5 d' g) D8 |0 X" f+ ^
    import org.springframework.lang.Nullable;
      r& E5 u* N6 M+ ~1 T! r; O) R) G1 w, X: E2 Z! F6 r# u
    /**( G! F5 o# h. M0 c& W, ~
    * @see MyScope 作用域的实现" c: z) ]& I& X, h
    */
    . f, D& h1 ?: Q6 U6 Fpublic class BeanMyScope implements Scope {6 p" h% H: N5 h0 U
    # g6 b$ a% d, u, O' x
        public static final String SCOPE_MY = "my"; //@1
    6 i3 N. E7 l5 Q/ ?* H* t8 c4 O" Y9 f; N
        @Override6 r9 h' V; A0 I6 }( o# K! Z
        public Object get(String name, ObjectFactory<?> objectFactory) { 3 {( g% _! I: v% H
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    4 H1 F7 A  h% v- V  s0 a# U  \        return objectFactory.getObject(); //@3. B- b( Y/ a8 w8 a, }/ L* {0 X1 P
        }
    8 ?& N1 P8 S" c* k- T- ~' A1 x7 y! }
        @Nullable: `! m( f; P. W3 O, D& T! U
        @Override
      C' M% B; r% X' j    public Object remove(String name) {1 h' h; H. @( r) |) h/ C. @
            return null;5 G* f1 {) ^0 [6 j
        }: o7 R& F2 C- W/ D
    ! F+ x& b* z5 R. C6 Z% H
        @Override2 r* J7 n$ \; I! u& f3 f$ ~5 P$ y
        public void registerDestructionCallback(String name, Runnable callback) {# P1 N$ ^1 O. B

    ; f  W; R) L5 A' w2 C    }
      o" c- [" y& @- Y5 S; e4 \# S: C" f
        @Nullable& k& P7 s# o8 M% g- ^' I/ ^5 Y
        @Override9 z0 {: a  D0 z5 E6 Q1 d8 u
        public Object resolveContextualObject(String key) {
    ) z- F$ h/ y+ g, w3 x7 B* i4 o0 m        return null;
    $ B0 `0 S+ L6 Y( e. [    }5 J. [- G/ ^, I
    / `' K- D/ h. B  t- ?! s7 b
        @Nullable
    & m6 D# J. M0 T    @Override
    , v% e, W0 U" k( I! d) c6 x    public String getConversationId() {; g8 X- C6 \" Z4 @2 {) h7 N( U4 O9 W
            return null;
    . g# t0 _) y, K$ V: ]! O* o    }
    ( [6 {6 Z/ |/ A+ T& \# Q6 l}
    1 l- z& g8 B! X- o) H@1:定义了一个常量,作为作用域的值" f( W5 v8 M+ u2 O& D/ K

    0 B/ i$ F$ G! k- _@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    & @" |, E8 {6 |7 }: o7 h5 y/ c$ q" P1 B
    @3:通过objectFactory.getObject()获取bean实例返回。
    , p4 L- t8 q+ e6 B- d' S( A2 j! y. e& t
    下面来创建个类,作用域为上面自定义的作用域
    , C# k# i1 v, Z$ S8 D" k7 u3 E  P3 R  g9 l5 P! }6 ?
    package com.javacode2018.lesson002.demo18.test3;5 c8 R3 L6 r# \1 I3 x

    # }! W+ H0 e3 f2 z+ s; z, ?import org.springframework.stereotype.Component;
    # q4 n2 D- r6 M' u( L; c
    6 o% k/ X6 e! n! N3 K) dimport java.util.UUID;
    # R" {- _. u/ [! ^$ m# B3 d0 ^" W1 }' S* g5 Q' S
    @Component
    4 }9 y7 R4 S& {% F0 ~3 U@MyScope //@1
    ) z' }& o8 O2 Opublic class User {
    6 ]: a3 O& O$ W  e! i* [0 P: }, e# d  ?* J) @
        private String username;
    ' a4 u1 O9 T) ?% c/ v" s/ e2 b& [1 D1 `, p) ~; X& b# t
        public User() {
    6 }$ O& W) q; M( |4 P( n        System.out.println("---------创建User对象" + this); //@2. u# w) I* l9 a$ O
            this.username = UUID.randomUUID().toString(); //@3; @( Z# g# p/ c, A
        }
    & P' |' `' ~5 C: B0 l
    + r1 @. q4 s2 H9 O    public String getUsername() {$ \- m$ [  X: M+ j
            return username;
    5 C/ m3 X0 Q6 ]7 C) Z' J6 M  `# B& o8 \    }, A7 N" Z6 G9 S. e0 q
    & d- C2 m9 x% l2 p: T2 x
        public void setUsername(String username) {7 F, X0 D+ s9 p9 i; Q" \
            this.username = username;
    6 U+ a# d6 s* R/ Y2 g7 S6 F! L    }1 L0 p: t, ~+ Z) B! G2 `+ F5 ~

    4 x. ?9 \; y7 Z) b7 A}5 [( u5 L+ ?3 y* ~' `
    @1:使用了自定义的作用域@MyScope" t' M/ ^! x, F8 V6 s! }
    ( e. H! `( \* Q( P% V7 g/ [
    @2:构造函数中输出一行日志
    ! ]9 H( |6 F) t4 c0 U" b, n4 y6 J9 _' d' l
    @3:给username赋值,通过uuid随机生成了一个8 S  K/ H" D! z2 c9 [

    3 \, f# W$ ~" ]# r来个spring配置类,加载上面@Compontent标注的组件# l& n0 x) |( }6 p
    ; k% b" y4 U, Q, e  w
    package com.javacode2018.lesson002.demo18.test3;* L0 `7 D2 m8 T, R2 X
    % D1 ^/ }3 d. _. n0 l
    import org.springframework.context.annotation.ComponentScan;/ S' W. [7 i; C
    import org.springframework.context.annotation.Configuration;% V& \4 a' v" j! @# r( p  v
    + x+ O  U7 K* f1 m
    @ComponentScan
    4 M! Z; T( ^8 z4 d& V@Configuration! P) r4 m) u$ f
    public class MainConfig3 {
    ) b  H$ B" s; D. b}
    6 Z+ |5 Z: x2 d; t7 D6 x) V& j下面重点来了,测试用例
    % K1 F( o+ Z, u9 S7 j. t5 J. E% z) {  q+ k' P- {
    @Test
    ' _6 V4 S# j5 wpublic void test3() throws InterruptedException {7 U: h) d5 W" W4 Z+ l9 `! n" _( T
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();* v9 `) J% L7 j6 m
        //将自定义作用域注册到spring容器中
    3 Y! x( W5 v) u7 O' Z$ |    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    + r( I% u6 }# g! A3 n7 ~! f' z; x    context.register(MainConfig3.class);
    , t. w, l# F2 l0 D    context.refresh();
    6 M" U& r  r5 x$ q
    4 N0 x' g$ Y5 P  Q1 \* P8 M    System.out.println("从容器中获取User对象");: _' U7 N$ o6 @! e; M) d/ [" h
        User user = context.getBean(User.class); //@2
    ! D# F9 v/ E9 m8 e8 P    System.out.println("user对象的class为:" + user.getClass()); //@3
      q; q) I9 q, U1 p) O& g/ p1 G
    , I& q; p% f0 z- N: @* a, d    System.out.println("多次调用user的getUsername感受一下效果\n");
    8 w) U; N% F1 f- P1 b9 A6 X    for (int i = 1; i <= 3; i++) {
    " L7 |1 ~% W0 V# x6 E        System.out.println(String.format("********\n第%d次开始调用getUsername", i));6 W  o  i- J- L% y" C; `, x$ P
            System.out.println(user.getUsername());
    5 g, n/ L7 \) b# w- _; Y  n        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    ! x  j; v# c; N# H# l6 ?    }$ W' T* {- q1 s# v; I
    }
    ! J) `% d) T6 X+ ^@1:将自定义作用域注册到spring容器中3 C1 Q# c. a; D2 h" m

    5 q) M- i/ @+ m% C3 h9 i5 U4 k@2:从容器中获取User对应的bean
    ( g9 I; q5 E4 @: W1 K) `% |" D( j4 e3 ~1 [4 L2 R
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    9 [$ O2 M9 ^0 |% f2 U. p0 F0 o% H+ D# Y8 a0 C
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。3 p1 `+ \& f9 O. j/ S/ y: H" a/ g

    ; r* r3 g7 x3 V+ O, U见证奇迹的时候到了,运行输出. g# a' M. J4 `6 a  f
    # y5 Q+ r, c5 `8 ^5 O
    从容器中获取User对象2 j4 [, X. ~8 T: B" J# ]
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    " E" P& x( y; O多次调用user的getUsername感受一下效果
    ' Y# P  p  T# |. {$ P( C0 ]% h& G/ u1 {- a
    ********
    " T  [# Y. u6 a9 a! z- u第1次开始调用getUsername
    8 ?/ ]+ \$ `2 S1 \4 Z1 b2 cBeanMyScope >>>>>>>>> get:scopedTarget.user
    * X5 D) s- z' F# k* W! U( c---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    4 e& u; {  k* H+ m5 S8 }7b41aa80-7569-4072-9d40-ec9bfb92f438  j, N/ }1 F' v7 p
    第1次调用getUsername结束, {+ _7 r7 R' J# r- [) \
    ********
    7 g, h$ E4 d9 L7 U1 b! W
    % |; k! L  z' O********1 \5 h# e- P6 [2 K$ h1 `
    第2次开始调用getUsername  o+ [5 a, ?. L- ]" y4 P, ?
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    ) A* x7 J: B1 T" q---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    6 N3 H1 ?8 R. A1 j01d67154-95f6-44bb-93ab-05a34abdf51f* N6 ~' E' g+ n* {; K/ d" x0 G1 B9 [" ?$ {
    第2次调用getUsername结束
      u' N4 T. j' q: J" d********# u/ V+ ?! j$ P2 p

    * ?4 A0 G3 Z- f* w' Z********/ w" ]0 H9 G1 v) t+ l
    第3次开始调用getUsername
    : M) Z: h/ k3 T( W4 v- iBeanMyScope >>>>>>>>> get:scopedTarget.user9 _" |5 @2 D, k* t9 s& l: n
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15, X9 C" M; D4 O
    76d0e86f-8331-4303-aac7-4acce0b258b8* N/ C( T" V) ^5 B
    第3次调用getUsername结束
    1 u7 F( [$ O6 E" Q# N" s********
    + g/ u( u6 {/ E7 u/ y, Y& S从输出的前2行可以看出:) S; {! e0 p9 y/ Y$ }) m. t$ Y
    6 j. a3 S0 v7 m+ I0 V
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象; O- x9 N; H( S! k. j
    2 ~' c. d! M, n5 v$ L5 F; e
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。9 i. z# u: q) j. |. E! U4 P& L

    ' J! k. Y+ u9 ?2 s后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
      x& Z, v& D+ O
    $ i! b: N% l. N( G通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。) l" P7 C1 y: g$ b
    , ]9 p3 c, ]0 I; ]- h: Y  L  E8 Y7 r
    动态刷新@Value具体实现" x! Q& a% N  e% D# }  y$ M

    7 e1 {$ H2 l, \1 m' V/ U, D那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    , i( M# F) S' j" V! d) f7 D6 g0 h3 L
    2 z2 j  q) n) V5 ?# x- O6 T; ~先来自定义一个Scope:RefreshScope
    8 L' a8 _* @4 b  `! i- {* E! S4 c* ]9 L
    5 ^  R, L0 V' r2 W' r, ?6 E/ H7 [package com.javacode2018.lesson002.demo18.test4;
    4 _8 g( M" V5 X+ ~
    9 v0 J/ _( ^6 o7 }$ f/ Z7 x! ~import org.springframework.context.annotation.Scope;. y+ _* }/ f0 j$ D  U
    import org.springframework.context.annotation.ScopedProxyMode;
    $ D: [9 Z9 [& G! n1 w; }9 I; K4 O1 b, ^: ]) {
    import java.lang.annotation.*;
    - ^' `+ O8 K4 j
    " g' \& Z. w# B# \9 I6 k@Target({ElementType.TYPE, ElementType.METHOD})2 J( U9 P, I# l
    @Retention(RetentionPolicy.RUNTIME)  U! ^5 k% R$ i
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    " D; s: }! @7 C; ?@Documented
    5 a+ G/ r! y, j% b+ `5 n# Mpublic @interface RefreshScope {1 A: Z; E$ c6 X
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1) v. r& U, K! w
    }- q$ w/ \6 l3 o, ~
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    5 w) q% @$ K$ _2 }" g9 R6 |8 S! K' X) e
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    7 g  K1 F+ D# N5 f, p+ m7 ?9 h' g: e% \* q. @9 z
    这个自定义Scope对应的解析类
    3 y0 p. A6 N! v2 @) P
    5 P5 Y. a8 J5 G8 W! o下面类中有几个无关的方法去掉了,可以忽略/ D0 s2 p5 G. s# Q9 _: I& y

    0 `0 c. h' K. bpackage com.javacode2018.lesson002.demo18.test4;) [5 a1 u! ?5 G) k/ a
    - O  n  b; J; c
    5 w* m; e$ J  L1 c
    import org.springframework.beans.factory.ObjectFactory;, O; a1 ?: R, z" `1 |6 |: r
    import org.springframework.beans.factory.config.Scope;
    & q% x: m* u/ e0 Fimport org.springframework.lang.Nullable;
    4 f" T! w1 t9 Z1 N6 V, E' I0 B% b7 m$ l! o- L( k
    import java.util.concurrent.ConcurrentHashMap;
    4 o6 w& k  [3 v
    ' [) O6 T, q, n. r9 n1 @+ A  q% v) xpublic class BeanRefreshScope implements Scope {; b2 B  Q( [2 P* t, |
    . M# v+ L% d6 g5 y# f6 s: R& m
        public static final String SCOPE_REFRESH = "refresh";
    9 D2 f. X; S1 v/ A; \" t, {- O6 Y: {6 L$ n4 u4 L
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    - i! ]( S) q% A# O! Q: F
    " R' l; O1 j4 p5 e% u; {    //来个map用来缓存bean: ?# O3 Z' s8 F9 R! \$ A
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@16 H4 J/ h2 `/ O7 Y5 k" ?
    5 x, i' q4 Q, O$ P8 A& m( d8 n6 m' ^
        private BeanRefreshScope() {+ u6 W4 r- w3 G0 }% V% ]
        }; m* e' ~2 T5 w  x

    ( E% n7 d' a! @  T" C    public static BeanRefreshScope getInstance() {
    ) u& o8 A( i( R6 m+ X; `' r        return INSTANCE;
      W9 O4 c+ C3 D. U    }4 v3 W  u. Z' i% \

    - h" p) @0 ?/ R' Y    /**
    ' A" Y- n2 H) B2 x( u. v: v     * 清理当前7 U3 j' o8 o- N* I# ^
         */
    / O8 b$ ^  b; w    public static void clean() {0 [7 }4 M7 d. f) X( l& |3 d0 d
            INSTANCE.beanMap.clear();
    5 n2 Y7 q: i7 O' p4 J    }) c( [: w# x0 W0 x% w) Q$ o  M1 e
    " n0 l* W7 S& x3 m  i6 x
        @Override
    ; @) m, B0 k6 [2 U# }, J) J" M    public Object get(String name, ObjectFactory<?> objectFactory) {
    3 v$ ]1 M: r! K. ]3 z' f5 G6 k        Object bean = beanMap.get(name);9 [' k2 ?' c5 c
            if (bean == null) {3 Q) |3 p& Q% ^3 e7 m7 E8 d  x
                bean = objectFactory.getObject();
    : z5 ?4 n* \+ S" v            beanMap.put(name, bean);
    , ?: u% W4 M9 l: O4 n% \        }
    4 f4 K& ^2 H8 t. n5 {. T( A; |1 `        return bean;6 h; {% s9 z8 L
        }5 n5 G+ V; Y6 O; V8 E( a

    9 Z) @0 |3 Y2 X" R8 f$ F}/ l% c+ A) j! Z, ?3 \
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中: ]$ P: z) O0 Z* Q/ A
    ( Q! f+ D8 v& m# _  Z0 ~& ]
    上面的clean方法用来清理beanMap中当前已缓存的所有bean- a5 H8 p, E- c$ |- U$ d
    . W7 G, T, d. D/ }7 ]9 e* t( y) t: F
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    8 Q+ t& T6 T, e4 d" G; z" L  O: z7 _/ l# t1 L0 U9 ^2 D8 z/ B
    package com.javacode2018.lesson002.demo18.test4;7 f9 w% t3 V# m0 n+ s

    * x0 m$ |3 {* Zimport org.springframework.beans.factory.annotation.Value;% Y' h3 O8 t9 _: ?' ]9 T/ e. b
    import org.springframework.stereotype.Component;
    3 L: V; r0 Y2 G. \4 x* O$ a) g
    8 L, d, E9 j3 o/**4 _) j3 R  o1 U' k$ \
    * 邮件配置信息( ~& |) b" F, V
    */
    ) x& \* H3 g1 u/ g$ R@Component4 \9 U; z% f) ]9 n0 z
    @RefreshScope //@18 ?6 J' \6 g: C& t+ }
    public class MailConfig {6 I( u2 B' ^- j+ E" H' G: N$ U
    % D, x8 U% ~7 J/ o) a
        @Value("${mail.username}") //@2; Z, c8 Y! i) S1 F# L3 K1 U
        private String username;
    % g% s* @7 {2 G1 L
    + d- I' a) [: v    public String getUsername() {, V# D5 d2 D* y* O( O
            return username;
    1 w$ D3 y( \& I- k7 T+ i- u0 M    }- t/ y9 }) W' m* T( Z, c
    # Z/ j& q/ k1 Y- H) M/ @) o" _& K
        public void setUsername(String username) {% z0 L( u8 f, I, q
            this.username = username;
    , p% k' v0 C* X# i* o& ?    }/ t. s' l+ F3 o% I  ?7 s

    0 i) U" X# O( H1 o) A7 I/ V    @Override9 N% l0 b& j2 s! R: j
        public String toString() {
    2 a5 V% G* D% g        return "MailConfig{" +
    $ t# h5 R; K# h$ w7 [3 q                "username='" + username + '\'' +7 ^9 Z7 o' V& R+ o8 [
                    '}';
    ) G+ O& j5 x$ u- e; G" P8 R    }  B! v) j0 z' \$ O: {' h1 b
    }- _% e( T8 X9 W
    @1:使用了自定义的作用域@RefreshScope
    ; i) o1 c0 n7 [2 H4 V, ?  i$ H! C
    @2:通过@Value注入mail.username对一个的值
    6 S$ O, E4 l2 U6 k# v% e& |, O+ W0 l4 D
    重写了toString方法,一会测试时候可以看效果。- s' h3 t& G) e7 L3 {7 ]. @- ]2 _

    ' a0 K. m) S5 B' L7 g再来个普通的bean,内部会注入MailConfig+ z# ]2 a' Z5 j# z- W& U# j

    8 E' C, x* \5 w  M8 q- j/ Gpackage com.javacode2018.lesson002.demo18.test4;
    * J: h5 x& r+ ~$ ?3 R& `5 o, o6 U# ~, M' n  |; I3 P
    import org.springframework.beans.factory.annotation.Autowired;$ D+ T4 o( T5 O
    import org.springframework.stereotype.Component;
    % A: O2 f+ i9 q% _' C: s9 A* L. q6 H/ `  i
    @Component. K: s# r/ x. g; |# o) l# b5 T9 J5 p
    public class MailService {6 p5 K, _- [! y) z  ^
        @Autowired
    3 O8 x" ]2 [/ K    private MailConfig mailConfig;0 V7 b  N- X- o( c$ B

    9 F2 z3 i4 `( k# V2 p  ~! i    @Override
    4 @  M4 E5 B4 s  C! X    public String toString() {6 z# p9 v( {  c: w* |' Z+ ?) u
            return "MailService{" +
    2 }# H- g$ r* S" g: v6 V( B                "mailConfig=" + mailConfig +( O" V+ A; t, P2 x
                    '}';
    ) ?; l/ @. G6 A/ n6 ^- M    }  @& v: x* k( ]; I: J% H5 ]
    }
    * a* `8 A" C6 A6 K) ], \* j代码比较简单,重写了toString方法,一会测试时候可以看效果。
    % q% H9 Z0 l6 w  ?7 s: x' B( y3 _1 f$ S6 J! i: I
    来个类,用来从db中获取邮件配置信息
    6 i. I+ X- H* h" _5 f
    # ^3 b2 Y! r1 L# x  q+ @package com.javacode2018.lesson002.demo18.test4;; m5 u! v6 X  C; g/ Y
    ! ]" y$ z5 _3 S. a
    import java.util.HashMap;
    $ b% }3 D& u5 W' y( b  B% ximport java.util.Map;
    * ?$ [, k- w$ a. e; ?, m8 limport java.util.UUID;
    $ s4 w: q! `+ B* D. l6 @& A) ]& g7 y0 l5 u& T) T( v- X
    public class DbUtil {
    . ~( \, T0 s; q, z- p8 e    /**2 ]% Y7 y' _9 g5 C" n3 p' B
         * 模拟从db中获取邮件配置信息0 c  v1 m" @/ E8 j8 f
         *, m1 X0 Z$ E# u& V2 H1 G! u
         * @return
    ! Z1 Q6 X9 J+ s6 q     */
    " v1 Q: h* V6 J$ G& W+ M    public static Map<String, Object> getMailInfoFromDb() {% U9 _9 W( O' R6 ?- M
            Map<String, Object> result = new HashMap<>();
    ( g- G8 [& [8 f7 T2 t+ m        result.put("mail.username", UUID.randomUUID().toString());
    " R- z, w3 D0 D: m, A4 ?+ X        return result;
    1 ?  y$ Y9 m8 X: @: E! w8 \2 J    }
    ! b" n$ }  b' }1 F2 `* s}
    " H1 Y  I5 \! |8 Y: x* X" o来个spring配置类,扫描加载上面的组件
    4 t, T& V+ R5 x# f1 U9 m0 ^' j  w5 ~+ V( R
    package com.javacode2018.lesson002.demo18.test4;* E, E* {" v0 E4 H2 Q/ g( E6 I# P
    9 z: o+ l( o* X' v' T
    import org.springframework.context.annotation.ComponentScan;
    8 y5 B. e2 |0 u) j2 M$ yimport org.springframework.context.annotation.Configuration;
    ) d/ O3 ~' M4 k- s7 o! F- _& J2 u; V7 K
    @Configuration
    * \7 F0 l/ _! S  z@ComponentScan
    ) X  J5 A4 E$ E! K9 lpublic class MainConfig4 {
    ' s: V" D$ W6 v+ [}8 j& q  y& u. l% v1 s& d* B. _
    来个工具类
    : B) R/ l  ?5 w: \- y! H8 H- t0 x% N/ p/ [6 W) ~! L9 e0 n# D8 n
    内部有2个方法,如下:
    : k$ b) V( c0 x# U) B+ K2 P
    8 z3 V' t& z8 I8 X. tpackage com.javacode2018.lesson002.demo18.test4;0 v* X" h3 ~/ h. `# ]8 I' X) z
    5 K5 s8 z: o5 l) l0 k6 W, R
    import org.springframework.context.support.AbstractApplicationContext;5 B- \, J  n! e! ~
    import org.springframework.core.env.MapPropertySource;
    2 C  u; v" s, z8 k% p0 l3 |6 Y9 R" D! T, V9 ?
    import java.util.Map;: c3 Y2 _/ f4 V8 J. E

    # [' e% t8 {  q! J9 X4 F2 ^1 ^public class RefreshConfigUtil {
    : ~6 `) E3 d. ^7 e$ D5 ]- C5 u# v    /**
    ) `9 _5 q6 R6 M. f, k# V4 T     * 模拟改变数据库中都配置信息" |' W* I5 q" s. |3 ^
         */5 Z  r5 T0 ]" c7 }/ o
        public static void updateDbConfig(AbstractApplicationContext context) {
    0 q9 L. U5 F8 h        //更新context中的mailPropertySource配置信息# _* |* a4 r6 C) e3 l1 ~% t
            refreshMailPropertySource(context);& |5 b9 A& b, r5 z& K6 n  U
    2 y: |2 Z2 H; ^0 T
            //清空BeanRefreshScope中所有bean的缓存
    ' K! R4 Y6 ?3 y; K  y        BeanRefreshScope.getInstance().clean();
    3 D1 _" J) H% V/ x: T  z    }
    % F7 q- k, I- e6 l- ^3 i
    ' l# ^( U, H1 H! J4 j: a* M    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    5 ?7 v7 p8 u3 `! h: c        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    : q$ J! d( V+ H! X$ n! V! |        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    - d/ I1 |* Q" J6 t        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    . _! i9 y3 r/ `( Y( M( w5 Q        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    : b1 }8 R3 \/ @9 `, ^- p    }/ I  Y. G  L! t7 `9 L

    $ M9 B2 V" w3 U}7 g( s; n: O4 k% M$ t; g
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    " d( N$ Z  f( n/ r) H# i
    ! e% q0 [7 ^; G  {9 P+ aBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    . c- H) s, P/ r: j
    4 m  ]# o4 N" `来个测试用例
    5 A9 O% i4 a8 m3 Y2 |  ?% k" a# V7 ]
    @Test
    # ^" X4 e1 N2 Jpublic void test4() throws InterruptedException {
    ' P$ m4 c; l; m: i) B    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ H8 A. s  P) h9 @; L$ H) o+ t    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    7 q5 r. S& N* y* f. q    context.register(MainConfig4.class);6 V2 Z3 L9 W$ `) J. c$ D8 P
        //刷新mail的配置到Environment
    # e5 V  h, B4 M$ J1 `    RefreshConfigUtil.refreshMailPropertySource(context);! L0 g  T  @# X3 I" X7 f& g
        context.refresh();+ H4 Y  c0 T) \; y. }

    ' n: R* G# C7 D2 \$ D    MailService mailService = context.getBean(MailService.class);" A- a. Y& W0 M' E
        System.out.println("配置未更新的情况下,输出3次");
    ) @0 J  }+ I. x    for (int i = 0; i < 3; i++) { //@1
    2 a7 n5 A+ y( F; w# M5 n2 b        System.out.println(mailService);
    ; f% e! G& O0 R1 a        TimeUnit.MILLISECONDS.sleep(200);
      G' [4 S1 s6 x# [) }    }
    - W* n1 Z6 w1 d9 N# U8 ~7 O$ c/ o* T7 D, O$ m4 M
        System.out.println("模拟3次更新配置效果");0 O$ b# j0 v4 |9 u- E' m" G
        for (int i = 0; i < 3; i++) { //@2# y, g' V# V8 \, L4 Y8 i3 c" Y
            RefreshConfigUtil.updateDbConfig(context); //@39 L$ a* ~- d, o' ]  a
            System.out.println(mailService);
    0 h0 y8 v, L( b# `" }        TimeUnit.MILLISECONDS.sleep(200);
    6 O" V- x7 p; ^" M- {    }1 p6 A, x! s( Z- @
    }
    8 u+ n: N; b- L! k@1:循环3次,输出mailService的信息
      g* i3 ^0 A: [& y
    1 S6 L- t  o$ M4 J@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    ; ]  N: s$ k2 r/ p7 k' M4 B  N) v8 A1 W  w0 `$ X/ U! P9 S
    见证奇迹的时刻,来看效果
    : R( l" K( b, Q* Y2 D1 c1 ^1 u  w: {* C9 n. b
    配置未更新的情况下,输出3次" ^; O+ S, I% ?1 j' i
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}; C8 M1 |$ x: U
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 x9 g0 M8 `. a) {/ aMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}, ]! s6 {" b" H. o, A  O
    模拟3次更新配置效果& i4 t: _, G0 s4 k2 s$ s
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    2 g" H' s3 W$ C8 XMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}3 Z! u" M4 j5 x/ @' E3 U
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}/ \" d# E9 b; [6 s% n5 b
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    ' Z* D7 D! R' f% _9 F3 F
    3 K. \% Q* |5 v9 r小结9 P" b+ s/ _2 f/ ]
    4 M) n# Z# f8 a# X9 D
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    $ R- D) q5 p8 c' z. A  w9 Z, Q9 e! a# b9 e
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    1 j7 Z+ t" z2 O
    & `$ y2 [1 a5 M+ G- _总结9 [: J: J1 l# B9 C3 }
    2 W) S6 {2 D( B
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    4 i$ ]% `6 ^# t
    + X# O' Z! w) z: Q( S$ s* P7 S7 D案例源码4 ?2 n; }7 l1 E! d9 T: d
    . {/ \/ `& k: X7 y% K. Y6 ^* C
    https://gitee.com/javacode2018/spring-series" C: b' K; k0 r- m1 s/ I5 c3 e, h
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    ; z' Z4 G- P; e" m+ b
    1 e7 G% E( }% m& e) VSpring系列
    + R- @9 Q1 v, j1 U6 G" X7 ^4 m
      t6 Y9 U. n6 v5 a" ^7 a' s" }. V' w9 J  GSpring系列第1篇:为何要学spring?
    . h2 [+ e% y6 @
    & i/ w6 t- I5 pSpring系列第2篇:控制反转(IoC)与依赖注入(DI)* T9 C( X1 O9 _

    ; N( e1 p, S+ A8 t0 ASpring系列第3篇:Spring容器基本使用及原理
    % v1 z8 `( F$ X  Y$ q1 U! e& A: \1 Q4 V' o' W6 @: \" o2 ]
    Spring系列第4篇:xml中bean定义详解(-)/ |. i: S  Y! v7 g5 T
    6 a) k% p. a9 t
    Spring系列第5篇:创建bean实例这些方式你们都知道?$ c7 ]& e5 G- j& `. G# c( o

    # @4 G. S+ y$ l% Q, j( r3 H# dSpring系列第6篇:玩转bean scope,避免跳坑里!+ i, y, D. ^4 S

    1 ^3 {  I5 n7 m3 g/ }Spring系列第7篇:依赖注入之手动注入) e" H" L" F0 A4 ]+ ?9 S( L2 U
    7 G9 s3 @3 y. L5 J2 [0 G
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    1 x( q9 i6 {- J, u1 J- D2 B" \" |6 g& n6 `
    Spring系列第9篇:depend-on到底是干什么的?+ I; L( i+ p3 O

    3 w2 N- A# U/ @Spring系列第10篇:primary可以解决什么问题?
    " S, v  v0 X1 r0 h/ ]' n/ Z+ `; X0 g' l+ `2 ]! y
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    * `" z2 `! N/ T: C" C" `! [3 R9 ]; Q
    Spring系列第12篇:lazy-init:bean延迟初始化
    ( L! `$ P+ @; a+ K" E9 i  v& B, k+ p0 X' b
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    8 K. V  D- A# z5 R' ]4 O; O
    3 X, ]- Z1 m% Z5 t9 J1 x3 fSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?$ Q- {) H: s6 g" I0 |* z( z. J

    9 l3 m) g& m% p3 I& g& ~* u6 PSpring系列第15篇:代理详解(Java动态代理&cglib代理)?) ^9 |% x  w" ~( l

    , n; j7 w) V$ h# }3 YSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ) q  _! \0 D& s- W& m  t
    9 t% e1 V- B! M1 g: N9 Q" kSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
      X. q' ~" g8 `4 l
    3 U3 o# q1 u1 xSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)# {: y; X0 _) m9 `$ p) S

    % E/ E* ^  g7 u$ J, U$ d1 ySpring系列第18篇:@import详解(bean批量注册)7 r7 g5 H( N1 H; S- {( p% D9 y$ H
    - a7 ?5 r. o2 ?* g" h
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    ( B7 }5 |3 d3 h* f7 ?
    5 s) W- P0 N) o& H1 rSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)* d* J9 n6 r$ I1 E+ f3 S- B! N

    " m. W$ A$ [4 Q- B6 o/ eSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解( g2 O: F# u/ K  e8 F% A+ _5 C/ P4 ~
    " X& X6 K9 q" i0 K
    Spring系列第23篇:Bean生命周期详解. t, {2 i6 V0 `
    ! Z9 f- W6 j$ ~, A" ^1 e9 h4 [8 R
    Spring系列第24篇:父子容器详解
    $ s+ v' j, y4 o. N7 D0 v0 Y. G$ E& ]+ M6 n5 T2 h3 d, R) R
    更多好文章( z) J+ j( k4 o6 n- O  C

    ; W& C0 S0 r! x# ~# {% R7 }# TJava高并发系列(共34篇)
    % H) Y% f7 K( m
    5 o4 K0 X8 y7 \8 s  tMySql高手系列(共27篇)
    ' z# V$ G) K0 S! H. M* w' g% W
    0 Z0 j( P' M8 v; CMaven高手系列(共10篇)/ G4 @  ~" I4 U9 _8 K
      J. @/ {7 `( \8 f$ ~1 b
    Mybatis系列(共12篇)1 l1 R' u6 F, P+ ^

    9 y# H9 m/ s" t( r, {! C+ d0 q$ H聊聊db和缓存一致性常见的实现方式8 G0 R, |3 S, Q2 T* k' W1 o, D

    7 ^( h2 u2 I0 s' {& G接口幂等性这么重要,它是什么?怎么实现?1 f: P9 j* G8 J: z% \  C

    6 O" n3 J  v0 T! m3 S1 D1 w# b+ a/ z泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!* Y  ]) |' `# d5 T4 N, w' J, s* l" P
    ————————————————
    2 F7 D2 N2 R5 F版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。3 P. Y1 x' a4 b" w" O
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    3 {; Y+ o! T  D3 n0 G0 }- q6 R

    : E( m) @$ K! P
    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-8-4 14:05 , Processed in 0.642403 second(s), 51 queries .

    回顶部