QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5392|回复: 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!
    8 w- _6 m6 G: U! U  j  _% c3 G5 A. y疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    / {6 U$ f# K0 ]9 S% x. w3 `2 f0 }- h; M
    面试官:Spring中的@Value用过么,介绍一下
    ' b" P' c. m( h# [( c1 b! S
    3 j7 ~! m  {" |我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ) y( k/ ?( x; J1 L2 E: Q4 d! Q6 U4 R; E8 A9 @) v$ u. h' u9 @
    面试官:那就是说@Value的数据来源于配置文件了?( I7 N) {( d, S

    9 H2 Y4 A% ^2 K8 b; [) F4 }- \. ^我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    9 ~0 I8 E3 O: M" N
    5 j4 u7 E4 k$ y( d0 `  R- E面试官:@Value数据来源还有其他方式么?
    ' b. {0 s( O& f  R. K
      J0 c/ {+ L+ k. l/ X3 }我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    8 j1 n# m4 l: u' ?0 F  \( m
    0 p: l4 Q  H5 I面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    1 i2 ^4 r6 p. H. s  Q
    % C' Y* ~; c. Z. y  d) R我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧6 h- Y/ y8 e# G& f( U4 N

    % m. ^. R  E; _( g) l; I% Y面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?5 G& |8 E, |& q, \0 O/ J7 ^) D
    + k. I9 z1 l, o
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    4 \! m* Z' F1 f1 S: w1 ~4 t; \
    . o7 M9 U: ?& [5 w4 r2 c* I9 D( T面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?; Q5 [0 k9 l/ I4 L) x
    ! d2 N3 o, i9 |5 `5 d1 R
    我:嗯。。。这个之前看过一点,不过没有看懂
    ( E% g: M. F3 U/ q5 O! [
    " F0 J& F, m8 C! k# [6 h面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    2 S7 k! e! f9 J5 T+ o0 H8 F9 U! O* V9 o+ Y0 b
    我:3万吧
    4 ?0 L; {! ?  R/ A7 _" G
    + O/ Y# S8 g5 `' S+ e. K1 \/ i5 U! t面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    4 B1 m" N2 g9 F2 s/ J7 g! d4 B+ a9 a' s; R) }/ s7 L# K
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万3 W5 O0 h* @7 Y3 {
    8 P/ Z5 B5 ?" e( D
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    2 I' Q- ]  |: M+ \3 Z' [0 p' R; k
    1 k7 f! e8 S1 I我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    ' s3 c+ Q, c6 B" N5 b, \: d3 O% f4 N: y
    这次面试问题如下; |" F* }3 O) _1 z

    & k% k9 [6 ]( k@Value的用法. k1 V# J( h. R. a4 y6 J

    4 Z) ^+ z- y1 E* |@Value数据来源
    4 A; L3 F2 W+ j+ q
      ]7 T  T% A9 X3 t  S5 D9 O1 x9 K@Value动态刷新的问题
      m' T5 F% p* Y5 H/ A8 q
    , K/ ]6 b7 M* z" J% ?: E0 i下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。; k7 \, B# k$ g* N6 ~& K

    ) p- S, l5 k' i0 \9 h9 R/ `@Value的用法1 V% V/ j; C! J9 n* l$ u

    # w7 Y- L* q, L, l3 s1 _! ^系统中需要连接db,连接db有很多配置信息。
    / I$ U  I: X! ]" K
    % Q0 i" g% S- ]系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。5 |; ]. {/ m0 z7 e( x8 |
    : x! Y; |% E7 ~" K4 p
    还有其他的一些配置信息。  E' |5 |0 O0 s% h$ C9 ^
    , g4 o$ r0 T" p. G% F, p* Y' x9 L
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。9 \# u, q* `% ?/ o3 Z
    - w4 g  i" k" S% D7 ]& d& j
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。. B5 }3 h# o( n7 L3 @  V
    , ~6 G* F& B9 e$ @8 W  K7 x; [
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。7 P/ O4 m! ~" A8 D- m

    & W$ [3 R, Z( Q( l1 r7 x5 I- P通过@Value("${配置文件中的key}")来引用指定的key对应的value。0 y. d( v2 C, h  c; u

    ! E  [7 f: W7 o; t1 }@Value使用步骤, w: P4 t1 E6 Y7 \/ O* K

    9 Z, J2 s- O8 J步骤一:使用@PropertySource注解引入配置文件
    & _5 g/ U% J6 f& D7 p* k
    + V- I1 ~7 M* t+ N1 Q将@PropertySource放在类上面,如下
    5 g# f  y4 {. O4 j4 c7 q
    ; {, `' I( Z  i. E; H9 [# H@PropertySource({"配置文件路径1","配置文件路径2"...}); a, m. u: v! E& V0 ]
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    4 p$ L) m. `% [
    3 ]0 k$ M9 @: F+ ~如:
    8 p$ a/ t* u2 L) E# z% T# s/ f1 P+ ^" r* d- ?1 J
    @Component! D: g- y/ q9 ^. \  }5 T$ u" h
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    ' a0 {9 [/ ~; u' N" a1 _$ `& Gpublic class DbConfig {; I7 V/ L- S& Z* H. U
    }
    / G% [- D% S' ?& p步骤二:使用@Value注解引用配置文件的值" C& N3 \! u- i- d2 `

    6 h* x1 }+ f: K8 s% |9 a+ M通过@Value引用上面配置文件中的值:
    4 ^" I0 U7 A. E' w
    - F1 \/ v9 g: q  m  l7 K语法
    " x3 N  S7 H  p" ^  k$ W* m, [/ ?1 y( f+ {
    @Value("${配置文件中的key:默认值}")2 g/ Z/ J6 R* ]/ r+ F- ]
    @Value("${配置文件中的key}")/ i- t  P! Y+ ~. c
    如:% L! n# {( v( W4 c  T! _! j

    9 g# w9 X* I+ j- v6 h5 [@Value("${password:123}")6 y  R5 M8 t7 D! C1 U# v" s
    上面如果password不存在,将123作为值
    % Q: Y8 T: O7 D' L
    9 l, j5 d' _' J; J/ P@Value("${password}")
    ' D' _2 A$ k5 r/ P( u! s上面如果password不存在,值为${password}! s0 x( f9 t( m5 f6 l, {

    " k% X6 h& `8 }. g7 }, p# U; d假如配置文件如下1 D7 j6 S+ D8 n4 r
    / h3 w! V7 K- v7 g  [
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-89 b/ C; I7 i! @( e
    jdbc.username=javacode
    , y* B# r. B7 Ajdbc.password=javacode
    ! T) s1 q- ~4 c, [使用方式如下:
    * ^9 c: t. R+ d$ I
    3 d: g! N4 e6 ?7 b$ _@Value("${jdbc.url}")% c" f# H# _3 i6 }, _4 N5 g2 Z8 H
    private String url;
    . q8 m% C1 Q% Q" |. U4 k" y0 N0 {! X- L! o. ~- y2 n" i
    @Value("${jdbc.username}")/ M9 i# e+ b& `. V3 i1 M# ]
    private String username;
    1 L: {6 x# H1 X2 z' R5 g8 A6 }( Q. I. _6 \! d- [/ Y$ |7 ]9 E% g: x
    @Value("${jdbc.password}")' L) D7 Q  N% H9 @" ~6 O7 i
    private String password;
    1 E3 d" |1 {- `9 O4 k- r  Q+ r" j下面来看案例
    ' p6 g2 t, N8 R; |! u! v" [1 h9 P7 V9 y% [
    案例
    6 h( x5 n8 w* k: ^- E
    7 c4 }; A4 J' s6 Y- `来个配置文件db.properties
    * M; h7 C) f6 _! V& J* c8 i
    9 f( k& O/ t2 T3 R4 ~/ V2 U1 `jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    : w: k9 B9 y% ^jdbc.username=javacode
    2 J1 Q0 _1 S8 Gjdbc.password=javacode5 |, p+ u5 G) e0 Y
    来个配置类,使用@PropertySource引入上面的配置文件. d9 y  [7 l) z% N; J6 a) t1 H
    1 v" o7 T% L0 G
    package com.javacode2018.lesson002.demo18.test1;  H& N' R  g( [- q
    / p1 A1 |6 o# M
    import org.springframework.beans.factory.annotation.Configurable;9 l& W; N& H& a; ?8 U) z, j
    import org.springframework.context.annotation.ComponentScan;% E9 d* b* M; ~; T, J  }" v7 ]
    import org.springframework.context.annotation.PropertySource;
    9 ^6 q  k& I9 @
    9 a5 H" g1 W. U( K" p) z9 s1 S9 U$ G@Configurable
    4 }2 P0 T- f  m  |@ComponentScan9 K' }9 {9 N! r
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})) D/ v2 e0 T% K
    public class MainConfig1 {
    ( }# q* W: p3 H}" y- M2 X* E( h1 V
    来个类,使用@Value来使用配置文件中的信息
    1 u1 T  I# ]" Z" R/ ~( r$ w/ f5 N3 y1 G7 m( n& V5 ~/ }' T, e6 a  a
    package com.javacode2018.lesson002.demo18.test1;& i. S1 P9 r: d! L' O" P2 Z

    + g  w* Y0 r! l5 I5 kimport org.springframework.beans.factory.annotation.Value;
    4 V: N/ T/ l9 w- k# i- nimport org.springframework.stereotype.Component;
    , Y; b' p% K  w% ?/ P# H) J- w. b, L" _
    @Component- ~! q7 q/ Z9 P8 q/ g
    public class DbConfig {% T1 `/ `- h' B* F
    8 {$ H/ v" a5 ~  F+ N
        @Value("${jdbc.url}")) b% y6 X; P( ~2 c6 H
        private String url;) |7 t/ L5 ^% U+ v9 G1 U
    - \9 g, D9 H) U; y8 _  t, t
        @Value("${jdbc.username}")0 U; U# S1 W0 `9 P) p  q
        private String username;9 j1 L, D' X% H! W! j9 m7 o
    0 F9 Z& K0 U$ _
        @Value("${jdbc.password}")! w5 D5 n( t3 v+ v$ O3 H
        private String password;
    " Y# |9 e! F: _0 g" V9 m3 e( e+ o4 w, h$ ~) G; o/ ?
        public String getUrl() {3 N' _0 w' a# W* L: F
            return url;
    # a  Z( T6 b& w8 [5 T& _* d8 L    }
    ) q9 `: _3 M& @& _
    , _. i4 b8 ]  w; S    public void setUrl(String url) {
    % M; X$ b0 F1 ?4 G        this.url = url;
    6 r3 |1 _8 r# |! j1 [    }4 p4 \! E  M$ B
    # u  _8 R3 n( e3 ]6 D
        public String getUsername() {
    ( L5 b" Z8 m2 }5 J: N% u  K        return username;
      v" l+ L: m- i8 ?/ ?    }. `( ]* Q  u7 M6 E- ?3 D: m
    ) g/ Z: Z$ s8 ]- v& z3 ^& g7 f  j0 z
        public void setUsername(String username) {8 Y2 |5 q" A/ L* W/ j1 z! V& n
            this.username = username;4 J. o4 l0 {( Y/ M8 n
        }
    * e! _0 j2 e, R- E9 [. e+ M. t& q+ ^3 O) _# L& S' L+ N9 z
        public String getPassword() {
    1 [+ L2 e# Q1 ]* @+ f        return password;# {; [& u4 o) }+ H$ D! |+ l8 a9 h+ o2 ?
        }
    , U. V1 c+ I7 t7 Y( ?- D: U9 c$ K, d( \6 g1 K. b9 s
        public void setPassword(String password) {  _6 y6 N( h% e+ q
            this.password = password;
    ) n$ [& d& N% ~    }
    ! S! ~& d9 E% V7 n: A0 n" d3 }6 B8 V9 ]* T4 m3 b$ h1 Y
        @Override
    7 P& A7 E" X- a( I: \, x' v# @    public String toString() {: u0 T. {0 Y. R6 e7 j
            return "DbConfig{" +. W9 o0 ?/ L5 J$ i6 U6 M
                    "url='" + url + '\'' +$ J) _: |! Y* i  t& g$ ?' l
                    ", username='" + username + '\'' +6 c) P1 N% I, a. E
                    ", password='" + password + '\'' +$ P  d' O& ~) V% ]9 D# W
                    '}';
    . v3 P7 r8 K. b, p3 z% W, C    }; w  \: s, y/ Z- J
    }
    7 x  t$ n* c3 a: Z# Q上面重点在于注解@Value注解,注意@Value注解中的
    ; R8 h0 z; k  ?3 i2 `: C
    $ O6 F# q) x& k( q2 R7 A; `5 T来个测试用例
      G- W: {. V; y/ l  ^7 ~* O8 R9 Y+ N8 F  D4 U# ~3 G
    package com.javacode2018.lesson002.demo18;. r  F; G' p. G0 Q4 m
    + Z2 c* t! }% N7 s1 S
    import com.javacode2018.lesson002.demo18.test1.DbConfig;; @7 Z' M! ^7 g3 L1 B
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    - @8 @5 x( J( [/ C0 ^* gimport org.junit.Test;
    - [% x" D$ O0 v9 Z4 ?3 @' w- [import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    2 N9 K: ]2 d, n+ B6 ~
    # }4 p) J2 M# r. f' R+ K# Q( Kpublic class ValueTest {
    ! J" W. p. L( s: g  u
    " n* C* T3 |, |2 c; _6 B% U    @Test- [6 ?* C) {* l. m3 W
        public void test1() {
    - Z1 A8 L0 W; @1 i        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();+ d" A& t# i: {- G- S7 ]  V
            context.register(MainConfig1.class);; J5 m  g% n0 Q+ k+ {& J4 J' _7 L
            context.refresh();6 h( }$ j4 X( p
    * N, H1 Z5 e6 D. y, Q' k3 T
            DbConfig dbConfig = context.getBean(DbConfig.class);+ p4 x* N' B/ `. d5 Q  O3 T
            System.out.println(dbConfig);
    ' ]" w* [+ U/ a- ~: B/ F& M    }4 b- r& s0 S" W' D* @
    }2 R8 r+ `7 Q* ~" ?5 j
    运行输出2 R1 |' _. t, Y2 _
      L0 ?8 I9 _+ ^" i1 L
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}1 M) H! R6 a$ s, g& ~  b! r
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    7 R1 O2 g# q3 Z
    * t6 G. ]; p& `5 o@Value数据来源1 S  c9 G! Q$ a' r8 ^$ C
    8 Y6 ^2 p5 u" V# l) ~( J8 M
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。: q  R* p: q# R0 s
    . Y9 S' U% H4 m6 T6 i
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    . a' H2 z' u1 c0 |- V
    + H+ J. q) ?1 a% Sspring中有个类
    - _  f5 q1 p) e- G: ^1 M1 C0 f: b/ p8 q. G7 v
    org.springframework.core.env.PropertySource
    ! k8 N6 j$ L8 d. U6 o$ J" |可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息' w+ V4 ]$ F4 A

    + f0 r1 ?$ K  _内部有个方法:
    8 U% h9 K/ O* \0 m5 W- S
    8 g4 r6 _/ f/ L, N+ C$ \3 t" t  T' wpublic abstract Object getProperty(String name);
    ( P0 Q$ ?) `9 E; Q: P$ u% g通过name获取对应的配置信息。
    : W& P" C9 m2 [2 R* |3 G2 c' D  ?0 `$ f. S- V# o9 `
    系统有个比较重要的接口% A$ c8 c( f2 z5 o5 H8 F7 u3 s5 f
    , m* v  s* }# l2 I) F( P" i9 T
    org.springframework.core.env.Environment
    , S6 a5 A& d% p0 a% U; `0 i用来表示环境配置信息,这个接口有几个方法比较重要$ e- V% B5 z: l0 N3 y  m1 `1 G

    / F- ?1 q7 R9 M4 ?0 I' sString resolvePlaceholders(String text);
    - _1 N( Z: d8 q: @MutablePropertySources getPropertySources();" t7 I# D$ b( s9 W  F: ?
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    1 F! I& z: R, a  N  q7 ^. |3 Y5 L+ m! W7 [
    getPropertySources返回MutablePropertySources对象,来看一下这个类" X' M2 U7 P# P/ b& F
    . g: z- l* U- t; m+ ^
    public class MutablePropertySources implements PropertySources {$ Z+ \' K5 L7 Z; `: v

    ! h, j. w& x/ G! K' ?    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();9 k1 [: w, ^; m: |7 u; v, W
    6 ^5 M5 G7 r$ w& c3 a
    }
    , C+ T- V. G8 |: n  ~内部包含一个propertySourceList列表。5 p7 [- G+ Y- w+ p
    / V" k1 q. y* t2 h
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    5 a6 r. ^8 R' Q9 V
    & Y7 K& N3 N( ~. l  o2 D大家可以捋一下,最终解析@Value的过程:
    3 @# \4 z# l% I, t. @
    ' q9 f7 x. U* ~+ j, @8 }; I5 J1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    " _! t. q; v  l- o4 F2. Environment内部会访问MutablePropertySources来解析
    * F( ?" K3 B" r2 i- _3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值2 a$ s  _2 D- E7 Z- i
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。6 e0 D% z. z/ m# |) L7 R. o! u, j2 b
    : r3 c' Z5 _$ p2 }* n* a$ ]5 \. S! G1 j
    下面我们就按照这个思路来一个。& u! v7 {+ H  N( K& r

    * }1 s. {/ U6 `来个邮件配置信息类,内部使用@Value注入邮件配置信息. b  o. @" y4 k2 Z; C( t

    ! v! T' [- F. M" G* K  }9 a& v9 c5 zpackage com.javacode2018.lesson002.demo18.test2;6 Z0 c# @6 |# @4 D7 l4 L

    + s; S: C0 f7 R" I/ O- ~# Ximport org.springframework.beans.factory.annotation.Value;
    9 j- m! r% Y! z; t2 w1 bimport org.springframework.stereotype.Component;
    0 U: a& f, L3 k) |
    - \# M* I1 ]2 Q' E% h* h/**
      o2 r4 i! S. E  Y  I* w * 邮件配置信息
    & D6 Z0 m2 B5 ^+ G% c */
    & K) q7 z! \7 P/ [+ q, \@Component
    # D- w; f- h9 Z7 P" H2 a7 rpublic class MailConfig {$ U7 {, [& v3 `5 \$ m

    . f4 G2 N" k3 _5 C    @Value("${mail.host}")
    7 g. V9 @* g1 q, _4 e    private String host;
    & _8 j( o* b, c2 `$ H$ l
    9 G6 ]& a; D- h, V% j- P    @Value("${mail.username}")
    - e- K/ r% l- ?% h/ w    private String username;7 T2 y, @8 e5 \

    6 C' A" Z" ?6 O- b    @Value("${mail.password}")
    / z3 c( |+ ^, `+ ~    private String password;
    4 ~4 f$ a. v) P- y+ K$ _: A( j7 F: x: @% _3 M7 {8 G3 Y
        public String getHost() {
    % n+ _' s1 T. c- h& {. K5 S* U        return host;
    4 Y1 s! W+ P/ Z4 q5 M    }- L$ y- D; o2 ^8 G
    2 Z- _; Z( c! X, Q% q, W
        public void setHost(String host) {3 r$ J- `! F$ L
            this.host = host;# l8 y3 b1 L6 g( Q$ H* X
        }
    # g5 g2 h0 y8 j8 w8 B6 u  U  B7 E8 P  H- S1 m
        public String getUsername() {
    6 R( g% j% m) R8 e6 L# i% Q        return username;
    ! K3 U" j* K/ W# F9 W    }
    $ z$ _: y/ L  H$ X) A- }
    ; K! Y& y7 u& z- s    public void setUsername(String username) {& e3 r$ N- T. U. t
            this.username = username;, }0 f0 O; H4 a- @% V
        }
    0 Z+ v+ C  U$ y* w9 ?! Z, H' k' }+ ^4 ?
        public String getPassword() {" T  F3 M0 C. h* `
            return password;) h, L% C5 G* I
        }. [- M0 E, q. _; W

    - `2 j3 z3 H1 e% t    public void setPassword(String password) {
    + j  F- |% e  T5 ^, Q$ f# y        this.password = password;* l# Y% o- e2 t* o
        }4 f$ I! u( g4 J; E6 y
    / K# w( u$ g/ {( `7 ^
        @Override
    * }- d- M/ H9 Y! n, _0 [    public String toString() {" `1 S, @2 I/ D4 s( o& P5 t  ]
            return "MailConfig{" +
    7 z: V" M% e8 z7 N4 G                "host='" + host + '\'' +4 w' _* W+ K* {% o
                    ", username='" + username + '\'' +* }. h/ g- C' o! x
                    ", password='" + password + '\'' +( D5 c' `+ k5 ]* m0 }
                    '}';
    & {) {# B4 i7 |    }
    ( q" G) G" l- j* V, B}% V; c* _  |' ^8 b( c
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    ( ?" }. \2 v2 a4 N% F$ h
    ' L$ E; U6 u- ~" R% {8 Upackage com.javacode2018.lesson002.demo18.test2;4 d7 Y3 q  c- I' [( g" q) u

    ' j1 F/ d: m5 A5 c7 n5 {import java.util.HashMap;
    " L  ~: e+ c1 _" ~import java.util.Map;; M5 U* b: M. n! f% `; o3 z7 }
    - d3 _, W) E4 Z! s' _
    public class DbUtil {7 D$ j$ n# O5 i5 j1 F+ @! e
        /**
    4 f8 X0 f' t. N( |     * 模拟从db中获取邮件配置信息
    6 n  T0 i& e+ B' i" P- m     */ U! S( ^! A* l& C; E8 v( u2 Z3 D: t
         * @return
    + f6 u; t: {; {" g& w& V1 Z     */
    * a7 E' c! L( P# i4 \+ m    public static Map<String, Object> getMailInfoFromDb() {  \7 @7 H- u5 S5 w& M
            Map<String, Object> result = new HashMap<>();% m* B  H  m! N! {9 [- n5 z7 |
            result.put("mail.host", "smtp.qq.com");: J5 r, E) B& z) q
            result.put("mail.username", "路人");
    ; V2 o7 N" [2 t2 G: i        result.put("mail.password", "123");. s+ A! |, q6 k* X1 |) I- i
            return result;& f7 v/ k) ~- _% r, ?2 l; w  r
        }8 G. j  A' E5 E  s+ X, ]& W
    }/ Y( e2 ?! c# f5 l9 Y4 @/ y
    来个spring配置类) ], K, ~6 m6 g' l9 }6 v8 x: x

    % h+ B) _8 `) k2 {! h% zpackage com.javacode2018.lesson002.demo18.test2;/ {% n2 U5 ~$ a, f

    + Q$ X' D- O% iimport org.springframework.context.annotation.ComponentScan;
    $ z( ~: g" K) D2 \* M3 M7 A: H' P5 ?import org.springframework.context.annotation.Configuration;( ^  O9 ~& M  [8 d0 R! x
    , `4 }1 {' _' W; a# w) j
    @Configuration
      y$ w0 m9 S6 J- k" S@ComponentScan( t8 H! g7 ^* e7 {
    public class MainConfig2 {
    ) \  G6 g, E/ r9 ]' g4 q5 K* B4 I: b}- m) _6 A" O- J
    下面是重点代码
    . o% O8 B7 v' u( W1 D+ j+ S4 w1 J$ t. g
    @Test
    ' @) k3 j# W/ I# ^public void test2() {, x. |) v7 `* o8 W5 n) I8 n. C$ t+ x
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();+ b6 s5 C/ i$ W$ k

    + g& Q$ h# `& \8 R7 r    /*下面这段是关键 start*/
    , T9 a# @  }% W( U7 ~    //模拟从db中获取配置信息5 n0 _+ N. e# q
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    3 p  t+ v3 ?1 _) M, H. K    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    & l3 I- X8 L) [0 \    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    2 H, G- o8 e" T# ]    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    & S$ r0 K. D1 ]8 S. |9 r    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);7 T: q) K& ]7 k
        /*上面这段是关键 end*/( L9 J% f0 B. O  S8 ^; d! R
    $ Y8 a, n2 ^- Q+ k- x
        context.register(MainConfig2.class);5 S8 o+ w- K' S- B; @9 L
        context.refresh();& u. f. ]6 ~: c/ A  w4 F5 ]0 q
        MailConfig mailConfig = context.getBean(MailConfig.class);
    % H8 `% @! n4 V    System.out.println(mailConfig);
    2 t& C1 G" ^2 B4 v}
    $ a  R5 E# e! j1 v( l/ `( e注释比较详细,就不详细解释了。; v, x$ a4 B4 Y5 Y2 N; m2 c

    $ |; b% Q" v. [# J直接运行,看效果. j' m$ a) c$ T* i2 q6 g4 Z
    8 A. x! b2 i4 j. T3 t) k1 t; T- Y$ U
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    1 v3 _! T/ Q7 S% L1 X2 u有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    5 }5 D) g! Y+ I- {& y* F" R  Z) P7 m: K3 h9 {0 A( L
    上面重点是下面这段代码,大家需要理解
    5 @# p" b" w" a
    * p! w5 H% Q. u" m/*下面这段是关键 start*/! X- B) T# L+ I5 Y4 n4 ^3 o% J: B
    //模拟从db中获取配置信息" `8 ^$ ?& \( b7 v
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    , W$ {6 @' L0 u# B7 n2 [' C//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    8 M+ f( D) v0 oMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    " `" |' L8 k6 @1 D) H//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    1 `2 p2 t: q% K+ l% n2 icontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    + G; }& @, V2 l* n$ S  {6 h/*上面这段是关键 end*/
    , B0 D( j5 X  p+ D咱们继续看下一个问题7 \1 i+ }% O- e0 [  d: ^

    3 R( i% n3 @. W如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    ! b9 k! S; h' O4 a$ `3 P& n0 G2 Z4 s$ F3 y, t' A* d
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    : u! a- V- }( H, a/ C- ]; K7 o! j6 S. A
    实现@Value动态刷新
    + c* e# K; H9 q  k, y2 x' j& T8 m
    先了解一个知识点
    5 _+ u5 M2 u1 T" ^4 e* c% V) g8 K7 C, x/ ^
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。9 D! ?, K* J% E; [

    6 O# V4 K# e4 z0 H7 c. w7 ?( A这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
      \7 x$ v* N9 J" c7 W6 x9 _# ^1 t
    8 t# q& t2 T5 p" f; abean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:  U1 a9 C) D5 l+ c& t: W
    2 [( T" m# G" ]2 @2 K, y9 ?5 @& L
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;# a  o9 |( t0 M
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中' f- d0 h6 D6 G

      u1 K# P3 Q# q- b4 q/ Y3 A6 wpublic enum ScopedProxyMode {; m& }4 V5 v8 _' Z& n
        DEFAULT,9 a) T0 D2 T: i! u' Q
        NO," w* Y" j% J8 ]5 o
        INTERFACES,
    : t6 W$ Y# `4 [  ?    TARGET_CLASS;
    0 l8 M5 J6 P# L9 G" G}
    - M% P* n$ u' ^前面3个,不讲了,直接讲最后一个值是干什么的。/ w& c: J3 H6 p5 O! h
    8 X& i' Z& C% ?9 _- i" m3 Y
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。+ Q- M: y8 ]0 }2 l# Y8 W

    & `; S! o0 B" z) \理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。6 a9 u; w- L! V2 ~
    & Q/ L, X2 V+ B/ l# w9 n
    自定义一个bean作用域的注解8 `; G! s5 l; s2 E

    ! a7 I4 z6 L8 b( S+ r& Ypackage com.javacode2018.lesson002.demo18.test3;/ [- G5 Z/ G/ v; d& F& i

    " ^4 s; M6 w  W0 D- M5 W% _1 t  p7 himport org.springframework.context.annotation.Scope;
    1 T8 ^  u/ H' i0 bimport org.springframework.context.annotation.ScopedProxyMode;5 m. `1 {! m# q' m" H5 O& P

    - v' t/ |" J/ n0 {import java.lang.annotation.*;
    : J  g8 U; p: r) O
    " O8 [4 }" o; [' a) L@Target({ElementType.TYPE, ElementType.METHOD})
    - p6 r. j0 x9 S' L- h8 a( b" r@Retention(RetentionPolicy.RUNTIME)1 ?& X  d9 c6 d$ d8 q1 H
    @Documented
    ! p0 K% i- W2 C@Scope(BeanMyScope.SCOPE_MY) //@1( a: f' I: t! `" {5 \
    public @interface MyScope {
    3 I$ ?8 i. B+ @' s8 G( d    /**  h- [& x8 V/ ^' m% t4 B7 |
         * @see Scope#proxyMode()
    * @! k- g/ M1 J  T  c     */7 w5 j# C+ m7 O8 F9 B8 ^% V
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2  W0 L# j2 \6 ]6 E9 P; E
    }
    . }$ Z6 N% `" j. E5 f@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    0 m+ T' v1 w4 ~, m7 n2 O
    / E' @+ l" a7 g/ `7 T@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    3 e! S  m" Y8 P- ]4 M5 f3 C% s1 U, H- \- v5 e6 I
    @MyScope注解对应的Scope实现如下
    9 H1 }. t2 L9 z4 |8 |) v2 B) [  C2 \6 y, f2 E, D
    package com.javacode2018.lesson002.demo18.test3;* A; f6 G6 `0 d4 j3 x) ]
    / g5 ]. ~9 P) @& c3 K
    import org.springframework.beans.factory.ObjectFactory;
    4 o9 D) x* k5 Z- o) Z/ T6 D" qimport org.springframework.beans.factory.config.Scope;4 {2 o) ^6 |$ Z7 U% o* H( i
    import org.springframework.lang.Nullable;
    ! ?: M/ I. ~# Y& M* c( }
    0 K. F" x) \* x; j6 l: x/**  O0 f% ]8 N+ f- Y& |# p
    * @see MyScope 作用域的实现
    : {) I% K$ N# j) C0 m3 p3 z& j */% H7 w- D. J7 e1 X2 t
    public class BeanMyScope implements Scope {
    " c: [3 u$ |6 ^6 R" A5 l$ b+ u" l, o! X0 p2 {! @3 A- `# ?" _
        public static final String SCOPE_MY = "my"; //@1
      t% b- L( y8 y" K& B0 r1 i
    # C, h1 ]2 {3 F9 U* D" o    @Override/ L' N% s; D  g1 @4 n2 C
        public Object get(String name, ObjectFactory<?> objectFactory) { 3 Z& M; B  ~# [
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    * U. X+ |+ [1 W) d) q1 F$ K1 \        return objectFactory.getObject(); //@3
    2 z& g& I( \4 X( {    }# C3 W) s) p9 |
    7 B) T; P( i$ J+ J8 B
        @Nullable1 v4 v8 D; C- ^! f2 E
        @Override$ G% b6 O$ f. f" j
        public Object remove(String name) {% k% e/ J) O! V
            return null;3 y: C4 {  D2 U! m7 O6 f: |1 Z1 v
        }4 H) L; x( G* H" C
      m9 U' F8 N( p9 b' a4 o4 P
        @Override
    ! b2 w! k  N& \) N    public void registerDestructionCallback(String name, Runnable callback) {
    9 Q1 Q$ x) O; w8 w
    ! S* g" u& s4 O    }! n" H, v7 g5 y% C3 f

    + j5 T0 W6 |, f    @Nullable
    5 Q% U8 s- `7 s; p    @Override' ~+ w3 X7 M" J7 A! M4 I6 n9 l* v
        public Object resolveContextualObject(String key) {1 x/ ^4 Y: U1 i1 I8 T
            return null;+ L# {6 s2 N( C# A
        }6 }* I5 i# o% D
    1 i' T- B" L6 c8 k; P! N" R
        @Nullable
    - L: _$ w4 r" ?2 l  l    @Override
    + O! n  A. Q6 @" V    public String getConversationId() {7 M# h, W1 u5 d) o& v
            return null;, h8 w) f& w  F; S# ]! J% L, N( k
        }
    5 @+ n% P$ R# {# N# v}
    4 e% b/ i( h! D# Y! B@1:定义了一个常量,作为作用域的值) p8 `( Y7 \/ R1 N" g
    3 V7 C9 O- p5 H  }( B. ~
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果7 T+ A5 T2 [& v

    0 P# u) H! M5 N; \* [1 I" |@3:通过objectFactory.getObject()获取bean实例返回。6 z$ X6 z, D& S4 M

    9 J* B& a7 E; E5 e/ ~8 t7 o下面来创建个类,作用域为上面自定义的作用域4 b5 @1 ^4 f$ S# w# S

    / ~7 @+ A/ \/ [% Z1 C1 t0 ?/ O  I2 \package com.javacode2018.lesson002.demo18.test3;
    6 ^/ c! ]8 X- y/ _& o% P$ b" \- D: _* O$ F& H
    import org.springframework.stereotype.Component;
    : K7 c9 D  f& H) r7 l  W. B  ]
    9 N, v0 u) a$ D/ n* [( zimport java.util.UUID;
    ( u4 X. z6 b" x
    8 Q, q9 ~1 M8 z) S9 @@Component
    # l' z6 |. S! {. C! L. O@MyScope //@1
    & o3 d) r" K/ H' D+ l  mpublic class User {+ ]- ~8 r3 n9 T8 {

    5 E! ~: C# Y+ m+ D) w6 `0 A    private String username;
    % A0 e) S$ I9 o8 t1 w' i* y4 f9 K
    8 T, ]. \) _( U6 n    public User() {
    " u- p; H# N5 J( F8 S- L; [2 K( ^4 O0 x        System.out.println("---------创建User对象" + this); //@2" i  c6 d& e* L/ ^) Z  S7 O
            this.username = UUID.randomUUID().toString(); //@3
    8 g" O. d" h* m4 \7 k) Z    }
    8 \* [+ v; _. X( K* @8 ?6 ]
    / n0 M5 m+ Y8 R3 k5 q3 M' v    public String getUsername() {# a/ l$ s8 ~: S/ X4 s
            return username;
    # c7 K; z2 R) o8 B    }
    ) y0 Y: Z, `0 }; @  P  D) J6 ~+ a  {5 S
        public void setUsername(String username) {
    ( D6 Z7 r& N& A        this.username = username;
    3 }+ ~; S% F8 S8 v* X+ |0 ?- `    }7 ~0 c, Y* g# D4 P$ P

    5 ?' K+ H, A8 r}3 c; m2 _, k# x, h! a; `1 `
    @1:使用了自定义的作用域@MyScope6 J: K' W4 L" d' `
    + D6 [, B% ]' M0 e
    @2:构造函数中输出一行日志1 q+ e) Y0 v& _+ Y6 g5 Z
    8 o) y; @) z' ^: P% k
    @3:给username赋值,通过uuid随机生成了一个
    $ _7 F1 a* X* j& E4 I9 q5 d7 L
    7 ]1 H, L, }: h, A5 X8 k来个spring配置类,加载上面@Compontent标注的组件
    : ^% p6 x' Q/ e7 w
    3 p' l, l6 m/ X# q( D+ vpackage com.javacode2018.lesson002.demo18.test3;
    , U/ ~+ e' C9 J" I. _
    0 A: G6 i1 L( I; qimport org.springframework.context.annotation.ComponentScan;' b7 `9 ^: L' U+ i/ L, }7 F# a! P$ m
    import org.springframework.context.annotation.Configuration;
    & F& z, I. V" Z
    + p. Q7 X, Y4 a# e@ComponentScan9 O* g3 v5 u) P; R) j, D
    @Configuration
    $ O1 O. n" a! }* m6 E* N* i  wpublic class MainConfig3 {: @& {! O9 ]- _% p4 p" S# V+ I% L8 A
    }5 l! o# r/ L) {, }
    下面重点来了,测试用例  I9 d; Z. ?! C

    8 y0 _" V( d$ O@Test. d  R# F8 w3 q7 ]6 L  l. E
    public void test3() throws InterruptedException {
    6 I# u) o! x& O5 J+ S    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. l  k3 Y0 A7 G9 t. H
        //将自定义作用域注册到spring容器中
    . \8 f" R+ I  U7 W2 ?    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    6 ]0 j4 E2 z. O8 |& O& c    context.register(MainConfig3.class);. U# C" ?5 a: O3 q: W
        context.refresh();- G1 C6 i. W% z& j" h2 Q- Y
    / W2 S# a: ?, X# p
        System.out.println("从容器中获取User对象");
    5 e& [' A" h. R1 l# e$ d6 J  p. [& F    User user = context.getBean(User.class); //@2
    $ C# {7 I0 n' K  \7 C    System.out.println("user对象的class为:" + user.getClass()); //@3- z' O- i$ h3 p5 b( E8 W, p+ V$ D

    / V" A3 x; y% A. ?" J4 F$ Q    System.out.println("多次调用user的getUsername感受一下效果\n");
    ( U: D  f$ H- v% I    for (int i = 1; i <= 3; i++) {, x. y3 w+ f7 r! B
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));  }7 `2 h1 }: `+ {
            System.out.println(user.getUsername());
    $ @6 Y7 m4 F+ D; j4 w        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    6 U3 X- Y  `! l8 b. u    }. r/ ?% N5 e! u! r# @3 Z
    }9 X3 |+ i/ ]  w# b* x$ j
    @1:将自定义作用域注册到spring容器中
    # ]2 E( ~7 ^. }: m  m! @4 ^* H% z. {, T! y. @
    @2:从容器中获取User对应的bean% k. T2 Y$ S& G/ }4 J

    8 m* J4 g: y& F- a* [  D8 m' M) Z0 c@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ! k- r) b0 i6 U* }  j/ |% x8 j6 u" q* ^
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    ( P  _- V5 [, D4 w0 p
    0 |- j/ ^4 \! F9 B: b6 N见证奇迹的时候到了,运行输出! Y, f- L3 V5 @
    ( n" j7 U, ]8 [' c4 f: r! q, Y. o
    从容器中获取User对象
    - a, c4 @( h3 Q! Z3 [2 _# q3 H2 Buser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331270 D: i$ ?  _; u4 H" w2 F
    多次调用user的getUsername感受一下效果
    " G2 N0 b% _2 w* T+ Q+ N/ U1 f. u, E3 E$ D  N' g) y
    ********
    ; [1 q" l: X) V0 u7 k" G. T第1次开始调用getUsername
    & V. |) A" a8 H9 iBeanMyScope >>>>>>>>> get:scopedTarget.user2 R; K0 Q# C1 O
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    ) c8 h4 y1 n7 g1 A% ]* K* h5 b! Y7 h7 ?7b41aa80-7569-4072-9d40-ec9bfb92f438
    # z7 X0 i: O% u. [% l: C第1次调用getUsername结束# w$ F7 I. ]& h) F6 ^7 [
    ********3 I! i8 O. K0 w- N

    % Y' A3 Q6 v4 w% b- _" ?********* }7 x- M1 X- x! @) p# [2 R$ r0 ^
    第2次开始调用getUsername2 c4 C- Q4 {. Y8 n
    BeanMyScope >>>>>>>>> get:scopedTarget.user/ l0 r1 Z6 z/ O) {
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b2 }) _  ]) a7 n& ~
    01d67154-95f6-44bb-93ab-05a34abdf51f* B( B- i( r/ `8 }/ y
    第2次调用getUsername结束" z) d% u' @/ `2 {
    ********
    8 j8 t$ C& e! o3 r9 d, U3 c7 Q+ P1 o  |6 C1 M; \  j5 d' o0 D& Q
    ********' x: j' ?# b" ^4 _: `
    第3次开始调用getUsername
    ' A3 Q* f' c/ R: C( G( kBeanMyScope >>>>>>>>> get:scopedTarget.user
    + H: y4 y, D; V; A- t! {6 U4 V---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    # u4 G# |. N: u5 g: [7 O: ]76d0e86f-8331-4303-aac7-4acce0b258b8
    : d2 y9 g0 A( N- E0 {第3次调用getUsername结束' \1 g  l7 {, W  F; u
    ********
    / p% Z( h0 `) E% \  d) z9 S从输出的前2行可以看出:8 K& \; @0 t7 R' p+ M9 Y

    7 W( W% S" z- S& K0 S调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象8 Y9 B. ]! `1 C) x

    " p$ g. a0 g; Z! G8 y: o第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。4 r7 U9 m3 f* e3 K6 W6 x$ r
    " y$ E, A  d+ b1 W4 T9 C8 d
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。' G7 e7 t, E- `& Y& \' j1 C" @

    6 z( D0 W- w) b' Q9 x" i5 `通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。0 y* f- }# ?; n9 d1 f

    , i8 P* B  t! T! }* O动态刷新@Value具体实现, K( W. J, ], b- E) p1 c. T7 \, m/ _
    * h4 i  F4 z8 Z# ^: R* h( q
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。1 [& W) c: X8 M; m
    % m9 ?) Z/ p! L
    先来自定义一个Scope:RefreshScope. ?; K  d& M, o9 ~4 |7 Q
    7 i7 P# q/ t% n6 u% P
    package com.javacode2018.lesson002.demo18.test4;
    $ X8 }8 i4 y6 o2 n6 U  Y
    8 N, k- X2 D! l4 M: t! H& C( _import org.springframework.context.annotation.Scope;
    - y6 Q, g( r/ c/ m. E+ e- D% T# iimport org.springframework.context.annotation.ScopedProxyMode;
    0 r* c; Y' Y5 ]
    ) ]  ^( Z" d; h6 d0 L+ pimport java.lang.annotation.*;. y; C, y6 {, _4 c2 o# m7 R3 T
    1 i5 ]3 f7 D& ]$ K+ V1 [
    @Target({ElementType.TYPE, ElementType.METHOD})
    . g& t" {( x( p: {@Retention(RetentionPolicy.RUNTIME)  _% l4 E7 ~2 I
    @Scope(BeanRefreshScope.SCOPE_REFRESH)+ b9 H) F4 q( }# b8 c" E
    @Documented8 \, r* [# W- B/ Q' U0 [# G+ [
    public @interface RefreshScope {
    % [4 }, |! w: q) A$ M! _    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ! i" ^0 D# b( Z+ ?- w, N}( \" I3 c% `5 A! [$ I
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置9 U0 `1 \9 l% i

    1 l+ F* G1 c; a( N( _; R5 ]@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS+ ^4 n0 G- G9 i3 I4 y

    ) _$ k- x' d. X; [! v- l* @6 J这个自定义Scope对应的解析类
    ! F) Y/ J& P! J8 D. @6 f  C; B6 D/ f
    5 E. I+ Z7 }0 ]8 K下面类中有几个无关的方法去掉了,可以忽略
    + A% s$ X: F7 A% X% e; Y+ F0 C
    4 C; ?/ J0 n9 I7 Z8 l7 @4 w& g4 I9 Bpackage com.javacode2018.lesson002.demo18.test4;9 {6 W/ d5 E  s% |! O+ H( H

    $ n: }' e* O7 k4 q* G
    : K5 C* B3 s- }% M( q! A1 dimport org.springframework.beans.factory.ObjectFactory;! \* C' N* |" ^- f1 _" g3 J
    import org.springframework.beans.factory.config.Scope;  g! [# M# N8 I
    import org.springframework.lang.Nullable;+ R& v/ b) ?# J* a& G% l

    & P/ k# I5 B. s) Dimport java.util.concurrent.ConcurrentHashMap;
    * x; p5 s( }$ ~3 @: B0 W9 ~/ U
    public class BeanRefreshScope implements Scope {
      A: a, G/ x" y" t/ S! B4 w/ X3 [+ ]: ?/ R/ L: b% q
        public static final String SCOPE_REFRESH = "refresh";
    , G( }7 V5 r0 f& R* q& k7 l, |( U3 _7 L- M) f6 p2 Y8 I  ?
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();/ T- M4 K1 ~2 Q* }; S/ n; M" D
    ' N% s1 v. W5 L1 X* h
        //来个map用来缓存bean
    ' |6 w" T$ x! B) |) Q2 c5 {    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1& B+ x" N" _/ W% V  V# M7 {, ]

    , {2 ^( Y9 \( I( @) L7 Z- h    private BeanRefreshScope() {7 d0 L& X1 N; B  W; m
        }! O/ D  `7 p/ Y, x6 b+ v( N- W

    / G$ t: [$ b/ G    public static BeanRefreshScope getInstance() {) T; y1 b7 {8 C  |" k: D
            return INSTANCE;/ w# t* {. s8 p- z6 d
        }
    3 I* a/ s/ f- B' N! W! A: }/ y7 V. a  S5 e
        /**
    ' b' @! e2 i/ @1 K6 a4 i     * 清理当前. W: G8 a' u! G
         */
    * R+ ~( `# E- u4 g, J8 Y# s1 q    public static void clean() {
    # Y' `+ o( _+ ]1 ~" B        INSTANCE.beanMap.clear();0 k$ J8 z3 M" U
        }6 R8 X- a; F4 r: K* C# r9 `
    0 U4 n" Y7 e! y7 K& r) u
        @Override
    ! o9 v+ d  z- n3 I- o+ x    public Object get(String name, ObjectFactory<?> objectFactory) {  X- C+ H/ R8 ?  p
            Object bean = beanMap.get(name);
    . Z% F3 i+ U; a) H4 e% v9 k8 w        if (bean == null) {
    9 t6 ~# P! ^7 g) F            bean = objectFactory.getObject();& u4 S, P" h( U+ p$ b5 ]7 n
                beanMap.put(name, bean);$ I  h2 w# J9 p; g0 A7 P5 S* a5 A
            }4 c. s6 m/ z; J! }- ^& l+ a9 O
            return bean;  m* i8 h0 G# V9 u" y
        }
    $ x  T4 |, P( J; j- I6 ~' [( l$ L: u8 F; ^# r! O
    }
    + x. o0 M) r5 L) m3 C: ]上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中+ |$ e; Y) n$ o; j1 P" w
    % B# c( }/ z4 p+ Q
    上面的clean方法用来清理beanMap中当前已缓存的所有bean$ ]% I; M8 C' N4 R! \  ]+ T

    9 m5 @, W" G5 p  x2 P0 Z# r8 _& F来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    4 H9 W: i9 o& |  X8 H. w: H& _* \. b* Y5 g* g! O, [' Z- ~
    package com.javacode2018.lesson002.demo18.test4;
    $ V0 \: o) B; u6 I2 O
    " V# U8 z# a) e2 P  d( }import org.springframework.beans.factory.annotation.Value;
    + H8 g+ k4 H0 @+ k4 u! [* Simport org.springframework.stereotype.Component;# y7 m8 Z# F8 b( K" p  v
    % [7 H1 O" b* L
    /**
    % g, \0 o0 W- L. w * 邮件配置信息4 N9 r; l$ S4 K2 u* h5 P+ K
    */- }% V+ n( ~. e! l5 d! G& X1 ]
    @Component
    0 {2 ?8 N+ L2 i4 d4 z@RefreshScope //@14 L; n; {. I! U
    public class MailConfig {" ?7 _/ ^: T) s

    0 Q* a5 `* ~- ~0 G( \1 o3 \    @Value("${mail.username}") //@2
    5 N/ R5 v, w. ^" N) U7 D: n. d    private String username;- l9 T. ~! P( @" R2 B: D2 S9 ~

    2 S  ^- X2 f0 t3 q- Z    public String getUsername() {1 O: ^& ]( g9 X( b" x1 \
            return username;" Z: Z# e5 ^0 ~" S( F3 n
        }2 w) U$ D& ]! S2 o1 U
    $ A$ \* X! `, Y' T" B2 C' x. l8 R- x9 U
        public void setUsername(String username) {' v9 _/ C0 ^- F% c8 U# H& a" Y" I
            this.username = username;# j! `7 n3 J  I: M  ?& e
        }
      _' @  R/ `$ l" D& Z  i$ E: _! Y4 `
        @Override
    # t% E$ _( w* z+ z9 l    public String toString() {
    # h6 F) t$ c+ I1 D        return "MailConfig{" +
    ! T* I+ L7 Y4 b0 t2 u/ B                "username='" + username + '\'' +
    ' V/ B( Y7 G/ D/ M, a& a                '}';
    ' ]& v  d' z% c* e* F2 P    }
    ; p7 E3 A4 O- H# l; O% T}
    , c& O# T1 t1 }7 J( N6 k0 T% o@1:使用了自定义的作用域@RefreshScope' C* x8 _7 V8 v8 s5 ^: R$ P4 o

    2 E" [7 J+ w* T* \. ~@2:通过@Value注入mail.username对一个的值
      o/ G" Q$ E+ b& {7 r7 |; N) V" \) C. P( R6 Y- ~* @  V$ T
    重写了toString方法,一会测试时候可以看效果。5 p* Z! Z" a! P! P5 K

    & x. A( h: \, ^! E  q8 G- B再来个普通的bean,内部会注入MailConfig% y% h7 g4 W7 u4 _- D, ?% Z
    * a- d+ A  U0 `2 w+ x
    package com.javacode2018.lesson002.demo18.test4;
    1 _( M2 R- p" R1 u7 y! d1 y9 ]& Y% j' [( q
    import org.springframework.beans.factory.annotation.Autowired;
    * P6 @7 c' c9 ]. {. Cimport org.springframework.stereotype.Component;- P: y) \% @6 ~: {: m
    " \2 J7 I$ A# n) [/ V( {
    @Component+ N+ M. O3 q, w' d
    public class MailService {3 ?( M6 t+ G2 O5 G8 X+ C9 }  J) r
        @Autowired; y$ ~" L; p$ n& F; @! h- |3 F
        private MailConfig mailConfig;  r  J, X+ ?6 q" j8 p
    ) R) G# h7 k& q' ?
        @Override* A1 w8 t1 D/ E6 F5 g" X' y1 }
        public String toString() {
    1 X( X1 {1 ]! g8 r$ A        return "MailService{" +
    + a3 _* ^. k8 r& \! |5 }, z# i                "mailConfig=" + mailConfig +0 Q$ q4 u: k: ?! o! G+ A
                    '}';
    $ Y2 O+ K6 t% Q- I    }
    % ?0 p( z7 W# V8 L, s}0 Z  e( ~5 S% _' G' r4 S# U8 b
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    * z7 g" x2 @% I1 p1 ~7 P$ g2 L# V# w
    3 I8 Z% p' s( F  J6 s* q, N来个类,用来从db中获取邮件配置信息
    ! N/ ?, d5 v* r1 a# O1 M( m  F4 [, L
    package com.javacode2018.lesson002.demo18.test4;& z; m3 V6 s1 Y2 [' l
    5 \& C& z" _& f7 B$ o
    import java.util.HashMap;- h7 I. j2 T7 Y  V1 D, A. S
    import java.util.Map;0 Y! D4 c/ b, Y8 F0 B% A
    import java.util.UUID;  m3 A6 w( q6 b! c! P

    " h" g8 T5 d- O' y' X1 ~public class DbUtil {
    " h8 r2 U! X, K2 c* z    /**
    ; q& o' Y6 k2 B; ^' O/ C     * 模拟从db中获取邮件配置信息  c0 N/ k  l8 o
         *
    7 N" u; O' A- J) g% g9 l7 B     * @return
    2 u& K3 K5 Q! s2 F6 R# B. K     */
    5 z; D# P* ~$ W5 u& q    public static Map<String, Object> getMailInfoFromDb() {
    ) o1 q5 ~6 k2 b+ {4 J  _  g8 k0 Q9 Y        Map<String, Object> result = new HashMap<>();+ X, r9 U+ L% E; C& J  x+ b
            result.put("mail.username", UUID.randomUUID().toString());# x1 U" b6 y7 O+ A9 v$ k4 U: `- B
            return result;
    * {, w1 h2 t' u    }
    ) S# _, m! X7 L$ a7 X3 y  b+ ]0 d$ s}# I+ M6 U# n- g5 h# ?/ }' q& h
    来个spring配置类,扫描加载上面的组件- Z$ n8 _" X7 t2 B" Y7 m! O4 k3 U4 P

    : F" r) V5 |: ppackage com.javacode2018.lesson002.demo18.test4;
    # g' M- K( h' E1 Y9 W+ @+ u+ H5 m0 ^1 }; f% w
    import org.springframework.context.annotation.ComponentScan;
    # S; P) F- H$ @( ]! Y4 eimport org.springframework.context.annotation.Configuration;
    ; v9 u* F! S- Q3 L
    8 D  C! D) ~# }1 M@Configuration
    6 p4 K' {2 Z* i  b8 |  E@ComponentScan
    9 j: V% ?$ ?  L1 epublic class MainConfig4 {" I4 V/ P! x0 t3 ?! K: p1 q
    }7 M* j1 m# }* v; S: s% v+ m8 o
    来个工具类+ ]6 @1 P6 h! x4 _! W
    % P, I: n- S4 i4 }# l- s( _% Z. r
    内部有2个方法,如下:; x% _  Z7 f# {3 F8 ~( |

    : u3 D. m& P# S6 l, zpackage com.javacode2018.lesson002.demo18.test4;- J% f) V+ P, D7 r/ g& a# r9 @

    , {$ s+ c) [6 t2 s8 [9 k8 D, M% k/ Z4 Cimport org.springframework.context.support.AbstractApplicationContext;
    5 ?% n. _+ l6 D* l4 D! ?5 s" rimport org.springframework.core.env.MapPropertySource;
    - J$ x/ t/ a, ~! {- w9 T4 [  Z
    ' q5 h& C. p7 U  \5 E$ s8 N- _3 d& wimport java.util.Map;
    0 q) H3 C7 Y9 s' p: J, s" {# s% E% \2 m% M/ |7 b% F, k+ G& n4 ~
    public class RefreshConfigUtil {
    0 B/ P$ C6 k  O9 l    /**9 E; u5 V, t: [' L
         * 模拟改变数据库中都配置信息% a, Y, |) _- E$ I- k' ]
         */
    7 C- `4 C2 T  C2 L    public static void updateDbConfig(AbstractApplicationContext context) {: ?/ a% P5 s3 y4 d. I
            //更新context中的mailPropertySource配置信息
    $ B% \( U3 Y/ `        refreshMailPropertySource(context);5 R6 v+ B* Z' M% I# J# Q

    / n! F* B% K# \# V1 F& M! g        //清空BeanRefreshScope中所有bean的缓存
    - b) t" j# F' s! i        BeanRefreshScope.getInstance().clean();  A5 A& h4 D/ C% u$ k& k
        }% N8 A# _5 U# x' x7 m% d

    7 h1 J  M, ?' h8 c( }9 Y    public static void refreshMailPropertySource(AbstractApplicationContext context) {6 y0 y/ R. p0 @$ f% f
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();. |5 _( C; {) |, }! L9 ^$ T3 H
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    * `, w* P% i/ G) ^        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);8 H6 B( s9 X" n1 m2 T5 E
            context.getEnvironment().getPropertySources().addFirst(mailPropertySource);  U5 O2 l; x' V& h
        }
    4 ~+ ~  `2 F' q
    ! T% J  M# U/ ]/ E}
    5 n- r. n8 v* o( T% BupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息$ q* }4 a/ Z: D: P2 ?
    * }, h9 Z7 v  n  P
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。6 P. s) f% {' j3 R7 n8 t- p/ o
    1 |: H  H! j' O: ?6 I: b
    来个测试用例
    6 U5 C$ g2 C  }) g9 Q  U
    $ x. E" U6 ?! o# c7 @@Test0 I* ~4 |- b6 @+ _! W* E* e
    public void test4() throws InterruptedException {
    3 j4 w; x1 j) E; b* {    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    0 \7 ?  P5 g; _6 t. V/ w' D4 E3 H    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());5 @4 \) h+ S' u
        context.register(MainConfig4.class);1 S- a  _$ q2 |, u3 d; Q
        //刷新mail的配置到Environment# L, x( t5 G) ^
        RefreshConfigUtil.refreshMailPropertySource(context);4 H- q3 m. e6 G: n
        context.refresh();
    - Z1 {/ n$ o- k& l+ m0 Y! I# a* D- E
    5 B$ }$ R, O% i* b. y/ z& ]    MailService mailService = context.getBean(MailService.class);4 Z2 O: Q3 A  V# o( Z1 z
        System.out.println("配置未更新的情况下,输出3次");
    % n; h" F4 I% O" `    for (int i = 0; i < 3; i++) { //@1: z1 o2 ^  i8 m" D" Z
            System.out.println(mailService);2 m' j+ ^/ @+ \2 l) M1 F
            TimeUnit.MILLISECONDS.sleep(200);! J5 v9 \7 A; y! J8 w( x* s6 u
        }
    5 `/ o+ D1 m! K5 {! d& L
    7 S. O/ A  L8 N' [7 `4 s5 d    System.out.println("模拟3次更新配置效果");
    ) j; d9 k* A) P& b+ s$ ^    for (int i = 0; i < 3; i++) { //@2
    % G% c4 z, z6 ^3 f5 O        RefreshConfigUtil.updateDbConfig(context); //@3" M  i4 }3 b$ X2 e7 u
            System.out.println(mailService);
    9 I* d% u$ c" z- V$ ~! |5 |        TimeUnit.MILLISECONDS.sleep(200);8 ~, b- W) f# h3 d
        }
    . T; g. }& `' p5 r; p4 g}
    % H  [( ^4 K! ~' g/ K- C5 E@1:循环3次,输出mailService的信息) z  }! R7 r$ W
    7 E8 b* ~# t! k" ~7 Y
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息; n. ?' [: n; ^  G$ @1 X0 {+ u1 R

    3 h6 {/ d; Y% _5 y* R" \见证奇迹的时刻,来看效果
    " N+ X$ {0 |' _. @3 \: I% P8 Q+ g2 [! X
    配置未更新的情况下,输出3次
    . y# k' J( p  @! W/ vMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}, W) p, ]& t4 _" |7 x2 E; h
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}/ C3 }7 `+ q5 _0 H/ ?# L8 ?
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    : v% O8 g0 n+ _" C; r- I3 m  _8 {模拟3次更新配置效果
    ! M4 v6 h+ L4 G, \% w% oMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}& D  ?: w. T; G9 ]7 l& |0 P
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}% Y0 d$ _; ~' H
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    % n+ K( R/ \2 z; u4 o: L1 o上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    # Q# }3 V8 F- C* }: ^) H$ H4 R6 X0 C9 F. x8 s' c
    小结
    7 Y9 D2 a/ [/ H0 P
    6 J8 j( `6 w: I! H( D动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。& T2 b3 ?: y/ l% Q) L( j
    . j" l* ?6 _) r7 u, c! C
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。$ [! p$ i8 l! I! s& v
    / h+ ~2 ^# I  T: l
    总结
    * l) }+ X$ a( S' b0 ]# l! g( k: B& V! D- G* }% H7 Q, m
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!! [% K9 e6 K, F3 K

    ( h& p( x4 ]1 l4 c案例源码
    ; \  e6 E. Z! S1 D7 C1 e+ I0 Z9 n4 x* @" q' s# ]! P
    https://gitee.com/javacode2018/spring-series) A% O/ h$ c2 a" z! r
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。) w- N- ~- X9 @! W+ k: ~

    0 G, |9 k$ }/ V$ f2 R+ U8 M, W; ISpring系列
    ( c* L/ M+ u+ }- J3 T( O* L& t6 r( \# J& v# x1 z. N
    Spring系列第1篇:为何要学spring?
    ! ?+ ~$ k: M+ _3 U- ~* a# E* M7 o. h6 L# q7 c2 G
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    4 ^$ q: Y1 w: y# B# A8 ?( O0 S) u( L( i
    Spring系列第3篇:Spring容器基本使用及原理
    & R) {  v1 ^1 I" e% V; @# W" a" o/ q$ z/ X
    Spring系列第4篇:xml中bean定义详解(-)
    6 \& J- {- v( |3 C4 V% A2 T% L2 z7 n$ n* x: X4 ^2 b4 x
    Spring系列第5篇:创建bean实例这些方式你们都知道?
      P" ^( B: W% C, f: `8 w* F
    9 `$ X6 W- Q- g' ESpring系列第6篇:玩转bean scope,避免跳坑里!! q; K5 @: I* R. J& U. W/ A
    , d5 O1 O+ {8 B
    Spring系列第7篇:依赖注入之手动注入
    7 T9 L8 h. N) r- c/ d* T. q% `
    ) a4 V3 f5 \3 t9 A; TSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
      B0 F5 C" {5 X, S# m! f" Z# y7 g4 m& r! I
    Spring系列第9篇:depend-on到底是干什么的?
    1 h8 }' L7 V) G: s% L
    0 m- ~9 T( N( n, N- y% b6 s; r  k- @& DSpring系列第10篇:primary可以解决什么问题?
    & x% _5 X+ ?; W7 Q% M6 U9 D- [* N& j  J. Y
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
      b: T4 \& ]0 M7 D# b. U
    1 \+ d* d3 _; z" U5 n$ h, ~Spring系列第12篇:lazy-init:bean延迟初始化
    6 U+ U# Q6 m" T" u+ A- h( e
    3 _! ^" C# M5 k1 o) FSpring系列第13篇:使用继承简化bean配置(abstract & parent)8 V0 [# V1 X% f4 N5 e; d% p# Y

    * z0 o: Z2 g8 d* A6 q& X8 x' G0 M- YSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    8 _; z8 I$ K9 F" X1 L( f1 s( C3 e& R! ?9 R1 W
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?. o& [4 B7 T6 @! U5 d# X
    3 }- v: p0 O) z+ v) u! b8 s6 r5 {) W
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)3 I& G+ h) J4 _; f2 {7 j

    ; [" x% s: V1 A! F$ G! jSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    . _" a" @$ M0 O9 X( K- w; d
    . S3 A3 b+ e! n1 |# z- ]Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册), o; l! q; S8 F+ O" h4 a4 P

    * f# B1 o  b- d! v1 U* uSpring系列第18篇:@import详解(bean批量注册)2 u1 M) i. @( V4 z

    $ s# L0 c$ Z+ q6 d" pSpring系列第20篇:@Conditional通过条件来控制bean的注册# F% a% ?8 k- @
    & i. ?; t7 B3 S/ @  G! N+ K4 ~
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    / U. ~9 x: F: h: i/ u
    $ p1 q( |: ]8 RSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    . x3 W  t& p) L& }) L
    2 }" ]/ |& M) i) U' \7 GSpring系列第23篇:Bean生命周期详解
    0 L' z) Q# c% O" d2 L6 }/ e9 n- A6 E2 W6 z% s7 E- K* ~
    Spring系列第24篇:父子容器详解* D; E2 K$ l' q

    ! Y( K' H+ T2 Z更多好文章
    5 W& P% T7 O' Z) r7 @9 g. A8 y
    7 t; p, j+ m7 {! \Java高并发系列(共34篇)
    ' [! N7 {- w- c5 l9 X; Z2 W0 @% B8 r; A% B4 j0 `4 b* z
    MySql高手系列(共27篇)% u/ R0 G6 Y. Y3 f8 A2 |0 c8 }7 N% z
    ' a2 t% _3 }& O% Z
    Maven高手系列(共10篇)
    6 k2 m7 _4 G; M2 T% `9 g) _# D4 X! x) q  b  _1 k# ~6 \
    Mybatis系列(共12篇)
    " x* \% a) a9 S3 z4 u$ C9 T  Z" W4 g, n' n( u( @
    聊聊db和缓存一致性常见的实现方式* v: @: v7 D# ]3 \( O  J

    ( h( @2 y8 G5 Z5 W$ z接口幂等性这么重要,它是什么?怎么实现?
    . K, w7 I$ J* i& q/ p' V8 R* w$ H- Z9 }9 M! D4 A: @
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    , Q, O' E6 P% _+ z$ n1 ]————————————————) z8 T$ N, ?; Z, I8 s0 C
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    1 G0 R/ _/ I9 W' t7 Y' G( F, v原文链接:https://blog.csdn.net/likun557/article/details/105648757
    % m- X1 g. H& W& Y9 V3 v& K
    ' M# g+ V+ {3 q9 B9 X1 F7 K
    7 \; {( W5 P/ f! w4 k7 b& _
    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-19 11:45 , Processed in 0.440666 second(s), 50 queries .

    回顶部