QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5622|回复: 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!
    / I" b/ H* R/ q. ?0 I; |疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    2 L- B. E7 x# R0 `" T  y+ b2 U" E6 s! ]+ g' D6 i
    面试官:Spring中的@Value用过么,介绍一下
    9 P4 |1 V: _$ V8 n; r0 g1 m" ]" O0 [1 t9 ?1 W7 ?- W/ i! m1 [! T( s- U% W
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    9 o) ~0 A9 h" V8 ]8 b- M% Y7 i' e- G
    面试官:那就是说@Value的数据来源于配置文件了?
    4 O* e* e/ {) p" H  F, T- y3 B1 R* ~7 H8 b) K5 G9 q6 b; c0 z9 r6 I! N
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置2 f( a, W& Q6 g6 M" u0 a5 l1 I0 w8 \
    9 b; p  K* {! D2 H8 j
    面试官:@Value数据来源还有其他方式么?! `7 r% d6 G( c  q

    ( m& d. I( u8 i" ~  [% t1 Q8 d我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。5 d; A! F# C5 {- y

    9 A8 r2 h. ]# [3 C. l7 t5 p/ @面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?5 ?, c1 {4 u: y0 A

    4 j' c# k7 E9 N" P我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    * G& i8 ?4 q" D9 I7 j" t2 q. w$ m  o5 S5 Q3 H% k/ E4 _% B" H
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    ' o0 a, N- d7 I: ?$ E+ G8 S
    $ n2 N; V. H# M. c9 M! T0 |我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能% {+ P$ u& B6 s& u0 `/ x5 A

    5 T/ w' k* b/ \0 O面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    ' [* h' A6 i5 _9 p" I
    5 m3 G8 e0 n4 u$ H) q( ?3 }* `我:嗯。。。这个之前看过一点,不过没有看懂' \4 H# x% T7 I, h1 `$ c

    5 E' u2 F% [+ t* A' C% T% c. |/ G面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    " p% T1 |2 |4 n7 i" ~$ J$ ^, e9 s0 A7 z; B' M2 J- H" s3 u5 ^+ z6 Q
    我:3万吧+ O2 _- U- m% H. B' M
    & V' T; [/ K1 u9 x# a) F
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    ) \% j: o' ^/ V! N
    1 }2 s7 L1 _/ u5 f* {我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    0 L/ C/ x1 R3 J; ^2 {' ]. X! Q' _% H* U- c6 V, k0 X/ I' R$ _( l- q) d
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!  e( J: h! ~$ ?  g, Y# A+ d+ \
    % \6 y& J! c1 }' r1 G9 e/ n
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    ; ^' s! n3 t' F, Z$ Q
    0 \  O4 G$ \+ ^4 V  X& ^这次面试问题如下, y" e: @; B9 Q" [- g+ q1 z

    $ v4 o( |- O1 I@Value的用法! N3 ?/ x9 e, Z/ d: c
    & A2 M3 `+ h) T6 h
    @Value数据来源
    5 d% U2 [, B! U* q/ V0 D% k/ J
    + f- q3 {2 w- F) l@Value动态刷新的问题6 K' l2 I2 S3 Z/ e
    5 f8 X2 z8 J) U$ J: S  O' x
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    & w% q9 L. x: s% }: Z
    $ m% l* P. j. e8 a4 C@Value的用法+ h/ w8 H4 f, h$ o% D2 M& N' ~! z
    ( O0 b( |( G2 _, e3 J: A# N) E1 u
    系统中需要连接db,连接db有很多配置信息。
    2 Q, Y/ r" [! e, A8 _
    . n& u  v( P) w# O系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    - W% H2 s: ~$ b, b* |+ W2 K6 H& h- P
    还有其他的一些配置信息。
    2 H  s$ C) ?3 [8 p, a, N* w9 x0 w. L* `0 o6 Q5 G) {
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。0 ^7 c2 l" }( \# S, |* y/ A
    - ~/ i' s- \4 R/ Y$ M' m
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。3 q$ d8 C, S* ^% o  u9 ^
    , u5 J1 Z, F3 Y6 O) }* s) K
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。0 _# f- g1 o5 J* w% k

    4 C/ A; ?3 g. A& ]7 h通过@Value("${配置文件中的key}")来引用指定的key对应的value。  d. s3 Y2 q% @8 G* ]- c7 k
    ! C% q) Q% _/ L  o- n& l' P. Q$ l, u
    @Value使用步骤; b+ {$ t) J2 b' s& O- `

    " i, }  ]7 D6 f9 ?步骤一:使用@PropertySource注解引入配置文件
    ) X( M  A% f7 W$ k- n- J% H2 ?4 z0 x8 _2 b
    将@PropertySource放在类上面,如下
    # x- J, i5 Y) a! d, E
    8 u$ k( b' ]! |2 q" g@PropertySource({"配置文件路径1","配置文件路径2"...})
    6 I& N+ c. u- z4 b+ [8 Q/ r' p@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。) M2 b* O% d" ?+ E4 M
    9 l  t' {0 P6 S! ]8 |3 ]0 e$ N
    如:
    ( _& `9 b4 z+ W4 A4 b3 r6 h3 j8 K4 o/ l  X2 h% P6 B
    @Component
    , C* A; Z2 e+ X+ z9 p+ e@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    8 i4 m/ @2 b- d0 Spublic class DbConfig {8 }# I" u! {; Y  `6 R& m/ y
    }
    ! p1 d" d9 e% O# f8 I: k0 p步骤二:使用@Value注解引用配置文件的值8 Z" X1 m; p" C  U( m6 m

    & f: }/ e" @( L" C/ e通过@Value引用上面配置文件中的值:
    & N0 G( I& o4 X' D$ B
    - k$ I/ a$ i4 c  g语法6 l; A( M2 l# L) ~  n. ?

    * G: T, E+ o# J7 h@Value("${配置文件中的key:默认值}")
    & t3 U3 }& A& S8 |: H6 F' F@Value("${配置文件中的key}")) z! D# u& l# i' }
    如:
    $ E1 O% r9 y6 r. E" g  R- z3 R% g, ?0 A0 j9 p/ g' W" s
    @Value("${password:123}")
    6 j2 P! l" [5 [. Y, p上面如果password不存在,将123作为值
    3 }/ e2 k) }" B% }! |5 @1 v& i6 ]& z9 e; E* Y$ ?
    @Value("${password}")" M& y4 G$ J* Y0 {
    上面如果password不存在,值为${password}1 }6 ^- e+ A- D5 E  P( d/ M

    : L; p. J9 F6 [& e5 S$ {假如配置文件如下% L0 G+ h0 C; S

    2 n7 p" a( e) m9 ?' f0 L6 W6 F  n) vjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ( u* a9 {! G2 R4 ?jdbc.username=javacode
    7 N; x+ ^3 K# G& i4 {& fjdbc.password=javacode
    ' ]' R: W7 [! m使用方式如下:
    . v3 K% N: X8 }
    , p0 H6 ~( y  d  w@Value("${jdbc.url}")
    5 t: j2 R4 C  a" |/ O) t/ Iprivate String url;
    " J( y6 K$ D" l2 q3 H
    3 H, `% m" h0 b7 `% H@Value("${jdbc.username}")# p" v7 Y5 P! A4 Y2 N) i  P0 q
    private String username;
    " }# G5 u9 L( ?. Y- f6 e/ U( v
    . a- s' h# k: j5 ]2 O6 j1 n@Value("${jdbc.password}")
    + \# h3 Z/ v+ ~, @: k- C. T! {private String password;3 f0 a) S7 @5 ~( c& g8 q
    下面来看案例
    7 k4 j6 x( J; }- H* t. z% Q( M2 D5 f3 E
    案例
    % `& D& P" [& o: s. T0 P8 h* B) r0 n
    ) b8 L) P+ K: N/ t& s( ^5 g/ e5 R来个配置文件db.properties
    * s. g5 p% k  d3 B3 s/ m3 ^
    1 x* I4 W( }# g; g+ Ejdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8' O3 f6 u! y9 m! [) P5 G
    jdbc.username=javacode$ `; p" D; }" G, N5 K9 I- f
    jdbc.password=javacode/ |& C& I+ C7 J; r; m: L
    来个配置类,使用@PropertySource引入上面的配置文件
    $ J8 p" n# E4 c6 s6 q- n4 y' k- C5 R' d8 S+ \1 O$ R
    package com.javacode2018.lesson002.demo18.test1;. q% g+ Z" v7 _. Z+ t
    $ @" S0 a# ~( I9 m7 p' C2 ~
    import org.springframework.beans.factory.annotation.Configurable;) @. X( a5 A$ D
    import org.springframework.context.annotation.ComponentScan;
    + I" X8 _3 l, q9 v5 _import org.springframework.context.annotation.PropertySource;
    . ^% s4 K6 w$ L% w! ]% M, d5 r. a( S# {- _& `
    @Configurable
    , |, z- P1 Y* p4 o@ComponentScan5 x5 u  t0 F/ C* T! \0 b
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    7 K; [* v" h, M5 E- d2 z2 ]public class MainConfig1 {
    % f  e$ h. B0 G% }4 l}
    1 M- s1 y/ y  R8 Q/ R$ s/ E2 {, v  P) ?来个类,使用@Value来使用配置文件中的信息! o8 h' T; H6 p5 m
    , d5 r0 a4 ?9 S$ R9 G
    package com.javacode2018.lesson002.demo18.test1;5 w7 l% i, B) A3 H3 v5 }

    ( U. b* ^3 i, \import org.springframework.beans.factory.annotation.Value;- k; d7 {8 G) j, K
    import org.springframework.stereotype.Component;; L7 U$ L* N% z1 y  R+ L" Y

    + B2 N5 i, ^% G, x4 _+ w@Component7 y+ y+ z9 _. \: T3 Z# G
    public class DbConfig {
    0 O9 \$ [& I3 f/ d1 n' x  O
    0 ]' p3 t$ F' j2 h    @Value("${jdbc.url}")3 y$ B& D( L4 D4 q6 _/ L
        private String url;! t8 n9 n$ ^& B7 s
    ) K0 E6 q5 }- s
        @Value("${jdbc.username}")7 l3 V0 l$ Z& d8 q! R+ `
        private String username;
    1 G. s, y+ x, S+ B& i9 ?7 o: k3 c. D7 X! n: _8 ~# r
        @Value("${jdbc.password}")5 F  U* E/ j# o6 M/ e% |( T4 {
        private String password;7 g6 A4 w! |- w5 d

    $ I( k0 h% \: f! t8 g2 f+ E2 t    public String getUrl() {+ ^8 H: m. j9 P/ j' r& v+ n
            return url;7 T) H! Z' v3 z) z- O
        }9 P4 X7 @% P% v+ v1 ], a6 j  [
    $ z7 q! l+ u8 U- O
        public void setUrl(String url) {/ [9 V8 T' _3 ]2 a' u& w5 G, g
            this.url = url;
    9 W+ i6 K4 Z# {2 u    }
    & X+ t7 K5 @; [- i! K8 Y$ s  o9 P! O7 r9 J5 R/ ~
        public String getUsername() {7 y' C9 k2 ~1 M) a/ t5 q. Z( J
            return username;
    6 A" b- u. i, z# E* U) S    }8 F7 m$ l1 `; x$ k: G  g
    ( v& n6 ?  @% ^, [% {9 W
        public void setUsername(String username) {
    . [0 J2 ]+ U0 F0 x        this.username = username;
    " O/ ?% [# Q% `% C1 `% l- r: i( j    }) o6 m9 S% g( E( T$ ~
    - w3 x( B9 m6 M/ {( @
        public String getPassword() {* d+ ]2 E- ?3 m9 n2 W/ S" n
            return password;7 v% l9 f+ b3 E' A7 |' K  u. S
        }8 u/ i/ X5 K2 R# k7 _+ p
    ; Q7 [% O, `, a+ t; `* [5 |
        public void setPassword(String password) {
    " Q( m$ w; V+ P3 A        this.password = password;
    - y5 l, l, x# ?8 \6 Q9 j! ~3 ?* H( W1 ]    }3 Z, D1 @4 C4 f

    4 z5 B& k, ]) ?    @Override
    ( e* H; W/ f8 g. w* L    public String toString() {
    / I6 D: E9 A0 c$ g( i% \& [        return "DbConfig{" +, Q2 x7 |7 @8 @$ n! k
                    "url='" + url + '\'' +
    0 ^& _! i0 ?1 u                ", username='" + username + '\'' +
    " J5 M$ h6 b' z9 y                ", password='" + password + '\'' +
    9 C/ Q! c; k) k2 u                '}';7 {$ f" Y3 x# y0 f9 {4 U
        }0 {$ b3 z1 \0 Y  y, M$ L- o- U
    }- f0 ]" |: n* e
    上面重点在于注解@Value注解,注意@Value注解中的2 B8 U5 \, d! S  m
    / w. B& d, ?$ Y- A
    来个测试用例
    . V$ u. z% H8 E+ h
    " J( G; D7 c4 ppackage com.javacode2018.lesson002.demo18;
    # \, P$ s. b: D0 e: G6 g+ Z
    ! r& R! f7 y9 cimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    9 w" e2 [9 K' g6 ^import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ) u8 U) a" a$ v* v& L- e; \import org.junit.Test;( K2 [% i/ g4 }  R2 I% X1 k3 q
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;5 A7 V( i+ w' O" m* f
    8 ~/ B; L  v: g+ ^0 l
    public class ValueTest {$ k7 i1 w! G& K5 e9 F4 H$ [0 C

    8 p9 e/ i' C8 |2 C4 N    @Test" q! W+ D9 `, O/ I" \5 F! i; ~! M' r3 V
        public void test1() {  i% `/ m) e! S
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    " U7 I4 N8 |. y/ R$ U3 Z        context.register(MainConfig1.class);/ Q- \, T9 M2 U+ m7 H1 |7 Q. A; E
            context.refresh();
    , w+ A' f! \$ n# f" }* U& V" N; c9 J- q+ }6 k  v: Q
            DbConfig dbConfig = context.getBean(DbConfig.class);  S: P6 n9 `) Q! z$ ~6 I/ b0 \
            System.out.println(dbConfig);
    3 H6 a! w( F' O8 a) E    }; S" n  V/ E: W: }* U" [3 h5 \
    }1 r# _2 {/ V( M8 f* [; S/ _
    运行输出5 t+ t: }! W( M$ r
    * p7 K' w) }0 S' Z, w1 r: E( u8 f, r% @% W
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}( v" `6 `0 o; J7 }% W, K3 _3 J& P0 k; ]
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。, E8 f7 {0 W  F8 C1 w  R

    6 ]/ l0 P4 c: {@Value数据来源
    ( m( c$ K& T6 f% `. j5 T) @) s, R3 Z& Y6 ^9 f
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。' B. C, V2 m8 _/ G/ K) e
    9 o+ W4 I4 M; u8 ?  z/ s5 n% B
    我们需要先了解一下@Value中数据来源于spring的什么地方。; q& v/ U; }5 B+ F- |1 f
    # p7 l/ _6 M7 U. l) ], X$ v- ]
    spring中有个类
    ' c* ^, o% l. ?' u( q6 a  z8 R3 X9 l: y  L2 Z, t0 C
    org.springframework.core.env.PropertySource1 ^0 X2 K5 P/ T8 k" J( i4 C
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    3 ?% j8 J# R$ U) F5 }) v' d
    . t8 I) g" s( i" g) W内部有个方法:3 T4 c. P! ]8 i; t
    . }  H4 Q0 U0 T& [; K
    public abstract Object getProperty(String name);
    & K6 S$ u( E4 a通过name获取对应的配置信息。2 A" ~0 W# m7 }( ?( P% Y( C
      w9 J) r# p6 A
    系统有个比较重要的接口
    4 N& {, N; ?! f5 o$ A8 h$ b! K- H% [
    org.springframework.core.env.Environment4 _2 }+ i) z+ {  Z
    用来表示环境配置信息,这个接口有几个方法比较重要' d7 W" p0 V" [+ q' Q2 _; _
    : U  C4 J" y$ \) z
    String resolvePlaceholders(String text);" W+ g/ h" Q. }2 c: X5 H; q; R
    MutablePropertySources getPropertySources();
    ! ~1 J* G' Q6 e3 G+ X1 t- W* YresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    $ E( j+ D; N% S  w4 I# C  x8 E! K3 p# N, ?. U4 O; F
    getPropertySources返回MutablePropertySources对象,来看一下这个类3 n- h9 b- C: O/ j8 m% P/ _

    ; k5 d6 _- F. dpublic class MutablePropertySources implements PropertySources {! R# p2 v- Y4 v( o& p6 Z* r4 ~
    ; ?0 A8 ?4 @4 n4 X. |- J1 }
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();% y0 S9 y0 x& b! C- h# N8 {( I" c; A

    % C3 \' S  y. v, h, Q9 C" A}: n: r9 J. Y7 L* Z
    内部包含一个propertySourceList列表。
    * f! T! f1 B7 B, r/ J3 N* E/ q* Y' d- Q- I3 p+ S+ Y& W% J
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    3 m5 G0 |" i+ |& z3 O+ B7 Y9 z
    : F5 Z8 G- \8 Y1 G* Z* y- o( n大家可以捋一下,最终解析@Value的过程:
    * f! M' F& g, {  O2 c3 s' U% m' |# s# I$ y: ~
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析* w4 H- a' g( E* P2 T2 A6 q' G7 A
    2. Environment内部会访问MutablePropertySources来解析
    ! U+ l( F* v6 i% ^3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    . F: r0 Y8 `' z: ~% ^) F, u( [通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    ' _8 q  M* N/ j, W) P# ~( S% g; B* f5 w; Y- U( k
    下面我们就按照这个思路来一个。
    4 d# @7 |+ O+ F, I6 t6 |! |9 d, E7 p" m- r& d1 T
    来个邮件配置信息类,内部使用@Value注入邮件配置信息8 w- P+ D" i1 p7 T, ?' m
    % L: j! ^$ ], \# Y9 x0 f5 K# l
    package com.javacode2018.lesson002.demo18.test2;! I, ^7 ^1 |& N6 o1 O
    & _+ S! B: J' _9 x/ G$ G
    import org.springframework.beans.factory.annotation.Value;
    ; f& w& y! S, K0 B2 ]- ^2 d5 ?import org.springframework.stereotype.Component;( m# G4 ]: d, E9 g2 }
      C6 a$ ~8 J' V+ r7 Z) J* L
    /**+ b7 V4 o4 U& ]
    * 邮件配置信息$ _: W! P9 ]: k# b7 [* G+ n
    */
    ; L' f8 H/ p4 G: L# m% t: A@Component
    , U) l) {' A( W. r: x+ opublic class MailConfig {
    7 _" R! C. v8 o/ V" y) V0 g" x: ?$ b
        @Value("${mail.host}")6 X6 U+ J: F2 c. W) a6 @1 o
        private String host;
    ; _7 G: f* w& [0 E4 `6 H( R9 `  G1 l- ~; q- a
        @Value("${mail.username}")6 g8 i# E8 {+ c( Z. X  x4 v
        private String username;
    + ~' t/ D4 q7 O$ E  n1 v1 y6 Y2 b7 _0 b+ |7 i- F' I
        @Value("${mail.password}")+ o1 h7 a4 ?, j2 m1 R9 h
        private String password;% T* B" Q$ }9 c  |1 u

    - ~; N) M# P/ s/ {, {    public String getHost() {; G; m) c& c9 E8 b# h' y. b
            return host;* [( u3 B4 f7 W2 E* e
        }8 ~' U- j4 Z3 S0 B$ N9 f/ c/ v: W7 h

    ' n, _# L6 R( A! P% f+ ^    public void setHost(String host) {
    2 d, c5 h( D: \        this.host = host;: ~7 w  w6 X5 I; w$ d
        }: z- q/ u* B& D; k

    + `; C  `6 f: \: C7 @* V    public String getUsername() {
    / D* @! G) P5 w        return username;
    1 b, M8 g/ `3 {% t* l4 _# N    }
    0 g) y$ Z8 G" P% A6 q/ @4 f& R0 ]% @: m% ?4 b7 [! ]
        public void setUsername(String username) {3 j. H) b7 ]) U" D) v
            this.username = username;
    " o# C; c! f& g8 q$ L6 r7 q8 X    }
    & Q5 q, I. Z5 h4 R3 b* X0 @
    . ~4 E# J; B) P% U. \    public String getPassword() {
    ' a9 R! V# k! E: g6 U        return password;
    0 s5 a4 F6 s5 ^- G$ Q    }! R% f/ j$ v: z  y' a

    2 J: [) Q9 W: N    public void setPassword(String password) {7 a! E0 I1 X0 X4 I; R+ a) y
            this.password = password;
    5 z9 ^+ N( m$ p5 E    }
    " V, {( v4 N' r1 j2 z
    $ ~8 V4 C8 T8 ]3 ^. i* o% P    @Override5 t* l' `( h: L! g2 r2 T' h1 i  C% J
        public String toString() {
    & s3 k" e* x# N& v. d( ]! g        return "MailConfig{" +
    & {9 k- A) b/ \                "host='" + host + '\'' +" P+ x5 [6 ~; m0 q+ g1 Y" I
                    ", username='" + username + '\'' +% V' f4 t7 M4 r
                    ", password='" + password + '\'' +
    * ^0 @7 b1 {+ {' {. Q& u0 q                '}';
    6 Q: p/ i6 k, _' Y  }$ `( x    }
    2 F8 G& q$ f3 Z0 O) [5 P1 ?& K: ]$ S* x}
    ' o. W- B/ l  d  c, C再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中9 [4 T# ~; j0 v

    $ x! _0 Y% _# L& Ypackage com.javacode2018.lesson002.demo18.test2;
    & u! _* [& t8 y9 ?5 z: t$ e
    3 [4 m  u. i- h5 vimport java.util.HashMap;# q! w6 [$ m( b5 ?
    import java.util.Map;8 a" M" Q* p) E8 G$ m5 q2 p* D, [
    8 @0 V. R% f, @+ U- d) [
    public class DbUtil {
    ) y1 U4 }. o7 W/ u, D1 U    /**
    & D& G' x" L; k, ?     * 模拟从db中获取邮件配置信息& F( D$ B! v* `5 i3 g
         *6 }5 e' l/ n/ G( v- V- p+ `
         * @return1 ^9 E5 m3 u" r( R( @1 W. E# A9 ?
         */+ v& b( r4 W$ R
        public static Map<String, Object> getMailInfoFromDb() {: p. G# ]4 V9 G. J8 A
            Map<String, Object> result = new HashMap<>();
    3 Z5 ]0 b7 U# z; z4 q# U        result.put("mail.host", "smtp.qq.com");
    2 t; @( r+ K& R  T7 d. o        result.put("mail.username", "路人");$ w, U8 o: P# ?6 p8 u4 a; F
            result.put("mail.password", "123");
    0 W3 D* M3 V; s0 f/ R0 E        return result;
    ; e9 g, v% v9 a    }
    2 G0 g% r: }8 M2 t9 h+ ]0 N+ F9 {}0 S) L; e- n1 Y( P" M4 c5 i  f. ?
    来个spring配置类
    " M" ^) C2 ]1 S0 S! i+ |) {! f% w$ w) `" c! [0 {' M9 Q
    package com.javacode2018.lesson002.demo18.test2;
    1 @! X- N; h3 v: F% q9 U+ k! |" D' U( b  J* _; N( ^
    import org.springframework.context.annotation.ComponentScan;
    6 r0 {/ D; F' U. qimport org.springframework.context.annotation.Configuration;$ g- \3 _& I; J4 R, N( H

    ; X5 l- j" X9 Z/ ~. e  n  y) u@Configuration
    % O1 f& O5 D  D, [( t% H9 E0 w0 C5 S@ComponentScan
    : M3 [5 R& m" L5 Vpublic class MainConfig2 {# w% _: y& p& s2 g2 b# o& ?+ c
    }5 B8 b8 @. a$ n5 ^! }' T
    下面是重点代码4 ^) N3 t, V, ?- f0 `, }, J/ C7 ^' F5 k
    & N  n" G, Q# a( r
    @Test8 m9 j7 d0 c# _6 y
    public void test2() {% G0 |& g" i" L( _
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();; t' G$ U! A9 }, ?, w

    0 e+ b; N# F3 O. c    /*下面这段是关键 start*/
    7 C! \1 h- I4 ^9 V    //模拟从db中获取配置信息
    ! T. B# D6 G4 E    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    + C. G9 v5 ]3 S6 x1 E    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    / x8 g. R1 w& X9 f' U  ^6 s: {    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    & ]  D4 j1 b) d% Z0 Z( M    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高  u+ f- t9 Z. E# ?6 d
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);7 J9 }" P, r8 Q
        /*上面这段是关键 end*/
    / u# P$ E, |7 b9 Z
    5 C! b. X# O' o- n% Y( P    context.register(MainConfig2.class);; ~; G+ n  @7 x9 u, P( D+ l/ i
        context.refresh();
    . p% h! ^$ Y# u  F3 K+ L/ x" q    MailConfig mailConfig = context.getBean(MailConfig.class);
    2 e4 D7 P: ?0 h) H7 @5 I    System.out.println(mailConfig);7 `" B4 Q, I( t& A2 Q; Y% O7 W) |
    }7 Y$ b* |4 I( r# B! p
    注释比较详细,就不详细解释了。0 y9 J5 r. u. y  D4 s9 g

    3 g( n1 ?3 m; ?7 L直接运行,看效果5 [$ W( m1 }  j" [8 S- j8 V! g" W

    0 F  K# Y7 y( o8 g: p9 KMailConfig{host='smtp.qq.com', username='路人', password='123'}
    ; ~& Z* r9 M1 n: I3 M. f6 D4 r& n. ?有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。2 n& ?% I5 W9 p7 f5 Q9 _" _

    6 V' V4 r! {4 u7 r6 }上面重点是下面这段代码,大家需要理解
    - K* q1 }+ d5 {0 r8 E! L9 o4 s# L! b. {6 O- r: {6 ^
    /*下面这段是关键 start*/
    8 W; Q! O3 x, e7 Z, A* v& G3 E//模拟从db中获取配置信息
    ! p* ?' C/ Y/ M$ I' W& m- Y% P5 RMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ( P4 z- l$ g) p& P" W//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)! x# Z  o/ ]0 M5 Z7 j: d6 r% i! u
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);+ Q& V% D& n& C; B' W% ^% B
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高# ^( {' ?$ `) R
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);8 c( G/ {7 W. l1 r0 ^/ Z
    /*上面这段是关键 end*/* e! ?. |3 {: g1 Y) M
    咱们继续看下一个问题
    1 g' V1 ~( F3 ~& X& D
    ; x* V" }/ F8 y1 d6 }如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。$ u0 j$ S& {) d
    2 O- e# J- u. G. I
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。0 [+ C1 A. k. r# B' E5 A( ^

    3 ^8 j( I3 [) i+ U) A实现@Value动态刷新4 M3 T  S; Z' x  H# X5 Z
    % M7 B+ M6 E! K
    先了解一个知识点% C0 n8 ^9 ~2 x

    2 u# s  r  z8 I8 ^1 a* r3 O1 L: M这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。4 g8 A5 E* M, L( l% u

    - }; T4 N1 z6 `0 h" ]# t9 P( R这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    + x& v( p+ ^; @7 D
    4 \$ B" O  b, `bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:: b& J- B0 o$ c, @
    + w% V4 b+ F( U8 L5 @* Z6 E$ Y4 m
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    + z7 L$ i+ B# C8 i! F这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中9 D. f" T" T0 ]
    # S/ ^; H& U+ _  I
    public enum ScopedProxyMode {1 T% Y8 S8 Z  ?/ n, K
        DEFAULT,8 u/ @9 c7 g  X" _( k
        NO,) S( A* O! \( d0 }' r# d8 V9 N# |
        INTERFACES,
    4 T6 w" t7 T1 z! L- w1 N    TARGET_CLASS;+ D6 G$ `; z" T/ ^; |, V0 ~
    }
    & D( k& @8 Z; U+ W' Y* B5 I前面3个,不讲了,直接讲最后一个值是干什么的。- O( R( J" o' F3 {8 q1 b

    " C; \! \( I* O: f( t# n( I  W+ g  a当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。3 J9 \  L$ F' e
    5 N! p5 G7 o& @( c3 X' h7 k
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    8 N5 |, V$ D2 z) ~8 A+ C4 q7 U* K4 n! {# a4 P. k0 c
    自定义一个bean作用域的注解
    * l6 G! r; L" |; ^/ g# U
    4 e- u  J) M$ y2 Mpackage com.javacode2018.lesson002.demo18.test3;; Y) k: t8 ^" u1 Y' m; k9 c
    5 y8 Y9 ]# L! G+ y+ `1 ^! ~# M  @
    import org.springframework.context.annotation.Scope;
    , O2 z" J5 A% iimport org.springframework.context.annotation.ScopedProxyMode;" u" R- }( Q: l. F4 |( P! l

    " i- G# \' P; D8 |) {import java.lang.annotation.*;
    4 ]( a; o! U4 a# m) d' w: I% C
    7 ]6 k9 J+ f: l$ ~" N* \$ l@Target({ElementType.TYPE, ElementType.METHOD})
    7 R! |% D" p% n/ \4 O$ _  b* `@Retention(RetentionPolicy.RUNTIME)4 K( ?: D/ R, R$ d' Q+ F
    @Documented
    8 l% o( z0 w: g7 B( n( s0 l7 l@Scope(BeanMyScope.SCOPE_MY) //@1
    5 H+ N7 R4 n% q3 {) p, qpublic @interface MyScope {+ Z5 M- y! b5 l8 h6 z
        /**
    & ?" T. B+ C3 z4 i+ M1 b3 |     * @see Scope#proxyMode()# ^6 D5 r2 O. \' e4 _  N
         */
      W1 ^9 v* ~! k0 d0 y8 S7 F    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    6 i% K5 H8 G4 o# s4 y}
    0 u4 y: g# W" Q2 S8 S5 d) v@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。' w! V3 G! G  k' Z' h

    ) ~6 G8 q0 `1 Z' v" q3 z% r* a@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    / C/ n( I" t; F6 \% C0 _, f
    2 w* T: u% R6 O$ `& `- v' O@MyScope注解对应的Scope实现如下
    7 r- m, U) I, B* ^4 g% q
    ( j2 N- G) \& w2 H4 W- bpackage com.javacode2018.lesson002.demo18.test3;+ h. p( n: u) ]+ g

    3 ^8 D& r8 \" t: K% Yimport org.springframework.beans.factory.ObjectFactory;# Y' C7 X2 R* x0 m" z+ [# S
    import org.springframework.beans.factory.config.Scope;
    7 E% G: l) c. Gimport org.springframework.lang.Nullable;
    ' n1 X7 K  k! k' j8 F8 P  K3 P# l& U" x( ^' C' M3 m
    /**
    8 e9 j/ K" I( e# T# h * @see MyScope 作用域的实现2 w; Y0 u  p  I$ T: U
    */
    5 ~4 r' o8 U5 J. u% @$ R/ }- c& h; Kpublic class BeanMyScope implements Scope {
    # q  ^2 o2 Q9 H' [$ v' M( Q$ [, R
    + B/ m3 Q' ^2 W$ R; @% J% j6 Q    public static final String SCOPE_MY = "my"; //@1& a$ D, `) ], z0 ^$ K# g4 ^. B( O

    , `* h% K3 n& K, Z8 y6 b    @Override
    - z2 T& o2 G. I( y  R/ Y. b6 B' x. q    public Object get(String name, ObjectFactory<?> objectFactory) { 1 C" T$ [: g/ u" X* X
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2% p" r- A5 ?4 Z- C* X* j6 q
            return objectFactory.getObject(); //@3  e, y& l) @" C9 W
        }
    - s( A: a' R$ Y: Y; U" O5 x" x* b
        @Nullable
    6 v3 R6 o9 t) K1 t, f* I% [    @Override1 c8 |2 ?, t9 L. o5 S
        public Object remove(String name) {
    + Y; N1 o! _* f+ E1 Y3 ]        return null;$ J/ F3 O: T6 b" v0 z* b* ]$ G
        }
    0 c0 ^* f* E& v/ E$ e# b. Y
    7 |& \& d9 u$ k2 `9 C% \! I* [    @Override) R0 C3 b2 a, {7 r
        public void registerDestructionCallback(String name, Runnable callback) {
    4 f- |5 R, [" Q' D5 [- b  G: j5 J9 T2 y9 ]9 ^6 x# G! y
        }$ m9 w; x. f! N  R7 H. m9 F

    ) K: D! k1 U- X$ s" q# }- `( J4 j    @Nullable# R& l1 {4 i# b* T; J/ |  j
        @Override# h. B$ D/ f$ O  v4 B
        public Object resolveContextualObject(String key) {$ _# t) J; N( Q! y
            return null;
    9 W- e. K" @/ @, n: v4 m    }
    0 J$ r! ^' c: @" P* O
    8 L& X& m" o4 A) B+ W    @Nullable5 s( F0 o0 ~3 B" M/ Q) [
        @Override
    5 @4 E9 ~: i4 k# V    public String getConversationId() {
    ' o5 ?; T/ f' V1 E$ J' k' p. v0 q        return null;& G# i5 L" l: m  N5 {
        }
    ' X( ]. g! T$ Z}
    & [6 @3 W# k* w5 [1 @@1:定义了一个常量,作为作用域的值
    % e; c0 @" U5 U  N0 `
    5 F+ `6 H! Z  Q( I8 j* ^7 X6 F2 Q@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    ( Z% T! |* d  g9 r* }
    3 W6 l1 h7 h3 M6 J: G@3:通过objectFactory.getObject()获取bean实例返回。
    8 E* }' e0 o- A1 h! Z: X1 @+ [2 z# r6 ~- H
    下面来创建个类,作用域为上面自定义的作用域+ [' B4 Z# ?$ l

    1 R' Y3 W( X6 ?package com.javacode2018.lesson002.demo18.test3;" X9 B; w6 D/ e' Z7 ^/ X" V7 B

    * X! c! u, H7 Z8 rimport org.springframework.stereotype.Component;4 ~3 l5 B& o# o! q% B0 M
    2 M6 u" U4 q; b5 b3 \
    import java.util.UUID;
    8 r' N; U0 j! y$ P2 f2 p% F% _* t! H9 `# U+ l
    @Component
    * D, G6 U$ S8 l% n@MyScope //@1 3 R; [% D2 n2 X  P0 p. U$ J1 b
    public class User {8 r6 I4 N2 J% Y; s% [, Q" l* I
    ( l( e6 B! B$ D
        private String username;7 O$ K" j: N  p  W% [
    & T. ?) G4 \0 J8 i' A
        public User() {
    4 K0 k6 [, f$ ?        System.out.println("---------创建User对象" + this); //@2
    5 x2 ?9 t4 q: K$ F: y) i        this.username = UUID.randomUUID().toString(); //@3
    0 d9 i# N1 B6 z' F: K$ h$ Z    }2 ?8 v' K/ `. F. G

    9 ~4 `6 t4 o4 P    public String getUsername() {* a" ?! o" @% u; A- N( I. y
            return username;
    7 A& ^; U( ~8 ~% p, j; B8 `" l    }
    " X- b8 H; i( O
    4 \* ^5 d5 g5 [. b2 e4 J- \    public void setUsername(String username) {
    1 Z8 ~$ R4 [8 \* N9 a3 u3 t        this.username = username;
    # C& v# x6 p7 [' d    }/ ~! z7 z$ \* [( }

    1 E1 r1 }. G% j+ J}' Q, v. U% B0 ?7 ~( p0 x1 H
    @1:使用了自定义的作用域@MyScope7 C6 v- @' I- ?* w  D% s- s8 w+ U
    : L: B0 S* j* y& R& j% u; \/ ~# O
    @2:构造函数中输出一行日志* h8 ]. m* F, n$ d
    ! R# l  z7 D. c5 N( M
    @3:给username赋值,通过uuid随机生成了一个
    2 A: E. I* r/ I- R" O) c
    7 A5 k) A% Y( j5 S5 I9 b来个spring配置类,加载上面@Compontent标注的组件
    + O& ^' [( |6 j; r! Q" `: O3 a% H4 z; n9 i' {; z) ^
    package com.javacode2018.lesson002.demo18.test3;% s& t- R( D9 M
    ) l- K& \1 ~! s; f4 |4 e- ?
    import org.springframework.context.annotation.ComponentScan;5 _3 ?" ?9 r5 P$ u4 P" M
    import org.springframework.context.annotation.Configuration;) S- W' u, V% F8 e, E% }8 ?% a
    1 |8 u, o7 M6 D. [& g7 D
    @ComponentScan
    ( B, m/ s2 P" s* t3 h: B@Configuration
    : W( `$ Q& T3 q6 v! ~0 D0 bpublic class MainConfig3 {; r% s5 i; e0 l* `: r
    }7 q* z6 A- L/ F. }
    下面重点来了,测试用例0 }7 |' X1 P+ a: j. Y- a0 }

      J$ a! \" ^+ a  X( Y@Test
    % f- n' v5 @" o1 \public void test3() throws InterruptedException {
    ( A& t' Q* a& D; \5 ^" v7 X, H    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    - R  [4 e5 `% c& \6 k$ Z$ p3 v* e    //将自定义作用域注册到spring容器中1 W3 z) w6 N/ G  q9 S  d- w
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@16 c7 w" K! b  i
        context.register(MainConfig3.class);) B! a" Q6 }4 A5 k7 Z( |
        context.refresh();
    - f+ R( L$ C- @2 W' {) W) N* g
    $ R# i) h( F. a; M1 o( n    System.out.println("从容器中获取User对象");% j1 i7 E. |2 [3 ^' Y( F
        User user = context.getBean(User.class); //@29 A' m$ \7 ~& w
        System.out.println("user对象的class为:" + user.getClass()); //@36 T) f$ N5 R6 b6 M

    : s$ ^7 c* B( u    System.out.println("多次调用user的getUsername感受一下效果\n");
    4 T( u5 s' K/ I* ?2 v7 L; Q    for (int i = 1; i <= 3; i++) {3 T6 {; B! g& @0 B
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    1 k; e( P3 M  F5 i' ~        System.out.println(user.getUsername());- j1 l2 r/ q) u/ K; Y3 E
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));9 F4 b3 ]% [7 F8 L7 \8 _5 _
        }/ v) g) v$ s5 L0 Z' K
    }9 U. y# F4 n9 C6 r4 R" V- `3 Z/ C4 f2 b
    @1:将自定义作用域注册到spring容器中7 E9 h% \/ u' H! d

    : R. V2 D3 ?/ v/ m7 b@2:从容器中获取User对应的bean' c2 G' T- ~4 C5 _# X
    2 a9 ^* @  ~& R, p" W' T: q) a
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    & q2 B* j5 I4 q% q0 B9 [* ]0 J" \3 W2 j
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。8 |( L. m: _; }2 t
    + n* q. s6 c" v' ^6 t6 c/ V
    见证奇迹的时候到了,运行输出/ e/ v8 N+ c, ?1 D0 Y3 u
    4 v) \8 V9 O+ @' G- T* o  }
    从容器中获取User对象
    ! u& _/ G: ~3 o/ W9 ~# Yuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331274 z6 ~- |; F5 `2 O+ x9 H* `  J5 x1 @
    多次调用user的getUsername感受一下效果5 s3 @) r" K. Z4 Z

    ' i9 a  X. B; G9 X4 l********
    / N' n  J+ p8 s1 P第1次开始调用getUsername
    8 m! B/ M$ T. P7 C8 x) E, H* }+ f8 pBeanMyScope >>>>>>>>> get:scopedTarget.user
    $ z4 K6 Y2 r" e---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4/ L& L$ `! ?6 s0 Y
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    ; W& K' c) {* x. N6 A: z1 D5 D第1次调用getUsername结束
    * ^" D' U4 c: K/ n6 F********% o. r1 m- R. `. V; ]4 b+ T
    1 R! {! W; d$ l& G
    ********
    6 [, V$ a6 @3 O7 H/ R  o第2次开始调用getUsername1 M- Q% r! \: W
    BeanMyScope >>>>>>>>> get:scopedTarget.user2 U% L3 w4 W- L9 @/ u* u
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b7 d- h2 k0 J! O
    01d67154-95f6-44bb-93ab-05a34abdf51f
    5 t  _3 k0 d9 T' e2 B( i+ {第2次调用getUsername结束
    # S5 n0 }1 }4 l********! c/ }" M6 Q5 M; N' w1 h
    ' q& D$ y# \* w$ L7 B
    ********& }1 C2 n7 ]& Z+ Z1 F; r' K0 D0 s$ P
    第3次开始调用getUsername( X9 }$ B* W( S( Y! _  J/ l8 ?. B( @
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    + Y$ a7 l7 l* J7 J. \* q# ~% @---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    6 H2 _. |: ]- `3 k/ q76d0e86f-8331-4303-aac7-4acce0b258b8
    # V& Z- s6 t) E. m% t! C第3次调用getUsername结束* e' S4 o) M/ F% M
    ********. s5 m7 I$ Y6 F- n" L
    从输出的前2行可以看出:
    9 `) b* A5 u$ ^2 s2 `$ ^# P1 f: D8 h: Z1 [9 P" a3 T: X* L1 P
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    + Q' d1 I4 I  i$ K8 D& l! o) `
    # P& G& a+ v3 f第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。3 H8 @6 S' ?, x1 L" W/ }! s# P! N
    " ]7 a$ r8 g9 V7 t' v8 b' W3 h
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    , w8 j4 W4 X  L  w" @, I
    ( g" k* ]2 {1 f# b$ }通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。- H2 O: F3 r9 b) [

    5 ~+ c. }  k* M/ ?+ T动态刷新@Value具体实现5 G$ l; ?' g: H9 c" C' ?( x

    1 u# G  V2 d/ f8 A那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    ! h! v8 Y3 O1 u+ L! K! o' r& N) A3 o$ V8 w" `/ ]# E" ~
    先来自定义一个Scope:RefreshScope* D6 k+ k0 u+ r7 |
    4 o7 L, ?7 g/ x# b1 R, e
    package com.javacode2018.lesson002.demo18.test4;
    9 x* A) ~5 R: d7 g/ e5 [7 N& n0 n  V7 H' f$ Q/ l
    import org.springframework.context.annotation.Scope;
    + z  j, `3 Q5 X5 r0 j8 [" m7 Q5 C& Jimport org.springframework.context.annotation.ScopedProxyMode;
    $ Z9 p' R! O; B! d, W
    $ Q$ P$ C' l& O: Jimport java.lang.annotation.*;
    8 v2 c+ F  v: t, ?1 x, S9 J( ]; L9 ~' p# ]: J4 z. H7 \
    @Target({ElementType.TYPE, ElementType.METHOD})6 k9 J. g' ^1 ]0 @2 X- q% v# J
    @Retention(RetentionPolicy.RUNTIME)
    # G; K# [& u. ~6 |$ C: z4 ^@Scope(BeanRefreshScope.SCOPE_REFRESH)
    - A* t8 T& H& v# q; m# S8 Z@Documented5 N, i* X" G) h7 j
    public @interface RefreshScope {( c8 V8 x: p/ \/ t* }) B
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1$ S* c# }/ K" M1 X
    }# E0 M' _6 O& M+ U
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置( D& A& Q2 r* o% U7 r) ]; I! D

    3 S7 I$ j$ [3 `( g@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    , ?, j7 |. R6 z, Q
    3 P3 \: o; c+ P5 B" B这个自定义Scope对应的解析类
    % a  g/ w6 n" J: M. M. y* g: v+ X0 |& _+ ]
    下面类中有几个无关的方法去掉了,可以忽略' {6 W+ H7 r% B/ ?/ ^
    + w5 A" P5 z. ~* f1 L3 \  I! L/ V
    package com.javacode2018.lesson002.demo18.test4;
    , s, C, V, P1 |& p8 A) j  [
    & g: B  Y. k  J% \) V
    $ p  ~( l5 r3 y+ ~import org.springframework.beans.factory.ObjectFactory;- Z/ [# g9 a. y9 j- P( ~
    import org.springframework.beans.factory.config.Scope;
    * u0 c, U8 o5 _' q" T& s5 Mimport org.springframework.lang.Nullable;
    $ X8 |' {5 V" ~1 X: ]+ f
    ; E) a' g- B2 q. uimport java.util.concurrent.ConcurrentHashMap;7 p- q0 H3 N1 y
    - c# A; G1 ~, s' \# l3 k- s
    public class BeanRefreshScope implements Scope {
    1 y- [1 {+ ^1 b( y
    1 j( @' C9 r( A( Z, g    public static final String SCOPE_REFRESH = "refresh";
    7 p$ Y+ Y9 G0 l) L6 o
    ; D4 s0 n. q. Q9 A  M0 \    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();9 o1 Z# m* l, t2 n

    + a9 u5 s2 p& P    //来个map用来缓存bean
    3 q/ l0 X, F- r" n    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1$ H- N1 c3 s! W+ s% r' C4 G/ t7 v

    9 l+ }3 w$ N( r7 k    private BeanRefreshScope() {( y; R* y; |! D, n  X
        }
    * L- c) n& @) T$ r) G  w
    6 K% R4 K$ }6 Z) R1 d    public static BeanRefreshScope getInstance() {$ a* m* {3 k0 C7 t  O+ @, l, A
            return INSTANCE;
    & v# E0 d! j: }: f+ O. e    }
    8 h7 q' L# P2 F
    % X' V' z0 a! ]2 c1 G    /**
    + I6 ]. a/ D6 P( l9 k- B     * 清理当前! j" P( }+ z. E# i: J
         */
    6 O! g* X2 y5 c0 x7 x7 S4 n* g7 C    public static void clean() {
    ; Q, g! d, X1 }! W        INSTANCE.beanMap.clear();7 @" N" D+ R5 y  r. ]$ e& J8 K' p
        }
    ) ~( {( n2 |+ O/ V7 ?1 ~
    3 X- u$ k. c! j# y6 C    @Override
    ( N" Z' V! {2 I+ X+ W' k$ t" x    public Object get(String name, ObjectFactory<?> objectFactory) {; [* Y0 M2 T1 v. Q+ z5 R. L, C
            Object bean = beanMap.get(name);! N) v4 A+ h! j1 L
            if (bean == null) {
    , }+ n. W: M9 m            bean = objectFactory.getObject();
    * t, l# b$ y% o; m            beanMap.put(name, bean);
    0 b2 B  l8 i4 r9 O8 x        }; Z9 ^2 G" _+ N
            return bean;3 E; k3 M+ N0 {, t: s
        }
    - X* l6 |$ ~8 d" S! K8 l: Y2 N9 Y5 K# p8 @$ k7 e8 L
    }
    # d) A0 {. K& {; J: i4 x7 o上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中* O; g- d# y3 j& d9 x+ m

    * k8 j" X+ l" ~& R1 h8 c上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ' _; ~; d2 `0 l3 g- r- }8 j( o
    $ y; n, v8 U% {0 H; Q9 q; H来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope  m) W& ]) k1 u, U" t

      Z0 w% M6 \' G4 ~$ spackage com.javacode2018.lesson002.demo18.test4;
    * S  j) @2 l- ~$ E& o8 ?. a) `% s: N$ c: n6 K
    import org.springframework.beans.factory.annotation.Value;
    0 l4 m( e$ ]. \8 ^& i' Limport org.springframework.stereotype.Component;' y! R0 j0 \% b+ X& O5 q! S% t
    " C3 T$ S/ k0 R9 N4 c
    /**& x  z4 w5 @$ \9 r! I/ f4 F
    * 邮件配置信息! u! \% W7 y, g2 x$ ~& a
    */
    4 [0 G6 E9 i0 S. K& @@Component3 F! K, S% \% g% q+ M; v( b
    @RefreshScope //@1
    , \3 E: f/ C& j( B! Jpublic class MailConfig {% |$ ]8 ^& U) d1 q( z1 f" {  `

    3 J4 m0 k" ^4 S; P+ |8 _    @Value("${mail.username}") //@2
    + B1 ]/ A8 J9 t- g8 J0 G0 J2 I4 j$ a    private String username;
    ; k# F& ?6 j2 Q% j
    ; r. K, t8 z5 F  j  Z$ u0 o, j$ Q2 f    public String getUsername() {
    % |6 s, S5 P, Y* D        return username;4 q6 I- P! o' g
        }
    3 p, C' W  \2 t) e
    ! G  Y, u9 @9 y$ U; T( `( Y, Y    public void setUsername(String username) {
    6 L( `+ a+ @6 r+ q5 j, I3 J        this.username = username;7 Q6 I$ T8 a# D9 q7 H
        }
    # C2 X1 Q0 V: A1 S& {: [/ t6 z* x# A$ b. Y  _
        @Override  |) I' g+ L, Z& w/ Z7 a1 w
        public String toString() {
    " m/ H, b$ I' R- R        return "MailConfig{" +! M( r# v* A1 C5 M; I
                    "username='" + username + '\'' +$ x4 r. c, D! U  S, }3 t
                    '}';1 G- k2 [8 k$ M+ _
        }! n) F7 O) [  u/ x- M7 Y
    }
    2 u! ~0 c# X( j4 K1 Z@1:使用了自定义的作用域@RefreshScope" b* V% {% D! f+ S9 N
      [: C9 z( A  \5 C2 e
    @2:通过@Value注入mail.username对一个的值" v: ~# c( u2 z1 U/ W

    8 h1 q$ w8 s% A# x$ T$ H( t( T重写了toString方法,一会测试时候可以看效果。6 s. N$ X1 b+ z: b

    ' n9 V" {  x4 x! `$ g0 ^再来个普通的bean,内部会注入MailConfig
    ( T% D3 t% J; F7 H# q: k, S& Y8 `$ b$ |, A/ f+ R$ R; r
    package com.javacode2018.lesson002.demo18.test4;3 v! {# X: Y7 ^8 O9 f" G5 w

    4 D, f9 B1 s% M5 Q0 m5 p$ j$ d9 dimport org.springframework.beans.factory.annotation.Autowired;: v- {) q* H  n) V
    import org.springframework.stereotype.Component;
    - x1 N3 I: V' {# k- C- r% ]" i  Y! o2 A3 V
    @Component  K0 @! e. t0 [3 k
    public class MailService {
    , z: Z" \; g0 ~2 y, L- n1 G    @Autowired$ i5 W1 ~0 o; W' P; J* ^; t  \  \
        private MailConfig mailConfig;' _  c1 x% Z! E- U/ A, ]$ B! ~

    * d1 `; ?+ }/ U  T) V0 x8 p    @Override1 e* s/ z. o7 `# i# ~
        public String toString() {
    . ]! A# V1 F# w8 I6 U        return "MailService{" +
    0 R) m% A0 f0 z$ ?) W% {& k                "mailConfig=" + mailConfig +, \8 u: E8 V+ m- G/ z
                    '}';
    5 s% B" Q5 c5 a, o' s    }
    0 Z  K" @; O) m4 a* `}
    : d9 C  E' U/ b* ~7 ]代码比较简单,重写了toString方法,一会测试时候可以看效果。
    ) b& R: {( ^; W7 O& a( X5 v+ g8 S* |0 x  V
    来个类,用来从db中获取邮件配置信息4 Z* ]% n8 c0 H2 ]' X& K  V+ S

    1 {/ b% `5 ~  {package com.javacode2018.lesson002.demo18.test4;
    ' g. ~% |" l1 k) C! n7 T! P; t) \5 {1 \: n
    import java.util.HashMap;5 D, _$ z8 S& e, d# {
    import java.util.Map;
    / {: ~' ^  N0 p# Uimport java.util.UUID;
    7 B6 X" U9 Y4 d4 T. _( r- r- W
    4 y4 p9 Q# b4 A& {# Q* zpublic class DbUtil {5 p& M9 w' E) @6 x
        /**
    7 O" V" b3 Y3 [2 x2 x( I: T     * 模拟从db中获取邮件配置信息
    9 P  H0 f8 c0 t, F/ w2 b     *, d( G: }: i& K
         * @return
    . e# z3 ?, _/ u     */
    $ q) p  ~) b$ D* J0 o6 w( h! l    public static Map<String, Object> getMailInfoFromDb() {
    % i) P8 p/ w9 Z& q: q7 |4 w% I        Map<String, Object> result = new HashMap<>();
    9 t8 w: `6 R7 @& D        result.put("mail.username", UUID.randomUUID().toString());  {9 z- h' s+ C: y9 w
            return result;- P" A' Y: \$ T$ L' `( r
        }7 M; B: Z: O4 P3 f3 M( |
    }7 s( H2 }/ M& R% O+ M  }! i
    来个spring配置类,扫描加载上面的组件
    " j% W- U/ g" S$ f- m/ j
      A9 Z" R- L# k# ~4 V4 D* w0 Lpackage com.javacode2018.lesson002.demo18.test4;9 M7 t. _$ D/ k+ W) p- Q1 X
    $ I8 F& r8 q* \$ s& s4 w+ Q. E
    import org.springframework.context.annotation.ComponentScan;
    + Z6 {& z4 U, ]3 `$ {+ v7 t7 @- Uimport org.springframework.context.annotation.Configuration;+ h6 }6 R( ^# e9 i3 u  t6 W

    , j1 l4 o3 l, @@Configuration  e% z/ Z/ V1 M' m& a. E
    @ComponentScan! S* b( f4 ]  z5 A
    public class MainConfig4 {
    , V5 E9 U& ?# O' c/ A  T$ u% q! S}- m7 E; H+ y) J* t4 i% f
    来个工具类
    ) y$ b/ k& f6 B0 G: P: Z1 K1 l& X0 u% h" e8 k* A' ~
    内部有2个方法,如下:
    9 a  ?- t$ }& f, n/ N! k  r. j. w6 F8 j
    package com.javacode2018.lesson002.demo18.test4;
    $ n. T/ S1 Q8 Z" N, m$ o% x, V! j
    6 Z5 P0 Z) Q9 e' `import org.springframework.context.support.AbstractApplicationContext;
    + i: z- K+ @7 V, e& Eimport org.springframework.core.env.MapPropertySource;$ }% x$ [- {4 C, V- S/ E( R
    . h1 F8 m- z: x+ P; U
    import java.util.Map;# X7 l: {3 u% Z. _: V

    / O$ m. g, a9 G6 u1 K& S+ J; S7 Fpublic class RefreshConfigUtil {
    , e% O2 B# s; E" ~( c    /**
    / j4 |9 g1 Y0 f& i     * 模拟改变数据库中都配置信息+ s: E* N0 d) r8 y) e* E& n' g
         */
    5 K# [' M- f) b7 u& R    public static void updateDbConfig(AbstractApplicationContext context) {5 B8 j( H# G$ m! f
            //更新context中的mailPropertySource配置信息
    ( |# P; N: y2 u: C        refreshMailPropertySource(context);8 z9 d0 W5 T8 ?  w# W. E
    # W* E  F( d! R+ i6 g; d( ^- L" l
            //清空BeanRefreshScope中所有bean的缓存
    . z+ v0 L0 X) T6 r9 \        BeanRefreshScope.getInstance().clean();. y+ N& S; }7 ~" \8 `" g1 H/ p) a
        }
    ) |* N  l6 i, d/ N. ]% b3 m/ I9 ]' p- t  G
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    + D% u) j8 |3 W# t) A4 w( L8 B* H        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();) i6 m  W) M, o3 L+ Y/ S( Q
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)5 m" d! J( z7 o( {
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);9 f2 p' k+ F3 @) y: _
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    # c0 X1 ?# U) f* R6 ~- J1 H8 o    }
    * G5 _' a  z/ }4 S2 x
    ( U. m9 O% v# F) X; D( J% i$ l}
    " {) {# Z! T; ]' oupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    , @4 _6 `% r# Y. S
    7 N/ ]  |* }/ W$ JBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。  A/ _3 e( v6 z+ o) |
    / E6 Y5 h8 e0 S0 q! C/ b3 F" S  k
    来个测试用例6 Y6 }" Y* R  K1 ~% s
    6 H7 v/ N# Y" |+ }& B9 ^
    @Test+ L  \+ }  b! w/ o" U
    public void test4() throws InterruptedException {
    8 w5 G8 J2 i, g' R    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();% P( V' e) u, d9 m& _
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());1 [: ^- s9 Q/ t) U1 n) R1 \/ Q. ^' E! \
        context.register(MainConfig4.class);
    & S' h2 G" |( S4 V0 q    //刷新mail的配置到Environment
    ! c# h, [; F3 }# O+ _    RefreshConfigUtil.refreshMailPropertySource(context);: Z; l9 R! W' s8 U  y1 q, N
        context.refresh();; j1 T5 E8 l/ ^! h

    7 F+ h5 A5 O$ K; Z! {    MailService mailService = context.getBean(MailService.class);
    5 Y7 C" u; i; [, g# W    System.out.println("配置未更新的情况下,输出3次");
    2 f  \9 Q" I& @    for (int i = 0; i < 3; i++) { //@1' ?% b% }) T4 [% x
            System.out.println(mailService);
    6 D8 k7 E8 m: l, Q( n        TimeUnit.MILLISECONDS.sleep(200);
    + o+ L( u5 @6 ^) {! h    }
    0 I: ]* T& f7 ]' _4 i& Z8 @# J- x& n4 B4 B0 R1 Q3 p1 j
        System.out.println("模拟3次更新配置效果");
      A* N  C: n& {* t( g) f+ [8 ^    for (int i = 0; i < 3; i++) { //@2
    + d" H1 S" n1 r        RefreshConfigUtil.updateDbConfig(context); //@3
    + c. w- l: b6 M7 g3 f& y        System.out.println(mailService);( B( W5 C( b" ?1 M1 [
            TimeUnit.MILLISECONDS.sleep(200);
    + b" [; u7 G) r9 z9 \    }4 H+ A  p/ t+ n, n! M
    }
    ; l# U, A. ?" N2 e@1:循环3次,输出mailService的信息
    $ c8 u5 S* Y+ d: Y% }8 S
    " C! S, E: z9 o4 z. T@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息- W7 _/ y6 w' `+ M
    * H8 L/ u! K# t# V
    见证奇迹的时刻,来看效果( ]% r) R" E4 w) e% T$ x# [1 F" K
    % p. P( k9 S2 K% r8 n
    配置未更新的情况下,输出3次
      r( H, J2 x4 c4 d8 tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    0 |5 x1 Y" m' {: J9 UMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}) `4 N$ Q- C8 l$ e; V# M5 r
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}0 X: ~$ _" T+ V/ H7 ^
    模拟3次更新配置效果/ W4 \' F; U6 v0 d7 q7 b) q2 Q, W
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}% }& O( m7 v0 N* A7 C
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    # I7 [& u4 z% ~: aMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    : J, G$ e4 [/ m+ i3 _上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    4 g0 j% F1 f+ N- a7 {, I8 w! J$ `, x4 E5 q( B4 }% V! ?
    小结# \3 G; W" y5 x7 q

    " U: v" v9 T  p2 Q- b% \动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。% c- S$ {9 L  ?% V. t1 l2 R* e2 h

    . v* L0 r" I# g$ ?7 `* r: f有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    3 D' W8 P; h" T  Z0 G; E- g- T3 G. ]
    总结  [7 `5 p+ K1 [6 C, y7 Z
    . \% e/ Y1 m5 z( g6 M+ U2 Q
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!8 K9 r. Q/ M, K+ o0 Y

    4 P7 ~1 B: ~' W6 c! Y" i案例源码! i( L- P: ~9 d% Y4 c0 T% c
    8 u" a; u& I4 h3 T& B5 i! z% f% k- |
    https://gitee.com/javacode2018/spring-series7 ~: g5 v* e) }: J  a
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
      L. K- U) N4 a( i+ G% P) D! B, v; `, b8 c  C; c
    Spring系列
    * d+ P, G( ~# G, n
    5 |  \, N, x1 C' [Spring系列第1篇:为何要学spring?
    ; t& v! P# X* E; O; n2 K1 e
    $ q3 h. O* ]) j) YSpring系列第2篇:控制反转(IoC)与依赖注入(DI)
    0 y" R' Y- P- c0 j9 i9 e# T" V# `% c% P' ?5 ~5 K5 i1 P+ k  Z, `
    Spring系列第3篇:Spring容器基本使用及原理: t: C& J9 g8 N+ X# ~; |
    . l( l) N0 ~: _$ G( O# u
    Spring系列第4篇:xml中bean定义详解(-); h6 P1 F5 A/ p% e0 ^
    & }) q5 m8 a6 D, r2 h; A3 a. R
    Spring系列第5篇:创建bean实例这些方式你们都知道?( Y6 c; N: P) i2 ?0 D! J
    3 G: Q. s3 }) z
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    ; m5 i3 s8 S% @( f* U6 p) F: A4 r1 ?" m2 N& p0 t
    Spring系列第7篇:依赖注入之手动注入# q. ^1 L0 j  Y3 X9 l+ o. ~: W  W
    6 A1 H& O/ Y+ M) j# W! {1 Z/ d; ?
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持& A# \# {4 E% W# j
    * x& V8 d0 G' B  {% r5 `
    Spring系列第9篇:depend-on到底是干什么的?
    : {2 o% o- p5 W6 n/ K
    7 r' H& ?( O( @3 W- \/ N7 {' r- e8 OSpring系列第10篇:primary可以解决什么问题?/ A* p# X# {1 C" q1 B
    * @' r. U0 W) P% a$ O
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?) d/ u& l  n& x4 `
    & e- j& p! E6 \  U  y- q
    Spring系列第12篇:lazy-init:bean延迟初始化
    2 _$ ^6 \7 K# w5 q3 y& M! Z5 {% M% U+ k/ D7 b
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)% h  A- m$ G' u+ A

    # ~+ F$ Z% u0 @2 e) E+ PSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?4 @! C$ J; R5 X$ \6 m# j) m

    $ G! S$ Q" L* @3 r8 E+ t' F  DSpring系列第15篇:代理详解(Java动态代理&cglib代理)?  v9 N+ b& \' w, j" w! G

    ' b/ u! l5 J# N& @' Z( ]/ ISpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    9 Y1 O$ W1 w4 E9 G" S7 w' c; r/ X9 [+ \- Y6 e2 P
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    3 R0 a7 {- g9 a$ M  h  a6 {* `1 }: c2 S& V! V, [
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    5 d& b! q! j: K; r# O3 h% g& P% A5 x6 q& K! `6 _
    Spring系列第18篇:@import详解(bean批量注册)
    ' P$ Q  c2 d! r" o5 L' l7 v* ^% T+ Y& S- N; Q" {' z5 ]$ E
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    8 B& g7 ]. X/ m& U, k3 p; B) h' J& D" B. b+ ]; `( v7 m) O6 B* R, b+ V
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    # e  e8 D% \1 f; N" J  f# t3 L" v6 f& y' p. W+ n+ E4 t+ U; b
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解* q2 f3 u! V0 L  M+ k  m

      l! v4 G. A. m5 hSpring系列第23篇:Bean生命周期详解4 L( M* _, K( s/ n% v& e
    . o/ H0 e4 ^  }8 L5 r- g) t* ]
    Spring系列第24篇:父子容器详解
    + C; `2 r2 A1 f) ^7 q& P3 U$ b0 f; Q9 i; g/ x- ?
    更多好文章. C3 D% Y+ `3 L! P( i5 }! m

    # H" e$ R. `3 Q6 j9 ^Java高并发系列(共34篇)/ j9 b- ^* v, ~( k, I7 y
    . h" f4 d9 b+ N. C' j% a$ ?6 R
    MySql高手系列(共27篇)4 y% z9 }5 C! P0 C6 ^
    / E5 o, E( @' Z) `: \
    Maven高手系列(共10篇)
    " t& A6 j% n" z' l4 Z5 q$ d* S6 f- V9 I: r" q2 r  B
    Mybatis系列(共12篇)8 L- ^* U4 u6 y) j

    6 F' @! J0 D  W  }聊聊db和缓存一致性常见的实现方式
    " N3 t$ h! `) d5 f' @
    2 g2 a6 c" G0 b! ~' n接口幂等性这么重要,它是什么?怎么实现?) r7 B* Q* }2 N2 ~1 G/ @

    : w, h1 z' Z# [7 ?6 v泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!) |9 m4 r  J1 A; }
    ————————————————/ }% [# T: D# V( R
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    7 W; K/ [% `: g4 A原文链接:https://blog.csdn.net/likun557/article/details/1056487578 o+ U( s* c# e* U5 S
    2 j* T6 `1 d5 n

    ! w% q/ Y$ }! F% a8 F7 E" R' h
    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-9-24 17:45 , Processed in 0.517012 second(s), 51 queries .

    回顶部