QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5400|回复: 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!9 ?1 B/ x; Z4 M6 [$ C1 c/ I# l8 j3 ?' E3 R
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!& j6 B, q, n' ~

    0 R$ O: @# l  m1 E. f% n面试官:Spring中的@Value用过么,介绍一下
    . _! t3 b$ Q0 @+ t$ l0 W( A- p: r$ k$ @2 h
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中1 A3 J  p4 m' K

    # L3 `, e1 Q4 W4 L9 b面试官:那就是说@Value的数据来源于配置文件了?
    4 I0 B% n9 E4 b1 r, P
    + I1 S  @2 K  q+ k/ ?  Q我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置0 D/ ~  X% c* J. ]+ f3 x! l
    + I6 Z# G2 L" z# U
    面试官:@Value数据来源还有其他方式么?- f+ l& \+ f# w

    6 Q1 p& f) H% Q' d% Q/ v& o我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    # B5 O* H5 C7 ]" N2 r! Z3 x4 U) s: U/ ]9 a  n! H
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?$ o9 u9 d+ P) E& f

    ; g  T) {! w$ ^我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧) P2 L( G* ~: x! P& e' a& m

    ! y" ]  R: d% B3 {面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?7 T" A7 \4 k8 Q  ?+ F# h% n
      V  b) H7 `9 S* r) U, F
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    ! }% I$ E+ B- A: g  W# x
    0 H$ c2 ^$ g  X( [4 L面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?3 V/ x% Z- c/ \" t2 R; l

    7 ~& J; m0 n, r5 F( t; Q我:嗯。。。这个之前看过一点,不过没有看懂/ d9 F  Y$ z9 l
    $ S% g; `7 z2 \& }8 W: S$ y- Y4 x
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?: V& f& e3 A% k, F; F/ l( @  b
    - |% d$ X& S7 b
    我:3万吧
    % F8 h3 J6 ]+ D8 y; O, M% P1 }: b. s5 I: u$ ?1 [2 z) p
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    . F- ^( l4 l, {5 W( S* V/ E! ~2 v0 Y. y: v* _# H, E' A0 S
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万% l1 v5 e) b3 _! w
    7 c$ |- D4 {8 \, i7 T) T* J
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!  y/ M5 X8 u5 V) D
    , ]( y( Z9 f6 F. O, F6 R9 w/ \* G
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    $ l  Q4 D' ?4 [( }( D5 Z# F% W8 [8 M* C8 |: X- a
    这次面试问题如下7 r- m9 c; |+ q, [
    4 \7 x( @. G  i+ I/ l
    @Value的用法
    " k7 f  F! b* N/ p7 ]. u: A0 s, |
    ( f% \+ m$ P2 y* ^@Value数据来源0 b0 C( Q7 p( `8 N( f7 r  g

    ' Z2 s, b7 z- u5 N; W& M5 T8 l@Value动态刷新的问题* W# p3 ?! r% H6 X
    9 L) R% |& B% }+ x
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。( l# [! D+ B2 e
    ; w1 _6 t# N8 j, L: E, _
    @Value的用法8 ~( c4 v- J' Q3 G" @
    $ I) E$ }) l4 @) C' I+ y5 T
    系统中需要连接db,连接db有很多配置信息。; G* o) [0 I( X# |, g

    1 O- p7 D6 {3 u( N  y. f系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    - d5 V2 G9 [% b3 |6 k) [
    6 N$ ]& v- g, m" ?- u: ?! X/ B还有其他的一些配置信息。# g1 z9 z7 m' T9 x1 T% S

    : a4 u3 _2 M( B8 ]我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    * o( u9 X7 V5 M: ?
    $ H) a& ^. ?, {4 E! I" }那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。3 K& o& s0 S3 ]8 _. O
    ( t4 K- x" Q6 ]
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。1 B8 \) y; R8 Z  z; R
    4 K" u+ O; [+ I9 N+ _8 M( i, k& R
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    0 Y4 V1 ?( e6 i/ F
    ! L( k. p% U# M& F% f: p@Value使用步骤! O/ a; b/ \, ?7 G
    # F1 G0 Y; q4 r3 L
    步骤一:使用@PropertySource注解引入配置文件5 O8 v4 k) k% f& c: P
    / ]  {5 e- L% P' X# V" S
    将@PropertySource放在类上面,如下
    : P  I( H/ `  O1 @' a- s# w3 ?
    5 o4 z4 l" Q, B5 y@PropertySource({"配置文件路径1","配置文件路径2"...})
    ! g: O7 ]  e$ ^- N! Q6 E@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    , v: h/ M& Q) o5 \1 {; H) H- H- W6 i2 Q# U8 S$ s' m/ j
    如:3 x" W% \7 ~3 p% V% K* ]3 P
    & z; Q1 o; `1 i7 Y
    @Component
    9 J8 e, Y7 q5 N9 [. N@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    & J3 z8 @. P: Z# t% v' z2 fpublic class DbConfig {; @8 x& u2 ^  r1 @5 T+ J+ r
    }
    % ?* W: \/ Z' q: ~步骤二:使用@Value注解引用配置文件的值
    & v! n+ R5 J* L* W4 l
    7 p- ?* t' h: l通过@Value引用上面配置文件中的值:  A) u0 V! @# Z% W3 N

    + e) U" F2 n6 r3 B0 {4 O2 r语法. c( i( Q) g/ J* y$ J1 b
    * I  H" o  |, b8 X. v5 C/ y" H
    @Value("${配置文件中的key:默认值}")
    , _, b  H1 y9 W" Y; E7 a8 R@Value("${配置文件中的key}")
    ) X  M  h0 @# a8 _7 L如:
    ' E* ?5 r9 t; V) s6 j9 l% |1 B# }" k& Q: l' d; a
    @Value("${password:123}")
    ) S+ V" H( D! q上面如果password不存在,将123作为值6 ^, n$ y: u6 @2 T( Y+ D

    # G5 g/ t0 L0 X/ p; G0 F1 w; w6 \@Value("${password}")
    2 f$ r% i/ Y0 a% ]上面如果password不存在,值为${password}
    6 R& U1 y: p( s8 ~3 y7 i6 \2 y. I6 ^" D7 p8 `1 z' {; @& s- X9 H) B
    假如配置文件如下
    8 o2 @/ f6 T7 m4 ~4 u7 n9 c2 @- |! A. o. X
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-89 P/ Q+ l+ @9 {/ R+ P0 ^1 L  L& s
    jdbc.username=javacode1 l; C- _0 ~0 N3 \* I# I/ ?7 r2 m
    jdbc.password=javacode  x+ A1 Y! N+ {( d8 j1 S/ l) g
    使用方式如下:
    ; F. H9 Y  I  @+ d2 K
    , B& b( ]5 J" a9 F@Value("${jdbc.url}")
    ) I5 y: g; C# }/ Z5 v* n" @private String url;0 h8 b. d" ?2 _( [6 A0 W6 Q
    ! u3 [5 M  g  y1 V$ S
    @Value("${jdbc.username}"). q7 A/ ?& K9 f
    private String username;7 D7 [( [5 p" ^$ f6 d: \' u$ r/ R
    # n! i! _6 r6 G+ t/ V. L% B( m
    @Value("${jdbc.password}")
    & ]0 u8 t  H. Q& P) a2 Nprivate String password;
    # g: Z' c0 k1 _& g5 c下面来看案例
    & A8 r) B5 Z  z- Q! G/ n4 v) X+ p( ^, {' w& s9 K! g/ w, _3 j
    案例
    6 _& M+ \$ r! d) e8 X; j/ [- i3 e: E% o
    来个配置文件db.properties
    # \- H/ r3 ]5 v
    * I% C9 J. }- a& B4 n8 v0 tjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ( ]5 |& X+ a/ ~3 c0 q0 Ljdbc.username=javacode
    ! p: [2 w- J- F  C0 v4 Ujdbc.password=javacode
    ! Z' O: `" y" [来个配置类,使用@PropertySource引入上面的配置文件
    $ z7 O. k4 Z. _4 g* z8 ~1 g1 A& Y* ?! L, v" d9 G6 l
    package com.javacode2018.lesson002.demo18.test1;
    ) N+ J  ^/ \$ O' ?$ i5 [' n/ `6 t! X6 j' @
    import org.springframework.beans.factory.annotation.Configurable;
    4 L# C* Y$ @; Y7 \  t( `import org.springframework.context.annotation.ComponentScan;
    6 J" y) a' _( d; Qimport org.springframework.context.annotation.PropertySource;8 ?0 O; {5 K- r& d5 N% ]1 W3 b! o
    ! \% J7 o, S7 Y+ J& d  X) H0 _
    @Configurable! \* h' Q  O- R- u( [( @' G
    @ComponentScan
    / U$ A: V& v2 G! {9 s& J@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})) ]& D% ]/ T4 m
    public class MainConfig1 {; ^; Y0 X# h* ?6 Z6 j' Z" g
    }: g; V. i1 R7 E+ s/ Q8 }9 }
    来个类,使用@Value来使用配置文件中的信息8 T* O+ P$ g% i. }1 [3 u

    2 U/ C- f5 G2 O& Y; X& Epackage com.javacode2018.lesson002.demo18.test1;$ m- [  Z& s1 t$ n8 `( s. v; |
      }- w2 \( m  q% k( _
    import org.springframework.beans.factory.annotation.Value;
    + A+ X; y4 z9 @# h  limport org.springframework.stereotype.Component;
    % q$ f* @0 C0 f/ p, s% y, T5 l/ v( v+ t, |2 m. n
    @Component
    # r  \" _8 \: \; R/ N+ Rpublic class DbConfig {( \  t! j5 u% ]7 Y, \- t
    & |/ M4 G5 E1 g# `! W# i
        @Value("${jdbc.url}")9 y5 F9 X# w5 |. |3 b3 E
        private String url;
    ' w$ [9 B4 b7 b! s: C: L  j" I5 h3 g3 w4 o
        @Value("${jdbc.username}")
    ( y" U* |" [0 a1 m. T    private String username;, b6 ]9 S3 @  O

    0 i* o* b# U5 D& N1 H( t- \5 J8 N    @Value("${jdbc.password}")
    ! ^4 ], [- Q4 Q2 s! x; _& v6 _    private String password;
    $ ~2 Y  X4 C$ e3 t1 B
    0 [: C! ~; o8 z( ?! i8 T2 Y" H    public String getUrl() {
    0 _' m& v9 N# a+ l+ \        return url;
    / M  Y, `' g$ \2 s, i6 x. E    }
    / r/ _: F1 q7 n, e; Q7 [6 Y2 ?
    / \' v$ V8 q1 ]% t    public void setUrl(String url) {
    ' n* G" N9 ~& |3 T, w8 W" L        this.url = url;
    ! u5 X9 |9 c5 T- \# b    }
    8 z  |6 P- Z$ T& t9 k; A7 E0 u" n2 _4 l" w2 S: A
        public String getUsername() {. j, l# k/ T5 u6 \
            return username;
    # J# t! m! F8 U! C    }
    9 }' ~9 l( v) h; n
    8 n3 [5 ?% ]" B* [0 H    public void setUsername(String username) {
      F5 Z0 E) W. l9 [/ b5 U5 l3 s        this.username = username;! U# f" U2 P$ q; }7 t
        }
    - M& w9 r$ d& O" b
    . Y; `3 b( k8 ?    public String getPassword() {! E" R8 @% n7 O8 l4 w% j* `5 E
            return password;
    # ?/ n9 A6 k+ E' y& y    }  Y8 u, ^( j6 M& _$ y& E$ |

    ) L, U0 W  X: g% {& u    public void setPassword(String password) {
    5 z4 U+ I. t9 m& E8 b        this.password = password;
    + B2 E' @* m( z: F: u( g8 o    }
    / R+ ?- {9 z& x, \2 D. R' d9 ^# E, `* \# b  v1 d, }  b; R
        @Override
    ; f5 i1 }; Q; M: Z% s  c* ]6 Q8 [    public String toString() {' o; H! ?, A& x! V
            return "DbConfig{" +3 h, w5 ^4 P1 ?" |& q
                    "url='" + url + '\'' +4 S" S7 V5 Y' s5 L
                    ", username='" + username + '\'' +  {0 t1 ^' `* n
                    ", password='" + password + '\'' +
    2 [! j% C- D5 V1 g6 u" b; L                '}';1 D$ R0 e1 o$ F8 C8 R8 A
        }6 r/ @, @3 c! t0 K6 ]+ W
    }0 C" l; }2 d6 a- X
    上面重点在于注解@Value注解,注意@Value注解中的
    9 A/ x9 u  _8 b/ g! a" r* q/ \4 x* f0 L- S6 O, w3 f
    来个测试用例7 ?- E+ _/ K$ _. P. Y
    0 i5 H) g. B6 D4 h1 q# O& y
    package com.javacode2018.lesson002.demo18;
    " u; t2 t) p5 C$ d- j& e4 f5 r) {  r) |' ?+ ]% r6 W) A' x- K
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    / z% B# F* s/ fimport com.javacode2018.lesson002.demo18.test1.MainConfig1;8 q9 [) V* J3 E/ z. }+ I
    import org.junit.Test;' i8 I2 z9 Q# z! \3 D
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    8 k/ G" {; F$ w) J1 o/ G& G
    ) A) {5 F" o+ O" G; Jpublic class ValueTest {' Y, ?! P* v  Z6 G0 Z# F

    5 K3 g0 U, N( f    @Test* f" h" _" X8 p3 o% Q% h
        public void test1() {/ Y5 c( b# s- f2 v3 l7 w7 m
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();+ g0 o7 s# N9 _* Y
            context.register(MainConfig1.class);7 j* z' d7 U9 y6 ~- X% v, N
            context.refresh();: h& m4 G3 b! _; p$ W/ Q, g/ Q
    $ y7 f; w2 q# v
            DbConfig dbConfig = context.getBean(DbConfig.class);  p8 }2 \6 L7 g
            System.out.println(dbConfig);) J4 [7 B. V; R; N1 G( O# {
        }* }  H, e/ A) {! U. |5 p
    }7 O* y, ?( A! i
    运行输出. q% D) T: \: R: D: a, q$ o
    , B$ S& Q6 ]2 |2 j  t
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}, h' g1 r! I) ~. B/ b
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    5 W" O% {! B! g/ E8 Q4 U5 V0 K) ?* X5 a; t4 |) a
    @Value数据来源
    ( E- _9 F! @9 U  g1 U5 W
    * Q" i5 p2 ]2 s" p, W通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    $ V6 y% ^  K: U+ f& K8 T3 Z6 @3 ]) @9 Q* w6 ^
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    8 W& s" C* W  S) D; _
    5 ?2 ~$ `* d# O4 ~  Xspring中有个类
    & F- j7 P1 T4 c* \6 S
    * G7 u7 f" `) R, p1 E; g: J- \org.springframework.core.env.PropertySource! y6 I' a3 v6 F" Q$ g
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    ' C# O- }! u  }4 x. r- z: {* @0 o) I0 g& P- `
    内部有个方法:
    ( K9 A, F6 Z7 {* U" Y% }2 f7 N( C9 T3 [+ c8 j0 a
    public abstract Object getProperty(String name);& E; |' Z* `2 N4 ^( p4 M
    通过name获取对应的配置信息。: e( p3 z+ ?7 H9 ?$ P) w

    7 n; N; v1 U6 D" c4 \系统有个比较重要的接口$ a5 `& W1 B: ]1 t

    1 ~$ G' Y, }" B5 rorg.springframework.core.env.Environment+ _; ~, P  {8 t2 a1 G; \. e$ Q4 Z
    用来表示环境配置信息,这个接口有几个方法比较重要$ Q7 f5 \* A- u2 t

    * S$ a) n- x% H5 u6 s( \String resolvePlaceholders(String text);
    & Z  H! ~7 V5 Z$ G& ^) D0 nMutablePropertySources getPropertySources();
      y! q" |* U9 ^& N9 g) XresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    2 @* i9 s# A) h
    9 g6 U* c7 j1 a0 m3 E. T1 F: ZgetPropertySources返回MutablePropertySources对象,来看一下这个类
    & p3 `1 R7 r/ G6 t& F" X( ~4 m  ]6 P2 q9 }6 S( }
    public class MutablePropertySources implements PropertySources {# M  i4 z, y; T* J4 X
    " a# {* W" y" o! z2 R0 m
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    0 V; e5 u" a2 e( B7 s# A0 K& N  |5 b% `8 F- v- a
    }
    % X2 ~% ], k$ N2 \. q! S内部包含一个propertySourceList列表。9 r+ e. p4 o0 ^+ |9 y% s
    $ L0 S1 V4 O, ~3 e/ R, D2 A
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    : I3 j2 H* R/ u# |; q+ g( a' G# {& `; {) i' \$ b8 Z% U
    大家可以捋一下,最终解析@Value的过程:
    " G* O0 [1 d* a3 u
    - s+ w9 f+ z& c- \0 d* V1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    0 y; U( u( w0 Y4 C8 J( T2. Environment内部会访问MutablePropertySources来解析
    6 p: `9 W! y, f/ Y& @8 K3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值! p5 g% T6 t- h5 f% S  N( B
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。1 Q4 Y9 u0 S" M. i" F

    ! e: O+ i, W. t2 F$ x下面我们就按照这个思路来一个。
    4 ~( J. d$ h, e  ]  n
    % q# U% m2 @4 s% q+ v来个邮件配置信息类,内部使用@Value注入邮件配置信息8 W0 l) s2 W5 d! Z
    * x) a: m! c$ c
    package com.javacode2018.lesson002.demo18.test2;
    " I6 F$ |. ~8 \- ?2 u: d. s: ~8 R/ L, h( H% K5 E1 N; b# M7 G$ h+ M# I1 D
    import org.springframework.beans.factory.annotation.Value;: m% T) a/ |) s9 v! \; M: e% ]
    import org.springframework.stereotype.Component;5 ~, F- Q$ L% F+ ]+ w

    6 o. k* |0 l! R1 d) {" G8 F/**
    * y& C3 M' s5 p+ F9 j  |  | * 邮件配置信息
    & p( q1 u# M: {# i. R$ W, w& Z3 S */
    ! v) L2 O3 L* l% O& H0 b; Y@Component4 \! M8 H/ x+ W9 N, s) h( Q
    public class MailConfig {- l2 L# d5 E2 o$ y

    8 P, P; H; F' Y  P) C$ E    @Value("${mail.host}")4 b4 ]+ {  Y$ u( F
        private String host;' G1 }, T0 H# X1 n/ x9 v2 }: t
    ; ~2 \. X% s) I
        @Value("${mail.username}")( Q, {- v2 P& A8 _1 n0 _. Z( W
        private String username;9 f0 m* d# Q6 Q# x: u( [. |
    8 L4 B9 N! A% ~$ _" v: c
        @Value("${mail.password}")
      |: H, b/ a2 U# P1 g3 p    private String password;& e2 \- c& t0 b/ M! \4 I* D
    7 g) Z; O) a6 f; S- b# \
        public String getHost() {
    ' ?/ J9 d3 j: k. ?1 h        return host;
    , k, `1 U% z/ O( s    }: q) H/ C4 w5 D" y8 ~! M. l

    ( q0 o: N) [* P: I    public void setHost(String host) {
    % H0 S) ]" q0 a* n8 T7 l& O$ a        this.host = host;4 q. m' B) G- t3 C6 u
        }
    ( C" F% q. V( D( T: k- b
    5 X4 z/ f2 v/ y4 g    public String getUsername() {
    / }! p  r. D+ D2 h9 R        return username;% q7 b9 @5 b8 |" y
        }+ t- I. t; u( M2 _2 f/ D- S3 n

    9 K) f1 [4 s/ G1 X4 y+ @0 V+ }    public void setUsername(String username) {
    / ^0 R2 t/ ]4 q        this.username = username;
    / i1 q2 [5 ~8 d3 \    }. T" r- m- r' d. O3 w8 u& z; ~

    3 d) i8 L  h* P/ G: F3 ~5 P  N    public String getPassword() {
    8 y( w$ h4 x4 n' }% l% i1 V        return password;
    2 Q! m6 C+ b$ a- T: w    }
    / r1 T$ [7 L; _- N- p4 R7 h" G+ F( D) Q* G" K; g3 L
        public void setPassword(String password) {( V1 e* @+ x" w: b6 ~0 G: v- ]
            this.password = password;/ y9 r$ ?  Y+ L, q1 L+ Y3 z- y" L* j
        }
    ; T1 R& U' J" L, b, |8 m! b' |1 y- c4 G+ G) Q
        @Override8 C( v# O, k; q3 r
        public String toString() {
    0 y* T9 S) M9 D/ j/ ?/ x- p  Z        return "MailConfig{" +
    4 G7 m! w- G- W( y! K1 r) n                "host='" + host + '\'' +  R* P% T6 z/ h" {
                    ", username='" + username + '\'' +
    . k) o. Q' d) g7 a0 l9 ~- f                ", password='" + password + '\'' +/ t# c" p8 z$ \
                    '}';
    . I/ u5 m' C9 s* {$ }6 W" D& G    }1 C4 e, {- }. Q& K  Y% v
    }
    0 _4 G$ a8 k$ f6 \$ m3 P( A$ h再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    $ Z; w2 e1 b1 b7 m. v9 [
    2 `- v* D: Y% M! a4 S) \package com.javacode2018.lesson002.demo18.test2;
    ) p& M) P! i8 o. e1 o; M
    , d* V! I4 h$ G$ L: W: W9 V3 zimport java.util.HashMap;1 {  g3 |- S5 d( l2 _* x/ b* }
    import java.util.Map;, M2 o) k" v5 _0 P) i9 U$ }% M
    + v6 V/ d" m1 T; m8 E7 o& B
    public class DbUtil {
    . z# x( ^/ j0 k    /**0 W& ]  `2 ?1 J' B
         * 模拟从db中获取邮件配置信息$ _( w- i  P, x2 U
         *+ |9 L: j: F4 q. m# l7 y
         * @return
    3 U/ w$ G3 g- n% N/ j     */
    : N5 o6 x# h; ]6 o, V$ ]    public static Map<String, Object> getMailInfoFromDb() {3 L4 x+ g" p$ [6 |: h# w
            Map<String, Object> result = new HashMap<>();; M. E" y8 X  I: e. C
            result.put("mail.host", "smtp.qq.com");
    ' d) v1 X2 Y5 e* `        result.put("mail.username", "路人");+ h: Z. x7 Y$ m. q) C' \
            result.put("mail.password", "123");
    / W4 N7 e3 I$ g& h. P, D9 j; j        return result;
    " s% A# l6 q9 A  a    }
    3 ~0 s$ d7 w! k( U7 i9 U7 T}
    9 D$ C- I3 x( Q2 V来个spring配置类3 Q' D- B9 N+ ]  u( P# ^
    : ^8 }7 a/ J0 }0 @
    package com.javacode2018.lesson002.demo18.test2;* a; [5 Q4 q7 [  z
    ! g& [  l, R: F' d$ P' ?2 H
    import org.springframework.context.annotation.ComponentScan;% t' ~  b6 X) G. @
    import org.springframework.context.annotation.Configuration;
    / N/ ^! H+ i6 W$ y3 M8 ]4 F7 K9 x; V% G- x) o# N
    @Configuration6 \3 M; G- {6 j
    @ComponentScan
    ( r* F  T  l+ T2 W5 L: }) spublic class MainConfig2 {
    & X, q' T/ J, U3 b* `}
    ; [- r2 l, ?! f' ^下面是重点代码
    : a- n; p% O- |5 d" g
    6 B& q1 w  Y3 u9 d. [: g, y@Test
    , H, T! |& U+ d7 Q0 z6 ypublic void test2() {# ~; k; a; g8 y- i) y/ ^
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; \9 A) M2 p3 `8 W1 _
    7 a8 L3 Z) U" b( P$ U7 ?% ]! }- g3 k    /*下面这段是关键 start*/
    : ~& {1 [$ r! o& ?- Q  t    //模拟从db中获取配置信息
      ]0 A+ r! O( V8 N    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    6 W# t3 l) [$ }- |3 |" a" m/ H$ M+ B    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    1 h; n: B0 W7 g& J$ k$ M    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # E" @5 c( P) ]1 t4 n0 ?0 _% k    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    * ^8 I# u" j1 T5 \0 X    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);: {9 l2 ?! y) u, C0 t
        /*上面这段是关键 end*/
    7 C9 J' p* I% r9 c" S1 m( T% [- i% u
        context.register(MainConfig2.class);
    : q: ]! N# V. z3 I6 v' J' ?    context.refresh();
    : O* ?; u- a, ^+ C6 K    MailConfig mailConfig = context.getBean(MailConfig.class);" ~% o" E0 k; t* [) e7 g3 D
        System.out.println(mailConfig);/ E1 F. Q) N' P- E
    }
    " x8 o. A0 [5 Z. h5 Z. D/ n注释比较详细,就不详细解释了。! F- l/ [" W1 h- e1 s. |7 j
    5 J( p8 [& g: x* l
    直接运行,看效果
    ; h/ I2 X0 s2 T, L  D$ }, J6 w
    " `- Z3 N9 K# S! ~MailConfig{host='smtp.qq.com', username='路人', password='123'}
    : z9 x; {( s2 q/ ?: I) T# p有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。/ j/ e' R% p* K  |3 ]

    / e) o; ?# ~1 j% p: s上面重点是下面这段代码,大家需要理解
    4 |  b1 _3 n/ w  C" k/ S
    $ d0 i3 i+ P3 k% i/ l/ E/*下面这段是关键 start*/
    7 O9 E: x2 J' `4 M) p0 S0 ]//模拟从db中获取配置信息5 i6 L" P, K0 C, J! \6 r8 |8 X
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    7 o  X; H+ X, [6 V. q//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)+ C2 d* z' r8 f7 c, O
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    * e" }$ B7 Z" f7 X7 E* \//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高' P* `. ~% I5 R6 f5 _
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);+ c& h; u( b4 o2 o( h
    /*上面这段是关键 end*/
    & L& b5 M! ^+ N3 H. O8 t" t. [0 k; T咱们继续看下一个问题8 z, q& }( A0 Q- J. n2 G

    1 O1 D" Q  Q' n0 q如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。/ d: B  [# G! p; r8 v) K
    ' X2 Y8 C5 Z% q2 B. G1 h
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。7 e2 ^" q5 j2 P- \

    : y! [: L. J: M实现@Value动态刷新# ~  c. ]  Q- ~& ~" b+ y1 o+ B
    : D3 p% M, e% h
    先了解一个知识点
    - X3 H/ S+ R8 W/ p8 @
    2 u8 q5 ]' y8 Q- |这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。2 Y  _6 `& X; o7 r; z3 \. U

    2 Z- b: f- P( L! y# }, J$ w这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    ' g" x5 M0 D" U. r9 T$ O8 b
    2 [& Z9 E5 T; e3 v0 t/ Zbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:8 {% P/ E+ c& K, M
    4 V6 V% }4 x; a) r+ x
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;2 y/ e! N6 s' k. Y6 b: U
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中, r0 h- ^$ j; T0 f

    ! B! w1 C; b4 |7 x7 vpublic enum ScopedProxyMode {; t" {9 m8 ]2 O: G( e7 ]6 b
        DEFAULT,+ V! w; p7 x9 o" w; n
        NO,
    * K' |- m- p1 G6 ?    INTERFACES,
    ( w- C" ]$ e8 i7 l: m9 l2 O    TARGET_CLASS;5 L" h! x* }1 I! ^% |0 z$ l
    }& F' J  F9 N8 }" p  _: J
    前面3个,不讲了,直接讲最后一个值是干什么的。
    ; D* k, S7 w6 q9 t0 B+ l0 r. x
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。9 f' x' ]3 x+ X0 F8 f2 B4 S3 |
    8 x3 @# c# G8 U9 t% W5 v( o
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。4 C' n% \. k3 L
    / _( F( u' _4 ~( b' S
    自定义一个bean作用域的注解
    $ q5 m) v" s+ I' ]6 l# k; d' @) m" c! N* R* w, i- N  v  |; i' f8 C! |
    package com.javacode2018.lesson002.demo18.test3;
    # Y/ w5 H; r# D# S. s8 X0 d
    2 Q; D, C! y* \9 i# n, \/ a7 Cimport org.springframework.context.annotation.Scope;$ W4 a$ _6 l$ {0 v2 ^( _
    import org.springframework.context.annotation.ScopedProxyMode;! p4 H. U" a$ h8 I% \! p& ~+ G" m

    0 j9 g7 ^! `, Q1 x3 qimport java.lang.annotation.*;1 Q* p. Z, X2 O8 i2 r% }3 S

    : {. ^1 W1 o% _0 f: G  ~; O% d+ a@Target({ElementType.TYPE, ElementType.METHOD})( E% b; d6 {& R
    @Retention(RetentionPolicy.RUNTIME)& _& k0 y8 i: w2 s7 l
    @Documented- A' C- m+ X( y9 I# v1 @
    @Scope(BeanMyScope.SCOPE_MY) //@16 W: H9 _+ V- X+ `9 N. ?
    public @interface MyScope {
    & G8 J, e. I! k7 `8 I5 q$ B$ K" L# A    /**& o/ q. G1 w0 y
         * @see Scope#proxyMode()0 D+ O7 }9 @: s+ o7 E
         */
    # l0 N- o- \2 K/ _( M& z6 S    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    2 z8 o% B: w5 |9 G4 D' G5 N1 }}) d0 H. ^0 d7 G( y
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    : t  Q+ R; V# P+ S: D5 t
    " \- w3 `  B& [' ^/ |0 Y5 S@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS( y3 J9 M( B. R: ~  J$ j3 V

    3 h2 H/ E& _2 d% W@MyScope注解对应的Scope实现如下
    8 p: D! n# E4 ?" [$ x+ B7 C" V6 z
    package com.javacode2018.lesson002.demo18.test3;
    7 I; s: C& r) o9 Q
    0 B  L9 G' {( J/ U0 ~import org.springframework.beans.factory.ObjectFactory;
    $ G1 O9 C0 D, o% Iimport org.springframework.beans.factory.config.Scope;$ u) X2 T: k8 G& L. X$ t" Q
    import org.springframework.lang.Nullable;4 B( I: M/ h- ?( a  S7 b

    # R2 P- E  e, B3 }8 _/**; T% G% n) ?# K  V, L" |
    * @see MyScope 作用域的实现
    8 o* {; S5 i, s1 ^) d5 V0 @' z */# ^  F2 Z0 F) J, m+ ~2 o
    public class BeanMyScope implements Scope {
    $ c- _! w8 x( N1 `+ X7 l. D5 k" u5 J$ ]# G+ G4 x
        public static final String SCOPE_MY = "my"; //@1
    ! T3 N4 O( |% r* m1 e
    ' N' p! A  w7 @- L# F3 y    @Override
    7 J) r: u4 h* }$ |8 W3 L; |    public Object get(String name, ObjectFactory<?> objectFactory) {
    4 q: a: D8 p7 W        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2( D+ s3 L% I0 [9 |$ N/ H2 y
            return objectFactory.getObject(); //@30 e1 \6 G6 e0 [* {& R
        }  f9 X' Z% a1 C; t

    ( T. J: \1 P% b3 ~$ _/ F* J    @Nullable/ }. M! O% t; y% }4 G+ x9 A1 m
        @Override1 f& P0 p" [4 t. H% ?$ k
        public Object remove(String name) {
    8 h. a$ c" o, }1 z$ L0 W        return null;
    ' q9 A$ {" Z: Y' G    }
    2 s& P7 C- a4 U% i* R; s( b4 m! j, e
        @Override+ W5 y' c  B: h4 _# A: B3 `8 j% w
        public void registerDestructionCallback(String name, Runnable callback) {; X% a# O- z' |% h

    - h& c& Z/ r4 L* V9 |' v    }0 W; u. P# @% @, N( v1 R: F8 I
    6 ?* T- N" v3 x; b' j1 y: V2 X
        @Nullable7 s3 L5 z) F: B, }1 V) L
        @Override1 o$ `+ v; @( s
        public Object resolveContextualObject(String key) {
    $ X& B! P# \$ b( N* Y0 g# ]" q        return null;
    + w0 R5 `! K4 j: L    }  L- ]1 D. z8 j4 n+ P
    ( r0 u% \5 Q# z, e" m  b; R: a
        @Nullable
    3 r: q, T- J  @    @Override, B3 c4 M6 g- G$ G; G$ {0 o! K
        public String getConversationId() {
    % ^, {# I  v* x: v; R* K8 v        return null;
    ) [4 @" I# P3 _& F9 Q7 q    }
    9 l9 ~4 d# E" _1 u! X}; O6 l( Y' @! P% Q
    @1:定义了一个常量,作为作用域的值
    8 q! E. p" q9 V3 S8 F9 u' |
    , F+ J9 T$ f( R  o& A- L@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果6 t/ W$ l1 v" _2 ~! m+ E1 {, ~

    5 G( ^3 X' d" A5 h! r@3:通过objectFactory.getObject()获取bean实例返回。
    3 Z  t  x) w% R# D' ^8 z  q2 r2 L5 }) q6 X
    下面来创建个类,作用域为上面自定义的作用域. |: d3 i( Y) ~6 X

    4 {. m# ]# h( j! xpackage com.javacode2018.lesson002.demo18.test3;
    4 u: ?8 \$ _+ W- H1 i0 o& t8 ^( V8 Y7 r  b; B7 V9 Q- f; p) H0 U& e
    import org.springframework.stereotype.Component;
    $ T# @9 R# ~) ?  \7 T; T" @* W7 s$ u- y1 y8 _+ T
    import java.util.UUID;
    % D5 o- n# Y/ P% Z& o0 M+ L/ e% E% W4 t$ h- B( ?5 J0 s3 a
    @Component% {9 V3 R' O5 O
    @MyScope //@1 / w) [1 V" i. v, \2 b7 z: L
    public class User {9 p; O$ m1 C  k" X4 y- B; J4 c( l
    , l9 K/ P: c1 d/ \, }, \) B6 w
        private String username;& E: f" u8 n1 Q
    8 h  l1 D, T3 \
        public User() { , B: b, ]; B; V7 B7 F" o4 B* ~4 j- w
            System.out.println("---------创建User对象" + this); //@2( H. k! p' A' ]. O
            this.username = UUID.randomUUID().toString(); //@31 q) v: R5 d& Y
        }
    2 a/ U6 C3 q5 E6 `
    5 w8 E" ?; c5 q  Q# G7 E    public String getUsername() {
    1 P3 {9 q: T8 U+ B1 l        return username;
    ( G, B0 S( J! J6 V    }
      C  c9 A; b, n, I0 x) Y, M
    ! X5 Y! b: q+ ~# Z" K    public void setUsername(String username) {2 j3 s& p, a/ }  x0 j% D
            this.username = username;
    , \& p5 U: m* t6 b& Q7 L# O+ n    }5 ~; r( Z" I  Q/ S; }" W* |
      g  _9 m; v3 [' N8 d& }: a# X/ [
    }
    " G6 \% j' r/ z4 t@1:使用了自定义的作用域@MyScope
    " v9 V; L7 F1 s, x) {0 g9 j" X# h
    @2:构造函数中输出一行日志
    % _9 L2 l8 U& G4 L. S# \" l5 p# ]( Q. M; W$ D
    @3:给username赋值,通过uuid随机生成了一个( e0 s3 O" e' d! @1 ]

    4 r' y# A% G. E来个spring配置类,加载上面@Compontent标注的组件
    & E' N- u) c4 a* p0 q6 ?* u/ ?$ ]: b& o& H$ C
    package com.javacode2018.lesson002.demo18.test3;
    ) c$ B2 A: R' J
    9 h/ H, \- Y! v% Vimport org.springframework.context.annotation.ComponentScan;% I% v9 Q4 _/ G7 b+ m
    import org.springframework.context.annotation.Configuration;; D' E! A( o; u
    4 X2 Q. [7 R* _5 l. x& t' H
    @ComponentScan' B, Z' K# J3 a. D7 S# S
    @Configuration
    + k& q& h7 l$ y* s/ h' O" gpublic class MainConfig3 {
    + Y- O$ _  F0 @& v}* V* M" D" @" c3 S4 B! I3 B
    下面重点来了,测试用例) B) x4 k" \/ \
    ) u8 o" v9 C3 T; A5 s
    @Test! d" U! f' D$ [9 T! q
    public void test3() throws InterruptedException {
    ! i! }$ K( Q4 J2 F$ ^7 J: J2 ]    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    4 [, T0 M6 Z7 C# l    //将自定义作用域注册到spring容器中
    7 n9 [6 G% O4 L7 K0 T    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    : ~. m* a% N0 q9 ~. `# @# P5 s* e% Z    context.register(MainConfig3.class);
    7 m+ t. x* g* h. K& {; k    context.refresh();0 R/ ]; O/ p7 f7 v  Q# t6 B

    , @# r: L  l0 f" @" u    System.out.println("从容器中获取User对象");  J- V7 J% f8 Y$ M4 z" b( m6 a
        User user = context.getBean(User.class); //@2
    # }- h  `& D- j7 M3 G    System.out.println("user对象的class为:" + user.getClass()); //@3
    5 s7 J/ o  f9 u6 p& o  x1 V) r" j/ }
    " ?7 e' ]8 F6 N6 v    System.out.println("多次调用user的getUsername感受一下效果\n");4 b/ a6 v. F( L: _3 K# v6 F' y: t
        for (int i = 1; i <= 3; i++) {
    8 n+ n* R: C) W        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    ) q$ Z) u- g7 L! R# l        System.out.println(user.getUsername());* M: Q5 ?# x! W
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));, b! l6 q& b5 c
        }
    / r+ k0 L6 q0 v1 D* o2 J7 B. \}
    9 z% G3 f0 Z5 H/ Y" w@1:将自定义作用域注册到spring容器中
    $ n% w+ \4 R; ~8 Y* x7 n4 c" d0 |6 H/ f
    @2:从容器中获取User对应的bean$ r6 t; Q) y$ e% p7 }3 I. X3 b
    $ B( |; v* z. K
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    + |  M0 J3 p, B. D- h, y% B( W1 G( z9 C
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    & l" Q; X& V% Q8 U% F0 B
    ' x3 ?: y  u7 ^1 o见证奇迹的时候到了,运行输出- r& k+ H4 i7 ?" F& W

    . X7 E: C6 A+ S4 V+ g4 M! s从容器中获取User对象' ^" H; M0 h; ^" |3 i' K+ g0 h
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    - Z) S. F  O4 i: l, H多次调用user的getUsername感受一下效果( [8 G5 x8 V* V- i5 j$ p, }
    6 x* h& Q6 [8 Q# M6 K  k6 }8 S; i* ~
    ********
      U" z; L# i/ i2 ^: T: J第1次开始调用getUsername. a# W6 _6 I! b; \- N5 y
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    ' O/ ]' x+ N. V---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    * o+ m  H) k4 y$ X7b41aa80-7569-4072-9d40-ec9bfb92f4389 r& X# G/ K' b
    第1次调用getUsername结束
    % Z2 J7 x& U7 r2 l* O********* ?$ S2 E8 Q6 ]5 C. `+ t3 e5 x

    " M$ o. [& x% C; F********- J* F4 `4 k9 M7 _3 W7 H  W3 {; K, B
    第2次开始调用getUsername
    & `, Z8 I1 k2 EBeanMyScope >>>>>>>>> get:scopedTarget.user' Q4 b6 v5 ?* s0 i9 o
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    6 l: |7 r9 ]( e/ n* \6 Y7 {- T01d67154-95f6-44bb-93ab-05a34abdf51f
    ) I3 ?8 S/ n# V$ @" o第2次调用getUsername结束
    5 h- X; |6 B' K$ I********
    ! T2 m. u' n' V' a4 O% I3 ?$ k# C7 J/ n
    ********, y8 S/ ]/ ]* {2 J  s
    第3次开始调用getUsername. r! U0 @+ a1 ?0 `8 [4 s% n- V
    BeanMyScope >>>>>>>>> get:scopedTarget.user$ \2 X5 F/ Z3 K/ T9 V8 S
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    9 ^# M7 c. ]2 a/ i7 \% \76d0e86f-8331-4303-aac7-4acce0b258b8
    4 h3 z5 V3 K3 c9 Z) x7 O  x/ o: n第3次调用getUsername结束
    % z, R3 E2 F% p. b- p( L********
    & c! c1 z2 h4 k" Z从输出的前2行可以看出:
    7 R  |7 o& Q$ f8 }+ U- Q3 X" {+ B# }
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    5 v" U" G# S4 c% }' h$ K6 g
    / v, j9 ~, e% M) T" P! J  k第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。0 F2 D4 O; P+ h0 N& o+ O
    * }  ^( {# ~" ^9 y% \
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。) h: L) l3 Q9 d
    ( K) I1 ?( {: K- g0 G+ P
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    7 I+ @, D, R; L1 p0 v
    ; M1 Y# u- |6 m4 q( q$ u- |* w动态刷新@Value具体实现
    ) Y- i, b! g, p; I) y9 q9 e5 F2 S; J* o8 q- K" g$ C& k
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    ! n! Q. s5 l: `+ |+ u3 M' R& f7 b- `1 S/ N7 N! r5 r
    先来自定义一个Scope:RefreshScope
      U0 c. I2 {! t! Y
    / u% v: }% M7 i" C. n8 P" ]package com.javacode2018.lesson002.demo18.test4;
    % x. D% l4 G. `( ~. f) T9 w1 D- d* c& i7 i3 ^4 z
    import org.springframework.context.annotation.Scope;
    ' i& L( K/ ~" ]2 ?import org.springframework.context.annotation.ScopedProxyMode;
      l5 W( O2 S9 ^' A& e% [; t; l/ ]0 T1 h% z- x/ J: L
    import java.lang.annotation.*;) g& F7 P0 w. r& H# M9 `: {

    : }1 F: F/ X. n' E  B- ~' P@Target({ElementType.TYPE, ElementType.METHOD})5 g8 \* b) w. F* p0 t* E
    @Retention(RetentionPolicy.RUNTIME); X( J) g8 O* v8 v
    @Scope(BeanRefreshScope.SCOPE_REFRESH)+ w: i* C+ Y! T
    @Documented
    8 ^3 s  T* v. M6 S, f7 x7 _public @interface RefreshScope {
    # `+ x6 z# A) H, ~    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    3 X( U9 h, y& @7 k# M1 a3 P) l}0 R; w, A5 P5 M- _" c2 ~6 t1 @
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置% v( C8 Z+ p/ I4 c* x$ e; y  H( e

    + ?3 z$ N' _, T& W@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS5 e" z% [- t" H: l% G1 \
    ) W9 _3 c- q; B( K
    这个自定义Scope对应的解析类4 I# g# @8 F, M' W! r$ I

    * A4 y! `& S) b5 N! _/ {) E下面类中有几个无关的方法去掉了,可以忽略
    ; L! d4 Y7 Y/ X3 o
    4 |* }, O8 X* Q- ^package com.javacode2018.lesson002.demo18.test4;
    # A1 Q' D) I: F% e! C8 \% |; c8 L0 t+ ]+ R8 r; q# i0 K+ x; Q
    6 Y7 F" H9 b/ i. U- @; r+ x
    import org.springframework.beans.factory.ObjectFactory;9 s' s$ Q$ a1 m  [% [# F. G
    import org.springframework.beans.factory.config.Scope;# |2 H: u" h" D- `: B0 x' U  d: v
    import org.springframework.lang.Nullable;2 T& [! V  X8 m2 s" R

    7 f2 }' l! T- T, S* C; P! U1 Fimport java.util.concurrent.ConcurrentHashMap;
    ' v% ?+ j0 H1 c( C. J7 @
    7 b5 R. M" C1 u6 W  S% bpublic class BeanRefreshScope implements Scope {# I9 E3 R/ Y- e1 D% R; n
    , j4 u. z& h5 b2 ]" V9 s5 t
        public static final String SCOPE_REFRESH = "refresh";$ v, Q* ?  \$ P* O/ e- k

    ' z) q) y$ O8 x. ~    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();/ f# i  m; O0 a* Y

    # V3 O5 D4 D5 B$ T) o+ u    //来个map用来缓存bean( n. Y& {2 r& t! Z8 D# i& c
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    ; s+ H5 X7 o+ x( Z: U9 N1 [% h; K* t9 [* }4 S  H' r* F" @* w
        private BeanRefreshScope() {) V7 l8 U( y3 Y( C& o5 y8 F
        }
    0 c, ^. C' v' F. ?
    " q1 x$ K* [  ^" N$ \, R    public static BeanRefreshScope getInstance() {6 t+ {% i" b# c( y# w4 D. c
            return INSTANCE;$ f* q" v+ L+ Y* X* ?
        }
    1 z. m  f3 r4 z* |3 |3 C1 f. t0 X) w& V3 q2 Z- h2 E; [
        /**3 h$ N$ W  K) D4 f  x$ V
         * 清理当前$ _" ^! h" ?/ w2 ]
         */) K! f7 E7 G, c
        public static void clean() {
    - q: ^' j- t: p        INSTANCE.beanMap.clear();
    0 v: O- @- M0 s    }
    ; j: b1 g" [( M. B( G; [* G9 r
    ( C+ t. z* R* p, \9 r( l& m& }    @Override9 {) f, R9 m% N) z
        public Object get(String name, ObjectFactory<?> objectFactory) {9 A4 b+ j  x* i
            Object bean = beanMap.get(name);
    9 c! o) O5 M% o        if (bean == null) {9 b; G: Z/ L$ ^
                bean = objectFactory.getObject();
    9 Y. Y# K0 p& j+ |2 M# @7 G9 ~            beanMap.put(name, bean);+ B, t! L* c% F8 ^9 Z
            }
    & y. l# J) x6 P- M+ v        return bean;( F: U+ L  [* X6 b
        }
    2 D' F6 i- j: }, ]
    0 D: K) V  c6 T% A7 {  Z}% F% V+ A2 w# j/ }
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    & T' D/ k  A( D9 g8 V- b1 N* v
    4 n* Z- O* R. C+ ^7 g: R; ~4 X) N! Z上面的clean方法用来清理beanMap中当前已缓存的所有bean. e6 B2 R7 P- l  L& `# ^
    0 i, z# {3 K: c. M& x, ?; v5 k
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    % c* _  K. G0 R! d3 h9 f# |3 x" k7 h8 l. X+ ?% C  T% [8 I" }
    package com.javacode2018.lesson002.demo18.test4;' N, k9 }% L; a8 C4 g
    ( [$ k; E0 ]  x( G9 P
    import org.springframework.beans.factory.annotation.Value;' _* t# _8 {* b2 N% I
    import org.springframework.stereotype.Component;
    , M% q8 |9 l0 \7 N5 e9 E) M7 n8 I+ n0 C3 d/ b
    /**
    , e1 B; [! W6 a: q * 邮件配置信息2 A6 o' D6 J: e& c$ P+ l( s. E
    */
    , m) B! t- ~" T@Component
    " x- S1 }% i: z' \2 x& N5 E@RefreshScope //@1
    " {; x' ^( A4 D5 I2 c' upublic class MailConfig {
    4 @% ~6 U+ C6 P9 u" M
    + m/ _' q6 X! H) E6 J    @Value("${mail.username}") //@2
    ; Y" t5 ^. J* e7 B    private String username;
    % R* W% e3 l5 T# P+ p) e1 E3 R2 }# e  M. V$ J( K
        public String getUsername() {) w. X$ r: @) P/ ^7 Y( E. _
            return username;; W4 e; s% h7 @
        }0 m0 V' {0 X1 p8 k

    6 o* l9 Z( j5 G3 V    public void setUsername(String username) {$ z% A, c5 X$ q( v/ k* |
            this.username = username;
    ; s, D" R" r5 ]0 @* S    }6 c/ I! X2 n& ?8 k6 O4 \$ ?0 s, h

    / x* E' j8 o/ d1 }. [3 P! B* T! q    @Override; C% W) W- S! P5 E
        public String toString() {- ^- q1 n: w* j3 p# x
            return "MailConfig{" +
      ^  W  Z" I+ j& n* J; {/ ~# t& f                "username='" + username + '\'' +
    $ i( w4 h7 f* j0 q                '}';, o7 \" m  {% t+ F6 }( p: k
        }: O" [) d8 ~% g1 D* k
    }+ ^- ~( g! {( r0 d1 B7 I
    @1:使用了自定义的作用域@RefreshScope
    % L! \$ T+ N: @4 B1 B8 f7 T2 y) f
    . {5 s0 k% T) X; t* k@2:通过@Value注入mail.username对一个的值. B6 S) a( ~2 g" ?& u, I
    # N2 n( |+ R- J6 d% k
    重写了toString方法,一会测试时候可以看效果。
    ! Q) D! T, w4 L
    # ~2 Y1 ~: a! O5 n( u/ y再来个普通的bean,内部会注入MailConfig
    ' O9 j' j5 `( ?! x, w, n5 Y, t3 U3 I: v. Y7 X' M% p7 V4 k% w
    package com.javacode2018.lesson002.demo18.test4;0 Y; n! y8 M1 Q% l' }
    . i1 L" }+ Q. z* X4 i  R# Y$ B* b5 F
    import org.springframework.beans.factory.annotation.Autowired;
    " R0 j9 ]: [0 T6 k. Bimport org.springframework.stereotype.Component;
    $ t! O" s; w( \! B" j
    ' W* h- Z3 }: k! S, s' d; ]' V@Component
    ' J( ]$ P* @, G6 q' t( Q. Zpublic class MailService {! b. ^+ X/ ]) L. M& I0 [: Z7 T
        @Autowired
    # H3 P4 r- t) r0 h    private MailConfig mailConfig;7 Y: }0 x: [$ w- w

    ! ~9 N, t$ k' v3 t1 F( s* }    @Override) U4 }  ?  ~! ]
        public String toString() {/ I/ W8 [* \9 J  H  @
            return "MailService{" +
    # j9 w* l$ x. v% x! ?1 n  Z# ^                "mailConfig=" + mailConfig +1 M2 Z0 ], T# Z1 V
                    '}';. S+ b7 d1 Z( `
        }
    7 _7 T; Y* s- w* t1 t}& I& \6 Z$ N% T  }" H* W6 o
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    / @" U: Y3 E! C8 c8 t8 F! y$ `$ K  ^# [
    来个类,用来从db中获取邮件配置信息5 c4 U5 P" V! d, C" l
    & ]$ H8 E) q3 Q
    package com.javacode2018.lesson002.demo18.test4;3 ?/ K8 s8 L# ~% K' j' Q( _3 y5 o7 J
    : j" U& A9 q/ r" z7 G
    import java.util.HashMap;
    1 B( ^' x: v" e  u% T: himport java.util.Map;  F4 k7 `# l6 G1 v% E* S
    import java.util.UUID;6 ^3 Y! N2 d6 z

    ! Z' K" b: Q( y1 }4 H# Bpublic class DbUtil {; C, _' H, B' _4 ~
        /**
    7 A" r, A9 d" P9 s* z! u7 g& M     * 模拟从db中获取邮件配置信息
    6 h4 G: c2 O7 k8 U0 A     *
    3 O3 ~2 H1 W+ |. o5 [9 H, L8 M& q     * @return) |1 v& [% \. \
         */
    ; f1 N% A. l1 \& l$ L    public static Map<String, Object> getMailInfoFromDb() {( Z9 Z. _! C  K$ v, c; z3 c
            Map<String, Object> result = new HashMap<>();# q  i, N* P( I& p
            result.put("mail.username", UUID.randomUUID().toString());* g5 j- L: a. q* m+ j3 A# h1 |
            return result;
    2 j1 {' ~* Q" c$ p7 a' `5 X1 w/ X    }$ |: m# c4 D! X! X2 V
    }
    , B( y& |) l; Z来个spring配置类,扫描加载上面的组件
    7 w& W2 ?4 d8 P
    ) ]% q6 E" b# {3 O- Hpackage com.javacode2018.lesson002.demo18.test4;
    5 n& g2 d) Z+ E$ x: b2 |' u( r9 z% `* M3 Q
    import org.springframework.context.annotation.ComponentScan;
    + e( T6 K) H3 E% o: P# w2 dimport org.springframework.context.annotation.Configuration;
    ; E* E; J( K$ L1 X7 ^* a- Y3 g& |5 G5 Z' x# E7 o. E
    @Configuration
    % t2 N' M" G+ |9 Q2 i@ComponentScan; j! Q# }/ }7 G3 x) z1 p' C1 I$ H
    public class MainConfig4 {6 |, Q9 t+ y/ H+ y' }* P8 Y, ?' I
    }0 S! b+ n2 J2 _* Z
    来个工具类
    6 o, w, C3 k# p1 T* _1 |
    , c( n4 m0 O1 A: U内部有2个方法,如下:5 L' }- C3 w# N, t/ ~) V$ I

    , \) \, q- C9 m0 H3 Apackage com.javacode2018.lesson002.demo18.test4;+ `. m' J/ y8 m, z6 |9 r0 T6 e
    / ]! ^3 C* d: L
    import org.springframework.context.support.AbstractApplicationContext;4 Z; f& F# a$ R, \( B7 U  V! T+ ]
    import org.springframework.core.env.MapPropertySource;
    " ], N4 p, M0 h2 H) J  a" l  ~
    , T3 X6 k- Z+ o' O3 _import java.util.Map;
    8 e; p4 q) U0 n2 C6 B! y/ |0 F6 Z6 l  {/ b( q7 Z$ ~7 Q: f8 A
    public class RefreshConfigUtil {3 g7 a7 K: {2 u
        /**$ U9 ]* P* [. W" [' j' N3 Z
         * 模拟改变数据库中都配置信息
    7 B6 u7 e" m8 R% |+ H. x$ Y/ G     */
    . c/ ^" {- T1 l  ~    public static void updateDbConfig(AbstractApplicationContext context) {" v6 S) M  ?# D6 C$ A) x8 K3 e
            //更新context中的mailPropertySource配置信息
      o# h1 W& I% Q6 ~9 L" R9 p. l. o, \        refreshMailPropertySource(context);
    8 k" c( }) v$ S& k, L
    - e5 ~3 T8 t% c- f        //清空BeanRefreshScope中所有bean的缓存# M9 f* i9 Q" I+ H
            BeanRefreshScope.getInstance().clean();
    6 ], y9 i0 f- ~5 x' h    }  ]) ~# a& c8 A

    0 z. V# z* r! D! ~( M1 n% A    public static void refreshMailPropertySource(AbstractApplicationContext context) {: B4 n5 n" `" Y. F$ w  ?# m1 ~
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();0 P3 q  z) o2 y+ q; @
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)) R$ _2 e" o. d( K; l. m+ h
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    1 X6 J" Q% V1 g1 N# V8 c- {        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);! ?4 A- x7 e; F! ?  g
        }
    3 n3 \+ C: W9 o* b4 T! w$ s8 G
    : X% V' R+ D+ ]/ r& r8 d: t( j}  U, s. l" Z, f. o9 W
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    + v$ W+ l+ \) z5 y% f5 \* v  J. I) e9 b
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    / v7 l" F$ D# r# _8 h7 x) X, J
    来个测试用例& B% L# v7 x4 Z6 ?

    & H0 L6 C/ c' I7 L( [@Test& i& I% y. n: I% J! b
    public void test4() throws InterruptedException {
    + z. m, _8 p3 O* D: t6 X, \    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    % Z2 ~9 }9 r  Z+ g: H( G    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());9 g  q9 \6 R* @
        context.register(MainConfig4.class);7 z9 d0 _% n" K4 h( l8 j& Y' @+ |
        //刷新mail的配置到Environment! @  R$ w  `$ o0 G5 Z( @5 T
        RefreshConfigUtil.refreshMailPropertySource(context);) @, Y7 Z1 ?/ j
        context.refresh();
    : P8 e! K8 z; P* C  o) t9 O: s3 a1 \, }8 V' S" k9 j
        MailService mailService = context.getBean(MailService.class);
    4 R2 p7 \5 s; x) k% M% @    System.out.println("配置未更新的情况下,输出3次");1 f# R  ?; v! h4 c
        for (int i = 0; i < 3; i++) { //@1
    3 G/ Y. u- P4 m; p$ ~4 ?        System.out.println(mailService);
    ! \+ W8 x+ f( O5 w2 Y        TimeUnit.MILLISECONDS.sleep(200);  m8 n4 |" s; V' `2 d
        }/ E5 ^3 P9 x. _8 N  U: M2 Y
    : L& Z' v/ g9 r# U; @1 l0 j
        System.out.println("模拟3次更新配置效果");% W. B2 a  m& y  C' U1 h9 @
        for (int i = 0; i < 3; i++) { //@2$ o" }. N) E8 E
            RefreshConfigUtil.updateDbConfig(context); //@3
    . \! R. [; V3 F* N( A* r        System.out.println(mailService);
    % p5 ]: Q/ g% V* X# Q- c: ^5 W6 V        TimeUnit.MILLISECONDS.sleep(200);
    * [% B- e% Y9 F    }
    ' m2 H- t+ D# v}
    , K7 N; n! Q! z( L# _* o/ N* V. P@1:循环3次,输出mailService的信息
    " S: O$ d( y3 m
    7 \: d9 m# c" A9 I* A. }. }) |! H" U@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    8 ]' J7 s5 \: t8 ^' i8 e" y
    ' ~8 ]7 b3 f6 M/ H  t' R见证奇迹的时刻,来看效果* E( w; P) B1 I; f4 E

    ; G1 u, n: `0 w# ?  _配置未更新的情况下,输出3次
    8 `9 [/ q/ ~& n' Y. L; h& {MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}$ l$ n9 D  ^; j& P0 E' f
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    " i$ Z& e. `* s1 S2 F8 w, BMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    3 K+ G- ?) p% t模拟3次更新配置效果& L3 g* i7 q7 t# t- ^
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}+ [4 W2 B5 B8 k2 S+ Z
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    % a$ M1 {4 _$ r; oMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}* l* l5 A( L* E' V, r1 H
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。3 O' v- M, U7 e1 N7 w9 Y

    : w) y4 E3 @: M/ S4 |- e! C小结2 ~9 L, H2 H" J

    5 t) Z' m  h$ ]; q动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    . u- N6 ^; Y4 g- i1 F/ a
    * W1 w9 q' B% Z$ o: f( ~9 y3 m! {' h有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    6 b7 U- M( D  b, A6 v
    + V" F% |! R$ F总结
      z5 m4 L0 E' t. p( {0 ^
    1 w. D  ]/ w" A( B/ p; C: a本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!, H9 m" [" C; E- m5 d2 a/ F) R9 C

    + K; g, i; l8 {- F案例源码$ b0 A. Z9 V$ A) v9 o; V
    3 l7 W' I' A" v8 f& h( H
    https://gitee.com/javacode2018/spring-series/ g6 G" Z) }% `: i! _' `7 z" A' p
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    6 I0 V6 E) ~# ~- F
    1 C) Z! r% N1 h) _/ O* S$ r1 _Spring系列
    ( B3 F% |( H: q) E; R$ C* h- H7 ~" v& u1 L! r5 Q
    Spring系列第1篇:为何要学spring?
    , D: [6 q3 _& C% |3 D
    # ?" C- t0 M' @0 E/ N5 \Spring系列第2篇:控制反转(IoC)与依赖注入(DI)! M; {3 U$ t+ Y4 N( z2 k* N

    ( n: i" }4 J& B( n' n! qSpring系列第3篇:Spring容器基本使用及原理! M( J8 D& s0 m3 C
    ; w) u% n& i- i, ^4 s9 d& Y
    Spring系列第4篇:xml中bean定义详解(-)8 _- u" R8 A5 g

    * Y4 P, @- g  HSpring系列第5篇:创建bean实例这些方式你们都知道?
    9 T# k5 W) f5 o. ]' V; M7 E0 U4 K/ V: ?8 @8 N
    Spring系列第6篇:玩转bean scope,避免跳坑里!+ N" j) d- U4 P1 v
    * O" c# `6 s1 ?1 i# n
    Spring系列第7篇:依赖注入之手动注入: G! c* s% y' Y' u

    # f2 [% s4 K& t' ~; Z5 ISpring系列第8篇:自动注入(autowire)详解,高手在于坚持9 r( `% B& s) c4 }
    - c8 {  f" \! ]5 F( h
    Spring系列第9篇:depend-on到底是干什么的?
    2 D3 |8 U9 O' C0 J8 K+ v& \+ J/ Z
    / G% h! s# I: S8 P0 L: ]Spring系列第10篇:primary可以解决什么问题?- b8 c" E7 `8 o: a* l2 z; x2 i

    " E: x( I2 G5 X" g; RSpring系列第11篇:bean中的autowire-candidate又是干什么的?% @& ~0 a2 P' a6 t- N* b+ R

    # ?0 v5 p& t( R8 `Spring系列第12篇:lazy-init:bean延迟初始化
    " Y" U3 S5 y' v; B3 f+ a: }5 q8 K3 z& B6 Z3 O8 U7 j, p% Z
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    ) Q( T8 ~, J/ E0 a
    + B: Z/ P( F! L; c6 q* HSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?& E4 E4 ?; q) {+ L
    5 _  U& t$ x  _) ^
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?1 W9 P  K  P; ^( ~9 S# A, ?
    2 \% ?! r$ B8 x# y0 t) @
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)0 s) K) N9 s# `2 R
    ! w7 j6 P& C8 i; }7 f: o
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)* w* j4 J  D2 l8 Y5 }9 X( n

    . D" w  I8 n: HSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    7 A& k$ u: @. ?8 f  Z; S
      u) E9 Q+ {9 }+ H4 vSpring系列第18篇:@import详解(bean批量注册)
    4 u3 ?" N! j5 U
    / @) R1 i/ ?% e/ D$ g5 GSpring系列第20篇:@Conditional通过条件来控制bean的注册
    , T5 Q% S* @9 }: k. B
    0 \, U' s9 P% i0 l7 f" h3 KSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    ( H4 u" U' i6 L5 H1 |$ E: {& g! _! g' a3 }0 V
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解* u' j& a! n# V. n: y) F( h" c* M

    0 m! R) h% w) bSpring系列第23篇:Bean生命周期详解
    , a- Q% a) X5 y2 R; l& U
    & Q' w/ ?) g5 V# T1 f% oSpring系列第24篇:父子容器详解
    0 m' T) ~6 ?1 [! y* y/ X4 R1 H! X$ I) a& ~9 F2 `4 t
    更多好文章
    - o/ h$ g/ u! c$ y% C" L9 O+ l( o2 b
    Java高并发系列(共34篇)
    * P+ `" N4 u, b3 Z. }9 P2 E: v
    MySql高手系列(共27篇)4 m0 B3 x( E+ D  H- |  f; m2 i

    % t* k- c% O, J' h0 j% w! Q( z. L; n9 A/ kMaven高手系列(共10篇)
    ; x! L/ j! E+ i8 M
    . O* s( N. ^* K9 C- Q% XMybatis系列(共12篇)
    ' y! o( U" ^; J' d) W3 I8 j" q8 A8 m3 x+ S0 s+ T( \" f3 Z. v
    聊聊db和缓存一致性常见的实现方式4 Z, k5 M& O# T- y

    / k8 P  K7 }5 Q, X' D9 ~' s. w0 d接口幂等性这么重要,它是什么?怎么实现?, u8 ?, M% ?4 a$ ^
    , ~7 U4 n, B; ?! w& W/ \! s+ @3 p
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!6 H3 C1 K1 |& K; X! y
    ————————————————* q8 t: J+ y6 o% T3 }
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。5 b+ v! f# p$ j. \! k0 ~6 x
    原文链接:https://blog.csdn.net/likun557/article/details/1056487575 P2 G3 v) i0 w6 Y) W
    7 K1 I2 i  e1 p- C8 N

    6 ]' i& I. S# Q) u& f! i# Q
    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-4-21 00:09 , Processed in 0.496395 second(s), 51 queries .

    回顶部