QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5454|回复: 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!
    * {, k7 w2 z- ]. z疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    ) l8 f2 S  {# G7 f
    / O7 b; F, A6 G5 R' o" D5 o  ]) f面试官:Spring中的@Value用过么,介绍一下
    $ _! G2 X# Y. Q% A. }$ v$ d2 I" g6 t) t$ p9 j
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中% {" M* r: J) `& |" U

    0 S5 k) t; F) k- G. }, |5 S面试官:那就是说@Value的数据来源于配置文件了?, |5 `$ }" B  @% q0 P
    - P% S4 z% B) W5 B$ J0 \
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    % @6 P( b. O) ?+ Y: S
    8 D+ v( U1 T; M! g面试官:@Value数据来源还有其他方式么?
    ! J. Y2 x( Y6 R  {: ?$ \1 y! l+ ]+ e9 n' j% q7 y4 _
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    - M  r6 ]1 e2 j9 }1 q+ k+ i' h, F( M6 q4 {
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    : }' k  u. _9 h( R; f. ^
    4 W  h! r9 r& U7 ^  e- e' |我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    * e7 I, p# M( x
    - b5 H. V+ p# z4 M# [面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?- m+ Y7 D- @0 a
    . N" H& M7 A! L6 B1 W+ x  O! R
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    - A  W* k. A& |/ e. U' S$ {" S3 N; Y. o" w4 ^: m
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    $ l$ o, v) X* w" W4 U3 Z8 W4 k/ T8 z5 n  T4 t
    我:嗯。。。这个之前看过一点,不过没有看懂
    / n2 g0 H7 `  E  f$ K6 q+ I0 y. [% q: x4 @0 ]7 K$ d$ z' \
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?2 {& v" U& p/ K0 p# g* y: {
    7 Q8 I: p# W. I; K$ P
    我:3万吧  n' M9 s: G& d# v
    ( W3 G2 I7 |" y' f
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?6 T4 P/ k$ ?. l" ]! Q& r
    / h/ L* r3 s4 U# V
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万; L" v; o7 l9 V( X, a: o

    ) @7 f8 M3 o$ E7 H面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    # q8 t. J  [2 j' v- o$ B: _4 H6 Q- K
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    / x8 D0 V( w' n7 B
    % \& x5 N; r1 L( R这次面试问题如下5 Y3 E2 I! l/ Q  B! E! u$ p
    7 l# i6 p4 `& J7 L0 D2 Q: L1 i5 W' m
    @Value的用法
    , e: k4 {1 N  {1 ?$ w) }3 ~% k% x8 @+ k) s' L
    @Value数据来源; n) ^5 R( e+ R% @4 L# T" Q! E$ \
    - j. |& D6 C9 d, A# i4 V- |6 b+ Z
    @Value动态刷新的问题
    + \6 C6 b/ Z$ f1 H! M1 E0 h8 v, p4 M) M  j- u/ @
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    7 U' }) K# O2 @* P: B3 l% ~6 h; B
    2 F& B) _  S5 C/ B: h4 a( a@Value的用法
    + Q1 u6 F+ k+ ?
    - M- \1 m% J2 P* k( E/ A% r系统中需要连接db,连接db有很多配置信息。+ M. ~/ `# L3 S; b% u1 R

    " r: j  L8 V! p  O6 U9 X系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    6 Z) J4 Y" G3 @' l
    8 o: x9 O. V. ~* z9 Y* O. ~2 b还有其他的一些配置信息。
    , d4 ]$ e$ H4 n* o* J% Z
      a# z! ^# M4 Y# I. p+ U9 U' U我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    - u  s$ V; j# N" m% y' o( K, s/ A/ U% [9 X1 H- l
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。2 Z6 V# u7 s; D% G. p

    2 s' R+ v  U3 X& W, G通常我们会将配置信息以key=value的形式存储在properties配置文件中。9 B3 N5 ~) V7 H0 d) C4 O
    / |9 y6 }. C" j4 A4 E
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    3 t3 a0 ~6 Q* K5 g, E" e4 j* R9 \9 }* Z  `; t- [
    @Value使用步骤
    ) `2 @& ?& z7 `
    " G2 H: o- p* d7 K. [$ v6 Q步骤一:使用@PropertySource注解引入配置文件# Z; X3 u' t; [) p! X
    ) [0 Y  _. ?. J1 n1 N/ g
    将@PropertySource放在类上面,如下
    0 r  R+ W7 `# A8 J3 X+ i
    1 M" X& V" R0 L& ^@PropertySource({"配置文件路径1","配置文件路径2"...})
    ' Q# P% L. E8 u8 }# b@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。! T% o) F$ f2 E0 }4 a% O- Z  Q
    ) P7 e$ Y" V: f4 e$ D! x2 g
    如:
    - Z* Y4 O: I$ }1 r% ]1 j% Q8 W  ~: f5 }7 E  D  |$ c' q9 q% |, r
    @Component
    ) T1 v1 C8 K: d9 C2 t@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})- h3 ~9 h; \% H; L4 a% L0 D
    public class DbConfig {
    3 w, c( _& f+ ]% U}
    1 [  J1 B% T' ~. z3 d& N% E+ v步骤二:使用@Value注解引用配置文件的值
    % c5 f9 V+ ]$ }* p1 j, d# }) X7 c' L& e  T) n& Y* j
    通过@Value引用上面配置文件中的值:
    # |. `; ]( g6 g6 P: {2 K0 S# [: M% o0 \2 K+ R6 g
    语法
    # h5 V. d& \8 `6 \
      _$ E$ ~! ]) I& Y@Value("${配置文件中的key:默认值}"); C9 _% O' _$ P" h1 _# m; p
    @Value("${配置文件中的key}")% a4 E! c- `  e: {
    如:7 \0 N* P" v1 [$ r

    9 b5 b, @1 o$ I@Value("${password:123}")# }* I& N8 p  K$ r0 [+ B4 L5 ?
    上面如果password不存在,将123作为值" v8 d/ x# s; t( d
    / H, C1 C+ |  R2 C6 [* ^3 r1 P
    @Value("${password}"); O& \( q$ Q3 a+ {
    上面如果password不存在,值为${password}) }/ f3 \: U1 m: P; B3 b
    , i2 [/ i& d' I9 M3 f. \( O
    假如配置文件如下
      ^5 v( y: H9 t5 ^" l, o: F4 ]2 L* q2 V! N0 [8 h& H' i! z
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ; _( I7 M5 N$ o8 j* I, ujdbc.username=javacode, u3 h% g' t' P. ~
    jdbc.password=javacode8 x- ?9 W9 C6 ~( c" U5 D" o4 p
    使用方式如下:9 l0 \/ v, ]3 U/ q
    ! W; R4 B! u2 U7 ~& G7 T
    @Value("${jdbc.url}")' ~, S& s; ^9 G
    private String url;+ L6 A$ z9 m2 {! j( I- ^$ G

    ! r" T9 [. o; U& u5 r$ ~& u@Value("${jdbc.username}")9 ?, I4 m3 D! U* q% }
    private String username;
    4 g! u* _: ^4 b6 @3 }/ w; H# d1 O( O9 s. s- b: b
    @Value("${jdbc.password}")$ W/ R8 ~* P' O* q
    private String password;
    ; o1 S  _# T- p# U# E下面来看案例
    4 ?1 K8 z' `3 l2 [* L3 l
    2 @  P: N$ U2 l$ a; X; Y+ q. S案例
    $ O8 {+ _) A$ x$ _* O% Z3 a9 @3 @
    * k5 e, n4 x4 _% E0 [7 L来个配置文件db.properties
    + K( z8 u$ c8 ]  V5 c/ g6 i
    + R0 s! ]! c+ yjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    0 X8 ?* D2 C1 z" h/ yjdbc.username=javacode3 l! l/ Y, p" |! i( e
    jdbc.password=javacode
    8 d7 Y% a# C9 R9 g; p/ G8 F来个配置类,使用@PropertySource引入上面的配置文件
    & W# S1 d6 @% @. j3 u
    , F- D& {9 ^6 P8 Ipackage com.javacode2018.lesson002.demo18.test1;
    ! ~+ Z% F( N! X" \  R/ t: I. j+ P( `. ?5 f
    import org.springframework.beans.factory.annotation.Configurable;
    0 x2 B  J! W. U( T; K! jimport org.springframework.context.annotation.ComponentScan;
    % a% s0 P) b0 |& s  Ximport org.springframework.context.annotation.PropertySource;
    5 E; o" S4 E- Z+ i9 w
    * _# E" |5 e2 O. t; w@Configurable
    7 M: U: Q1 r! B* y4 q4 S@ComponentScan# f+ L3 C; |1 j7 l( u
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})- v- T) Y! @" b* v1 b1 e
    public class MainConfig1 {* R% o' e: w" Q. x2 X9 E6 i' J; E: Z
    }
    ; d* s8 c6 g0 N+ y来个类,使用@Value来使用配置文件中的信息9 |4 L! }& X$ R3 q) V4 D5 P

    4 ?. s: V$ ~8 O1 hpackage com.javacode2018.lesson002.demo18.test1;$ |' l& x+ B3 K# R1 m9 m) {

    , H' z  l4 j- B7 i  u! D; Himport org.springframework.beans.factory.annotation.Value;
    4 z. J% b# ]- [- F* G, z0 Mimport org.springframework.stereotype.Component;
    1 {9 N6 X  F( C
    1 V% W5 @/ J) `# y@Component
    . i! K5 i) w( U/ R! apublic class DbConfig {) G" i8 Y, O% Y

    8 M9 k& Q: q9 X& z2 E    @Value("${jdbc.url}")  G, |. S( L5 V/ u6 r& b
        private String url;
    : X) k, g7 [9 t6 U; i1 W% h: D' ]) F- b" I5 _
        @Value("${jdbc.username}")
    : v$ B- d* ?  n. O    private String username;
    9 X  q2 a- H( U
    # @' F2 o0 \( s% A* F2 v  J2 Q) v    @Value("${jdbc.password}")
    # b" g7 W$ F8 _    private String password;
    % L8 E$ W$ Q, {" t3 o, I  D+ f; d' ^
        public String getUrl() {& F2 P& D+ L9 O- T. m; I, p8 L
            return url;
      C" k, \' C4 D% [) m7 y" v$ U    }  J' \7 F2 X1 h  d" _3 o# v" i; M
    ' Z* D5 V  k8 N) k( u9 a* k, u
        public void setUrl(String url) {2 f2 t0 l5 W; \4 ]
            this.url = url;- s1 v/ l$ d4 J2 [" G
        }5 b2 d0 ?" n, j
      f3 e- L* _; n4 \) _7 o
        public String getUsername() {2 v. b6 {, W2 [2 E( A& c! U4 h9 `
            return username;4 {/ H7 n. q# a6 u) v
        }
    ; J9 J' S  i7 x# n$ X6 j/ i0 w1 i! {& P4 r6 K0 D
        public void setUsername(String username) {  B  I' x; V- p  I; W
            this.username = username;
    3 ^3 c3 ~2 p8 L8 W1 Z6 s    }% \, j1 t# N, q4 V" s# i

    ( R: V5 z: i& g7 T0 W    public String getPassword() {
    , M' a7 B9 k) ^8 ^        return password;
    & p# F/ o9 D, d% @3 D: I7 d/ Q    }' }7 U; r* J& r1 l$ X) k7 G# D

    3 f- _6 ?& }3 c. _    public void setPassword(String password) {
    7 h, k9 D1 U) }7 R        this.password = password;
    ' e! j* R+ _+ Q( K    }
    3 D$ \+ C3 p8 Q" {  E% I9 l) o" V- b
        @Override5 l5 M' {& D! D( ^6 D
        public String toString() {
    2 @( s) X5 f7 `        return "DbConfig{" +
    ( q( Q* i  _" \' c                "url='" + url + '\'' +$ X6 u; j$ }' C% l' |
                    ", username='" + username + '\'' +
    ! V- X* F9 z! I& Z                ", password='" + password + '\'' +
    0 k  c- U# P+ y: M8 f3 V' L                '}';
    ( `+ @: A# q' i: K& b    }- q) W/ G  r: a( i
    }/ [# Q0 Z: S% t7 B- J
    上面重点在于注解@Value注解,注意@Value注解中的
    7 k4 }9 x7 O4 Y  J; s+ I! @6 G2 l* k& D& J  T. {1 D
    来个测试用例# j6 U8 J( M# l3 o# {7 d! ]* ^

    & l$ X6 \. h! @package com.javacode2018.lesson002.demo18;
    , K# F) N8 {( F7 ]/ d4 K, X
    & y* o7 T' E3 c3 Nimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    % [) q" l  R& C3 g! H- bimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    " g# K& ^: T1 ]5 A. zimport org.junit.Test;
    . N9 f, ?0 f! P% mimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    - I9 J7 z4 n7 G0 t# h& ~, m
    ) A6 @2 K! [3 K1 O6 O2 Tpublic class ValueTest {6 G. m0 c8 M/ N6 ~  @4 Q& N

    ; w! `8 L1 A- ^' z/ o    @Test
    8 H/ I% H, Q$ X7 |7 l9 v: h- P: \, W  |6 W    public void test1() {0 I! r; @, n  J9 G5 |: U
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    5 \( X' G0 }9 `$ m( {# U        context.register(MainConfig1.class);: z. C0 U4 A8 E; h
            context.refresh();
    0 ]& C' a; y6 Y* a  V( z8 j; Y5 P; x, l4 t0 \2 I$ Z, [& B) Z
            DbConfig dbConfig = context.getBean(DbConfig.class);* v! Z  a7 V  z
            System.out.println(dbConfig);6 M$ M) L" C6 M! R6 e
        }8 e2 h* Y& ]* }2 f2 v5 S
    }/ S+ ?- O: K( o1 H& Z& _6 A- K+ D# n- g
    运行输出
    6 z6 e2 d* Q  A' e
    & e$ }: R, K; J$ }4 X! VDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}$ Z- F9 n* d" i# x6 r
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。2 ]6 e5 b5 _( L/ D% h, q8 V
    + z$ ]+ f4 j5 E, r% g* ?+ I( n
    @Value数据来源
    . V+ x5 T9 N% B! v  q
    & `3 H( G5 W$ @2 L8 s& e* w* m2 N通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    : w) b' M% d/ |( z' q, b- l/ l5 [% p9 \, b  Q9 ~$ M
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    6 o" X% {  o" c6 |4 _$ d1 e' z7 S
    ' @" o& e/ k& w3 W: z5 L# Lspring中有个类! l# l+ r/ E" w( J
    ; Y; s# Q" `* A: i
    org.springframework.core.env.PropertySource
    / R- h0 a5 D  y" \可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息& }" C+ U$ z5 f+ U
    . j3 c2 y; X) S! j9 U
    内部有个方法:- _2 M* w1 |4 I) L
    % }! j* Y& b' {+ c/ D; Z
    public abstract Object getProperty(String name);6 h0 p- c9 Z5 k# Q1 h4 U) c
    通过name获取对应的配置信息。
      B$ T3 X( S5 w7 a$ m0 u4 |6 _% _( C
    & C* C( Z7 t7 b3 R: ]% `& p5 q系统有个比较重要的接口
    1 L+ a. A% g. A" D' v( z1 T0 O, U1 T* J- o- q
    org.springframework.core.env.Environment
    : V! b6 v1 H4 E) U# {6 Q. z: D用来表示环境配置信息,这个接口有几个方法比较重要$ k; k$ V& d9 x9 Y1 S
    % T3 q- V& f, r# `. x! p2 k
    String resolvePlaceholders(String text);- L+ ^9 z2 X7 m
    MutablePropertySources getPropertySources();. S3 U4 g7 H/ U, ^, d
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。- x6 T3 d9 k9 I  N0 }) G

    " s& ^5 s* r: I8 a4 U  hgetPropertySources返回MutablePropertySources对象,来看一下这个类% f9 u! @  w' L. B, s7 p6 p
    3 D1 b) ]2 }* }( g, h: r. B
    public class MutablePropertySources implements PropertySources {
    " }2 ~; Y% ^+ f* N2 }# \! y* V' F* p/ o- U
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ! i& D* w2 }! B6 h: u
    0 q( `9 V- D% r5 i  ?' H, S! P}
    $ D5 q3 G8 t- C  r: V2 K内部包含一个propertySourceList列表。; L. N: i( X, t$ K, Y1 t

    ! V! F6 N! e9 yspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。: ?& |( m$ F8 ?7 R
    ( ?8 S0 f1 ?( Z2 i6 r
    大家可以捋一下,最终解析@Value的过程:
    4 [3 s$ z& B3 l) S
    + ~3 L# }+ Q, e& D. v: o0 x1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析+ w: ~: `3 }4 u) r) V+ x; y
    2. Environment内部会访问MutablePropertySources来解析/ i! x% m/ \. w6 i, i* g; E# V, B' {+ p
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    / Z0 y: H: {# s通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    % y/ O* [  ?  M/ L2 W) v( {( K& L7 k% `" Q; Z+ u) O: @
    下面我们就按照这个思路来一个。
    " j  s/ T$ W* t% I
    - k# U3 G% A/ g% L1 D. S3 `% E来个邮件配置信息类,内部使用@Value注入邮件配置信息
    & x7 x( {5 n* s  ]  L. R9 a* P9 J1 Y1 r" P, @% V
    package com.javacode2018.lesson002.demo18.test2;
    ; A+ U7 t* A( c" i- d
    8 h8 k! y8 |3 K/ {import org.springframework.beans.factory.annotation.Value;
    2 m7 O! c! l! Fimport org.springframework.stereotype.Component;/ x, M; u+ M* Q% q9 G
    6 z' ^1 \/ [1 X6 S/ y( X
    /**3 f% X/ O/ w/ d; V# k# W
    * 邮件配置信息" W+ P& d) s+ o2 Q) d7 ~5 f
    */
    ) n- s, n1 n3 O: @/ v@Component
    3 O% {4 b! s3 a  P% A: vpublic class MailConfig {, k+ z( @9 I: F5 ?# G  b

    : k6 _. @( Z6 _4 m. T# W6 f    @Value("${mail.host}")
    ! r. i" A3 `* w: K8 Z    private String host;
    ; O& [, e. s' W; X/ b" }2 ^" ?% R- f5 [, P% ~( E( M% z
        @Value("${mail.username}")8 [* y0 h/ I" k& a
        private String username;$ h' s! l8 o4 A* ?  G: X9 |8 p

    ( o1 @/ W$ D$ A& D/ W- [1 G    @Value("${mail.password}")8 I7 t0 O9 C3 X4 ?0 v3 ?/ Q
        private String password;
    - A7 m' E  h! R, Q. z1 j1 ^; E% U
    ' @, [- a! a% Y( t) g4 Y; J: C8 N    public String getHost() {
    % `$ n3 F9 W/ b3 o+ @2 e8 p8 C4 m        return host;
    5 y# `2 n1 ^: A5 D/ X    }$ q3 ^! u: f! l, v

    0 r& C4 e4 V) W% T" K2 [$ m    public void setHost(String host) {8 v0 m4 a- p- B: r
            this.host = host;% Z' x2 }: ^: U$ |8 F4 z$ [- J
        }
    7 s" z! v! E7 f% e; B
    # a( n! }  s& g    public String getUsername() {- X5 M) d& k6 Q
            return username;
    ; k9 ]7 J. O$ b- K, R. c    }& v) d5 ?. v" Z  N7 ?
    4 N2 D2 a, Z( |" ]- l
        public void setUsername(String username) {
    4 {2 e. ~6 H8 T2 {3 E0 [9 Q7 g: l. C        this.username = username;
      B  n7 ~% k* t7 z. l* l) w    }
    3 H& h& h- p! Z1 |2 e1 T, |4 V9 i# H' N7 o, Q) ?
        public String getPassword() {
    2 X& D5 M  P1 J% l0 a        return password;
    " [7 F4 H. b9 \* P, O0 v1 h+ O8 N3 K    }
    ; A9 w. s& T5 g  Z9 d5 k3 e& l+ b! n, _
        public void setPassword(String password) {
    % X. {8 Y% v& Z% o% y        this.password = password;
    9 U" C7 J, r" H3 M+ b2 ~, e    }8 n+ U0 u; B/ }- q& f( g

    ' x) [) `' l% a  x2 C% F" Y    @Override
    & X( {& K% S  s3 `    public String toString() {
      U& p0 y- F4 [  f9 n# D4 w  E/ n        return "MailConfig{" +
    & a. n( Z- W6 i; t  h- N                "host='" + host + '\'' +, E8 v! ~- |; j+ S* ]
                    ", username='" + username + '\'' +' g9 D- B7 O* Q0 A7 p/ o
                    ", password='" + password + '\'' +7 ]) m" M) \% t6 B# T
                    '}';
    5 f& _% ]+ m: K, R/ t# R% ~    }
    ! E$ X8 W( s7 x6 Y8 g}
    / M( b; f' G, f. I0 j再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中0 H* g  T+ {5 E3 \
    2 n7 s) m& H1 p, p+ l0 `! _! e& g  f
    package com.javacode2018.lesson002.demo18.test2;
    , r, v9 ?! m. Z* o! g7 |. u+ s0 Z
    import java.util.HashMap;9 B8 t0 s7 h& u4 z7 m
    import java.util.Map;
    & l7 k5 x! @2 E% A) _) m: _+ I0 R  c9 D) C
    public class DbUtil {2 v  _8 N$ M6 V1 [, b# f
        /**! \! f3 ]* `2 k' o: B
         * 模拟从db中获取邮件配置信息
    - e5 u; Y4 K; ~) A4 o7 d; S     *9 C! v1 q/ q- H4 \4 h. X  D' A/ w
         * @return
    - S! U- q6 {- n8 E0 b" Z     */
    4 Z. u6 ~4 \; C, M# X    public static Map<String, Object> getMailInfoFromDb() {5 A8 _" |! z: a
            Map<String, Object> result = new HashMap<>();
    - w3 _! j" c' z6 c        result.put("mail.host", "smtp.qq.com");! A. e8 t, M  J, `  |
            result.put("mail.username", "路人");
    7 |; m. w* W5 r4 ]7 ]9 m/ y! q  r+ `        result.put("mail.password", "123");
    # t" ?# f5 e% Z% a4 L        return result;
    # M- X& K9 m, T' m" u* Q! s6 _) n    }
    $ P$ d# F$ z4 J3 F" b1 n9 f}- ~7 l7 @; H. O, m4 g" {) S8 u: M& U
    来个spring配置类
    " d# [8 n/ f0 U: L0 u0 N2 o4 ^+ j7 N/ r4 C6 f
    package com.javacode2018.lesson002.demo18.test2;. ?& p5 d" I& O$ o0 l* S

    9 I$ e! e+ }) r7 jimport org.springframework.context.annotation.ComponentScan;
    $ M" K- j( s5 D2 g! Q) nimport org.springframework.context.annotation.Configuration;
    " p' h( n8 [4 O3 k6 K& I; n* x5 G6 x5 E) V) _
    @Configuration
    $ S7 f- }0 @) S: v1 j7 z4 v; z$ l@ComponentScan  e# r1 y! `9 i! L7 G6 x7 W; ^; L
    public class MainConfig2 {( C1 U  U9 ]) n- q2 z
    }
    ! X/ q$ O/ k3 [; y' o* G" k下面是重点代码
    6 Q: K% [/ t" B. ?6 Z2 V1 S+ g7 a9 R* N- c4 @
    @Test
    # Q5 L5 L+ ^! O) i  apublic void test2() {. C1 i6 y5 A2 ?
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' _. Y' h! i, d+ w$ x1 M- o

    9 h7 r% E5 U. i* i# A    /*下面这段是关键 start*/8 R! a4 v$ c3 t0 ~% ^& y3 s: o
        //模拟从db中获取配置信息
    / l5 U& J+ Q9 ~% x4 H/ U    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ' k# S, q) X3 m) w5 P5 {    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类); g  {: Z! H+ @8 P6 L3 g: P
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);# N0 P2 u. p4 k+ Y: I6 m
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ! g4 q% D; x9 U2 w3 D    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ' f  M$ ^" o! _4 z) ?* x    /*上面这段是关键 end*/
    ) E1 G6 h# P0 @! W2 |. s
    4 v7 j0 ^2 L, U  W, I5 N7 ~  s    context.register(MainConfig2.class);
    3 V5 B2 D/ l1 L) @7 d+ }, b# U    context.refresh();/ l7 ^. i  v8 i& ?2 Q0 h
        MailConfig mailConfig = context.getBean(MailConfig.class);1 W% @( u; o" E: Z( }+ o+ t
        System.out.println(mailConfig);/ |3 Y0 J' U0 R6 z* V" \% H$ A
    }+ ^! W9 @5 T# s* L
    注释比较详细,就不详细解释了。- k) I+ _' J$ @& H. j
    ) J4 a0 C1 h2 G
    直接运行,看效果
    - U# u( A1 S+ h  A" `+ y9 S% f3 m, g. ]8 @  i& U
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    : J( H( j8 t$ Q  {0 B/ I$ Q1 E有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    , m  B% \- I" f3 |, k& C5 ]1 w' P* o& J* _8 r3 I+ ~
    上面重点是下面这段代码,大家需要理解8 L: O1 x5 q( `1 Q7 e2 y

    ! z  A& h8 d; }# ]  X) f/*下面这段是关键 start*/
    2 V% y1 ]! S- s6 J//模拟从db中获取配置信息7 f, k6 i) Y2 m' u( z
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    3 w* R" I' y5 X4 z( \  ^! A- z//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ! P; u) Z. O) s5 E- k% ]; oMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ) o9 Q- a- i! B6 A) i//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高3 [; U: e+ O- `. Y7 O% Z
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    + Y" D* c& r9 j) o/*上面这段是关键 end*/6 y: J# b. O) z  d
    咱们继续看下一个问题
    3 {3 V/ L  i7 Y' c
    3 R; O( y! l" I0 H% v7 X如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    # r0 E( ]+ J: L  q, [
    : Z; D- g! W& B, G( l) P- V  v@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    8 R# K4 _! [, E# W! k. _4 i# r" v3 E
    实现@Value动态刷新* d  [! G" x( W8 i" L6 n1 M

    ; G+ [% d; m9 |8 k" L! r( I4 v先了解一个知识点
    ( s' |8 h7 ?8 r  R5 S4 a% u. [" u  Q  K2 @7 J+ o; f/ ^  P' i& _' q
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。- N# M2 X1 U* n3 ~7 }7 l, c

    ; J) H* v7 Y9 p" \5 p; H, l' {( W这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解' g2 G0 Y7 W" S0 E" @4 o+ D
    8 G" h1 ^5 b' C
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
      W' e9 U, h' l8 \* _
    5 Z! z9 d4 v" w1 U* YScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;4 f" x; P2 Z! W3 s2 M& v# d8 C
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    0 H1 Q" {9 c! a  r! Z. p# \8 d- N3 c3 i% T( v$ M; N
    public enum ScopedProxyMode {
    + v4 r' H+ W) j. z7 g' l+ C* Q% I    DEFAULT,2 G8 {, c2 H& V/ k
        NO,4 \+ Y/ N2 I/ p9 ~
        INTERFACES,; V! t. v" y+ p& R- c# Y+ C
        TARGET_CLASS;
    " W1 M% T6 P; J8 q: Q9 T}
      f/ A% _  o: K; C( i: B0 F前面3个,不讲了,直接讲最后一个值是干什么的。
    , [) e5 g6 B# a9 E2 O
    1 R0 n* X; ~( _. I当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。) J' g; {, i1 G7 K' P4 T4 h  T- p% a

    7 d; H! s  M( C" q5 ]; v理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。% D7 H& q6 ~  F4 L
    4 T3 P* N/ K. I" Z1 C
    自定义一个bean作用域的注解
    3 a' G. Z4 G, N4 k
    # R" c/ Z5 S; f0 ^5 Vpackage com.javacode2018.lesson002.demo18.test3;# w- I+ ^+ P. \0 W7 w3 a3 B
    6 L! L' p) T! f# e8 u: S+ z' g" J8 n
    import org.springframework.context.annotation.Scope;1 W4 C) A! l/ }# q! M. C
    import org.springframework.context.annotation.ScopedProxyMode;
    9 v1 d4 ^3 P% X6 d, R. h1 b
    9 t0 q! w6 g; iimport java.lang.annotation.*;
    - v+ W! l4 L; i' H- [6 f3 X% h. [8 X8 e5 P9 G( O% X+ N
    @Target({ElementType.TYPE, ElementType.METHOD})) k2 c! k( w5 C! O- ?5 Z$ m* Q
    @Retention(RetentionPolicy.RUNTIME)
    4 R% ~, h! y  m( f9 X7 V- D@Documented
    - ?8 }) Q0 \# ^; O$ `) y5 c" Z@Scope(BeanMyScope.SCOPE_MY) //@1% Q- d( r  U9 i( {; h# Y& ^% q2 k
    public @interface MyScope {6 }8 F8 s; C$ [  d# W( P
        /**4 n* b8 b, b7 \/ d! Y
         * @see Scope#proxyMode()
    $ c. E/ {% F  i2 i3 M- w     */0 S# K: Q- z5 ?# n
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@26 O2 ]2 x% A; w' c
    }: V9 R' x) U* z- T! ]. e
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    5 U% Q5 p4 Q4 j+ ~+ P1 V$ @1 X% {2 y+ l& c
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ; A; M# A3 O8 ?6 C! H, i0 ^. m2 `9 X- s8 ?* G. K) T3 G
    @MyScope注解对应的Scope实现如下
    9 ]" M  G: U  H! r  n- |( W- T; R4 @
    " U6 d) p& p4 z( T5 ~% xpackage com.javacode2018.lesson002.demo18.test3;
    . Z5 O7 h: h/ d2 K: R7 M( ]" @& V0 c) |6 m( m
    import org.springframework.beans.factory.ObjectFactory;- y9 W, p/ d. g1 p5 j
    import org.springframework.beans.factory.config.Scope;
    ( i- T0 o) |! cimport org.springframework.lang.Nullable;% e7 n2 |, Y7 D" Q) b# b

    + r* f; W) b- Y3 S6 b# G/**
    % H+ y& T6 b- l; i% h! o * @see MyScope 作用域的实现& s( O! b+ N. U0 k' h
    */
    / L! o$ ]7 K' I6 L8 R0 q' `public class BeanMyScope implements Scope {
    7 ~& G, y& v. K# E) ~0 p% `) Z" Q, `0 d+ d1 F4 d" g
        public static final String SCOPE_MY = "my"; //@1+ K# y" O( D! ?6 C% o; Y( I+ O
    1 R& m0 U: ]+ }
        @Override
    - I8 [' P, p2 l% ]    public Object get(String name, ObjectFactory<?> objectFactory) { + h! d1 J; Z, |! A/ Z& y
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@28 k1 C. l, F' J% n. W
            return objectFactory.getObject(); //@3
    $ v4 m" ^6 S1 y% K  y3 I% x5 a; M    }
    . _5 Z( K2 T6 V5 `# N3 [
    * X: o  b5 G7 H, G" ^4 v    @Nullable
    ! t$ B9 }" t, S( o" O    @Override- C+ x2 c( W1 R
        public Object remove(String name) {
    $ A; c* a$ |1 N, ?" {        return null;
    ( V" E' j2 r  O4 C    }
    ' G) g9 j+ b1 u+ Q8 {/ b( j# w  o6 j. k+ W. l; q: k7 a" X8 ?
        @Override8 O$ k/ D" R5 w  R& Y2 j0 ?
        public void registerDestructionCallback(String name, Runnable callback) {& F3 T; x7 V5 B: U* @

    . o# R# R" x9 P$ N2 t' b    }
    ' b3 A2 A! a7 u3 V7 M7 Y; g* Q+ u& J- t; D5 W( Y
        @Nullable
    - L9 `/ k& p9 Z$ \    @Override
    8 }. F; {' p" \# W5 P    public Object resolveContextualObject(String key) {
    ! ?! ]$ D+ v. {        return null;+ w; U: N+ @" q; B5 W& u$ m% Q  Z
        }4 B  h" ~( Y8 _$ _

    * e6 N0 E3 j4 @9 \    @Nullable- {2 J2 D' c. X6 Y# g* X
        @Override2 S4 [) A. @+ _. m2 S- ~& n& y5 ~
        public String getConversationId() {& Q% L0 m/ F. K1 p: q4 W, v3 r
            return null;
    ( d4 C$ G9 [; O0 W7 S- J' A( |! X    }4 j; @7 K' S3 i. M' Q' A
    }
    - v8 W! j# b. }. f2 y, k@1:定义了一个常量,作为作用域的值3 U, C) {, ^: n( Q- x% G& q
    $ ^" g/ x7 C) R" \
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    ! w4 m+ H  ^5 ~
    1 H, H0 L/ V7 j0 }- p@3:通过objectFactory.getObject()获取bean实例返回。
    8 N9 V7 m3 o7 g, ^+ C( }5 |
    ( ]) \. @& ?+ V! \2 z下面来创建个类,作用域为上面自定义的作用域
    8 P3 x. n- T' ~) ]" U* _! W% s) g3 b+ P
    package com.javacode2018.lesson002.demo18.test3;
    2 g6 _- _! b2 o: X+ L3 w" p  ~7 f2 o2 N" G2 d  X$ S& H* h% d
    import org.springframework.stereotype.Component;
    ' f4 y) x& S) f8 ?) |) q, Z+ n
    ; j" X9 }3 L  [import java.util.UUID;& D) A; p8 ^! Y2 g- [( A

    2 Y- y: F& e+ f7 C3 V) K4 Z& K@Component6 ]8 z, l8 U- e* x7 y6 A( F$ c
    @MyScope //@1
    : I2 A7 o/ Z( Gpublic class User {
    ) ~9 l& e" J# D  t6 X1 O0 w' \9 f  Y; p) r& E
        private String username;
    3 P6 s" v1 D, u+ Y
    0 k7 F. e+ M2 ], `    public User() { . Y% ~# u0 A  [  P' w, {. Q
            System.out.println("---------创建User对象" + this); //@2! r  U. l* c) Y9 a! M. e
            this.username = UUID.randomUUID().toString(); //@3" ~- ]$ F: h" ]2 r9 x& R( i
        }
    8 N3 D2 v, z" ^3 j& @7 P: u5 ~5 A% X: D  m6 t4 S, \) f8 z' [" g( ?
        public String getUsername() {
    2 i) q8 B  O) S+ L0 C5 d0 T( G0 k# W        return username;
    ! d4 L6 f( P' N/ F! w, h. W" ?  B    }
    6 o5 Q" u$ d7 B# h5 \! @' u4 m  G7 G
        public void setUsername(String username) {5 [- \$ }1 p) u- |9 X2 T
            this.username = username;+ q) M) ]% x5 {9 \- ~
        }- W+ |, X; R  |$ [
    4 W. [* y; G: `
    }8 g2 S, w6 |; T2 m+ h2 w
    @1:使用了自定义的作用域@MyScope
    : o0 v. w1 u4 O' ?! z5 d8 M; ^. w/ Y. \& a
    @2:构造函数中输出一行日志
      C% u$ L* k; ?, t. v* ]$ s
    # N% L' s) B5 F1 C7 V' Z' \@3:给username赋值,通过uuid随机生成了一个' X3 w# c$ k$ W. N; q

    + y/ B% X/ v, }! w: @! N来个spring配置类,加载上面@Compontent标注的组件! {; Q" `6 Q6 {. ~) f0 _2 _1 V

    - R* U' H1 d0 k$ S0 Rpackage com.javacode2018.lesson002.demo18.test3;
    0 @, g8 b6 Q0 ~' i$ p/ R9 a  A" x$ u- {& z
    import org.springframework.context.annotation.ComponentScan;
    2 F* `, o; D! z5 g% |& {: kimport org.springframework.context.annotation.Configuration;, B4 ?7 C! P: d% T5 ]2 ]/ @
    # c" W1 L, c1 V. |6 ~
    @ComponentScan
    . [  Y$ S+ I3 ]' P@Configuration0 ^8 P7 l& n9 O
    public class MainConfig3 {- c. `# E# N: L  L/ V+ d, a) Y
    }
    $ v1 S, O) R* z$ _$ p" ~  \下面重点来了,测试用例
    0 Q/ `8 Z7 v2 X5 K. f" o- S* g! H# j0 L* c- I* J% E6 b7 P3 Z
    @Test
    # m5 n; K! j! }* o. Ypublic void test3() throws InterruptedException {. `: C& R& k+ H9 k- V
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();! R# F) i9 m5 C+ @& D" o2 b! l
        //将自定义作用域注册到spring容器中% m  I% T2 M7 \: u: a! R
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    9 q# Y; [. F6 Z8 s' x# U4 l8 c    context.register(MainConfig3.class);1 B, S1 k+ c" X) {& A
        context.refresh();
    ) d: v2 n( v, W: w+ ^: a) z, A
    ; b0 n& _  R) q7 K    System.out.println("从容器中获取User对象");7 f4 `: r8 o- s6 u7 D% V/ s
        User user = context.getBean(User.class); //@22 L# y* J: o  F. Q5 Q
        System.out.println("user对象的class为:" + user.getClass()); //@36 l0 {$ b+ @% ?+ o3 [8 \
    8 {* R+ n. w" }5 \& b1 X
        System.out.println("多次调用user的getUsername感受一下效果\n");! |  w/ j* }/ ~/ w
        for (int i = 1; i <= 3; i++) {& e1 ~- D3 L: Y+ {$ S
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    * i- Q0 B6 l4 _3 w$ o        System.out.println(user.getUsername());2 G) w# D- t- \! O; d
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));! H. Z% Z% E' `5 V9 N
        }
    ! Q8 W+ m  R2 a9 M  O8 @}
    * P, y. m8 m* l4 _@1:将自定义作用域注册到spring容器中3 H7 Z3 D8 V+ O* i
    ) j0 z. L: p2 T* b% b
    @2:从容器中获取User对应的bean3 h- R% T/ U' @- c/ q4 n

    % W( @5 _; m# F) F7 G9 D6 a5 a@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的' {; Z; f8 C  ~' i6 n3 @" L- M$ M

    . L/ Z: F7 T7 Y- y$ m% |代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    0 C9 o, u7 Z, r1 U4 d
    * M1 w* F$ v, U7 w2 S2 H5 |, |* f见证奇迹的时候到了,运行输出
    . F1 l5 ]& t1 E+ }6 X  F9 T, F0 p" Y! _# ~
    从容器中获取User对象. u# S. T$ W( h/ O- k
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    7 K4 d" S) n" A5 V1 y' A' Q& B  o多次调用user的getUsername感受一下效果
    0 X% B3 a( k5 M% {2 G9 f8 g0 w% F; V4 J2 S& c) Q
    ********
    ' _* j+ f- }6 ^! y/ J2 y第1次开始调用getUsername* W' \# i" d# x
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    + F' A' ]' R$ y4 P# N---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    / E0 G7 N, j: Y3 k7b41aa80-7569-4072-9d40-ec9bfb92f438& T6 {9 [1 X. |' u+ b
    第1次调用getUsername结束
    % X4 T* B& x/ f; G& q********8 u# J) w; N+ b2 h& S
    3 D/ ^* t; Z& @9 p/ k
    ********; I. E) e; F# b$ U. j
    第2次开始调用getUsername
    , _7 h* u' Z4 X7 P1 X, K: DBeanMyScope >>>>>>>>> get:scopedTarget.user
    : D4 l2 j3 o4 Z1 y7 q7 `3 l; a---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b* O( P" h) }/ ?# T2 d! g" m+ d) f7 u
    01d67154-95f6-44bb-93ab-05a34abdf51f
    2 n- s9 x6 V2 g- u第2次调用getUsername结束
    , _" d- N; R5 G********& ]  U! S$ G" C) V* \/ |
    ) |: @/ A8 Z/ N4 R0 g+ J( `8 k0 E
    ********% }) X8 h& K' _- Q2 s
    第3次开始调用getUsername
    9 M# e  G. o' U7 EBeanMyScope >>>>>>>>> get:scopedTarget.user
    " G% Z% }6 t) h/ |& S  Q---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d156 L% _, `: t0 _/ ]
    76d0e86f-8331-4303-aac7-4acce0b258b8- P" c) Z. v" a
    第3次调用getUsername结束7 O% f  |7 o9 u; w6 o
    ********+ d) @8 B) _7 J- E+ k4 S/ |
    从输出的前2行可以看出:9 t3 M% [0 ~* j1 ~: ~
    1 {5 O; a$ J. _% J- Z# I
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象( z7 p! U* n1 k0 D2 j. N
    , @0 I/ A+ s3 a- e, y: u  z
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    9 d6 l  y# _! H- z+ v% a
    . M* |/ |6 D0 Y$ W3 R后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    / h' d& E+ I, l+ Y/ s. R1 s+ I" t5 n  c* k- Q5 y' x& a3 u3 |
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    " W* N- P8 K, R6 U
    3 j& X+ T" h+ a) s动态刷新@Value具体实现# X6 p4 n0 S3 p  c( V( G
    ( @% F2 s+ S1 p5 s
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。7 K4 \3 i. K" l
    6 m- e; m4 U" N/ z* K4 T1 N
    先来自定义一个Scope:RefreshScope8 Y7 d) f! @1 I' P) ^5 r; V

    5 b$ a6 _. J& fpackage com.javacode2018.lesson002.demo18.test4;
    7 o) C8 Y3 Z- J5 x0 I2 \3 Z7 T3 Q9 \: ?  s$ P7 v( j  E
    import org.springframework.context.annotation.Scope;9 J" c2 v' C" z, p  }' {
    import org.springframework.context.annotation.ScopedProxyMode;
    % E7 R/ a3 O9 r. G6 ]+ D
    1 Z/ ?$ L  v+ c) u; timport java.lang.annotation.*;
    ; ]8 K7 d* L$ q( P
    ) @: C& r- d* y@Target({ElementType.TYPE, ElementType.METHOD})
    * L6 Z# U6 }2 M# {- {/ S4 L@Retention(RetentionPolicy.RUNTIME)  A: P9 b5 X- j/ c6 \$ V
    @Scope(BeanRefreshScope.SCOPE_REFRESH)7 K! t( c  S" q: P5 z- o7 |7 }6 [6 \
    @Documented
    , U# P  X( T3 b4 U( fpublic @interface RefreshScope {. b7 q" b2 T2 A6 h
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1: J  f& ~7 {8 y1 f3 c
    }
    $ z' h0 P% k& w要求标注@RefreshScope注解的类支持动态刷新@Value的配置. {$ V% l7 I1 r- M) J2 K
    - @/ i$ D" p* d- x9 `1 u
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS8 w2 t& m3 b$ c/ u/ w

    1 _" s8 _. w$ s0 G. f# J" y这个自定义Scope对应的解析类
      T3 m! z8 {1 B8 B6 c0 b# z( H6 S
    1 ^4 j. A# X2 H, b* m下面类中有几个无关的方法去掉了,可以忽略9 `+ ^$ v3 Q; Y% y; s% X: L" R  K

    % H3 w* [! P/ @6 @package com.javacode2018.lesson002.demo18.test4;
    + @" k% [# I# P/ S
    . c2 s  g4 r# m; L) D4 x
    # c4 N; n* ~; h3 L1 v9 y4 pimport org.springframework.beans.factory.ObjectFactory;
    4 p# S2 B( i' B) ?$ l; a. Zimport org.springframework.beans.factory.config.Scope;
    0 @4 t4 m) P% D) v% W5 simport org.springframework.lang.Nullable;
    : A6 T" T7 h) Z- o
      ?. u  I( W' Dimport java.util.concurrent.ConcurrentHashMap;, B6 O* w. l" Q7 k

    9 m) n3 D5 _# }+ r) M! z2 y; [5 wpublic class BeanRefreshScope implements Scope {# _' ~+ R: M' ?: L: }$ z) z* D

    $ r2 N; ~$ p" @% D2 }: u- r    public static final String SCOPE_REFRESH = "refresh";
    # h# K9 e. D$ g+ C
    8 j% Y  X3 t7 e! k! M    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
      `3 a0 ?9 Y9 ^0 W
    0 h+ O- K5 e, K* k& E" z6 Y! W" ]    //来个map用来缓存bean5 ~+ a3 \# p6 \0 A- ~$ K) c
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    # a  z7 D- X6 l# T0 O6 ^1 u1 R1 Z  C% U& K( w
        private BeanRefreshScope() {& X% O& h1 \7 |( s$ G/ u
        }
    ' l/ U0 `( x8 G! ~: c- ~2 p4 D/ ^$ E# E" _0 l% V0 P
        public static BeanRefreshScope getInstance() {
    / k* }* D: }) G9 L  L        return INSTANCE;4 A4 C+ ]/ J, @' |
        }3 Y. k0 l6 ^, ^! J! T) t

    6 v$ J% \& F: u; G$ l    /**
      S2 M# q# E7 d. s     * 清理当前; z- N* c3 Z2 P2 `. c8 I
         */7 l0 C0 D2 Y. B" i. l) o: L
        public static void clean() {: a% I: f, K  m
            INSTANCE.beanMap.clear();& C* Q9 S. ?7 n
        }
    : G( O( K2 y; O+ \9 [9 X
    - S# P0 b. V( B+ }7 ]: r$ [    @Override  {+ ~9 Q4 a, @. k
        public Object get(String name, ObjectFactory<?> objectFactory) {8 X3 x# Y9 T8 Y9 E' f8 S* Q1 Z
            Object bean = beanMap.get(name);6 x' X0 v2 Z/ e
            if (bean == null) {
    ( `0 H4 |+ d/ g, T* ~3 r4 f            bean = objectFactory.getObject();' ~; X  A# R: M0 Z
                beanMap.put(name, bean);
    . k$ [5 W( ]' S) U5 E6 ^        }4 |1 P! h  Y' e
            return bean;
      }* o- J1 A" J2 I9 v! w3 Q1 p1 K    }! I$ q7 N7 a# g$ y" |

    " m+ P, g, l3 b& q5 @  Z}
    & j$ U+ n. ^) H/ m1 p上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中9 i6 S/ G- I% p, u# j) d( d( D: \* G

    : Y( `9 S5 R' _1 Y' r6 t- f/ d3 f. n上面的clean方法用来清理beanMap中当前已缓存的所有bean, l2 S" ^! Y1 r
    4 J( p$ `* l4 H9 g, u2 {
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    ; l  M. D7 j/ f
    % u: E. c, @; mpackage com.javacode2018.lesson002.demo18.test4;  _* a8 _$ a# J4 o1 }, W

    7 l8 c) k% m6 v& I" N) Q! W# \$ zimport org.springframework.beans.factory.annotation.Value;2 O" P# e  w8 N3 b1 ?# L$ [2 k" b* G
    import org.springframework.stereotype.Component;
    * V9 n: W: S# u4 I: y' J( n' x% g4 j+ N# p( ^$ f* B) b2 n
    /**, x( D" o9 X) U: W; b0 {
    * 邮件配置信息# n0 @5 B7 O8 [5 N0 X" J4 y6 J
    */0 q8 {9 r2 O; ~9 S7 q+ D, p( ]; A+ S
    @Component6 I0 r! A* N9 B' m3 G  g# J
    @RefreshScope //@1
    $ ]% M- p! A9 R- c9 t2 upublic class MailConfig {
      P' o: ~( d' T9 n4 l* z
    3 F7 b  c$ k2 A1 L: B0 H5 P& f" w5 i    @Value("${mail.username}") //@2
    8 s  }  L+ d/ T- e    private String username;
    # W+ f$ M# g# T8 W% y
      L' u  W# m$ F2 O    public String getUsername() {2 Q7 R* G, p% F1 z: j3 }' F
            return username;1 t) v0 c0 K- J  q. E' l
        }
    $ W+ e+ o( O8 U/ y! y! ^$ k3 {; n, |: }
        public void setUsername(String username) {
    0 N/ _$ ^4 A1 B: F        this.username = username;# @1 K5 X, T: ?$ O- e" |
        }
    ) Y3 H! p2 J  N! X$ e0 a: ^7 w) R- d* x9 j2 k/ z
        @Override/ c# A: u& _+ F( j
        public String toString() {
    ' B" R$ G! [# O: k: r9 W        return "MailConfig{" +) ^, J  k% M; o! O+ @( ~4 v
                    "username='" + username + '\'' +
    9 Y2 |, T4 J  ~                '}';
    & E9 Q5 N& \; W0 W  Q+ z    }
    6 k9 w5 M; t9 \0 {' E}9 i; d9 ]$ a* F2 w0 V8 c3 D: r
    @1:使用了自定义的作用域@RefreshScope, D1 }3 j# \9 ^

    0 c; N9 Y0 t" q9 z@2:通过@Value注入mail.username对一个的值
    / r/ u$ }2 t  B& m; m" k' Y3 j" ]6 ?6 z: i$ O, y7 y& B
    重写了toString方法,一会测试时候可以看效果。8 x+ c6 u* ^) s" @: s5 Z1 F0 z' G. w
    - e4 b9 f5 U, L  O! d1 G" X
    再来个普通的bean,内部会注入MailConfig% o& ^( L/ q0 c" x

    6 b1 c: g7 _# qpackage com.javacode2018.lesson002.demo18.test4;- c" B7 E  V# s5 c5 h
    2 a- c& Q- P& d7 ?+ g0 s
    import org.springframework.beans.factory.annotation.Autowired;- c7 U) x6 q. |% _& O* j
    import org.springframework.stereotype.Component;
    ( N/ V" i: {' J  C) R- W
    ( R5 N! e0 |" O9 ]( J" F2 H6 Q@Component# _5 K' J6 |( T# u1 K# d
    public class MailService {& K$ B+ u# i( u( K1 l% b6 b
        @Autowired
    / d9 k1 n* s' O' c$ Z    private MailConfig mailConfig;
    ; x: C  O& ~& S; C& r0 A" K& [7 ~) _2 M6 j! u2 ^" W
        @Override
    2 t" v2 s( t9 A' u5 S    public String toString() {# f/ w! _- |! D' d! m! H; v* @9 S3 l
            return "MailService{" +
    & G$ u' G! [0 ]) N3 D! L                "mailConfig=" + mailConfig +
    5 [6 q/ Y, I2 B8 w$ ]1 E$ a% `4 f' n                '}';! n5 K# C* k. l9 w1 c( Y& `
        }' S$ D) F. N# ?0 T0 t( m6 l% q! T
    }0 ~/ |9 d9 |6 `6 I: L8 ]
    代码比较简单,重写了toString方法,一会测试时候可以看效果。8 f' ^! v. W9 t

    : l  E6 D& L( Y% _& L7 ~& `! Y来个类,用来从db中获取邮件配置信息
    $ ~* O8 h: _7 w7 X  h' D+ |5 {2 @5 ?& P) Y( i1 a: p6 t
    package com.javacode2018.lesson002.demo18.test4;
      N" `0 u8 f; A2 X; b1 S& V# d. y" e+ Q# N. b# U$ s
    import java.util.HashMap;6 s0 ], v& U3 W- [$ P
    import java.util.Map;! K- I) G9 Y$ J& {3 p& k
    import java.util.UUID;
    + o) Q7 a# g3 g4 x2 e% P
    $ x& M# q; h* x! q8 F3 Opublic class DbUtil {9 Q( x- Y! a5 u& M  w! A- j
        /**
    % S/ F& t4 N1 e8 g) l5 P     * 模拟从db中获取邮件配置信息- u# T$ Z, R* o$ A% H
         *# |) _& u% [: X" w' ^( c
         * @return
    % u$ o; {, r9 O* u/ y     */! I0 K9 q; A' F
        public static Map<String, Object> getMailInfoFromDb() {
    1 h8 u/ L* ~) K" `! t        Map<String, Object> result = new HashMap<>();
    ; ?& r0 j/ q' c1 ~& |        result.put("mail.username", UUID.randomUUID().toString());% T( V% O- |4 u# {8 e
            return result;( U. v8 m! z4 c/ p$ _* p( q! ~
        }
    / N) C, y9 j4 t: e0 n8 |* m( R}! t6 ]! E# B3 G3 P- k8 G( m8 g
    来个spring配置类,扫描加载上面的组件# V& h. u7 `8 A9 H  ]9 \9 |. P7 S

    2 o+ Q& z* C* ~. v* L* N% zpackage com.javacode2018.lesson002.demo18.test4;
    $ `( J! l0 \" w$ X% A; y8 B% l+ K' w
    ' M8 a$ y5 I6 U" u  J, Pimport org.springframework.context.annotation.ComponentScan;, a& A& f* h+ ^) t
    import org.springframework.context.annotation.Configuration;  `% L" B7 \! l2 B& A, x2 j

    ' }9 r7 j; K- E# W@Configuration5 z) j& k, y3 l6 n
    @ComponentScan
    5 ~3 b1 z1 r/ B& V% q# z# ~; npublic class MainConfig4 {
    2 k( ]1 T; `% m" m" R2 z}* j( s7 z( Q- x( y" v
    来个工具类- N$ z# ]! D" S) t" E

    " I# u9 o5 T* j8 c+ X& _) g内部有2个方法,如下:
    . v8 C1 T" T9 {/ ^8 h+ d9 M6 W
    9 I/ ~+ T' A0 b% Fpackage com.javacode2018.lesson002.demo18.test4;
    - T' S8 z! f7 L7 b3 ~' @) ^8 ^
    " L! x& C# V3 r  nimport org.springframework.context.support.AbstractApplicationContext;
    # ]' j) q( S, H3 C; S; j" U3 h; himport org.springframework.core.env.MapPropertySource;
    7 c  @! g+ W! E) Z6 \! d3 F# D* Z& z: ]; S
    import java.util.Map;! ?4 S+ s* C. p: a4 B9 {1 |
    + ]/ L$ I1 ?) X8 `! x9 o6 Z% M
    public class RefreshConfigUtil {
    % T5 [) y8 k( v9 T% d. a) \    /**& M4 g+ q! {; ]- T
         * 模拟改变数据库中都配置信息8 w' r) T, u6 J# w' c" A- F
         */
    1 J3 _) f+ A9 M9 ?. _4 |    public static void updateDbConfig(AbstractApplicationContext context) {: D# i! @; o3 w3 D6 s" ~$ ~' S0 C
            //更新context中的mailPropertySource配置信息9 [* ~- z2 w; U! j5 V: j+ J
            refreshMailPropertySource(context);
    4 q2 W& @# P: v  A4 F7 a
    ! V) y+ O2 G4 P5 z3 ]" ^( X& t3 x, A$ X        //清空BeanRefreshScope中所有bean的缓存
    " k* Y1 r  E' f4 ~& d1 v        BeanRefreshScope.getInstance().clean();3 k; X) Q0 d$ v! l
        }- V* b7 V8 j2 O8 q2 E+ R# w

    * b& U8 n2 q- v2 K2 `. i    public static void refreshMailPropertySource(AbstractApplicationContext context) {1 K* v0 {" m* i
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();& U1 X5 {/ I" p$ x( N" v- l- y' K
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)/ _! b6 M. C9 g( }! C
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);8 q; e6 |( E9 x4 z
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);5 a; |( H/ ?9 n7 B
        }4 B. D9 g: F) w- o" N( m, x

    5 X8 e) K& Q  N* p& u}# R. m0 U0 J  W1 I( H8 X) x9 {
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    , o# @* u  _9 ?* \5 b( v! L9 L
    ( A" z; G& n% p! l  R& TBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    " c! A: @' v+ Y2 R- L' ^$ @$ o( {
    . ?) d# v: a) t. f) A* m& m9 l0 b来个测试用例
    $ V& k0 \3 Y2 z4 w- L; s7 |5 ^* k8 P# m
    @Test
    / C9 M4 t* ^5 g8 O& Npublic void test4() throws InterruptedException {3 }: a6 ^5 r" N0 t& L
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();6 F- z/ }: k4 i0 {, x* X
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());. c- L. v/ |6 R" A, ]
        context.register(MainConfig4.class);1 f# f- n: }$ a
        //刷新mail的配置到Environment$ F$ `) t$ U# q- B8 ?& }4 O$ \8 n/ J
        RefreshConfigUtil.refreshMailPropertySource(context);3 s; B, o* R6 c* x- g* `+ S
        context.refresh();
    ' O& O4 W2 {3 V% L0 n6 I0 A+ S6 i6 ^$ S' r  `: A; @! n- T
        MailService mailService = context.getBean(MailService.class);& \! @* Z4 R. f
        System.out.println("配置未更新的情况下,输出3次");; M; f( k  w% j( I3 B: S
        for (int i = 0; i < 3; i++) { //@1
    2 S0 ^/ s$ l  Q        System.out.println(mailService);/ I  a" W5 r) V- P& {
            TimeUnit.MILLISECONDS.sleep(200);
    3 }4 }  d4 L% R    }4 X' E( Y. T9 t
    6 T! N0 \! P- l' U/ \
        System.out.println("模拟3次更新配置效果");
    , y$ V) n+ n7 r& @3 F    for (int i = 0; i < 3; i++) { //@2& }$ p7 M$ T; i, _
            RefreshConfigUtil.updateDbConfig(context); //@3
    8 G  W' K: ~1 p8 ?        System.out.println(mailService);4 k% ^# `( T) i3 W& x
            TimeUnit.MILLISECONDS.sleep(200);
    - g7 f& x; n: M$ P! @: b    }
    6 F" Y+ X- a: d' ^# [9 `: B# E}; J' E4 l+ g7 c5 Y
    @1:循环3次,输出mailService的信息
    # Z4 z3 }  ~  n# m7 U4 v1 D' t# \: g* T* \) S; \5 l1 @6 Q
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    # H6 B( a6 [- A- d4 u
    . X7 x1 |; n1 `. v8 D  y见证奇迹的时刻,来看效果
    . h# I5 ], w4 i+ z' ~. K+ j9 z6 ]9 Y6 R, [7 J6 r( d6 Q) {/ d6 h6 q5 _3 ?
    配置未更新的情况下,输出3次0 D1 b" p" ~! i) M
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}; D" \# k6 y( y( i* r/ l9 s6 E
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}4 g5 c) M: t" C/ k; T( e' d
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}/ {, c& |# }; [! r7 |: v. q( ]
    模拟3次更新配置效果
    9 T( L# @& Q) @3 f0 K* m" vMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}4 H  s# s' o2 a0 X5 {# c! Y% H& q
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    2 ~, R! E/ S8 ?3 I( u) R3 t+ M$ DMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    3 P: a: k& g3 N! p/ M上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    ! Y4 S- d- R6 ?
    4 |0 S$ G: d) ]- _7 ]小结) F" F" @+ D; N

    $ a0 U# H3 F( N# `% C9 g$ B1 R4 Z动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。: O9 m. c5 d% `* r4 M: d
    8 B9 L% V  G  F6 R( {* d; o
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。9 N# p2 X1 W: H; q) E2 \- G5 o
    $ q% V1 S8 i0 ~) o1 x
    总结0 y  b8 x/ R- a+ b9 O
    ! K8 `% c0 q% H5 q& m
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    9 Z$ P2 W, e( }; c# ^; G, h8 r: w5 ]6 {' p' t
    案例源码/ d) v% C- ^4 h2 I9 b1 E: R

    6 B( L+ q6 Z' @0 R4 D* D0 a4 Y; `* Jhttps://gitee.com/javacode2018/spring-series6 B! ~2 ~: E, Q3 ?
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
      {  m/ a" i$ j% @& [3 ^* N, p: Y& `4 K" C9 d/ [
    Spring系列
      e  E- v6 R1 E5 y+ _4 _
    4 Z3 @; ~0 B& P+ E; P! V2 vSpring系列第1篇:为何要学spring?
    8 [5 `' C1 G, u3 u0 m5 g& u+ e+ I
    1 d7 U8 K" m6 ^4 D6 `% SSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    1 a. z$ \9 `$ j1 _1 }/ a8 i" y) y
    ! x# y0 P, @- sSpring系列第3篇:Spring容器基本使用及原理
    + \5 f5 k* s5 r/ E! o) X; U9 \+ p, L: r  ?1 w- V& f+ B
    Spring系列第4篇:xml中bean定义详解(-)% Y, U, _0 K: _5 t5 B

    3 F/ G" s5 v0 A' H! OSpring系列第5篇:创建bean实例这些方式你们都知道?
    ' Q! ~5 c, R; q' D
    / R, [* W8 [, _; k( g' D# f* mSpring系列第6篇:玩转bean scope,避免跳坑里!
    ) v# q0 ^% r/ M0 L1 J- y. e- s+ [6 c+ l# P8 k* P
    Spring系列第7篇:依赖注入之手动注入" ~& `( [$ x4 S7 X4 s
    ' @4 E% d8 n# p. }. G( e0 i
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    1 [  w6 f$ }3 W: s, R$ I% ^& y
    # N4 l7 [+ {7 }" X4 e& \# qSpring系列第9篇:depend-on到底是干什么的?4 H( p. N8 U7 l* c+ Y  ~0 S/ s

    / _$ ?- ?! b) M( o5 n8 RSpring系列第10篇:primary可以解决什么问题?
    , k% {+ s) Y& }' E' A* S0 ]$ B  A' e. ]: \# O- F! ~6 B% R
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    6 i- `! n* `8 X  R! j5 n8 B* S5 C( }1 W
    Spring系列第12篇:lazy-init:bean延迟初始化4 i) z# t# [0 j  c# I

    3 ]) `! ]! t" h; t7 V! @2 FSpring系列第13篇:使用继承简化bean配置(abstract & parent)8 n1 d1 c6 h9 p" Z* V' `1 K

    + d  j/ g6 B: j8 HSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    5 K8 C. h9 u! A% a( u
    0 R$ l1 O' z( A' t% \9 E+ [Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    1 G/ v6 H3 m# O  U7 p5 M0 P+ j2 @- O& n2 V* U' A3 I
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识), C* f$ e2 Y4 u) }6 w# E
    2 W. S/ o$ F4 U. O% _( ?3 j
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)3 z8 e. P) j+ V) s! B* N4 L
    3 o% ]) N3 @2 S
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)$ c) m# K. y) Q; q0 x1 _

    - p* c& C9 e" ]0 |) k, _3 FSpring系列第18篇:@import详解(bean批量注册)" n6 a: b( I: I: u6 O, z8 s

    3 \; N# r! e, _* mSpring系列第20篇:@Conditional通过条件来控制bean的注册
    / @% J$ y- F! n' r* K7 l8 b! _, F
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    . h( [' x6 N% g
    9 Q" v% l& H2 \7 |3 ~Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    ; g1 P  @# g; W5 t+ p. }  g2 t, T+ h
    . B4 ~* D. V+ D/ E: ]# CSpring系列第23篇:Bean生命周期详解
    , F$ Z; e* j5 ^1 N( x1 c3 {6 D# S. i
    * T, ]+ m' j( l6 H+ ASpring系列第24篇:父子容器详解
    & ?# h( L/ b5 L( P: C0 P. W' y3 v/ @' \8 w
    更多好文章
    . p% Q2 ]0 G; Y- t$ e+ v5 i- o. i; l6 V8 s) Z& j7 f3 \% t0 f* s" A
    Java高并发系列(共34篇)+ I# u9 Y% m! [7 q

    2 d- ?, k" M0 y# z% B; {MySql高手系列(共27篇)) t* ]  e6 S5 f' V

    7 Z" R: h+ ]* L5 J6 R+ N# ZMaven高手系列(共10篇)9 }: ~8 o: ]" o( e. A+ i& q. u' v
    / h& p2 R, O9 f: i: o
    Mybatis系列(共12篇)
    3 `+ J" c, ^8 i; u# E6 X: |. U# T3 a
    聊聊db和缓存一致性常见的实现方式
    8 K+ w; w; K. m' _! ~/ Y# R
    3 F+ }" ]: F/ q接口幂等性这么重要,它是什么?怎么实现?. R% }) a* ]# j# c5 D( Y
    / o% |- r: ^- Y! t( y1 g
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    # s6 L6 ?: Q/ x' Q2 u. i————————————————
    ( f6 G: @$ a0 Z& x! D版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' m! ]8 j! I- o4 ?" ~; I1 T原文链接:https://blog.csdn.net/likun557/article/details/105648757+ t6 U* N$ l$ u- q( f' l; X1 F9 Z8 M
    # Z8 j% d' p3 h; Y% ?
    + i& I. p0 T7 O! @$ j3 g
    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-18 16:32 , Processed in 0.431639 second(s), 51 queries .

    回顶部