QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5447|回复: 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 D+ n2 d7 W* [6 u; [1 ?; A& k5 ]疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    & x. p2 T( j6 V( l/ N" k1 ]7 c; t* C. Z" k$ _; {' u0 f7 @& v
    面试官:Spring中的@Value用过么,介绍一下+ X" i2 y& i; S0 U; ?
    * E, N8 b; n. g' Z0 |3 n' p
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    $ R) [3 c. w+ m* e* V8 U7 J, U
    4 \' G5 c$ L2 z9 X: h; l面试官:那就是说@Value的数据来源于配置文件了?( x- Z$ ]: [/ G  C7 n  x

    4 e/ E0 h& {# F" `: m我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    / U! m2 j) u3 G& |1 b8 N2 B  r5 |; W1 z1 I( j
    面试官:@Value数据来源还有其他方式么?
    0 |- t8 m* @% g5 \' t2 e# S6 ]3 {# }. f, A) h
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    # d! p8 w4 J# u/ W
    0 k7 X9 W+ y6 ~$ }; D( m: T) h5 }面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    4 [8 g, Q0 F( g5 p8 [2 o, i
    2 z8 J3 w* _! n: T我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    ! D* `" ^+ r* D8 W/ M& f) m7 O: v) e/ x8 [9 I
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    ) W) T, Y5 F! f: X* K5 f% h
    / a4 @/ j8 X' T, J* ^: d我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能' Q1 Y. m6 E9 e% o: G
    ' K, F9 ^% C! X. W- }- G/ |
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?1 w2 ?4 }  k, o0 P4 |
    7 v  g8 R; Z1 g7 a! _
    我:嗯。。。这个之前看过一点,不过没有看懂: i6 g: v* r/ X0 p) g' {) C

    / F: p3 p. P/ t. R6 E面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    % a, h+ G* J& c6 R
    $ |$ F: Y7 C) @( ^! c% k我:3万吧- A$ A9 I8 }7 N+ ]8 t
    & Y# [+ n$ u0 o
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?/ c/ O% e( ^6 i9 ^" u/ K0 N
    4 A/ x3 J3 I! z7 u/ k* A% a
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万* _  w% w1 u* A& j1 D8 ~; E

    ( r0 i$ Y3 X; J( N+ C/ P. |$ R% M面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    5 n+ P! V) \6 U6 p! d: w& b8 c
    - y1 `0 B# s) n$ @! h) o7 k) `+ {% I# B我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。1 \' j5 v4 H; o) I% _9 m$ W

    , `% n4 r2 R. ]& k+ `这次面试问题如下3 w1 u# n- k+ s' w4 ^$ P7 F

    2 a: n4 Y4 f$ {( |@Value的用法" w1 Y4 `" c% j. j* r

    ( W4 A0 r4 o9 b! ^3 T( ^! g5 |4 ~6 g3 D@Value数据来源0 X+ X/ M# |4 ?! J; S

    9 Q8 V6 q$ y) z( v@Value动态刷新的问题/ v- z6 F, q1 M/ b$ ~
    # a/ f* {/ x4 J$ v/ ~6 E9 C6 Y
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    1 g  s( B- `+ k
    & Z' {0 z0 o: C4 D$ m@Value的用法+ f; b6 _: \9 o; T$ q% J( h

    + M) v5 r% _: D2 i, m) y1 M3 I/ K系统中需要连接db,连接db有很多配置信息。
    ' h* O5 i. {* M; c
    ; j9 ~: H, Y& J1 B/ N2 M9 }! O系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
      i" z3 q  Q- z3 b. l; w" M4 Y, w4 A2 R; e$ q
    还有其他的一些配置信息。' a" p0 R9 j/ Y

    + V* t5 Y& }/ J6 Q( ?  ?我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。5 g* T8 g1 S( j. d- }6 S: ?2 s
    0 b4 |& Q$ v, }' ]8 R
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    % O) h( z# l. _  i, y( v5 C+ M' J4 C
    ; B" c+ b  J4 S1 q  |' k, Q通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    + T- Z! M1 i) B4 y' w- T8 y5 n% j4 ^' G1 s/ w0 |1 N$ b' @; e3 O4 P
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    " @& }5 P' M+ r) d! N: @0 H: i* y( X* @; p
    @Value使用步骤5 i6 |) V1 C! Q- J- U+ E
    6 y8 e" D. x$ F4 `& @, c- b
    步骤一:使用@PropertySource注解引入配置文件
    " N" F. u" R+ P
    - Q2 F8 [% b) A将@PropertySource放在类上面,如下
    ( q; P! B( N" N5 h, X% ?! g$ b7 A- X8 v' y6 o& n9 a2 m% z0 [
    @PropertySource({"配置文件路径1","配置文件路径2"...})( p- B, A! r  b3 w2 D. a/ L
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    9 T' Y8 u9 D, n7 c, I3 u7 i9 P- a& O# u' u
    如:
    ! u' v% s' K4 U' j1 W( h. _: X+ i
    , {4 l* u$ Y+ Q9 R) {@Component, ]- l, `2 C9 h6 L* e! B
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & x! r: b, s# npublic class DbConfig {) E. f) l2 d; u$ d" z6 p
    }
    # M+ q; _* T3 o( d" I步骤二:使用@Value注解引用配置文件的值
    / F; ^$ M4 i" a- `% b/ p
    * M; t6 @: m5 P$ ~3 x- e通过@Value引用上面配置文件中的值:& [# D: z* G& E8 ~- ]$ ]2 x, p- u0 M0 S
    & h+ F: V% b4 T; J2 k- _# x* b( i
    语法
    0 \1 L% ?* V; ^+ q; W) V4 S$ P9 S  k: s* Y* \4 V
    @Value("${配置文件中的key:默认值}")
    , H8 v: O. ^2 \5 N4 N@Value("${配置文件中的key}")& R0 Z2 @: ~8 q
    如:0 O+ V' ?' M) B# c2 h3 v' m
    % u* X% k8 A+ l$ V0 A
    @Value("${password:123}")
    5 |* H1 N- j$ v7 N. Q0 a- L上面如果password不存在,将123作为值
    7 l, Y8 C( U% @) e9 ?6 B0 F
    ; ?  _7 @; M: P# Z* Z4 i" u@Value("${password}")
    : N) z: v3 F# ?0 V, N! r8 O' q% E上面如果password不存在,值为${password}
    ) Z- K8 c, q1 G" p6 V& S+ b8 l$ }/ P0 A: |: H7 n
    假如配置文件如下
    ! K% t/ N, W; q6 O$ ^! C: U: _. h+ u9 a) p: u
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8. `( S* |: e2 b* b# F: z
    jdbc.username=javacode
    ) u' m+ [' l* O4 Y* Ujdbc.password=javacode  f. {4 U/ k4 r/ f$ V, @( ?4 R
    使用方式如下:
    * e+ M; ?# }4 k6 b* \' S3 D3 M, ^/ d* I$ r0 {3 o/ j) ]9 W
    @Value("${jdbc.url}")
    - y9 ]7 l0 _: U7 Y& o/ x4 z0 T; [private String url;
    % g/ u" ?, T) V' H% `- J; f0 A7 C5 f1 v6 v4 r% s6 d
    @Value("${jdbc.username}")8 Y) x: ]/ N0 j* Z) X
    private String username;
    * J/ B( E$ o9 d4 e/ k' w" ?9 k* K2 c# \, {& o0 ~  R! x
    @Value("${jdbc.password}")# R. s& i8 N7 q$ Q4 F3 t) c
    private String password;
      f5 l5 U' P( r下面来看案例1 v, I' L# m, F

    * j) q7 Y' J; t: I5 Z( Z案例1 \. z$ K5 B/ M" j! D; @0 o
    / ?. f' H  C3 B' A. \8 t: Y! H2 h
    来个配置文件db.properties
    8 c- P% V7 ?) C9 L1 [2 R; C+ {( d" R6 k: R5 Z
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    6 g, _8 C! v2 P# z2 }jdbc.username=javacode
    + X- w; k4 N4 a' S/ L/ r' hjdbc.password=javacode) p5 y' h& B% a: s1 ^# k
    来个配置类,使用@PropertySource引入上面的配置文件
    " o, V2 O: ~4 \# [9 l
    ! r' L6 }  \3 Y, Vpackage com.javacode2018.lesson002.demo18.test1;  r5 P' U1 {( {: v& C* Q

    4 M- C: \  _2 D" j" {* ?) i1 U6 d/ @import org.springframework.beans.factory.annotation.Configurable;- E$ _  G8 v9 T' t
    import org.springframework.context.annotation.ComponentScan;* F2 j2 u! F1 A* w
    import org.springframework.context.annotation.PropertySource;$ E/ V- l5 x0 {
    & I0 O4 x7 ], X. p
    @Configurable$ h+ c# c8 P# U  I+ K! z: y5 l
    @ComponentScan. y7 T0 B! R: F1 ~) l3 h; b: P
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})- z, e7 @: Y. B2 N6 \
    public class MainConfig1 {. \6 G( y( u$ v) ~' @
    }( z4 }- G5 O8 z* f
    来个类,使用@Value来使用配置文件中的信息
      c4 Q; N; |: N+ n( [
    ! i; h. l) S% gpackage com.javacode2018.lesson002.demo18.test1;4 W$ _. K& L0 x0 n( P# X& l+ C
    8 Q/ U. h* i2 j" {+ f8 T
    import org.springframework.beans.factory.annotation.Value;5 ~$ H# |# b, A$ ?+ h: k! o
    import org.springframework.stereotype.Component;# G9 [8 {% w9 v4 Y9 U

    ) L! ]6 r3 J3 b$ A@Component" J. T) ~3 U- Y% {1 D' {
    public class DbConfig {
    2 x% j4 Y4 D, W: l% X$ U# |& ~! t' u, A+ m+ {
        @Value("${jdbc.url}")
    6 L; }/ v  A) Q0 A$ a' K0 }' z% [    private String url;
    2 P! E, d6 N3 B
    ( f* \# w* C- h/ e% \    @Value("${jdbc.username}"); D# ]' ~. y( I1 I
        private String username;
    0 V3 q5 o. R* x4 J% x. @+ i* |5 `0 s2 T* i  f5 f- c" B7 A
        @Value("${jdbc.password}")" e' I% D4 e1 S) d8 c5 }
        private String password;6 q0 y" J7 s" i0 l1 k/ Y

    . i3 Z3 P5 f, x. I) Q, E9 o* \# H    public String getUrl() {. f* @' t( J3 W2 [% g2 d* g
            return url;
    / [8 K, d6 o- ]0 `5 D" I- d: g8 L    }
    ' V. L/ D& B6 r$ K/ I( Z3 Q1 Z: K. w2 x1 O+ a( J9 o- F
        public void setUrl(String url) {3 ]- I5 S( Z. x4 X2 N  g' ~9 ^$ g' b
            this.url = url;
    9 y& z1 I" h4 |! N    }2 ~% V' g. x, O1 ^

    % R7 ~2 l, _5 z6 s! o# l    public String getUsername() {
    8 k. c1 U% l  L1 I9 G        return username;
    4 K# U/ ]$ |' G$ p. j( @    }
    2 u( M  l1 \$ d& N6 w) F0 l
    6 V6 _7 D, C! s- u8 }    public void setUsername(String username) {
    : r% ]. C+ j( t& U& W! a        this.username = username;) W- f" L! g1 z( X: j6 o
        }
    4 ]& _* G7 ?/ W! b- g: _9 {+ q; B9 G
        public String getPassword() {  [- M2 P' o& u1 M" i( p
            return password;+ Y. F1 \$ D5 H9 X, M$ r
        }1 B# e/ i2 Z4 s. S; A! q4 ]8 ^

    , X# V5 b2 [5 O$ L5 q2 `    public void setPassword(String password) {& J+ n/ C+ ], ~4 D5 E/ L
            this.password = password;6 h- g0 y( H7 Y2 G
        }
    8 o  D. N( D, B0 O6 }! }' r# U. L- m5 ~2 U
        @Override
    0 V) B" M* ~% t. K+ x    public String toString() {
    . Y, q' o# e, y( a) x6 ~        return "DbConfig{" +
    ! T- O: p% x) O1 _6 R1 b                "url='" + url + '\'' +7 [1 E7 i4 K' U! n( `  q) K
                    ", username='" + username + '\'' +1 L- j7 |% p) `- w# o/ J3 A
                    ", password='" + password + '\'' +
    1 K' ^- \, X% x" i3 U! K9 w                '}';4 v$ D7 K/ k* u! Z& ~0 g( s9 |3 Q
        }, O  [, c' q' S9 ?* k
    }
    8 ?2 p. F4 q: Q- ?" g# b) z上面重点在于注解@Value注解,注意@Value注解中的
    # P% F+ c% S) o  _) j  p. A( u
    0 u, Q$ N# `1 S  o5 V来个测试用例
    ' v4 H4 c. T6 V, h. E' t7 B
    0 t+ a. `# n2 X9 L! bpackage com.javacode2018.lesson002.demo18;
    $ _( _% B! g2 f
    6 I5 i; q, l' ]# _6 Yimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    + a% T+ ]# h2 u3 f1 Kimport com.javacode2018.lesson002.demo18.test1.MainConfig1;1 U& j; _- ^3 _5 _
    import org.junit.Test;
    7 n3 H  l/ `  ]import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    , \0 q8 }9 ~% K0 E4 E. x; A. m: m8 N# C; ~/ a
    public class ValueTest {  ~7 g; c8 R- _1 @# U
    + y# Z/ _; `6 G- |) x
        @Test
    3 _: e1 ]. L: A: I7 ~6 x    public void test1() {; W1 ~7 x& ^* r/ }6 H
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();, `' f; B: L- Q1 U3 k
            context.register(MainConfig1.class);; \2 P+ `/ H# i0 `
            context.refresh();
    ; U/ g% c$ q2 B% {' i3 H1 u( L9 U& m
            DbConfig dbConfig = context.getBean(DbConfig.class);
    ( c; @# a" [. Q( H9 R8 v' J        System.out.println(dbConfig);/ |, E! n9 @* P& D" @1 {, X
        }
    2 Q5 z4 l2 j8 N4 w; g}8 `8 Z$ ~: h# V0 p  C6 E$ f
    运行输出& h5 [& E. Q5 r- B4 _) _% X

    5 C3 g% t  Q- l- yDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    6 P1 o0 e  a: o! d) r" @  f上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    - z3 A5 T: H( p' h# ]; B4 N* B* e; u) k; X  O
    @Value数据来源
    3 Z: u- P! n5 @6 S. _9 J# W
    8 Z6 t8 f% y5 m! ^  C通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    ! c: q0 [! p2 |/ Z* h$ T, C! @3 a( W: H& r6 g/ N
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    ! }! x( B; P* ^* w+ H! @1 z2 V- \8 i8 w
    spring中有个类
    , `9 n: x0 w$ R6 c7 K
    / `" `* o) }. @org.springframework.core.env.PropertySource
    4 e1 v, n4 f. w- ~9 i6 k可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息3 X6 F  P' `" ?

    * [% z# ]/ J4 t( h4 p% {. d( k9 H内部有个方法:
    5 g& b. T+ H9 M/ K! e8 Y5 c9 B7 M+ O6 u) b0 O5 [4 M: |4 {% c
    public abstract Object getProperty(String name);) {' x5 y, J; S5 a  u
    通过name获取对应的配置信息。0 O7 \2 s  p/ S
    8 z, b/ Y* f$ i  V, q
    系统有个比较重要的接口
    0 `7 j- t6 G9 F& c5 x- k3 k; L
    $ `2 b( d! ~. A7 qorg.springframework.core.env.Environment( ^* E0 K! H, i$ g
    用来表示环境配置信息,这个接口有几个方法比较重要/ @) i9 I# y2 X( q1 y8 j

    # \6 k0 I& l' SString resolvePlaceholders(String text);9 g5 i3 w2 E3 v1 d8 _2 e9 T3 ~: S) q
    MutablePropertySources getPropertySources();
    , @0 _7 P2 W2 c% w* d( mresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。4 b/ K  D4 j2 z

    * E$ w4 P7 K! z# a% B1 N0 {getPropertySources返回MutablePropertySources对象,来看一下这个类
    $ ^( H/ ?7 ~% a
    ) [( e+ w7 {! D  B( x; Ipublic class MutablePropertySources implements PropertySources {6 O- ]9 E6 D) [
    ( y! `+ ?! i! |2 q$ `
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();5 k  E, G: X. j. A7 @3 F% z8 y

    " n5 J1 k+ K6 p  b# E  E; u0 |) |}
    9 M/ E# X" x' B+ W" X内部包含一个propertySourceList列表。
    . U" |1 U( y' U1 q( G
    5 I! l5 `3 e  j3 |0 W9 u7 Pspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。# H- j# _) C, ?6 S9 F/ a1 V2 I7 g

    + B+ f$ I/ e& p+ F. l0 j大家可以捋一下,最终解析@Value的过程:
    6 Y/ _; n) T( |% e2 U! s
    ! h3 m3 d% y2 m1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    $ l% S( }  T$ P1 n* V2. Environment内部会访问MutablePropertySources来解析6 }; H* N3 P% T4 {2 Z  \
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    " ]: r$ z1 }# }# x' G通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。6 o! \) ^3 p" S6 ?2 Q

    % ~# v8 t2 w5 Q& _, y2 x1 l, Y9 V3 N下面我们就按照这个思路来一个。& _' j1 ~9 G0 F4 S; z/ C

    9 ]; j7 K) ~% J! P( `, c" v' Q来个邮件配置信息类,内部使用@Value注入邮件配置信息' U+ k) A7 t; q' J

    & M7 Y: [9 p4 h* P0 ?package com.javacode2018.lesson002.demo18.test2;* g: S4 C0 m# y) q9 }6 [
    # f  w+ R$ j# c7 |( _) _" A% q
    import org.springframework.beans.factory.annotation.Value;
    3 f% S# ?+ _; N4 `, w0 E. Kimport org.springframework.stereotype.Component;
    4 K+ H! N: p1 Q) M" o$ ~5 x3 ~2 R
    % A: b1 [$ I! k/**
    # O+ X" c4 l7 m5 R0 Q) ] * 邮件配置信息
    2 @# i& Z3 V4 R  t# i */5 c: ~* L) o. I8 }
    @Component2 D/ ?; a% ^: {
    public class MailConfig {# I3 }; V5 N! [& q! A
    0 l; x- d. }: K3 S1 Z
        @Value("${mail.host}")* l9 q. R6 \% }- ^
        private String host;, A5 g& ?5 t8 t' E$ z
    / ~2 i7 v' U1 w5 [& B
        @Value("${mail.username}")
    $ `7 t; e' i. j  o    private String username;9 P9 z% n! j% c+ }7 p% T6 B8 _6 \/ X

    . a1 F& n( m, K/ H2 y    @Value("${mail.password}")- G" F8 G* D6 f1 b  `8 Y$ g
        private String password;# b  b. f  v" B1 D
    + m% r' ^4 v4 A; z5 J7 C( W
        public String getHost() {  V& Y. o" X8 Y8 r' C! d! I
            return host;
    & V# G. y/ M2 o' L8 e    }% o' t5 a5 T# A- B/ _) c
    & \! l6 a! s- N/ j. ?  w8 N
        public void setHost(String host) {
    : G$ B3 j2 _; r; f2 x        this.host = host;  n' ?5 W; r& Y3 s; q" }( i8 d* q
        }
    3 i6 M& k8 m/ o* h. R/ [+ M! W" M% D; p7 m/ X! G
        public String getUsername() {
    ' ^* k9 Y' l9 c2 s        return username;
    1 E2 x8 l* |1 w    }
    7 R6 n" [$ v, p! u0 ]
    6 u8 F9 W, {" Q  q1 E    public void setUsername(String username) {2 C7 H8 d9 s1 @
            this.username = username;9 Z/ q; c1 g, t& x6 @6 `) T0 q
        }
    : A% a5 D7 [9 M" ], `0 \2 `  I# I/ s! t3 f
        public String getPassword() {4 D( {2 N, H/ X  E% F3 ]
            return password;# ]9 _2 e! E" [* J+ P2 Z# ?
        }
    ( Q0 S9 X3 b+ t+ X, I3 }( V9 U9 n" M
        public void setPassword(String password) {
    % w7 r  O. \! o+ H7 H        this.password = password;
    6 C6 Q* [" m) d5 t, V- ]    }( w, Z" W$ s. S' h
    : v6 D1 |: n6 j3 ]( L: e2 F+ K+ q
        @Override
    . k% r5 h% ^# p    public String toString() {
    + l! K) i( p3 H- a* B- G- h        return "MailConfig{" +
    ! j( ?, X) R+ L' a+ A                "host='" + host + '\'' +
    + H4 c3 J. B8 |( U- }                ", username='" + username + '\'' +
    . N$ E. M" B. t+ _4 G                ", password='" + password + '\'' +1 {. P4 y* E. f7 Y2 [
                    '}';
    7 j+ z7 Y3 I# g7 j    }
    & r3 W! {2 S* y: _# F; A}# I0 g4 {0 V* N+ M
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中0 w. ]; b$ I; F
    4 [3 V7 B+ w1 c* l; \
    package com.javacode2018.lesson002.demo18.test2;0 V7 S4 l4 ?$ J9 u0 P4 V& u
    ! X1 N9 E- q/ |; U9 q9 b$ U
    import java.util.HashMap;( x2 n/ Z* i5 N: f4 ^8 b* _# Z. M: E- W
    import java.util.Map;3 d( ]" d# A: L. k
    ( }  h0 S  c; O+ t
    public class DbUtil {
    ' p, E; G7 e& W. Z    /**
    ) c5 Z; T+ c0 j7 z7 P2 K1 Z6 I1 ?     * 模拟从db中获取邮件配置信息
    ( ^/ M9 |" {" I, f/ `     *
    5 }% ~7 x1 r4 j* ~- g     * @return/ y0 z1 R* b9 B- b0 X- J& D9 o# g
         */
    + F# g% D  I8 V  @( Z# |    public static Map<String, Object> getMailInfoFromDb() {
    * o* w* O) q( Y) }2 _9 r- ^& d        Map<String, Object> result = new HashMap<>();$ `7 y$ a, G* N/ r! |$ F
            result.put("mail.host", "smtp.qq.com");6 t" l, W) P$ u3 [
            result.put("mail.username", "路人");
    % b# `9 s5 s# `+ O/ Y* L1 R. k' s" B; L        result.put("mail.password", "123");
    9 G: x0 Y( B- \/ M3 ?, f        return result;
    - D% P. {' a$ l7 P5 D% s2 g  V    }
    0 Q- R, g7 S& x9 w$ ]}
    0 Z5 e) V4 b# F/ N% |. V来个spring配置类/ w2 A* q; g6 J/ N
    9 J: u% _, E7 x( a3 \
    package com.javacode2018.lesson002.demo18.test2;
    ( n5 B- ^) [' _( g5 K( q4 Z
    0 K. D" P" ~! b$ v0 L  Rimport org.springframework.context.annotation.ComponentScan;
      k- \4 H4 c* y6 I. @import org.springframework.context.annotation.Configuration;2 r& y/ W7 U" Z, J

    3 ]8 P3 ^8 n4 k) ^7 W2 q% P@Configuration
    $ ]( a% m2 m7 v) G* s! J, C, [2 h1 e@ComponentScan
    9 {% Z& y: b. A/ k  S6 ^public class MainConfig2 {7 j1 A; k& h5 P  @  Q7 N/ @- A7 `
    }
    ! |8 K( G& r0 N! ^4 \& o  M下面是重点代码! b0 y- ~. e& k+ p  T( y& |! R

    6 Y; w1 z5 I- Y@Test; }. ^* M5 q! f! Z8 _
    public void test2() {
    " j- t$ A: f* W' ]! I  G: `    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ E# C4 j9 ]' j' G3 q; j
    4 j& J& u8 t. Y$ {    /*下面这段是关键 start*/
    5 i2 _( a4 X: {, k" y! S( c    //模拟从db中获取配置信息
    5 D3 h9 R" H# W5 d9 S# K3 ^$ X) o/ [    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();( T  U7 e3 b; [; u. L
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    0 }. W' A1 I  f% v5 G4 P, Z    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);9 z5 N; f- R: j  R# }& C
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    6 f5 y- B9 G# S7 E9 w    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);/ r% i2 V. M+ }& R$ J0 x, C
        /*上面这段是关键 end*/
    1 j2 g6 Y0 A/ X* f! S8 t1 V
    ( [1 j9 P3 X4 V) `4 J    context.register(MainConfig2.class);
    1 n  D' F3 U0 Y    context.refresh();
    7 W3 P4 B8 S! b    MailConfig mailConfig = context.getBean(MailConfig.class);! V; \3 j# n! G6 P- v6 Q
        System.out.println(mailConfig);
    $ z" M" ~0 ?# R) k% `6 e}* B* l6 L9 K" F. q, w
    注释比较详细,就不详细解释了。% h5 T7 Y5 b' n7 C/ e7 ?
    5 j, W2 m8 z3 |0 h& J9 ~
    直接运行,看效果# r' n7 d- j& v9 |
    6 }. i' l# x% I' I
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    / a5 p4 a4 u" c5 D; r( G有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    1 n* H: t4 s$ r5 T3 G1 p7 M5 g8 w6 ]7 d+ {  k, Q* L2 e2 A
    上面重点是下面这段代码,大家需要理解
    - w1 `3 w, U' }& }& X, j- m, W6 j8 V+ Q! ]. X2 r. w
    /*下面这段是关键 start*/
    + V7 ^3 R4 \9 \' i" i//模拟从db中获取配置信息# K0 Q. R9 I4 b8 u  J  H
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    8 ?  Y# @, g8 Y* k//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    8 E4 o- Y) O, E6 Y' t. Y% cMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    + Z$ v$ V6 h" i//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高: ~4 ?" S* }1 S* D2 y
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    1 Y5 D9 c0 p+ h* e2 o/*上面这段是关键 end*// {* q5 O. ~% h+ H9 ^7 A( M  C
    咱们继续看下一个问题4 i. m% K1 s' |, K) _8 Y% u$ p  C
    ! p7 H! ~# C9 |; V! _9 ]/ j9 E' N% A7 ]
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    - p: b' a" ], o2 L3 F) E1 x" x5 `6 I6 m
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。/ \' ^3 I* {7 I2 U$ M) j. ?

    ! f* {+ n: ?4 p  h实现@Value动态刷新
    ) D" h8 L6 [% \; \. H. a6 ?: [
    0 [' I) V7 e: N% F/ a3 s2 o3 S先了解一个知识点2 f( w% U( e& ]! o# Z

    * @; C5 W* V6 q" X这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    1 }! m, Q9 j- d4 g& h4 q7 [' F& a# W) m  o0 Q+ R" Q, J% _, y
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解* K" O3 Z# j, f5 t" L
    / S% @5 i3 N# T% ^/ v; k( v
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:/ `! Y! c4 W6 T* S
    4 h; b1 t! i) a5 k5 q% O: x
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    & v3 k. c4 p  `- G& ]这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中: ^8 ~; x- ~) J' l

    . h+ @! T% z  W" X: {! hpublic enum ScopedProxyMode {
    3 B% e# I0 Y$ u' F: y# w3 W    DEFAULT,4 n! w( K  c* T: E
        NO,+ v5 ^/ T' E( r" j2 c
        INTERFACES,
    # B4 M5 g9 q, w4 w1 T9 Q* x    TARGET_CLASS;
    9 S" a  Z* e. t2 I6 o/ j; m}
    / t; l' U7 R7 h+ h前面3个,不讲了,直接讲最后一个值是干什么的。5 t, C1 f+ [( |/ w
    $ I1 q+ L1 V( p2 D
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。" l: W5 T0 M4 m5 a% r& ]. P, ?

    ' z$ r" F4 }- r4 [& x9 ~" @理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。2 ]$ b9 m" r3 B1 @

    * z3 W/ q! l1 t, r" N自定义一个bean作用域的注解4 @! c5 H7 X7 X/ D

    4 i: T+ X) e) X0 cpackage com.javacode2018.lesson002.demo18.test3;
    ) q9 n1 o* a+ V& t9 G, q5 b& M* U2 `* D! x
    import org.springframework.context.annotation.Scope;1 t2 C* C9 s" Y$ r9 Z
    import org.springframework.context.annotation.ScopedProxyMode;
    * C) y/ I  M) _( D
    - Y, Z# D0 |+ F& pimport java.lang.annotation.*;
    3 ~) O* g6 @. }- H0 \) ?1 j. g! [# u% U5 C$ Q$ Z( Z
    @Target({ElementType.TYPE, ElementType.METHOD})! v, l- Q) D7 V; `
    @Retention(RetentionPolicy.RUNTIME). H% W9 V% k( b
    @Documented' [) R3 P; c5 p  `/ H' j$ ]
    @Scope(BeanMyScope.SCOPE_MY) //@14 D, `4 K7 _2 M
    public @interface MyScope {
    3 T2 ^6 \: x) P8 ]+ b    /**4 _5 s6 @# V( Q2 ^( s
         * @see Scope#proxyMode()
    , `# e% S6 {  x/ e# z( a" [0 [  r8 E     */
    " V8 a. F& @( O3 `3 ~9 V    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    # H8 w/ N$ Q6 F8 g* M8 t) A}, b# Y7 A( ?( ^$ f
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    , ~/ n& ~! j( Z$ ^( J1 A. X4 Z/ n/ O. O6 N8 U5 O
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS4 a3 C3 M( z& [  q9 {  c

    - M4 d: k# c/ B3 _6 Z1 x& ?@MyScope注解对应的Scope实现如下
    7 p& Q* K' g" F6 C+ h! J. I3 p% s. j/ R" i1 {/ e0 l2 t
    package com.javacode2018.lesson002.demo18.test3;
    ( z. E& _* m4 ^* U  i& L( d2 ]- R4 C9 Z
    import org.springframework.beans.factory.ObjectFactory;9 s) k3 Q: u- }/ w" G. @! i
    import org.springframework.beans.factory.config.Scope;
    8 _2 K+ a3 X* H$ r% Qimport org.springframework.lang.Nullable;) y- Y# F; z- Y# u! M4 A$ b# ?

    # r1 k: @. j$ M# {/**# p, u- I; [5 x) N+ y
    * @see MyScope 作用域的实现% D+ z) J, M* k! v2 M' J5 |3 h2 s
    */
    4 s7 r& b0 O0 M" Ypublic class BeanMyScope implements Scope {8 `1 ~- ]- M6 p- x  x7 `4 a
    3 b, ]( l- S3 u1 ]/ X) O4 ]
        public static final String SCOPE_MY = "my"; //@1! z1 m5 [* C2 b# E& M- \
    / z4 o- D# w7 w6 y& Z
        @Override
    7 r# j9 @% U3 s4 [( S" [    public Object get(String name, ObjectFactory<?> objectFactory) { 8 h' O) w/ s+ K9 B5 Z8 A
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    % F" n( W! P; _% R: x        return objectFactory.getObject(); //@3
    - C; q6 Z- _' L" v& p    }
    8 [; f; W; b; T8 K* F$ R: t" W& {+ \" X7 K1 G4 z
        @Nullable
    1 R- H( y5 O, `: W% l" T    @Override
    7 @: h) O! F+ z* ]7 U    public Object remove(String name) {
    0 s& T) q( E+ |  t: R9 _9 r        return null;
    ) m$ B, M0 r1 s. I- W, a4 \  {" o    }1 G( Q+ K/ V2 Z+ O0 A

    ) T8 n) B  G; n    @Override
    0 [( E, Y$ \" s    public void registerDestructionCallback(String name, Runnable callback) {
    4 F" W1 b6 _* p+ g5 F, Q2 M2 }9 j- A& i
        }
    - @. X+ S+ W, W, c  R2 L, |4 [, g$ ~) b( Z
        @Nullable
    9 N& B$ ~2 _% r; V7 _6 W5 J2 `    @Override) x& M% {0 e8 K7 f: |+ f, @5 _
        public Object resolveContextualObject(String key) {; P( T* @$ A  J
            return null;
    ; ], z5 _) t# M9 w$ g    }
    6 n8 i8 ~5 M. l2 b7 A9 R0 n! E$ Y
        @Nullable+ E. W" U7 W9 L  S' Y8 D3 y
        @Override2 \5 I/ C) {  i' H" ^* j; w
        public String getConversationId() {! H2 H3 S1 A) Q' O1 G! U+ d
            return null;
    ' G% ]6 Y& K2 |- u& L: w' d    }- X+ j  ^- z$ }1 j' _* j. D
    }% K$ E7 [" K) y
    @1:定义了一个常量,作为作用域的值; E! T# b6 r) n  {
    4 q9 V6 V, m: \
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果- _  S. x4 g4 C8 ?( F  I; B* u
    - q2 e7 k) ?7 _; X
    @3:通过objectFactory.getObject()获取bean实例返回。8 O7 ~- G: L& w3 Z0 E& C; h
    % y+ S% \; x7 Z( Z. g0 ?
    下面来创建个类,作用域为上面自定义的作用域4 N# e* ?3 l8 r5 {) {

    2 r6 n. D; ~# `7 |. Tpackage com.javacode2018.lesson002.demo18.test3;
    - i! x( @4 Y9 s( v2 [
    ) }. }: C4 X  _) P$ nimport org.springframework.stereotype.Component;
    ' V3 q$ \4 R% X' h: b0 [
    7 [3 e, \- u. Z0 Y, D0 \import java.util.UUID;
    - d% w- r! l; ?) V. d9 n8 M2 v6 b0 r
    3 \$ X4 P! p2 Z: G) d1 a) N@Component
    * Y, ^- j- N5 Z/ m$ {3 M@MyScope //@1
    3 \' c; y3 \, r: V2 i% `0 vpublic class User {
    - J1 {  K8 |6 m6 X% _4 D# V( f, @7 `& W5 v; ?$ r
        private String username;
    + O7 K; N, Q# K* s* e+ Q; g- D; u9 W$ P
        public User() {
      W/ N0 u0 w3 u5 q        System.out.println("---------创建User对象" + this); //@2
      V  f6 g! R9 U0 V8 N        this.username = UUID.randomUUID().toString(); //@3" \( h( }# N2 Q& x* w
        }
    " A+ N3 m$ A) L. ?6 W
    ; P' \4 _: k( |7 O& \    public String getUsername() {3 C: z' G6 N: Z* z
            return username;
    , D; w6 n4 V" J# l3 s3 k    }. K6 t) b, y, ~0 k4 \" I
    ! ^5 ?0 f/ J* f( l, H
        public void setUsername(String username) {% L- @' w* j4 f3 ~  r
            this.username = username;( x  V  K, x9 q3 F- e% p5 X  b
        }/ Z0 T# A# _7 o; V! ~3 r
    0 e7 z) K; W& g5 i3 Z; F" W
    }( ?9 u) A7 t& m: F
    @1:使用了自定义的作用域@MyScope" P- b8 p, Q+ R5 j3 C6 ?& P3 d- p1 t
    6 j6 P1 y( X0 h
    @2:构造函数中输出一行日志
    ) S, f) x# W5 B* W' }& D$ `3 v: L' c1 O
    @3:给username赋值,通过uuid随机生成了一个
    % q8 b2 h, e; }0 n7 \2 W- v# r& F2 L6 Y: h) z$ \: s
    来个spring配置类,加载上面@Compontent标注的组件
    , U+ O4 S. D  f' a( C: o+ O+ j, ~7 c8 H- b& h& y8 k: ]. F8 p" j
    package com.javacode2018.lesson002.demo18.test3;) u! m4 V) E/ o' V8 v7 f
    6 v( z" j; p1 p+ V* ?
    import org.springframework.context.annotation.ComponentScan;2 J0 q5 F+ U- R7 v, s$ ~3 K5 d
    import org.springframework.context.annotation.Configuration;! P1 T0 i: `' @& Z

    6 \" A# Z* K8 z1 K@ComponentScan1 H5 Y2 n; n7 G* ^& Q, ?9 e
    @Configuration
    8 ]0 b! z2 p, mpublic class MainConfig3 {5 j; C/ J( v4 G* M* ~3 f: Y* H
    }$ e' ]5 w: M$ k0 f
    下面重点来了,测试用例
    ) b4 \. z2 E' k  o+ c1 A  M' o8 ~) U0 H' m6 ~" _, ]
    @Test
    1 R' `, D  n2 p  T) r+ O4 epublic void test3() throws InterruptedException {5 c1 G9 @# b$ i
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();- F5 H; @* ^) L/ Z  |+ q! L. G
        //将自定义作用域注册到spring容器中- f( s! E1 ]& M' |# D6 h: P+ I- ]
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@16 |0 @; a- [' S. n- j: B
        context.register(MainConfig3.class);; q# u" C( V  f+ g6 Q, X0 R
        context.refresh();, l, |  P; t6 n
    $ f4 ^5 u3 N) C8 b
        System.out.println("从容器中获取User对象");
    3 T3 k: b3 k7 @) g6 b' F- D* K    User user = context.getBean(User.class); //@2
    ' T- l4 l2 a( w4 }# I7 r5 n: H    System.out.println("user对象的class为:" + user.getClass()); //@3- O* r5 k6 i7 R
    % a' Z: P# k. n0 }. B5 }/ B0 z
        System.out.println("多次调用user的getUsername感受一下效果\n");, M/ C* I3 s% w4 z1 a1 N
        for (int i = 1; i <= 3; i++) {' O( @: E. e$ G
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));, V& N2 f7 `  P! u( b
            System.out.println(user.getUsername());3 G+ u' y$ O8 p/ d, f0 g
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));/ `/ k+ x" j  Z) j' Q
        }
    ! A7 F' k( b/ U% N( I}
    " T0 p3 O- S& i2 k+ S4 ]4 Q@1:将自定义作用域注册到spring容器中
    8 A- ~0 v5 B/ _6 H3 \! x! o6 Y2 h3 ^) u2 }
    @2:从容器中获取User对应的bean
    # k( I  _8 g+ h8 n! u* I! w
    ) y" B) g" z5 i' y6 `1 a@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的, W+ u% o+ e' a' c9 S: l! e- I4 u
    % d  N! N& F7 e* {, n7 L
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。5 T$ F/ n2 P- p) }

    ! n" Z* w! g; |% r4 |, B( F. m见证奇迹的时候到了,运行输出1 s* e8 a. z! V+ ^  P0 O
    ; u" ?$ t7 l( F
    从容器中获取User对象, @, }' W. u. ]. F( u! t* K
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127; f$ x& u* f% p" z$ C+ c: w( U3 n
    多次调用user的getUsername感受一下效果- a, O( q! J3 m5 t
    5 \( Q9 @% y( k8 J1 |  t' b2 h
    ********
    6 `  ^9 }5 o" ~. r2 X8 d5 ]: Z, g第1次开始调用getUsername
    ! r/ e* c  X5 D4 i9 RBeanMyScope >>>>>>>>> get:scopedTarget.user: H9 ?" {4 `/ @: N
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4  }& \9 O9 h* ~
    7b41aa80-7569-4072-9d40-ec9bfb92f438( g4 M' ~% {% I+ |! G0 a
    第1次调用getUsername结束
    ; M  S# [9 ?" r* g4 F* K********6 q' g. t5 b$ a- z
    - r9 [, D# ^7 I" y: s- r/ ?
    ********
    3 z, x+ F  F4 J4 I4 E4 B第2次开始调用getUsername
      f0 `& i$ y0 ^0 w2 \, K5 oBeanMyScope >>>>>>>>> get:scopedTarget.user
    ) w6 ?9 Y) a3 ?( v0 Z---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b4 w' Q, A8 \: d
    01d67154-95f6-44bb-93ab-05a34abdf51f- s6 B' R5 b( n$ x, U$ h* J
    第2次调用getUsername结束
    # Z7 A# R! Q* U( ^4 ~********
    * e3 z4 y& w& Z" @6 V5 D5 f, r, T. z) X8 f
    ********
    2 g2 I, e+ q" a' M9 q4 Z" D+ @第3次开始调用getUsername0 |- W( h( X+ i+ W" F" c8 ?9 D
    BeanMyScope >>>>>>>>> get:scopedTarget.user& K; ~! I4 v; i: R4 E+ w
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    5 D, p1 M( U4 Z0 H# v" X76d0e86f-8331-4303-aac7-4acce0b258b8
      X! }% Z4 [2 d" K, F7 k% ]第3次调用getUsername结束: E+ f( z; p' s1 {
    ********8 T  h. }$ {5 P; f+ d' i
    从输出的前2行可以看出:
    % F( _; L/ K  U1 \
    4 C5 ~2 r& }. }8 w8 O; a调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    * i8 W& L  v2 @* v7 h# ]& v! \- t3 R& p
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    * u! [6 O% P$ O9 X5 m+ K6 G& `9 M/ E" {6 \) {! B& @8 n# V: O) C
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。9 R$ u& G! R" @8 {

    ; e+ }2 A- x' \5 k" c通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    + Y! \* e( n$ |5 `5 D1 E
    7 v1 E$ c1 ?& V/ u  @) n$ V6 \0 @9 T动态刷新@Value具体实现
    ) c! C  x/ i1 A! V8 ^
    , N" R; t( {6 a) k: [那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。- ~* ?5 V7 [+ `3 z3 j/ ]$ A1 M
    " S, l' U: o7 S- [+ Q
    先来自定义一个Scope:RefreshScope& Z" @/ b$ j, h2 K, ?/ p" W

      F* e& R9 S$ a8 k; |* [8 U4 spackage com.javacode2018.lesson002.demo18.test4;
    - p- v4 A/ z* o0 q* r' d8 x/ |0 N5 _+ l0 V: o, \
    import org.springframework.context.annotation.Scope;
    5 c* d7 U, N; g2 Uimport org.springframework.context.annotation.ScopedProxyMode;
    1 w- S( x, `6 q3 {" @
    3 |: s- ]) e8 e: w/ E" Himport java.lang.annotation.*;
    " D* W$ h: x# X, a4 f7 c  u4 x, g) ~, W8 K2 J
    @Target({ElementType.TYPE, ElementType.METHOD})" q$ C: S6 D* c/ f
    @Retention(RetentionPolicy.RUNTIME)
      M; }0 W; O9 t, U  M7 ?@Scope(BeanRefreshScope.SCOPE_REFRESH)3 w' d$ b4 s/ `8 J/ g
    @Documented
    - S& h. s4 H+ S2 u7 spublic @interface RefreshScope {5 I' Y% I7 a3 l
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ! g6 N6 n9 x, \3 y! O9 g}$ X) N  h8 [- L7 W
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    ( j, N, l& T( W2 i. K1 h* I, Y' G& s9 X. U5 E* x
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS  O" x$ p8 U# C) x  w

    - V& g$ d" `/ B7 |3 q! d- L这个自定义Scope对应的解析类
      \) D$ C2 q2 X% r' a; J+ X3 i# J4 |% h
    / c$ T% n2 H2 F8 |! r下面类中有几个无关的方法去掉了,可以忽略. k; H, d% f) I* \
    ' r" @" k) }. k) M* t. T6 i. V* y
    package com.javacode2018.lesson002.demo18.test4;
    / o# [5 r7 ]$ R) ^6 w( n6 r
    , [3 l% P6 n6 r! J; E! M3 G
    6 ]# |' i4 |9 f( V" m& Rimport org.springframework.beans.factory.ObjectFactory;
    2 f' E2 Z: b4 C. W: ?; Uimport org.springframework.beans.factory.config.Scope;1 s3 M1 `# P: R" O/ h/ v
    import org.springframework.lang.Nullable;. X; U3 M/ F3 g
    $ w0 P5 ~0 E( y% k3 F6 h3 g% r
    import java.util.concurrent.ConcurrentHashMap;3 J1 _% `  I+ K, ^" l+ D
    / @& ^% V, |" i, J: O
    public class BeanRefreshScope implements Scope {! P. P* O0 y: y4 I. p

    ( `" V2 B+ n7 S6 f0 U    public static final String SCOPE_REFRESH = "refresh";
    % C2 O: B8 Y% [
    # S% f* U5 K9 s7 J$ ^" r% o6 E    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();) Z8 a! F; o/ M& O2 f" Z! z- S
    - I( f$ E! @) q  B& v
        //来个map用来缓存bean
    3 _1 b! r4 p' L8 c    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    & q% @) n4 I8 d( `) }  l9 Z* \
    3 m# W2 q- @& r! I( b' [( l; A    private BeanRefreshScope() {
    : H1 h3 I4 L) o/ T    }( U+ {) n7 d$ y) J7 f
    # _# k3 c' I% O7 e; @- J
        public static BeanRefreshScope getInstance() {
      b# R1 ~& Y2 M% O8 G" I1 G8 ]( a        return INSTANCE;5 \% p3 }9 T7 g
        }
    % d2 t5 N; \4 l/ L7 P4 i* t. f$ X& j
        /**
    % l6 V9 m2 S+ b; n/ X     * 清理当前! U3 Z9 ]8 E* i# @  W* g; y4 ^" K2 @
         */8 D8 S* \' S0 U" F6 h
        public static void clean() {3 l5 F9 a1 }9 C# L
            INSTANCE.beanMap.clear();; V; r, l0 l- k8 P! U8 F
        }2 {  x9 b! L8 i
    0 G' S. x( I& l5 f7 \4 {
        @Override! J7 J: e/ H7 q; r" z5 t
        public Object get(String name, ObjectFactory<?> objectFactory) {  T% ?: Q6 o& o8 W. s
            Object bean = beanMap.get(name);
    . f" t, W6 c& W4 [# M6 O1 }2 V& S        if (bean == null) {# J5 L' b5 ^' y  N
                bean = objectFactory.getObject();
    . u; M+ M2 J$ S7 A" {) E4 l            beanMap.put(name, bean);
    . H8 b8 m% E4 A) `9 q        }6 T0 ]: T( N+ Q) A# {, L+ s
            return bean;
    # `2 r+ W# k- r' h6 d' J) d, [! v/ D5 u    }9 y8 C1 H, U  [' h! C7 K; J3 @1 n
    . {3 t7 V6 a  c3 n) R4 R
    }8 f: U& P% ~  q
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    0 y) U* f* ^' {
    # M3 Q" L2 y3 x) c8 k7 Z上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ! C, }, b* F  y1 h. p, v# T6 C: M- C0 \9 x7 B, d9 }
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope- P4 y2 D/ Z) e3 j- h" H6 _5 N
    , _+ Y. u! M  i+ o- d4 e2 W
    package com.javacode2018.lesson002.demo18.test4;
    7 N) I5 B9 h( |6 m; Q
    0 e4 R) ^& R3 Y; ]( R% T. cimport org.springframework.beans.factory.annotation.Value;4 l/ x) X% l4 j) P
    import org.springframework.stereotype.Component;: o: L( A( e9 E2 B" Y7 R4 A( I

    ( Q; M2 O# d4 A6 t, j; O; l/**+ A) K) G( o! |6 k3 n
    * 邮件配置信息  {3 O. i# I. B4 W- w
    */
    9 z9 u. C1 q" n0 }1 I; x. H; x@Component
    9 G6 f3 @% Q4 V; f1 P3 h5 p' e@RefreshScope //@1
    7 ?+ F; t: k1 i' v2 ]1 T. Tpublic class MailConfig {
    / @! [5 x7 W0 U+ ~3 @
    ; P8 a- e$ y) z9 q* P8 ?2 d$ J* [    @Value("${mail.username}") //@2
    ! m! L$ ]3 @: Z9 e% e    private String username;2 C- [' @9 t* G( U

    8 e& \( n& d, F: H    public String getUsername() {/ i/ I8 m4 `2 i) k- C9 q0 ~' f
            return username;
    ' o0 X8 z$ L( L8 B% c; N7 F    }1 J7 H$ q( p0 B3 I6 _
    : [" R. U! m6 M5 U5 p/ q' x
        public void setUsername(String username) {
    9 [8 u" C5 b' U8 g8 Y        this.username = username;
    * p- b/ j  @3 [    }
    & z8 T  |' R: w8 v8 U& t9 G8 E: O: z* H: N
        @Override1 |5 j2 U$ [- G" v
        public String toString() {' Q+ i! w1 I8 b( }; v: q' K' Y
            return "MailConfig{" +
    ' J) W5 {! Q! }$ T* d                "username='" + username + '\'' +- n& K6 [3 X( J' M+ q% w- S  E4 @" H
                    '}';
    ! F9 l9 A6 v" F0 D: A1 W$ w3 l  H. Q    }5 Q1 @1 ~$ X" g+ f( ~
    }
    ! @. d+ b& i" L9 t  n" o! w& b! I@1:使用了自定义的作用域@RefreshScope8 Q2 L% J. P( O8 |; f; U
    ) s' D2 b/ v; o+ q" y! F
    @2:通过@Value注入mail.username对一个的值
    % d$ k8 D8 q; h* [4 m$ k$ {8 [' _8 D
    重写了toString方法,一会测试时候可以看效果。
    2 D, u' {# I/ x( D/ Y' h' a: p3 Y8 i, j, Y. @* q; c! {
    再来个普通的bean,内部会注入MailConfig
    2 Q; P) g9 v, }
      E( ?; s3 S6 [+ ]% W2 H. P1 vpackage com.javacode2018.lesson002.demo18.test4;, ~. Y8 V; z8 ^" \" m& ?

    + B  n- [4 U5 O: L3 Kimport org.springframework.beans.factory.annotation.Autowired;
    2 V% W, R1 [  M- R4 cimport org.springframework.stereotype.Component;
    2 X! `& ^0 i* W
    2 a+ s  O! k6 b3 _1 W) B@Component
    . f) G# ]$ t- `! R: i; a% W' U' Spublic class MailService {8 [) D4 _, ]. ~: b( d
        @Autowired- _# y' i( p& u; p, Z( M
        private MailConfig mailConfig;! ~8 H6 x4 Z0 m7 B$ z

    ! I. I3 {2 Q1 R3 p    @Override% X6 T& z/ C2 z/ I
        public String toString() {$ J; a! d3 [, ?2 \' J
            return "MailService{" +5 }7 c( _6 f  G0 d
                    "mailConfig=" + mailConfig +
    ; K" S+ L/ d' l7 t( k                '}';
    3 V( X' L  h2 t- H    }6 m2 a1 [1 q- o: H
    }% }) @. o* y+ x+ E4 H1 i3 D, U- D# `
    代码比较简单,重写了toString方法,一会测试时候可以看效果。, E# T+ U0 g# r& |

    . Y# B7 p. D/ i7 s/ ^3 }% w( Z$ G来个类,用来从db中获取邮件配置信息+ d+ x. W, z/ d* Y+ H; R

    2 e/ U# R# A# b1 P+ I# r( v7 Opackage com.javacode2018.lesson002.demo18.test4;
    9 K# k! i; R- ^5 i9 f# T) n. h" A4 @% i
    import java.util.HashMap;
    8 R! X2 r0 U" v% {/ T0 ?8 z7 Bimport java.util.Map;
    $ u" Y, o" B" ?: u0 }import java.util.UUID;' u+ U" @1 c9 j) Q$ Z

    . H  @! o! v% z0 P' ypublic class DbUtil {
    9 t. C# Z3 x9 }  r7 \/ q- k    /**7 U  _4 M! y, N9 `
         * 模拟从db中获取邮件配置信息
    & p4 j- H$ x- I7 E; q4 {     *8 [  {$ ], ?! b* w3 a$ y4 C' q
         * @return. P6 Q9 h6 D/ X6 j. `
         */
    6 G. d4 Y. A5 N    public static Map<String, Object> getMailInfoFromDb() {0 B1 A$ x) U) f5 [9 ?/ y
            Map<String, Object> result = new HashMap<>();1 p* P0 Q: h9 f8 ^2 [0 O! `! @* L
            result.put("mail.username", UUID.randomUUID().toString());; ^" `6 ~, ?: o" c/ U
            return result;
    & l2 I) e: V2 n1 R, U8 k    }3 h# m! {# a* P4 _
    }
    ! W& _+ r$ X* T$ Y- P- N来个spring配置类,扫描加载上面的组件
    4 x! Q6 s+ k$ R
    ! U7 E2 E7 N& c5 K8 \package com.javacode2018.lesson002.demo18.test4;8 d# n5 |  U0 o9 H6 r

    ; \) Z9 a9 }! Yimport org.springframework.context.annotation.ComponentScan;
    " D8 n9 ]" N; w1 I0 f1 ~' O0 {! yimport org.springframework.context.annotation.Configuration;
    $ m; ^  A' t/ {0 H2 t" h
    & C1 U- }+ `; M# D. y@Configuration
    9 a6 e1 |7 }, x4 z@ComponentScan
    : ^- k( U6 ?  c! z: |, Gpublic class MainConfig4 {
    5 _; j% Q3 P$ p! F}
    ' c7 P9 S: d9 Z来个工具类
    ) C; u* |8 T; Z- f  g2 V+ C+ v) z" R0 k$ y1 e* y- A5 {
    内部有2个方法,如下:$ f2 @' ?; o: Q. u$ V
    7 ^( U' m9 w, W% ?) a" I
    package com.javacode2018.lesson002.demo18.test4;+ T; f/ h$ X! \' f. x
    ; H3 c, H8 c; h5 w' w0 d9 _3 {
    import org.springframework.context.support.AbstractApplicationContext;
    / i8 v$ ^1 c; d# d9 Ximport org.springframework.core.env.MapPropertySource;
    0 Q1 H( I2 D) b9 W* @8 Q4 K3 a9 W+ i5 T& d- Z4 g
    import java.util.Map;
    6 N/ A* c0 P! ^$ ~/ i
    + r& w% s$ X  `! ~public class RefreshConfigUtil {
    4 e2 `+ f  j) W4 t$ @% Q# U8 U( d1 b    /**
    1 m" C6 t6 x% J* B. m' c* }5 E; }     * 模拟改变数据库中都配置信息, H# e8 k0 d& {* o$ o; g9 C. `
         */% \( ]1 |. f* W' Y0 e1 \4 i  M
        public static void updateDbConfig(AbstractApplicationContext context) {
    4 R; S: \. W& `! j5 h( a        //更新context中的mailPropertySource配置信息
    $ Y% L8 Y- T. @  V        refreshMailPropertySource(context);
    ) x# x) a  _$ G% R
    . s3 n& \) B1 `) `8 J        //清空BeanRefreshScope中所有bean的缓存
    ! ?+ a  ?3 u4 C9 c, l        BeanRefreshScope.getInstance().clean();
    5 Q6 V, L9 k& ^. @1 v+ s    }
    * o, Q% H% E* Q% X+ [7 e$ i4 @( l# K3 h
        public static void refreshMailPropertySource(AbstractApplicationContext context) {, {9 t+ h, Y- p# n6 z
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ! o, c# g" A/ _3 m' q% G0 L        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    " ?& L7 e, K; {        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);4 j# P' {( d. C6 c$ Y( h9 t: U
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);* M6 g( `( f! B, [  A5 n% J
        }" ^9 b3 ?3 `0 I. c

    # L% Y# t2 x' M* W! o- r2 ~2 H: N6 `9 o}
    3 f! ]* }1 B3 L. ]% e8 f% fupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    , G- v, n; }, o. b7 w, O  x( C" i
    % S2 @+ f& D, F) [+ VBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    : w: j( O* a  v8 v  @' c- q! r: _8 x2 m9 l  M+ U4 d5 G0 ^! e' E
    来个测试用例
    ' e" A) q( i7 s  d
    1 g0 S7 }$ j  v: p" D5 o# @@Test7 {% y7 B0 g2 [) _4 i( ?4 |
    public void test4() throws InterruptedException {  W- b9 l8 d# E, L, A
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();: n3 K: X. c: L
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    - s% P+ P1 s* q  I! u    context.register(MainConfig4.class);
    2 G( b$ U4 t! L( E6 J( g. |8 u    //刷新mail的配置到Environment
    ! n7 W. c- s$ V1 Z1 V    RefreshConfigUtil.refreshMailPropertySource(context);. o. ~$ l& w! e" i- X6 I0 z/ g
        context.refresh();
    , a! m; O. Z6 h% G; T" I+ v# g
    $ W6 j  u5 q: L! }) _    MailService mailService = context.getBean(MailService.class);' |6 a; n) x9 w4 T8 o5 o
        System.out.println("配置未更新的情况下,输出3次");
    0 N& X: r# {2 S: q* p0 d4 z    for (int i = 0; i < 3; i++) { //@1
    : J8 X- f. |8 _1 w8 D5 o        System.out.println(mailService);
    9 S# F; G% q4 u; o! s1 X# O        TimeUnit.MILLISECONDS.sleep(200);
    : i: {5 k6 t( c( w" A    }# ~2 l4 H) m2 v3 D" w5 A
    ) |+ ~% R4 D% _. l$ G7 y
        System.out.println("模拟3次更新配置效果");" m" [( Z( M& `! t: t
        for (int i = 0; i < 3; i++) { //@25 Z+ i2 N$ Y$ j% H7 s& l7 u$ }9 F" H
            RefreshConfigUtil.updateDbConfig(context); //@38 o# T/ I9 {0 i- r
            System.out.println(mailService);
      [) p% {  J- f* V; [8 ?3 _        TimeUnit.MILLISECONDS.sleep(200);( D) Q: G/ H* ^5 `- E2 `) _) D, n
        }! B* f$ F" A, c
    }" W0 ^* Q% ?; e$ j$ E
    @1:循环3次,输出mailService的信息; L* B7 r# E8 M8 U5 O* \

    ' Z# S( L# ~5 i: Y@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息% ^0 X5 H, [5 x9 I3 J) O1 w
    / Z7 e/ y: Q" k8 V; D$ m! x9 p
    见证奇迹的时刻,来看效果
    3 `3 K6 I; A4 J7 m8 D
      H1 p1 D. B6 n( t+ I8 b& R配置未更新的情况下,输出3次+ ]# T; F1 E/ x# Y  a+ E
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    6 P& D2 p: C6 N+ W' P/ Q  ~MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    - k3 j/ c" @9 @. K8 S. O, JMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    7 {& P9 S# ]9 ]: ~& k( |1 \模拟3次更新配置效果: r, \- t( d: B$ O% R
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    / U, f8 Z$ y) t" R8 wMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    , ?- p* ?7 v- r8 R4 e# i8 oMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}, `, G5 |8 Z! T6 [
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    + _2 A7 x5 ~" q& y+ K' ?& z) X- w* y6 _2 F7 j
    小结
    ' N8 }  ], M( a6 S& c! e/ r3 R
    ' H/ ~; K2 ?( I! u+ `1 w" |动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。+ q7 e! [, X  G( j
      v$ E5 c" v% P2 ?6 J% A
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。. X/ I9 j! j* J! J0 @6 V
    0 _* b  C7 S! R  Z/ @* Y
    总结. ]3 N" u8 a1 U
    % Z) g1 L8 y$ N' A" F. \
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    * t8 f) K- [. {5 e( X- C
    ( V" S. W, H2 r8 X; M* [- h* N, ?' y案例源码6 c  X) r/ S0 U5 v/ _

    ' c( }- o7 C) D* R( a) ghttps://gitee.com/javacode2018/spring-series2 w4 _8 r4 p4 a' J' S& k
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。5 t# v2 R4 h* |3 m4 D

    . m$ h4 z5 [* q  l% qSpring系列
    - j& H" A2 G& b/ r( L4 C. {! a; j5 P3 s  Q3 P( l
    Spring系列第1篇:为何要学spring?0 Y& }$ k! B% i" [/ ?" B

    ( x0 y2 n* Z& U& vSpring系列第2篇:控制反转(IoC)与依赖注入(DI)- O" ?1 Q3 ^( m) \' r

    ' K; L9 X, j, fSpring系列第3篇:Spring容器基本使用及原理  m" }+ w% T& c" u# h2 K

    - H$ V# u+ j8 b$ ?/ g+ k( wSpring系列第4篇:xml中bean定义详解(-)7 J5 e( ^0 i! b5 j6 u, n

    : t5 L) a2 e3 s/ T3 `  _Spring系列第5篇:创建bean实例这些方式你们都知道?1 X' W/ }! C- y1 q& K

    ! _8 }" h( j; c; K9 a& ^2 FSpring系列第6篇:玩转bean scope,避免跳坑里!
    & W, _0 Q4 P& I1 }3 h' q) s+ U+ a9 e2 q( V9 [
    Spring系列第7篇:依赖注入之手动注入
    $ |2 j! E# C0 b0 l5 i( V& q* ~, o6 Q, W2 G# P/ G4 c
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持) G2 n) P* I8 z' y! E
    2 g: G" f4 X8 p$ V' Q# T# {1 _& N
    Spring系列第9篇:depend-on到底是干什么的?
    * B1 M6 \) a- ~4 L6 d# Z% }$ p6 z2 Y
    Spring系列第10篇:primary可以解决什么问题?
    7 f7 i' p0 W3 x% [7 @
    2 @) [9 n1 {2 YSpring系列第11篇:bean中的autowire-candidate又是干什么的?7 w( o/ m# Z; M# ]

    ( W/ N$ o( ?) I/ j3 \" VSpring系列第12篇:lazy-init:bean延迟初始化
    . M' ~1 ]6 G. Q" U/ k& g
    1 A" C! n6 @$ j4 M4 y: mSpring系列第13篇:使用继承简化bean配置(abstract & parent)1 D: X* |8 l; |8 f2 ^8 Z& i4 L5 x7 E
    9 d* L. Q7 T3 {/ z. }- `
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?% D: s1 n* [3 q5 p9 O) M
    1 \8 l2 {" a" l
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    " K: ^$ C4 T. b! U4 M) E  {( ^: n2 j/ B; a5 y' I( ~3 E4 m% P
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    $ o$ F- W( o# y- o* y9 I) _! x0 N) E, G$ o
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)2 Z; R2 d( P  E9 o3 l1 S8 i

    - Q( d" \. ~: k" t( M( W( Y( TSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    * F2 K( K/ H; h
    $ n! R3 o/ J( m; R/ uSpring系列第18篇:@import详解(bean批量注册)6 s# T1 i' o1 O4 ~4 J5 M9 S

    5 H. a# n, L$ D& MSpring系列第20篇:@Conditional通过条件来控制bean的注册$ B0 f7 U( w- V1 d
    * c$ M6 ~0 L; C$ F( @' B+ `8 c+ W
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)8 z: A- Z* n0 G( W/ i& O  C, x
    - Y( l+ W( l' f7 l" r
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    ! B4 b. [' |' o3 k# Q) [+ U! l9 u" ~$ S0 S' G8 V, ^: Z8 |$ l* g' }5 i  j
    Spring系列第23篇:Bean生命周期详解! l3 p* g9 i" Q  b7 I3 b; p+ v- s
    " C6 N9 I& ^' u) b3 n5 ^' u2 Z
    Spring系列第24篇:父子容器详解
    % K( v% v% B% X# u, v6 j# F
    ( G( c) Z1 ^( J( x$ F更多好文章  p- e: t2 r9 O

    / V; Z* N7 r2 }; ^: v' LJava高并发系列(共34篇)
    $ K$ j9 y6 ^' H( g1 L$ T2 `3 s, V4 n7 V/ A
    MySql高手系列(共27篇)
    : f: S- o7 m4 k% y$ `, ^# ^5 N9 K6 Y& l6 x7 M  s. t; S9 @% q
    Maven高手系列(共10篇)6 j8 [$ c0 x# W3 U5 W$ O) [- g* H
    1 R- s9 q2 E& S9 ^$ ~
    Mybatis系列(共12篇)  ^3 y$ q6 h/ r

    0 Y4 H% G, M3 x8 N2 L: H聊聊db和缓存一致性常见的实现方式
      y' t: J& \) O, ~$ a
    4 |6 T, h; _  |* L接口幂等性这么重要,它是什么?怎么实现?! {9 a( y% o3 Q, @% \
    ) F; k0 _8 c+ q2 }! ^8 [5 `: W1 I
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    . {/ e! B3 x/ X3 |$ d& u3 \7 o% b————————————————; o  d% j! j, [4 f
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    , s& x3 W: G2 P8 _& n原文链接:https://blog.csdn.net/likun557/article/details/105648757
    9 s$ x9 U, V9 v. a. u
    # S3 Q; {2 y8 s4 \4 _; [% d0 L( V) C7 B# \" Y5 e. j
    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-6-13 04:55 , Processed in 0.478112 second(s), 51 queries .

    回顶部