QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5443|回复: 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 O8 J- x4 y" p! [6 q" j
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!3 Y+ H, ]5 i" Y, {. j" }: J' \

    / n6 N; E% p  u( p' \面试官:Spring中的@Value用过么,介绍一下" S9 S7 b5 D( D" S) o, N

    * X; {% M9 h; {# P' N! u$ B我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ! Y! y3 ^& u3 s1 k$ Z" a  |! w$ C: v! b: k2 G; j# z  p5 q
    面试官:那就是说@Value的数据来源于配置文件了?
    " \4 d. k( t; p( k7 ?* G+ m" A- l; z* q6 n7 L/ r* B4 c
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置& `& `4 {  l$ I) h
    2 x' W: d" O% H- O6 e. l) c
    面试官:@Value数据来源还有其他方式么?
    3 W: p( Q$ y* T" R
      N5 V% _8 x# y1 q% e' J# _我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。% _4 F2 p7 G% Z. c
    : f* A5 W% }" }: w# Y
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    1 A3 ]. a5 w6 g' |' Z, D5 n. k! q% g3 c
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    3 a  b' v& l: V! t
    $ v+ t6 u' a1 C面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?% ^* T- [4 X. S& h2 Z
    & n7 a* L5 i( F& |5 Q; L
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    8 E6 E$ g" E. z1 T
    ; W+ t3 ^% w' ]" R1 ]4 p+ f% p面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    3 A: \! ~' C. t# ]$ k+ r& |
    5 r( e. P+ N( L我:嗯。。。这个之前看过一点,不过没有看懂9 m4 ~4 H+ U0 v3 H" X: G+ d
    5 U7 L; E  X; h, ]9 _! s# n6 D2 q) V
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    ' l+ @! r2 C: S) V6 a. X; V9 G" @+ ^- Z0 p
    我:3万吧" }* p5 }5 W7 z, k, j
    * ]5 q! B- ]" z
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    1 v' s) G( F1 m- A% H
    0 K  Q5 Z: E0 p7 {我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    - a: O5 T! \7 B" d7 ?  l& M+ y1 W5 E0 c: B4 n+ h. G
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    : G, U) b) Z) M) y( p  e" @$ O) R0 f  n7 i4 W
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。( y# L( T" \$ b# }# M: N& k

    ' O% s5 w5 P+ u( f这次面试问题如下5 }* z4 P0 M! Y4 w$ B; g

    7 S5 A8 w3 m8 V  S@Value的用法
    9 @* E+ g% w5 W4 _6 i$ o2 j, U+ K3 g, o) Z1 s8 t" J- X# h4 E
    @Value数据来源& g) y$ E) w- x" [% f
    ! a0 D2 u% c$ L, d
    @Value动态刷新的问题
    1 N  s- L, H2 y# C$ X! e  p4 Q' Q/ Z  V2 G0 T
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。0 D: f' K6 s: o4 p; H/ b
    " d+ y1 }& s4 b; Z
    @Value的用法% h) D# X" A5 W

    0 ^% h! M6 R9 A+ J系统中需要连接db,连接db有很多配置信息。
    0 k8 d$ j; L  n: B( d) C; F' V& N- D9 u5 \
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。3 Q$ `; S% g) d9 Z1 o

    $ o9 c  A! \* D  C0 D, w* H: k还有其他的一些配置信息。: T& L2 a" T1 h8 h2 l4 C

    0 q1 Y8 W* ?9 x4 d" B我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    1 {7 y" R8 W, f+ D$ n7 S' U: }; n- e/ i* X* F+ [& M$ ^
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。7 K1 k8 k% j) ]

    2 S" Z! D& |7 p6 w, n通常我们会将配置信息以key=value的形式存储在properties配置文件中。/ L& b1 {0 h" A& o6 d) w
    ( [- [' I2 B  a' ^7 h* P& F
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。6 P0 a7 A2 Z/ @( m

    0 c) X; n* y1 c( ~2 s' A@Value使用步骤
    ; h7 [5 `7 p+ ]3 ]+ |- d, o7 s0 P0 x8 V7 U; T! w
    步骤一:使用@PropertySource注解引入配置文件
      `# ]( H; T; J8 B4 F
    ) u& B4 S$ \4 _7 L# A1 m# v! G将@PropertySource放在类上面,如下
    ( V. e, Z/ V. C5 I% e7 Y, @; f- I, M' \9 o
    @PropertySource({"配置文件路径1","配置文件路径2"...})
      T/ ]" ~- F; P- n% [@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。! L, b2 G4 r4 E- H) s

    & ~" ]8 ]$ k, k/ Q' A) Q  V3 Q如:
    2 J6 k+ W+ s, x
    $ o1 P5 f2 O" J: T5 p. d" i@Component  H& Y- n! r& G
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})* o' ]- v0 Y# U9 _2 f/ f
    public class DbConfig {
    7 q. R) q9 R% `! k}
    4 A6 S, ]7 w4 U: B6 O步骤二:使用@Value注解引用配置文件的值4 @. Z4 W) b8 P3 e: ?

    - k5 G6 s- s7 m- J通过@Value引用上面配置文件中的值:
    2 R9 t+ o+ U! U3 |, j& s5 M2 W3 W( M, h6 n% b( [/ t0 F
    语法
    - @! t8 H) Z' S0 C0 E# b; W* j5 T* W5 X( z" ^, K+ ^3 Z$ q
    @Value("${配置文件中的key:默认值}")" o5 B- R0 ?: H+ r& l$ [
    @Value("${配置文件中的key}")
    % T8 X7 C: a3 i如:
    - H) J+ G4 `2 c4 i% l( u# v& ]% q3 l% ]. ^$ i. Y) g- o2 z) k
    @Value("${password:123}")
    7 F- A- a4 J/ H8 f( A' V. B1 ]上面如果password不存在,将123作为值3 T& T, ?+ a8 s

    1 k! ^! k/ v+ O@Value("${password}")
    7 B# n( x' c# m* S' F& ]% X上面如果password不存在,值为${password}+ r! x& @+ b0 G5 M

    4 u: S3 Y5 B9 g! A; o假如配置文件如下0 @7 v% |- L$ x
    1 E# l0 N' v& {
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8( `/ g6 P8 d' H8 u" v) c3 Y
    jdbc.username=javacode
    * V6 D3 n" U# t5 k. i. Pjdbc.password=javacode
    , i1 `" q* R  g- x& E3 Y使用方式如下:$ E3 C% r( ]( j9 j6 t: n
    , t; Z! S' b/ x3 r# G& n
    @Value("${jdbc.url}")
    ) h' h6 T& t1 Z* i$ jprivate String url;2 k  G/ a/ Q2 n& U7 P; e7 d; @+ V

    2 B  o- I. ]2 k@Value("${jdbc.username}")
    , ?1 F6 c: X# }; `3 Y5 _private String username;: B2 s7 A! j, F$ l

    . @* p0 y3 N4 s% ~* ^1 u@Value("${jdbc.password}"): _0 Y- b9 h' Y  H# Q
    private String password;1 h7 M) a' W+ E
    下面来看案例
    # y" B% L) w8 {' K6 s3 I
    : Q, Z" h8 W0 D案例
    0 N9 C; {! W7 i
    ; N; K! V) w8 j/ w1 n来个配置文件db.properties
    8 q8 ^$ C5 A+ C% i( R
    : [. F$ D" J4 X$ b1 N! u# _jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    . D8 F( t3 H3 s8 x7 t* b, ^* U! Gjdbc.username=javacode
      O2 H6 k1 J0 K3 {- D) ^jdbc.password=javacode1 ]+ h' b' s; T# S/ d& k' u
    来个配置类,使用@PropertySource引入上面的配置文件
    , m1 V* x. r9 ~3 v' y
    8 M3 h: ?+ u7 a6 h1 }2 l' i1 epackage com.javacode2018.lesson002.demo18.test1;
    # w0 ?  H2 h6 N3 ?! n: C) G" P6 Z3 k: a- K
    import org.springframework.beans.factory.annotation.Configurable;
    8 k# v7 R6 F( U6 ]( l8 r( @import org.springframework.context.annotation.ComponentScan;, Z, f- E! h. p  m
    import org.springframework.context.annotation.PropertySource;
    & A4 T4 E( J% g3 `" w1 s# j" [/ h* v5 h
    @Configurable
    3 W! t+ s$ G" o@ComponentScan- ~# ?# [  ?7 |1 L: r
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})" |7 R/ }4 Z0 a4 R& M7 k' c% ^
    public class MainConfig1 {
    . X; s% t. S$ a7 O  J7 f' \}
    6 D: a: a3 j% t) E, x来个类,使用@Value来使用配置文件中的信息0 c+ R% d) H7 \. G7 }
    4 T# X  [3 s+ {+ p
    package com.javacode2018.lesson002.demo18.test1;
    * s, J* v1 `6 N& z6 O( E6 T& u1 x! J  Z' K) O. H, F
    import org.springframework.beans.factory.annotation.Value;
    . H* W' l% U! ]9 c$ pimport org.springframework.stereotype.Component;
    0 ?; O. j! V) a; ^; o  {9 `/ J9 _* q4 a8 M6 J
    @Component
    6 P: b8 L* i3 B& A4 f# vpublic class DbConfig {" Q7 r7 C, z, U

    / A% e* ^  _) l. }    @Value("${jdbc.url}")8 H! t- j" ^" B6 X6 v% k5 K
        private String url;
    1 O! `* {5 H6 L- c1 E
    9 s2 h) e' p) i3 A: ]; t2 [    @Value("${jdbc.username}")
    1 }/ n9 Z6 k8 O" o  ^# k, e2 z    private String username;1 P# _) l* n- x. e8 r

    8 t7 c9 r+ D* F; F6 H7 }* K& Q/ o+ b    @Value("${jdbc.password}")
    7 ~3 g" V9 t6 x    private String password;
    # h3 R& E/ S4 @/ ?7 m; v% f4 Y: J* q/ j4 o. o# a* ]
        public String getUrl() {
    1 ?0 _7 A/ Q- l) p1 }        return url;
    1 w7 ?. B1 g, @# O3 h( V  I    }3 E1 {6 I* O7 {* D- @6 P2 V( p

    % r) m  p4 X" G! T8 L3 J    public void setUrl(String url) {4 W- E$ U2 o( M
            this.url = url;$ |5 j0 W, H: ~3 i
        }& `+ r3 l3 r# l. Z! [( q2 I
    9 B( M& T2 K2 [- N  ]
        public String getUsername() {* N' D% Q& k& |! H
            return username;
    " g+ p  Z- @' w* l8 j- W: W# P    }
    ( x1 P8 V+ ]6 @. z* y2 U( N( r) o. Z& D
        public void setUsername(String username) {
    . t' z+ o; ]* ?" d0 ^        this.username = username;
    % v0 o- B! g# X  e4 ~0 d3 b    }7 p& K! o8 d$ P7 q2 {/ M6 R% |
    8 b% C. E! Q, f4 S+ {1 V
        public String getPassword() {
    4 b5 M: H: H; M8 G        return password;
    $ r" B; a! c' [5 y9 b    }1 E% I- l, D4 B! b, B9 o/ k) Y6 ]  p
    0 o  ^8 U! C( n: ]! Y
        public void setPassword(String password) {4 T8 y: Q' u/ L5 F6 Q
            this.password = password;
    0 f" B% s7 p5 K3 m* L4 n: Y) `    }+ X) F+ `& E7 A$ t( ~4 h) q
    # c0 |5 X4 F9 [' M" E. ^; I
        @Override# F# a" z* J, V: S/ P
        public String toString() {% A& P0 X2 m( ^8 M. K
            return "DbConfig{" +. W: Y* Q7 s8 z
                    "url='" + url + '\'' ++ [- \2 _/ j+ q6 h! ~9 H* v, h! _
                    ", username='" + username + '\'' +1 k* i+ Z% `4 z5 o5 r
                    ", password='" + password + '\'' +
      n! M1 N8 ]8 l+ q/ V9 O( m- {                '}';
    % G0 X# b2 u- t( v% i; `' O9 a% o+ |    }% c( h0 n" ~% v! {6 k. l- J
    }* [5 @4 f. R" Q0 W& ?
    上面重点在于注解@Value注解,注意@Value注解中的  `7 ~* W8 ^2 e, Z

    $ m- i! ~6 d+ L& K来个测试用例
    0 A# n! U  A+ U% d2 p" }- v" I9 A! Q  o) ]
    package com.javacode2018.lesson002.demo18;0 \- P( k8 `' k2 n5 _; w

    $ j! I& r$ `7 P7 L& x' o9 vimport com.javacode2018.lesson002.demo18.test1.DbConfig;0 y8 [+ E: k6 q- b% a6 T
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;" n6 i% Q3 K) K, O
    import org.junit.Test;
    / @5 Y0 S. T; W0 V9 X& E1 v7 x1 |. |import org.springframework.context.annotation.AnnotationConfigApplicationContext;' B: k; e4 i2 ?% `+ j3 X" ^
    6 n% ^; e6 ~& l. U+ u
    public class ValueTest {
    6 j7 W0 A$ M5 V' X$ P
    & t4 O- }; d% z: \    @Test* u; P$ ~7 {* y4 D1 {4 [5 `
        public void test1() {
    4 ?$ E' c2 L: J2 S+ r$ R$ I        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; d% n5 d3 v- ?; u        context.register(MainConfig1.class);
    6 H: b1 `5 N& |3 C( S8 S        context.refresh();9 {: ]' h' }! `3 J$ ~

    7 E) N. S  ^) A% |# i7 A        DbConfig dbConfig = context.getBean(DbConfig.class);
    / q% a& a% ]! L7 J! b3 M        System.out.println(dbConfig);
    . R4 S" ?9 }% A1 j2 t- h8 r# r    }
    " ?/ x* q% S7 ], d! \; V}
    ' q/ ^. A- K8 t  M1 b: O6 o$ g" D运行输出
    * f2 W1 e( }5 {- n( G! ]- p0 y" U# @" L3 Q: J
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}5 G8 u9 V5 K" W' ?! a; w2 K0 C
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。0 U$ h) z) y$ u# Z* n4 \; g

    ( T2 V% t7 v  q  c8 O( B@Value数据来源
      f  J6 p  U  q. h5 w& O1 }3 K% k( _5 K
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。1 ?; i" g% L' O: B2 p- Q
    1 T8 {; ~9 s2 g; M3 z; X) W; I* }2 \
    我们需要先了解一下@Value中数据来源于spring的什么地方。- }, z* [# R9 H% t' ?
    # @& T; ]) C, Q
    spring中有个类+ w, }( z7 a) a4 ~2 m) W
    - a0 j  f" j0 I0 J
    org.springframework.core.env.PropertySource
    & Z" U! h  u( ]# E8 m可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息' a9 ~! p0 v! ?" d& A0 U

    / |  M4 g( y9 s/ }内部有个方法:* r' w1 a* @% g1 X3 e* _; ]) @

    8 ^! v8 B( ]( x# npublic abstract Object getProperty(String name);
    ' _7 n- `4 R) j9 `: P; O通过name获取对应的配置信息。1 [  k+ B5 L8 u6 c6 r) V! h5 ~

    # u4 r7 m) K/ l8 L系统有个比较重要的接口
    $ H7 Q+ I, R% r% R1 \
    - d" M3 }9 B. S: Q1 B% ?+ oorg.springframework.core.env.Environment
    3 \9 ?& Q8 |0 j# @! p8 C$ R用来表示环境配置信息,这个接口有几个方法比较重要
    , b. B& y" J7 J, V
    : Q+ x* o) T) @' b8 {String resolvePlaceholders(String text);
    ; p; X/ O+ t/ Y% @3 N  Y: TMutablePropertySources getPropertySources();
    6 ^3 H$ A. h$ w" W+ hresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。. K5 _# \  k' M
    ; a; J" a! t' {* m
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    , @) j" Z: f5 B8 c7 T$ l; ]6 s  J8 A: z% d5 p( X- c1 ~
    public class MutablePropertySources implements PropertySources {
    2 A1 z! B6 e! K. J2 Y7 G( J0 g( u- L
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    * N- |: ]4 d4 W( I0 {/ q0 \+ o6 [2 I; X- ]
    }' o3 i2 ^: i  `' m6 I/ j
    内部包含一个propertySourceList列表。
    ; u3 M; r% r/ |$ j: g4 p# p" N+ u$ h/ |0 N6 v: l0 |8 K
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。2 \1 N$ l" r$ T+ u! a; Q& q6 s! w

    , M$ l! v; h' ^* M7 Q4 S/ x大家可以捋一下,最终解析@Value的过程:
    9 p# f; A/ V; `/ [  i/ K5 ?4 S% e4 E, m; l
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析7 I; `* b0 D3 r( N) r
    2. Environment内部会访问MutablePropertySources来解析3 \! ]9 J  k+ @9 T5 e' S( K
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值/ `, ]  F5 y2 n, T  J- B
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    9 Q  a  o9 m: R  k
    ) O+ H1 \' |7 k, M& ]下面我们就按照这个思路来一个。
    . W, \  k4 _( f, x# ^/ @! d8 j( D5 R( h/ z" D, w
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    " ?% D2 M2 r) Q% a) J  w( ^1 @
    0 @8 L) B1 m8 c& o  \6 bpackage com.javacode2018.lesson002.demo18.test2;
      b1 S5 e0 _% s0 l" I* Q: V/ }) N' w& o3 x
    import org.springframework.beans.factory.annotation.Value;
    ' P8 M' s& C) N' K2 I/ mimport org.springframework.stereotype.Component;
    7 b4 R& _( U. @& \& C9 V; c3 u7 J4 j# ~3 x; a! J
    /**: x: u( S$ s" d+ [
    * 邮件配置信息
    " `, f$ Y, ^; n7 { */
    & U6 C$ e  r/ K0 p/ A. g3 b@Component
    ! u; i' A4 c0 I8 h1 O$ I# U' {& lpublic class MailConfig {, _" u( r7 u+ g/ U9 I

    7 a) l3 a/ g8 K& B3 Q    @Value("${mail.host}")
    : k, H& i' V' m3 ~3 k( T    private String host;
    4 B; t  M& i) R7 C# t! A5 P
    ( `- G4 g/ h" \! d# h    @Value("${mail.username}")- P8 r' l/ J4 `1 f
        private String username;, f5 j+ r# R, P. q

    ( [, N) E( j5 r8 V- }2 v7 d    @Value("${mail.password}")
    ( Z- F; i$ H- ]: ^, Y+ v: Z    private String password;5 E- N7 S& W  c# |8 l
    0 R2 Y1 f( `7 T
        public String getHost() {
    0 g0 I/ Z$ q( {3 m4 c9 c        return host;
    3 t7 u) S' R& y    }
    . `  l% y9 F+ q+ ~/ r3 `& m9 D
    - L& s& A$ E* i- ~0 o( G( P& L    public void setHost(String host) {5 C5 h6 i- X  \8 Z
            this.host = host;1 A: L) b2 C, J4 Q: G& x0 ^
        }
    ( M7 z4 f! u+ V4 W5 h0 V: e8 X8 v1 x! a2 c! d/ K% h; I$ b8 s5 W
        public String getUsername() {
    , u0 X3 i, B' z; J        return username;
    . p6 H  n3 V: l1 p" f    }2 {" i" P8 t$ [; J$ a
    , q  h4 _3 t9 S0 G$ z$ g
        public void setUsername(String username) {
    ( k  F! h* N& m: [: O8 H7 E- e        this.username = username;- z$ f+ _0 `9 ?# S- B6 r
        }
    4 T( Y& L' A; e/ C" q% U2 w- H8 i+ Y2 J* \
        public String getPassword() {
    % o9 o7 M" v2 ^" E; h3 e        return password;5 d) Z$ I! f, i: g
        }8 m1 Y+ v' R7 n  d% ^
    3 ^8 F: n( Y6 X* N
        public void setPassword(String password) {
    0 w2 Y- A2 V. ?$ |/ E- R        this.password = password;4 l. S2 u& ^9 m, N
        }
    9 E) H0 ~6 F- C% R* \# O; f3 K( ^0 ^8 _: t9 k
        @Override
    ) o$ g! J! a: ^; P4 q    public String toString() {
    7 _  I- X1 }8 L$ A        return "MailConfig{" +
    ) c: s' x& V* _" _1 S                "host='" + host + '\'' +2 U% O2 j! L2 ]0 i3 X# B0 l
                    ", username='" + username + '\'' +
    5 S# r9 M5 @/ q# d0 a                ", password='" + password + '\'' +) i- i4 }' p; [2 p
                    '}';
    8 |0 e1 |) A' _/ q: |    }5 Y$ W  j' K+ F  j2 A. T( B
    }
    , E) q: d$ J6 M; g7 a! n再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    / F) b6 V- K' E. m. W  O: S2 H
    ; e4 D8 [7 A( L+ }, spackage com.javacode2018.lesson002.demo18.test2;
    7 V9 G  i- K* R% E& s8 Y/ `+ X2 p/ F  R" f2 m
    import java.util.HashMap;
    8 \- n5 `: p  z. C4 Qimport java.util.Map;
    : q! B6 ~3 M& _# R' V( ]0 W" {7 v6 H, m# c2 [
    public class DbUtil {
    3 I7 b9 _8 |, ]& [4 f( W/ V' M- |    /**
    0 Y, h  m6 X  U, k     * 模拟从db中获取邮件配置信息
    ; g1 ?3 ]0 r* Y! S* D- d; z     *8 f' o# F5 {; D* G+ H4 n
         * @return% f0 ^, W8 V. T" X2 z+ `9 a2 M; P
         */
    . J3 L# e/ j; f; a& F0 ]    public static Map<String, Object> getMailInfoFromDb() {
    * W6 T+ Q9 k( I+ M! t8 m, J$ X        Map<String, Object> result = new HashMap<>();- V1 y0 R5 \' c& s/ d6 y" O  C
            result.put("mail.host", "smtp.qq.com");
    , @/ z! Z% D8 [' a! @5 ?        result.put("mail.username", "路人");' A! i- D; M7 p* G  L. }/ P
            result.put("mail.password", "123");: ~9 I! |7 F# h' b
            return result;
    1 E# ]( X* [* O8 h: s, C$ g  a    }& n) t( r( U' T! @6 [
    }% E; {% }3 i* n; C
    来个spring配置类
      N5 d1 r  J$ v' @* m* q2 @  N  s6 n4 Y: T% V6 B0 t
    package com.javacode2018.lesson002.demo18.test2;, q7 h  J& c5 e8 e' M1 }7 O. ?/ u

    6 ~$ ?  o( v. C( Qimport org.springframework.context.annotation.ComponentScan;( M6 G; Y$ k. s
    import org.springframework.context.annotation.Configuration;: ^' g) S' z+ [0 J6 C

    4 H0 o; r! E1 s9 J/ H@Configuration
    - z+ b3 x- g+ w@ComponentScan; [! F! f3 u2 H6 k6 B
    public class MainConfig2 {
    6 J' L2 k% G: F0 ?+ @' {6 P9 y}
    ( D$ C3 u6 D! g1 _下面是重点代码
    . w# p8 P6 c  [" R" G8 _  Z; R0 M
    9 z( c. w- S& L2 G@Test5 z, j% p3 S' e
    public void test2() {) P1 a: z! v$ g/ b) W* n- D, |' z: g
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();: W# b2 E' _+ N5 [. M

    4 I0 j3 F' x; V  q4 [, U    /*下面这段是关键 start*/
    7 |1 e$ h0 Z" @    //模拟从db中获取配置信息
    * H  q2 i3 m, f0 s- c    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    0 F$ z- U0 M1 C- ?& ?0 @5 j6 h    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    9 ^. ~/ H5 ?- N& I) |0 ?: {    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);7 u, A' r1 R- F% ]. A) `  E4 d6 I
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    % {, F: d0 }+ l4 c% p0 U/ d2 V    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);' O' f' a- c1 u0 O
        /*上面这段是关键 end*/5 X: u' a+ q' }0 `+ r

    2 a0 N. I" |3 J    context.register(MainConfig2.class);$ Y$ l, J6 q  ]7 o; C* Y: s' c
        context.refresh();
    9 K* a9 a- O% t2 B    MailConfig mailConfig = context.getBean(MailConfig.class);
    ' _1 C3 V  G8 Y( |    System.out.println(mailConfig);* e, G0 N  H: i) [, [
    }5 X2 d/ k! @) a" n0 j7 i
    注释比较详细,就不详细解释了。0 z+ h% @* k% y; A
    * V8 ^9 @  }0 }
    直接运行,看效果
    + p0 d% d" r8 |7 Y* W. |: s5 Y9 U' G* g5 \  ]% M' ^
    MailConfig{host='smtp.qq.com', username='路人', password='123'}* ]" q, }5 U  t
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    6 |& G2 ?$ V! d0 G! g3 ~% ~# y' n( B! Y* P1 J. y. ?& B, ?1 P
    上面重点是下面这段代码,大家需要理解6 }! V0 f1 {& t9 R

    4 r' \  B$ ~8 x9 B) O/*下面这段是关键 start*/
    + e3 C! ?0 j1 ~5 x2 W7 X& \//模拟从db中获取配置信息
    % _  F5 l+ W1 bMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    * s8 y6 X/ |7 B4 a0 q//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    " C/ L( Y) Q: @2 w4 j7 I1 mMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    $ y0 G8 ~; A8 p( Y( a//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高/ T, [, A: F  W+ L% X  p& ]
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    2 d' A! G$ A" B/*上面这段是关键 end*/% u: p6 b7 @/ X# ~
    咱们继续看下一个问题3 }2 s6 x4 G0 D2 j6 S7 E( u( b
    : Z5 x+ u& K. G, A4 F
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。7 s9 C  ^$ F  h5 d" G& \8 U
    % |% D" u4 Y2 A0 N8 C7 y
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    / Y9 J+ c9 p# t( a
    & |. @, Q" r6 M) z5 b1 }* {实现@Value动态刷新$ ^/ Y$ s" z4 S7 q

      t/ s- a1 g, W. f* h" n先了解一个知识点
    9 x  y7 e- s3 @9 z+ n: _" j5 ~& \2 v# T7 \9 J% {: `" n* ^0 j
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。4 U) i/ t  x; {: ^5 ]

    2 k0 ?7 p2 y4 B$ i这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解. c) b/ G! \" E

    3 {: U3 o! A. Y" @) O9 Fbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:! q  ~) H  H! _: e/ w: W; G$ q
    & R6 P6 j7 M1 y0 j
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;- D: N' o; D8 p* p; k5 G" [% V0 d
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    : ^* R2 C4 W' J( _# {4 u  X$ M
    public enum ScopedProxyMode {0 H1 y& T2 x# @( f
        DEFAULT,
    3 F# C: ~5 |) u' U: _    NO,$ h; W2 x3 `$ x) A* D: J
        INTERFACES,2 m  u. I: E. h, T# Y, F6 w
        TARGET_CLASS;) o, [) u' K! d$ r1 L' p. e5 d" r
    }, i& ]0 Y5 s/ ~5 p) [. m; S
    前面3个,不讲了,直接讲最后一个值是干什么的。1 W* o- K. ?; |6 P9 Y

    " }0 ~# ~# s6 \+ N1 I  X  C7 f, F当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。2 s. W( S1 p1 b) D

    2 ^+ `6 s" S* |0 u理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。. {- g* s2 N9 i2 }
    - }( F% e7 |6 @
    自定义一个bean作用域的注解
    $ J7 b! I5 K" v( p4 g7 _
    " g! p! Q- X, cpackage com.javacode2018.lesson002.demo18.test3;
    , d4 V, K4 C+ b' p! P" J1 U' o8 L6 s: T5 B  F- C( J. c$ b
    import org.springframework.context.annotation.Scope;
    2 r: _. q; `! R- fimport org.springframework.context.annotation.ScopedProxyMode;" C. d2 a7 }: l$ w

    ! Y/ N" B( M9 M  }import java.lang.annotation.*;
    - z$ _5 M) r0 x+ i( z, p+ n' E4 s7 p0 ]6 }
    @Target({ElementType.TYPE, ElementType.METHOD})3 }) H" u( f" k. w
    @Retention(RetentionPolicy.RUNTIME)
    1 {1 i* ]4 ~6 X! h@Documented; y6 S( u0 z9 J+ S9 p8 M
    @Scope(BeanMyScope.SCOPE_MY) //@1* e. Z4 `, _$ T" o  Z) ]
    public @interface MyScope {) a2 N7 m9 b8 x" V; N& H/ `
        /**
    ) k- j: w2 S/ y2 K     * @see Scope#proxyMode()* ]% D4 Q% ]6 K0 O- E+ w$ f
         */
    6 t, I/ T+ Y* w3 q" r, w    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    0 I$ r% |/ u+ V$ Z  R6 p( F; y}( E# n- m5 {9 j2 o  @
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    ( P; b% A8 Y' o) ^, k$ g9 l; Q$ w  C0 L5 s
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    . C5 d; ^3 O4 P5 ?9 D; R( l3 y7 n0 I0 T- z) c9 L) K& v: p/ x! o% u
    @MyScope注解对应的Scope实现如下* \3 e# z" y  I+ y0 G# F/ ~, x. L
    3 Y7 t1 g# X4 Y1 U3 l- X/ Z5 e
    package com.javacode2018.lesson002.demo18.test3;
    0 \, I- E- U- w( c* M" o8 `5 C: C' \/ w
    import org.springframework.beans.factory.ObjectFactory;$ X5 D  w" b: S1 M; P# r, h
    import org.springframework.beans.factory.config.Scope;
    9 A& i0 A  s; e% Simport org.springframework.lang.Nullable;
    4 z# p( C& P7 h* ]% b9 K% H; o
    /**  t" k6 x* w5 Y; m. B
    * @see MyScope 作用域的实现
    , D, i: t9 v( {( X */% K6 K' M  U% f; G0 L3 l8 H6 ?
    public class BeanMyScope implements Scope {
    9 Z: T6 Z/ g7 B7 A8 o9 O$ Y; z" }: h( ?' @
        public static final String SCOPE_MY = "my"; //@17 z3 K# i3 W2 O, `: F# r
    2 S' Q' h+ W" N! L: C. V
        @Override
    5 W8 I4 Z& x9 C    public Object get(String name, ObjectFactory<?> objectFactory) {
    ! f  j* S0 I% i# e3 d        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    ( I2 {: h/ Y7 r  P/ \        return objectFactory.getObject(); //@3
    , k$ B1 T3 z  e, [    }* v/ w, d: t# ^
    : u, {( V; E8 A# J: K( @% ^
        @Nullable
    1 a' T' P4 e! B, T/ J    @Override0 i5 n* }! F6 d; ~
        public Object remove(String name) {8 K$ n& v; o& O4 a
            return null;
    6 f, k8 c' B) ?; m& v( m! I    }
    " {: N3 }+ b% h$ E0 W4 t
    3 k& ~; `3 B* r/ P; `7 n5 d    @Override$ _2 C4 T  p1 o9 \. z9 ^3 [! E
        public void registerDestructionCallback(String name, Runnable callback) {
    ) u! A0 u. d  w7 V/ [! N5 ~
    ) g9 X4 G3 U( b4 {6 p( W    }& p/ F  b, T0 E* n

    0 ?! J! }: n  u9 U* l' m    @Nullable
    ; b% N$ y6 O7 H$ b    @Override, F6 l2 O  ^9 z; G3 o
        public Object resolveContextualObject(String key) {& S/ I# `# C! e& A
            return null;
    1 O& t9 r, p* s* e) s    }7 ^6 _8 b; |, e  m+ t% ?* y

    . J. @5 }: G' Z3 ^2 j    @Nullable2 \. Q. L$ E: g8 T  `) b2 s
        @Override* T0 O8 I* z, d# L( |
        public String getConversationId() {
    # V2 \. Q. Y! m. U9 Z' U' M        return null;
    ! o. o" ]' K1 l6 R% j3 c    }
    % f  i( F4 q; \5 w( r/ e}
    3 @8 W  X* z8 ~* g9 L% u2 ^@1:定义了一个常量,作为作用域的值" S3 \' F1 n  T# t8 h% D

    " F6 Q8 ^; d1 }! o' Y* ~/ B@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果: _5 L& B/ i) T" T  r
    7 y0 U# G) r+ a0 c% E: d% F
    @3:通过objectFactory.getObject()获取bean实例返回。
    / I- k- F2 o% R1 V
    . S& {; e* p5 {下面来创建个类,作用域为上面自定义的作用域3 Q6 m+ u9 e3 P

    ( F) H5 S, K9 v6 {' l7 g' r+ tpackage com.javacode2018.lesson002.demo18.test3;! ~1 a/ h: W4 T0 t! h; u3 v& N2 ~  m
    # Q" }2 b3 {% G% o3 F
    import org.springframework.stereotype.Component;
    , i. {% B9 \7 n! M+ j7 S: z9 ]% }
    6 d; ?% K5 h7 e& E% y  Fimport java.util.UUID;' r2 N3 z6 K! z, ?6 _

    ' I8 V; j; G6 h$ j6 i1 L@Component  z2 u5 F& m) d" R9 g- o9 j  J
    @MyScope //@1   B' n; o# j% w  @
    public class User {
    ) C/ h; z1 w: c0 X: D+ m: E$ A1 B" p  J: W4 G) y8 b5 l* s
        private String username;8 I  f" E0 c" D, I. ]. M0 O6 G
    6 G6 U# h" W0 [& O: a  T5 _
        public User() {
    2 h8 Z! u* }  G# K& @. k6 j/ x7 J        System.out.println("---------创建User对象" + this); //@2! h; Q2 {7 C* l
            this.username = UUID.randomUUID().toString(); //@30 q2 g+ H  t! Y' E, T3 r! m2 h! P
        }+ x3 M" t7 i5 p) Z  A

    8 U. e& N, g. f3 b9 r4 ~    public String getUsername() {9 K' `' I0 \3 k& _" V5 |& X; ?( X
            return username;
    + Y4 S8 c( r/ ]: _    }& u4 y7 k" W6 B3 u, K1 I

    % c# q$ [5 v8 e: {    public void setUsername(String username) {
    & G3 a# Q6 m' W$ o8 b        this.username = username;
      b& G; u8 `, k5 J    }9 s7 l# \; e8 `- T% X0 X% @

    * w! k  b- \5 q# \+ }0 q8 d}
    ; c. i. A2 |- q  ?) I! i@1:使用了自定义的作用域@MyScope: j, Y5 B& f6 r& A
    ' U* ~6 ]2 S9 P  v
    @2:构造函数中输出一行日志
    8 M! t3 m1 A" d3 x, ]/ e2 y  @: m% |& ^' S; E8 Q9 N  s
    @3:给username赋值,通过uuid随机生成了一个
    $ ?8 B7 A7 E( i1 m
    6 _* a$ ?. @2 B6 e2 E5 v  v来个spring配置类,加载上面@Compontent标注的组件9 V- C# W1 G  {' B

    7 q2 _- @# m$ |1 ~package com.javacode2018.lesson002.demo18.test3;$ O" c7 }  A% U

      d6 v9 q! P# u; o7 M7 gimport org.springframework.context.annotation.ComponentScan;" t+ ~3 x5 l5 T. @6 `# m
    import org.springframework.context.annotation.Configuration;
    , W' Z' X9 `/ ?: ]) w0 k/ }7 @9 k, R5 ?% [! S8 V0 w
    @ComponentScan$ m" h, j& d6 [
    @Configuration/ s+ _, {  h% f7 ?
    public class MainConfig3 {) B  E$ P: I% g
    }
    1 y/ ~. E* E5 _7 ^下面重点来了,测试用例1 ?1 X) F" q0 t, c

    - V& [% y: F- g  w) M/ d1 o- [& S@Test
    4 \, d0 `) X) g9 z* Y/ N1 \public void test3() throws InterruptedException {, z: G0 ~5 u; O' ]7 {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      @' j0 t% z8 H2 S4 q$ T! F4 e    //将自定义作用域注册到spring容器中
    & k8 W0 G6 h. p5 C3 O# c  S    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    $ P+ D+ ^" ?- t, W6 j. G$ `) C    context.register(MainConfig3.class);
    " B7 D3 o9 ?9 g/ H    context.refresh();
    ' ^: ^3 X6 ]6 a* v) i: w
    1 e" q4 F- W" p5 \1 t9 N# V) b. S    System.out.println("从容器中获取User对象");" t. o! G# o$ \. t& e. i
        User user = context.getBean(User.class); //@2; w) o/ J" `2 B+ K/ E% i( y! G
        System.out.println("user对象的class为:" + user.getClass()); //@3; b# ^$ Q" Q- I9 Z
    7 f( ]3 D! Y  o4 @, M& p& y1 q
        System.out.println("多次调用user的getUsername感受一下效果\n");
    & G. i# @9 w( c& u3 V    for (int i = 1; i <= 3; i++) {' s: T' S9 e. v0 w, W+ u
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));4 d" K2 \6 I) Y
            System.out.println(user.getUsername());' o2 }+ U6 S! ^' c; a. z
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));- t& {0 R5 H# |) I  D
        }
    , V: e! U2 D0 I4 h% m1 Q+ `$ }}
    ; }( g% z2 W  G/ j@1:将自定义作用域注册到spring容器中
    1 G% x9 @: `' L/ A, C6 F( g- A
    2 p, }- h2 z- B, S% ?2 h@2:从容器中获取User对应的bean, t* d& G' O' l5 Q
    - l5 m& e1 f& Q, d
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的2 X! o' J5 `- W4 C  \* X# g. F

    / @8 t$ F! `7 t: r" l3 o  ]5 J代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    0 m' q1 N3 Y% i" F( m
    7 b- Y- i. }3 R( m见证奇迹的时候到了,运行输出. [8 @# N+ w) u  P7 K. E

    ! N' I9 [* o) }5 l5 E从容器中获取User对象# U' ^8 Q/ u/ u9 y
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127# [5 U/ ~* P: P  M) l9 ^& Z! R
    多次调用user的getUsername感受一下效果9 k; g4 L& Z& |2 H1 X. k

    ; w( m: x: `/ J3 G2 D2 f! n********5 I; ]  X* n3 A
    第1次开始调用getUsername
    ' [( o1 O8 X; |* N1 j  Q. B9 pBeanMyScope >>>>>>>>> get:scopedTarget.user7 V- Q, \9 x- r& K
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4& S2 U& b/ `  x2 _: Q! b& @) S
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    2 y  `0 k: Q4 P5 u2 \. d' c1 g第1次调用getUsername结束
    4 U" f1 T( l+ I- Q********. y9 t' @5 k* D1 G/ k7 \# b- b

    & M! e, a( ^2 U6 g# b********
    # ^9 ]3 \1 v8 |' ^; P第2次开始调用getUsername
    " ~# h4 s- U4 X* b1 l3 s( R. VBeanMyScope >>>>>>>>> get:scopedTarget.user- c6 Z4 ?' l. H) c5 F2 g; b+ G
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    5 f# V) F" }- k' B01d67154-95f6-44bb-93ab-05a34abdf51f! y- F& p& Z8 q- q; U! i
    第2次调用getUsername结束
    / c+ {& J+ u1 N) \********
    7 s6 ^7 H; G) v# a* G( C+ p
    6 q* H. o( g  V: z6 G) G4 u, t********
    ; ~/ @8 w7 q3 i4 _! @9 e第3次开始调用getUsername
    - C/ C& R& e3 D0 t3 w$ Z" u. DBeanMyScope >>>>>>>>> get:scopedTarget.user
    7 L) g( s5 Z( N---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d150 Y% K1 O. ]7 s, q5 q9 H
    76d0e86f-8331-4303-aac7-4acce0b258b8
    : h. F# c) `1 p! y1 X% ^" U第3次调用getUsername结束( F4 i5 ]% O9 [6 {* \
    ********. U3 J( C+ ?2 Z' b; q
    从输出的前2行可以看出:
    ' f4 b. U# U% i8 w( d* {
    ! }/ i& h/ F: c调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象5 ~0 z( m/ d7 q( D+ [( N! H9 M
    5 G/ S9 I$ Y1 c- L' L
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    : f6 ]5 D6 H+ D, s3 b* `- w# y. C7 X* Z$ E
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。3 L& d) l. J7 {/ h* P( p4 v8 z

    / w' ]. T1 T8 {, Y: b7 m通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。# p3 m9 i; a7 g

    , }2 W5 Q5 d; R7 q1 X# I, B- p& t动态刷新@Value具体实现/ e; |- Z" X4 |: e1 _1 D. A  H

    4 k5 H/ y+ W5 A那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。, |# O. w7 W0 Q1 Z" }
    & j, o' I6 V' m* \7 E
    先来自定义一个Scope:RefreshScope
    / m* y1 n, e7 E+ w: I/ I. s, v5 J% S5 o/ u# j0 e# p' j0 h
    package com.javacode2018.lesson002.demo18.test4;
    - d( d7 S" v8 s- n$ y& Y5 m$ l  U, ?
    * X4 x! s- C4 V1 d& g. S, K& P5 @, Iimport org.springframework.context.annotation.Scope;
    + _  v2 d: o( x1 O" F7 gimport org.springframework.context.annotation.ScopedProxyMode;
    : B+ {1 f$ x5 x2 h0 J
    ) P- T- Q! s* M& ]% [* A5 pimport java.lang.annotation.*;0 q9 V( e$ j( S$ R8 r& m+ I8 I* I

    $ O. y5 h4 f! i) O' U* J: S- d@Target({ElementType.TYPE, ElementType.METHOD})
    9 \4 N3 ^4 A# r" ~@Retention(RetentionPolicy.RUNTIME)! I2 a, B% o) M# r' r, P5 l! q
    @Scope(BeanRefreshScope.SCOPE_REFRESH); j9 a$ c2 S$ S- n
    @Documented
    6 ]7 T: j4 q3 C& k: R/ i& s& ]public @interface RefreshScope {5 x: p9 [* {- f1 N* I, W! U
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ! K' U. L* S. L5 v! u2 {; Q}
    # s1 R4 V# B* g6 Z& R# k9 G要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    : y/ e3 M% R. Q) e& D, C! q
    & |: y: e( d  v3 {# L% R# A5 q$ R3 W@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS9 r* O+ |$ R/ t0 r7 D/ p
    . N( o8 n/ Y- C7 ]
    这个自定义Scope对应的解析类: ^8 ~# B) _0 Q

    2 z! w2 S( T8 x7 W) s7 R下面类中有几个无关的方法去掉了,可以忽略* S" \. C! a" _1 x8 a, B. e

    + U8 r/ m3 d4 Y- k" {' fpackage com.javacode2018.lesson002.demo18.test4;
    2 t- L) {* c  L+ E  h5 n
    : ^, Y$ V- M3 r  T* [
    9 l7 a" x' n5 E2 f0 ]" e4 w* R7 Ximport org.springframework.beans.factory.ObjectFactory;+ L! O. v* d" ^- n4 ]
    import org.springframework.beans.factory.config.Scope;2 F9 L# u6 \1 O4 a( E& |" Y
    import org.springframework.lang.Nullable;
    . a6 A4 f$ G' D; J. a
      l4 n. r2 V8 @* Wimport java.util.concurrent.ConcurrentHashMap;& N& U2 d+ e  y
    ) U  K5 D; s& N" ?! C, ]7 Q; ~
    public class BeanRefreshScope implements Scope {
    : Z) U. j" U0 \7 H8 L, |
    . ^5 X) a. Z* m# M$ n% ]    public static final String SCOPE_REFRESH = "refresh";  c/ t( Q, @% f" G1 A: N  P
    % `! ~' M# }4 P1 U7 O
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();  i9 b/ k! n& k/ m
    7 t( f& c. Z% Q" J2 {( I- w; Y0 s  [
        //来个map用来缓存bean) D3 m/ t! ]; m9 C
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1" n  U( X' Q2 Y0 y

    8 j- p( L3 y6 x0 |    private BeanRefreshScope() {
    9 x! v; Y6 l. C    }
    4 C. r& f2 f1 }
    1 @) t9 m8 T( J% Q& Z4 n    public static BeanRefreshScope getInstance() {
    3 X. C! Z5 k" \, \; ?; t        return INSTANCE;
    " N1 ?7 ?' f8 a! P" [4 c  t: X/ R    }6 ?9 S+ n6 [+ J2 Q

    ! I9 D$ R$ I4 r# f# R; s) M; F    /**
    9 |0 w) ~% D. b; N     * 清理当前
    - R! ^9 _( b+ `% t% k! w- A, o     *// ^' `% b" {: w* i
        public static void clean() {
    0 c  M9 e, Y+ Y; S6 N. p1 {5 R        INSTANCE.beanMap.clear();# M) d* B" d+ `* F1 r0 q4 t- N% f
        }
    ( p0 K* p  B) i/ {6 J1 y3 G
    6 [' n9 h( Z- L3 B: ]    @Override
    ( j( Z$ w1 u2 ^  k+ R0 ~1 y1 `    public Object get(String name, ObjectFactory<?> objectFactory) {
    & {& k2 M. ^; y  K' O        Object bean = beanMap.get(name);
    ; t2 P/ k; e% }" b2 G$ I3 d        if (bean == null) {2 {9 ^: m+ l' x8 \$ P8 z
                bean = objectFactory.getObject();
    " w2 r9 ^+ e4 A! F            beanMap.put(name, bean);
    , e% p: T6 e+ ?/ ^. f. L        }
    2 [& H2 e6 H  E0 Y( e' I# `        return bean;1 h( [2 `) Q+ l8 w. Q
        }
    ! v) @' R4 Z: W; x. A# Y1 f- G+ E
    ( X- z5 Y" A8 n! @! v' o}
    % j' Q# Y. G$ B: }; C3 @上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中! i' t3 p5 G1 u0 y$ i9 j
    ( d9 k% H5 _: p
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    " N: b) F0 ]4 d1 x" l  h0 A  H, y: {& }3 k# W' Z
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    1 w# x) R4 Z( G& n3 {) X
    ! n3 y$ ~& E% v( c' a; a- Spackage com.javacode2018.lesson002.demo18.test4;
    4 g, b1 G2 U1 n: I0 T5 Y( u* G$ j
    & ?) }# V& b& K8 N; ~, Gimport org.springframework.beans.factory.annotation.Value;" \/ f( V# m/ K: b
    import org.springframework.stereotype.Component;* u; O( B; t) n1 V, N3 {
    ! I3 F5 V0 {/ s; i
    /**
    ( u3 l: I6 Y7 N2 B# D3 V4 t! E! ` * 邮件配置信息7 W& {% ?7 l4 k2 b: o/ @
    */4 L3 U1 k4 S' N
    @Component; w# \- R  _* b/ L* ]7 _
    @RefreshScope //@18 a6 Y4 R% F4 T& M
    public class MailConfig {
    0 {9 I0 R! q' E
    " j1 ^% W8 q- O# m* ?6 n    @Value("${mail.username}") //@2
    , j* `" p$ C2 ^8 l, L$ g    private String username;
    3 n/ M# o. G* d# |4 b  x# l1 ?9 u0 V* m  [9 E3 I
        public String getUsername() {
    1 ~, C9 n0 ?$ J$ h  u        return username;
    ; j$ ]% E/ G: o. n( A* ^    }3 C/ ?+ v/ t5 a. W
    7 _! Y/ g+ I" _: q' v- o  H& H5 A
        public void setUsername(String username) {
    8 W. N  q7 Z6 p        this.username = username;
    + b7 @; ~  n  H# p0 ~5 N7 _; E    }
    ' [" b% F1 N2 S' u
    1 M! C1 }" k! _7 c' L: N4 d: m    @Override
    ) `, G: l  n6 K* M0 [9 |    public String toString() {
    $ v/ {) ?2 C) u( \; J        return "MailConfig{" +$ W2 |" {9 U: n& B2 u% f
                    "username='" + username + '\'' +
    " a6 S% N2 X3 Q8 h) [* Z% S                '}';1 Z: V2 ~4 Z- ]/ d; y. r
        }8 ~, E7 i+ H4 ^2 e6 `2 z) Q% s
    }- G8 e2 |5 i* y: v8 y7 G4 n  p* y
    @1:使用了自定义的作用域@RefreshScope
    $ `7 S8 `. c9 l2 Q. L* O8 l7 Y8 q% T
    @2:通过@Value注入mail.username对一个的值
    5 ~$ D2 C, i! h" `' r# _. R' G5 F/ w! p/ ~; X7 C1 c
    重写了toString方法,一会测试时候可以看效果。
    8 ^" E2 k+ C4 d" n. g" o1 t' b( Z- j" E" r3 W' K+ |3 Q
    再来个普通的bean,内部会注入MailConfig+ M& r" f% a4 ?( D

    6 a/ x" u9 H- M, L  \* |- |package com.javacode2018.lesson002.demo18.test4;
    * c) F1 ^) J* h8 A. g5 B, _! [. M/ k
    import org.springframework.beans.factory.annotation.Autowired;& o& W) L, r9 O9 u
    import org.springframework.stereotype.Component;
    / H( z! i: ]2 ~
    : E, T: L& D, o; O0 u1 m( u@Component4 q) r* r' \* y$ ?; s. r
    public class MailService {
    + h, P) |. y( o% J4 i% G  X6 l    @Autowired
    $ {) k' N/ |- R4 m+ Q    private MailConfig mailConfig;
    # y0 G7 f: d, Q$ H$ g% @* D; T/ N; l" p9 z+ p9 G: y
        @Override/ a5 a  H  b9 Y( F( J
        public String toString() {
    " N& J  I5 {# [        return "MailService{" +5 `/ ^% t% l8 ?: h! D
                    "mailConfig=" + mailConfig +9 ?4 H9 T+ E& V! F7 _
                    '}';
    9 W% T- y6 u7 D) a) V    }
      k" q- @7 g/ U% G; ?/ J; c& k}# {% q0 f& n4 j# [5 b
    代码比较简单,重写了toString方法,一会测试时候可以看效果。9 u+ [5 j* u3 z, n4 w" _- g0 K
    1 D2 Y& I' o1 C1 m
    来个类,用来从db中获取邮件配置信息2 |* A. K2 j) L  g# L0 o6 o) O9 U, \
    0 q. {3 E' b" k* ?  f- \6 G6 P
    package com.javacode2018.lesson002.demo18.test4;
      Y& H9 u! N+ U6 b- d
    1 B& E+ K* U& n7 d! aimport java.util.HashMap;/ w/ S! `3 E- {6 |1 e
    import java.util.Map;
    1 o' D% Q5 S: t6 [7 dimport java.util.UUID;6 K9 ~' L# r0 }$ Z2 R

    5 r  U6 s$ o3 ^/ Z3 opublic class DbUtil {
    4 m) C% [& W. ]. K! x- _    /**6 l% p6 P2 X6 y6 ]- J% y
         * 模拟从db中获取邮件配置信息
    * l9 ]; m- U2 j2 F. N( J2 }     *
    / |3 R: h7 X& I     * @return" l$ G. d: H& @4 A: A
         */. e1 i5 q8 M! o3 y/ {$ h) C, \& U
        public static Map<String, Object> getMailInfoFromDb() {
    - ~: b7 g6 h& i$ l  |+ t        Map<String, Object> result = new HashMap<>();
    6 V% l7 A& D6 B2 S2 ?/ q        result.put("mail.username", UUID.randomUUID().toString());  x5 {. s! [' u5 D/ \# [8 z
            return result;+ T+ @6 w3 M- S. j# C' B$ O) |
        }
    " }0 _; K4 l3 w}2 q  i7 m3 W0 U( Z( [: g  {/ `6 \
    来个spring配置类,扫描加载上面的组件6 Z- y8 w3 x/ p2 Q

    ) h6 C7 P4 y3 E; G: c$ E$ Y8 spackage com.javacode2018.lesson002.demo18.test4;
    ) f; t& s8 I$ g; R) Q  B
    / i3 i# m) a- H6 rimport org.springframework.context.annotation.ComponentScan;$ Y4 u2 j3 _) V6 U. b1 m9 o
    import org.springframework.context.annotation.Configuration;( M! P. K6 i6 ]4 |4 E

    , j! B6 G2 G) h4 o@Configuration$ g, x; x7 G6 `
    @ComponentScan
    9 B6 z$ B- S* Apublic class MainConfig4 {
    7 b3 v2 H) ?$ l0 I}+ U/ c. `+ W$ t) z# M; F( V  d4 m
    来个工具类8 r7 `: S% c' d
    * s. p! P" l7 [2 g; y! `. L
    内部有2个方法,如下:3 d: E: M3 o, p/ \

    4 y+ \2 p, M, c$ Jpackage com.javacode2018.lesson002.demo18.test4;
    3 g( Y: q' e8 j. F, h. y; n0 o
    . g3 g, J- E" G$ n; `" Simport org.springframework.context.support.AbstractApplicationContext;
    , a+ i- ]: V8 ^1 fimport org.springframework.core.env.MapPropertySource;6 {2 d8 s+ I3 s5 o( R3 z" ~

    2 ~7 R( {3 N1 c+ _, k2 L  `import java.util.Map;) ~4 t& b/ h& D( \
    ; S1 }& [7 ~. D3 r  h1 z9 ~' S
    public class RefreshConfigUtil {+ T) k; V5 X/ p6 ~# G' t8 }0 Y2 [
        /**
    1 |' M3 |6 i" P3 P: V4 t, w     * 模拟改变数据库中都配置信息( N! }5 ?* P: {6 \' X/ y
         */
    ; g5 q% d0 o* [& q! C3 X, j1 q: ?# F    public static void updateDbConfig(AbstractApplicationContext context) {
    ! y' ]' l" x! y( n8 ~        //更新context中的mailPropertySource配置信息  u0 D: k: n; i7 p4 Q3 v
            refreshMailPropertySource(context);) ~4 t0 G* P) ]

    4 h9 o6 f2 t& k  R7 S        //清空BeanRefreshScope中所有bean的缓存: L- P) w% j, n5 w
            BeanRefreshScope.getInstance().clean();/ }; j2 m% b* P1 `- s, J' r" h; p% ~
        }
    0 u" _6 x7 i2 \2 ~, b. E( E% l+ w4 V; b% H7 t* M) m- V' H
        public static void refreshMailPropertySource(AbstractApplicationContext context) {; `( o. P4 `& y0 z; d2 K; @* c
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();& `: p1 e! {' b4 ]
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类), e# @0 R5 i0 A. j
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    * b# c' {! N8 K' F' [- w" i        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    $ X/ [& b8 a4 k- x; x- T    }# P' \4 T, O) B' Z3 S+ w

    6 d1 K( O; q* e3 i0 r}( k! S' i. O) K3 J7 n- `. H
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ; x# K$ c8 y6 N  B( w
    7 b' i4 e) c, t/ z. @2 DBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    ; }- H9 Q9 u+ B* Y- B. m5 @3 Y0 P! }8 R7 C+ ?1 @9 M/ L
    来个测试用例4 Q: h+ V8 |$ t- ~  @- F

    % y8 w: g( l, j& C3 Z+ s7 X. f8 t@Test
    : V. Z3 i3 u9 |  t  V" q: {public void test4() throws InterruptedException {4 B5 a. w- l- p0 F
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ; K9 i$ i: d5 ?2 \* u    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());" S- v$ X) V- p: a$ K6 ^5 C% Z
        context.register(MainConfig4.class);; T4 t& b' y% m2 C
        //刷新mail的配置到Environment
    1 K! v. j5 T$ Q! x+ X: h8 M    RefreshConfigUtil.refreshMailPropertySource(context);5 J$ g$ I1 ^  W; K8 r3 t
        context.refresh();
    ( q) z/ u7 H4 f1 V: A& K& a7 I* k/ D8 s  F
        MailService mailService = context.getBean(MailService.class);
    ) u8 P8 j$ A4 V) c1 U5 @  u0 N    System.out.println("配置未更新的情况下,输出3次");
    & s$ z$ u) E7 x/ ^    for (int i = 0; i < 3; i++) { //@1
    ! ?% }  K5 ?1 ]2 a, z0 k7 |        System.out.println(mailService);/ n; h0 a) @' v% m& F& g
            TimeUnit.MILLISECONDS.sleep(200);5 O8 i# y  K! F/ @6 b) \
        }5 J* o$ O9 o- c# z4 \: f

    6 B/ g' w* t, V    System.out.println("模拟3次更新配置效果");0 H; i- {0 F" H( ?* e4 u* f
        for (int i = 0; i < 3; i++) { //@2
    - u/ H: j- d- g        RefreshConfigUtil.updateDbConfig(context); //@3  }& B. t9 ?) `: A0 I! _
            System.out.println(mailService);+ i* y5 f7 y6 @5 [- Z
            TimeUnit.MILLISECONDS.sleep(200);7 K2 `$ Y. q% Y4 \0 {% ^
        }1 [+ ^7 H3 D4 U% C
    }# ]$ Q7 c# W+ Z8 E
    @1:循环3次,输出mailService的信息: x+ l3 Q( }2 _8 y0 ~$ q/ a5 `+ n& Y

    ) a. a) w" w% O; }$ @( G) s@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    0 D7 Q( g2 B+ V% W4 D  C: I* B( a" U* v7 @3 V. f+ _) L9 d7 |
    见证奇迹的时刻,来看效果
    5 T% ^% Y( Q. j/ {  q) G% D  W; s5 J& n0 Y
    配置未更新的情况下,输出3次4 J- N: r+ y/ O8 x! x
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}6 r* ?1 [% h  w4 A/ x
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    & R7 s9 j' `' [8 K' h) dMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}, k9 K+ W! [! d2 l5 c" q% k# E
    模拟3次更新配置效果
    9 K  I' M' a+ w( ^* n5 s' fMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    5 {7 h3 g# ^- S, h! _8 X6 F$ Q( PMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    ) W  w/ b( A: c* `MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}* e! i) }' T7 O
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    . v0 r, a! W+ g) W9 g
    6 n9 e/ P2 O; V0 m小结) x: ~( l9 A* z! l. J  t6 O; [
    7 S3 u! J7 Q4 K5 i
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    ! e5 {! G0 N: B2 x0 V  {# @" L; C
    2 M( D* ^* @# J2 D( g1 i1 j有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。. J* a  o2 G2 n6 V- F  ]
    2 [: a; Y, W5 ]! a) d2 [
    总结& i/ j- [  m0 {+ W8 K
    1 U$ e& i# Y1 H  V3 Q' q
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    + r# T4 T/ I, Y7 z5 ?
    1 J0 d3 k" b3 G, B- E案例源码
    " H1 F# w# ^. F) e
    $ l2 x* j, Z5 }4 }3 Z; G  Ehttps://gitee.com/javacode2018/spring-series
    8 ?; T: v4 _- H路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。! m, ^9 z. g$ v5 w
    6 R* Q. T2 W3 p; c6 u* N4 v
    Spring系列
    - W$ [4 z* l" |+ l! F, ]- q4 H( M* O# @
    Spring系列第1篇:为何要学spring?
    3 W& M% |( H0 t2 D2 h1 G$ F# C# g' S0 u" j
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    9 a# Z6 p' V3 \, o. ^1 L3 l
    ! F& N1 O$ H0 z- s9 cSpring系列第3篇:Spring容器基本使用及原理/ K) g) ?9 _+ q7 G% L) M

    . W  R7 }) G2 H, USpring系列第4篇:xml中bean定义详解(-)
    % t4 e' k# ^9 @  j2 ~& Z/ w
    9 V0 A. q7 ]( c: v8 c7 \5 s+ gSpring系列第5篇:创建bean实例这些方式你们都知道?6 [8 T9 j# o' e) |8 _
    ! F; m1 w' d- g3 c( @: W+ v
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    1 }* O. z" k0 u
    , K& M$ u$ R  ~4 zSpring系列第7篇:依赖注入之手动注入
    " P9 _7 w4 R, Y  I) x& m( i1 O# F$ z7 n4 w
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持. L# f+ {. z/ t) v2 J6 ]
    ' f* _, y5 c! C
    Spring系列第9篇:depend-on到底是干什么的?- Q+ f) @2 V# r. ~0 m! d

    8 T5 E0 V  `7 zSpring系列第10篇:primary可以解决什么问题?
    1 ]8 N. U, e& A6 \; V4 u; q+ w
    : \5 S; [+ V* o; [; OSpring系列第11篇:bean中的autowire-candidate又是干什么的?2 {# w1 y( {! Y( G' g) F
    % z# m& w  q( Z6 Z# i
    Spring系列第12篇:lazy-init:bean延迟初始化
    / K% }' K8 x6 `" j9 U2 U5 |0 A4 _0 L0 k7 G& P
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    / j- c: |8 W3 O* b( x! s2 A# j/ s" J. J& s; ?  q
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?' j# b" P; u4 C, {+ X: W" _7 y  v
    ! _$ j4 Z; T# a! p
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?' x8 k7 t! v7 g% G) P+ d7 U4 U

    ; v. C7 ]+ A: a$ d; M; R: XSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)5 C) k7 V% Q  J' \9 Z% u( g. u

    1 Q- x0 m2 s$ E* Z+ V0 JSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)# `# L! u6 M. h0 Q  A  d/ v
    ) F( y# I' B- I% ^% I! X
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    ( W" m. w# J" y' o9 O. R& v3 s; e, }" t0 ~) \  u9 z. e& D5 Y
    Spring系列第18篇:@import详解(bean批量注册)1 x* j3 \/ a; E7 d* J% j7 r: O7 p

    , d/ {& T" t9 ySpring系列第20篇:@Conditional通过条件来控制bean的注册
    5 H" ~9 c3 f9 n/ ~* Q$ g
    8 B+ t8 N" m3 |% [0 rSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    $ c: E5 e, \6 R. \% I* i
    + h+ I" Q0 N: K, j: w. h0 nSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解  k% I5 ]; e- n! r' b2 j

    3 H+ k) y9 X4 ]  o1 ^- mSpring系列第23篇:Bean生命周期详解
    . ~5 S/ A7 N6 p8 M8 z5 k" [' [' L2 l! X0 K) Q5 J& N$ Y( V
    Spring系列第24篇:父子容器详解( g, k, Q0 c" g, B1 a, U+ n7 X$ g

    + U+ |4 C! O/ T2 r4 ~: ~2 D更多好文章6 J8 G) s; {/ y% N* g- r4 K" C+ w
    7 A! n5 b# a, v, c& f5 `
    Java高并发系列(共34篇)
    $ y; x- [1 g3 j+ S: f* O/ X! V) m" i4 y. [6 j( R6 e( P0 S9 p
    MySql高手系列(共27篇); b" p, M; i* G& d: K" p0 [1 x
    : N# r9 f% a- r# c( h
    Maven高手系列(共10篇)
    * R3 o: a$ q9 j/ _
    9 B1 D5 P" ?) B" A7 H: T; F7 ?Mybatis系列(共12篇)- I1 S0 h& T: f5 z) v
    8 A& }3 D/ M" B0 F) E! s$ h( |
    聊聊db和缓存一致性常见的实现方式1 @" ]) E* m" Q
    : z6 l1 E& c% S  I
    接口幂等性这么重要,它是什么?怎么实现?6 b% ~, I6 I# ]; X

    " v; U; Y9 i6 {* _* c泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!( {' B# N8 x& Y0 j
    ————————————————- G" M) i, I3 ]9 B' K) C
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    0 L; k. X" R, }: r" [4 E8 P& P9 _原文链接:https://blog.csdn.net/likun557/article/details/105648757
    / b2 z$ [' [: I8 x) L; H
    & [" W, x4 e: D# W( a
    ) o- A- @5 M2 ~% ?$ H
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-6-11 09:24 , Processed in 0.485859 second(s), 50 queries .

    回顶部