QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5448|回复: 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!; s3 X1 w; c3 k" H
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!# S& l% D* y% W% v1 H
    7 [+ Y- E& w6 \+ _; k$ G2 m
    面试官:Spring中的@Value用过么,介绍一下
    3 ^! L/ l3 n' f
    , c+ N8 [1 o+ U. |4 u' ]我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ( K3 k, l) c) P; `" N7 j* Y7 ~/ H0 z$ @( n
    面试官:那就是说@Value的数据来源于配置文件了?
    * [' E4 Q& T/ k/ o1 V
    $ z, d7 W, r& J" _) T" y我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    " t+ f0 k& u% |& q2 S6 [9 X4 v- v
    ; t" \  i+ N% Z4 U* i面试官:@Value数据来源还有其他方式么?
    : `) F7 L1 I. X+ r/ _) ?
    5 e( L& h2 H1 D9 M我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。$ u; P" q8 n0 j

    + A. U" h5 S/ {! X9 o* |6 n面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?* U6 Q2 m" b. S; I0 t. d

    / e: s5 ~& m# z% N) k) l我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    / O  e1 S6 x/ |$ w1 z1 P5 D
    + b" Z; {9 O0 [5 d0 R7 g面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?( u2 |, ]+ f& D# \8 R4 U( x
    8 @' _' E' b3 N' m4 K6 A
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    ( f$ Y* r) w/ ^; A! j" a, u! J
    $ H+ D* a1 M" h/ i6 ?面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?6 f% A- {: [& \& w

    4 f4 t0 M! a# f$ n( K) `3 L( r我:嗯。。。这个之前看过一点,不过没有看懂. b9 |4 F& f6 t$ Q+ J( k$ F$ r
    ' P0 U& A2 E6 V2 J* |7 I) t/ O
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?0 W5 D5 q8 |. R' s, U7 g  b6 E; l: ]5 a
    1 z8 g) Z$ T# w
    我:3万吧  |6 F" z; y6 m3 p$ F

    ) l% J; h& ^, ^面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?1 \8 d) S. }+ l. x

    0 F0 l. `  I5 V) K我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万' H2 Y0 R3 Z1 ~3 N& [( ~# z: C$ E

    : e) x! X7 `) q" s+ b面试官:那谢谢你,今天面试就到这里,出门右拐,不送!  P$ Y& \+ L* W" m' k( }
    9 l, w1 E: g3 m8 l, k& n/ E8 M
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    1 j. l/ q# S/ Z4 K
    6 _% S7 J+ ^# Y7 E; h8 _# D3 r这次面试问题如下7 \' c3 }) k. v4 I+ {" _2 S7 o

    + F7 _" P2 T! B# z' w3 x@Value的用法7 I2 p! o) f  W4 ?
    7 i9 c3 g* ]( R1 ~0 N  K
    @Value数据来源1 B5 J$ \2 V  y* T$ c

      v! K4 a9 S4 L; u# ]@Value动态刷新的问题7 _5 m. J" D$ H8 B! k
    8 j: W2 s% [3 E' |7 G2 H
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。! x/ R. ]' f+ x' V2 q' x5 {' o
    ! Y7 f, z4 V: {7 e, B  E2 I7 W
    @Value的用法
    ; K2 g4 H3 r1 ^: k/ |$ O; c5 w) @! @$ N/ h) t
    系统中需要连接db,连接db有很多配置信息。% q3 h- |3 z5 L4 F
    - d% u/ f' E3 D$ z
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    2 `1 j; ^. l# x2 Y4 u! b) K  Z+ g8 V  F2 g  n5 }: o  p$ A* Z* L
    还有其他的一些配置信息。
    / |' J5 u# v2 A. R" Z# M5 p' [. Z/ Y6 h9 B. u3 f' l) Z9 y
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    3 t  _( o3 c* A5 X) G- D+ F6 Z6 O- |" `
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    + I  B" F$ v8 \% c8 P: \5 s4 h- _  J' m+ h- p4 N
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    ; ]6 a# M6 K6 D4 G  K* ?+ l# J/ U( r( Z' {
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    5 [, H: M+ ?9 x6 t2 s: L
    " B9 X" U. m7 d& q4 }' D@Value使用步骤. O! ]# @0 \1 K( D9 b& @. ]1 L
    $ y9 o1 n$ E5 Z& [
    步骤一:使用@PropertySource注解引入配置文件8 |( ~7 y1 E1 B* o" N
    1 z3 ?- |( i" b4 n! }# V
    将@PropertySource放在类上面,如下
    6 j& z* l* ~. [) ]# m: `* V" @+ r6 Y' v
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    + p; ^# ], U+ n9 ]' k@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。% V2 b# [8 G% Q" g
    % s7 R5 c) |6 a7 f. }
    如:, l1 [9 U/ [( o" A3 |9 \
    . D3 y. e9 g8 h0 H8 p2 H
    @Component  a! O% N. b- j5 m! [/ {0 C  Q
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})( M( L1 ~9 L2 N1 a8 p0 o; A( _
    public class DbConfig {6 }2 b, W) H; M/ H1 D7 [
    }3 \6 G+ W; ~( U+ d6 P" T
    步骤二:使用@Value注解引用配置文件的值
    3 l, V0 u/ K( H; {- x/ V' v, P" i* L& ~, ^
    通过@Value引用上面配置文件中的值:' h; b, e! u4 h' g5 `
    ! X( @4 W! n! d3 {
    语法
    . I. }6 {" B/ x: F3 E; d) z1 n9 O: p. U& H( e/ ^5 _
    @Value("${配置文件中的key:默认值}")
      X% F$ V7 m, M% x* r@Value("${配置文件中的key}")
    + n" N  k; T7 ]0 K! ?& m; H如:9 b1 e% A, k; H% o" ^& i
    % t8 N) V9 j! f3 A: A  i. j# b( K
    @Value("${password:123}")
    $ c5 Y4 t/ E+ k5 K( b! ]上面如果password不存在,将123作为值5 C$ B  \$ Y3 D+ L

    7 {) r* \% c- V) y9 m5 r# e@Value("${password}")
    + ~8 }% F/ c  _$ I1 x上面如果password不存在,值为${password}' R( X0 n' Q' i4 {/ c

    * K. ~1 G# p# @假如配置文件如下
    # M: e9 i0 h+ w, ]1 [; |1 D+ I2 E! M9 G; }
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    . ?9 F; q4 y0 Ojdbc.username=javacode  ?$ C3 R; Z1 e: ]
    jdbc.password=javacode  K$ K# }$ b+ G' N+ j. ^' A) a
    使用方式如下:
    " Z" i6 D& H( d! m2 ^3 d! F+ f/ e9 t. m3 w
    @Value("${jdbc.url}")% y: C4 H' v8 \
    private String url;, V/ B& }' n# g# ]; ], d, X: t0 y
      Y- m7 I* _6 r7 O! G
    @Value("${jdbc.username}")
    + _/ b( E4 k2 y$ g1 Q' }# eprivate String username;  V0 V# N2 A9 B
    & ?5 y* b6 b* D# [0 M$ v
    @Value("${jdbc.password}")& b/ {  S9 c3 L5 E* k
    private String password;( a( h7 K0 p/ j  E) X% J5 Q. l- m
    下面来看案例7 d3 q' D% S" s3 N( p7 r( a
    ( p# z& |2 A. j) H' V/ Q  z
    案例
    7 Y% a! c' ]2 G. v$ b: m
    & {" Z) f6 c' Z  I来个配置文件db.properties8 }! I' E: n, B: ?2 U. `# p
    1 H) |0 z0 J8 P' X
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8! ~- B) [+ g! ]: Y. Z* s0 d0 J
    jdbc.username=javacode, e  Z, T$ s* l% K
    jdbc.password=javacode
    - L' b+ A% @. O来个配置类,使用@PropertySource引入上面的配置文件
    ! u- v0 J; `" `2 C3 B  [4 k+ F) Z5 H& _+ S! I
    package com.javacode2018.lesson002.demo18.test1;
    & J, A2 n) k; G3 C' A7 q8 Q3 O6 @: [. N. k3 E
    import org.springframework.beans.factory.annotation.Configurable;  J: r, [; b% l3 V
    import org.springframework.context.annotation.ComponentScan;& j" J6 r1 F- p
    import org.springframework.context.annotation.PropertySource;
    & I1 i- r3 {8 f5 z5 f$ B, J4 g3 S3 o- `5 F
    @Configurable
    3 M& M8 E/ r; p# Z@ComponentScan" T# w- m5 E9 F5 S3 H$ Y
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}), P) |# C+ ]0 E4 ^$ _
    public class MainConfig1 {
    5 |' W( v- M$ i}; n5 {  R2 e  j: d4 [4 W' O
    来个类,使用@Value来使用配置文件中的信息' v. R7 E8 s8 S: c  j8 B' l

    3 y* r# {9 _: R; Vpackage com.javacode2018.lesson002.demo18.test1;
    7 q& @- e; ]( t9 L! c
    6 V" q* b( R7 I+ ~import org.springframework.beans.factory.annotation.Value;( I+ Y1 @9 v/ ^9 n/ X0 J
    import org.springframework.stereotype.Component;
      R' m2 d. d3 C" j. B# p0 s! Y
    % ]9 ~* N, ^. \/ T9 |6 S+ ^3 G@Component
      y3 I& D9 A7 m$ x' W9 Epublic class DbConfig {
    # A' ?& |! N' E! x/ C9 a
    $ w) d; b; P  n* K. D+ n% `/ E    @Value("${jdbc.url}")
      G9 ?+ O: S7 ~    private String url;
    / ~: o/ Z, ~8 U+ z  u" E2 P- L' R) C  c. V5 P
        @Value("${jdbc.username}")0 A5 x+ g8 ~4 A- ^- \4 ~' s& W
        private String username;. G& B* q; l. f2 o

    ! p5 H5 Y% Z5 n    @Value("${jdbc.password}")
    0 J* @8 ~+ f$ n% |    private String password;
    5 g$ @) |; p0 q; r) g7 P' j5 c" m
    ( p( V6 h+ n4 I% b. `# o9 x  M2 @    public String getUrl() {0 R2 ]$ @- J7 G/ B6 I- E
            return url;
    8 c( P1 q9 h) }: A    }
    " j2 L! y- m4 ?1 }  |
    ' c6 x+ ]  [& r1 m  ?5 ?    public void setUrl(String url) {
    % Q) O2 j9 ]$ }. A3 w        this.url = url;
    3 n0 q% a0 P) u- S, N    }, @6 L# g/ h( @4 W9 ?/ n& X# H' w- S

    ; `( Y% _4 E. a! l$ T  r( n& w    public String getUsername() {
    8 h7 @3 [+ D; v        return username;+ y% ^2 c3 I4 j+ l) _6 _0 f
        }( D' K' ~4 s: D0 m1 h& E
    8 q4 j* j! w) L9 I1 i& L) r
        public void setUsername(String username) {1 A! e2 A+ z5 K
            this.username = username;
    7 a9 P5 X8 j) w5 E# Y7 c7 Q    }% \9 H0 d. r6 B( U. v* }
    # l, B/ }& k9 ]0 ?+ c3 t5 F
        public String getPassword() {
    ; ~; E7 E4 b, R5 N* b# E8 f        return password;
    * N2 e. c2 t( p5 ?! m    }- c% T4 ]* b5 b- \) T0 h3 C$ L0 ]

    ; B6 S, K4 k4 @: l- o    public void setPassword(String password) {$ ~" L$ C# ^1 g: i* a' J, |) u
            this.password = password;. I8 B' V+ }# G% V9 \- l
        }
    % T" ^0 J: C% Q0 _3 V, ?: o
    " [7 f. l( C( r0 ]% m" Y5 H: f    @Override
    " O" a' t+ P3 z  p    public String toString() {, \  u: T, s4 {9 f0 P
            return "DbConfig{" +
    4 o! J% U8 G5 P. l2 A                "url='" + url + '\'' +
    ) l5 C* y* b6 y1 D8 K                ", username='" + username + '\'' +
    . m3 `' [2 Z5 ~7 i                ", password='" + password + '\'' +
    7 [* `7 y/ R8 A' R                '}';
    * S% f9 |% }! K' `    }
    6 r. U5 l1 `, l; o0 Z% [}$ v3 ]7 {' [# m6 F
    上面重点在于注解@Value注解,注意@Value注解中的
      u* n/ b; p+ ?1 I3 B0 J7 l, ^! a& r# w1 [) Z
    来个测试用例3 r3 f0 f" c, G

    0 q/ w* Y8 b, S8 N5 u* lpackage com.javacode2018.lesson002.demo18;0 g2 \+ ?5 E* d2 L6 y9 t9 Z
    2 b- e. i& k8 C. b
    import com.javacode2018.lesson002.demo18.test1.DbConfig;/ U+ v7 x% N2 ^% o1 j' Q: j' H/ Q
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;6 E- H) v$ d0 Q7 `7 U
    import org.junit.Test;. M( n2 Y9 s7 [1 f) W0 s0 |) ]7 N" {) B
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    : Y$ z: ^* b* h. u- n* i
    ; L# c" x3 I% opublic class ValueTest {
      `; X/ p( E4 s6 j. O# T% \; v+ \) A/ E. T' y; s" u
        @Test/ z& J' C6 Q2 l% P# E) Y* E
        public void test1() {7 Q& m2 Z/ P/ P* x+ A' @9 p
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();  ^3 a! u3 ]7 f' z4 P/ Y: e* o, Q
            context.register(MainConfig1.class);
    , V3 h/ U, q+ @% P4 _8 P- X        context.refresh();, W& r. i7 v& V
    # Q) y  {- {5 r
            DbConfig dbConfig = context.getBean(DbConfig.class);
    " i. _- D0 r- H8 z* [, r        System.out.println(dbConfig);) ?# b) s) T) k0 o+ Y: r
        }
    8 Q1 a4 ]( q, O; b1 s}
    8 N& f% z- E+ y. X运行输出
    6 z' F. m* \& E' h# |# p" S7 ~# ?5 h
    3 F9 j& X; [1 |4 S; h1 \( m4 @DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}3 [6 O) Z6 }2 N, l( y! U# H) ^6 c
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    + H( H  Z+ |: ^
    0 v& }# D: |' G0 u@Value数据来源6 q3 b8 C/ ?1 o) ~5 }
    - \/ B6 c' }) d+ C$ r) i1 R
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。/ U: ?* I) r+ x9 v3 _  A

    * c% M# ~) @/ i3 d4 {我们需要先了解一下@Value中数据来源于spring的什么地方。
    ( }2 M( ?' n( X# b7 t( o& S+ `; u: t
    spring中有个类- B+ v6 R3 [2 M; y8 e

    ( m, R, e+ T' z5 q  S5 S$ {  m) Sorg.springframework.core.env.PropertySource
    : ?7 g9 A) \* {/ F5 T2 r1 ]: Y6 i可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息' l: V: h' e- D. T

    / r: ?4 U6 Y) n$ C% V- S* _内部有个方法:: I2 H$ H. O1 X: B4 T( \) v
    9 ~) \$ F& W% E- E
    public abstract Object getProperty(String name);
    0 f0 q) r1 g: l8 n$ @1 C0 F通过name获取对应的配置信息。
    $ x* Y7 Z' O# D# K# m* G9 E
    , V; L8 f8 F& W系统有个比较重要的接口  d( Q3 p, h( {/ N9 [2 L* t! v
    4 {3 m- h8 |7 n& h; D& L, ?0 B
    org.springframework.core.env.Environment
    # M! d5 V+ r# z: M- d2 m' l用来表示环境配置信息,这个接口有几个方法比较重要
    - J& [" |+ @% e7 Q7 ^) F# n
    % q3 }8 n/ Y2 q+ SString resolvePlaceholders(String text);
    / O, y! U2 X$ B7 s6 e6 X1 OMutablePropertySources getPropertySources();
    0 D, ]: Q2 B+ V& O; _resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。, M/ }0 S) j) p. ]1 v2 n

    3 `$ \+ _4 c, |8 `5 K( Z- ]getPropertySources返回MutablePropertySources对象,来看一下这个类. O% Q& S' H; I- F# G  w

    0 {! R2 w! K. g7 X/ Epublic class MutablePropertySources implements PropertySources {3 a8 j* N0 x# U' ~+ j( }( ]

    ' L# @% U& ~" O5 K    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();- ^' u9 w% q. y! V! @: F% Z

    1 h  D5 G- m( t/ G( Q$ B4 w$ h}# `) h# P8 \0 q3 o" X$ [, v
    内部包含一个propertySourceList列表。. ?, c" z4 A0 H3 G) X

    6 N* [3 a, r+ z9 S4 \spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    ; `8 [. y4 N( J2 e7 F7 }* W  a& g* z' z3 m  P+ X& ]9 O$ @) }; D. Q
    大家可以捋一下,最终解析@Value的过程:
    ! B% C- e8 ~0 i7 U" N9 d7 e, F* K) j0 K8 g5 s- B8 H
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    2 t+ z1 Q+ `/ p' Z6 V2. Environment内部会访问MutablePropertySources来解析4 l( Q6 N6 |! @
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值4 [7 P7 A0 w8 o
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    * J2 p3 T' v/ Q, F# i8 a6 z
    . G3 H( h9 c) Q. _, K6 Q下面我们就按照这个思路来一个。
    1 T6 g0 o2 H& K9 ]* {9 u( i( J9 V% r6 `- U
    来个邮件配置信息类,内部使用@Value注入邮件配置信息7 Z* j0 y1 p- I  X  _. |5 S! d9 Z: G' V

    " D; K! E$ F$ a! H- E7 m& a! tpackage com.javacode2018.lesson002.demo18.test2;. Q- A7 N0 v7 S% X( H. _. N

    # T; d$ I6 R1 s5 `5 N4 t" H- `/ O% [import org.springframework.beans.factory.annotation.Value;
    3 ~' F9 r3 I# y# O' J4 Kimport org.springframework.stereotype.Component;( O. j1 ^' {4 z3 J, ~1 E

      f3 h2 ~3 A$ B2 g9 C/**
    1 R/ K' N% e; y7 X * 邮件配置信息
    - }# c0 O: x  H* @% B */
    0 q( I( `7 p$ G* ?3 z  o@Component
    , ^% l: g3 m4 B  q% j% vpublic class MailConfig {$ p& \% s. t$ `0 ?9 }) }/ m* l" N- |

    + ]2 o+ v+ C: r: V8 J' ^& {    @Value("${mail.host}")
    ( p" e) p3 U+ j/ I    private String host;3 b5 D5 m0 \/ j0 e" H5 s. N' r
    " f* g5 _% k0 [- p" A4 b# o
        @Value("${mail.username}")
    ; G" l9 _1 `6 B) u    private String username;
    ' J, y2 W& k- m6 W8 o# K" P: S
    6 X# k: m* \1 y  }5 {, f* @% D    @Value("${mail.password}")
    3 I8 D; K9 j9 P6 F6 r    private String password;& w$ n3 l# H0 h8 i' C" f

    , \* ^1 ]+ M/ `$ O    public String getHost() {
    $ J7 t( ~3 O: u; k. r4 S        return host;
    6 Z& g/ j; C- C  |    }7 H8 h6 U. @0 K

    % d2 c* e6 s5 |$ \8 F) Z- F. V    public void setHost(String host) {
    + N- C5 T! ?" R4 N0 N& R        this.host = host;: N' B3 P" C7 U+ G
        }: w* a* e6 b1 g5 Z  f6 p

    ) ?7 ]7 P; p7 v8 i9 G+ n    public String getUsername() {  T8 {% c/ X7 r2 q( F/ s4 i
            return username;& p+ B1 ~9 R4 C/ O7 i) Z
        }1 f0 X) ~9 |4 a
    6 N+ j* v9 Q' X3 ^3 x3 _
        public void setUsername(String username) {+ N' z& d$ v% b' N6 j+ j
            this.username = username;- t" M$ W1 I/ B3 @0 W8 T
        }
    ( D! l$ Y3 E2 u+ V! [# h. V* [6 r/ f& h8 l0 |& n/ d( O& \8 r
        public String getPassword() {
    9 l+ g3 g/ v% E$ F# u        return password;( _* ]/ S; z. |! M+ k; k' y
        }
    : B- G2 M+ p5 J- l! T# N4 T$ a* I& B- h# K& F: Z0 ^3 i9 i- \
        public void setPassword(String password) {
    : {! h. ]" |- G# o5 i5 C  A. s2 R        this.password = password;! u, `0 O) ]9 G# w+ I* j
        }
    7 a+ p7 r$ j5 B* Z$ C8 o& A: ?1 N1 h
        @Override
    + J) o8 e! I8 q    public String toString() {
    2 T  s0 @$ A0 y! K6 z: G        return "MailConfig{" +, T1 O. |$ T; g7 F
                    "host='" + host + '\'' +
    * \- c2 C3 W1 f7 ~                ", username='" + username + '\'' +
    ; O; L/ ?8 ^8 @                ", password='" + password + '\'' +/ P9 l+ Q& a4 g4 R8 M
                    '}';
    ) T& i3 ~+ ^4 P( h    }
    " F2 y9 E: Z) I! |}1 G+ U8 j. d  _1 n5 W& c2 V
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中. a) _4 t  R6 r) o8 F5 k  W; F7 s  K

    % i2 e2 V0 i4 M3 W; U. x7 Gpackage com.javacode2018.lesson002.demo18.test2;
    ! ^* ]7 F% h' t3 a3 j' r# P* L7 u3 P" Q4 C, P& C4 A
    import java.util.HashMap;
    / X4 _& ?  U0 U; l: o+ timport java.util.Map;6 ^0 J3 ^4 A, N) e- l8 d
    8 e  d5 F) ~0 J& w. b& u- M9 W
    public class DbUtil {1 h/ j& G- [' {# R' B. ~+ s
        /**$ R4 t% i1 l6 m: J# N$ e
         * 模拟从db中获取邮件配置信息
    . `8 P9 L5 W  H1 a- C     *
    8 Q* A5 X+ C9 v' {     * @return3 q8 O7 n. B4 j7 W  n6 d
         */' R2 n0 Z6 ~/ C$ `
        public static Map<String, Object> getMailInfoFromDb() {% w1 _$ }# t5 a5 B% u7 I
            Map<String, Object> result = new HashMap<>();9 c: r- I! a3 a* {& C* }
            result.put("mail.host", "smtp.qq.com");( g1 F0 i# O7 w3 N0 a  g
            result.put("mail.username", "路人");# z( y! C) ~  U/ M( s1 m3 o
            result.put("mail.password", "123");" c! V; o, l+ P
            return result;. E& z9 H. C; S3 {( I7 J: m
        }
    " F6 I' |9 E* s6 t9 @( }! W}0 R/ w$ |* e0 P" L/ c: Y# [2 l
    来个spring配置类
    ) R# |6 u3 j* R0 S0 B  N9 t& G6 e2 M6 G
    package com.javacode2018.lesson002.demo18.test2;
    % b  \, _2 m& A9 r! N1 u+ P
    8 j) G# Y9 T" ]' timport org.springframework.context.annotation.ComponentScan;
    3 e( R5 ?( `! N( s' N+ y5 \import org.springframework.context.annotation.Configuration;
    # E- G2 ?  S4 w' N; _% c
    # J5 s  v/ @, S  C$ ]@Configuration7 W+ ~! i  M; t, Z9 e# s$ j
    @ComponentScan1 v) }' c1 {' d# S& w% i$ ~! M" q. h' w
    public class MainConfig2 {
    ! F/ j0 }3 \$ b, p% t}' G. Q* h. S% S0 X# S4 b% I) B
    下面是重点代码  \" y$ G/ |. e+ Q

    2 u  R# y' X0 S. G/ a. m' |@Test4 `* H, D% i3 y1 B" L- s
    public void test2() {& @' A7 C" H4 q! t8 Q
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" j5 S' ^6 S+ \* i& W0 ~+ f$ W8 }
    " M5 G' Z2 @4 T$ h4 E. w- i% \
        /*下面这段是关键 start*/0 W' M2 X5 H6 K. R7 Q2 ]! J9 B
        //模拟从db中获取配置信息
    1 [3 L2 N3 p& }( d$ i% E/ m0 r2 H    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();- l' ~) t" ~8 h' ]( }& q) J- i
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    2 _& O" X; O  M; O    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);6 R  E% ?3 I: J! y) U6 R
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    : U: K- B+ r% s" E3 K% H    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    3 }6 ~- `# x5 p2 `    /*上面这段是关键 end*/
    / G# p% W  W+ }0 C
    " C) R8 k' f" n3 m    context.register(MainConfig2.class);* ~/ S4 U$ E. I" ?& r* R
        context.refresh();
    0 g# s( _$ Y5 p0 o" Y    MailConfig mailConfig = context.getBean(MailConfig.class);1 \+ k! `1 F/ T# p
        System.out.println(mailConfig);
    5 A' i# w  ]" J8 e% O+ R}0 M6 P  Y$ R9 h& v
    注释比较详细,就不详细解释了。! S, z/ Q8 a1 m: z

    ( @9 b9 Z0 Y- t# N$ _直接运行,看效果, i8 |  y, B9 V

    3 S4 X( c$ s, I) \  d3 BMailConfig{host='smtp.qq.com', username='路人', password='123'}
    ) T6 @7 R. _2 M0 b9 ]- Q/ p有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    6 Q: R9 x" x" T0 F- L. l5 t3 j- N/ j9 u" Z
    上面重点是下面这段代码,大家需要理解
    " U, y7 O& ]( N4 [0 ]2 t' ^8 v' R+ w% _. z
    /*下面这段是关键 start*/
    ) W  q8 r* l, D, S//模拟从db中获取配置信息6 H* Z% n. j) C7 g" K! V5 l
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    8 w% d0 f% w5 |  W//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)1 i' n( F- W1 n
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);3 E* H* n) A9 r/ X" X9 f
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高8 O8 `# s5 c) T  Z% C
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);' O9 u4 m) g: `9 f: B4 k  ~
    /*上面这段是关键 end*/2 ~/ t0 r) ^4 l' q9 t
    咱们继续看下一个问题
    , `" Q% Y- _! a2 Y: m5 H; b7 @" Z: o8 H( T6 Y4 {5 n0 a& L
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    # h8 V& r# C$ Q- ^5 m" g; m# g  m* a5 ]) h4 l: O! S- h
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ( \$ I4 _- W" `: N1 l$ c/ \5 j2 j6 c  ^! j, ?, \* n
    实现@Value动态刷新
    $ q; C4 x! v7 U) K8 b8 \3 K8 d6 j4 ~: f7 \$ w( u
    先了解一个知识点
    5 Y" U% y3 Y5 _2 _. D$ t3 G4 @3 j' J2 n2 Y
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
      v/ y# @/ v4 V1 o" U; c) A, B
    . F7 n5 h1 b3 L* L) _这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解, {' [" r- A: w' C  u. l0 j; }0 f
    , d+ O; _! s$ U9 c2 q
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    4 w3 @* i8 e0 p4 z, s& q7 Q' E7 I, z8 ~7 A; G$ T
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    ) H) q; [/ \  d! e这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
      i# G4 r- i- |! J7 i
    1 M0 Y- l& d) m4 U0 Y; N1 fpublic enum ScopedProxyMode {' [4 v! s3 e% x4 X* ]- A& L. K
        DEFAULT,% m. \5 c, e, y- N* Y: H: j3 T4 X6 H+ o
        NO,- G5 u$ |. j+ w' u: N# T
        INTERFACES,
    5 M2 m3 Z9 M: `' L    TARGET_CLASS;. G% A0 g! K/ n0 X: D
    }
    9 _+ R5 {# B) v  y7 E& J; V9 a前面3个,不讲了,直接讲最后一个值是干什么的。/ S/ i! H4 Y7 K9 h
    6 i' ]$ I9 y" \2 N( v. W3 o
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    $ a( S: Y$ h; a# {1 J, b! X0 r) s' E: Z3 `1 U% A1 v
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    6 C# |$ v, @. `7 t* p1 j4 }
    $ L2 e! T# X8 Q0 p自定义一个bean作用域的注解: n; t. h; j1 C$ y4 w/ N5 n2 E# Z; H5 `
    , ?+ R6 \; y5 G. H
    package com.javacode2018.lesson002.demo18.test3;& F0 E4 j* O) x( L* f& S
    9 |, T) _+ Y/ H5 _7 Z
    import org.springframework.context.annotation.Scope;
    + k+ e# Z8 Q9 j  ^. z: j# fimport org.springframework.context.annotation.ScopedProxyMode;" n2 T8 Q7 G+ e4 j
    % v2 W$ H# A8 F: x, T
    import java.lang.annotation.*;
    7 J: n: S; f# }2 U5 ]9 d( j% z! Y: J/ u# S" |6 ]1 A
    @Target({ElementType.TYPE, ElementType.METHOD}): a9 [  f2 U0 s( a' [) @9 g$ ^
    @Retention(RetentionPolicy.RUNTIME)
    , `' t3 @# i/ V% e@Documented0 d; Y# W; }; o2 [
    @Scope(BeanMyScope.SCOPE_MY) //@16 Y' J2 \0 n$ I; p# F
    public @interface MyScope {$ Z6 ^7 w% C4 a4 M1 b6 X
        /**0 g2 M* I6 K) l3 I4 i  @; {
         * @see Scope#proxyMode()
    8 ~9 ^3 {) j- F6 [& t$ ]     */
    ( G( ~! l3 [3 o# B. H    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2" @- k) S0 t0 w# G
    }
    1 q8 T7 p% E3 k1 V- ?5 w. P0 \@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ' G. @4 h" e+ e# Z' `* T  A
    9 S) U8 ?* [% ]& Y@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS1 [: T1 z" v& i  v- o
    / C. X/ E+ ^/ |  h1 F1 m2 x/ z
    @MyScope注解对应的Scope实现如下
    : L: j6 E/ k8 z# Y0 G4 i6 s( D
    ( s; @% ]$ L; q! h) e- n6 Vpackage com.javacode2018.lesson002.demo18.test3;
    & D1 I& d, Q1 Q) A; x* V* x; _, v
    import org.springframework.beans.factory.ObjectFactory;
    / R6 p9 o4 I. Y% v0 j* T% cimport org.springframework.beans.factory.config.Scope;
    " E" P5 _# n3 Dimport org.springframework.lang.Nullable;& O& [6 \+ \/ @) m4 A

    8 I6 P2 d1 R2 D/ K$ w/**
    1 F+ Q3 K! [' s. J/ l3 } * @see MyScope 作用域的实现
    . m0 j4 N- }0 M  i% E7 E */
    , i- o4 U. n/ d/ W1 j- i' apublic class BeanMyScope implements Scope {1 z2 U& m: R2 [3 y

    1 F6 P  C+ N, J0 I( s; W  m    public static final String SCOPE_MY = "my"; //@1
      ^4 ~& ~; w. i
    + E! A' d4 m0 ?. w" p    @Override) P' `/ _! V$ @( ?0 @2 n8 v, h
        public Object get(String name, ObjectFactory<?> objectFactory) {
    0 r$ y: l$ [; ~% O8 }: J& Y5 o        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2" [' w' g7 e: w" {" U, h* f
            return objectFactory.getObject(); //@3
    # x! @. s$ R9 C( ^    }& _0 z; W2 C# y  R+ n) u" `0 _0 B
    1 _8 p: L" S- I9 l
        @Nullable  X5 X* b$ P! ?0 y
        @Override
    # P* K! |' x$ \! O  H( x    public Object remove(String name) {  P( {7 o6 |2 B/ g% [' Z* c: ]
            return null;
    * E4 `) A, ^3 ?7 \9 B5 m    }6 q0 `: @" T7 L, |2 q: P

    8 s: n$ J0 W0 E  c    @Override9 @2 k( _, z7 J6 h- B4 k
        public void registerDestructionCallback(String name, Runnable callback) {7 t+ u5 L, S& C' D7 k
    : E: `( n# `$ F7 G4 ~
        }
    6 \# _% d; j8 ?
    . C2 T+ o1 d/ R, h+ ?    @Nullable8 d" c# L# m. M/ C- V2 d
        @Override
    - d( }0 u8 u4 s* U' L    public Object resolveContextualObject(String key) {5 O) {1 r* C* R, E, H
            return null;$ Y6 V! u4 H+ s/ }
        }& h; @$ i- ?5 f3 E" }

    2 P3 z7 g3 M) S2 S; L    @Nullable1 S0 ]3 ^. X4 A8 e% h4 n8 [7 @
        @Override
    5 u5 n- E4 K# v" j    public String getConversationId() {
    7 V' g8 a9 `9 g% T0 w4 U6 g        return null;# {) t7 R  t! m1 Z  ~
        }8 r% |7 S' K" R8 _  \* E( m
    }
    4 N6 \5 |7 f" N5 U9 H@1:定义了一个常量,作为作用域的值
    , q' M7 S! Y: z- P0 n7 `9 Q. B3 ~% g; t! _- G
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    + v$ t# h4 ^$ Q1 D; D; o& J" f/ G% h
    @3:通过objectFactory.getObject()获取bean实例返回。' k- D4 U, o6 p
    , i' `! D) P4 j2 z: [# ?
    下面来创建个类,作用域为上面自定义的作用域# f+ J+ q; c$ a6 T4 }# V) r

    ( _! {; t4 O  upackage com.javacode2018.lesson002.demo18.test3;
    - @0 t* f( P9 B+ |/ ?" E; C* o2 H" D" H( ?. r- K: K! I
    import org.springframework.stereotype.Component;4 y4 g/ M3 O5 V  F. x
    ) f' i. ~* `% C* n- C4 H3 h
    import java.util.UUID;
    : X/ n; w/ l  m3 x( ]& B1 H
    9 G9 p. Q6 \9 e5 D. x5 z@Component
    - [" p) X7 H: ], L! L@MyScope //@1 " f' l) \' m) s0 X& E! u/ Q
    public class User {
    " B1 I: t6 {  G+ M- V/ w$ G1 w* A; p! q2 p+ I
        private String username;
    $ C/ r- O% k8 v4 F) j
    + a- L9 H, K( C! s- O    public User() { / [" z3 B8 _; _' _
            System.out.println("---------创建User对象" + this); //@2
    % q/ r9 |( P9 @8 u- v4 x7 E0 X( a        this.username = UUID.randomUUID().toString(); //@3
    0 \5 u0 E7 [6 a0 w6 S4 k( y+ b$ S    }
    * f; _, ^* e7 X) j0 {/ ]
    ) k, S/ Z1 {+ Y' c6 Q  B- E% O, r! B    public String getUsername() {, j- `2 u. o: V4 f* j
            return username;
      R$ I# E6 d0 }0 {1 v/ R6 X# S% p3 A    }# `, A! m. {3 I1 v4 c! M

    / ]! z# ~8 |9 V6 w7 N7 H    public void setUsername(String username) {
    * x5 P+ {; l* H! ]4 J" ~0 @        this.username = username;
    6 b, G) N/ j7 p$ Q    }9 r1 A- ^3 X. K3 a, Q, S

    8 o/ K( R- V% w) B; p2 Q+ e}
    ) T; z1 s, i2 W. _: }: t/ f@1:使用了自定义的作用域@MyScope3 x) ?* V  E' m3 a" G' S% ^$ {3 y! ^6 a

    & l) S( a, ~# P* W* K@2:构造函数中输出一行日志1 A0 y  i8 d: L0 U6 u- Y' A

    ( K( T0 S3 Y0 \) e@3:给username赋值,通过uuid随机生成了一个# |( w  J: F" z- p
    2 Z" y: }3 h6 O9 p
    来个spring配置类,加载上面@Compontent标注的组件
    # w( H9 i% K9 E! \1 A# f
    % Z: I  m/ A, U- Cpackage com.javacode2018.lesson002.demo18.test3;- L8 |$ ]8 m9 ~" V# z7 j: y% U

    * X3 W" j' B6 Q0 C1 g6 e& O' h+ mimport org.springframework.context.annotation.ComponentScan;: m; k  s4 a+ d0 Y# m+ t8 E* M1 J/ D- B5 s
    import org.springframework.context.annotation.Configuration;
    / r3 v( j( b/ E9 f- S. i& M1 H& D' j
    @ComponentScan# b: o: a1 O" j1 w( m
    @Configuration, ^7 z0 E5 d# W6 c1 d* A0 U# p) _
    public class MainConfig3 {
    6 A, V1 f; f9 R  s, I& U9 E}" h. ?4 w1 }* J) |  \7 m
    下面重点来了,测试用例5 U8 B% c, z8 i% e$ s

    3 ]: o! d3 j( _5 d8 y$ \6 T6 T@Test
    . F7 e: s4 z+ Z5 u4 dpublic void test3() throws InterruptedException {6 s: N. a$ y8 o0 \3 V
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ s& m3 e. T& E9 g    //将自定义作用域注册到spring容器中: i  q- K. _+ d; a1 F
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    6 _+ k. H7 o) P+ t    context.register(MainConfig3.class);
    + T2 X8 A1 J! s+ {6 s    context.refresh();+ w* [/ K1 Z- E0 L7 }0 h, h
    4 ?$ g5 M& x' |  Q+ W
        System.out.println("从容器中获取User对象");0 C" d$ M0 q: X6 ?' M: Y
        User user = context.getBean(User.class); //@20 g  l' q# Q0 }
        System.out.println("user对象的class为:" + user.getClass()); //@3/ t/ c- \5 U2 B6 \# x: r, Y

    ! R( s  y( Z! f% F    System.out.println("多次调用user的getUsername感受一下效果\n");
    : ~! H) ?4 e( e3 x; B! A: @    for (int i = 1; i <= 3; i++) {
    3 N# X6 R6 g* c& N$ n' [8 }        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    - I3 v1 N4 v$ @2 f3 N3 g        System.out.println(user.getUsername());
    8 K2 w+ F8 i# A! Y        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    ( M( m: ?6 M8 i    }/ [* L2 {4 A- n  B2 }# o
    }
    " Q' t7 a1 o6 B) @; }@1:将自定义作用域注册到spring容器中
    ! W% I$ N( l3 K( o1 E' C( g0 V' ^! b/ F; p+ A3 I9 m1 b
    @2:从容器中获取User对应的bean/ O6 {1 U  i0 L- t. `4 E( E2 N
    5 H4 ]5 Y( f3 G% r
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的! B+ N7 Y; |+ @5 a$ D* E- p
    % ]5 t3 F- {$ p& F! o. r! |9 l' p
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。) E' a% w  C  G( A

    ' X% ?& X, W0 `  ~8 X: F# t( ]% G见证奇迹的时候到了,运行输出. K3 a1 T" v5 G; g  V0 x- O

    6 F$ K# k# v4 n( t/ a% `  b, h从容器中获取User对象
    . a3 z$ U5 J# B4 h- g) v: Suser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    4 O  d0 |9 G! f# H多次调用user的getUsername感受一下效果4 K5 m6 M3 T3 n$ }; ^
    2 S( ]3 r8 J; |
    ********) G7 s0 L" k; ~9 N1 v( q
    第1次开始调用getUsername% y4 L! @4 P# ?
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    " c( s! @! S7 f---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    & Q: s* B; Q8 _5 S  n" Q7b41aa80-7569-4072-9d40-ec9bfb92f438
    7 u& Z- H" O: `7 `) [. |第1次调用getUsername结束
    - F9 [0 l) _1 B- i, \! j********5 s' C5 n' o  R
    ! R; j1 C% N, U$ N0 h3 h  w( [" e
    ********4 U3 f. a# h: C
    第2次开始调用getUsername
    , W, `) n# N% u6 FBeanMyScope >>>>>>>>> get:scopedTarget.user: T8 `; h, e0 \1 v; h+ ~/ ]4 ?8 Q
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    6 }3 ~+ [# B1 f  t1 [4 b0 \8 M01d67154-95f6-44bb-93ab-05a34abdf51f
    . o! S2 l7 F. @; z. y第2次调用getUsername结束6 w  m" t& n% y8 @/ a; U
    ********/ p& ]5 j/ F/ ?" L2 n

    4 ]4 @7 W, I3 N********; l) o# V% ~5 F% H
    第3次开始调用getUsername
    * v) a- u4 Z3 i& ~; }BeanMyScope >>>>>>>>> get:scopedTarget.user
    5 g" s3 K0 Y1 z9 c3 ~---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15  T5 w; g7 r; Y" x8 `) x- |" e1 i
    76d0e86f-8331-4303-aac7-4acce0b258b8
    2 R/ [3 X9 s! n2 o第3次调用getUsername结束
    + a4 U8 A# Y7 |/ R********
    ' n. X) ^, j1 y* T* l. f; l9 N, A从输出的前2行可以看出:
    3 |/ G3 h# y. Y5 G1 n4 \* |; k0 W
    7 _8 Z, x3 I- _) F; t" {调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    8 U9 D+ o6 w( h' Q  h6 {
      @2 b. q. ~* ~1 n4 D1 A0 i第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    5 B) e+ l, H& ~8 {6 E, y' l
    ) V6 C4 {8 x3 M. g7 _9 P后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。# W$ ^! [( E2 I: l

    + r+ y1 p# |2 e' {1 o通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。# j9 d( K( @- Q2 a! k1 C  F

    2 \+ ]8 k9 v9 E动态刷新@Value具体实现" F8 \  D7 B2 f9 l

    / Y. [+ h8 W1 h2 i: n" i2 g那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    + A1 `) a. ^7 R+ |- E' }1 G* {$ x
    & p' d% I. Z, Q1 a4 Z先来自定义一个Scope:RefreshScope
    9 F" T% a3 R# M
    6 K; S2 m( l& v0 k' t8 j, `package com.javacode2018.lesson002.demo18.test4;; x$ o+ D0 [* z3 I( W

    . N( _, `2 _; Fimport org.springframework.context.annotation.Scope;
    ) w& i( H. s; e! z" [import org.springframework.context.annotation.ScopedProxyMode;" }  `5 U5 S' `2 q4 x
    / k/ x$ Q2 w4 M8 a) I8 _% C% y' n& W' D, \
    import java.lang.annotation.*;
    # s3 H$ c  M" U2 |. C; M0 O! p& O- l! ~+ d5 G
    @Target({ElementType.TYPE, ElementType.METHOD}); J9 u- M& D  s
    @Retention(RetentionPolicy.RUNTIME)% o6 N2 u, n+ c+ f* v" o; K% q
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    ) A+ y. ^8 e4 b2 J6 S@Documented
    9 `, k+ b, e' E' E! `9 c# s/ ~public @interface RefreshScope {( e7 d5 P& i9 n. v* s3 s
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ; B6 M- x. s; j1 \$ F}
    . _1 c& Z6 f5 h% ^" X要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    / e+ g) g" {, S8 S: d
    7 v4 a2 j. G( @@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    , E% ?5 W4 c3 l3 l
    * n& ]% x. U  f  d$ Q) R: X! A这个自定义Scope对应的解析类. x- u* C8 p) ^. d5 d

    # F5 l. A0 k: k下面类中有几个无关的方法去掉了,可以忽略1 t/ P, d+ \, _; w0 Q
    ) H' K+ E6 c, ?2 f
    package com.javacode2018.lesson002.demo18.test4;
    2 u8 R$ b" H( O+ E: |4 m6 p8 S  B" R, y  J" @+ S3 t: v3 V

    8 ?* Q1 ^" c2 |# w& g+ Mimport org.springframework.beans.factory.ObjectFactory;
    1 ?- n! w. M" ?* h6 o7 U& \0 Uimport org.springframework.beans.factory.config.Scope;0 V- c2 O- R8 F! S! l
    import org.springframework.lang.Nullable;: Q' ~& H# A+ B2 L$ Q

    6 c6 `3 l+ t, S# u- V+ A5 F( ~3 |import java.util.concurrent.ConcurrentHashMap;! U9 _; s! l! b" A6 ~

    1 }+ z% X2 @, @" ]2 `1 ^  Wpublic class BeanRefreshScope implements Scope {
    ) z. g8 J5 v7 _0 L( _, U8 D: R: [( e2 k! m$ H7 Y" o6 D1 ~3 v6 z5 G! |
        public static final String SCOPE_REFRESH = "refresh";& _' h7 b# a4 X/ M+ F
    * b+ O  j: ?6 R/ @7 W( ]7 l
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    6 g6 ~( ^- O8 {) d) b% S
    , o9 H$ v' E8 l9 T9 o7 B    //来个map用来缓存bean; r5 e* Z( u/ Y9 {% \
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    9 c8 \! B$ A5 `4 {& I
    4 M+ _% O( j1 F4 _& T    private BeanRefreshScope() {
    ( B/ X" z5 [) {& f    }$ Z+ }9 O% U/ @8 I4 ^

    4 `+ E& j) n* \, `& t8 S  w/ `! {    public static BeanRefreshScope getInstance() {$ T9 X- i/ K% [3 E: ^
            return INSTANCE;
    ' ~$ T: s7 n( i/ ]1 B    }
    6 T9 q3 d8 {3 v$ E3 v! A  K  a/ n
    9 c9 p+ |* I8 B3 c; A2 }7 Y    /**& \/ G* I) b6 Z& G# s& G8 x
         * 清理当前% H0 u! q7 v% O9 F; y$ z4 u; {
         */* q4 h5 F- v/ m
        public static void clean() {
    1 J( E- F! n8 m$ C  i        INSTANCE.beanMap.clear();
    0 ?: J  A1 H- u2 H    }
    - H" J" y9 ~) ?8 f
    : ]& y. w: _0 S  n' a$ b3 A1 ~    @Override) v7 D* a7 u; G$ M! S8 h. w
        public Object get(String name, ObjectFactory<?> objectFactory) {
    . n$ C8 w: M# b% j5 }+ u7 B2 z        Object bean = beanMap.get(name);& {8 p8 i* I/ D) K4 u! |! v' a4 |
            if (bean == null) {+ c9 X7 U( h3 L
                bean = objectFactory.getObject();
    . _9 F7 k% @9 ~( a            beanMap.put(name, bean);# T, ~8 d, M+ A/ v
            }% i  \: W3 e: p* b6 f6 L+ x: p
            return bean;; g  Z3 C- C) s; W# m7 M
        }
    ! D& ?! f" V) e+ h8 m" ~% B4 C2 S! J
    }2 M+ I) |* H7 I
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中! D8 G( g: S# \' A/ q
    ) ^9 C" B# L8 A
    上面的clean方法用来清理beanMap中当前已缓存的所有bean. ~: B1 S: Y/ M
    . _/ C# g# v8 r! i1 m1 v0 s
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    ; h* u) |! b/ D' }% y
    & Z6 ^0 V# p, G6 a5 X% S) Jpackage com.javacode2018.lesson002.demo18.test4;
    " X1 m9 w, x) W1 p/ Q6 W$ w+ F1 x+ x' P# C1 W& T( T
    import org.springframework.beans.factory.annotation.Value;
    : y" q8 t2 b' N0 ^import org.springframework.stereotype.Component;* h1 Z6 a9 x" K$ Q

    1 O4 _4 }' H6 o& n4 Z3 ?: W/**; o) W  W. e! L2 J* @
    * 邮件配置信息9 w# \4 z  [  U- K7 u3 J
    */
    8 B7 j+ O; W9 x( N  X# j5 g3 M@Component
    ) @' O5 x) ]+ b# s2 B6 t& K, a: ^9 K@RefreshScope //@1" G0 Z3 ?1 d1 S3 q& O6 S2 T& ]
    public class MailConfig {
    : ~& f5 T, j- F; }2 Y& V. g3 {8 [" Z6 [% ~. H5 N
        @Value("${mail.username}") //@2& T3 o+ [( @; o) e: ~
        private String username;
    ( }4 j4 V' R( q9 u
    " U. k" G$ ]$ a$ b8 {- S& I9 @8 o4 M    public String getUsername() {6 e% K6 a: Z* S5 t+ }' w
            return username;+ h; G2 H& G" Z
        }
    - j' Y) V% x1 _  U1 v" `' ]# Y0 n* y3 R5 D+ q
        public void setUsername(String username) {
    ; G9 E1 S: Y  h0 L7 m        this.username = username;
    $ D8 C0 p. ]# N5 S0 H8 w/ \* v" w    }' R+ X8 S/ p' C+ W7 [6 k
    ' @- w1 K7 ^7 ?
        @Override$ h" C4 d) j) R# v# w1 K# ]
        public String toString() {
    : D9 u6 e, q5 `3 [: A        return "MailConfig{" +
    $ _7 h6 `- f( I* x                "username='" + username + '\'' +
    5 c& f# L* D0 h) e; h                '}';
    + N1 _! f3 ], u' H% p( b    }
    1 x3 H/ e3 b& Y  B: f+ {' O9 C}% y2 I4 F4 N2 Y# ?0 t/ P
    @1:使用了自定义的作用域@RefreshScope( R% r! a7 ^( y5 u7 i, R
    ( K0 X( ~) _3 s
    @2:通过@Value注入mail.username对一个的值
    & D/ Q+ j4 q6 C( f1 u, ]4 L! ~
    ; S5 N1 N" a* H: e重写了toString方法,一会测试时候可以看效果。( d9 Z, M4 U6 a9 I
    / I" m: z" A3 b& r. |+ y( ~
    再来个普通的bean,内部会注入MailConfig; e4 H* Z! r% K4 s4 Y8 Z

    " }9 o$ V  B  V9 Jpackage com.javacode2018.lesson002.demo18.test4;  V: e0 t9 K, Y2 V% C9 @9 A% p1 a

    ; l% D( v9 F6 j8 }' E3 simport org.springframework.beans.factory.annotation.Autowired;
    1 o& H# e0 u5 Q8 [. S% Jimport org.springframework.stereotype.Component;2 C8 X; O! M* X6 }. A* i5 P' G4 c

    ! n4 ~# g1 L9 p* j4 v$ K2 P" I- J2 ~@Component' f) ^2 W* H+ F4 y
    public class MailService {
    ; h: X; U+ p. {8 g3 X; q5 z    @Autowired
    * }6 \" L/ r' e; u    private MailConfig mailConfig;
    ' d5 f( X; ?/ i  @& f2 v
      u  Y; c. k, ~1 E1 I    @Override
    " M2 T, v: {& g1 H/ p    public String toString() {
    $ O# g( s' b; \  `; u! F$ z( ^        return "MailService{" +* y6 J5 d( Q! W$ \9 ^* e
                    "mailConfig=" + mailConfig +! l" Z+ S# e9 Q
                    '}';
    & x* z$ L6 Z2 a$ [! a* A    }
    0 |( m, H8 W' W$ S7 V}
    ) ~" R5 R" s$ Y代码比较简单,重写了toString方法,一会测试时候可以看效果。! k2 L! Z! O+ S0 s( A& ?1 q
    : u* [) y/ O7 k. m# y' h7 H. I
    来个类,用来从db中获取邮件配置信息7 ?) S* U! ^) i) D" |
    ) u; f; z$ G" b' T; i
    package com.javacode2018.lesson002.demo18.test4;! \9 ?+ F6 j1 S1 z9 ~( x# f
    9 U& ?) n' o# m# t
    import java.util.HashMap;0 g5 E, C6 X6 y/ }: N
    import java.util.Map;
    2 i2 B  A! e9 y( x( _import java.util.UUID;
    ' B' C9 z. r7 f  l: z( |- v6 {. @$ m7 g! F. J1 ~3 c3 }
    public class DbUtil {4 F# M. ?, j( E. v# q- P
        /**# F7 I* r' D1 R( v4 m" D
         * 模拟从db中获取邮件配置信息, ~. S" a) w. F' `" B& ]" d
         *
    - s7 l- _4 _" C     * @return
    6 Q3 x0 _; M2 P# N& [; ^' j/ l; b" y6 N     */8 I% {+ ]$ r4 v# w5 W4 a! q+ d
        public static Map<String, Object> getMailInfoFromDb() {
    ' U4 x0 \; K- N! D        Map<String, Object> result = new HashMap<>();
    ( U4 e- j: l9 ^7 ^# _& f        result.put("mail.username", UUID.randomUUID().toString());$ D$ u* T$ X1 C" f/ c. @! w/ Z
            return result;+ ~+ w8 R7 z/ c7 o$ F
        }
      j6 C3 R: s. [. c7 V}
    3 ]4 J3 `; c' T4 S/ r, H% y来个spring配置类,扫描加载上面的组件
    ' S! y  o+ J5 }- k9 ?
    3 q2 V) G/ ]: v6 p9 [1 tpackage com.javacode2018.lesson002.demo18.test4;
    / W  k( Q# I) f/ n2 \$ V6 V% K& T# a* i
    import org.springframework.context.annotation.ComponentScan;
    0 M3 q9 C+ @% Zimport org.springframework.context.annotation.Configuration;
    1 g( _4 K2 e9 ], R" w2 f3 x$ X; t4 }9 F% }
    @Configuration, @9 o5 h5 m( y2 I; `! W
    @ComponentScan
    # k& \6 g/ Z. q4 kpublic class MainConfig4 {  `" d$ v% ^7 L/ s3 c/ J! p5 m
    }
    . u. P5 Q2 V3 l' A8 B2 _3 o; j; }来个工具类
    ; `7 O2 H% J3 \: X  |# c: a7 A' ~1 O0 k& W: Z3 d$ n
    内部有2个方法,如下:( M8 ?6 h; ~5 u0 ?' w

    4 N) b* J% P" y" [* Opackage com.javacode2018.lesson002.demo18.test4;1 z  Y6 w: \8 G

    . c" W( O4 z2 ^4 R. r* }+ }import org.springframework.context.support.AbstractApplicationContext;
    . W7 X7 S' P* V- V& e% Zimport org.springframework.core.env.MapPropertySource;
    9 K% C3 G4 E  f; i) N  P3 u( k9 \' l" u' @# [" h& b
    import java.util.Map;
    & \- z* p' G- A9 z3 |$ B* y. d. |4 z% K- j6 p
    public class RefreshConfigUtil {
    3 z" \# Z( k/ T3 Y5 _8 Z    /**/ U  h3 A" x" {/ D3 e
         * 模拟改变数据库中都配置信息
    2 Z1 r, G. s4 ?6 n& F: f% B     */# I$ T$ ?" Z4 _+ C
        public static void updateDbConfig(AbstractApplicationContext context) {
    ' ?% l+ h; j! q7 B! i( `" k        //更新context中的mailPropertySource配置信息
    ) M! ~" Y* c! t) x6 q* w! P        refreshMailPropertySource(context);
    - ]% j* O) b  w3 B
    0 N- I  C: s1 \6 F8 h        //清空BeanRefreshScope中所有bean的缓存/ C+ @) b1 y6 v3 D9 ^4 V
            BeanRefreshScope.getInstance().clean();8 F% o9 B# p/ T0 d, D
        }) r- v( ~0 b7 i% {2 z! `" q
    4 ^! T% o6 D; a0 F( }3 k0 Y
        public static void refreshMailPropertySource(AbstractApplicationContext context) {! c  n( K6 m. ?4 N2 M4 Q
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();+ F/ O! v' ]3 B8 }
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    6 _. W# P0 u2 Z  R: G, ?        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);4 _0 R% ?- Z1 f
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);2 x: ^! p7 P  m5 ^1 Q
        }
    5 ~, i* I" W+ X1 x, D/ @; `  z4 W$ A1 [
    }8 e1 J6 K1 M/ u
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ) V  \9 N9 p& T/ A* W; t* i2 e  j! f6 c, C
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    9 r" G8 }$ ?5 I6 |, u/ h7 S
    - r- M6 z6 F4 l- a. q3 ?% @来个测试用例
    6 x) e4 H6 D$ x% d% \9 t: x( ?( W7 v/ ?+ f$ c, g# N) C- U
    @Test
    0 K8 a7 v4 ~" B" `4 G' ^  Z0 y' Ipublic void test4() throws InterruptedException {; C7 I( M6 Z' B8 U, p
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();2 Q, m) ]" e0 B
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());) L! h  {4 |  U
        context.register(MainConfig4.class);1 O/ D+ L  @5 q/ |: {4 k2 e
        //刷新mail的配置到Environment
    4 s; c7 }& q: @5 [1 M    RefreshConfigUtil.refreshMailPropertySource(context);$ M" `: W) ]$ b  e1 e
        context.refresh();
    8 g+ a! _7 b' b" k' [% n$ K6 r3 w) t" Y+ z! J4 V
        MailService mailService = context.getBean(MailService.class);
    , q' G. o2 U5 v$ ]. \4 ?    System.out.println("配置未更新的情况下,输出3次");1 a7 r+ Q: }# n
        for (int i = 0; i < 3; i++) { //@1% a0 v6 D6 z7 t/ a
            System.out.println(mailService);9 a0 f5 e/ W' O, W% y5 Y% d
            TimeUnit.MILLISECONDS.sleep(200);) ?$ A. i; b3 e# G* ]
        }
    ' k# z0 F; H& f  H$ @3 @. p: a  O* A7 f
        System.out.println("模拟3次更新配置效果");
    # r" ]* v; Z! N. d( o9 V! D; j    for (int i = 0; i < 3; i++) { //@2* R9 ]2 o- i5 W, K4 U
            RefreshConfigUtil.updateDbConfig(context); //@3
    3 S0 V* t+ L. h7 U7 P# \1 E        System.out.println(mailService);
    , c0 H0 g0 W- V. N& M        TimeUnit.MILLISECONDS.sleep(200);
    ; t8 ~; K/ D" m' C6 u* X    }
    , |! y9 B' q5 {, @0 m: D}
    . ^" w- Z. G- R7 ?@1:循环3次,输出mailService的信息1 F' d0 V' f8 r! T) v) D
    3 z# j# Q4 u+ h0 b  S- ]0 B
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    5 h1 t5 w/ @3 A, Q( c) [$ y9 U1 _1 N* O) X9 M! R4 e; {
    见证奇迹的时刻,来看效果
    , H. \- U% F% n( |/ S
    ! a' F5 J# c$ x' P0 X配置未更新的情况下,输出3次
    6 [- \0 \: x5 |MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}) Q( g' l4 Y) m3 Q
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    6 q" B7 w, M) k7 L' eMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}  b/ Z# O" m* @% W
    模拟3次更新配置效果+ n! t1 B- T3 A3 O$ n7 S, R
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    % ]- H* t" I' N7 rMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}! h9 T. \: n! u: N' j
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}, U) B% S* l* c2 u/ Q/ w7 B
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。2 t2 N/ Y+ t( k  v# n/ L# X% m
    : ]! o' X) ?  {: V
    小结
    , I+ ^" t# z' o2 c
    # [( Q0 @0 N+ e7 u, M+ r2 c! A1 r动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。- H, `0 Z( \2 p) u2 J

    0 D: O5 C- B. a有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。" k/ k$ \) P4 _" o

    & k% b% @8 O9 r. f3 ^) P; c6 m总结
    / l$ K: Q3 @4 L# l  c, ]) n6 A9 D* j7 \' F4 l
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    . B3 w& d  I# s; p4 }/ D+ y( ~) {' N8 D0 j" f  l( c4 U; d
    案例源码8 |3 U/ [. H2 g/ x: ?# M
    * [4 n, D# _; J: Z$ \: j
    https://gitee.com/javacode2018/spring-series
    % l. L6 r; J( e5 m5 p8 b- l6 N路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    5 q5 v- A  t1 I& X
    # W! v' E' z! i8 A6 wSpring系列
    5 h9 Y! _$ s' I6 k
    # w4 x% U4 T; _, Q9 QSpring系列第1篇:为何要学spring?# q  v$ P3 q1 |8 _
    & ?: c" ?; u( I6 @8 d0 X
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)% P9 E+ n+ f2 v* M. `

    0 X3 g8 `# B0 B0 V3 `" i2 SSpring系列第3篇:Spring容器基本使用及原理( @7 j% w4 A6 ]+ R- e6 G) t4 o2 `& f

    1 t5 [( r* s0 nSpring系列第4篇:xml中bean定义详解(-)2 ]: n- J+ Z7 v) A
    2 p( K' Y7 L, ]2 \. Z& W
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    - n" Q, d: w, D; N6 Z6 o* M, u2 K2 T% e6 a. P9 W$ h+ q
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    % l$ `$ c0 B3 ~5 b2 A/ L& T
    ; M% e: k9 w# P# {0 I4 j. K# sSpring系列第7篇:依赖注入之手动注入
    - A3 a3 I- x, ^* ^
    3 x. K: F9 A1 T  R+ Z9 A0 iSpring系列第8篇:自动注入(autowire)详解,高手在于坚持. A) U# }3 h9 x  P# q$ x  d$ {: E. l
    1 u% ?7 A- q3 C% Q# P
    Spring系列第9篇:depend-on到底是干什么的?2 L' _. H6 U$ g! c
    . \  a" K# R. M) o0 G! _: p* X
    Spring系列第10篇:primary可以解决什么问题?8 O; Z6 ?  O2 a: l+ U3 j3 `& U( X2 a

    & s2 y! Z1 y5 [1 z( |3 \+ L+ P+ p0 mSpring系列第11篇:bean中的autowire-candidate又是干什么的?
    & m- b! V" v0 T& O$ ^* f( b( d
    2 K) M/ M8 `$ h, `/ S: DSpring系列第12篇:lazy-init:bean延迟初始化
    ( n. T4 G7 H7 T/ _. e6 k5 o- K% g0 o+ }/ h2 E( {7 i0 _5 I
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)8 l0 H# F: G  h$ H0 s( O
    - w6 s, J% l: Z, [
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?# [1 S  z' B/ i. ~' l4 K  V" ~
    / i9 `1 v, d. Q3 f
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    + q. g& m; t; A" d! U- F
    ! i1 }6 n& j; G% o& J- e* @0 fSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)* q- K3 a. X% q3 E  x4 e1 i
    2 r5 }8 g) ^0 h* r/ x7 M& {9 L
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册). P2 N" y4 I, s  R

    ! W/ m& u: \+ _Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)" `# M: ]( K; A, `1 g7 B7 p+ D
    ; n' f% u1 `2 l* k
    Spring系列第18篇:@import详解(bean批量注册)
    ( F  f0 B  f5 Y& c# [1 m% E6 `0 R9 Z
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    - m' v6 m7 F2 n( {8 M* m3 L& G6 O7 m% Z6 A
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier): v% f) D- o8 |" u4 N' i

    ) J- i; b: [% i/ Z9 C$ \Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解4 X' o! B9 u$ R  |0 N

    ! e1 C3 C9 \9 k) M4 M. E# X( ^* A* KSpring系列第23篇:Bean生命周期详解
    % {; @  a2 K$ E) O/ U) P  k. \3 P
    4 p$ C8 k8 P$ ^. O2 eSpring系列第24篇:父子容器详解6 w$ `4 r" l- a* \: o0 ^) _+ p7 }

    5 H; U8 ]$ g( P0 K3 O9 r, P( s更多好文章
    1 O' M5 w: p9 G: e) ^$ C) J: @5 n, x# S; {1 a
    Java高并发系列(共34篇)
    . S! ?" z# a9 D! t  T* @$ x
    . Q% r9 C( Z) z: S  n/ r( Y3 x- fMySql高手系列(共27篇), S* \3 [/ v5 b! A( k2 n
    , f* m8 n% r& u1 h
    Maven高手系列(共10篇)
    1 @* H- O! e( b
    7 F; h" v2 a4 TMybatis系列(共12篇)8 f/ ^/ l6 ?1 \, x) f2 n! V
    / c/ |5 N; N, n2 q* p" {
    聊聊db和缓存一致性常见的实现方式
    ! l: B) d# B% `; C# i  t+ V. m0 [& Q- Z# Q5 A
    接口幂等性这么重要,它是什么?怎么实现?
      _  A* N2 e% x+ x! D  r( l
    : f5 A( ], Q3 Y% A; d% @泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!' ?& z4 F. ]! Z% S7 w3 r
    ————————————————
    ! ]+ Z- b( X1 T! M+ v, l" O1 q9 A版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 ^5 B- N9 A* B
    原文链接:https://blog.csdn.net/likun557/article/details/105648757
    ; h5 Z1 r( J% f. X6 a0 L  i, Z$ U. e" Z1 z( \8 q: t% O6 m

    8 y7 Z5 M0 ^- D( u( }; n; I& z  j% B
    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 07:44 , Processed in 0.668845 second(s), 50 queries .

    回顶部