QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5401|回复: 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!
    6 J5 K7 p3 |  g" W0 C4 I疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!5 P( M+ Z3 ~" L6 ]" M# @) s
    . Z; j4 S1 O& _' T/ n. E! H1 K
    面试官:Spring中的@Value用过么,介绍一下
    6 ^6 l) w: h. ^# l6 |7 S& W/ d, b, x4 E5 M
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中3 n0 L4 Y% u* E

    * A# b% q; r0 {2 |  `面试官:那就是说@Value的数据来源于配置文件了?
    , d4 w2 h: U$ o$ X* Y1 a  I
    - C& L" o* F+ E我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ' h- J' \1 q! j- `+ K$ X' R& i4 `3 z1 ^
    面试官:@Value数据来源还有其他方式么?, u6 C; r; [. n0 G  L: K
    + R$ d4 x, J6 N/ x+ u
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。9 u) f4 v/ w6 ^- F7 Q

    6 T& s) y5 d& N  F+ ]+ u面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    $ k0 q1 q; [! A+ _6 F
    " K8 _6 i; a0 j5 ^9 V1 f我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    " A1 G, d7 k- _$ g& n$ B
    ) `! T# o% a# j" E0 F面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?. C' n5 B+ D4 v* r( ?/ E7 B
    1 g' ?. N/ j: R8 i
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    6 T' J$ t+ u6 Y7 i' Y! ^( P) H5 \1 m
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    8 h" h7 d; }$ c. O- F% Z: N
    3 n- `# H, Q, d7 J$ _( k我:嗯。。。这个之前看过一点,不过没有看懂' A& U4 a) h( G$ ?
    9 g4 E4 H5 I. l7 u. j
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    : x. w2 K" H! n# i% N
    : v7 w/ H4 U( T我:3万吧
    ! S/ }  _( A0 ~; D# S3 r
    ( J* d3 }9 j6 {  l面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?* Q; L9 A$ l5 E8 W0 H

    / ^- t, `  x& z3 P8 k" b我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    # n1 U, X1 I5 z
    7 w1 t4 i/ Z6 Y$ D1 A! u面试官:那谢谢你,今天面试就到这里,出门右拐,不送!( |, Q- ~  ?9 b9 K

    # |- Q1 p2 q/ T我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    6 Y; `. u7 W* Y" A" m3 I4 N" |  Y1 v/ O0 p( n3 R2 r9 k" X/ ^
    这次面试问题如下3 _7 ]' G# F$ z

    % l* s5 L, x4 B5 ~% ~5 s@Value的用法
    3 k& y' s1 J; y8 A" }2 [. m
    ) B# ?9 L# C; P: [' ~! ~) |! L2 m@Value数据来源! ?; K7 B. K' \
    / \9 ]' _9 w3 V3 i' ~( {
    @Value动态刷新的问题
    % ?8 F* f% J" `9 ~! P  m$ p+ w: z
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    0 K' E$ @3 X% B- P% N0 `$ ~$ Z7 P% T4 [9 g: s& U
    @Value的用法
    4 H' w. |  X3 ]  d8 Y5 q1 h- q7 S! z$ }. E, V  U( J5 X
    系统中需要连接db,连接db有很多配置信息。
    : c* a6 H9 W/ a+ @1 J8 g: h2 J5 x
    / \/ u) m0 f# H; L! T& j系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    8 A7 m0 V8 R6 b2 Y- R; l4 J8 P0 }, q' J- {: ?: s/ M) {
    还有其他的一些配置信息。
    " W' O: s% S" [7 {5 l6 h" s" |( q
    " w9 D3 v2 ?/ F/ P3 T5 }我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。7 \: V0 u/ b" U: Z4 a/ U" D

    ! P; Z% z/ y" @9 }. }那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。5 C+ ]. h0 z2 [3 V4 W2 H9 L
    . S) }' s6 z$ E& W% a# L
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    : W( @" K6 X4 L& m2 @" Z( P6 o& C, V$ ?
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。1 K8 p- d: t1 E1 `. w
      ?- i7 {% [9 A/ r# j2 C! A9 m
    @Value使用步骤  N5 K" t8 B& d7 D+ s$ V* a$ K- ]# Q
    % b7 B/ W+ Q9 O. ^3 S* m0 G; g
    步骤一:使用@PropertySource注解引入配置文件
    2 n3 n) Y6 ?) W3 i' o+ a$ \0 Q( H! Z9 o4 x! @
    将@PropertySource放在类上面,如下/ i  ]1 h. F; A" [
    . b9 {5 F0 l! c, B: b2 c
    @PropertySource({"配置文件路径1","配置文件路径2"...}), n: ~8 ~  o. H' `! F
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    8 C* r$ G- v: L0 `- d" v0 }  m
    ) l6 q9 O& V* s! o7 W如:2 T' B6 H+ s+ o1 c0 F

    / q  V: s( t/ g; S3 @: [) L" _@Component, U7 ^8 Z7 {* `' q' ~6 W; Z
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})9 A) z" A. p( ~  g% K9 K; D
    public class DbConfig {
    # Y! l4 Q" M! R}. q! K/ `4 ]4 p! l
    步骤二:使用@Value注解引用配置文件的值
    1 ], p+ o  k2 v3 I0 V, k
    ' ]( |8 j: S+ |4 P通过@Value引用上面配置文件中的值:% |% U$ h3 t$ G) K8 U
    : }5 N, P0 T, h& T
    语法! Q6 k! j2 n8 n# c7 _, m
    / ?. k" U, ^# l2 D* @( H' T' t7 ~
    @Value("${配置文件中的key:默认值}")1 d0 f# l1 }  ^
    @Value("${配置文件中的key}")$ W+ t  F- p1 C/ M
    如:
    ) I3 U9 M' n6 O7 V! v. O: {$ N$ ?0 u1 |  p" S8 A1 V
    @Value("${password:123}")
    4 D1 ~) @9 _! ~; U* H+ l# O上面如果password不存在,将123作为值
    $ h8 U& s: K+ v3 Q0 v0 d  B) m! ~4 n, k
    @Value("${password}")
    ; S* V9 l" S, y0 e. h上面如果password不存在,值为${password}  w6 l, n, R3 b- M
    . g) l) m" ]( I3 W$ V+ q9 a; [
    假如配置文件如下9 m7 q2 S7 ?& I) i, B
    $ H1 A0 l7 u' _3 H
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8, @" E9 [6 f" E, d6 R9 c
    jdbc.username=javacode" i( z% A8 f6 V6 W
    jdbc.password=javacode
    # D, _. e4 s* f& ^7 `. I$ k: ~: G使用方式如下:  D( s( {' H4 C) e: v1 c0 q' o
    ! ?# {- Z  p+ U7 K
    @Value("${jdbc.url}")
    ' p  s( ~0 E% W8 jprivate String url;& p, ^% a$ @- g% t, G

    , h2 U+ d3 Z0 ?7 C% N9 `! i@Value("${jdbc.username}"); l, L! n% v9 @2 g# y& c& Q8 G3 V
    private String username;" B' n! R) l5 Q1 P* v

    9 j0 I4 B  T( m. Q' \' R$ M' U4 n@Value("${jdbc.password}")
    ) ^4 \' T* {6 Y+ b. Z& `& ]private String password;
    . p0 i- x0 R* G& u下面来看案例# x" d' q7 p& j
    * X; \9 P) N) x; h" ^4 i
    案例1 j9 e7 ^+ ^7 |

    9 C' i& k7 T8 p% \( V, k来个配置文件db.properties$ B9 s. r! f9 s& p( z. E6 h6 E
    + Z6 N$ d1 ?4 _# b- Z/ A/ Q
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    4 B: C6 s6 L; X$ j; Zjdbc.username=javacode: s2 }- M" U+ G
    jdbc.password=javacode
    6 a4 o: v5 M, d/ H" l来个配置类,使用@PropertySource引入上面的配置文件: w- u- l: y+ u3 F. ~: H
    $ O$ J- \% Y% Z/ @# y/ q' d
    package com.javacode2018.lesson002.demo18.test1;
    3 u, j( u# I5 l  d& V
    ! x! `2 h2 f4 }import org.springframework.beans.factory.annotation.Configurable;
    7 l9 W! Z& j5 ^! d) _" uimport org.springframework.context.annotation.ComponentScan;( u1 r* g/ {2 D/ W7 A! N# p
    import org.springframework.context.annotation.PropertySource;
    ' P# I- [! o: e( {' g6 p. G+ [: I% Q# k  I$ _  ^* o" T
    @Configurable
    ' N& j3 Y- Q7 d& T2 A2 X@ComponentScan
    % R5 q- R  i1 `. E/ ^@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})1 m. l; X* H/ F* H
    public class MainConfig1 {
      s  j# C7 [( U7 c: a9 A}; O' o. L' s7 Q( L
    来个类,使用@Value来使用配置文件中的信息7 i! `& j8 D5 h* p# r! {
    % _1 j' x6 ~& e$ D+ A
    package com.javacode2018.lesson002.demo18.test1;
    4 z) x" E( j& I/ D
    , v; l& U5 {; ]: kimport org.springframework.beans.factory.annotation.Value;3 h7 l& Z9 k. R# ]& N; t# ^$ E
    import org.springframework.stereotype.Component;
    ! y+ C$ E5 Y/ |! `' V
    + m+ A/ G: r. s. C8 k# T@Component
    : X- Q8 d3 i/ Wpublic class DbConfig {
    8 z6 D; z" G: Y' S8 @9 g" j$ e, F# W5 K4 v/ ?# [
        @Value("${jdbc.url}")
    ) Q( m' \3 Y, Y    private String url;
    , [( A/ b! ?" ^0 I+ C# m/ F0 a
    $ ?8 |" Q( k2 c5 H, g6 I( w    @Value("${jdbc.username}")
    4 _& o9 n( S/ l6 g" |1 H: |$ ?    private String username;
    ) q1 @. H3 I0 u' N: x7 i/ s5 W
    2 z5 o; I) N" K( r    @Value("${jdbc.password}")& v) [% N3 v! o; V; D' l1 |6 F
        private String password;
    , O8 N" o5 t2 J: Q) k) D
    / }4 L* V' z, D, I0 G- A    public String getUrl() {
    5 q" F' Y( ?, y; V        return url;
    ( k. K# Y. y6 M4 @+ _, n- @& r    }; Q3 J' e0 Q+ x; D: }$ t

    $ _$ \/ w  t" K  g5 L$ g. ^    public void setUrl(String url) {+ L/ K- l! q# Q9 ?
            this.url = url;; Q& F  K! @: X$ P1 z
        }
    ) F* ]: ~$ q7 A6 J# F3 s3 L- [( b& T# s
        public String getUsername() {; A0 Y+ e8 w0 H2 U' E$ \
            return username;
    4 c; h! l6 B. P1 m- W  m0 V8 F/ q    }! `0 A  d/ l* s

    % \" e5 p0 N1 B- U    public void setUsername(String username) {3 E3 r6 S* k$ u; a+ O0 O3 ]3 e
            this.username = username;
    . G7 M  B* `  c& `$ {    }7 w6 ^$ F, _4 \
    * p8 I. Y. Z7 u3 v. ?
        public String getPassword() {
    8 o% S: E1 J7 C: S, z- b# y        return password;  ~, W) r& b" O/ y, \5 `/ x$ G
        }; i  V: U4 T0 y" O
    7 k2 _: p1 }9 S
        public void setPassword(String password) {
    0 D9 K, {2 `% X9 z        this.password = password;( M* V8 f2 C, }
        }2 V1 [7 L* W" V. C, g

    * \& x! q5 V4 d    @Override1 N1 m% a: @0 O, R8 Z/ K
        public String toString() {# \1 z6 j, o$ j% e
            return "DbConfig{" +6 E5 p/ d9 `/ C5 r, y
                    "url='" + url + '\'' +2 F$ K* @4 a7 y9 V# V3 v/ t& P
                    ", username='" + username + '\'' ++ b5 g) m( q' U, l; ?" z. p, y
                    ", password='" + password + '\'' +/ R: c, ^8 t9 a& ]3 K3 V
                    '}';
    0 ~9 |$ j; t: f+ i5 J9 Q    }# C! D; q) F0 g9 Q$ n. q1 A
    }
    ' T2 V, E) n# q上面重点在于注解@Value注解,注意@Value注解中的, N! b, a7 ~$ n9 s3 ]; C
    ; }8 q+ l0 X) M
    来个测试用例* i/ f$ d: {0 A2 i# P* s

    + k* ~2 o. D5 G; B: Fpackage com.javacode2018.lesson002.demo18;) P- D4 x3 @, }" j5 s0 ~
    - A9 j; ~5 X2 v/ K( `
    import com.javacode2018.lesson002.demo18.test1.DbConfig;6 x. @0 w% ?1 x8 z1 M6 \- C
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;$ x( e# \8 h8 r, w
    import org.junit.Test;
    % Z+ X+ m+ ?7 U* {1 E# Wimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    7 J- [# I8 S6 c+ s/ L
    ; y8 s. Y. X2 Npublic class ValueTest {3 p0 n: h" U. a# g( [
    ! \9 X; _& I$ ?8 x) ]0 M; j
        @Test
    8 c4 ]3 {3 {) p( d3 l& ^    public void test1() {
    ! D# E( n6 S( X- K+ J# |4 _' r: ~        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    0 {( a) F% R) D! R5 `        context.register(MainConfig1.class);! O/ a9 H2 n: U, i! J" B
            context.refresh();, v9 p9 m3 j; L4 }- n% h2 G

    7 X8 ~5 ~: ?9 @6 v9 H        DbConfig dbConfig = context.getBean(DbConfig.class);
    1 K/ x+ Q; Q# b3 q5 j1 k        System.out.println(dbConfig);5 c1 `8 g, a. g. l5 Q
        }
    " x! Q1 x( L; \" Z6 g8 p}
    ! M4 |5 S3 \# |& S运行输出8 V9 s3 ^8 q/ N) Q

    ' u9 t2 D% i. u. H/ p9 [DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}. M: C. @/ a6 Y+ w! {4 j: v* S
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。/ x' X- t) V  u# v2 d4 O
    ' J3 h& w6 ^% Z1 m7 }6 ^
    @Value数据来源9 l" Q: X; D- U9 k- u
    8 d3 K! M; Z4 z) k( u  ]& a1 o
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。$ l+ i3 d7 ^) |# Y+ r
      I" E1 d4 C" ?& o3 b
    我们需要先了解一下@Value中数据来源于spring的什么地方。
      ^4 U, w4 P. k8 ^( u0 k) S
    $ @) K' X& j- R# ^" a2 {. _' Bspring中有个类
    ! i7 _6 h4 m" }7 K1 `: S' U. k  y" R" n/ ]2 o0 d1 \
    org.springframework.core.env.PropertySource
    9 X+ @2 l+ E5 S可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
      z8 e) l. U  ]8 N+ w. y
    ; c. `: q) M* Q. @& C# g内部有个方法:/ q/ Y+ i. h2 C8 `0 U! E" G
    5 L2 d' R0 U$ _( [5 ?& v
    public abstract Object getProperty(String name);
    2 e0 @" _9 e, I6 }6 k  y* [/ U通过name获取对应的配置信息。
    & ?' P2 J, ^; ]1 W) v. n8 c+ R
    + e; a; p. V; b* Y# ^6 }系统有个比较重要的接口; @3 j( J( ]% V- d+ n$ i$ X

    $ c0 w$ A# k! @7 m6 p/ q! [. Y6 d; korg.springframework.core.env.Environment
    1 F% F1 x) Z3 _: A/ S, d9 T, @用来表示环境配置信息,这个接口有几个方法比较重要8 |# H& K) {( k1 m) b% T8 `

    / a  h1 ^0 L; J+ }2 NString resolvePlaceholders(String text);
    $ p3 M" ^+ Q3 B/ h/ V8 LMutablePropertySources getPropertySources();8 r3 i7 r* K8 P
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    8 O1 ?/ }. t2 \
    9 j7 x1 m! \7 S6 f& K( agetPropertySources返回MutablePropertySources对象,来看一下这个类: ~7 d) C8 ~' G
    5 b2 P& T- J+ ?
    public class MutablePropertySources implements PropertySources {
    ! p( s1 q+ J" E
    8 C3 t1 V- |! _! Q    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    * A0 P; Y* I4 i% i
    2 X; G$ A3 |, r/ e$ r$ o2 D}( M4 |: s7 H3 Y/ w: ]5 o
    内部包含一个propertySourceList列表。! d* t" D8 W6 p; _) ~2 l; s
    ; t8 c, @4 p& u# H4 \2 i. I' D' h
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。& b  p3 H- Q) p
    & C# I3 }$ g5 b! _, |
    大家可以捋一下,最终解析@Value的过程:2 p- X7 a7 c; n$ @2 L$ C

    ' Y- x3 s8 ]* L: H2 d, z' Z+ k1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    2 u: @" n* _$ `# R3 g8 j: Q2. Environment内部会访问MutablePropertySources来解析  ]' n9 r5 f# e5 x
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    . z. s! @8 L! F% c2 B通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    3 q" X8 `) e/ C/ K4 V$ ^
    " m  A0 p" X$ G4 Q' B0 K下面我们就按照这个思路来一个。8 |" E  \6 g- s4 A- T
    6 H- o- v; V# `9 x
    来个邮件配置信息类,内部使用@Value注入邮件配置信息) K& E3 m& D2 h0 f# O5 y6 q7 ^

    8 ]: [  q; e4 S( wpackage com.javacode2018.lesson002.demo18.test2;, N8 a' L, H( x$ h8 o0 d0 Q

    : x0 d3 k) r6 W% p# \, T/ ~import org.springframework.beans.factory.annotation.Value;- `" z, P) J. E$ O! D3 g( N
    import org.springframework.stereotype.Component;& ]0 J9 f5 e6 _+ S- i7 q3 Z
    . O" B- L* E  N* I& v! \4 o
    /**) q9 @2 f4 S$ b: a
    * 邮件配置信息# a4 d$ m+ C5 C) G( I- R/ u
    */5 @. g$ e; N: I( l. e" S( L
    @Component
    ( |1 K7 u6 i0 j0 ?6 npublic class MailConfig {
    & o6 S+ Q. {  {1 J  q: U
    & ^+ ~8 b' v4 M    @Value("${mail.host}")5 R4 t- L, M, J, n
        private String host;
    % ?, U( m& |* k* Z, \  N% \, a! k: c5 ^$ k# A
        @Value("${mail.username}")
    ( k6 D! M; s, y8 X$ G1 h    private String username;
    % t9 u8 B7 v1 C- q/ K
    3 J& ~- ~0 T: L2 J    @Value("${mail.password}"), \4 o' e0 y* _, U( p, y! n
        private String password;. Z# B% q8 K1 I: @) Q! v

    * @3 H' ]: D) ]( S6 L6 Z    public String getHost() {9 O: |8 C# |. l! C
            return host;% ]% ?% }. W+ w. @, Y
        }- w$ J1 |8 O$ c) \. n' I2 }

    / W/ r. |) u, s# b& c    public void setHost(String host) {
    0 Q) ?( p0 l# c9 u  R        this.host = host;
    - r+ U# a% J+ P" {/ d    }
    8 G6 [- e9 a' P  z& l& m1 {+ \# ~% }: l! r- T. J. P* }7 |, E
        public String getUsername() {. o. E" P$ S4 b2 p- i
            return username;
    & ~; ?( G  h( s; V. Q) s7 L    }
    6 W! m! e/ N7 M% d6 N8 D* h. F' ~: m5 Y8 p0 m
        public void setUsername(String username) {
      w  ]& _, `/ ?) C- a        this.username = username;
    . N* C, k2 l" H2 \    }
    9 j2 ]7 v7 F) r* [5 n0 u
    : w* l" P9 a' O# h8 _    public String getPassword() {0 [1 E0 H7 f! i- L5 X- d
            return password;
      E& i$ s- U* D7 A    }
    1 i$ W2 U7 C: w4 e2 g6 J* x, j
    + v" J/ N" |& [: m- E( U    public void setPassword(String password) {
      i& \7 q) [3 j- @& r0 S        this.password = password;, n$ \. S7 a. @) f7 m9 N! Q0 g
        }/ {  j0 F5 H$ {4 g5 o8 G8 T

    * ?$ H8 e5 E( a5 x' m) z    @Override
    " ]: ]6 O  S4 n' x: r1 z6 f    public String toString() {$ L, V' m9 x/ w$ t
            return "MailConfig{" +
    - {9 g8 w$ s: }8 {% b4 u1 h                "host='" + host + '\'' +
    ' B2 u- v; d) A: O* n& L                ", username='" + username + '\'' +* B$ K$ Z1 l7 P
                    ", password='" + password + '\'' +" P. x& ^; `# [* g
                    '}';
    9 u/ n2 H1 B: r, S    }
    ; i- t4 s' r; q+ ?# u$ J; D}- U2 X5 V1 a" h+ t! E/ j
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中# Q5 k% `, [6 g. B- w* D3 r: {
    " f4 E# _7 s, S9 ?8 ?6 g; L
    package com.javacode2018.lesson002.demo18.test2;
    ! i2 s% k  L6 x1 r" h: h$ g1 ^. C1 _( }  Z2 T! E
    import java.util.HashMap;9 M! e; [( P- B2 k! U% k! e
    import java.util.Map;
    9 G: n3 X: {. U: Y$ w
    % |/ Q" q8 G4 v9 |% q9 ^( @. O; Q2 M. e" jpublic class DbUtil {
    7 B, v( z: a' l- I) Q    /**
    : r7 T9 M% o( d# u8 [1 r$ s     * 模拟从db中获取邮件配置信息
    + j/ j: f1 Y5 Z# x0 [. F( l     *
    9 U3 j6 o3 x. n1 j9 b! }3 D5 O     * @return
    - t: |1 z+ M) k2 l     */  T) L- Y/ s0 L% x  I
        public static Map<String, Object> getMailInfoFromDb() {
    . k; S* l7 {: m4 w        Map<String, Object> result = new HashMap<>();5 Q& E' C6 _" l+ A( J
            result.put("mail.host", "smtp.qq.com");* W8 x9 w3 x; P2 Z  ^" V' N# r
            result.put("mail.username", "路人");: V, H7 a$ P9 D1 P
            result.put("mail.password", "123");1 v0 q; @0 @" u- h: u
            return result;* A/ x6 j- S" c/ ]' l6 }
        }
    2 F. z0 }- C+ G9 E) ~}
    : i7 L; ?- t# v* a/ O: d9 R来个spring配置类
    2 Z1 o1 c& O* @" ^4 n$ X& k/ v0 f8 c7 @
    package com.javacode2018.lesson002.demo18.test2;
    + O" Z3 e* u& _4 B: S$ d: p( B+ v; ]9 r+ K  u9 I
    import org.springframework.context.annotation.ComponentScan;8 e. B8 z4 Q" K8 S1 z
    import org.springframework.context.annotation.Configuration;
    ; ~: p. ~5 L) n6 C
    ( w2 {8 K, t' x5 E! y@Configuration$ _3 s$ c& ]8 k* E! A
    @ComponentScan
      S0 G5 F* J7 n- K/ x- R3 v6 d# qpublic class MainConfig2 {! t" P# t! c7 q! n
    }7 a8 i( S& o, k% [& k1 i
    下面是重点代码
    $ A7 k" F, g" u! \. B, s1 g2 U2 C
      \/ p2 N* w. k% N  P( n@Test
    7 `# [4 q7 Y& Gpublic void test2() {" G6 J" K% d  ?8 I: c) q/ u  c; K
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    2 z7 U" n" Z5 w# R- m+ [) P0 \1 A3 ]) B
        /*下面这段是关键 start*/
    9 S8 S3 u- h6 d$ u) {8 V7 `    //模拟从db中获取配置信息
    " x# D# P7 k  u, t' V    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    / ]. B6 x8 t' ?- v6 ]8 O' H; w" C    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)# R6 W$ o7 U* u% o+ Y
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);+ _& [+ n* C+ j1 g# c0 Z6 o0 O2 x
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高0 O. r- h, i  E3 c1 j
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);# S% e3 P4 m7 u9 \( a7 }/ P+ I* E- y
        /*上面这段是关键 end*/- m2 [3 K% G; T/ W& {; ^
    4 c" d2 Y  A$ P0 {$ d
        context.register(MainConfig2.class);8 S; l7 _& D; u7 E% [3 |3 C
        context.refresh();- m) e- @3 r5 h  A4 q0 w4 A5 \
        MailConfig mailConfig = context.getBean(MailConfig.class);* E" R" z& s2 [+ C- N" e. P
        System.out.println(mailConfig);
    . u2 Z" s' |% ]9 T' H}+ S$ ]) R7 J1 k' K; O
    注释比较详细,就不详细解释了。$ M& B# I( n$ z4 a3 H# l
    " e. j0 |$ B" F( ~9 ^8 N4 h8 b
    直接运行,看效果& q6 a1 ]* L/ c9 Q. G
    % W- v9 G4 @4 Z5 k# F# `6 b" p; a
    MailConfig{host='smtp.qq.com', username='路人', password='123'}5 O7 Y; L# D5 s# T/ S; h* l5 _
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。+ Y6 E* @1 N" p5 v

    ! z3 _  h# }- X; B上面重点是下面这段代码,大家需要理解7 b! \* z: `- x" C& T3 W- V$ O
    ( S( L& M  G3 _3 c# t1 ~' j- b
    /*下面这段是关键 start*/6 ~: ?+ _+ s/ l4 U- H7 t
    //模拟从db中获取配置信息# y8 P$ h1 _9 Z( i, b
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();3 ?8 u' k0 l0 G$ y9 l6 l
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    * k) {# j: s( T) w' PMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    2 E% z$ L) S/ d1 L+ i: c5 n//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    : H4 A9 l: Q1 q. k# W$ {$ Bcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ' n6 o% Y& Y# W4 Z5 r9 G1 V/*上面这段是关键 end*/
    & [: _* ~; A9 I# |' ^% f) |' H7 v咱们继续看下一个问题6 i( G4 u5 G" @% l4 d7 p

    # x3 q: ?' N& y如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。! Y# e% p( g% d! w
    ) \  i6 E5 O! r* X/ h4 ~# W
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。; w4 C6 j, w" K: f' n
    6 a3 J' X' A% ^+ \
    实现@Value动态刷新% c+ c( `+ p9 Q" H
    # f- S. s' j9 c7 U  p8 |& l
    先了解一个知识点& f) q: `. }6 |+ D2 h

    ( h  i& `2 F1 W这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    ) L4 Z) C/ [1 G, R4 Y
    , m* n) X9 B( D/ A; V, T* H7 V这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    . c9 S6 B# `# v) Q$ w+ Y2 r% Q+ ~- s2 b
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:2 ]- V# z: ]# B

    1 o1 g8 ?# T4 nScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;: R1 P1 T" F/ Y6 ?5 d1 a" Y7 c  |3 e2 M
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    + ?4 t( q8 q3 ?1 f" l4 R4 n
    2 |! p' B: Y; b: i8 Y' M& M/ X3 u3 Npublic enum ScopedProxyMode {
    " }' |. {: }6 c, |, i: Z' P4 Z    DEFAULT,
    / V9 X3 U$ r6 e' T    NO,
    : U) Q7 A/ g# p( d& l! }    INTERFACES,
    ( G0 t: v9 @. d6 ?    TARGET_CLASS;
    # d1 y7 X/ r# h/ J}1 D4 O5 U4 d6 |5 u+ M9 ~" @
    前面3个,不讲了,直接讲最后一个值是干什么的。
    # ?% j& W. j+ h" C! ?8 O
    ! E& Y5 C" i7 h当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    1 E8 C# ~7 P: D. y( f; ~. M' h8 j
    0 s" T$ d6 @3 l$ [8 f/ Y7 V( x理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。) z4 A3 a4 h7 G* k2 a% Y5 {, Q) K, c

    2 @" d" i5 v& \2 I自定义一个bean作用域的注解
    + L/ c: ]( j/ F* p) _8 ?7 F( y
    5 s2 H4 V2 s/ T& }% n# i9 Bpackage com.javacode2018.lesson002.demo18.test3;
    1 _3 @! J7 f5 N7 A7 C6 Y* q
    0 t* b& ?5 \2 y2 j% Himport org.springframework.context.annotation.Scope;3 z: u. P( X( J3 w6 m: q
    import org.springframework.context.annotation.ScopedProxyMode;8 N- b- w* r, y6 Y, U( v' s
    1 K5 z. P: W7 s2 C
    import java.lang.annotation.*;6 L6 Y* ~! o2 L/ t
    5 a- @1 O9 h* S# D. E
    @Target({ElementType.TYPE, ElementType.METHOD})
    % B/ q3 ?0 j$ C2 r+ j8 T3 v@Retention(RetentionPolicy.RUNTIME)8 ~6 [- d! N8 l/ x/ s3 Y
    @Documented
    # H" z3 \' a3 Q1 s@Scope(BeanMyScope.SCOPE_MY) //@1
    ) r5 |. t9 v: F* N; r8 qpublic @interface MyScope {
    : B& P3 Q2 r* Y; x) }5 ^' ^! d( C    /**
    ; u/ A5 j& u1 O7 b  I7 J2 r     * @see Scope#proxyMode()
    - U. X$ R) B* A& a5 S     */! ]4 w' I. ^: j: _; v
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@29 W' L9 [: b. H% g9 H
    }
    ( X( T9 f/ ]2 P* Y@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。9 M- e/ s: G- P& \+ g
    4 |) ?, }! _, }" J
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS. u0 k. D7 p! o

    " I2 {: [  r5 a@MyScope注解对应的Scope实现如下5 O5 v3 B6 E# O8 |% K, M
    ( N( N8 i$ n0 C5 o' `; Z
    package com.javacode2018.lesson002.demo18.test3;
    2 k) @3 u; k1 _, g6 r+ u! ]4 a( m
    + B. C# c6 T4 c' A4 [3 v* L* Fimport org.springframework.beans.factory.ObjectFactory;
    & x0 w) V. |+ l8 S, |, ?import org.springframework.beans.factory.config.Scope;! E& s; y% W1 |, a: T- l! g  O
    import org.springframework.lang.Nullable;
    ! [) E6 \$ z  ^. C6 j( M; @# M% [2 b) s) k& g! ?- n# r% ?
    /**0 ]* r: g0 E; [
    * @see MyScope 作用域的实现
    3 _$ \8 b7 \# [5 x. G3 v  M' r& {7 s */
    7 l2 R! B+ P$ `" n$ m& q  q) Dpublic class BeanMyScope implements Scope {
    ( s! a! w  h0 r5 H; W" F7 m- g/ z! ^6 f2 E4 j
        public static final String SCOPE_MY = "my"; //@1! |6 x7 x* f8 B* b; N/ C8 y
    6 ]* F' `: C1 O, t$ a
        @Override
    % Z" Z8 ?+ X  S    public Object get(String name, ObjectFactory<?> objectFactory) { ; [0 p: z* J: r6 N% k
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    7 f4 }3 m5 l! v( A2 x! _  u        return objectFactory.getObject(); //@3% |6 Q# O% K1 t* P
        }
    ) M2 Y7 `3 P6 c4 I- G. s
    8 t3 _1 f/ _0 X7 O    @Nullable! m* q3 m8 l' V8 ~1 [
        @Override  ?# y) x. A) }
        public Object remove(String name) {
    $ ]/ R9 T. P. {/ r* P2 \        return null;% E/ R( y% _; T7 v  C$ ~
        }
    " A+ |- h0 T5 G" D9 {. D" X0 N/ ~! U/ v% h
        @Override
    6 [' v! n( y% f; Q; c    public void registerDestructionCallback(String name, Runnable callback) {
    3 m2 d. d, i) w, O+ w  \- |$ ]$ {/ H
        }
    , o' I$ k$ W' U4 W2 K
    . s; W/ O. b6 q6 b    @Nullable
    3 N' _$ R, |+ J6 ], T! u' G    @Override
    4 B+ g. K$ X' k* p& c    public Object resolveContextualObject(String key) {' K' ^$ y. ]/ P9 e! G/ h1 h
            return null;
    9 G- [4 Y, q% C* Q; u    }
    * I( x) k- C7 `8 n
    ! R1 C" d, O; j, s    @Nullable
    / U9 ]* `  ?  i) ?) q5 q    @Override
    0 Q% z9 A1 @) w+ z! a! [1 s    public String getConversationId() {
    ) N- @. @: v+ F        return null;
    8 v- S/ m9 ~! v* }1 ?    }% s: B  L9 P$ d" E( o2 z5 h
    }
    ; R$ L& h0 i/ d# f! F@1:定义了一个常量,作为作用域的值
    ( L3 R& w( \% ?" {7 q/ ?. R
    0 w* F7 o( A. d: U. e& ]* p@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    & P  {+ g' T7 O' {" {) b7 Y5 l* S$ |4 D1 A8 e! S5 U- U5 a
    @3:通过objectFactory.getObject()获取bean实例返回。
    ' L4 }- ~! ~, X  P3 t; \- e! ?0 U& W; i3 F) A4 {9 G6 h
    下面来创建个类,作用域为上面自定义的作用域
    / g/ z5 j( W) ?2 F4 m) b
    ; ~7 V2 w( q  B" F" vpackage com.javacode2018.lesson002.demo18.test3;
    4 E  Y# m  Z$ g* Z1 z& `$ {7 \$ |0 v+ Z7 a* A
    import org.springframework.stereotype.Component;' d. G8 a, i: [6 l+ b% A+ C' P
    5 C8 L" J% U$ R! w
    import java.util.UUID;! X' S+ n4 g6 q5 y/ A

    5 a4 P5 N, ^/ e& o1 Z@Component
    / g# R: G0 a; m@MyScope //@1
    ( ~7 d. |5 H" E4 A; A: Gpublic class User {
      C3 N3 I$ w& q! u, J  @3 f& c- Q, a8 z4 \" V
        private String username;
    4 K* G: k% f0 W$ ~7 {
    ; U. M0 ^& C( D  ]* T    public User() { : E/ d) a; |4 X7 L$ l! }
            System.out.println("---------创建User对象" + this); //@2
    8 ^' F7 u0 |6 q( M& V, d0 l/ D        this.username = UUID.randomUUID().toString(); //@3
    7 A0 r1 ^; v9 M( r( i* N. g6 D; V    }% l9 N: b) X4 S' ~

    ) @8 m( A* ]! `+ R: g0 P4 c; C    public String getUsername() {
    / x$ D. a2 Y+ ?$ Q4 p/ b  ]( [        return username;
    # v6 A, Z, T1 [/ [) Q    }
    $ t& d3 N9 f1 t* x8 i
    ) e5 H+ |3 [. x! e4 ^, L  ^  R    public void setUsername(String username) {
    6 n+ h+ V, r* U3 ^        this.username = username;
    + B, O0 I: x/ ]. j+ O7 D    }$ g4 I- ~. N3 j, R
    2 }# n/ _) `4 \8 R3 n1 Y( k- a% F
    }
    . y) \3 p! j* a5 z8 _@1:使用了自定义的作用域@MyScope% O* M! i; M& y3 v, f4 f

    4 I- w, U+ v& f) [@2:构造函数中输出一行日志
    2 Q) Q7 v- F: {1 b" Z- x, Y! \% e7 C  n# A2 o+ Q
    @3:给username赋值,通过uuid随机生成了一个
    ; x5 e* s& S  p$ |
    / [& d2 L% c( @来个spring配置类,加载上面@Compontent标注的组件: p/ `, r- S2 r. `: c

    " t1 C( G( j, j2 g1 Z& C1 X) hpackage com.javacode2018.lesson002.demo18.test3;
    * J8 s9 l  q/ M" l! p4 i* C* l7 w. G" X  c: s4 }) X# S
    import org.springframework.context.annotation.ComponentScan;( e9 k( i7 J  k
    import org.springframework.context.annotation.Configuration;
    / \' T2 V5 {" T5 q1 f. x4 q& W2 I
    @ComponentScan
    + u8 _3 N* v  p: h@Configuration7 o% H* [7 @) s; i, P5 q
    public class MainConfig3 {
    # G) U- S+ [8 ^0 V7 A$ u/ J6 E}
    3 N3 i2 U' J: Y6 S' {4 |! }下面重点来了,测试用例
    1 f% d( M( m* b1 q
    " d# O8 X. S5 ]2 ~) L! v4 ^@Test3 ~1 [& Y0 }! I& s6 [  Q
    public void test3() throws InterruptedException {
    2 B% P. X' u0 O' d5 Q    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    , J! a2 W  s6 n' s3 b: G    //将自定义作用域注册到spring容器中
    ' F6 v6 z  H( H: j) J, o    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@15 j; P! {9 f+ |2 p
        context.register(MainConfig3.class);/ ^* _* n& s4 N, J# V4 G8 K  N# l
        context.refresh();. A" j5 n: V) m4 g( O/ T7 S- O( b
      c$ n$ u  p* b3 D
        System.out.println("从容器中获取User对象");
    0 e: Q% x+ k* L1 @    User user = context.getBean(User.class); //@2
    5 S! M/ U: U5 `  K- B    System.out.println("user对象的class为:" + user.getClass()); //@3$ I# e1 E. d4 v2 c' h

    # p& ~4 N) I/ ]    System.out.println("多次调用user的getUsername感受一下效果\n");. L/ N0 k5 r4 J9 M' M
        for (int i = 1; i <= 3; i++) {
    1 c. g' G; `1 _4 q- o! b        System.out.println(String.format("********\n第%d次开始调用getUsername", i));$ j) I3 C$ F( d0 I! i4 V
            System.out.println(user.getUsername());
    / g8 {" R6 o# x+ i( d        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));8 ?' g; z# X- w
        }
    ! @' W. q4 Z! w8 F}1 x' ?8 D# i/ N3 I+ o9 F
    @1:将自定义作用域注册到spring容器中  A! E" F. n, t
    & N* x) J8 |3 q8 V- \( I; ^% u
    @2:从容器中获取User对应的bean
    . z/ B9 d6 j# d" ^  Z& `
    3 L$ x: N! ~) r1 t@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ; h& m  O: a* Q  a+ d7 m7 p9 T+ f: q* U
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    * l( b) q5 [5 h% H  |: e% K
    ( z  R$ N% B% v* I5 |. J* _见证奇迹的时候到了,运行输出1 @' x# R+ K- w

    1 K# V3 v. c7 J( P' G  d& y+ l% w- m从容器中获取User对象
    + F0 Z& _. r5 R# b$ t. Zuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$802331272 Q2 F2 @$ @4 [( n
    多次调用user的getUsername感受一下效果
    : l$ j: d1 Q6 [+ g3 S( U5 e5 i( ]9 A1 l6 ]8 ~3 J3 |- V
    ********, r  E9 R( K& l1 r$ A- S
    第1次开始调用getUsername* G  m9 _. Q/ E& e. C; ?2 L
    BeanMyScope >>>>>>>>> get:scopedTarget.user: v: s4 y) C! |# v0 @
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4. x1 t, f- C* E" [. A
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    " T3 u3 F: s  q$ x( {- g第1次调用getUsername结束
    , ]# c2 G  N, \& N3 {! v  Q$ n********
    & b  v4 m& ^) a5 Y
    6 k) z9 [; y! y, F2 w/ n# F" E********
    ; _" v' n6 X" e# P第2次开始调用getUsername  \3 Y; J, k7 c# d0 v
    BeanMyScope >>>>>>>>> get:scopedTarget.user" k7 X3 F. b5 c! ^! a
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b2 n& v3 z* x- Z
    01d67154-95f6-44bb-93ab-05a34abdf51f
    : ?1 b( J1 K6 k( M6 e: a" n第2次调用getUsername结束. v  R6 l4 f9 _# U! j1 g
    ********5 h! Z- m1 o% |: l3 f/ O
    - y2 e* ~5 f( S( v. K5 S
    ********% [, ]5 L6 H8 X! |& h
    第3次开始调用getUsername
    + b8 o7 `. v" Q! Y1 z. c. LBeanMyScope >>>>>>>>> get:scopedTarget.user
    0 n) u2 h3 D9 h+ H---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15$ l5 ?" V  k% G4 Z8 A' ~  Y" Q
    76d0e86f-8331-4303-aac7-4acce0b258b8$ |6 U+ q0 S: _4 ~" X! \1 U% X
    第3次调用getUsername结束# E9 N5 u! A. @. x+ S' _
    ********
    7 J( H8 z$ w. |. k1 u从输出的前2行可以看出:7 e0 b. V6 u$ ~! M1 J5 j
    & ^/ U6 A7 D; ~2 r
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    ' N; B: _" @4 e# N+ W* ?0 z; J, x) ]& a, b
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。4 J5 }# g1 |; P8 L, h9 I

    ' a, A) m% o( ?2 O. R/ o# {后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。- l" ~+ r$ ~; ?! ~6 u5 s/ i9 ], ?
    % y% h5 N" m$ [5 e# w
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    % p+ t" Q  l& i# Z( k2 d; T2 U/ ~
    动态刷新@Value具体实现1 S& J+ B2 w+ h0 ]% h- U
    % c& W% ]$ e& W4 C# G$ h; k
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    - T9 p9 F0 F! S2 g# z: k& W+ D0 D2 Y+ |: y8 ^
    先来自定义一个Scope:RefreshScope
    / O- i& X! Y/ I, _) q' f( g3 |8 I1 C) m3 N, T) d) ^
    package com.javacode2018.lesson002.demo18.test4;
    $ F) |: ]3 x! a# U% m
    % ^# {# |$ ?1 W! Q6 p& `, e2 }import org.springframework.context.annotation.Scope;7 [5 e7 e/ M( s4 a+ n
    import org.springframework.context.annotation.ScopedProxyMode;8 G6 @# J' L+ c: W9 F/ `& I
    , v( ^  Z# z! [% r' j. z' v
    import java.lang.annotation.*;: B$ [1 _6 v8 L  T( ~- _
    - t: y/ A" ^8 y" H: l- @) n
    @Target({ElementType.TYPE, ElementType.METHOD})
    % h2 L# Y: x2 h- \  S3 x@Retention(RetentionPolicy.RUNTIME)
    ; W& C9 u9 F) R; v3 s+ L* @: B@Scope(BeanRefreshScope.SCOPE_REFRESH)6 v# Q/ i$ I* g$ U. d, N  S
    @Documented
    ) i! e" C2 M/ T8 ]* spublic @interface RefreshScope {
      h0 v$ u& {& i$ G2 N# Z. F    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1" p$ l: g7 |. Y' {  K& D
    }
    - x/ q- E$ q7 ^( E( w1 O" |1 D要求标注@RefreshScope注解的类支持动态刷新@Value的配置$ T/ V3 C; T2 h# [* G
    9 _7 i9 G, w1 l& M9 Q
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    * Q6 G* \( F+ R6 G) Y- E; ]4 B" ~( [% p; F
    这个自定义Scope对应的解析类* x1 Z! D5 }/ s3 H+ d1 `
    ' E! |5 ~7 p7 b, f
    下面类中有几个无关的方法去掉了,可以忽略/ H) X& g( q9 i  m( H' \

    1 Y# z2 r7 M# J" epackage com.javacode2018.lesson002.demo18.test4;
    % U( \: W( c" v/ ]6 ^8 P( i& E

    ' ]. S2 g# H3 i' b0 ~import org.springframework.beans.factory.ObjectFactory;$ y- M. y* V0 A4 `" |5 b* N
    import org.springframework.beans.factory.config.Scope;
    7 W/ B( n2 I4 P. P2 a; gimport org.springframework.lang.Nullable;
    ) i# Y' T& X: I' ]
    3 b# j3 V+ l$ C4 u. O0 F! N5 [import java.util.concurrent.ConcurrentHashMap;
    6 |; f6 x/ j/ l. l% q: K# a2 c2 m: R# w4 ?' D( u  F* K( J5 z
    public class BeanRefreshScope implements Scope {
    3 a3 A3 j- w( K: K: W2 ~) A( C0 c% u' y5 J; x! e( J
        public static final String SCOPE_REFRESH = "refresh";3 b8 [+ e  B6 b4 T/ U
    4 ~6 B% n6 L% Z6 M
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    : t1 {/ v& n0 I2 [
    ; L$ p* T. v/ y8 Y. [    //来个map用来缓存bean. P2 ^8 y) f2 O% x5 Y  M+ \0 i
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    & ^' H+ E- h! ~6 \& Y' E9 Y
    $ k6 W2 _0 [. e5 K, ?* h5 b    private BeanRefreshScope() {
    - ?8 B* C* L9 v; p0 \, Q! n. y    }
    0 q8 N1 g$ X$ X5 H9 u% ^0 q* `% K- X7 W% l6 O- ~( f" C
        public static BeanRefreshScope getInstance() {
    : g6 M% k* t4 a        return INSTANCE;
    . g0 i, ]( b7 Y    }
    : z6 N* H1 P0 U& H
    9 L3 _  w- Q  M: f, R    /**
    - D0 b: {. {! b) n' e; N     * 清理当前  Z) B6 l7 s( @
         */9 z& B+ k# b+ P$ r
        public static void clean() {
    ' g9 M  d( K( w' f1 [. X        INSTANCE.beanMap.clear();! Y: u2 @, w9 N2 ?* q% u" B
        }- R! C! w6 P+ @( i, m% @$ @4 x

    . g4 N8 L8 Q  r. y- D5 v" r* t7 V    @Override
    & B- e2 h1 u5 _0 Y: x    public Object get(String name, ObjectFactory<?> objectFactory) {) m: @6 S. L7 Y
            Object bean = beanMap.get(name);
    / M$ D: j5 F* ~9 Y7 Z        if (bean == null) {
    * E! t+ N, L, u9 o8 S. ~0 c- e            bean = objectFactory.getObject();- @2 ^0 y7 }- f7 D- H& w" S
                beanMap.put(name, bean);
      n) F4 y  q2 Y6 `        }5 T* F+ T9 I6 |. z* A5 [* j+ k
            return bean;
    ( G; Q. U: m! l% a    }7 u$ B) E' \: N/ W

    6 u2 ?; s& @5 ], }7 o* k8 `}. t  y2 h# a6 A3 p3 y) ~
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中8 g6 q0 j# l4 _, m. l

    8 f4 M, T0 P1 n) v$ i8 W上面的clean方法用来清理beanMap中当前已缓存的所有bean0 m5 ~  ]/ A3 ^# |3 H- w

    / q: l( i! ]# ]) W# X3 p5 \来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    2 X4 D& k6 f! ^- R9 F, U) G2 |: F2 @8 |2 p/ b6 e
    package com.javacode2018.lesson002.demo18.test4;
    6 c( j, _: n9 \- z. w% b2 n* Y$ r6 V6 V: a
    import org.springframework.beans.factory.annotation.Value;4 Q3 e# U" [" [( ?9 l
    import org.springframework.stereotype.Component;
      z2 f- d, t' @8 n7 g# K8 Q
    + s; r. f" e6 k* b) ^8 ]. K6 `. m/**
    ! I0 L8 l* n2 Y3 _8 r * 邮件配置信息
    * p* i2 K# ^( N; D" U1 Z2 h& l */. o) j- m; v$ h0 O2 l  W; t
    @Component; I+ G. O) A. b  `
    @RefreshScope //@1
    0 w! T, Y+ s9 n+ z' ]public class MailConfig {9 _& h) {! u* R# i7 n# c% d
    5 u+ ]; L! D9 ]
        @Value("${mail.username}") //@2
    % A6 h" n  K# ]5 d    private String username;- i( e# n) D& r( u1 L& i! V: S% q

    % I; ~1 |& p1 V0 W% b0 D  t& Z    public String getUsername() {8 U5 O0 t; p6 n* ^
            return username;
    5 f! G: w+ x/ ~, p8 U1 @    }3 P" V5 ]# D& |

    5 X0 o9 \! r, z) E& Q% N$ A. C    public void setUsername(String username) {8 W. G( {! M' W& B
            this.username = username;
    * o7 M( o# A4 b    }
      t4 t2 y+ J* l3 f$ J) R( R4 C4 y; t
        @Override
    # n: L1 i) D. n) w3 w2 x    public String toString() {; L$ ~) g, _! G. x* z1 @  }$ L
            return "MailConfig{" +
    6 @5 f, I8 @8 N0 ~                "username='" + username + '\'' +
    , m9 L  F( O* C/ n9 y7 N8 a                '}';0 [4 L; Z' ?# e! E9 Z- H9 a9 |
        }
    , p3 h8 {# U, o$ U1 ?}6 p2 {( y7 U; \6 `7 Z$ l" h. ?
    @1:使用了自定义的作用域@RefreshScope$ a/ m7 a6 ?% D+ e3 }

    : |; w/ Z; v& F( X- s$ i3 @, a@2:通过@Value注入mail.username对一个的值9 q7 X3 g6 C! ?% n6 \
    * S- C: ~% @. U) U
    重写了toString方法,一会测试时候可以看效果。
    # e0 @, w7 ~* `" N$ B/ ~$ l  E& I, S" m+ f9 X* m: L0 a" e
    再来个普通的bean,内部会注入MailConfig/ W$ C2 @/ q! G# D. g+ K
    + j! z. b/ G: l6 ^% L& e' n  @7 e
    package com.javacode2018.lesson002.demo18.test4;% [5 Z6 ?& Y0 r4 N* Q% g2 q

    " {* Y1 R8 t4 r! kimport org.springframework.beans.factory.annotation.Autowired;4 A- B& |0 [9 r1 y2 W# R
    import org.springframework.stereotype.Component;
    , y; f2 A& {9 u) H
    2 E$ E: N  m1 i! ]@Component  J  h# a+ J* h8 f
    public class MailService {0 }; l0 r3 j; C7 w1 |* x5 }! |. h
        @Autowired) O: O# `6 Y$ V# P! b
        private MailConfig mailConfig;
    ) p; n& ~$ z! F( T4 S4 y1 R9 ]
    3 V5 t% C9 u: |6 a    @Override( I8 _1 o: B0 P1 m
        public String toString() {7 F/ V1 L$ [$ I) n7 ?9 _3 O/ @( K$ Q$ _- C
            return "MailService{" +/ {4 M3 e5 Y+ }1 x$ h
                    "mailConfig=" + mailConfig +
    9 m, G) [! \7 ~2 L6 F! O! u                '}';" y( O6 ]) \+ ?1 ?7 P
        }9 \- H+ N2 M* d8 G' k7 H5 b
    }! {7 o2 F6 n* {0 z
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    ' T/ d7 z4 q2 X
    + A5 o: M. i$ ]来个类,用来从db中获取邮件配置信息/ ^  J% j( W' A8 c

    8 T  F: d  s2 L8 \2 Npackage com.javacode2018.lesson002.demo18.test4;- s$ @% a: `2 p) Y

    9 u: ?! ^8 U% bimport java.util.HashMap;7 w5 Q4 j( l9 f" f$ ?$ ?" ^
    import java.util.Map;0 @, \  ]6 y' \0 {% ]6 R& j
    import java.util.UUID;8 J; s6 l+ ?9 @+ P; N( L+ u

    7 T; ~+ Z! N6 t& h0 Zpublic class DbUtil {
    3 z" _9 W6 p7 v- Z    /**9 C' m6 {' _9 g0 |: }6 ~+ o2 p
         * 模拟从db中获取邮件配置信息4 }' w( o4 l0 d! L7 D4 N
         ** i2 w$ y3 j' X  }
         * @return# U1 Z4 a! O* ~3 ]7 T
         */
    # c5 X! \+ [5 d- d/ p% ^0 P    public static Map<String, Object> getMailInfoFromDb() {
    : t$ F  \  m6 |" I        Map<String, Object> result = new HashMap<>();6 T( e2 z$ Y  e
            result.put("mail.username", UUID.randomUUID().toString());- ?1 \* ~. Z4 f8 L
            return result;
    ( p. c8 e0 E* u9 \/ D* F$ p; k    }
    ( `% h5 N0 z: n: s- w3 ]7 X}- D9 T+ f: f0 i* o; \
    来个spring配置类,扫描加载上面的组件
    ' S% C; l7 L2 F# d  |- r' l5 [* D0 ~& C! Z) s; E8 D, P/ L
    package com.javacode2018.lesson002.demo18.test4;" c% y( O  U$ M9 D9 w# v# _  G7 e
    ! C& x( L4 O/ s" ?
    import org.springframework.context.annotation.ComponentScan;
    ) A. O/ S) g/ J, U9 _; n( ]. `3 @import org.springframework.context.annotation.Configuration;
    * T4 V" ?6 B* \
    1 i3 _( M& x' ^. x$ I4 {# C/ v: T@Configuration
    # N+ E/ q  G& X2 o' o8 |@ComponentScan8 H: S4 p$ \/ v' d
    public class MainConfig4 {
    ( v' ^$ X' h$ \/ }}8 f$ B1 X) ?6 R% p, E
    来个工具类% t4 _/ Z4 _* S+ t# Z7 u

    7 w6 Q$ c" z# v内部有2个方法,如下:
    " G6 u6 f& L% k" v4 T% c5 s9 ~* @" J& K# j- w9 i' h- N
    package com.javacode2018.lesson002.demo18.test4;9 v6 g6 J1 \" m1 T

    8 }( A8 T" D5 y( I* m9 Zimport org.springframework.context.support.AbstractApplicationContext;
    - g$ Y% a; o6 D) E: Timport org.springframework.core.env.MapPropertySource;2 `! h/ b  M& K  |
    4 z. ?# M3 E% O4 `! I1 @& E
    import java.util.Map;4 {7 ]. L; ]3 W+ A: D& \% _. }

    0 b' G" L" M/ O) ^4 f( g1 ]public class RefreshConfigUtil {
    6 g# A$ k; [& x- ^  h    /**
    6 O1 }2 I) Z$ g1 l     * 模拟改变数据库中都配置信息- w! |3 U; O* U0 m. S
         */
    4 f- x4 Q! H1 O6 }2 @1 J    public static void updateDbConfig(AbstractApplicationContext context) {* h1 l* b: F6 |" b  v# [7 W7 \  C
            //更新context中的mailPropertySource配置信息
    # c+ Z  [/ A5 A7 J: v        refreshMailPropertySource(context);. `1 C' n4 e) P5 M, J( B$ z

    9 d; N: c5 ]% n0 T- G0 C' o2 j& S        //清空BeanRefreshScope中所有bean的缓存6 q) J7 Q/ u- ]2 k
            BeanRefreshScope.getInstance().clean();: Q7 l* {' t% m
        }
    4 |" ~- b: k. t% P3 |7 k0 ^; v0 s/ p: C& K$ R/ u
        public static void refreshMailPropertySource(AbstractApplicationContext context) {5 x" ]3 I* V1 T  e: X' P
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    ( r) y2 K( q* r  b; Q* h. J; ?, Z        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    & r$ k. f0 r1 p  F        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ; B' X2 K3 C* U3 c        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);* j+ N5 m; b9 @9 h: g
        }+ H2 x0 j0 x  C8 S" b1 ]
    - W; a* H) I  t
    }
    8 R. G& |4 Y% _# DupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息6 w5 @9 [) V+ \' i5 {6 W3 o6 i

    9 F9 T  `7 c/ Q$ U+ e) eBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    , i; n9 D2 `( Q3 Z; n" t. _/ ~1 K1 \4 k( z. S2 P
    来个测试用例
    ( ^( O8 R$ b% j4 J- N* ?$ R6 d- V
    - l  E# K9 ^  \, ?@Test
    1 w6 R0 Z% D# I2 [public void test4() throws InterruptedException {' ?! s. J8 k! V2 G( |4 J
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ' \4 ~3 k1 Y6 z, y6 o+ ?6 Q  }2 S2 I* ^    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    : Z5 M' N2 {3 r" [9 |    context.register(MainConfig4.class);4 j" @; b' n3 w& a3 M6 j6 _
        //刷新mail的配置到Environment
    ( h- W  E) X" K' [! o( h6 {$ r0 i4 n    RefreshConfigUtil.refreshMailPropertySource(context);; n7 @% q* e& P9 m8 i9 x% ~$ \* C
        context.refresh();/ F% Z0 }% O3 R' l8 E
    , d8 h4 m4 o7 k( M: M
        MailService mailService = context.getBean(MailService.class);+ |: Q& B7 f2 Q
        System.out.println("配置未更新的情况下,输出3次");: x6 e- o  m( c" Z% t
        for (int i = 0; i < 3; i++) { //@1
    7 s% C+ t& }+ \+ X( \        System.out.println(mailService);* K% `# m; d, }$ e3 u
            TimeUnit.MILLISECONDS.sleep(200);
    ( P. m$ p' F$ N: l9 p9 ]5 @    }
    9 ]  y# [; A- j( a* F, O: q
    7 ]* d( k* R# d8 M- n    System.out.println("模拟3次更新配置效果");* K3 i" Y; W, M9 {2 ]
        for (int i = 0; i < 3; i++) { //@2
    * [' k& W% n# ]        RefreshConfigUtil.updateDbConfig(context); //@3! G5 A( L; A( Y  t- W# t0 K% J8 W
            System.out.println(mailService);
    ( }; r$ w% v! Q# i- `        TimeUnit.MILLISECONDS.sleep(200);
    . k. R& [$ l5 O  r3 `    }* s" O9 D( h! o6 B4 S2 B
    }; Q4 s/ l; I. s
    @1:循环3次,输出mailService的信息% G( ]4 n, R: F' d! ^' Q+ o, Q

    3 l8 Z4 r% l$ T, A3 L@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息/ t1 J( [, X3 b( g- }- {

    - z  @# a" M4 i* |) G! Y6 A4 k# v2 ?见证奇迹的时刻,来看效果
    * J! g. z7 T2 B
    7 Z; s2 ]2 L2 \" Q9 a配置未更新的情况下,输出3次: m* }: [2 L( T& D+ c
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    8 R; {! ~) I+ z+ V( ?$ ~MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}! t6 T8 d# q6 f3 S# m9 c
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    6 Y7 A2 }! X, p6 C模拟3次更新配置效果
    7 N* B& l' V- D, X, u( A+ UMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}1 N4 c' ^! p$ z; g
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}( b+ B! i9 S: N6 e, o+ g
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}' B# i" I2 p3 R6 ?. ~6 {
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。  h+ N+ ~7 |6 B9 J& \. w. l7 L
    9 V) x# R. j+ G4 D% F/ _# a0 p/ u% e
    小结$ L8 W. G2 d1 v1 q9 P2 f" L$ i; i

    ' Q0 S. J1 `( e' q. d5 j动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    % ]) b. ?1 u- Y8 T1 W. I' ~. W) s  B* m" V- A
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。4 I: y  q/ F* U

    ! }: a' e6 t3 }/ d! Z总结3 }3 {9 g. c; x; _- ^
    3 c' {: v8 x0 r8 `2 n
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!) d; y) o' _+ Z5 Y$ Y/ k, l/ ~3 z

    1 H) c6 x/ ^7 c案例源码1 u+ A) z/ @1 d5 _# s
      q& b2 A7 q; O' |7 K- U2 j
    https://gitee.com/javacode2018/spring-series# }: D6 i% m8 F1 x) G
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    ) Z! b$ t9 ?4 ~7 z; M" a6 k! Q, s7 Q! l  d8 Q. n- W( l& F% m6 V
    Spring系列1 `& I3 _% l0 y0 s1 ^" R! p
    : c  z  R& K. L+ N# e  k
    Spring系列第1篇:为何要学spring?
    ! k# `/ a- c2 L$ E! G( k, a/ L6 U* `; J( O4 x
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)# _. V. b- l+ u+ G9 k0 x( n

    + e6 b6 G6 _- h0 v# h) M& lSpring系列第3篇:Spring容器基本使用及原理
    - h6 J; W# k: V& ~
    ! r$ w8 u- F( J& x" PSpring系列第4篇:xml中bean定义详解(-)& I/ a, w+ {: P- u5 S  l$ A% q+ s
    & z+ A) A& J" h; ]
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    ' D$ m- x6 y% O2 P$ Z/ Q2 \! i; k- ]- G
    Spring系列第6篇:玩转bean scope,避免跳坑里!: i4 V" T! e3 |% n. r
    6 r6 R: O6 y8 [# h% I
    Spring系列第7篇:依赖注入之手动注入: |6 O: Z, ~- R! f- h- q1 }
    - x9 b3 q$ d' t2 v# c( w9 D
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持8 {! ~* z; ]$ d0 ?6 ?3 D" n' k

    - @2 r, B9 J- r6 {) m. `Spring系列第9篇:depend-on到底是干什么的?
    3 w. s  B# Y: W) P- t7 P1 W2 b; G4 t/ ~3 u- l
    Spring系列第10篇:primary可以解决什么问题?+ }4 |. \8 W" p$ O+ B' n; K3 }
    2 |* V  B8 A2 I% m! e! V4 L3 G
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?9 j" w7 a6 S# x# w9 ?
    . {# m6 N/ B! w8 S
    Spring系列第12篇:lazy-init:bean延迟初始化9 E4 C% s" c, d" A  C& P
    ! _' l7 m1 }0 L# d
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    4 [0 G3 A% ~  |& l7 e) {' W
    6 M! F9 g) B' G1 ?" MSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    ( A2 o" b( m& A, K4 D% t1 P2 K% P, Y% Y, a; O4 l* R
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?+ _+ G' u4 G' f9 {- G) E

    ; V) E* Q/ r, h( SSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    # j- n- t' h- ^" }. G6 G9 [2 h& Q' v- w4 w( f9 l
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)& a% D! r' f3 Y8 F& q

    ; I+ ?) x3 w9 {0 W5 c' Z  nSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    ) x+ S8 w- d9 ?' D: D9 n! _- n+ Y3 F+ l: D4 Z4 ^4 G
    Spring系列第18篇:@import详解(bean批量注册)
    , Q1 q% t; }! V3 |. Q
    5 x9 N; M0 d8 ?" gSpring系列第20篇:@Conditional通过条件来控制bean的注册5 ^8 ?" R  y# \+ @& r' e2 O# Q
    & k8 D& z) p5 W+ `/ ^( X9 }+ `- h
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier), |6 y2 n! l$ c( Q3 Q& \0 n

      ~3 ]+ ~6 B: V; s7 KSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    " `! A# q5 E1 @5 _  n& b5 Z) ^/ D# a
    7 O1 v) `1 _4 ~8 C3 ZSpring系列第23篇:Bean生命周期详解( f5 P2 r( o: F

    0 f" q0 H# F2 t1 ^( |  PSpring系列第24篇:父子容器详解
    " y# ^7 p  ~7 k0 K
    . T4 `2 K* {) y! ~& W# ?* ~更多好文章
    7 T& g) Q$ o! q6 k' C* }
    2 ~$ y6 B4 @( m/ z9 d# k. _Java高并发系列(共34篇)' }& G0 R/ Y& h
      x) D$ j; O. _, i7 [, G
    MySql高手系列(共27篇)9 Y# @0 ~( y0 H; g/ ~" r3 ?

    ! o/ o, u& s4 I  Z% BMaven高手系列(共10篇)
    2 ?' _0 c9 a& {) h1 [2 L6 J2 `2 z% _! P
    Mybatis系列(共12篇)9 K  [9 K9 w) D; t7 ~

    ! U# h" i6 J3 a. q+ q聊聊db和缓存一致性常见的实现方式
    7 r% K# W- y9 X5 `3 |" a8 D
    $ G" A. S3 q# x9 D. b2 T4 b3 X接口幂等性这么重要,它是什么?怎么实现?
    ' _( B9 _, b3 C6 A% N; d# Y7 _' U4 S  v; Q7 e
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    1 F) l8 ]+ ?* c————————————————
    3 Z+ s2 c9 }" G# p, |版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    - g! H6 N+ z+ R# a( U, D$ [原文链接:https://blog.csdn.net/likun557/article/details/105648757
    ! ~0 ?# \& e. w- W5 _  k" M  G* o7 O9 Z# M' ?. @
    8 u% ^" {2 |5 Y( w! R6 n; \# H7 I
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-21 04:09 , Processed in 0.484495 second(s), 51 queries .

    回顶部