QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5367|回复: 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!1 O  J' y) M& k! m
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    % |3 a! k4 m/ O, ?! Z2 F0 ~+ J9 f
    + L& Q+ l) M/ @; L面试官:Spring中的@Value用过么,介绍一下, X2 O3 n: O3 N% \# G" E
    ( |* g  S4 M( j# s* a# Q" o$ s
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中4 L, n+ p$ \" q. O% U, q

    8 v, s, ?) A5 m面试官:那就是说@Value的数据来源于配置文件了?% C) f' ~) q& Z  D' ~

    & ?+ B  v$ w( p2 J8 {我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置9 p6 e# K5 i( y4 G/ O
    8 W2 F! Y! d4 r( Z' o4 G* m
    面试官:@Value数据来源还有其他方式么?
    - {" j8 X* a5 H9 F+ v* f, O0 j- Y  X$ T; m: Z7 P
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。/ j; q" n+ b1 N% t

    & Q6 P' E& d* F* N. `面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    ' `2 ~4 G+ `" N8 y, d9 Y
    - k/ Z5 k. w8 s* [; N我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    2 T2 J* T, u( `1 ~# Q
    9 a: m8 J) j  y面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    # j, G1 U8 ]* N7 Q7 F
    1 q% n8 X3 e4 O我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    5 T4 i3 W  X# k# f& Q$ i- a: G/ x9 p. u/ }
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?+ K( d' a+ d0 H7 L1 ^4 x! ?/ z

    7 L% k5 D% B& L6 A  ^( s# b* N$ {. f我:嗯。。。这个之前看过一点,不过没有看懂0 Z8 y' I# u$ P" a% ~  F
    , ^$ f  Y% G5 J; @$ t
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    ! v; \0 \1 J; X
    / r' d! F5 ^- M& }, ?我:3万吧. u5 s2 ~6 J! H

    / b! p% d9 _5 I" `, m& H8 C面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    + l# w) H4 G; c- \4 T; o2 Z& t6 n  X5 {! K1 i( m: T
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    5 q8 r- K3 u& s0 E$ b" ]
    2 W7 F8 d" M+ |& ?$ Z9 o/ |! T面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    # X; N  K% z) m2 F
    9 z% Z  o% `# V% S我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    7 Z' r- i. i# Q& d0 O# u+ B
    . K) X7 z% x( K, x' o这次面试问题如下
    + C2 d' g, U% q7 f
    - i# P( i% f4 B. G7 t@Value的用法
    % f7 Z+ D- H6 e# F* d
    $ h$ I# g  \7 H; I) ^@Value数据来源) v! S8 C. P, y% h* l, ^

    ' u9 S' W2 ?- k6 [3 x@Value动态刷新的问题
    1 |+ o! j% e8 W3 H. J9 q6 G% n3 w  B# a+ P  i0 I
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。# K  F7 o" P: y% ^+ z

    * N; o7 ]& m" U@Value的用法& G: v, C, @  k! m) ?+ R3 ]
    / |7 y4 U* s( Q1 K
    系统中需要连接db,连接db有很多配置信息。
      Y, d4 k9 J; q
    & M" {" s# t# j5 K3 Y. p- B' l系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。2 p% a# |1 ]) {3 d5 u1 t

    8 S  ?* h. I3 C( V7 B还有其他的一些配置信息。9 K0 a& S2 G2 {8 M! j# \! ]* r. Z+ L; w

    $ S1 `( h2 x5 i# s1 W' F* _& V我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    3 j* s, H2 O; G' i! Q( i+ _% v9 _  Z% `' k8 |' X4 ~  X
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。) p* p' n; K; C

    ! x( W8 M* @9 b2 z通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    3 G4 _# W. ~0 @" J
    % |) K, V/ V4 _* z1 P5 R通过@Value("${配置文件中的key}")来引用指定的key对应的value。4 _9 L! }. A8 h5 z( S* T

    3 m9 z. z% J& f8 Z. r1 [@Value使用步骤/ W2 K( e4 K# q: I/ Z" [

    , O* h$ X0 L- J) ~0 Q. s步骤一:使用@PropertySource注解引入配置文件+ q# F+ r7 v7 G; E  ~5 J
    7 k7 z5 {  H" Q' ~
    将@PropertySource放在类上面,如下
    , X3 j% A% v4 I8 _, k
    " L$ U4 y6 |  H1 C$ k* K( ^+ \+ G@PropertySource({"配置文件路径1","配置文件路径2"...})& V: Q5 r# W; J, E% k3 R* J
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。+ [5 ]: u# i5 R
    0 n' C3 `- S; ^% E/ d( c
    如:2 M5 E0 }8 {3 m( m, ^9 C* i

    " |  m2 N+ l9 Q1 _@Component. o( p% O; a" Y8 z; J2 y7 s' J4 j$ W
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}); f5 D( P- E* E9 |% h7 c7 f  l
    public class DbConfig {' L  q! y8 G* o7 g
    }. z' N* z$ n5 A* Z
    步骤二:使用@Value注解引用配置文件的值
    * t$ Z4 k4 y. ~& G( w  {0 w
    ' l5 E# h+ p+ Q" `3 r/ n- M# e通过@Value引用上面配置文件中的值:7 z7 ]  o. R3 l- v  W; i- T

    / \; }( s2 P! f) }1 V$ G2 j语法! X" R( N4 a9 B, s: C+ s, x
    7 S# O, z) e" b! C. b$ P
    @Value("${配置文件中的key:默认值}")
      W0 N; ?  q' F. v5 S5 e@Value("${配置文件中的key}")& `, O5 O+ Y5 t5 V$ z
    如:
    ! l* N: X" x/ D$ S1 y1 k  `
    & W1 B. z4 U6 z+ V# w@Value("${password:123}"): V; M$ v* _6 u8 E, j
    上面如果password不存在,将123作为值
    ( T3 J: n# \" B6 H4 _2 l% d8 M
    . p" `9 w, j0 h6 e% P; ~+ C@Value("${password}")5 ?" s. K& G" p$ b$ H; [$ @  ^
    上面如果password不存在,值为${password}9 G2 P/ v/ z: W7 Y7 _5 s8 h

    2 b* n1 ]3 I; U' g+ ~( v+ d假如配置文件如下
    : J" H/ K3 C" C6 Y' M# |( Q2 }9 E1 L
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-83 y/ I% }, K% j" c7 ?  r
    jdbc.username=javacode, }% o* ]- ?) P. U7 a1 P8 Z1 E
    jdbc.password=javacode
    ) ?) V4 k# w+ b- O, u& K9 h使用方式如下:
    9 h& U# n/ t1 A, i( z5 q1 K: L' W
    @Value("${jdbc.url}")$ f" ?' i- K# G7 Z
    private String url;
    % m( v) m* M" P, p
    8 R* ~0 B6 f2 A$ s. K8 ~# m# T@Value("${jdbc.username}")7 g4 B; n) s2 {# P4 x$ D% Q
    private String username;
    2 `# X* h# |5 t# o% \  F' `& L6 Z+ `+ F: i: o+ o5 {+ u
    @Value("${jdbc.password}")( F, k4 F; c+ J7 {' n9 C4 M
    private String password;
    ! O# w: s1 M- n+ u9 v' q. o下面来看案例
    9 m3 N# ^8 `1 o, e" b
    " Q% @7 n" z0 m7 i" b' M* K案例
    $ g& T2 c2 r: X3 ?3 s) p( x5 v* e8 r+ X0 u9 d
    来个配置文件db.properties
    9 F  r8 F$ a3 e; ^4 u4 M6 ]$ S$ P: \
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8; J9 z7 t4 _0 S- a4 H4 {( p8 }
    jdbc.username=javacode
    " r) I, m  I  wjdbc.password=javacode
    - h& A! C. s- {( l& ]. f8 P) s来个配置类,使用@PropertySource引入上面的配置文件* Z2 Q/ _; }5 U! d, _9 n) d

    ) Y" c! U! y; K# ?' P. j) Fpackage com.javacode2018.lesson002.demo18.test1;
    ' d! b- T3 w4 J$ H+ i
    , I9 ?" P6 Z, z0 Y: r4 Yimport org.springframework.beans.factory.annotation.Configurable;
    6 C5 k; B  s1 ?2 p) Qimport org.springframework.context.annotation.ComponentScan;
    6 L: T. b- X* g' b5 Pimport org.springframework.context.annotation.PropertySource;
    5 ~9 @( v. ]# q+ C* L" ?, t1 J7 i0 V6 |& b
    @Configurable
    * [  E, ]' u8 j7 i@ComponentScan
    3 z* B9 Q' b- U# T$ U" M@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})* H" B4 ^( c% J0 T
    public class MainConfig1 {! X2 x6 s3 y( ^0 x+ S
    }
    0 n7 f  P8 G) e5 x9 ?$ E来个类,使用@Value来使用配置文件中的信息
    7 A% `  }) R4 h+ u( \( v! M$ B; _! l: O( \
    package com.javacode2018.lesson002.demo18.test1;
    9 ]5 c+ g6 X2 V% z- m6 J' }# p+ M; `6 _2 [+ d, K, e  L/ u
    import org.springframework.beans.factory.annotation.Value;9 w) i( F! [( H* d, K" y
    import org.springframework.stereotype.Component;
    9 C- k) i! |; o  v8 E& C  t" Y: \1 {! B8 Q: l) M' k
    @Component
    # x7 z- F1 A) @2 Qpublic class DbConfig {! V  P) m  q5 O1 E9 \

    0 ], F; A' I% P. S    @Value("${jdbc.url}")' t) G3 ], {" M
        private String url;
    9 `- r' ]$ E9 o7 X; B- s$ u  f; ], l  i/ K" m  ^7 J$ c4 N
        @Value("${jdbc.username}")
    9 m0 \& d: l7 D  P; g8 i7 [    private String username;
    + W& O: W" E* ?1 |8 a' B) Y; T
    1 }7 K, |+ y) \! j    @Value("${jdbc.password}")* C& y6 F3 z( c2 a6 y
        private String password;
    , [! l, `6 I; R5 a! B3 F# x6 \4 n+ _0 `+ }2 T
        public String getUrl() {
    1 B8 }, p+ P' X- x2 Y7 y/ F( k        return url;
    - t7 W" f$ @# ?% X    }: x- I. L* O- e7 x- d- x
    " q4 \3 m& q% f/ G0 _. f7 ^
        public void setUrl(String url) {, L$ D6 i( o5 _1 @3 `5 x
            this.url = url;; U9 u/ T# `5 k/ [! `6 a. }& U; y
        }6 f, K, S8 F3 L( q
      T# E# Q9 f, K/ y5 {8 |
        public String getUsername() {' ^7 i  l' b* m6 J2 j
            return username;
    5 k4 y0 w" }  V, }3 A$ O+ S    }
    ! r4 t" c' Z  {) y. H$ `; p' b) x' @: L3 q0 |  g: G! X' P
        public void setUsername(String username) {" D; e7 M% V& V
            this.username = username;
    # h; |# e5 r+ T9 T3 ]$ z/ {# r  [& [7 i8 J    }
    9 g( u9 }% @; {; ]( q: U5 @9 D) K+ c- H! W7 S# Q
        public String getPassword() {
    + h0 m, v' P* v" O4 D" k- q! E" Y5 s        return password;
      a' W9 T" K* G( R    }
    ; e( R" v; X& u5 e! A$ S
    ; ~) t. w+ b2 N  ^5 J" [4 Z1 `    public void setPassword(String password) {' z8 q2 K# O" f4 i1 [9 M
            this.password = password;* y+ H) o: f3 L2 @4 l' ^0 ^
        }
    ' v( h6 s& \% W% z" }
    - \1 z& W6 j5 n( F6 D9 n0 O    @Override  o7 @! D, t: a
        public String toString() {
    1 q4 Y$ ?/ w0 d" Z4 ^        return "DbConfig{" +) |/ d+ ^3 q, L/ [, z$ r) J
                    "url='" + url + '\'' +
    * J) |% I# y: P                ", username='" + username + '\'' +  D* P3 |- ~7 |7 \
                    ", password='" + password + '\'' +
    # @$ f1 E& o$ a                '}';
    * j2 g" h' c' y/ t" N/ b5 P    }
    ! @5 u$ k9 K' H}
    0 x" \' O, A  Y7 Y- _0 \/ w9 A# q. ^上面重点在于注解@Value注解,注意@Value注解中的
    * K& v0 k. V0 ^' {/ Y7 `
    9 X+ X6 w; `& d) _* T! {来个测试用例
    . `- F) |7 h& K) h) @& \+ F
    ' A% V6 ^: T8 J# c4 M: \* rpackage com.javacode2018.lesson002.demo18;
    8 E2 P8 B, d3 j2 H# K$ n9 E+ |/ f9 m; Z3 G0 N3 `
    import com.javacode2018.lesson002.demo18.test1.DbConfig;! w: Q7 n% S( g/ S9 j" z: B
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    9 w8 @) R# A/ P/ `8 Y- A( x- K0 Uimport org.junit.Test;
    2 S0 N2 B8 l' D) M( S; H) Aimport org.springframework.context.annotation.AnnotationConfigApplicationContext;* @& g6 k! I3 |9 Z6 E7 ?

    2 Q5 e" @2 |+ x! apublic class ValueTest {. t6 A6 T: N. C1 K
    . H2 G1 j& L* G  k! n2 v6 Q, H
        @Test
    3 o% H; P+ ^- z/ u3 z" T    public void test1() {
    6 D, Q$ T! t* p; v        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" A, e2 w2 h8 J6 D$ m1 \9 f
            context.register(MainConfig1.class);
    6 U6 N- ], V* Q" L5 g: O" E* {        context.refresh();
    & m/ E* {7 i  h$ a+ h" [5 ^
    % H6 s4 j4 I" `        DbConfig dbConfig = context.getBean(DbConfig.class);9 i2 Q9 R# ?7 \& J- s7 P& Q% ]
            System.out.println(dbConfig);
    0 l. j' |' C9 g" P# v1 r    }$ H3 r+ A  B' ^- w) N) T
    }& d: n1 s% v. E6 H
    运行输出
    4 \) [2 \# r& A( E( J/ d' [
    3 G  p1 E- T/ f! YDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}4 C. X& }+ M) U8 ?$ F
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    9 _( s& p2 ~5 ^+ D2 B7 N/ q$ P; n
    @Value数据来源7 [! j( X2 p* h$ ?; U. g) x/ ~

    & D% A" I1 {' U% P* L通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    ' z7 w+ S4 }0 p# }
    $ H4 o  Z7 N' X我们需要先了解一下@Value中数据来源于spring的什么地方。4 p, L% n$ i3 x, A5 Y9 ^
    % t$ y- y0 [7 Q9 [
    spring中有个类( G% i" s# o' I6 d& q
    6 x; O; L! o# n( b" f
    org.springframework.core.env.PropertySource; R- x6 G5 n8 J2 H* b# G5 a
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    & Q0 `8 {- X) U+ m3 t: n( ]$ f$ T6 T
    % g: r: D" k% ^0 y4 [内部有个方法:
      S( i6 D- L! E7 g- M7 E- [, i  K& I& r! b6 u1 k  P' n! A5 z
    public abstract Object getProperty(String name);4 Q# s3 j. G" n9 D0 |
    通过name获取对应的配置信息。
    * ]' h/ Y: _6 W& I; o
    / Z& ^5 T8 G* J8 k5 ]系统有个比较重要的接口9 L" v+ r% j3 F" p. i
    + v2 c7 K' T. q* Q) b0 X
    org.springframework.core.env.Environment8 E! M* ?6 t0 W# D. ^( X7 k
    用来表示环境配置信息,这个接口有几个方法比较重要1 A9 X, C4 m+ N
    * W2 u- h6 K, ^: ^
    String resolvePlaceholders(String text);& |6 x" i. N( V$ Y* e3 R4 y$ j
    MutablePropertySources getPropertySources();' K! j$ x/ ^' d" i4 Z
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。! q2 q- d# r' J, w( T2 L; {

    : c) I8 h( V; W- r/ }: l/ VgetPropertySources返回MutablePropertySources对象,来看一下这个类/ G$ x  B. x1 K) A

    : J) h. [) g" A3 e) Bpublic class MutablePropertySources implements PropertySources {
    0 U: v& q. H: L. l1 G2 Z
    " B. L! f6 Z: k0 G! P' \    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();' Q! ~& g" l. }
    * m4 c9 G8 Q+ A5 {! y' F8 {3 G
    }- t3 q2 i% r% b" @* J
    内部包含一个propertySourceList列表。
    , m" h6 A% u: |. x
    2 j7 p: d4 i; A6 e' Ospring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    2 }) _3 |! K/ i4 U$ K
    . T0 A) a9 S, F. U6 L, x大家可以捋一下,最终解析@Value的过程:
    / q( E/ M6 K8 e8 l- V& T' Q+ o4 J2 ]& y8 ?8 B
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    , ~  j4 g* O- V9 m+ u2. Environment内部会访问MutablePropertySources来解析
    # R& A& [* R3 ~$ _# L# L3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值2 A" p1 D* T7 w  }) R9 a
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    + L1 i0 I6 ?6 G' n6 {9 S: C) R. D: Q- }
    下面我们就按照这个思路来一个。
    & |% L  u8 f7 W  f0 H: f2 u2 H9 _3 n7 {+ |/ ]* s  o
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    ) h. r8 u3 k  S( y+ z/ M/ w1 B/ ^( N/ Y  D' F  w# {! H7 V/ t; b- x
    package com.javacode2018.lesson002.demo18.test2;! b8 \4 T! E) p9 j: ^" P

    * m  z$ F$ w) ]. u6 Nimport org.springframework.beans.factory.annotation.Value;
    7 I3 Q1 _2 {" o4 s4 I0 `import org.springframework.stereotype.Component;
    ; D* P( U+ m' M  T: d& O- ~
    / Q) O, B& J) I: m- [/**' {, m8 r$ u& l0 l6 }
    * 邮件配置信息
    * m- z+ U0 T0 T* M/ m' { */- [" \5 m5 F/ m9 W" Y7 B9 h
    @Component
    $ O0 F! f% ]+ f' q0 s% q0 y$ j! _) vpublic class MailConfig {
    % @& S0 z7 n7 g6 Z% m$ J( m7 v- ]9 Z# I7 r1 F% E/ V: G
        @Value("${mail.host}")
    5 a, X9 o4 m9 S+ t( T    private String host;8 ^# P$ j" i; Z7 [) Y& P/ F% S8 I: \
    & E- c) a: U0 R% K* E! l
        @Value("${mail.username}")
    ' z; }3 {' ]4 F; e# f$ O. C; q/ S    private String username;* Z1 l8 r, D2 V/ M: V8 L) c
    . \) `. C5 l1 I% N* y3 Y# Z1 q
        @Value("${mail.password}")( e0 t/ q" X/ R! J4 B
        private String password;% G5 i7 O- V3 v( l! v* _+ ]
    ! y! M2 \0 o: m# ]/ o: X4 R; J! \
        public String getHost() {
      _  c' U5 D/ w  ^% ^1 C4 I        return host;
    - k+ ~8 I( _5 \3 C. E: C' r    }9 H' w- W: s" s2 ^: G1 D
    % o# f/ P. O$ C( Y) x3 o
        public void setHost(String host) {4 g3 _+ @/ N" J9 {, L$ D: |/ k7 R
            this.host = host;- O3 s* `) M( E* `
        }
    + U4 m8 l5 V. D1 y
    0 N) o- E4 `' F/ _9 G$ ~3 N8 |    public String getUsername() {6 Q* {5 q! D. z' [( F) H. @5 H6 H4 r
            return username;) E, p0 N' P+ N
        }
    9 a! _: i4 d% r8 c7 Y# Y/ [$ ?- S% r3 K- E6 w
        public void setUsername(String username) {. v: F. g- n4 ?+ x
            this.username = username;8 b$ |# Z2 [! l& U
        }
    * d+ x: ]+ U+ k7 Y5 B* n* a. c
    9 y* a# k0 S; G$ p) u7 o% K8 s. m    public String getPassword() {
    9 X& ~% C) \" d8 I        return password;6 J. z& a% j! e4 P( Z: r
        }
    # D$ {1 e; F) C& @1 V
      Y. x, P8 u3 {5 O5 {    public void setPassword(String password) {3 x3 g2 ~7 n3 M
            this.password = password;# @: N( h" `) E4 K
        }& J# T! r" y' P$ b/ L: M& {3 N) {
    ( N% J4 T3 i: `+ k" \8 n* n
        @Override) I4 U, A2 U+ W9 [0 G/ D
        public String toString() {
    8 I) v9 z# }+ m3 r7 E9 u- W) m+ K        return "MailConfig{" +
    , G9 ]* b7 k: y1 Z6 p                "host='" + host + '\'' +" m& {. @) ^  `) V3 g8 h: E; ?
                    ", username='" + username + '\'' +
    , L" V. t/ s7 s9 K5 ?                ", password='" + password + '\'' +
    2 _: B4 q) K- M6 }3 a; ]                '}';) p4 _) P! u! F- G/ @2 X+ j
        }/ @0 m2 [+ Y: x: ^/ Y2 f
    }  a/ S# P; w0 T/ w  w  A( J! V
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中7 `- x3 x1 ^. [; X! l2 f, \
    ( d" K: b' X# O7 Q3 g! |
    package com.javacode2018.lesson002.demo18.test2;
    1 P9 R& C7 |# ~" _, ~. p; l0 k; l7 M2 i% d
    import java.util.HashMap;
    + `& q# ~6 ]3 Q+ T7 H7 Limport java.util.Map;4 }7 r/ C! ?5 p/ w% R% E: _
    * r0 K5 r% y, q$ e% {
    public class DbUtil {5 m: ]) a* s8 Z
        /**
    7 G+ Q/ i* i$ W4 y     * 模拟从db中获取邮件配置信息
    * y8 W! m( _. w  d     *, z" B+ ]8 p4 K
         * @return
    8 Z9 l, |4 [- r# U: O     */
    ; w# Q5 N$ I% E    public static Map<String, Object> getMailInfoFromDb() {( B; H+ F8 H: ]$ v8 \
            Map<String, Object> result = new HashMap<>();% W0 F" J9 ]; _, R
            result.put("mail.host", "smtp.qq.com");
    . A: `5 w6 k  S4 _        result.put("mail.username", "路人");
    & m# j7 p0 a1 G1 p. Q: E$ l# O        result.put("mail.password", "123");/ m9 {; `' E7 X/ N9 ~& M7 T
            return result;
    8 D3 [* r+ L6 `8 E, e: C    }
    ! k( e, ?# }$ j+ `$ p3 e. [}/ e: A! I# q- h% V  ^- W6 ]
    来个spring配置类/ M+ T. g' y, D5 t7 o* T9 m  e

    & F4 N4 N) W8 E; ^# Ypackage com.javacode2018.lesson002.demo18.test2;
    % z: ~+ g9 x6 B6 K6 l0 S! f* f" f5 A* x
    import org.springframework.context.annotation.ComponentScan;: W; r4 d5 c' b$ k0 w' @8 }
    import org.springframework.context.annotation.Configuration;! _* P8 ~/ m& N2 ]: C0 j

    + J7 m, w. C: H1 c7 g@Configuration" [7 E1 t+ \( z  a% M3 u  s5 I
    @ComponentScan
    7 _  _% M& U3 P5 R# E! K1 D9 _public class MainConfig2 {
    ! V" q- H- y" ]: ~5 u}
    3 k# V  p! c% m$ E7 d- t' Z下面是重点代码3 z/ c" m' d1 u) l9 u

    ) v# K0 V7 H* Q) }7 _@Test
    - z( l7 \: q9 X' r: Z( q5 l( {0 `public void test2() {
    " P5 D4 P2 x3 f9 E6 h! D    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    : h" s/ _6 v# p2 r) e7 w" I  {
    & r" o- s9 Z+ T/ {, s, L    /*下面这段是关键 start*/
    + r: v8 t% |- T3 P3 S  G    //模拟从db中获取配置信息
    $ c$ l1 _- I- T+ G& \3 p    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ( v; d& H5 O) Q! O" }  f+ Q! Y) O    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)8 ]: a. t; K/ r$ [3 Z+ ]
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);1 W  F7 W; w' `9 `6 {8 Y6 c
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    1 q% u" `$ t% Y: b    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    + _( _+ E+ ?) V0 c5 |) K" B! T    /*上面这段是关键 end*/3 S+ G& Q; s$ Q9 C/ L
    ! H. J- Y, ?% t$ o" m3 u0 T# }  T
        context.register(MainConfig2.class);
    ) E3 Q8 r5 {0 x: z2 y! s8 \    context.refresh();
    , `6 Z+ [. X- `- J    MailConfig mailConfig = context.getBean(MailConfig.class);% _# \4 k6 ?4 g. d$ K" Q
        System.out.println(mailConfig);
    / Z& B. t* x' B9 e7 z8 w}
      l3 I1 z2 n9 J9 t, L* {注释比较详细,就不详细解释了。
    ; P$ p0 y4 f7 l* v2 F- Q# Q, X2 }4 k( M; h
    直接运行,看效果
    % d, G: C3 d3 L4 e
    4 Q3 c$ O- [2 {MailConfig{host='smtp.qq.com', username='路人', password='123'}
    % [- h! w+ q) F  V1 \$ M4 u1 R有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    1 b" n2 i) H9 e5 a, ]8 \
      j$ l6 p7 a3 C0 T  X- L上面重点是下面这段代码,大家需要理解
    ! Y4 O" Z& M# _$ t/ m: `. ^! x. f- Y( {2 c
    /*下面这段是关键 start*/
    3 u/ E7 t* l- h# ?//模拟从db中获取配置信息
    ! r0 S/ `# G  L% M% tMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
      w0 x7 s' I+ l! K( E//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    0 y* ?# f- Y/ l3 g2 l6 DMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    % y0 _' Q% t$ Z//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    & X4 ~5 k8 l0 h0 S  k3 pcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);% }" B& _* n$ r7 G& q% B9 P! F* k
    /*上面这段是关键 end*/* ]4 q( N  t) |1 y+ f9 i& M
    咱们继续看下一个问题: a- h5 o1 K$ `: J3 y; O
    8 N/ {+ l( s( K3 o5 {; E, m1 {
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。, h% B( y0 v3 `$ |2 P
    " L( [* t! d7 y% v: {3 l
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    ( \' a+ d$ m6 p" i* x) V3 X) ^2 h/ C# t' ]- Q
    实现@Value动态刷新, T" C$ F1 I  I9 K4 ]% p( D5 ]2 p

    3 S! A' ]) w1 F  Q0 Q4 M! y/ c先了解一个知识点+ d9 ?& ?0 X7 Q2 y* l

    ( |8 z5 ~: b. K- t这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。, K7 J; s4 n. y+ K/ E. L# q8 R
    # t  R5 f2 D) e% U
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
      N) y5 S( l. A* u" t0 K7 `' U2 o$ K- F
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:/ e' w# R, l" @6 \/ z% b3 \

    : X/ D0 W' G' w! X- M8 R' cScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    " ~5 N; U& v% y3 D' x1 v4 Q这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中4 {' z/ w' v4 M" l

    3 z4 c9 K, v2 @2 l8 ^8 P% Xpublic enum ScopedProxyMode {; k! p: N* W; @2 o8 `" a" }- ?! r7 v5 G
        DEFAULT,
    ( a: h4 {% s6 t3 F    NO,
    + e' K; y- o0 r5 M5 j    INTERFACES,& n0 Y! q/ [8 y& `5 j) C
        TARGET_CLASS;6 u2 Z$ k, a6 O* d
    }  f/ d3 S  x7 j+ v
    前面3个,不讲了,直接讲最后一个值是干什么的。0 T$ D2 u/ h. L0 m0 R# N8 [# h0 u
    * d  P/ D5 G$ m3 \% n; @6 c
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。4 z0 C3 p: f3 C  m

    4 Q% S0 T0 q" L7 N7 N& c" G# ?理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。  p, A! ?5 C2 d9 P  ]* t

    . Z5 ?2 n  v6 V9 r6 t; x自定义一个bean作用域的注解2 j! z- s; o& r' M
    . V' Z. Q0 J8 z# q3 m) J
    package com.javacode2018.lesson002.demo18.test3;
    7 r! K9 h/ f, O: b7 W/ {
    - J: g& G0 n/ v' h9 Pimport org.springframework.context.annotation.Scope;
    8 d* C9 l; K: f. x* eimport org.springframework.context.annotation.ScopedProxyMode;: w9 U2 e9 v1 n7 O, z  p4 M
    ) {' a1 p3 G; {/ t: Y3 v' H$ W
    import java.lang.annotation.*;8 w6 |: |& c2 z, j
    0 C0 O  K; P' a
    @Target({ElementType.TYPE, ElementType.METHOD})
    * G* y6 K" D& ^: \! C@Retention(RetentionPolicy.RUNTIME): U0 n1 k2 ^: Q" v. {$ p* `& @3 h
    @Documented
    8 a( i" ~3 T- t1 n# w# ^@Scope(BeanMyScope.SCOPE_MY) //@1( K2 b7 v: q' E
    public @interface MyScope {
    0 ^7 o" E& v/ ~: w# w% e: x8 z7 r    /**
    ! b+ G1 X9 r5 c: z) t2 t     * @see Scope#proxyMode()! |1 G8 `) c/ a. F+ i+ J
         */
    & F# e+ k" S9 G; H, U# I( i: d9 O    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2" t/ H* n# V0 U' V  H( _
    }+ r. Y% a5 S0 E3 ~0 K, r/ `# s
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    3 m" v$ Y: M6 ]* N1 O% m
    . l9 |* q- j' P  m" p8 e# |@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS4 v6 U; p4 s/ q  P
    1 r9 Y4 _1 p! y% \
    @MyScope注解对应的Scope实现如下- `/ Z% H  c. E$ |2 K- D

    4 k8 ^4 x8 P/ v$ Y; Tpackage com.javacode2018.lesson002.demo18.test3;- y" B& Z) q1 h& x0 Z  i0 D( E0 r* R
    6 Q% {: Q) k# F% {6 o4 g% s* l0 c
    import org.springframework.beans.factory.ObjectFactory;0 n& {6 _  x6 Z$ z* T! R
    import org.springframework.beans.factory.config.Scope;% d8 g/ Z+ W# C8 G( a  e" ?; N
    import org.springframework.lang.Nullable;
    - ^0 ]' U) C) V% _/ |6 o+ r- q& X( k2 |% i2 w& E
    /**) ~; B1 O6 c6 T9 K- E
    * @see MyScope 作用域的实现2 q4 W4 w0 r; |* e' U; Z# o' u$ B& e
    */
    2 @( U5 O, B2 _8 Ipublic class BeanMyScope implements Scope {
    ( ?, S; M8 G2 k3 a7 M) ^- V4 v2 _) `0 t0 |( m; k  o
        public static final String SCOPE_MY = "my"; //@1) U4 J1 w/ s! K$ Y# r9 k

    3 ~. J' u7 t$ P: o, D  X+ K    @Override
    : p$ U+ a' T, k* |: R    public Object get(String name, ObjectFactory<?> objectFactory) { % G) |1 Y; q: Q5 W4 m
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    ; \. ~9 u. v' `0 V        return objectFactory.getObject(); //@30 {$ P# q" Y, C8 p
        }
    + R7 D# `) g9 O
    1 o: d7 p; F! l3 g6 y    @Nullable
    ; x" o. e/ p- G    @Override
    $ D# \( ]& n( Q7 q9 `8 q    public Object remove(String name) {
    , P1 r; B: y- b        return null;& n! v# d4 E1 [' W- f$ G
        }
    + E% N& I0 y# A( i  ^4 b, N. t; D: U: q% x0 P* V  n/ @
        @Override
    ; j) ^* Z! T  Y1 q; v* I    public void registerDestructionCallback(String name, Runnable callback) {' u; |7 L* `& Y7 I
    " V6 t1 S$ x3 o3 G$ L6 q; S
        }# H4 r  {$ b0 Y; r: ?* W6 g$ `
    / n" n% n1 I* M4 [! g4 o$ U( H8 t
        @Nullable# _* I% O% Y, [! i$ J2 w
        @Override
    ( f) S! N  _: U# Z' q    public Object resolveContextualObject(String key) {$ D- N! e2 {9 g' O9 s3 p+ X# k
            return null;1 ~0 x3 {# z1 @$ |( J
        }
    7 }" L' b5 X, j  l" H( m9 T
    9 V* R, N4 d* h2 o$ y  Z7 K: G  G9 w/ H8 M    @Nullable& K( `2 V6 X( i6 S8 Z
        @Override$ Z" U5 @, {+ i( V# o+ L
        public String getConversationId() {
    . _9 K. T) G/ N        return null;
    7 {6 Y. D2 V% F, F    }
    0 c  b- }  v& l+ e1 @, h, R5 N) e$ W}3 E0 R5 V7 B* b% n6 h! V) x2 e
    @1:定义了一个常量,作为作用域的值
    ! w( G/ Y1 K. l. b2 n8 \4 I( A' f
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果) L! I; ]% i8 M0 Y
    & Q% E( c& t* m& o; U) ^+ j
    @3:通过objectFactory.getObject()获取bean实例返回。2 ]. L  P  `  d7 ?0 ]; m

    $ O* W# p6 z6 Z3 a0 w: b下面来创建个类,作用域为上面自定义的作用域8 t" S3 C6 W( S) a; u- u+ _% [
    , x0 Q! \$ U( P" o, }1 x" R% {2 K
    package com.javacode2018.lesson002.demo18.test3;- N4 f# F4 @/ R+ K$ E

    % g$ ]2 O! x7 F- Nimport org.springframework.stereotype.Component;
    ! G( n$ ^4 m; B1 z, }( w; ~. ]* y' J( u9 e, p! S9 ?* j
    import java.util.UUID;
    : N  W$ P7 r- _
    " M9 h( w) G+ Z1 O: k9 X; c8 h@Component
    0 y& l$ K: q# r: A9 \@MyScope //@1 ( |, L( \+ E. v7 f
    public class User {
    ! j8 Z4 h  g! M+ T4 O' F) ]* E# y1 q* g4 n- v. _7 y
        private String username;0 o8 q4 _; U; ], V4 F' i) `. m
    : W' r" h3 `9 @5 q2 a( H
        public User() { 2 T" I$ ?, v) Z7 |
            System.out.println("---------创建User对象" + this); //@2- n8 ]: v0 B# X; p; u
            this.username = UUID.randomUUID().toString(); //@3
    5 x$ a) R2 {( t5 Q    }3 o: X- T' T( r' B2 D: M
    * Y5 u4 E1 @5 U5 R
        public String getUsername() {
    / ]; Q5 L; z7 B9 L  R0 O        return username;' ^: S. C) G- M( q: t
        }
    6 W4 L) ?: f3 h. V! R0 h
    / X% A$ Z* l  Y( r4 I    public void setUsername(String username) {
    9 ]/ a3 N0 f: O        this.username = username;
    ' c! y7 Z( p  x8 X& C/ ]9 g    }4 K$ E; m" u5 |  W
    $ ?* V  w, x4 ?5 h
    }
    ) {  ^* i: ?2 Z- S: n@1:使用了自定义的作用域@MyScope
    6 ?" }5 Q. Y2 W( d. g
    7 ?& f9 N7 E0 z$ ]@2:构造函数中输出一行日志+ ?! Y6 l; Z! p" f7 v

    4 u# Z1 s; L, d% Q/ k@3:给username赋值,通过uuid随机生成了一个# F, ?- Q5 Y8 B! v8 `7 \7 I

    . Y) F2 w8 B* n, A来个spring配置类,加载上面@Compontent标注的组件( I9 w  p; c8 }; F/ m8 w2 Z
    2 ~2 p' P5 ~  M7 B% n
    package com.javacode2018.lesson002.demo18.test3;
    ! _1 ~! H& B: ?1 I  ?- z* C6 U. ], H' I7 b1 s) z
    import org.springframework.context.annotation.ComponentScan;1 ]  c, k, a7 y) R
    import org.springframework.context.annotation.Configuration;  W; }& J. v: o$ y. K8 H' m
    3 I1 f. m+ Y+ J
    @ComponentScan
    ) U7 u7 F& o4 h$ b4 S5 V7 ]@Configuration
    ; z: x: t* V! g1 V+ a2 j5 qpublic class MainConfig3 {& X: G$ [* V1 |1 V, w
    }! s# J' e6 P, U5 F7 w4 }3 d- a
    下面重点来了,测试用例& s3 |. E- `, H1 H
    9 v9 W7 r( ~4 p- j* U
    @Test, f1 M" b# _* m, b5 v6 n
    public void test3() throws InterruptedException {4 |* l* x" h  B: ]5 j" R, V" w
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    % ~8 a) i1 g" @) `    //将自定义作用域注册到spring容器中$ @' T+ O3 v5 H& }# R
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    : M! J1 Z8 U/ j3 D% l    context.register(MainConfig3.class);
    , z1 Y; K( S- K; G& D4 S! ^    context.refresh();0 g8 F$ Y5 e3 F2 s) v

    & G2 A, t: k7 t" b1 a    System.out.println("从容器中获取User对象");
    ( {9 n) c: r+ p7 z. O! S    User user = context.getBean(User.class); //@2
    # D8 _, o1 A! G! X- Y& O, U    System.out.println("user对象的class为:" + user.getClass()); //@3
    $ W! w) H: M- J' T/ c
    8 o( A% `3 h8 h    System.out.println("多次调用user的getUsername感受一下效果\n");
    * a  Z& i5 G# K7 E' _& a    for (int i = 1; i <= 3; i++) {1 G/ M* v9 p, h7 p; w! |6 h; ?, ^
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    1 F( {  @1 U) p. q3 h  C        System.out.println(user.getUsername());2 e, T5 G0 y3 m3 N$ h
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    % N! |- l/ u1 H    }
    " u/ s0 z/ e% I! H9 B}5 @) b  N' d+ O* l9 j, o& @6 k
    @1:将自定义作用域注册到spring容器中9 W' t0 a$ b2 @/ i+ j
    5 c2 M, L# u1 P9 {5 ~! r
    @2:从容器中获取User对应的bean
    ) J+ e9 U  V# w, J/ s/ Z4 \3 l
    ( \" J/ Y; b0 V/ t6 E% e@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    9 |0 d2 R* ?* t: [5 g9 [- [6 [! f' A
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。5 B: U0 C1 _& v# k+ P5 V
    8 {; V3 u! c3 Q. z2 B) q( H- l
    见证奇迹的时候到了,运行输出0 m" N+ G' q, t) z
    . [1 r! Q3 O1 S( @1 b9 W
    从容器中获取User对象! M" K. P' k) W: @' J6 A
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    ( t) p( Z! \& N0 @多次调用user的getUsername感受一下效果! P# I* Z, a0 D- W8 ]1 R4 I

    0 C7 ^( ]3 Q  F% u********
    9 O6 C: \* D1 S) o第1次开始调用getUsername) `' a* k2 ~* k# z  b: m- n" j
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    " y* [* g4 b! m, z  s% O2 o! G---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4! B( r; h1 Q/ C7 K
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    ; n  I; K4 F$ k, q7 R3 k8 D第1次调用getUsername结束2 e% H7 J3 _0 f
    ********
    + ~+ R  q' l0 g2 M* r# B, \3 z( c! \6 d
    ********
    0 O+ ?# ?- R/ J/ m: R* |第2次开始调用getUsername  r9 H3 `/ n5 ]& F1 [& ?' F/ [
    BeanMyScope >>>>>>>>> get:scopedTarget.user5 Q7 e+ B3 w' e" S# j
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    ! B$ O4 H% ~0 L4 z5 t01d67154-95f6-44bb-93ab-05a34abdf51f/ U8 J8 \6 E  D' M0 K
    第2次调用getUsername结束
    2 W6 W( p' x3 n  \1 I********, d! F3 L# Q+ g

    1 j& P: W% p8 r. P; }********
    ( A$ y; ]: |: _, J第3次开始调用getUsername
    . e9 f0 i  T1 O, {BeanMyScope >>>>>>>>> get:scopedTarget.user
    ( r( y" L" ~! t$ a$ U---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    8 F6 ]9 |/ C6 R. j. k76d0e86f-8331-4303-aac7-4acce0b258b8
    + g  t4 F: g6 `; z第3次调用getUsername结束
    ! `) {) N& f5 ]. M5 K# J. M********1 F7 _$ p4 A. K" E) a( d
    从输出的前2行可以看出:
    + N5 g# b$ P' w7 Y$ `, u7 x  N
    0 z' S1 S# D& A) V/ N9 z调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象9 b: m( C8 D  \
    - s* x+ h& j/ m- s9 c! F6 l
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。4 x4 i) ~# D0 j/ h* R4 l. W8 l
    ' G% L( V+ Q7 j
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。9 h" u' |5 }5 Y% `
    5 O+ o. u( V* u7 ]% n- O! a
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    4 ?0 ]/ v" {- P% E9 g5 J
    + \* }) v3 ^0 t6 Z6 o9 H" J动态刷新@Value具体实现2 J" V5 v' q' x8 K+ m; L5 S

    7 O/ x9 p- c( _: ~6 F$ K6 c" S那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。, Z3 T" J: [8 ?0 W( k0 u/ m) I
    0 K% p5 f- Z8 ?8 j0 B
    先来自定义一个Scope:RefreshScope- }5 n  `  D" O/ {

    " q0 u4 J9 ?. w2 rpackage com.javacode2018.lesson002.demo18.test4;3 P! T$ m/ {: Z; A" H* Y5 h
    7 X9 F5 m" Z( ?& K# u- Q; e& f, f
    import org.springframework.context.annotation.Scope;& u9 W& O$ x/ Y; v
    import org.springframework.context.annotation.ScopedProxyMode;
    # v- C9 I1 @6 e' O& {- |
    5 c+ S$ g0 b% N2 mimport java.lang.annotation.*;
    + |2 C( v. X2 z' d8 g2 w% w. {' y) q  X/ g4 |" D
    @Target({ElementType.TYPE, ElementType.METHOD})2 R7 u. i# [$ ~0 _5 M& F* s
    @Retention(RetentionPolicy.RUNTIME)
    ' B+ Z4 C  A; }# ^@Scope(BeanRefreshScope.SCOPE_REFRESH)/ l* F, t, d; l" N" N
    @Documented2 {0 }3 P& p, a! m. W
    public @interface RefreshScope {: F8 \: C. y  \  n+ F7 D
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    3 w4 Y. Z- C5 F. D; o}
    & O- H+ g9 y3 y* w要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    . K) D, m0 F# Z8 c2 l' d# B5 P/ ?, G$ o% W" N' Q+ A
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    9 s$ Y9 \) ~, {+ c3 t8 f
    6 @; ?/ X! Q9 \3 o3 _1 R/ ^5 ~这个自定义Scope对应的解析类- F, T5 k- L! I. l

    % X$ @2 T+ Z, V) E/ _3 I5 A% Y+ y下面类中有几个无关的方法去掉了,可以忽略
    : f# q7 ], X' W5 J. r7 l9 L5 `! _5 O2 g6 w+ D
    package com.javacode2018.lesson002.demo18.test4;( o8 u9 ]0 M( f* p/ Q% v
    3 q! C( M. H1 R* \" m, w# {

    , p5 ~2 T  _' Mimport org.springframework.beans.factory.ObjectFactory;
    , Y5 m, C; M  Ximport org.springframework.beans.factory.config.Scope;7 S8 N, w' {8 S& P
    import org.springframework.lang.Nullable;/ X: Z6 U3 d4 q
    9 a! i7 s. |; O0 o4 X) s
    import java.util.concurrent.ConcurrentHashMap;
    ' p3 G, N4 g, x3 c- z8 y" e
    9 x0 P- `. k0 i0 a5 kpublic class BeanRefreshScope implements Scope {
    . U7 \$ S9 N& B( y9 s. o2 P
    - s; L( k2 ]$ U3 `' i  e# N    public static final String SCOPE_REFRESH = "refresh";
      h  e( T3 _0 |2 o* e) Y. M- o' g
    * B) }; Q% j, r) C; p2 a. T. Z    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    : `+ w. M1 W  X! F7 H. X5 e; a- h2 f, P3 ^- i/ A( E( G% d
        //来个map用来缓存bean
    6 `2 ?# ~7 y# q0 `% I& r1 ~    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1; @) l/ b6 o. K; e7 a4 f& f

    4 Z. K. L0 k5 h, O; N5 [: \    private BeanRefreshScope() {
    # a6 Y+ @! a* c( _! s+ I! W# r0 L7 q    }( O4 A/ m5 N8 E7 `' q3 S

    / h7 t' a( a+ `: u/ W    public static BeanRefreshScope getInstance() {
    0 g. K* n: z/ g, m, F. K9 ~9 a        return INSTANCE;( z* v  C, O9 j9 g
        }0 ], ]$ c0 J3 A5 O" H9 z
    * h3 j, x4 }4 N5 g
        /**
    5 S1 w7 b  `: d" Z* ]% z     * 清理当前' c3 i" a- |) R/ ~( X% l
         */2 v9 H' \- o/ N
        public static void clean() {
    7 Q4 A7 f' p% ~( w& T8 M        INSTANCE.beanMap.clear();( e; J' d: z( L$ h
        }
    ' q+ x$ A5 Q/ w* y' D' x: B3 t
    4 E$ Z- }' C, B& j% |# P    @Override  Y! t) Y. c7 u4 f/ s
        public Object get(String name, ObjectFactory<?> objectFactory) {6 h. z2 Q* o: P. f5 J7 |. ~
            Object bean = beanMap.get(name);2 w/ D" q0 K/ s, R$ x
            if (bean == null) {9 X' P  f3 V& n: P& n1 V+ {
                bean = objectFactory.getObject();# @9 z" _. ]+ G2 q4 k6 g/ k
                beanMap.put(name, bean);# b1 y4 W' a3 z" L) H$ X
            }  Y8 }% \2 J; Z( s3 f$ O
            return bean;
    / J4 s6 f: Z* k& e    }8 ^. H. v1 d% _7 d' |6 o7 `/ i
    : f  z  \/ @& V( Z2 O/ j' u1 V. V
    }
    ) g! x' ?: R* G' [上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    : `! j' f: X2 V! w6 c6 ~
    2 e3 o! e; W1 F5 K上面的clean方法用来清理beanMap中当前已缓存的所有bean
    8 R, S0 U) ~" y0 A, y, X5 A3 X$ M- n5 s
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope. r: }# j. M0 f6 N4 V: j7 Y1 `

    2 {0 l7 K4 f8 n& `2 ?5 P; B- epackage com.javacode2018.lesson002.demo18.test4;
    $ W! Z4 w, c; l7 S+ t1 _. F+ v' l) G1 X
    import org.springframework.beans.factory.annotation.Value;
    3 f  o, \1 m& P; q- Y6 f6 x8 yimport org.springframework.stereotype.Component;
      B4 [( E' q& A6 v; J$ H& _# ?  V& |! d  j: {( Q1 Z
    /**
    7 ^; B& g+ v8 N- G8 ~) X0 u- {( s * 邮件配置信息
    5 q4 M- c8 }$ e5 Y! w9 w8 P: K6 h */
    ' Y/ v1 G4 c' _! G9 }@Component
    4 o7 N# q$ @- }$ ]6 D1 U) S, y! o@RefreshScope //@1
    8 K0 W( c* l  Mpublic class MailConfig {5 O8 R% e4 }- @; u7 Q
    2 }( T$ ~: [# X' @
        @Value("${mail.username}") //@2
    ) J. h7 E# I$ w    private String username;
    # F0 o. ]1 r% W2 C" Q- }+ G# _  X9 c3 y) C) n9 Q
        public String getUsername() {! R+ \. q9 \0 a' A. _" V0 A
            return username;# i) v9 K/ K; s5 ^
        }3 {. V8 @3 M9 E% u# H
    $ g' V7 S6 b3 }) k* {/ c0 k
        public void setUsername(String username) {  M2 A: E* Z) }8 d% M: i5 A& r' ?1 J
            this.username = username;
    . B5 L" k9 ^0 x$ C# \9 b/ ]" Z* d    }: I! L) ~5 x8 d( o! k; h

    % g, A) \1 W+ z5 i" J0 x    @Override
    5 N. P0 Y/ j5 {    public String toString() {
    - N  \" M  X1 a) r  x4 r        return "MailConfig{" +
    % Y) I2 I% v* ], X2 }  k                "username='" + username + '\'' +
      @) A* B# \2 r7 q9 y& m6 l                '}';) p# }. q$ y5 y# I; E
        }
    % ]" S$ X" l4 Y% ]+ c6 g! F& I}# C: u9 O  q, @' u3 }
    @1:使用了自定义的作用域@RefreshScope% k: J# P* [5 F- ~! W, B
    + P5 c3 k! o/ i8 q1 f( O
    @2:通过@Value注入mail.username对一个的值1 x5 D! V  l; ^/ l- U7 {

    # F* Z6 \% z# ]+ r; ]4 S重写了toString方法,一会测试时候可以看效果。
    + v; t, B" Y7 k  W) o& x: m- P6 p3 }; c0 U7 K! {) U* r4 s
    再来个普通的bean,内部会注入MailConfig
    $ V# y- R+ m, a; Z0 k6 v6 M& b) `# A" ]. V$ a& C  ~4 W- P2 S
    package com.javacode2018.lesson002.demo18.test4;
    ; Q+ W! ~0 Q: ~- B: y+ @
    ' f6 G8 L# |) K! J- E3 A# }import org.springframework.beans.factory.annotation.Autowired;! ?  d; D$ B8 I, r' P1 G
    import org.springframework.stereotype.Component;
    2 L% f+ D! }* k% }/ z) V$ d% `. W- g: U: V
    @Component
    ) C2 W3 I  [$ s/ N, N) k* ~  wpublic class MailService {. F0 u  Y, T1 o$ h8 x' o" X
        @Autowired3 A1 ^1 N. L1 V
        private MailConfig mailConfig;
    ) l* j7 O$ R# W* ]. g" R, w+ b) w
    ' S+ R' f# t( g% X2 c    @Override, I6 t" r2 h+ y; H# z$ R, X
        public String toString() {- P* ?) M0 @( T% @
            return "MailService{" +& m1 D: f) J- U& H. A/ S
                    "mailConfig=" + mailConfig +
    $ |3 B- f, e, ?% _& l                '}';+ h' w5 |4 |) u
        }2 ~5 V" I2 ]5 A& l% S- Y
    }! }% P0 H1 s5 h, Y/ ]- W6 z
    代码比较简单,重写了toString方法,一会测试时候可以看效果。* `' ]. T! |' ^
    6 w) K+ r. v* q( _! ]
    来个类,用来从db中获取邮件配置信息: `& H% j4 j' L9 E
    & x1 n$ C( p) y  K) t* n( i) X  ~- o
    package com.javacode2018.lesson002.demo18.test4;$ f; X$ x$ F% z  ^4 d

    7 @: P2 C/ z, E3 Zimport java.util.HashMap;
    ( u* T# c3 @9 z1 B, X9 A* timport java.util.Map;9 U) j8 y1 m. a* k4 T' S7 q8 G
    import java.util.UUID;
    ( d: w3 k" |4 P1 H% I  @5 [
    + O. F1 Z( b# _public class DbUtil {, |3 b- b) _+ ?, e, E# v( C
        /**
    * N. W; [8 b+ B8 N2 |3 n. ?0 S     * 模拟从db中获取邮件配置信息6 b# {' r6 T+ s5 ~; d& J
         *
    4 s# L6 a$ c4 x4 g     * @return
    % s9 K, w  J9 I4 W3 T' x: v     */
    , ~5 K: L2 m+ d: R; n8 Q% f    public static Map<String, Object> getMailInfoFromDb() {
    $ u2 c3 ?9 S1 D) T) H        Map<String, Object> result = new HashMap<>();
    & H$ W% a2 a8 ^) D- i5 u% O        result.put("mail.username", UUID.randomUUID().toString());9 A: X+ g% b  M. \+ \" H- e
            return result;- N! Z& L, d: n+ F' N
        }+ x% m) w+ X8 x4 Y) @6 X- s3 z
    }
    % l7 Y7 x7 J( \( f3 v& D* L1 u来个spring配置类,扫描加载上面的组件$ N% @. I' T+ d  L" \

    ) S: D) _: B, h$ b. c6 @package com.javacode2018.lesson002.demo18.test4;$ E, M9 }- `8 U7 h& {
    8 B; ^' Q' y9 C6 y. n( q
    import org.springframework.context.annotation.ComponentScan;
    8 V  _2 F/ W, l- I$ q, `/ `import org.springframework.context.annotation.Configuration;# U' `% k+ l% `
    : v0 j9 B+ Y" r% f' q. U3 U
    @Configuration% |" f) H1 ~* Q* h+ \+ e3 y
    @ComponentScan
    : U' w# m6 [. ?& ppublic class MainConfig4 {- C  t% {! ^% ^2 X( Z' A$ z/ X
    }3 ~# \* x! \! J
    来个工具类: _1 K% x+ w5 Z+ I9 B: z8 i* f
    9 P! y+ ?( H) Y) P- _
    内部有2个方法,如下:' I% _/ X( F- f/ o
    7 Q( o! ^3 j9 O. _/ Q- a8 A/ t
    package com.javacode2018.lesson002.demo18.test4;
    $ z  m, G. W% v6 J
    ! S9 |$ ?" Y6 s- P; i! ]- ~import org.springframework.context.support.AbstractApplicationContext;1 n9 v8 z, t5 f9 R6 m3 D9 q
    import org.springframework.core.env.MapPropertySource;+ M1 S# n8 Y. u" a
    8 k9 w- {7 P5 d2 Q. x3 d
    import java.util.Map;" ?% H6 I& U* U+ L4 y' X
    ; I% W8 |5 B$ X' E4 l2 _
    public class RefreshConfigUtil {
    7 L& K' @/ v8 j, b" L6 }    /**& v8 y0 Z  ^. q$ r6 X
         * 模拟改变数据库中都配置信息
    ! n- h( ^4 _4 k  P8 r' b     */
    0 j0 s+ B2 r  U- ?" W# M+ ?    public static void updateDbConfig(AbstractApplicationContext context) {
    + Z4 k, r* p, q$ t1 h' y        //更新context中的mailPropertySource配置信息
    $ N& N9 v1 z$ A  v% c& j/ L        refreshMailPropertySource(context);1 y8 v( X# I  k* H

    ! Y7 h1 u. {9 Q* F        //清空BeanRefreshScope中所有bean的缓存- e* q0 h' |6 a9 k! n) [, n
            BeanRefreshScope.getInstance().clean();
    6 o* i( `/ Q, i    }
    $ z# m1 T; e% X& u. Z
    7 X& G' j( a4 v# g# z- ~    public static void refreshMailPropertySource(AbstractApplicationContext context) {) W- S: }2 |( l7 b* z: x% ?
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    4 e7 b( j3 L- r        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    2 f" F$ f6 v( S2 ^. G  r3 e        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);' W3 L8 i9 |; v, K
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    9 L) s" q6 |9 E+ t+ }    }
    ' }# N( p& x, b( `
    7 J+ U" |2 h! V3 ?2 i( ~% A! k3 i+ O/ h) P}) h% d7 N4 I8 h9 S  v' K
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息( Q$ K8 p: Z9 S0 ^3 o4 @5 }& d: U
    ' g) c: p% G- f# ^, ]  _1 F
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    2 l6 Z6 T. M" A$ I; _# b) f1 m: {9 D7 `
    来个测试用例$ W& U/ q4 }& _& w! Q8 T, z
    2 ^# c5 q4 `& z* P( |  J
    @Test
    9 j) u, |+ Z, b5 I/ o+ ^0 `public void test4() throws InterruptedException {
    * }# S) x# f3 C( Z5 X    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ( M  P: T5 Q+ S* U: v    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    ! v+ a' m" a  {1 w; u+ t    context.register(MainConfig4.class);
      i. v* g4 ]- |3 w7 P  J    //刷新mail的配置到Environment
    & }: u8 l& F8 F8 C% h; E9 [    RefreshConfigUtil.refreshMailPropertySource(context);
    7 D- T( q7 p* t' ^5 e; n    context.refresh();% L; n9 H8 M# y; K  I6 k

    1 d0 _/ c/ g9 P5 c  W' B9 ?    MailService mailService = context.getBean(MailService.class);
    ) V& V/ R5 D2 {4 l( g( ^    System.out.println("配置未更新的情况下,输出3次");5 d5 {2 f! ~# }
        for (int i = 0; i < 3; i++) { //@1& F( @, {* m' z. |2 P
            System.out.println(mailService);
    # ~' L2 z8 l, g, r0 P2 {        TimeUnit.MILLISECONDS.sleep(200);- p+ S* V, f/ G% p
        }3 V& W4 E$ n! @" R5 R
    : T/ L; V: Q2 s
        System.out.println("模拟3次更新配置效果");
    1 [, H! _; C. T    for (int i = 0; i < 3; i++) { //@2
    0 X) j1 b4 X& J* L/ N        RefreshConfigUtil.updateDbConfig(context); //@3
    6 `" y) a! j$ v' s8 `        System.out.println(mailService);
    ) N; `, V) e; ]% J' _' |        TimeUnit.MILLISECONDS.sleep(200);# _9 U5 l; B9 p$ `4 w
        }1 {' Y; F$ ^7 W$ \" B  U: T7 t
    }
    3 \- r/ J& y8 u' s* g3 m( l@1:循环3次,输出mailService的信息
    3 N: E8 S8 \: J
    ( D: Q2 i$ x. g1 [  g1 O@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
      k' _0 L# o9 M6 ]4 @% ^* n! U
    4 m: ~, q) f& N& k8 n6 V见证奇迹的时刻,来看效果- @$ ]( a. F2 V0 p
    : g% t) Z% Y1 ?8 f3 M: g
    配置未更新的情况下,输出3次. c+ L) n9 ^6 r' B# d" k
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    * `: M; y, e2 r4 _+ |' ^% d2 ~MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ' H5 G1 a+ j& {, `* e0 a$ o  x: ~MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    & A. p: N6 s+ A$ y模拟3次更新配置效果0 N4 _, E1 z3 ~( ~6 e& q
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    - @+ F% F% W7 d. b9 n" ?MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}* R$ J3 m: n, G0 d* e
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    ' n& ~4 D; }% _上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。( _: y  J# V& r" \, D. \

    ! |- Y  h# W5 R. u) Z小结
    - o3 Z! ~! f) B6 B
    . H, b+ ^2 w0 {1 o0 U动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。2 w( ?6 t# X2 J2 p8 \. O
    1 n3 N8 }9 N- f1 ?
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。4 E$ Q: {1 [( m, V# x! O
    1 x+ B% _- _. S* z& J/ T, o
    总结' O" ?; y5 h: A" O$ D

    & n7 c# Z6 X/ j3 t0 y6 ]. X1 O本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    ; z) s. h7 C  d
    0 M" N% H* O( ^6 U& @) R6 I6 ?案例源码$ b$ b8 n# u/ }3 s

    ) S" f; f1 \3 dhttps://gitee.com/javacode2018/spring-series0 _+ c* P( K: g" e" a2 `
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
      R. [1 k5 e( ~
    % H7 q1 ^7 e: R" Z; q% ySpring系列. Y& T: f6 U. [

    . A1 c" c+ N4 z! M& ]1 _Spring系列第1篇:为何要学spring?
    8 D2 ~8 s# L  Z: q" C7 Y, ]. J8 R. W0 V& h$ G" C
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    5 C/ `$ m5 \' a0 l: U: P$ Z6 O4 ~1 f' K
    Spring系列第3篇:Spring容器基本使用及原理4 q; Y& l7 u. `8 [! c

    ) Y6 t( X7 z$ S7 ESpring系列第4篇:xml中bean定义详解(-)
    . t8 U/ C3 v) H
    9 s0 X1 R8 @/ k) _% P2 }+ K! WSpring系列第5篇:创建bean实例这些方式你们都知道?% W2 J6 f! T  c! O# s
    9 h7 n8 W) i" W8 E
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    * i* Q$ B- o6 ]( L( j
    3 Q$ g+ \- I: w3 F1 qSpring系列第7篇:依赖注入之手动注入! s& I- O7 K7 K' r* k+ _
    ( f" j, h- Y) S& ?
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持! g. [' P% ~- Y+ g# S8 X
    $ [  f1 Z! b9 ^1 W  q5 k/ W
    Spring系列第9篇:depend-on到底是干什么的?
    , }9 d8 i6 t0 o# _( m# B
    0 g6 `) E' C: PSpring系列第10篇:primary可以解决什么问题?
    $ l, J$ m# h& L4 Z  \6 W2 N) Q# X
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?; e  K7 S# Y0 V
    " {2 V9 E: W$ W; }' p# ^' ]
    Spring系列第12篇:lazy-init:bean延迟初始化
    ( G1 F( \( d: r- s- J6 Q+ l) z3 @4 O' F& H& L. p, G
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    # ]2 C$ a. ?& c3 Z7 d, g8 b0 M' F+ V8 Y: ?7 G3 |0 [, r
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    * e7 \: k' @# i# N+ E3 d3 t% T5 _" Q! p% Z/ |
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?! T1 e. Z& R; n! J

    / a6 W; R/ X& J. k; ?% H& ESpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)% A/ W5 q& L) c: u6 |3 C

    , A/ U, ^) a4 w  @" b! {5 M. l3 x4 c' O; {Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    9 Y9 G9 C) U* q3 M* S# m; ^8 M/ [7 a) o
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    # h& S# }( E) [/ o6 _/ h5 n
    6 f: l* D3 s, WSpring系列第18篇:@import详解(bean批量注册)
    ( c) }! h8 I$ k9 `- p2 l+ g' k6 g3 q% @4 T+ ^/ A
    Spring系列第20篇:@Conditional通过条件来控制bean的注册- V8 S' [2 x0 O% Z
    * H& A; x2 e" J
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    ' e, z+ B2 }7 ]3 X; |) ?+ r( R4 q4 z# K/ A7 G% r: b- N- y9 N
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解, c1 {1 a% ~( {' H# H
    * S$ C7 I' k# E9 C) g% b
    Spring系列第23篇:Bean生命周期详解
    6 }; `. g; v, P: P$ k- t& G8 a, c9 e( r% Q9 Z1 D( T
    Spring系列第24篇:父子容器详解
    * ]' B1 j" Q% @7 b; e: s
    6 z9 {; c1 n$ ^" J  L, A更多好文章: d; C, k* w8 A) |
    ( o+ j6 X9 p; S7 j: R/ J
    Java高并发系列(共34篇), z9 g7 Q+ a3 u7 R0 ]) E5 R1 u; h$ E% b

    3 }+ n' \5 Q3 `6 N8 V1 s1 ^MySql高手系列(共27篇)
    # N+ n( @* T7 f) S9 S+ L0 b' Y/ O0 \# @0 o
    Maven高手系列(共10篇)
    ! v  z4 p) ^1 \& Z  w2 K1 O, ?0 d3 p, |& y3 C: {/ N( ]; `' e
    Mybatis系列(共12篇)
    / y( h3 g0 H1 Y! ?7 M. c0 J
    1 S5 k: J3 e6 s) b聊聊db和缓存一致性常见的实现方式9 J/ s0 M6 D7 P

    0 ?* m6 w. r* S, ^4 S, C接口幂等性这么重要,它是什么?怎么实现?1 ^: c% |6 Y) e. S( I$ ~1 h9 E
    / A5 B; B( E9 |
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ; B* R9 ]7 ^- K; G2 w7 S————————————————
    9 W4 a. W; `( {. H7 p3 D5 S版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。! N8 X5 g0 A4 u0 {/ @- Y
    原文链接:https://blog.csdn.net/likun557/article/details/105648757. {5 g- h- i. t5 d/ @$ f

      Z. H5 ^! u1 n) J. S3 G+ m& E7 q& S& _. L( U6 e2 [  E4 A
    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-8 18:20 , Processed in 1.164838 second(s), 51 queries .

    回顶部