QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5380|回复: 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!4 e0 `8 V6 M+ v
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    7 b3 e2 m% s! G/ y. s* @: i. ]! g, ?8 j9 }9 Q7 z
    面试官:Spring中的@Value用过么,介绍一下* ^/ _; U: k) T: f) A; S# s

    5 m& p* j8 B0 D# F7 L  O我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    2 p( K3 @  ^* z% k! J' H4 w+ V
    7 C  [, s& G5 h% j* _2 S% Y! N  Z0 G面试官:那就是说@Value的数据来源于配置文件了?  h" g0 s6 i5 ?6 n

    6 m, M6 w, z5 r2 T, {我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ! m% o8 e: T* H, c
    + z4 J5 r7 E7 H8 p6 a面试官:@Value数据来源还有其他方式么?
    ; y( E) Y/ c5 ]3 ~8 m/ z+ c% A7 I! N* j9 O0 `
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    / p: B( B, H. o. l1 e$ r0 D4 k9 ]6 c5 B+ r  Y# i4 H! Q; I
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?0 Y1 h2 X: T% s% |+ b! I
    9 X: w( ~+ g4 o6 N0 l9 E+ {3 M
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧! c7 l( O5 l; v( r, h

    . Y2 d6 B1 Z) f6 ^面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?* o+ {0 a, B; C# O# E% b8 G2 a

    5 M! `6 }9 X6 y7 L- m7 f& |' [我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    + C0 S$ Z, a% \8 E; N; h3 P& l8 [$ {
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?8 Q9 p1 h$ R' a! Y( _

    5 V9 b# ?' ?2 L  ?( `; P, s我:嗯。。。这个之前看过一点,不过没有看懂7 y+ F6 V0 j' ?

    ' R* T  s2 [3 ?2 \* R面试官:没关系,你可以回去了再研究一下;你期望工资多少?$ K$ k, ^& y! [0 b+ C
    : p# {3 a, b4 W
    我:3万吧4 f6 w3 |" ?# }5 _/ m  V  K
    & i2 e# P7 ^- r* d! |+ s
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?) V! i# p3 ?0 [) {- b, E

    5 d+ [" s3 j/ P6 i' \9 @我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    6 G$ V4 [5 T' T2 `! |! ~
    ) }; v+ c9 _, s/ |2 s! Z2 @' v面试官:那谢谢你,今天面试就到这里,出门右拐,不送!7 G8 }8 B3 S  |2 f- |$ L

    / V+ q/ c6 K7 Z3 p$ O我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    " C" g+ M7 f- O$ @5 w
    2 D! P! D8 b- G5 ]8 o3 u. x这次面试问题如下
    3 M3 q# h$ `9 H; K
    - V' O0 _! m4 v2 A$ S; J, N@Value的用法  {' D& ]& {! F* c
    ' O# v% n' M) X! b
    @Value数据来源
    * l! s$ y, H- z) b% [! v' l6 |3 }$ J
    @Value动态刷新的问题
    7 T/ N5 y  u6 X0 Z6 |1 q4 V% k; U: w6 a; T( ]- l) `2 H' I
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    8 E: l9 p* C" J: f5 }! K: m$ i# _1 S/ R$ j* T
    @Value的用法
    . `7 W$ C8 _" U; V3 v5 }* b* K
    ! O+ W8 v% i' P: O2 B系统中需要连接db,连接db有很多配置信息。
    ( V; ?" Q% V% S3 L4 O  K1 @' Z+ j* r2 ]
    7 w( \) V. s0 x) @- p5 `" a# Y系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    1 \$ u& _& b( A& X4 Z9 N7 v1 k! M/ V# P* L8 w: D
    还有其他的一些配置信息。
    5 J! n5 K; R3 Q) {. M- z$ o' y; S2 p* x( h1 z- m
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。2 C- I) X2 `/ L
    , n5 e5 z1 V- q: K" C
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    4 h3 B4 D9 ^. g6 @+ a2 i# ]4 G0 x2 E- [
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    1 m* z! c+ V+ s0 I
    9 f* ^* y) @- Y) P- |% t通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    ; b4 L, r" L" m$ f& [. a# Q. N; d7 p
    @Value使用步骤
    - g: Z* q8 Y0 |" }2 {: \1 m' ]: X" {9 N; q$ P# {$ v$ u- m
    步骤一:使用@PropertySource注解引入配置文件$ f. o5 u& l. w
      ~( o) O8 m/ X- w+ t
    将@PropertySource放在类上面,如下
      x: p, ~. k* c7 A
    ; b( |$ T: l6 U5 P9 H5 D@PropertySource({"配置文件路径1","配置文件路径2"...})
    $ w+ L  Z3 A8 U1 l@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
      C' ?. ^. O6 |: g" K' ^5 j5 n8 Q. G1 q, V8 s# ~; r3 O
    如:
    4 f, P1 t6 Y, e4 w% r% n5 T* k. b$ j2 X
    @Component7 w2 z5 J0 F  p
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})# n1 o0 \. ]" y: I( h8 L
    public class DbConfig {
    9 s$ a: H' i) k- `+ b4 r}2 h' g0 x5 O- j- Z7 D# R7 j
    步骤二:使用@Value注解引用配置文件的值, `$ D% v$ v  K( X7 a: L4 R1 q8 N

    0 j$ ~2 O. C3 E3 t" t/ r) C2 t$ M通过@Value引用上面配置文件中的值:% ~1 l  [! `% w8 H* D
    % U2 k* }9 c( t( o+ H5 _0 m3 V+ e+ {. S6 r
    语法5 y- g4 x9 X4 G+ X5 c/ J

    7 k$ Y2 T. u; {# R" U/ ]7 r@Value("${配置文件中的key:默认值}")1 f+ U5 {0 X9 Y7 U2 i) Q8 U& e# ^
    @Value("${配置文件中的key}")
    ; Z- c9 s( g8 U5 k( W- o! X- `0 ?如:: z, w! O/ b; t  s

    7 [8 j, ]- x4 A. ?' d@Value("${password:123}")
    . c+ X9 {; z) f9 M2 F9 _3 p9 f上面如果password不存在,将123作为值
    . C  N( ^% ^4 r
    2 e( t/ D2 X- Q& w; f" l9 p0 u@Value("${password}")1 ]" w" m, N7 n2 @
    上面如果password不存在,值为${password}7 [/ K1 P( r+ c5 \5 l9 S

    1 K# r! r  `# h+ \% P假如配置文件如下
    . \1 C0 S# a& H' o
    1 t# u( `& l1 [5 p2 h. q6 a4 Ajdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8  J. n' M0 {( g. }3 K/ h
    jdbc.username=javacode) u! ]4 ~2 m; o  a# J/ e
    jdbc.password=javacode
    $ C% A1 w- s# z: C7 c1 t使用方式如下:3 o0 A8 p& E7 D5 X
    / b  d- M1 r+ G- l& m- `0 @
    @Value("${jdbc.url}")
    9 h' s3 C$ L- r0 r4 P! j- Xprivate String url;
    1 e( p$ d1 J; |2 A
    ) h3 I) L# w3 n, l@Value("${jdbc.username}")
    & @" z# X# N' N1 T. P5 wprivate String username;$ D" v+ g* ^4 X5 n/ T/ x
    ! _( Y9 I5 x2 K; L% y9 X
    @Value("${jdbc.password}")4 o/ c6 Z6 g+ g/ N5 t& a- s
    private String password;  @/ E3 w4 p- @  b& h1 ?
    下面来看案例
    " p$ h3 Y% r1 m5 `. D1 g- _1 ~- T( M) z; Y8 [7 x+ x9 F1 [
    案例
    0 Y2 G6 L. d: T" @$ w" D: q6 x4 H, b/ ~
    来个配置文件db.properties
    ; f, n. c, Y4 h: n( O
    2 t& k; n% x6 Z; F' Pjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    1 @+ j+ o" b1 W) k4 h6 `0 N8 _% [jdbc.username=javacode( `# x: S9 ~2 ?
    jdbc.password=javacode
    0 h9 b7 v6 O- z! O8 R4 W& P* `! T来个配置类,使用@PropertySource引入上面的配置文件
    . E- ^8 f! {: O% j# v! ]; b
    + o: B; }) M7 u2 Upackage com.javacode2018.lesson002.demo18.test1;1 _$ d5 f4 P1 @% B8 n
    0 {, K: P$ C+ E" }
    import org.springframework.beans.factory.annotation.Configurable;
    # J; s; I/ z0 S- Z$ J+ T. y" Jimport org.springframework.context.annotation.ComponentScan;
    5 w( j. g4 F/ b& O) r/ Z3 T7 Dimport org.springframework.context.annotation.PropertySource;
    3 o; k6 E& h3 f7 B  J1 ]' o
    3 ?1 V. n1 @4 `$ W% U@Configurable
      p1 o! {. {% y' U2 I1 I@ComponentScan1 T( R. w# V+ X0 P; w8 k4 {4 h: j
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})) e; `5 H" I8 S7 `2 X' J
    public class MainConfig1 {
      b$ g" N! f3 Q8 O( f# b! I( S4 C5 M- i}1 z6 k% t5 g, P: a( `$ i( f
    来个类,使用@Value来使用配置文件中的信息
    ) r* d% Q( G# B0 P% b
    1 p4 Y7 n, M" N5 v' rpackage com.javacode2018.lesson002.demo18.test1;/ C$ {6 Z2 k5 a

    9 Z3 l% F/ ~# w9 E3 ~import org.springframework.beans.factory.annotation.Value;4 I0 u5 u( Q# }( |5 B
    import org.springframework.stereotype.Component;
    / a7 j9 @( ^- ~8 i4 L
    $ j/ o" ~% P" x& D* E) n, }@Component
    # ?& |5 I# d$ U1 d+ Jpublic class DbConfig {
    1 o% l3 U( p$ u7 r. `
    ; u6 y8 K2 N) l) p    @Value("${jdbc.url}")  D7 j+ z6 D6 e7 f) a5 O
        private String url;
    - I* B& z% Y" ?1 w# D  x( d4 S1 M0 {  t
    & z3 ]! u% O' W$ p/ }1 |9 Y    @Value("${jdbc.username}")$ o5 f8 D' V! z/ u
        private String username;
      @$ {9 p# D2 h" B) ?7 b9 x: y4 l/ @7 E& l# `7 i
        @Value("${jdbc.password}")3 l0 ?: m1 R& I- s6 ]0 F  ?, v
        private String password;
    * t  S/ w* J* Y8 ?4 Y2 x* o+ L5 @4 c1 U( I
        public String getUrl() {$ p+ C9 v# p' G5 I
            return url;
    2 u1 C/ o, g0 B$ h; k; M& s    }
    1 f2 O4 K& O9 K: ^2 }2 R  G
    2 [- ?# ]+ C/ u- e4 ^    public void setUrl(String url) {1 ]1 O. z! O; z4 o7 a
            this.url = url;# ~* B& i- T! j* ~; l$ z
        }
    0 ]# m6 F- o% Q/ c. u8 f
    : M& e+ w6 d3 `# T0 P9 H/ T( |    public String getUsername() {9 B2 O2 H$ Y7 k  k9 H) M
            return username;2 H( s  O/ w* F4 H4 Z1 e9 Z
        }- F1 M8 I7 M8 B6 O8 A" }- t! V

    ) [% f' t9 ]0 a9 d% O3 e    public void setUsername(String username) {
    1 |5 N3 U+ G( s6 Z  y5 g& U        this.username = username;. |) [5 p" w& S/ ]1 s. T- h
        }& Q% s; W! t2 f2 r- W3 `1 K

    ; Y# G# z2 W4 t" \! C% D    public String getPassword() {% r% ~+ Q" T& h  E" H3 N6 n  F7 |8 I$ e
            return password;; U7 ~; o% B5 X* |3 g0 R
        }
    ) L3 `$ y; H; Q* Q, U9 V
    * T# g* U4 G$ ~    public void setPassword(String password) {- j  g& @' _! n! Q% q
            this.password = password;
    ; }& i) }2 W7 k7 V# i$ E: I: z/ I    }
    3 N$ ^6 s7 C, D" \9 w/ s; T. A
    / t- Z1 K* h1 F' a; J" z    @Override
    # _3 R* G& C+ C4 F" e    public String toString() {9 J& X, @( q9 M) r
            return "DbConfig{" +
    , H7 O* w: F+ W4 w                "url='" + url + '\'' +2 b3 X3 b% m4 W$ }( M
                    ", username='" + username + '\'' +; {, n# \5 g' `2 m- N% i0 H
                    ", password='" + password + '\'' +; ~. f: D2 W' X0 l. R  |
                    '}';, t( l9 P% K1 Q1 w4 D2 ~! K
        }
    0 t9 M( O6 g) D& H1 v3 S' m}; T0 N# _4 t3 [% K4 |, V( ?2 i
    上面重点在于注解@Value注解,注意@Value注解中的
    " w2 w$ b6 z# F. D8 c! i
    ! R+ ~* P* K8 ~1 _来个测试用例
    * E* t% m& q, l  A6 t. g0 V7 v5 l
    " l7 k; R* ^. v( m0 a* n, bpackage com.javacode2018.lesson002.demo18;
    " P( e# u$ f" ?/ B# [: J4 _7 R
    1 T. p5 P& a( ], h# R/ I0 r( x+ q- gimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    8 g' x% c7 u$ s) a" F& kimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    9 ^9 q5 r, h% Kimport org.junit.Test;0 T- M" A* O  X
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    % m" [  [2 ^2 J  ]7 f7 l( `- @* T
    1 c6 P4 T3 W3 i2 bpublic class ValueTest {
    7 J' [2 K- `, i" {; I
    9 K- L9 c" r: A4 n+ v7 E% {    @Test+ N2 a/ B' d& ~6 M/ w. G  c0 U
        public void test1() {
    3 S! A) S7 W% g: h        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();' w" P- A: \" b3 @9 S
            context.register(MainConfig1.class);6 t& Q" }$ v& c* ~
            context.refresh();
    0 C/ r& Y4 \# N2 ?9 w: m; X# o1 G# z$ s
            DbConfig dbConfig = context.getBean(DbConfig.class);" Q" e7 T! \6 f  ?9 v( U) m
            System.out.println(dbConfig);
    2 Y% o( h3 ^4 c  D" J    }, q: F; c" i+ Z; B1 G% v" X9 u
    }
    ' [$ u; u/ V, Z# ]6 {! a# [4 V- j, P运行输出
    - V8 ~1 X( D+ \( }' b( R: N0 w$ @% `9 g& ~
    6 H* T6 V* v/ nDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    ( H9 s, d1 _: m8 x7 j! c9 ~  x! A上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    " U% [  i8 s# [' u, O- B4 |" Q7 |+ F# `' a
    @Value数据来源' t% w7 W* n* F$ J  r
    : s" X( Y" f7 o( f0 h# N+ {: P% Q, Z
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    6 X2 u1 _' b% U% v* t
    ! a2 J  o* e$ ~: u我们需要先了解一下@Value中数据来源于spring的什么地方。
    , o0 j; u+ m! m9 w) d0 d; |* I: y5 y2 P0 X, J8 A
    spring中有个类
    8 H* d  K+ n# R4 E& C# J( W" f, Y) L2 ]* n
    org.springframework.core.env.PropertySource
    9 s; v- D9 a! T% s/ s! q可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    + V% w5 w6 X1 L% y! P" {$ _1 l: t" G
    $ N- C: N7 k% s内部有个方法:
    3 }& i5 g8 M  Z& J2 n  ^8 ~0 g: [3 i: ^/ s; x! T& \# K) b
    public abstract Object getProperty(String name);
    7 {$ `5 x& n' A: N" v1 J1 }" P( k通过name获取对应的配置信息。
    & e" O1 A. P( S. Y
    / v" b- A! U( i* D  S% X7 @系统有个比较重要的接口
    " [8 {* q9 J) m% }1 W  v! e
    2 s* m9 {% b! j% H2 y# Torg.springframework.core.env.Environment1 z% _* g, h4 `- N* [
    用来表示环境配置信息,这个接口有几个方法比较重要
    0 \$ |" s3 L* S1 v  _/ p' s
    . W! ]1 A8 E) R6 _. K$ [  HString resolvePlaceholders(String text);
    2 j) G) F; S2 e+ n6 `- s3 oMutablePropertySources getPropertySources();
    7 ?" B6 a  H, q' Q5 l4 u4 ?resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。( a: @+ D' l  z3 Z3 y( g

    % A" M! {0 T$ B& v: ]getPropertySources返回MutablePropertySources对象,来看一下这个类
    - z; N' N! Y! h7 o- E6 ^& b, X
    $ Q$ F" f% T! g2 a& @public class MutablePropertySources implements PropertySources {
    1 i  A4 i* `- b8 H7 o1 x+ F0 s% ~4 C# h/ M! S' r
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    - v: A# r- r1 u+ g; @9 w
    ) `. I* r3 @) A! j, O}
    / a6 [+ z( \* U! R; e) m$ I内部包含一个propertySourceList列表。) \, s$ Y1 h% W4 E

    " w3 a, p( F- ~( dspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。  t1 K" [5 l: z2 I. L

    & H7 o" o- r+ b+ y4 ~大家可以捋一下,最终解析@Value的过程:" C8 a' a* a6 ]! ^2 ?7 i

    & l- A$ T% \# A5 J+ K8 D1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    7 Z# |: Z7 e# ]9 x% M  Y2. Environment内部会访问MutablePropertySources来解析
    " I  Z1 S0 U$ C/ W& n4 P3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值8 _% r( \% ^' G
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。  r3 @# B$ X+ M; u" g

    9 j& Q  Y2 ~( T9 q' a; z下面我们就按照这个思路来一个。+ l7 u9 f5 i# I3 I9 e/ h! M

    7 Q7 |& J7 _  B# \来个邮件配置信息类,内部使用@Value注入邮件配置信息" _2 v7 t: A  D4 a3 h& ^
    3 s+ t+ L$ N9 T
    package com.javacode2018.lesson002.demo18.test2;: e0 |6 q. g: u' z
    ; ?9 Y7 G$ q0 @# }
    import org.springframework.beans.factory.annotation.Value;9 S9 k3 @- `# D3 k6 d1 c
    import org.springframework.stereotype.Component;
    - j* ^# M. s2 w3 S5 A& j1 q/ a, }- \. W0 E& J9 b2 \& {
    /**
    ! |3 y/ K% u5 K2 ~, k * 邮件配置信息
    ( K- U6 J: F) b- W */6 t* }' o- O3 l6 e3 \' M9 e
    @Component
    & A% j- q) h1 V6 x; B2 `public class MailConfig {( }8 V" s! Q# _+ m( {

    ( ^$ K9 L: m* q9 i( G/ r' [    @Value("${mail.host}")
    - U( {4 M  i5 U0 p! _% c    private String host;
    9 y: W1 F" g/ E: W; I1 E
    ' I" z" Z2 V5 l* C1 b    @Value("${mail.username}")7 j- t- M5 R1 H* O" R3 y6 s0 E1 A4 A, k
        private String username;
    ; D: f- C3 ~2 l
    . z; c  o( `' ^  v. {    @Value("${mail.password}")
    0 c" f: d/ C, Z4 i! J    private String password;
      I4 e3 n& z# g
    $ h0 J4 o8 F3 Z7 u! V    public String getHost() {
    4 q  O% o* z( {4 z. {" J+ B        return host;( B# [; s' D+ i
        }5 h; W/ A# Z, H) y( r, Y: E
      D, V# E3 y5 s% k  ~4 V
        public void setHost(String host) {
    + ]" i' L# L3 I* ~        this.host = host;
    , g* e1 L. \; J+ G    }8 G$ v; f6 R+ L9 m

    3 \  p% X. Q/ p- e0 P) d. h8 Y    public String getUsername() {
    + G, \5 y7 F$ ^        return username;1 W5 S2 \; I0 Z2 _0 W7 L
        }; F4 W+ \& i) |$ l( {+ w4 c5 m
    ; |5 ?  C5 b! m  k
        public void setUsername(String username) {
    1 r% ~& Q3 D3 E2 `4 d/ H8 J8 |9 |        this.username = username;  U" a( X6 j) p2 F7 m; x& Z/ q
        }% {- ?" B6 x6 D4 D8 W5 \/ J, q

    1 m2 v- a+ w% i( T! o    public String getPassword() {0 l9 ]1 u: U! X5 {6 K$ Y
            return password;
    + E% U/ P9 F& S) c' l5 J    }' r. F6 ?5 |# X

    : B3 c1 N% H: g, |3 }0 f  [    public void setPassword(String password) {
    ; o& O1 g: X- Y4 D2 R- e        this.password = password;7 ?% q' D/ b: N) {
        }
    5 ]- Q& v7 r/ l9 \  x5 a( w& y" X6 Y7 z4 t. K! S1 ~+ u. z& o6 u, w0 T
        @Override& K8 l( m" f! m6 h  r
        public String toString() {% f. t9 {% k, [1 c
            return "MailConfig{" +9 i  Z" h" ]% ?
                    "host='" + host + '\'' ++ a. P9 V5 ]- y' l% v, M3 t- B
                    ", username='" + username + '\'' +
    , ?2 v$ y& [4 N0 d5 C' `+ p" m* d                ", password='" + password + '\'' +
    - R, }$ X: x7 [( w5 z  X8 b                '}';( ?+ N: B  F5 m
        }/ [1 y) E* B* D
    }
    ; m# b* T, p4 c7 Y再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中. w1 U1 z% s3 b4 X
    & |* f3 J9 U# }8 V5 p0 {
    package com.javacode2018.lesson002.demo18.test2;
    4 v5 N3 _0 f; y2 V; h. p: Q
    & H% U/ Y5 V* {; w, Iimport java.util.HashMap;
    1 f# Z9 D4 l' c4 |import java.util.Map;
    & s( [+ p& ^  k! C
    & m1 y. _' Q' u7 J7 x! ?public class DbUtil {' q! q$ M8 L! ?& [) h- R( T
        /**
    # y5 n! \/ O" K: I     * 模拟从db中获取邮件配置信息
    0 e+ ]; A% H9 K$ C, e2 b. D% L     ** Z7 I6 ]4 A- H# ^7 t  V
         * @return/ Q& M0 h, F) y% r; i
         */+ U; S! M" w# h
        public static Map<String, Object> getMailInfoFromDb() {/ R, Z4 j! k! p" |4 F! x
            Map<String, Object> result = new HashMap<>();) i7 T% M6 b, Q/ C/ @( g0 ?; I
            result.put("mail.host", "smtp.qq.com");
    1 {- C6 H5 u, A" w0 {% G+ ~        result.put("mail.username", "路人");
    " q5 b7 U9 F- \# U        result.put("mail.password", "123");' l+ _* V& j2 T2 V0 n. L
            return result;3 r, o+ `1 u1 u* c$ S
        }9 t; Z6 W7 g: t9 }; M5 Z
    }
    ! c2 \7 ~0 V9 M- y来个spring配置类9 L, |. \) u6 V8 {, O" l+ T

    ( ~/ H0 R/ |2 L9 m, Opackage com.javacode2018.lesson002.demo18.test2;+ u" b2 j* g" y9 H, [
    ' i% [  k1 ]' }, `
    import org.springframework.context.annotation.ComponentScan;8 p- P: `9 a- V, J9 D% T7 P% |
    import org.springframework.context.annotation.Configuration;
      k  v0 r" d) n) |$ P% L% c/ R1 I) ^8 A2 F+ G+ h$ e4 |
    @Configuration
    5 F& [0 E6 T$ F! u5 P@ComponentScan
    $ @: i& ?; m6 r8 q7 E! ?public class MainConfig2 {) v; d0 ~  r4 R& h& ]
    }
    5 f+ c0 I, t5 A% w下面是重点代码& h& d- ?) ~8 K& S4 A8 K9 k% s- e

    6 k1 @+ @6 j! D5 \- ^@Test+ c( \0 l6 M1 Y: r' _
    public void test2() {# i1 m; ]  F4 n( Y/ n& {* X1 {( o! V
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();2 f6 ^/ m7 K- n: l5 Y* F

    # I! W6 b2 R  I! @    /*下面这段是关键 start*/
    ( k' l) S* V. B6 u3 A    //模拟从db中获取配置信息4 {+ D8 O8 o) _  y, A
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();; I& q0 Z, U& A/ r+ d! u
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). t) Q6 |7 J- h
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    9 D7 |, p) U* _6 n" g    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高) e& g3 C+ z- E% Q. U4 I0 x+ j
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    # o7 F- ?$ S8 _/ a6 K    /*上面这段是关键 end*/
    ) j2 w! w" |: V$ Q% F2 ?0 @2 B- x/ x2 t' R& c
        context.register(MainConfig2.class);  m! m  I) a2 ?- O' W" n% k
        context.refresh();
    ' c+ X6 f: {# f; U/ U' U/ q5 j    MailConfig mailConfig = context.getBean(MailConfig.class);0 g5 ]. ^1 l; Q% ]0 W( O
        System.out.println(mailConfig);
    6 r6 i, o6 l3 e& {+ V}
    % ], D: M- O# o& `1 l3 R9 P$ j6 s注释比较详细,就不详细解释了。
    ) @0 a+ Q8 J" Y  V- d& \+ E: a* g. ]) N( \4 t7 c
    直接运行,看效果
    9 X2 c5 c: K) E2 _  m6 Z7 m, X  a5 \7 L' x
    MailConfig{host='smtp.qq.com', username='路人', password='123'}. k8 U/ l5 F2 D/ w5 _+ N. Z! k# b' L+ L
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。5 A& D- a# ?( W7 I" T! S

    3 m5 T( g& {+ |* v- B. V2 o! f$ G上面重点是下面这段代码,大家需要理解
    1 v# ?+ \2 E+ T, j& `4 b) i: w7 O3 W# P2 [$ ~' W& e
    /*下面这段是关键 start*/
    1 y  i2 V# l) Y- L//模拟从db中获取配置信息& V* t. N9 _) ]1 k
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    & i1 n  K& ~* D# `8 Q  k+ ]# y//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)! ]1 M& T' C' x; U4 w
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( _1 U( z, h3 k9 t//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    - W. c; ]( c( p* O- T: E1 a* S* Dcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);0 I) A0 u+ m' L0 A' `
    /*上面这段是关键 end*/6 t5 D! z) r6 I+ Z1 Z8 F
    咱们继续看下一个问题
    ; t) L. k5 D1 e
    5 i6 g, X; o/ Z; U# C如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。! C+ C2 g, [, s
    " K- \4 r% {$ i+ N& r+ U* Q
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。6 S" U/ y! v5 U) w% e; e
    2 B* h7 W' e  I3 z$ U5 q8 v
    实现@Value动态刷新
    " e. |  x$ [3 g- B
    ! v& k2 A# L) t5 K7 G先了解一个知识点4 p2 ]2 }- `+ G4 D3 u7 X
    ) S5 `# R( X6 _, e, A/ B4 o
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    8 I" t0 \  |7 q2 ~! J! U
    : }( E% }+ U' N+ d8 ]% @5 G0 L这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解3 |) |+ W# q; G5 A2 {" k

    # c& M* m( ^6 m7 p9 \bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    0 [' D* v: Q, f( k; m8 ]' V5 X9 k) U: N  ?
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;, H, u/ c; [, `$ L; s6 e; c
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中  R$ j! A! H% |' w7 n! y7 d8 E

    . N9 ~) \" t6 _; W2 |. qpublic enum ScopedProxyMode {* L% ~* e: b' a4 u9 m
        DEFAULT,
    ) x, ]: I3 E9 V# I  |$ O# f& p% R    NO,
    % r: e+ a" d- ?% ^    INTERFACES,
    0 _% v6 s+ w: c& K    TARGET_CLASS;- c- d8 m. p$ b6 b( |. c
    }
    5 L( K, \, e3 \7 o, A' j前面3个,不讲了,直接讲最后一个值是干什么的。
    ) [( I  [; M' t$ V& N, V7 h( M  |/ }6 i6 m5 z0 L! h' q+ c; p0 ?
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    2 M5 G; M' b; J! A2 D9 w5 X: G  n2 }2 S6 J& ?- b7 `
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    2 l* @$ y+ j9 {" v  c, P4 O2 z
    / J9 |2 ~, o; K& y$ v' Y自定义一个bean作用域的注解
    / x% l6 L# c/ S& G+ B
    2 z: ^0 O. K" ^% y' }3 dpackage com.javacode2018.lesson002.demo18.test3;: x; h1 ~" _" ~7 n% J" ?+ v6 y

    6 o$ y$ s& S4 vimport org.springframework.context.annotation.Scope;' `) d3 U# w4 S  n
    import org.springframework.context.annotation.ScopedProxyMode;* [1 B) w( @- |* c6 r" l( _

    " U; j. W7 R, b* C9 u) Limport java.lang.annotation.*;, Y& A- m; B$ s
    # i' c/ V' h  o' X6 n& J! d' u$ s0 u
    @Target({ElementType.TYPE, ElementType.METHOD})4 i! h0 D. N7 m+ Y/ r) T+ y
    @Retention(RetentionPolicy.RUNTIME)
    6 S# ]) B$ u" N& k9 r$ e@Documented
      @9 z# _1 h) F+ }( W6 m7 v4 n@Scope(BeanMyScope.SCOPE_MY) //@1& \2 e6 ?* G4 Y+ w
    public @interface MyScope {) H& Y8 H, D, [+ f) m
        /**0 A0 w6 J9 j, ~# B
         * @see Scope#proxyMode()
    $ t# J" r) W$ ?     */
    7 r# H6 b8 C" B    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@21 s5 C( x5 [5 Y2 {
    }5 A4 S: [5 D- x" i2 N# T6 R
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。) U2 T4 r5 L' t& h

    2 ]' i3 z1 H( _; \6 S$ s@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    $ l+ t1 y6 \4 M9 L# w2 b& A2 C! Z) P
    @MyScope注解对应的Scope实现如下) [( q2 D% V6 z# f6 W4 \
    1 U; z$ U# M+ i+ {
    package com.javacode2018.lesson002.demo18.test3;
    ! b6 ]8 K" G, N' F" F
    ! n2 g: _8 n) }% f$ M( r( A, nimport org.springframework.beans.factory.ObjectFactory;
    1 k: p6 u7 g4 k% Vimport org.springframework.beans.factory.config.Scope;
    ' }) K8 l7 Z$ J5 b$ Limport org.springframework.lang.Nullable;
      f* n! T; i5 ~: E  l/ R! p
    % V; |5 h$ C" J' r/*** @/ J; }$ L. H
    * @see MyScope 作用域的实现
      v4 U, O6 T& [. S" [+ U- p *// ?& \: X9 H) |2 s: j, F. f" _
    public class BeanMyScope implements Scope {7 _4 `9 c, A% q: o/ F  G6 d3 v
    $ Z0 i! L& U6 p
        public static final String SCOPE_MY = "my"; //@1% p  m8 A* f; g5 R" R: w$ g

      q7 |3 |6 }2 i9 M4 l2 h7 A2 l, }    @Override! A. A% ]: l3 O+ }; J
        public Object get(String name, ObjectFactory<?> objectFactory) { / ~) T9 X- f; Z" j# k
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    " s4 S; X' P* ~% l- m7 w" ~        return objectFactory.getObject(); //@3
    ( v7 q8 J" v0 ^0 d& ?4 g$ M) c    }1 B/ V4 ^- d3 y- O& |' k- h3 I
    ; x5 \/ @+ [6 S; F& x  ?/ V5 m
        @Nullable
    7 I) T+ k/ [8 B: \9 n    @Override$ f& i9 H. S& u; `9 k4 g8 f6 m
        public Object remove(String name) {! r) }/ d* T) d6 V' b3 `
            return null;
    ) s4 G, ?6 \2 k    }
    ' D1 A# U. @% o, m$ z" S* I) O& H, a0 O' ]
        @Override
    2 G( n  e0 r' v2 I+ P    public void registerDestructionCallback(String name, Runnable callback) {
    ; Q/ N- p- [9 T* b# h2 S- m2 p+ d0 [1 |1 X8 J
        }, a! _: D# c& ~. g

    - N% J4 y0 g+ t' v7 Y$ V# |) ~    @Nullable
    + }* x' m6 E6 y! D    @Override
    6 C7 [4 A8 x6 Q! x/ i6 k. C    public Object resolveContextualObject(String key) {
    ! w* W$ ]3 J& Q9 v9 f$ B8 a        return null;
    9 P4 ~7 P1 ^; v1 X9 N3 h    }
    * T0 W/ Z4 v! K0 a, a7 o, ]6 L; C8 S0 S9 g) E7 v! E. i; k
        @Nullable* H; X$ l/ Z8 W" M" @) C
        @Override! r& {. V4 R  o; i2 H
        public String getConversationId() {6 {4 C0 p4 Y& J; ]$ [
            return null;7 l2 N4 W5 b7 D- }
        }
    5 P+ [4 d' A; [; X+ r6 W}
    3 J2 M# c" c* B  o; B6 a/ ^: z@1:定义了一个常量,作为作用域的值
    . U/ |* F7 Y9 ~/ i" Y
    - f( P$ x6 ^5 _$ Y$ x@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    7 u* @8 _, P) m
    ) l# A3 d; a, t% L8 ?: g  {, p5 n/ ~@3:通过objectFactory.getObject()获取bean实例返回。+ ?3 i5 Y% @) ~! D
    / g* ~7 r/ [8 g$ s4 ^" g
    下面来创建个类,作用域为上面自定义的作用域
    8 `9 g- Y, a3 s- h  T2 K' y- s! X/ [. d( ]' ?- C0 y6 M
    package com.javacode2018.lesson002.demo18.test3;/ o( e0 K9 o  S) O/ K( _

    , ?  ]# _$ l# V0 U8 Jimport org.springframework.stereotype.Component;
    , ^" F3 m. i- Q  S$ ^$ \9 P: y' Y3 D7 o
    import java.util.UUID;5 h. Y4 Z$ l; L

    * l8 P" z& u0 r@Component
    ) l9 }  F5 X' J5 M3 X% B@MyScope //@1
    6 x& S) e8 m$ {: A4 o3 O( q0 M9 y! lpublic class User {
    + J( g. d1 K3 J3 [  d" q( L* }( x: H' l1 ?5 R! Q2 }) t) ?
        private String username;9 |  {. H$ v, R1 W5 c  ^/ M
    # o# U. U4 a& [! L% I& `7 u
        public User() {
    2 d; [) W+ Y/ C; q6 W' z        System.out.println("---------创建User对象" + this); //@2, h* y) W- t/ Q- V
            this.username = UUID.randomUUID().toString(); //@3
    & h( C' \5 o" l    }
    # A5 _+ N* ^& T9 ]3 O
    7 S. x# z  |1 S1 i% p    public String getUsername() {
    ) j3 c& l0 f0 d2 d" z! o5 ]0 Z        return username;
      J0 Z, g/ \( `9 `+ b; ?    }, U# a5 _% ]1 D: D

    . `( ~5 s4 v3 @1 `( Z    public void setUsername(String username) {+ T: h# R: @8 K( R
            this.username = username;0 ^3 S: Q3 Z3 {) m: j* B
        }3 K- b# }. h6 Z# S* |* {, q
    : {1 ]4 D5 t: e: l5 C% u, O& Y
    }7 x4 p5 Y: D, s4 u! q
    @1:使用了自定义的作用域@MyScope3 ~, g3 S, s% C( E
    $ @  U* {8 v4 S2 K
    @2:构造函数中输出一行日志
    8 ?: \$ Q; \, D
    ; Y, Z: B. b+ a@3:给username赋值,通过uuid随机生成了一个+ Y  f4 c# \" K3 t

    7 R3 n1 \1 E, m4 t3 p6 @来个spring配置类,加载上面@Compontent标注的组件
    9 ?4 F, f/ L8 b0 E' U
    / c& `% ?+ S. g9 y; ^% [7 dpackage com.javacode2018.lesson002.demo18.test3;
    * q: z: \/ O; ^$ W  q- T4 U7 o5 m: c4 V, B/ W% s# a
    import org.springframework.context.annotation.ComponentScan;
    . A6 p9 s( f0 C7 c  _import org.springframework.context.annotation.Configuration;
    - g$ v  H. `: i0 G2 A; m1 {
    ; Y( q9 t8 V" s- h@ComponentScan+ m& t- i7 I0 |
    @Configuration" e1 h( n3 I: m% _2 I. ~
    public class MainConfig3 {" P/ {: ?- f: L7 U- V4 v: }% [
    }1 r1 r5 R& N7 }
    下面重点来了,测试用例
    , n* n3 H9 C" _# O& [0 ]. J. x9 i! q% i2 I
    1 S. l% g: v8 X5 c) ?/ G@Test
    / ^+ r$ X4 k2 I$ z! {" @public void test3() throws InterruptedException {, W: a/ o; n/ X) ?" i" Q2 r
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();) N- Z+ E! Z; P2 j; ~$ }, b9 \3 V
        //将自定义作用域注册到spring容器中  d5 C: I* n* |
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    4 [9 L3 t4 ?/ C7 F    context.register(MainConfig3.class);! X0 L' j" n- B" r; U' U8 C9 ]
        context.refresh();% Z5 |' K9 j2 M% {! Z; }+ C
    2 B! d. _% Y0 f/ x8 N: S3 a
        System.out.println("从容器中获取User对象");
    2 G, Z" J; I/ b6 p7 d& o9 u; e    User user = context.getBean(User.class); //@20 `7 Z  O. y4 p% x, @
        System.out.println("user对象的class为:" + user.getClass()); //@3. h( ~$ `; Z8 F) ~: N( B) a  @. c* W

    , |/ z) @4 j" N: q    System.out.println("多次调用user的getUsername感受一下效果\n");% c9 Y' l) J1 I% v
        for (int i = 1; i <= 3; i++) {% D3 e% P4 \0 `
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));7 D" @7 A- |( T. y9 C- H( L/ t1 I
            System.out.println(user.getUsername());
    9 e' H3 A6 G9 m2 m1 S- \        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));( Z1 Z- }$ o  C$ N& w% e' r6 `
        }
    * C, k  c6 g% E5 t7 ^) A}0 [9 ~1 N* K, a- L5 a% P2 ?$ K
    @1:将自定义作用域注册到spring容器中' o( y$ f: H2 R# O1 v; L. g# a

    # @: u( q7 E2 z@2:从容器中获取User对应的bean2 S% d8 ]7 q5 _7 S: V: m) [. z
    0 n1 u" ~) e( o2 N% }) ~
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的1 t" c+ J9 W. f7 X, n( v, G% C' E
    1 H2 M( _$ M) q7 n
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    ( J6 v* d6 }  F
    5 ]( X" }. ]; H& E3 W* a4 x见证奇迹的时候到了,运行输出
    3 L, r. C6 l) A1 ~4 X0 u  o5 k, w7 d: f' a* s3 ~- r
    从容器中获取User对象
    ( p- G, ]* G; ]: A6 B6 guser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127; p7 k. f8 ?5 v- G/ u/ j
    多次调用user的getUsername感受一下效果
    6 A$ _* ^( I+ U# f% l/ B% Y6 ?4 i: c0 W9 ?# x7 J& [" s4 v
    ********
    2 I- r6 N' T$ P0 }! C: V第1次开始调用getUsername* h  `1 U6 P2 B1 y. Q6 H- y( i
    BeanMyScope >>>>>>>>> get:scopedTarget.user6 S* K" I3 w, I
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4& B  K# h! i, m' L- Q' \! v$ _
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    ! [- o. }) \2 ~( l& S6 Y- G第1次调用getUsername结束
    / C+ P( z2 b& V, j4 z) F" K********
    " V; V8 b0 a: a% @4 R. W) b+ A8 E
    : b4 |: {2 @0 K0 X********3 }0 C  }) B4 q7 H6 W2 O  Y8 a
    第2次开始调用getUsername
    3 M$ ?+ g( S1 _" K" v  s! A! cBeanMyScope >>>>>>>>> get:scopedTarget.user
    $ X2 I) [& E/ L---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    " e6 J7 M$ K) H  v, ?: T9 i3 B, E01d67154-95f6-44bb-93ab-05a34abdf51f0 g! T" L3 H# O3 a0 m3 M
    第2次调用getUsername结束* B* m! |* L8 Z9 P  N. `% O0 u
    ********
    7 t* w9 H4 I9 Y! {9 b3 ~7 b0 T
    9 Y+ f5 X- K( R8 m  \5 u0 k********
    + u  y! ^1 g! o: J第3次开始调用getUsername" I( ?) a% _  ~9 }& a$ m9 c8 o2 d5 m0 h
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    . t2 O/ P4 _2 z0 C0 K' t0 I4 v# X---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    $ `8 t7 Y* `" {76d0e86f-8331-4303-aac7-4acce0b258b8
    ) B+ |; A! C! O: ]; k! r第3次调用getUsername结束
    % H+ L/ c% x; e- W. |********+ J- f/ T. E( n% O* o
    从输出的前2行可以看出:* g4 X6 t9 Q! J+ y- U

    1 b/ w% h0 t6 H. c* x! F6 q调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    3 b. W  ]* ~& t# U* A0 c
    3 g4 Z& O0 r( F3 t' `第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。2 @! @# v- k3 u
    & T" [; S& [) r/ o+ `' f  m, i" C
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    6 i5 g7 s! Z% {" E5 X3 I; c; L3 G
    & S$ s8 l: t; ?# v' O. f9 ~通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    $ E; ]  \6 U; ~, x/ v% O* L7 h  Y) p; o# }1 {0 J4 e' A: ^% q" Z% F  \
    动态刷新@Value具体实现0 M, _" Q/ w1 o8 B7 z8 u: l
    3 I! `/ e' L0 r+ q0 f* k  d
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    / u; G/ T" Y" p" C' L! ^3 Y+ @$ n; E- X6 A
    先来自定义一个Scope:RefreshScope
    * {; q; {6 X" o2 b/ m% \! N' D* p0 P/ H# o! r: S
    package com.javacode2018.lesson002.demo18.test4;) ?. k- [& O& t
    / ?! D* N5 @6 h( n5 H# t3 r1 V
    import org.springframework.context.annotation.Scope;
    " q3 S4 u+ \  j, k( d! \" J8 Limport org.springframework.context.annotation.ScopedProxyMode;" ~' p6 D; H; k+ t

    1 z' `1 o& L" m: r5 r: [: Yimport java.lang.annotation.*;
    6 K, @# ?' }& o# j; n# Z
    : Z8 N# M) B) U8 t5 V  {* [1 c3 d@Target({ElementType.TYPE, ElementType.METHOD})4 }7 |; B* |% J+ Q- Q/ L
    @Retention(RetentionPolicy.RUNTIME)- T5 g' M/ O3 c: i% x% t5 h8 \
    @Scope(BeanRefreshScope.SCOPE_REFRESH)/ N! l. E5 e' n
    @Documented
    3 i! [: q3 R* J" N+ L7 r+ @- ~public @interface RefreshScope {
    6 B+ t1 q7 @- E6 i% x! A+ _" \! ~    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1' o! A, \1 I& G' g) @
    }& D4 I: l! `0 R0 @* ~8 {
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置" ~  R# C. P) F2 X6 H$ m1 M# A
    5 R; E9 k. v  ]( b/ p3 c+ z; y% g
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS4 V5 s1 `  c' g- R. e  \, }
    . {" w' G1 W  q
    这个自定义Scope对应的解析类
    2 M  j1 a( T# h' h, }+ x% W
    2 L* ?# t/ d  p5 {3 B: c下面类中有几个无关的方法去掉了,可以忽略
    7 I9 v5 v+ G- \2 u" }# g; G- z' _( w# C( _
    package com.javacode2018.lesson002.demo18.test4;0 i' n' V: z; l4 ?2 H
    1 [: u2 ^9 k( R

    3 x% a6 c) x! G6 k8 \5 P  eimport org.springframework.beans.factory.ObjectFactory;
    7 f$ D% e2 N. i  q4 g$ {5 Zimport org.springframework.beans.factory.config.Scope;
    + w, T: F7 ]2 Y5 b7 O! Qimport org.springframework.lang.Nullable;' B( B* H" q4 J* X1 S3 P
    1 e/ g9 ^9 m* i: ?; t9 B
    import java.util.concurrent.ConcurrentHashMap;
    : p; F* W3 y: h/ s% \; S# h
    " t! f+ j1 w" X) fpublic class BeanRefreshScope implements Scope {
    1 s1 G. S9 K" m) D0 f
    % d1 X& s( E9 @- R    public static final String SCOPE_REFRESH = "refresh";
    ' w; \$ O5 G  M9 z/ p+ q
    3 N. g9 v: k4 A) T9 G/ Z3 Z/ A* `    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    ' |  U$ ~; r) G# M6 |& i  [) _, D+ l; c% Q  b' k" h
        //来个map用来缓存bean
      V/ ~% o% \) h5 {    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1; P( ]1 o" ~- X0 }4 I9 w) o/ R

    1 d5 t* i6 z: Z; B0 j1 N0 K    private BeanRefreshScope() {
    # ^( ^* m/ a) R3 D. U    }; K% v3 F8 {/ U* c

    1 R# M) e( N* e+ Z    public static BeanRefreshScope getInstance() {7 n! J! ]% @# V  O& X& {* }* m( W
            return INSTANCE;& H  J* R) P; _
        }& r: }: l$ H% B* Y
    ) c. W( J3 S" ^. a
        /*** Q, z/ L+ X; d" ?' Z
         * 清理当前5 S+ p4 F+ C; C; y: Z0 v2 h& d
         */
    / ?) x+ D- P2 G* r* d7 G/ ]    public static void clean() {. ^9 b6 L9 o) F
            INSTANCE.beanMap.clear();; a7 l2 Y- x# v0 e
        }
    ' ^7 j2 E3 G3 d2 J4 f8 z: p
    0 B4 X2 l& J- p! i) D$ `0 d4 h    @Override7 a! X- d1 B# `; ]  q0 J
        public Object get(String name, ObjectFactory<?> objectFactory) {# k( ~' S8 n+ [. _4 }0 K6 b5 s
            Object bean = beanMap.get(name);
    , w4 C* O8 e$ M7 }        if (bean == null) {. \5 P- Y& \+ v0 d. g
                bean = objectFactory.getObject();2 z& m, D0 \: D8 V( y7 ?1 ^
                beanMap.put(name, bean);' Y. h' R) T! h5 ?1 A
            }
    & ?7 H, B; e0 F5 n* o" T        return bean;) Q) ?" R; ?& c$ s
        }
    ! }+ Y7 @6 ~  y/ |& n7 _3 w& x% l, ^9 P1 B4 S
    }
    / E" T) l. l" z4 B0 _( N8 n上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    + T& _4 ?1 e- i/ F, V+ M. j8 y! S
    1 O  O( o. ]" \! k, w3 L6 M  q' E4 R上面的clean方法用来清理beanMap中当前已缓存的所有bean
    1 v7 P4 P5 D: V' r. V' @) ?" n
    0 u" |+ K! r. t来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    , W( D! h) `" J$ c: K1 w, `" Q# Z" w  k2 c0 v7 s
    package com.javacode2018.lesson002.demo18.test4;9 t; |( y2 T4 ]( M4 n: O

    2 Q( Y0 G+ k7 i5 S1 M1 ]1 k  j3 l3 @import org.springframework.beans.factory.annotation.Value;# ^( ~! b" e2 U! x
    import org.springframework.stereotype.Component;
    # H( j& _3 _9 R- q6 I: [  t0 K8 ^: |$ A
    /**" U& K' X6 c7 U+ _
    * 邮件配置信息
    8 C& t/ E( N2 z. r+ O* `' N! G& D) |/ X */5 S3 B) Y0 T: ]) V5 s
    @Component
    3 k# X, W; L5 y) ?@RefreshScope //@1
    - `4 C! R/ B/ M8 }4 Gpublic class MailConfig {- a* |5 ~! m$ u& J+ w5 ]
    0 P% T7 o. v  @5 ^
        @Value("${mail.username}") //@2
    + c- H6 v: E  t  Q' d* P$ V    private String username;% z3 j# P* z/ K, r2 z2 f

    4 V8 a; X7 H& `( P3 ~    public String getUsername() {
    6 C* m( f+ }' w: m( B        return username;
    & @9 v' t5 P  r6 j  O    }) _% U$ c& d5 v' W/ Z
    4 m5 v7 v  Y! G; s4 l$ \
        public void setUsername(String username) {
    , c, T' i) n. I7 z        this.username = username;
    & r9 I  J4 L% f( E4 F    }
    . u4 }4 {6 g" }0 d! g+ e  v, v) [" k8 r: T
        @Override
    ; O7 r4 J6 ], A6 [* c    public String toString() {
    ' O2 O1 F! x( A  E1 D( u7 O        return "MailConfig{" +
    ; O) T" a4 r/ M9 S                "username='" + username + '\'' +
    6 G  o  o7 r* h& m/ T                '}';
    1 ^0 d0 `5 Y2 y) O# a) n    }1 x% {6 j: H/ S4 U9 V) ]8 Q
    }
    5 Z; N8 f1 S) H9 F9 F@1:使用了自定义的作用域@RefreshScope
    ! _& ?4 }+ l) L) v; c8 D' M& ?# e6 E5 M8 G
    @2:通过@Value注入mail.username对一个的值
    ( d6 Y: V7 N' u, d6 ^4 b+ y2 G8 F/ m$ P7 }6 {- d
    重写了toString方法,一会测试时候可以看效果。
    0 N. ~. S( e9 J
    , X3 P- I/ i0 N. D7 h, W  C再来个普通的bean,内部会注入MailConfig1 C; X5 X$ N! {" n5 q) z5 V

    " {( f8 U1 T7 ^" Q8 Opackage com.javacode2018.lesson002.demo18.test4;0 z$ ^6 k( ^, M
    ! p" g3 F& u# R
    import org.springframework.beans.factory.annotation.Autowired;  s9 T1 s1 B% X. E! C
    import org.springframework.stereotype.Component;/ W3 U' K/ r/ c& ]

    " ]' l# f: p7 S4 F8 B" p@Component
    - u3 P! M- V; l! h+ ]0 Upublic class MailService {( |4 w: B& c1 ?+ J
        @Autowired
    ! O" `0 X  P+ l/ g3 R0 h' `3 G    private MailConfig mailConfig;
    0 |" t; g9 e0 f6 A, M' e" {" _; P/ p1 V
        @Override% t1 \; U. A7 R: M6 v) e8 Y. M* h
        public String toString() {3 ~% h' g" r. w0 G! t  }6 i
            return "MailService{" +
    3 _  |# x9 x' I$ ]5 I& ]: p                "mailConfig=" + mailConfig +
    5 V1 X" T6 q* X- O1 G: C/ X                '}';
    ; ?' }! O0 }, e& o9 R& F  v    }
    7 [, M; Y# B& J1 T7 X1 X5 o}+ _! u5 `" H& l# J6 X
    代码比较简单,重写了toString方法,一会测试时候可以看效果。# Z4 _8 ^1 H0 w3 A1 T6 u+ M
    : `* i: Y6 y3 T$ _! ^0 l
    来个类,用来从db中获取邮件配置信息' |0 j3 ^4 \0 s8 P) @# j4 P) w2 ^

    + q9 k- a) k! Q" z, T% _+ rpackage com.javacode2018.lesson002.demo18.test4;7 g7 h' E) ~( k. }4 I; l, Y3 |. b
    ( q& t0 N# Y0 a
    import java.util.HashMap;
    4 R9 `, J$ X7 X' Y& m0 gimport java.util.Map;1 R/ J" E* M* i4 M; n: B6 e
    import java.util.UUID;% x( D) s1 J7 j. d( _  v0 [

    ) [. `, |( k' {- bpublic class DbUtil {1 K5 X1 O7 `: o. p$ B
        /**
    0 \7 v8 n. h0 A# X& X     * 模拟从db中获取邮件配置信息
    5 D' S" F+ C8 L9 P$ [. m5 \+ j     *
      i- ]4 z! R& }: C: a, S     * @return- M& \! X, O2 W
         */1 e) F% v4 ^3 x1 r4 n
        public static Map<String, Object> getMailInfoFromDb() {
    * x- l! Y8 B  w2 H( w/ d        Map<String, Object> result = new HashMap<>();
    4 c% \. \" [, L        result.put("mail.username", UUID.randomUUID().toString());* m. x, F- h8 ^4 s
            return result;
    9 f1 j; s8 m% L* h    }
    7 N4 S; I! I" C" m5 W% ~+ V+ Z}; u* B8 d- v* Y) L+ c
    来个spring配置类,扫描加载上面的组件
    3 U( M" B& [& f* _$ Y
    ; Z6 A- }" O" bpackage com.javacode2018.lesson002.demo18.test4;" w, ?6 b- [/ Y) F' O3 s! D

    . ~' w9 X3 C& H, ?+ _1 M$ @0 Nimport org.springframework.context.annotation.ComponentScan;
    - C: i) M: x' k) ?import org.springframework.context.annotation.Configuration;
      S8 K& b  E* o) s. ^( C, [/ Q' @, X2 Y+ S5 [; S$ ~% e
    @Configuration1 f, R% P2 }4 M0 I/ k, P- M; F
    @ComponentScan& v% }6 q6 r: i) ?0 O
    public class MainConfig4 {
    + m+ c5 @' j9 w# z}4 b* _8 j( K! T
    来个工具类/ {3 `9 l/ p( Z9 A2 ~4 q
    ! l0 N& o5 l7 C/ l& T
    内部有2个方法,如下:; @2 m/ {5 T. c' _/ y! a  |# Y

    ! ?% l! \" F8 [$ \package com.javacode2018.lesson002.demo18.test4;6 {6 \* Q) l: O4 g0 H9 Q

    . z2 h8 o3 y! M7 ^import org.springframework.context.support.AbstractApplicationContext;* W0 M3 k  ]* g2 ]/ f+ L
    import org.springframework.core.env.MapPropertySource;' E9 g5 W2 z  ?; ^; Y

      b1 s* h9 s. ^& x# |0 K: \+ p* ~import java.util.Map;
    " G  T% ]0 o5 ^* H% }6 C" H* P% x; N4 O" K
    public class RefreshConfigUtil {( p0 [9 L/ ~: S7 r5 K$ H& ?
        /**
      z9 I( F9 [  V     * 模拟改变数据库中都配置信息
    + p! i0 r7 f3 f     */
    / Y' b5 q/ X. \% W  |    public static void updateDbConfig(AbstractApplicationContext context) {
    5 o2 d1 l2 T3 Z/ x5 b- L3 r        //更新context中的mailPropertySource配置信息. Q* C# U0 U3 L% G
            refreshMailPropertySource(context);3 P1 `) m& h8 C# P6 t: b) U
    6 `/ Y- d! C( J8 A7 o' {
            //清空BeanRefreshScope中所有bean的缓存8 K* _/ w* l/ [! O6 `: q
            BeanRefreshScope.getInstance().clean();
    5 x7 \( l' h1 Z8 W& E( p    }# ^& z" ]/ D8 v+ k
    : s, V% k( S" y- U9 @3 v# B
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    + k+ P- j' P3 |' u3 M        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();0 Z% U$ |/ T" U5 D
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)2 f  L: c2 R+ T3 c: i& c
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ) }, j, g' c1 ?( J; J        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);! q6 g, A0 y' f; L4 j
        }
    0 [- \  L% C* h( H3 W
    5 j! i$ }! B. Y* Z/ h: D9 j}
    3 M! w; ^8 i/ H( e2 P. `updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息  B# D' A) h, u. N4 S# W
    ( X# f  q* |# p, _; R$ I2 h+ v  ]8 g
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。+ m0 o5 X9 n9 Y3 X% ?! h% a" B
      u: `& o$ z0 n1 ]" T
    来个测试用例1 R9 x, B3 v1 l' c
    : o, l* [* M" y- {/ Z' f( c, A
    @Test
    & q1 n5 I" {$ n( F- W" ipublic void test4() throws InterruptedException {9 t; p, u  N& L2 p: |5 f9 M* B5 x
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    4 b" k0 m# q8 a6 y; J/ D    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    6 Y1 y) R* }+ j  V    context.register(MainConfig4.class);/ [/ p$ E5 i- y' U* v/ I/ p
        //刷新mail的配置到Environment
    5 o* @) `/ S) Y( Y3 U    RefreshConfigUtil.refreshMailPropertySource(context);% ^; M* h4 n; P8 ~* w
        context.refresh();+ U& Y) j, Q2 O6 E/ ~
    8 y& Q; |. h- o5 B& l" f
        MailService mailService = context.getBean(MailService.class);
    2 P6 ~6 |* [6 K+ B    System.out.println("配置未更新的情况下,输出3次");
    % E. `  S! x- C( x: r$ M# x    for (int i = 0; i < 3; i++) { //@11 f+ X! e" p6 e& b
            System.out.println(mailService);
    , x* Q0 V. L. _: N        TimeUnit.MILLISECONDS.sleep(200);/ Y+ w" L  R4 z
        }+ E+ G' U6 @1 T2 _$ J( Q

    4 t( z7 E- N* O! [    System.out.println("模拟3次更新配置效果");* Y6 v& H+ H9 v: l8 |+ q4 W
        for (int i = 0; i < 3; i++) { //@2  W/ n3 j1 |4 l+ Y' @* }) i$ q
            RefreshConfigUtil.updateDbConfig(context); //@3
    0 J: d+ d% w0 ~- n) ^        System.out.println(mailService);
    5 @8 _$ N5 {9 D; j9 A# g% Q5 T9 Y        TimeUnit.MILLISECONDS.sleep(200);- Z1 u4 B/ N+ b3 |2 ]1 L4 h
        }$ j- N; n( d4 r1 O- y# d
    }8 q3 `8 U5 V" @) e! X
    @1:循环3次,输出mailService的信息
    5 `* R* ~% p7 T
    & O; Q7 l# a, M( L; P@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息" V. j8 W* C8 l6 A

    % j) u5 _3 X0 U  z6 b, {7 D% n见证奇迹的时刻,来看效果
    9 c( U2 t% s1 B3 R0 z% K% ]% t
    / i2 Z0 C) w- h配置未更新的情况下,输出3次
    , b4 W) |/ f3 Q5 A& K7 I, AMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}# c4 J2 k# [6 {0 z! d
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}" R- U" Z8 i2 u
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 ?5 w# k" m' H4 B8 V模拟3次更新配置效果! U9 o( n" Z  [  E( |* q0 C3 O/ u
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}( s3 G4 G& t$ R8 b% ]  T! U
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}4 P; K8 U' P! F! U# d1 g' d
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}2 J7 `! U7 C) T5 n9 N9 Z0 Z9 e
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。# N& Z0 A' B6 a- [
    ' c  P( O, X- {! ^
    小结
    ! T5 L6 Z! @5 q0 ]5 r0 r. W0 A7 A) e6 i  K( T
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    + N" F3 R) I1 i+ o; d# U5 a& `# m# Z/ Z' Y6 w$ j, O
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    0 B. s/ I8 ~7 A7 ~
    ' s) |& y9 m4 C/ ~* v总结
    2 }+ P0 l: R% O( h
    ) c5 R: r; j9 c本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!+ C& `8 W) B6 S4 e0 v3 c
    0 ]0 @! y; v1 d
    案例源码
      E7 l9 L+ Q8 [/ o) ?# Y7 S" ]7 w& {3 Z! i
    https://gitee.com/javacode2018/spring-series: A) m7 P3 n; n$ z9 U) B0 `
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。4 U* B/ e4 g' k4 x, T+ Y" I. |

    & S. R+ k: ?+ U" YSpring系列
    / j( W- z: J7 u: n; F6 R; R1 i
    0 t, d9 R; e0 W$ m9 BSpring系列第1篇:为何要学spring?3 S5 b/ Z6 P5 ^- ?' j5 @
    , X- \7 c. }$ }$ d( ?& c
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    1 j  \+ q" K( P) ]$ `: w  x
    6 {1 P; W3 Z: y. V5 ^# f1 vSpring系列第3篇:Spring容器基本使用及原理
    " l& _2 T- ?+ o9 P5 m2 F" }
    3 G4 A- ^" d4 }Spring系列第4篇:xml中bean定义详解(-)7 I- T( J5 y& j6 K4 g- H- i
    , t' l& h2 I; F7 ^% V
    Spring系列第5篇:创建bean实例这些方式你们都知道?% S. B5 E4 M: c7 ^# ?4 N* S; Z& U

    ' T5 F* Y' w& \8 @Spring系列第6篇:玩转bean scope,避免跳坑里!
    - U$ R, R0 l- X
    1 v; ~- S1 q0 g6 |+ N; P* E! aSpring系列第7篇:依赖注入之手动注入
    & Z6 u4 F( K0 w5 b! s0 N  S  V  H/ n9 a+ H. D# u
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    + M) l( e2 X% x: c7 g6 G* b; J( y) f: c; j6 y# x  R
    Spring系列第9篇:depend-on到底是干什么的?( {' V9 O7 l' p" Z' O

    3 I6 c6 N4 g* H8 z) p2 J& a3 JSpring系列第10篇:primary可以解决什么问题?
    , J+ `" v2 s& J% _# X, P* w3 ?! E4 y; U* h5 h, e
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?7 m) l, g! I; w; ?
    : q. D) ~8 {  E+ N" {
    Spring系列第12篇:lazy-init:bean延迟初始化
    ! a, T- {6 @" I3 C* r* G
    5 ?3 Q/ k3 E' x( S! d. c% rSpring系列第13篇:使用继承简化bean配置(abstract & parent); S3 ?$ G, s6 @/ ?3 j$ M

    5 O" p; \6 @1 V" [, I$ F. NSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    8 y* F& V! g5 W" n
    7 U7 y- w$ Q0 T( v& y/ R' ~  [8 ZSpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    , B( L+ `. Y0 I6 L( Y1 @2 z" m: v8 Y0 ?. D9 {' p+ e
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    - N5 r4 R! t5 g  p
    / j) |2 d: t1 [% [1 x7 A+ SSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ' @% x; T0 q5 m9 X8 T$ }0 L6 Z+ E! |$ `9 O4 y- C
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)1 v/ N7 V7 w+ f# }* J

    1 i4 ~8 U( a2 y( ?/ CSpring系列第18篇:@import详解(bean批量注册)4 S1 a% m) A3 ]2 }
    & z0 l0 l7 x& N1 h
    Spring系列第20篇:@Conditional通过条件来控制bean的注册! O1 r" @9 l* n; D4 M5 |: S
    4 v) p5 H) b  x$ |  c* r
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)7 ^* ?% [3 s" Q

    ) f+ @, L1 X* H5 nSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解; {( ^* Y% F1 ]! f, ~/ I# V

    4 e  k1 M" @0 a* c# v8 Q4 j9 b7 eSpring系列第23篇:Bean生命周期详解7 o, U, w) P7 h3 [/ n) p' n
    4 E' ^$ b7 I4 R- l/ @% Q- k
    Spring系列第24篇:父子容器详解; |/ {0 S" \6 L9 U* N
    : b7 I* f8 t2 k, A
    更多好文章* h8 b5 Q$ F# j

    0 R: Q7 N, B" ]0 r5 y- cJava高并发系列(共34篇)
    ( g( `. w3 ?1 Z+ Y
    3 h: B+ h$ f2 {+ P6 p4 O1 M; ~9 LMySql高手系列(共27篇)8 z. h- A% k( F, N1 r9 _
    & S4 {4 v4 `# f3 y
    Maven高手系列(共10篇)
    & B$ J4 S$ ?' [9 Q& d/ {9 u- c7 b9 m* `: j% d
    Mybatis系列(共12篇)8 i" h2 X6 D0 p* n$ v
    ' T7 w6 l' g! N/ Y' Q' b+ ~+ d
    聊聊db和缓存一致性常见的实现方式& D% D7 V. H( d8 e
      i& o3 v  G' [7 k8 e$ F+ W! m
    接口幂等性这么重要,它是什么?怎么实现?
    . Q- D7 g) ]+ O/ o& O( a" N! G& Z
    1 s1 `0 |+ `" \+ j+ r泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    6 `5 B( e$ A3 ^! Q————————————————
    ( E% A: U- W1 Z+ e版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    5 B& K6 L" T: A; _原文链接:https://blog.csdn.net/likun557/article/details/105648757
    4 X3 X! |) ^8 g( k3 q" P* G/ ?
    $ K' F0 W* a8 Z6 v0 @0 }
    + T& O" I0 K! N8 D  s6 E# {
    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-1-14 13:52 , Processed in 0.363356 second(s), 50 queries .

    回顶部