QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5450|回复: 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!% S" }; U+ d1 p7 s; x, Q! U' }
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!) q5 S( f- c8 }% Q* J5 I7 o% g+ h
    . f& A1 y+ n+ l# m$ c$ \; M2 }
    面试官:Spring中的@Value用过么,介绍一下
    : E4 I1 k% V; L) \" n' U1 o; ^# ?4 ~3 B% [: `- v3 P
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    9 `9 w8 x1 ?0 g( i
    8 |  T2 L* `. T面试官:那就是说@Value的数据来源于配置文件了?3 t6 u4 ]4 Q/ Z: ^# [1 O. S
    / U, x' e! q+ b5 ^7 K6 A- L2 F
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ( q* B# C% D5 N9 j
    6 P) e1 ?+ h* T, {# e6 d/ D& o8 L面试官:@Value数据来源还有其他方式么?
    9 G& H7 \1 X/ p4 r
    0 l$ K, Q4 D% |3 \, G6 ~* y5 W; t5 E我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    - h0 \- g, S, Z, S8 X
    ! b7 _0 l- P; c7 X9 F- V面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?( T- _0 y% Z3 F$ [) x
    & u: R- u8 r* \6 V% B- l
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧  I0 v" H) Y# E) ~
    4 o4 c  M/ w, |# ^- B
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    3 }! e7 V' \# ]9 c$ ?% ?: R; @$ o; ^6 i6 w! G
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    . W' q' \2 k; y0 e* M2 z7 @
    : z* u2 w. v  N# a% x0 [1 e( J8 f面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?' [9 T. d# U' a7 J& U5 i9 h
    : \8 j* [# d) v3 n  L/ x
    我:嗯。。。这个之前看过一点,不过没有看懂# i) J& I; r, Y

    # L1 Z: {: B3 W* [! m$ ^) O面试官:没关系,你可以回去了再研究一下;你期望工资多少?1 M8 y' u: O3 o2 S) I
    2 I& h- M. v- L) O- a. g' l. o
    我:3万吧
    " z7 b) J) B/ ~6 t0 n. F  H, v4 k- P) m  V2 D: |% O
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?* ]5 h  S$ y* I: |0 Z9 s3 W6 N. B
    # [$ C2 R$ [8 l
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    # n% ]1 A3 b! b! K/ n
    3 N- M0 U0 a. e- i, j8 ~( |! s面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    7 ], w- O; C/ Z! N* [
      Y9 \2 x5 u+ c( y, W, l/ `我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
      P8 ]  _4 N5 p/ W' V7 X; J0 \  k+ R5 M3 [9 c# V6 c. Y
    这次面试问题如下, o7 v# `# L* E) X$ X4 q% z0 C
    % V5 Z, n0 F$ o- z7 w8 p
    @Value的用法
    5 p' c% j  T" {- L& n1 \$ H: K) n' `' X% J5 E$ R: m2 m& O
    @Value数据来源
    ( ]# f1 k8 d5 D$ C1 \, D- ]/ F6 ^: D
    @Value动态刷新的问题
    ' \8 M! _5 B+ \/ h. ^& A
    * F  H0 h2 R; l) g2 q+ m下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    . U2 p4 l4 p8 r: `1 O5 y
    ; j$ }: Y' f8 y( Q' d3 C@Value的用法; z9 f, w* Z  Z. A5 F( |3 Y" S# P
    , s9 ^! |; @- Y4 S) {" P: C
    系统中需要连接db,连接db有很多配置信息。
    5 N2 S" ?0 y% G5 [
    5 r# s# K% G2 e( x3 {8 B5 b系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    / S" |# p( J: e3 E" _0 l+ F
    1 @8 I) E5 n9 a8 b还有其他的一些配置信息。4 p$ W* i* K( W

    8 A+ `" }; S% P4 q( w" W! ^& ]* h我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。/ G" p' K; V) ~! b! h0 W. X- ?. ?

    1 M3 w( M# y, H那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    8 U7 p# D* w7 G; N9 ^: O. Q
    ! t* F5 M& o1 r- q通常我们会将配置信息以key=value的形式存储在properties配置文件中。8 Q2 A7 l4 K' y+ `  V6 X) J

    & s$ g, w2 ^2 u4 u通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    & C- O3 x5 Q0 X9 r. |
    5 h- J" [1 ?0 l; m$ l2 V) \! u@Value使用步骤
    ' Y8 o8 p: H+ K! O7 d  i
    9 d6 C) z4 d1 K/ F1 ]步骤一:使用@PropertySource注解引入配置文件0 D7 W( v) s2 H+ `* n) A& m
    0 S# {9 L$ n/ j6 \: n2 ^, a
    将@PropertySource放在类上面,如下, }6 C5 s2 G1 A2 E

    3 `4 Y$ h  E8 @. S5 v@PropertySource({"配置文件路径1","配置文件路径2"...})
    - B6 S9 {3 j6 q6 r6 A2 [@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。; P" m8 w) {1 R; X! ^' f2 n+ J, c% Q& f* L

    3 M$ \# U! f" ?8 L  n+ m如:: M( L/ _% B( j5 _1 ?7 |

    ; I" P* y$ a! S" [@Component
    3 C0 P# ?% i- w) {2 o$ H@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})! I' Y" j' s& Q  T* J
    public class DbConfig {6 A# J; [# g. c( o+ @+ P4 P/ M
    }
    0 ?& }4 M. j$ x7 H" J) {& n步骤二:使用@Value注解引用配置文件的值
    % L( ~. B8 v- N/ ^# n  q" T* H. P
    ' u9 {0 O; i0 |4 Y- `$ t通过@Value引用上面配置文件中的值:2 |+ d/ l* i2 x. M$ f6 A# v
    ( d$ z5 N& c! k! Q1 u
    语法
    4 Z, l3 M4 N2 u6 j# x4 Y; s; r4 L# i9 z: y( c& }$ Z' U1 f: g! _/ {+ f
    @Value("${配置文件中的key:默认值}")
    # |: q$ F9 N' L& e1 {5 T+ z' |2 O6 |@Value("${配置文件中的key}")
    / J5 i) h2 Y/ a9 Q+ |, o- d7 U如:# M3 \  H2 T4 w
    6 Q( I: q$ k+ y  H
    @Value("${password:123}")- X6 h) h" {* m2 y
    上面如果password不存在,将123作为值5 }/ M8 P  [4 s0 C! S
    # m2 T. ~+ T/ B+ Z
    @Value("${password}")
    7 E) n7 X0 o8 g# h上面如果password不存在,值为${password}8 ], W7 W1 I% E* n  t
      J% B% l; P# t3 r" L+ y
    假如配置文件如下6 V! q; k: P% n* P( S: b
    & n7 `- s4 e# B' @3 z
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    : P; g; p3 s5 B: E) f- B5 s9 wjdbc.username=javacode) U3 l1 D# ~1 c, ?6 t5 M
    jdbc.password=javacode
    , |& m) K7 S% [% N; N4 W# D使用方式如下:1 _" r  p; T# @; B! G/ L4 g9 c. Q
    3 [( ^  x! \9 H
    @Value("${jdbc.url}")
    8 }+ W9 O, G( A8 L5 f$ c- o" t6 ~8 Jprivate String url;
    ! |% n* E/ d! Q! @- t/ [5 M. k+ W3 Q" _2 ?
    @Value("${jdbc.username}")
    ( M2 v- H; w' J0 Jprivate String username;
    2 @# H  P7 R, E" h0 w/ B, U+ r. g% ~8 D. g& O8 L! \3 v
    @Value("${jdbc.password}"); @( L' U7 X- ~( N
    private String password;/ [" S& j3 N; E6 }% Q
    下面来看案例% ?7 _" c. u* g) d2 [7 n7 B

    * v7 ~6 [, Q0 }# i& `案例
    . [9 B" G4 \! N2 r+ {3 `  C0 S+ J1 v, x' U7 C+ R
    来个配置文件db.properties
    % f3 H! X6 D% l1 G0 }5 \/ ]4 U0 @3 f2 d8 P" y2 n8 ]
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-87 u# D3 \) m# E
    jdbc.username=javacode" E# \, c2 b% H1 q/ z6 a( T
    jdbc.password=javacode  j# v8 B/ @% y, @" c8 D) f' e0 U
    来个配置类,使用@PropertySource引入上面的配置文件' I7 _6 q) W% M( r/ l
    ) Z' B5 i6 g# p
    package com.javacode2018.lesson002.demo18.test1;4 h/ u/ \/ D7 }, O% f1 K
    / `7 D2 H! ]8 }7 V; m; i
    import org.springframework.beans.factory.annotation.Configurable;
    , R; i( Y; A( Q+ yimport org.springframework.context.annotation.ComponentScan;# @8 ^2 k% c4 g# Y
    import org.springframework.context.annotation.PropertySource;7 a  b) j1 L; B

    * `8 q" d2 M* `5 ]! }* W@Configurable& f" N$ |1 e& T1 R0 M2 Q
    @ComponentScan4 I7 k5 {2 W! v; j
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})' s) _) W) O' L( i$ h
    public class MainConfig1 {2 q9 [% Q$ n8 X  l# [
    }
    # C# p/ v" [8 Q5 B" w来个类,使用@Value来使用配置文件中的信息
    3 H2 {- o+ K' I# Q/ ^- H; d5 X4 z  K3 K  c5 [! b- g
    package com.javacode2018.lesson002.demo18.test1;
    ' f6 y3 C! @0 s, @( Y9 S
    ) N: r2 x7 j( Q7 rimport org.springframework.beans.factory.annotation.Value;2 V5 E* N. O/ Z; E& K% G* V7 ]" }3 x
    import org.springframework.stereotype.Component;
    ! z* q/ B0 b( u7 G# Q" |' c1 Y5 E
    5 D+ w7 @) I3 G( U$ H" Y@Component$ o) a1 }% S/ ?. O& h/ u( g
    public class DbConfig {' p2 A9 u4 U: d& _9 r
    ( _" M6 x! E0 M, w/ E! {
        @Value("${jdbc.url}")
    2 l- p& y: r  S/ |1 g' ^    private String url;  Z/ c# d$ }- r! x8 c

    . J; ~& H# b3 u7 G/ a! T. D    @Value("${jdbc.username}")& P. \& A  x6 N8 g5 V8 {# O
        private String username;& }3 l3 ^8 T( Q0 v

    6 m9 X! v* S: G4 C5 X2 z    @Value("${jdbc.password}")9 b8 Y$ o2 s( h! Z
        private String password;
    3 ]( v' S! G* M3 n* U2 i$ G+ [" f) h& z$ v
        public String getUrl() {
    * {1 b5 j) l- x7 l        return url;" z, n: A  Q3 _6 Z* h" V1 Y
        }
    " j" b, z; T" {. }9 k; \
    : m" k5 ^, H' O; F9 o3 k1 a# k    public void setUrl(String url) {
    6 o8 s8 |5 @; B0 [9 A        this.url = url;7 Y5 d0 A# Q# f/ B; h% j9 `1 w
        }
    ) L/ j8 I* _2 r; [4 L/ C/ n- [; d( ^, X" c1 p" C
        public String getUsername() {
    ' Q5 I5 ]$ D% b2 W9 V( w9 f- R. p        return username;) p$ N: I4 i4 e8 U
        }. k/ ^$ J" X" b/ a

    5 T: k$ x7 C" l! ^* t    public void setUsername(String username) {2 q  y; C' n7 @% h# `" M( S) n
            this.username = username;
    3 D+ c4 H4 f' q0 m) S3 ~    }9 d* ]/ H% E" j9 g7 o
    ) p6 u) _6 z$ \. n) V: i
        public String getPassword() {+ t* @  L: q- X6 i' N
            return password;0 c) t- h' k5 ~6 k( I: [& I6 i
        }. \3 ]9 x8 a3 v" ]' [; f
    3 S: b3 ~: R2 J7 h
        public void setPassword(String password) {% G2 T3 d- M, `. y1 |
            this.password = password;2 {- m/ B) N% z4 `) a% @& w6 X
        }3 |) Q9 v, c2 c7 H, d' Q' |" V
    . m$ y! ]1 y7 v5 i
        @Override
    ! x# ]9 t( c8 u) {/ |& \8 u! r    public String toString() {
    / O. W" t$ e  k: K        return "DbConfig{" +
    ) u- ^" m% N3 M/ C                "url='" + url + '\'' +
    ) G& P7 m1 W% h. W  t( Z: d! f% @/ _                ", username='" + username + '\'' +
    8 R7 e6 d0 z5 l+ {: l                ", password='" + password + '\'' +* S7 F3 d+ ~) X1 l
                    '}';$ v/ ?$ T' E$ e9 o4 {& l$ e9 F
        }/ u# r! }. r8 \
    }
    " D4 y, c* ~) ?: n2 x: U上面重点在于注解@Value注解,注意@Value注解中的( E& w9 a& F6 ?: F5 Z5 O8 q9 H
    , H5 C" {) p  E
    来个测试用例) z6 _. \7 y9 S, X
    5 g4 Z2 b% L" _5 a" B$ Z) z# ?
    package com.javacode2018.lesson002.demo18;5 r/ [. ~( Y3 M' L
    8 N. q" [6 P# k0 T
    import com.javacode2018.lesson002.demo18.test1.DbConfig;) [8 c' v3 p3 |8 y7 C% ]) n
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    % K; T2 ^5 J* F0 dimport org.junit.Test;
    - h0 `! h9 q6 p# himport org.springframework.context.annotation.AnnotationConfigApplicationContext;9 q2 [5 t2 S) d+ M5 Z+ i3 t9 V
    $ x1 Z) G- o; I" w. d: @4 Z
    public class ValueTest {
    1 l% r9 u- L. R: X6 \# @- }
    % A! K1 A- Z. s7 ?8 z" u7 m5 m    @Test# v. }5 N/ o4 n0 G4 l# @& E
        public void test1() {
    $ Y+ w7 ^" [+ c6 S1 d        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    $ [6 i+ J0 L0 ]9 T; G        context.register(MainConfig1.class);0 \: ]" @* }3 M& v3 @2 ?
            context.refresh();4 P# s9 X. C8 L+ C1 }3 p. N
    # {' v: J# Z4 i0 ^4 O. k& z
            DbConfig dbConfig = context.getBean(DbConfig.class);) M* K( ^" R( X! Z: V) u
            System.out.println(dbConfig);
    , |+ r( d6 c3 g) ^& {- k    }1 O1 J$ e. ]+ X1 P' U3 z( f
    }
    4 r& ~+ Y) h) W/ I  @0 F7 ?6 E) @运行输出& e$ k: M, c5 [) ~' \# f

    4 Z) U1 E5 o" rDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    " M# i* z. t+ E! H4 m# e& d7 l上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    5 n! M2 Q' v. r5 @2 @1 e- U
    4 k' j) G, q- g/ u, X@Value数据来源
    0 l8 |/ m! F  j1 K1 h' W6 _: r$ o5 B9 {/ |3 \! F  w  J
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。# a- Z: x& `6 c. c) e4 [; y3 w
      v# {# y3 |. c
    我们需要先了解一下@Value中数据来源于spring的什么地方。7 l  o" l( ~0 s1 \

    - P' G* p) n- |1 Kspring中有个类7 B- L# Z. t* c7 h% y, J/ x
    3 _' }) J5 P- l+ [, K- ?7 [) t: C
    org.springframework.core.env.PropertySource
    & |6 G3 ?3 _2 Q8 v3 \$ g3 B$ L可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息7 B& U, P4 X0 m* p$ J4 E

    0 V0 `9 H. ?  l5 O! W$ T内部有个方法:; v3 t! J. R7 A0 V& `' T2 W

      W7 @- Z1 F! @8 c% l" ?6 d2 Bpublic abstract Object getProperty(String name);& I# Z7 f" v# [8 g: @6 E
    通过name获取对应的配置信息。5 p+ b& c/ w3 J8 \: y

      q! a- \$ z" e) ^- I) r系统有个比较重要的接口
      T) [+ `7 r( T$ r# z* q- L% R6 l9 \- j9 j' p$ p/ f" D
    org.springframework.core.env.Environment
    8 b* I" I7 l& c用来表示环境配置信息,这个接口有几个方法比较重要
    3 e4 Q" g0 P6 q& Y3 l5 Q- U& w: b
    . ^' P6 f% w6 F1 g, `+ Q6 L# C+ L( u; nString resolvePlaceholders(String text);) i, a; k; E2 c' f8 E
    MutablePropertySources getPropertySources();$ ?: I9 g/ G' E! |7 o0 \) W
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。& m% l  \! `/ J$ k/ r) V: F
    ( W  \4 I9 E# I3 O4 Z
    getPropertySources返回MutablePropertySources对象,来看一下这个类% V8 F5 s& [( I( h0 s8 e

    3 c* d, x* I" W* u, N" h8 E' Mpublic class MutablePropertySources implements PropertySources {2 v- a5 ]# [0 E; _; u
    . G+ x7 S$ u9 ?% [1 t
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    6 |1 c# ]; |% Z: n6 m) }
    2 y+ S2 a; O/ j5 Z5 {}
    - `; e/ |4 y* u* O1 o9 n2 L内部包含一个propertySourceList列表。
    5 `# @, i9 ^: C# P1 ?5 {1 b) y
    6 D& C) L$ C) J! Y5 p! j9 F( Cspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。! ~6 z' y1 y/ ]4 d7 W; {, n' R

    , h. O) E, g6 q$ n$ b  y大家可以捋一下,最终解析@Value的过程:2 H6 e" g% e* A& V7 V: d# C. Q

    8 S% m2 K( u. o6 L- ^; o1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析! A2 i1 S6 r, @3 `, }: O; a  o
    2. Environment内部会访问MutablePropertySources来解析
    / T) s8 }" ~6 H: ^3 B8 x( h3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    * H/ K8 q2 w; C/ K' A) S通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    : Q$ N- l$ Q8 `3 {* `9 |
    7 y4 Y5 z' E" ~! S, w) N# `# X下面我们就按照这个思路来一个。6 C9 M. \) ?% q2 A% G( V
    , W; i# Y* j3 r/ r/ m; d9 i
    来个邮件配置信息类,内部使用@Value注入邮件配置信息/ p- m) `8 f  e& ^
    ! C3 |. D4 P+ S) I* K5 u
    package com.javacode2018.lesson002.demo18.test2;7 E$ }7 n, c6 B/ n8 o
    , ~" H  D: ], N
    import org.springframework.beans.factory.annotation.Value;
    9 C8 w& v$ h: ^# f2 T$ L. {9 j9 oimport org.springframework.stereotype.Component;, Z2 ~9 H4 y) n% F  Y( m6 y

    + j& {) \) M0 ?8 |, {& P/**
    * P8 G$ e' {) j8 f9 G * 邮件配置信息" H$ q4 P' b4 ?! M/ P
    */: ^' f" A+ \1 B  y; p
    @Component+ R+ f2 Z: @3 @3 L% |
    public class MailConfig {
    # R% ?; Q& G& k7 S; P. b/ J$ T( G& z
        @Value("${mail.host}")
    4 E0 k3 `1 j# w0 |/ Y& B1 C    private String host;1 u" W- u' g5 W3 o5 B. ~/ u
    / _, s5 v3 {$ [! ~
        @Value("${mail.username}"): o+ d! P; }9 Z/ U: T, e
        private String username;
    ; g# f6 {( n2 i2 Q4 L
    ( _; y( ^% J5 e, G+ o    @Value("${mail.password}")! \: |7 [6 v7 J0 k8 n3 c& F  Z# l
        private String password;# m2 b$ ~8 g5 A
    1 a1 k8 Z3 l+ z: L' }
        public String getHost() {! p$ A3 y7 l8 p2 T# T; J
            return host;  B. n% M' t* n0 \7 s
        }
    : i, U2 G+ x# I% \& i2 h# W: o1 K0 \2 E2 r* U$ B+ V: f# E
        public void setHost(String host) {. i3 C3 w: O) m$ s: R) t% H
            this.host = host;
    8 m" m: Q; _; M$ r    }. f8 d% i6 L  N
    ; x' C) n/ i& R) b! D& A1 l
        public String getUsername() {
    7 M7 i" V& q$ ?. P* K. ^9 I/ Y+ V        return username;" @& J' }3 d3 E6 `
        }: w7 T  s* V; \9 H4 w" e/ F3 D

    0 b, a+ h+ _1 e    public void setUsername(String username) {$ U6 j* F# T, ?! {- \" c
            this.username = username;+ ~* ~: g3 W& g6 ]: U4 v
        }
      c0 X2 `" x: K. N/ g
    5 f4 j# g! I$ ]9 w" t( C$ }    public String getPassword() {
    + a2 `7 ?3 I: P& {( Y/ _        return password;
    * g) i0 K6 K! L    }! @5 v4 \! O2 ^1 k
    % q; |- A% |$ [* b/ T. I. y
        public void setPassword(String password) {
    ) U! W( P: t$ z3 u! R        this.password = password;. \" [$ n" o: g# _* z
        }
    / H7 V7 G/ ]& \7 ]4 r% r' e! T
    4 Z- X; i5 h# m* T    @Override/ w8 j8 j8 u% n) N. H" e
        public String toString() {  t2 L3 o: ~; f
            return "MailConfig{" +
    * p2 |8 M8 |" b3 V: h                "host='" + host + '\'' +4 j* F2 I# c- D9 A) q7 C
                    ", username='" + username + '\'' +1 M) m$ R. ]" _7 h2 k9 f
                    ", password='" + password + '\'' +
      o+ v; h( O/ R                '}';) a+ |. B1 U# B( v8 K' l
        }
    4 J- T# i. \6 y# ]* e; D}# {6 ?7 S4 P( b! l
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中( {) @8 Y6 Y  d  ~3 J
    0 n6 E1 ^* y! V0 O( \; L7 l: x& t
    package com.javacode2018.lesson002.demo18.test2;
      \+ ^, ~* U0 B2 s& q9 B6 N. \5 M% M0 X
    import java.util.HashMap;" ?1 K+ |* U' i# s8 N
    import java.util.Map;6 W; C; Z8 C9 N; o

    * ^1 j! W3 o- `" Kpublic class DbUtil {2 i0 Q7 y( ?' V; J1 Y! m
        /**
      B# E5 X6 f5 w     * 模拟从db中获取邮件配置信息4 Z( u, j1 q1 x" v( C6 |7 \+ a
         *
    . ^: ^6 U( ]0 {7 F' `     * @return0 N* T+ P6 O2 g/ _- N7 \
         */8 F7 l6 w3 c5 \5 ~" G, E4 k) g* F  b! r
        public static Map<String, Object> getMailInfoFromDb() {" W4 o* |- ~9 k  v& [) S" r; I
            Map<String, Object> result = new HashMap<>();
    & @' U+ t1 f& `' ?9 y        result.put("mail.host", "smtp.qq.com");+ [% T0 p3 V1 q) g
            result.put("mail.username", "路人");
    ; T% |. E. M2 }. y& G6 t, @        result.put("mail.password", "123");! S) y) K$ |, q( G5 F
            return result;+ I/ ^) `+ [+ _1 z1 G4 S
        }) x: O2 V& f- w! {# Z# v; K4 ]
    }- k- R4 n7 D9 K
    来个spring配置类9 X* D! c6 w6 m: Z

    4 A( o$ L/ O5 ^' opackage com.javacode2018.lesson002.demo18.test2;
    5 i* J7 m7 g, J# B! h1 {/ m5 _& _: B% b! ?6 w) d7 x( L
    import org.springframework.context.annotation.ComponentScan;$ W  h$ q$ t9 }4 g9 {3 }5 X
    import org.springframework.context.annotation.Configuration;& i' ~. N- X4 p2 v! ~2 v
    1 D& @- i; L" J
    @Configuration
    ( A/ E. z( T+ m8 x. _5 L) X@ComponentScan
    & G; e" Z+ |( ]public class MainConfig2 {  r2 {6 |! C4 h2 H4 @
    }& I3 d0 U0 ~" A" j* F
    下面是重点代码
    ; u4 k; ?2 X$ X& B0 l$ I# a$ j$ Q8 ]6 b$ r" {* N4 M
    @Test
    % E6 A! V0 A3 U% `8 Kpublic void test2() {
    $ K; Q8 t- C) E7 a2 G% U8 K+ B    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();* p: v7 J" b( d- J# P7 ^
    7 E0 f4 p, O$ p9 W& @
        /*下面这段是关键 start*/. x% C" `: m2 W3 k$ N
        //模拟从db中获取配置信息! q  W- g3 L4 }" o. K6 ?  x
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    2 a( R( J( w$ C$ L6 C( B    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)% U' h, w. q2 d9 v4 Q
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);+ v3 g  x& H) Y/ Q) ^% s
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高. i1 ~, m2 E* a
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);7 D; z9 @6 L. i8 a' h
        /*上面这段是关键 end*/; _+ c/ J" }+ O5 u  `" N! r

    ; [: N; P) h) w$ Z# D* `    context.register(MainConfig2.class);! @; h; j$ I* q1 f4 b  x
        context.refresh();
    , d7 [' L; q- f0 f  O  Q2 N! Y  n    MailConfig mailConfig = context.getBean(MailConfig.class);& ?% _- x; z" w/ ]% ?( x! E
        System.out.println(mailConfig);3 N( \' C0 V1 K3 [0 F. U8 M, d; _
    }  r) `6 G' C/ L/ `
    注释比较详细,就不详细解释了。
    7 }$ }" I! b; A4 F$ H" C' g6 ]. b6 G/ j7 I, O8 G
    直接运行,看效果% Z+ t( X: M' y3 g# m# l4 }) m! Z

    + p" @; [, ]& ]% v/ ~- t2 VMailConfig{host='smtp.qq.com', username='路人', password='123'}
      ?4 E2 Q+ g. m$ D有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    / [% S, X# D& X0 |" i* n& }' D% ^4 G7 m+ h% P$ M
    上面重点是下面这段代码,大家需要理解1 s" n- ]" K0 G- C  M7 j9 Y

    - t3 `: b# L% f4 h1 S" ~/*下面这段是关键 start*/
    & J& [( o( s, |% Q) d& u//模拟从db中获取配置信息% X0 n: o  }& Q- J$ j
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ; Q& Z1 R+ y7 J+ X% ?; `. ^( T//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    / ]& d% k4 ]8 i' wMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);( v3 v! R( M) |8 I  D3 K  Q, A
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    & N- B0 B$ p6 J, gcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    . ~8 c( O: _& m; ~/*上面这段是关键 end*/
    + A2 I6 e0 ]) n8 \, P咱们继续看下一个问题
    - Z8 I5 W8 U% m" g' k2 g0 p
    & `, j: [/ c( R0 u3 P* O, |如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。0 i: ^' A& z8 v$ A/ p: W7 n
    / _* h/ N0 f. _6 b* O( w
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    3 }5 H- |' f" p; I0 E; w# U, o5 ^- `2 S6 b& e7 X
    实现@Value动态刷新
    : |- y/ Z5 J0 p1 a! U! [9 |% M, t- g! t$ r$ U; ~. D
    先了解一个知识点
    , N% ]3 }. O8 @# t; C$ x) l4 W5 E4 r% U! w7 d* T7 k' P& s2 |0 }1 D
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    0 j: }% h) ~# `% v$ w: j
    7 w2 s- P  f3 a9 u  C9 m$ h这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    * `" Y( I' A7 f* L
    - f" A/ P- w4 N5 B) {bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:7 g, ?' k: K* @6 {; E: J

    ! A1 t5 v1 V! Y3 ?2 J6 i2 |+ T* _9 p2 _0 ZScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;' \3 f4 V) @$ Z/ G9 ^7 N
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    0 m! g5 u" y$ s
    + c, e1 X( O5 J7 g: Qpublic enum ScopedProxyMode {+ w2 `( k% l+ }
        DEFAULT,
    6 k8 ?3 n! T+ T/ W( Y  E" _    NO,
    - u) @+ c  ?( x' z9 `. y/ ]% `4 P2 U    INTERFACES,
    ( h- ]' f" u" k    TARGET_CLASS;* |1 ?* C; h& p1 s# @
    }
    ( q8 c' ~: z7 D  T9 ^7 T. T+ [前面3个,不讲了,直接讲最后一个值是干什么的。
    % e" }: }& m8 h) x, E. ]8 ^, q
    % }2 @7 w, U/ m1 F8 I当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    ) E1 o& K3 c1 ~2 g5 R: Y) h9 I0 C- z: ~! |7 @
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。- ~0 H, @2 \0 }

    ! B7 g" g- t/ K+ B, G- U1 ?自定义一个bean作用域的注解- B0 X# V2 O5 r& T3 z3 J% [1 m, b# k
    ; b2 X$ Q- y( V# s
    package com.javacode2018.lesson002.demo18.test3;5 K- T# D8 o& V  |  Q

    3 t7 k8 m3 H1 S# i' f- fimport org.springframework.context.annotation.Scope;6 S/ o3 I$ B$ \* ]/ Z
    import org.springframework.context.annotation.ScopedProxyMode;
    / c5 ?/ l- Q1 ?5 E" T. r4 J9 S  H/ q
    import java.lang.annotation.*;
    ' x, F% Q2 B+ Y" y% i: [, R9 t' W( h3 l! G& h# A
    @Target({ElementType.TYPE, ElementType.METHOD})
    4 U* V  Z! n* H7 P% N8 |& I@Retention(RetentionPolicy.RUNTIME)* d% d0 C) \+ G5 u4 A
    @Documented
    6 W% ~& x! ^' @: ^@Scope(BeanMyScope.SCOPE_MY) //@1
      j! {% k* d8 ipublic @interface MyScope {0 W  g4 e/ N" _' l/ Y  x
        /**+ q& h& {5 A! b$ K  u' @
         * @see Scope#proxyMode()( [/ x& w4 S) a9 A. V3 F& P# Y
         */( S+ G, }5 K3 q  A
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    % Q" X9 b9 y6 U* H* M$ s9 M5 B}
    . a0 o8 @& K$ F& w8 x8 `0 k@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。1 t2 i: J( A0 Y/ H' I$ c
    ) {9 _# r9 S2 v  R
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    6 h0 c" G. d2 o; y; w) I" y! w& ~' ~0 `$ o! L$ \0 T7 {
    @MyScope注解对应的Scope实现如下; \" O: i5 C  e) N) U7 g& A, \

    ; d2 W7 f+ I9 J7 Qpackage com.javacode2018.lesson002.demo18.test3;
    4 d" |1 E  d; E- t1 C  l3 [( h' ~, `% p8 C
    import org.springframework.beans.factory.ObjectFactory;3 w' f, ^' b' u! w+ x" _
    import org.springframework.beans.factory.config.Scope;3 ?, Y( d- T4 D+ ?$ J2 K" Z7 E
    import org.springframework.lang.Nullable;, Z) A0 Y/ c! D

    " k  Y) G/ E& w6 u/**
    , Y' }1 U: G# V* Z3 S7 ~( W' ^ * @see MyScope 作用域的实现' M( F+ {7 c( l0 M
    */7 a6 R2 Z% }9 R2 x
    public class BeanMyScope implements Scope {
    8 `* x& A# U* M* k0 }* p' W% p0 z# D% k* a7 v
        public static final String SCOPE_MY = "my"; //@1
      I# }( Z: G0 v! J% J" S; Y9 W
    , x8 F( G1 a  x# b2 L% _    @Override
    8 s6 }+ {' v8 R  Z: c# M    public Object get(String name, ObjectFactory<?> objectFactory) { ( F- t8 @6 A2 b4 G
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2- C4 n. R) _- e# j, m& g  W
            return objectFactory.getObject(); //@3
    / v  k+ ?! z; d    }
    8 ]4 g6 o/ k, p  o0 ^% P$ ^4 X
    * j: z! a8 A6 |0 }7 [    @Nullable! h. {6 L9 b% N$ P7 A
        @Override3 u+ [7 r, O* J' M7 ^/ b9 \
        public Object remove(String name) {
    5 K6 Z1 i0 J7 k  _( x9 Z! J7 J        return null;; \/ [; w" m8 s  M! J* D
        }7 B) t" O5 i. L- U. T) B; T) ]

    / K  n- [" L5 g    @Override) q8 g, D( P0 G( O6 L% b% U9 t' T
        public void registerDestructionCallback(String name, Runnable callback) {
    % @* T# m3 [$ o5 f. O! |
    0 w' Y! j& r( a% ~% m    }) v7 e$ ~% p( ^0 v+ L

    % l) K7 X' `* L  G6 \7 B& B$ s    @Nullable
    ) Z. G- P. E1 l0 j    @Override
    - l$ h; r1 L( \- M    public Object resolveContextualObject(String key) {
    + B# l5 I& ~; J        return null;
    ; ^, d  i* o, q$ d    }( C! M& i8 J! e+ c+ f5 i

    : @! u2 u/ w( J" J) `0 I    @Nullable
    5 U; g6 W% {  L. T  |! t    @Override
    % p7 H3 L5 r2 @/ Y& Y    public String getConversationId() {
    + t7 m: \, U0 Y2 h; n' b        return null;/ F: q, T- D2 y
        }
    ' j& l) g/ X* q+ C}; I) \# S. W6 g3 P- b7 y# B
    @1:定义了一个常量,作为作用域的值
    ; e, r  m) y6 L8 M2 @7 d4 O* u: U3 |2 \4 b( B
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    7 Z2 ^& M0 z1 @/ g0 b- j5 N7 t" |1 L6 c, l: o
    @3:通过objectFactory.getObject()获取bean实例返回。0 P5 ~1 U! F$ z& m$ w1 O
    0 z$ }: ~& }. `) o; |& _( y$ w( b
    下面来创建个类,作用域为上面自定义的作用域# D5 C9 ~6 ^6 C. p  H- b

    . N: g" }6 r0 i! \/ opackage com.javacode2018.lesson002.demo18.test3;& \4 u# t  h' y+ n- J
    2 u- }- {6 z$ r1 c$ N& p
    import org.springframework.stereotype.Component;
    ! D: u$ Q, E+ W# p
    ) c% Y; e$ n+ B  k7 Himport java.util.UUID;8 S9 o3 S3 j$ [) a
    - v( }) m. W9 a9 k! R! j
    @Component; u9 D! k* U/ s8 a" Z# [; G
    @MyScope //@1
    % d8 g& F" p7 g# l% opublic class User {
    0 g6 J# b0 D1 T, K/ m8 @& e
    . h+ L% m- `1 L( n6 Y: t  G; [    private String username;1 S8 w  w5 {" [2 N
    2 o. ^- Y, `9 ?
        public User() {
    & |2 V8 X% |+ J2 R( u5 X- n7 K        System.out.println("---------创建User对象" + this); //@21 p4 B6 V3 D6 ~5 e- k; m
            this.username = UUID.randomUUID().toString(); //@32 Z1 k& g2 w% t0 p: R' ]4 O9 ?
        }
    / P+ ^" ]* W$ o0 `* k- X* A8 Q4 ~
    3 h8 G; C& G1 U, z; J& l    public String getUsername() {7 f$ X' K* x1 q4 V
            return username;
    % v) L, Z) D5 T    }
    9 R3 }. \2 V' Q9 F# i: I8 \2 p/ F5 o1 x4 v& {8 f, f
        public void setUsername(String username) {
    1 \! a" u* _+ S( i9 J        this.username = username;
    ( o! R; c( r+ m6 c4 I) P9 Y" u    }
    4 [) o2 V/ W' ?' F) \) }' C8 e- E5 T$ c/ p
    }
    , \( K6 @+ F9 h# U" F- y2 Q! a- d) i6 k@1:使用了自定义的作用域@MyScope+ s7 ?7 `- J" _$ }! h, l7 Q
    2 a. \+ |1 z. W  ~: b
    @2:构造函数中输出一行日志9 s7 D: \% s2 J: ~$ _- j1 I

    $ ~3 c& I+ z' |6 B, t, [( I6 U- {@3:给username赋值,通过uuid随机生成了一个8 D- T4 d% i& y4 O- p
    " B$ O8 t0 T( y3 c
    来个spring配置类,加载上面@Compontent标注的组件& ~5 l& Y4 q' ]7 |3 O* ^9 H

    . b" A8 i2 D- p2 {9 zpackage com.javacode2018.lesson002.demo18.test3;5 `0 R8 p6 |+ m

    1 F) ?1 h- J2 _) yimport org.springframework.context.annotation.ComponentScan;; r' y  E; l8 v" q  A
    import org.springframework.context.annotation.Configuration;
    7 G8 ?; T( w6 w4 `7 s4 t* P/ j* W8 k5 W$ y7 X4 G
    @ComponentScan
    9 x; n' ^  I' _* X@Configuration0 e, N$ u8 M  W3 h0 q% \
    public class MainConfig3 {
    ; _' x. x; q, h}/ y9 e; F: t5 X
    下面重点来了,测试用例/ c0 w  J: Y# w7 u
    + ^) y* l, s  v4 P
    @Test, \* {: x- v, u, F8 Z& ]
    public void test3() throws InterruptedException {
      M3 M9 ~) Z3 D, j- j% `; z4 j    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 h* C4 B7 H$ ?! m4 l
        //将自定义作用域注册到spring容器中" l6 u4 }* v5 T+ ^& L$ H& E+ Z
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1; Y, D: h1 r/ M3 V  N
        context.register(MainConfig3.class);. Y% g7 k: ?+ o: I
        context.refresh();9 n' ^, U: D+ s4 s, s! O, a

    ' ^9 n# H  P7 ~* ~    System.out.println("从容器中获取User对象");4 u7 ^' d6 o( z* `. \$ d- L
        User user = context.getBean(User.class); //@2
    5 o6 A3 r2 T1 H! ]    System.out.println("user对象的class为:" + user.getClass()); //@3
    1 c" e) P# E; x; |7 P- b
    % q0 U$ ^. r' X3 }5 ?    System.out.println("多次调用user的getUsername感受一下效果\n");  F2 P4 G5 k! L  m0 J. [! A6 I3 D4 w
        for (int i = 1; i <= 3; i++) {
    : N5 U; m# b; }; S$ X$ @        System.out.println(String.format("********\n第%d次开始调用getUsername", i));% Y2 Q/ ^" w0 l. V
            System.out.println(user.getUsername());
    & R$ \5 i( \1 F# t4 D( ^: G        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));/ T$ o9 h; {  W& ]
        }
    . r8 A% ~. ]; t0 l) X}
    , r5 q, x  g! d. l. K$ L7 d@1:将自定义作用域注册到spring容器中, j2 O1 i: T5 a. u: d* C  O+ H
    4 G& K; ]/ F) b( C" \' B8 g- v
    @2:从容器中获取User对应的bean. Z4 U" n: k( p  p" f
    / f. T5 y5 m9 J
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的, C$ k) e+ ~( _3 x

    6 g7 O" m8 R% i8 l; C& V/ O' q3 W代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。8 F1 H, Q6 |/ ?3 T+ G# L
    ! n* B' [4 x8 O+ }$ j
    见证奇迹的时候到了,运行输出
    $ j) r' Y" {/ m9 o  j7 L- J8 I8 D
    , R6 A0 _+ I+ b) s! z从容器中获取User对象- C0 a4 k; p8 R9 b. e
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127( E- d: Z" X# H3 A; o4 y, ~6 _
    多次调用user的getUsername感受一下效果) q7 v3 x+ T! q# T
    7 I' |1 Y. h" g4 _* e. E
    ********- I- Y3 r3 i! r* Z
    第1次开始调用getUsername- ]; R6 m6 @+ I
    BeanMyScope >>>>>>>>> get:scopedTarget.user
      n' y2 x% n" Q- D2 {5 @---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4* V$ i7 }7 P& R5 X4 [$ j. m
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    7 N. Q5 w2 N+ x* ?第1次调用getUsername结束5 \2 I" R+ P3 g: V0 R& m  x9 Q
    ********1 R& l' `6 \0 o  j8 ?% T

    & {! S$ v0 b3 }* ^********1 K: R$ ~  _( E0 W( M: _
    第2次开始调用getUsername
      u5 W) |0 M, ]6 p  [* m7 [6 GBeanMyScope >>>>>>>>> get:scopedTarget.user
    : g* q, K5 I& ~6 H* m8 I---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    9 U3 S* q/ ]/ P) q9 g( t01d67154-95f6-44bb-93ab-05a34abdf51f( E8 D- I6 i( c( |3 E
    第2次调用getUsername结束
    * n& `; R, a; y7 x4 D: e$ y********
    " t. H- s" o( G- B) M8 g2 b" x- t" g2 \1 J
    ********
    9 a* k) r3 I/ `, F$ T. s第3次开始调用getUsername
    " f' o2 W* T6 Y: gBeanMyScope >>>>>>>>> get:scopedTarget.user
    & x/ V7 W/ Q4 _/ C---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    8 e/ C8 p( t7 z7 c76d0e86f-8331-4303-aac7-4acce0b258b8) x; ~/ F6 p" ^* ~
    第3次调用getUsername结束; G1 G9 l) o9 h* r6 F: }, l4 a
    ********
    8 L0 R4 N! [9 c1 y  U8 L1 U' _" k1 B$ I从输出的前2行可以看出:5 Y& G' X1 i" W0 E8 b

    - h7 {9 G5 n) W" K调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    5 g5 W  ?$ }8 l$ p) y( V7 _! o# r+ X9 |0 |
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。$ Z$ e- x, r- e0 l) A

    3 N  g+ z" D0 l0 X; c. d后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    : w& k& \. S+ b- O' H$ J  C6 E5 `( e  E5 Q9 g/ p
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。3 j  H8 |( F0 y9 @
    ' Y! z, j* A6 Z
    动态刷新@Value具体实现
    ' e, K* J* [2 Z. u4 c" k! h+ m9 g- f8 S% X' R9 V
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    4 }2 n% S4 ?8 f* G, U
    2 b4 g& K% l' H4 N先来自定义一个Scope:RefreshScope+ _2 F7 \! ]/ K! f

    $ c+ O9 I' I" _- G1 J7 Gpackage com.javacode2018.lesson002.demo18.test4;
    $ `; @- v5 z1 C& d3 Q" R" \7 ~0 t
    import org.springframework.context.annotation.Scope;
    $ d( B/ S& |% W2 }; F/ p2 g8 i  ^. bimport org.springframework.context.annotation.ScopedProxyMode;- E8 q+ E4 a9 A* K- t5 _
    * l1 j  l( U% D. ?  g& m
    import java.lang.annotation.*;/ Y: |8 ~" {4 T$ ~# y

    1 h. c6 _# f3 _! V9 B@Target({ElementType.TYPE, ElementType.METHOD})  E# y% l: B  v" A/ B1 E4 z
    @Retention(RetentionPolicy.RUNTIME)! f+ z6 F7 F/ i9 K' U1 |
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    / H$ p' b- s# O$ ~@Documented/ @1 b0 C% k/ E/ S5 Q
    public @interface RefreshScope {
    / h6 E3 p6 j8 B9 J  {    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ! B; d* E$ t+ p4 ?  l2 u}
    ' C5 t% f3 [4 @' [! _4 ^( e要求标注@RefreshScope注解的类支持动态刷新@Value的配置! T& T* t, i2 z5 d; d& s: T

    7 _' ^! \8 c; O8 _( I  Y@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    , h. v9 G; X5 p; U# R
    ; U% P8 Y& }- A, M/ Z/ V& N这个自定义Scope对应的解析类
    5 X4 m, q* K  z/ @" U& m' l) q$ Z. g3 e- g5 c' E1 |% r) P9 U
    下面类中有几个无关的方法去掉了,可以忽略. z' ?) j* N2 q* N. y( P& m

    2 \7 \  `- a, t' S; y1 s5 Ipackage com.javacode2018.lesson002.demo18.test4;
    7 ~$ g& W' q' L  }' W2 w
    / G9 X# w/ }! L7 s5 z% V# c
    # y/ g9 o  Z" @! I1 {; w  R1 vimport org.springframework.beans.factory.ObjectFactory;
    5 N8 L* h8 Q( D- O- Mimport org.springframework.beans.factory.config.Scope;
    " @( g8 ]$ E* o0 s. rimport org.springframework.lang.Nullable;! p0 ?4 h3 \/ ?  ~3 d3 e
    ; s. v# ^6 p$ B* y- O. D
    import java.util.concurrent.ConcurrentHashMap;+ x8 X/ W" f2 O
    & M0 L! l2 J  D8 f) i: g
    public class BeanRefreshScope implements Scope {. h; R. _9 h4 k7 ?

    0 `6 d& i% |/ h9 y6 P    public static final String SCOPE_REFRESH = "refresh";
    9 [1 o9 q9 j' q+ z+ @$ l4 q
    1 z0 X5 p" d9 \    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();1 G6 B1 C/ ?9 l2 @5 G
    + U# W0 w: d6 t! T' {
        //来个map用来缓存bean
    9 N) g6 J- ]# K" W" d. q# U6 f    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@14 l8 ]( L7 y! |

    5 V" [! {8 z+ R/ _9 g9 A! q4 J  P9 p. n    private BeanRefreshScope() {
    , x% `) |) q) g    }5 _) a+ a  j9 e+ U; L

    4 ?9 Q5 m8 {2 _* s: q" p8 U/ T& z    public static BeanRefreshScope getInstance() {& \  u. S& q' J3 M; }% A4 C) L
            return INSTANCE;
    ) F& F( V. R1 l* [, E9 U    }
    4 d) g1 g8 a) \* \4 B# s
    1 _8 I) d) ?: h    /**# E# z0 I3 ]9 H0 ~; g2 C
         * 清理当前
    2 l" k4 Y$ G; ^; |# c     */
    8 V& \! s& j  f( s4 i/ v7 W' v    public static void clean() {
    8 n' r9 v6 T5 b' P& B2 p        INSTANCE.beanMap.clear();
    7 Y" Y' ~' e" b. x% D; B    }
    7 \) z4 r" w3 U8 H2 X1 y( }1 K8 B' @% r
        @Override
    ! p3 d9 \, }- g$ Z  V) A    public Object get(String name, ObjectFactory<?> objectFactory) {
    * |/ _7 P& C" [$ T        Object bean = beanMap.get(name);! E: \7 x: w0 C9 C+ F' Y+ d
            if (bean == null) {
    " _2 v  X4 S3 M2 p% X; `7 h/ p            bean = objectFactory.getObject();: d( J* g+ t! R) R  e. a, N
                beanMap.put(name, bean);! [* L6 e8 t5 K& h. N$ P% P
            }# P1 K0 D2 O# v- k9 I4 u
            return bean;
    8 ?; p5 r- ~$ a, V& a    }
    : p: ]3 F) K& S/ v& n8 s" b% n2 B% n  _3 B+ H, B; {9 V
    }* v. x* L2 O& A4 H& {9 \! z% ^$ }; m% m
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中( D$ @8 c0 s0 ~' F

    ! U/ Y4 u2 x3 i$ V$ J上面的clean方法用来清理beanMap中当前已缓存的所有bean/ |8 N& L$ s& e

    - e1 V7 }& F: q来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope3 m$ O; m' U3 Q2 U
    7 k# ]& T" {  x. Z
    package com.javacode2018.lesson002.demo18.test4;- C5 u# c+ ~  n+ R( j, t
    . g' F( |8 d) y; b  d2 Q
    import org.springframework.beans.factory.annotation.Value;
    / q: ~' v) x7 @8 k8 x; `' qimport org.springframework.stereotype.Component;0 C# n5 @6 ?% S$ [5 v

    ' |# K- F" z# i9 \; Y  s/**2 H% W# H% R. i0 t; k" K) e. N2 \
    * 邮件配置信息
    5 C& c5 R9 X) M */
    - A( f) m; Y4 D' v. k/ G9 M! _$ e% U@Component/ ~0 X: W4 ?# N# B# ]# K
    @RefreshScope //@1
    ; K$ `+ T) j% B* K# G6 ~. hpublic class MailConfig {: v! D0 a& d0 I5 O; k' ?+ s( ]
    & a, f: K  Z8 N; c: j  ]
        @Value("${mail.username}") //@2
    $ F  q' h* I2 }- ^! c    private String username;5 Y& u5 J* n1 S6 `0 F2 J/ S( A

    ( y. F  V7 j2 |# H: }    public String getUsername() {
    ) ]$ o" T8 P9 w* j; V        return username;
    / V: v: ^9 ^# u, }6 `$ f3 t& E- n    }- ]' ~5 e' {1 f; D" i

    + n' x' z. T0 b! |& K    public void setUsername(String username) {0 B6 u: X( p7 i/ N6 c
            this.username = username;
      _) T4 a$ j# A, Z    }0 p+ F7 L8 B8 y" |% q& L/ J8 Y

    + j4 B! F* \* R) [% Y    @Override
    6 U  A( ?7 F" r5 C& N9 K7 ?    public String toString() {% k3 e, J! Y0 J
            return "MailConfig{" ++ v2 X, C: T0 I/ ~
                    "username='" + username + '\'' +5 R' \+ J. z% I& B: I; z5 }
                    '}';
    ; ~9 t* z2 o( B+ E! H" D' [    }& I3 _4 f7 {' X! Q- j
    }/ X6 X% q7 Y$ x! V$ M6 M; w) f
    @1:使用了自定义的作用域@RefreshScope9 ?& Q, E! i# ^* b9 q

    / v; x0 y, X% K6 U" O  v@2:通过@Value注入mail.username对一个的值5 x$ A% _5 |3 [+ |

    ( X6 z$ j. U2 @* t% x重写了toString方法,一会测试时候可以看效果。
    7 C4 k; v$ `/ b" E# E8 U
    : c; V- Q4 ~1 O/ i: V再来个普通的bean,内部会注入MailConfig
    ; J1 L, N( _: F3 \  x/ R3 @0 A& r' i0 q7 U
    package com.javacode2018.lesson002.demo18.test4;
    . e+ F# C8 c" S3 R- ?) J* _; X
    9 O2 P2 X& l& G3 p: Timport org.springframework.beans.factory.annotation.Autowired;
    ! q, P( o, L3 p' h; B- Simport org.springframework.stereotype.Component;
    ' C7 K6 S" P3 `+ `2 j8 V2 w! J( a+ K2 a/ P9 `  S. ]
    @Component9 y7 O5 ^7 g7 z
    public class MailService {
    9 G+ O4 f- D" c    @Autowired. X+ ~; C8 w  ^2 T4 ?3 p2 k
        private MailConfig mailConfig;- I$ l% O+ G, y) X4 U) z
      T7 ?/ V7 D6 _3 j; p$ u) B# E1 z
        @Override
    : z5 f3 B$ i. W* i9 @. p    public String toString() {# ?! G; m$ U1 ^! A: w, ^& w$ R+ e
            return "MailService{" +
      A/ m! ~8 o8 J- e6 g0 H5 C. r' g1 U                "mailConfig=" + mailConfig +
    2 D$ a$ I, ^7 r5 D# C2 \, E                '}';
    , ?" A, T% m: r* E; F3 r5 Y$ X    }+ o, Y* d( u, F+ p7 V# E
    }
    7 V6 f5 i: m0 V" j代码比较简单,重写了toString方法,一会测试时候可以看效果。5 e# j1 U1 e  G  c0 e. C# [. U: j. _
    * J! n, G0 ]+ m' f4 Z1 V7 z
    来个类,用来从db中获取邮件配置信息# a2 Y$ V# c4 Q; H) h; G

    6 g: w* @; S5 N+ i+ B( Qpackage com.javacode2018.lesson002.demo18.test4;
    1 ~" c; g. @! \
    , l( U5 q4 q- `import java.util.HashMap;
    ) R1 v! ^. j! _& h: limport java.util.Map;2 Q4 E. s  i0 Z( @2 Q, e- l2 B* v4 M
    import java.util.UUID;+ D4 a( |, {2 w# Y9 N2 d

    ) T/ z4 S8 A$ ^3 @public class DbUtil {
    ' B6 ~8 m# z, p) P" [2 s3 ~- e    /**
    # J& w( Q4 e) y; Y, n6 J, ~; }     * 模拟从db中获取邮件配置信息
    # {  R! n% b8 m     *
    7 D& l8 u- @! A' [6 E     * @return8 t1 Q! K: i% Y7 f  ^/ ?" @
         */" K* d# M5 s2 Q
        public static Map<String, Object> getMailInfoFromDb() {
    8 A4 J, @( ?/ n9 U        Map<String, Object> result = new HashMap<>();
    5 {, H* z- B7 N: y. P9 X7 F        result.put("mail.username", UUID.randomUUID().toString());
    % I7 l' h. a1 ~/ @3 ?: K2 c        return result;
    + e! l4 M( z6 F9 E/ U/ E    }: D; I1 X  L8 x  X5 b
    }
    7 B5 K/ M' H6 g. I来个spring配置类,扫描加载上面的组件
    & f7 [8 s* s7 J& Q
    ; S! y, |/ x6 d/ A6 F( t5 H1 ypackage com.javacode2018.lesson002.demo18.test4;. n! k6 H2 Y0 W2 u
    . {: D- T$ C/ T# k5 {
    import org.springframework.context.annotation.ComponentScan;, s$ l6 t+ O/ K  @. u
    import org.springframework.context.annotation.Configuration;
    - B# V# y4 X0 q6 j5 f. i7 T/ C' D& E3 B8 s4 M# L4 }
    @Configuration" @( N# q1 E; S3 L+ `& A- B, y
    @ComponentScan5 @  X  @3 U' @' k5 V
    public class MainConfig4 {5 _8 h, t0 d5 P' J* d! }# o
    }
    : n' a( X( g  k/ a- {! B. G来个工具类
    6 f: a/ x; Q, w: f: W: \3 q2 a7 N9 m7 c& ~
    内部有2个方法,如下:
    $ E7 Z% L1 V" k) m" P$ K2 r* n" t4 t% X( P; @
    package com.javacode2018.lesson002.demo18.test4;
    9 m! Q5 n: {4 C/ F: D  @0 A) @
    ! C+ w' ?# [) {3 ^" S3 o& e: z5 Oimport org.springframework.context.support.AbstractApplicationContext;
    8 D2 m& _* }6 v5 k" Z2 Pimport org.springframework.core.env.MapPropertySource;( x' Y" X$ N1 j. o
    " [) y2 c: P' c
    import java.util.Map;4 @: l; l" h0 o$ T- ^

    3 c0 w7 b  r4 cpublic class RefreshConfigUtil {# X+ r. o- S& Q4 ?
        /**' Q" A: L. n6 a- r2 j: o
         * 模拟改变数据库中都配置信息9 Z, v- i, n* t" K/ |
         */4 D0 _3 J) r7 O7 A# R/ R7 E
        public static void updateDbConfig(AbstractApplicationContext context) {5 G. t, B6 X- [
            //更新context中的mailPropertySource配置信息( W9 Y/ H- E: O. w% r
            refreshMailPropertySource(context);
    & u% ~$ c7 L& f1 u
    3 h( t5 X# E1 G1 a5 q1 i8 `( v        //清空BeanRefreshScope中所有bean的缓存
    * [( B) c2 B( H( `% H        BeanRefreshScope.getInstance().clean();
    5 M) b& p* p2 ?5 W  t& p. @& ?. z    }1 I4 A' v# N9 _' C

    3 N- W3 v: h4 T    public static void refreshMailPropertySource(AbstractApplicationContext context) {1 G/ y4 h/ f- `% J5 O( A" v4 s4 w
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    4 ^5 |" d3 M/ |/ v" C! S( X/ A        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类), u. B0 P% O1 T2 D/ n
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);. V, O! r5 C8 U* k% r7 c7 X9 ~
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);, A% B1 f) o3 P6 J
        }' @5 y5 ?* F& G

    . d  x0 y, p6 P" V& T6 g1 t}
    ; S* n& L6 c6 d, W# |# e4 {updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    % P- n- X$ A" n0 `& L9 p
    ( x9 Y/ d7 O# g/ ]# T  JBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。- n1 ]1 h2 a" q6 A

    " r3 p( w9 m/ y+ j来个测试用例
    ( o! J# K8 L* F& Y& I4 a/ L' V" o; B; O' D% C9 _5 T: C
    @Test
    ' }' R. d0 `$ H; b) D, Upublic void test4() throws InterruptedException {
    0 B* H4 v- W: Z. w2 K0 V. ^    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();; Z3 B$ C" @6 U
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    6 J: o. l* p1 d' F    context.register(MainConfig4.class);2 B( R9 h( B. O7 e+ {# o
        //刷新mail的配置到Environment$ x9 I0 H* T1 S  d4 E( A3 B  X
        RefreshConfigUtil.refreshMailPropertySource(context);# A; I7 h% z# {( c! Z% A/ |: n
        context.refresh();
    ( K0 ]4 A2 K' J2 T2 F- i
    4 g; [  n; ]8 q+ O    MailService mailService = context.getBean(MailService.class);
    ; g4 N( o# z9 y% n/ F& O+ \% s    System.out.println("配置未更新的情况下,输出3次");4 P+ a) l/ ^! t' s
        for (int i = 0; i < 3; i++) { //@1) ~* f9 k3 o" [, b) b7 U. t$ l
            System.out.println(mailService);, U" r  d+ F# R8 [
            TimeUnit.MILLISECONDS.sleep(200);
    # q# G0 \6 |! Z- z2 d/ k  }    }
    - \; I) f. `  W$ c- N! H  `" w
    2 ^) X3 s; {% M    System.out.println("模拟3次更新配置效果");- _9 E$ n( U6 b$ b2 e0 p+ w
        for (int i = 0; i < 3; i++) { //@2  u; O: U4 ^. l, }# K6 }
            RefreshConfigUtil.updateDbConfig(context); //@3
    5 J) l1 Z. @: E2 W! a4 I        System.out.println(mailService);
    $ g. F0 r3 P% N. M' {) B        TimeUnit.MILLISECONDS.sleep(200);7 ]" x/ P0 v$ F1 z5 C/ P
        }
    ; E& M& T+ U: u0 o5 k, l" e* ?9 [}! V+ \6 [% v4 N0 J8 }/ _7 i  o# p5 b
    @1:循环3次,输出mailService的信息8 M+ q, G" I  g1 ]; G* l8 C! ^
    8 R, G+ c+ n6 {1 b6 b+ z
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息% a4 `8 C1 Y* H

    8 c$ T2 r) S' T2 y% j! i见证奇迹的时刻,来看效果1 X! G% v! i$ \  d6 A
    , l- H" \4 M( d4 j
    配置未更新的情况下,输出3次
    7 [! E1 k2 R4 p% d% eMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}9 \& Y$ p3 A9 Q) O
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}4 q" h' Z' w; D; B9 a. ?
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    2 G6 l7 u# }5 U模拟3次更新配置效果% }) h- E0 y4 m5 s3 W7 q. a
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    / a1 [* X* c+ l6 @2 E6 `  VMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    5 y3 e( o9 x' f; MMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    # `. ]1 u" t. ]) a6 G上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    9 I* a$ B6 g! M7 Y: K  l% T! w# K8 `' h3 B5 K
    小结
    5 @1 X! D/ M5 w! X9 F- [7 ?# D0 P! d) J# z% O, t0 e, s: ?
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    ( ^6 v" F$ d' ]2 Q/ S1 t; W* W9 Q; H0 c1 _# z( K
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。0 t. H: _  E% T. o- W

    + V3 u# s; K: ]总结
    ) q) J7 F& T7 C% [1 a/ K4 F# }4 y  `# J. R$ M3 ]# a1 c
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    ' o- S. o, L$ i* g/ ]' ~5 q8 Y: F2 f! `* P+ J# {
    案例源码
    + r5 Q9 N6 h& ]9 C6 b0 C& }; K9 n" y5 [" y. c" l1 z4 q
    https://gitee.com/javacode2018/spring-series/ q. [. M- |7 f" J1 {$ \( e
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。$ B1 l' F- h: A- Z" r' j
    8 [/ `% o" y0 F5 n3 y' A" g
    Spring系列' g* [4 I% D3 i, u: `

    + d8 t0 H1 e" [% Z- Y( m. b% B" W2 pSpring系列第1篇:为何要学spring?# ?4 h7 f, h5 d+ f$ G+ t, Y" N
    + G7 {  G  P8 f4 h* n3 s# ]" a5 f
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)5 D& [, \# Q0 }, o& s! u/ ~
    ; {8 z# d% U; s) {3 c- f! {- R+ X
    Spring系列第3篇:Spring容器基本使用及原理5 f5 {& h" B9 w3 Y

    , h0 D  K' l$ WSpring系列第4篇:xml中bean定义详解(-)
    . X  T  J" `( g6 S; Q7 e0 ?0 h/ x
    ! h6 d; A* P5 sSpring系列第5篇:创建bean实例这些方式你们都知道?6 L( P* X5 @" f' Z# V- f
    9 W! g6 K: T3 A$ B8 J& \
    Spring系列第6篇:玩转bean scope,避免跳坑里!0 Y% C. v1 h$ r- V9 _* Q" V- ?
      V$ ~3 }5 g, D0 X6 H; s3 L
    Spring系列第7篇:依赖注入之手动注入; j/ l* a2 P# r0 V( i1 B8 l

    : C! J% I8 F) Q5 v4 d! F$ _& Q/ ~Spring系列第8篇:自动注入(autowire)详解,高手在于坚持8 A& b7 G9 l: E- N& Q$ t" \) k9 y) ~

    & O$ a* a% e4 KSpring系列第9篇:depend-on到底是干什么的?0 s3 Y2 L5 q' w
    ) V$ C1 p1 y, g6 C0 M/ L" ^0 e
    Spring系列第10篇:primary可以解决什么问题?8 o* u3 x. M3 f* r) v* e

    8 L* j+ u" U9 w9 E1 X/ qSpring系列第11篇:bean中的autowire-candidate又是干什么的?* S' l3 Q2 r/ M, n% E

    0 ~3 B( S# q4 J: l: WSpring系列第12篇:lazy-init:bean延迟初始化
    & D, c0 P2 i( y' S( m# ~: f% F! A& l5 S. t
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)& Z0 H: w) S: Y" v; M8 K" A$ ]
    . `) I# C( T8 B* y- k& O& O# O
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    8 p! M/ A- v) B% c) {9 ]$ T% K' D6 y5 M8 C, W3 o4 ?' H. ^- o
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?2 e/ T4 @' B0 `+ T$ B+ @& T5 k  v

    $ }+ H: B) m" X: k+ Q. p/ F2 ySpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)8 v9 W2 @2 p  P! D: [; P
    2 q+ v- w( ?! z) T! Z
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    ) q( }7 k* M  I
    * U; p- ?! U$ a$ z' M. rSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)! N) Y, V& w( [) d7 z0 F

    % Y3 A# o- V2 z2 xSpring系列第18篇:@import详解(bean批量注册)5 D) _3 a# k' R! c5 C1 g
    $ Q/ J, r4 O: J# N! G
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    + z2 D7 L! R9 d4 g. m0 F- m8 m, t8 w3 I& {
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    " @1 a, r* ]2 B( z( t' i9 h1 u5 @+ C% O8 B& W
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解( d4 f- K. A4 Y8 k; N

    ; J% w% H8 \, t. U6 k% \! CSpring系列第23篇:Bean生命周期详解$ {& x+ i. x( I8 E" B
    / N, K( Z2 {& `) Z
    Spring系列第24篇:父子容器详解6 ?3 n* O$ _' l/ O( {$ x4 k. P  k' a

    & N! B0 q0 |! g( Y; Z) V* f' q# n, {更多好文章
    8 k2 \+ I7 T: P3 h5 e& O4 R7 u) C
    , V# t! K) b" P, UJava高并发系列(共34篇): t, d3 C) r; ^: Q* }! E

    6 P+ h8 v6 [, I8 ^1 R) q# ?- BMySql高手系列(共27篇)( _/ K: W+ {- k

    5 A8 y3 R1 X4 p; z/ CMaven高手系列(共10篇); G4 B  _6 T  Z) N- [/ A* J9 {2 T3 M

    ( X: ?1 x. e9 g/ TMybatis系列(共12篇)3 j$ ~, A0 r2 P! }  b

    + {7 g1 K# s: k) ?8 s聊聊db和缓存一致性常见的实现方式, t: P+ r2 ]- [) f

    + m; }, Z1 ]5 Z2 `. T9 {$ M6 z接口幂等性这么重要,它是什么?怎么实现?2 Q$ W/ l) n1 F/ |
    + [9 t6 U/ q$ z9 c
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    & c5 }: K1 b% ]. {————————————————
    - b5 b; u1 _1 l版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    $ t# C( u" n/ m9 ~; E原文链接:https://blog.csdn.net/likun557/article/details/1056487571 D$ ~+ N, ~2 s! {- y
    * v$ @2 o# k5 B# c2 h

    & d, t1 |7 L$ b0 @1 j
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-6-14 19:29 , Processed in 0.467973 second(s), 53 queries .

    回顶部