QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5621|回复: 0
打印 上一主题 下一主题

太狠了,疫情期间面试,一个问题砍了我5000!

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2020-5-23 11:01 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    太狠了,疫情期间面试,一个问题砍了我5000!1 t) @5 Y: I" q" j; l
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!& J, O) o  x/ ]( h4 w$ y; o8 r+ j
      O- O; [) r! G* Z" l1 \$ B
    面试官:Spring中的@Value用过么,介绍一下
    : y3 R& n' {' Z- q( @. N
    9 X6 E- k* e* y! b, f0 Z我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中* ~& N/ }8 D# E$ l, ~) ]- Q

    3 Q" V* d" V& M  o4 {面试官:那就是说@Value的数据来源于配置文件了?* N; T" `2 D4 q, j
    - m4 ]  L8 a! [
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置2 X5 H8 D: \8 T: \. P8 W. L: }
    6 o% ]& T: B* b  B; u! U1 A( i
    面试官:@Value数据来源还有其他方式么?
      T3 \5 N( T; q) b0 Z8 r4 Q, i% N2 k6 k! j2 D4 x. R' b
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    . U5 t  F* r/ v: R( a% k, ^/ s
    % l4 M4 e1 J* F" K  G. B' @面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    4 ]" I. I+ D" v$ j) N* V
    8 n8 M% G! J$ |+ E; A, x$ R6 x我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧. Y; G. u* O3 h* ^; |

    6 U$ r7 N0 L7 ^- W4 ?1 \# t9 _( [! f面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?$ A5 N  ^! K! v; ]5 X  j& g
    " k" g- |( v, X4 H% H" M$ D* q
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    5 X/ n( C  A6 q: j+ ~) D& A7 }3 a% A, J5 s
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?& t+ k* b0 |4 u- I$ x% y5 L

    : |* V$ _- E" A9 z2 q3 @我:嗯。。。这个之前看过一点,不过没有看懂) B2 K( `7 y0 P: @7 X( T; T& T

    ( u. H9 c% K. N- V) X- ]面试官:没关系,你可以回去了再研究一下;你期望工资多少?1 R0 H/ @: x5 r+ X! r

    " a( A/ V5 M# I9 w' `我:3万吧2 a# D, _, ?% R6 p/ Q

    ! q* G7 |. Z& \8 U6 @面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    0 N6 d2 I. N, ^* c  m& W8 T$ a: ^  x) T8 H% F' Z
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    % N' C4 ?" T& z7 z  f9 i8 D0 Q/ G- E: O  c4 J/ b. z: H
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
      N( t& H1 }. B/ A! I  }2 i8 A% W3 i$ \% B
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    6 ?+ G* F# c! x7 ?  E, l0 n6 P4 u
    ; X# Y) X0 T( G% \& Y) H3 B: D这次面试问题如下0 h+ N/ Z! r" c: G. O1 P2 u
    : f% t, |0 Y' n
    @Value的用法
    ) b8 U5 h0 o/ C/ U) \! q
    4 y6 C# I/ g8 A( P$ z* a4 B$ [5 L@Value数据来源
    # f- ~' A! e0 l- l7 d
    . _8 o1 _2 O; j@Value动态刷新的问题
    5 o% t/ G, i+ [1 {. Y2 }& [! D, C. w* S& ]- Q
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。; [- H; P. \, c9 j  |: x% W

    0 g8 `! }( @% m+ J7 E8 t6 I0 E@Value的用法1 Y' K; |9 P0 u7 f4 C9 E% A

    ! L' c" ]8 [( [系统中需要连接db,连接db有很多配置信息。
    1 J5 C8 m1 J+ I8 e. u/ N
    8 H7 R6 V7 q& [( I& v系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。( i$ S6 e: a3 Q

    + H/ g: V9 D5 h2 D) I- G7 H还有其他的一些配置信息。
    ; x7 f5 f- a) Z- L. v* L  K$ K) ]) _, \- F/ Y4 O
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。: ]4 \3 t; X# C! e
    % E, J. j7 F7 T% {
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。. T4 M9 \: D; {: l

    ! o& `5 N" A% `5 U7 s% u# \通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    $ Q( ?; H+ l# o4 T! Z/ A# a: E; x6 f4 |
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    - j$ o5 U: m8 w/ i' ~3 r/ \! ?7 T( _- c$ y# P# C0 N. M' Q5 r
    @Value使用步骤5 [$ m( n$ z+ O3 z5 q7 p
    8 Q. f  a. s/ q# T  q
    步骤一:使用@PropertySource注解引入配置文件
    9 n1 u* t* u  S; v6 C, j0 B2 ?  O+ ~" Y' [% `! Z& \
    将@PropertySource放在类上面,如下; ~: G! s! ^, t9 `' N4 m1 O
    9 o# T7 l% i9 A1 `% S
    @PropertySource({"配置文件路径1","配置文件路径2"...})9 h! J5 e$ f) b: H) N% B
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    " {! x; }0 R+ e, m
    # _5 |4 e1 x( ?. n! `如:# O$ B3 }" a7 L) b8 M+ ~/ c- W

    ! {' r$ f3 b1 _. ]# p. a+ b@Component( g6 V1 A0 g- y( B
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    * a: p) z& o, m" A' ?9 Wpublic class DbConfig {* o* g# [' r0 o3 l% I+ |3 z. p( x
    }
    8 b7 Z! R) B1 R$ U& Z6 t步骤二:使用@Value注解引用配置文件的值% W( T- @/ |6 {) e, q2 _4 I

    ' z$ [% H0 r* ]* D8 X/ x' i通过@Value引用上面配置文件中的值:) u0 ^7 G, A+ b
    9 e+ G9 f, ]/ Q" p! S: q7 D% Y4 L, T7 W
    语法( f7 R0 t1 [. O3 o3 s1 V

    $ _. n- E! v/ r& N0 C: E5 F6 W@Value("${配置文件中的key:默认值}")1 A. F& M$ P. _, d
    @Value("${配置文件中的key}")
    % i0 }$ ^1 W. b# `. r2 k如:
    1 O% M- s& X" ~. f; Z# K* O: i3 S* p' `1 E
    @Value("${password:123}")
    / b6 m6 z6 l4 W0 p( Y上面如果password不存在,将123作为值
    # ^! a1 O% R4 X
    7 [0 _$ K: b; v4 z+ A$ ^@Value("${password}")
    9 M0 S/ p$ V  |! m, }  Y2 Y上面如果password不存在,值为${password}
    ; P- V2 ^! L+ U# Y, d+ @8 R
    ) {( T1 _! Y$ ~假如配置文件如下
      l) M2 @6 N4 _# O4 D$ U# o
    6 B; B( X4 A2 a9 D8 d( V, Jjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    : a6 ?! j6 O1 R; q- I$ G% Zjdbc.username=javacode0 q1 Y: z) {' W3 g, q" H  [
    jdbc.password=javacode- d6 t% ]8 }* l% ~
    使用方式如下:
    % h' o& Y; y4 t9 Q
      ?2 X* k$ [: q2 J, ]' N) ~. ~@Value("${jdbc.url}")  p2 ]! D3 T% {$ q& C
    private String url;
    & K* N  C( {) N: |4 q
    ) z4 t5 `+ N) A1 M" g2 f@Value("${jdbc.username}")3 c; r* ~( T1 e* z
    private String username;
    $ z, x7 g9 x6 d5 O
    : ]6 O8 y" s# A& W2 I$ _0 P@Value("${jdbc.password}")# X/ {. z) R0 R9 r+ G- v! [
    private String password;3 I0 `0 x4 g( ]; @
    下面来看案例; \+ q6 B# Y1 e  \0 u/ G7 j
    $ A$ M9 p# h4 h2 S4 W: O  [! C
    案例
    1 l$ O0 X- T' z2 g3 n8 _6 f' K  }& I& [
    来个配置文件db.properties
    2 @' l( |! v; l, t. q* u
    4 u+ o6 }* D: q7 c) Bjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    7 k8 C# Y$ B" Z9 R6 qjdbc.username=javacode9 t. R/ c0 b5 _, n
    jdbc.password=javacode
    ' N$ x( \% T* P# O/ N来个配置类,使用@PropertySource引入上面的配置文件
    " l1 H0 _2 m5 B# k+ n* J  b, `" ]; H) Y# @
    5 a8 j. t" U$ A; o1 @  Gpackage com.javacode2018.lesson002.demo18.test1;
    ) x$ ?$ [! [) d9 j9 V$ M( F# A
    import org.springframework.beans.factory.annotation.Configurable;
    8 d7 E3 @! K, v/ U+ `' }- K( Aimport org.springframework.context.annotation.ComponentScan;
    ! }. r* i0 R" z; e& Himport org.springframework.context.annotation.PropertySource;9 N. @7 O4 F; N

    $ j: c( D: K* H  r. R@Configurable/ t# B6 O: X% o0 z8 i$ \7 a/ H
    @ComponentScan- ^8 V5 f( L: P3 V- e! F7 m
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    + i5 ?# K% Z. K- X( apublic class MainConfig1 {7 a3 i1 r" L1 ?$ |6 ?
    }
    5 s9 ^2 @- `# {& ?1 [# @来个类,使用@Value来使用配置文件中的信息2 ]7 r$ q$ `8 E8 }5 o9 L3 u
    # _+ f: a/ c! o$ @& s
    package com.javacode2018.lesson002.demo18.test1;
    4 r- G: G0 {/ m  ~* B! ?$ e3 I( `( M8 X( V$ }
    import org.springframework.beans.factory.annotation.Value;2 A+ f5 R7 r& }! P. s
    import org.springframework.stereotype.Component;
    . z' l# |% ?% G) p3 K( n# \$ E& h! L
    @Component
    # \) p8 `9 ]5 G, I/ a1 d5 ypublic class DbConfig {
    # [) e6 \# K7 J. F& G* v0 A* C' k( ^9 G( \+ \. a0 V
        @Value("${jdbc.url}"): v, A6 W5 u9 f( p( p& I! e! i7 F$ G& q% n
        private String url;
    - B' Y7 Q4 @! K8 ^' x$ P
    * j: m. D5 [7 ]% W1 j% Z; i    @Value("${jdbc.username}")# M- I- u5 V" D) |* R8 D
        private String username;
    3 c0 \0 r- ~+ Z6 w1 z- M6 a5 X! Y+ T! y' u0 _! Y
        @Value("${jdbc.password}")
    5 E) g( S- d! o8 V/ @! ]) R    private String password;# B& u" A  g- b+ ]) n' Y9 w* g
    4 g: w! B5 X) S8 T
        public String getUrl() {
    2 r# ~" L* s3 Q        return url;& q$ _) u! |% L3 h( i! R1 j
        }( k7 K' N& C! z4 L/ Q

    " [; V) W& [4 V. g2 w6 w' X1 b    public void setUrl(String url) {" i! c7 A) |7 o' |
            this.url = url;# \' l4 l5 E9 x1 @: H& j: r
        }
    ! U. l$ \- w9 s' z, w4 H# s  L6 y3 i2 u' N- @$ ]) k
        public String getUsername() {* N( C: x6 N; Z; e9 J0 _
            return username;
    1 B+ f) B" ]# T6 F/ w- V    }
    3 F+ C: o% Q- B. S  B" b' m
    6 i* B9 L6 A0 c( Q, g7 V    public void setUsername(String username) {: s/ ~/ P+ f6 \4 v) r& X
            this.username = username;
    8 H2 t! a/ C4 W" l% I2 k/ M    }! j! y1 i9 ?3 g6 i; Y0 X! N1 }, y
    & e$ i0 U4 X! X+ q" O* Q" O2 ?9 N
        public String getPassword() {5 k; y/ s' U% Y1 H1 j. ]2 @
            return password;! N7 l7 q, S2 R1 ]
        }1 ?# n8 u# @2 c0 E

    $ j$ j* Y7 y/ l$ X9 O    public void setPassword(String password) {
    & t2 }* g8 H6 O) B        this.password = password;
    2 F' C- Z; \2 z    }
    7 w2 D. A8 c* e" B8 i; C$ \5 G
      ^7 B( N+ c7 U  _8 X3 G# K' M    @Override
    ! \" q: w: `  i; v2 ?    public String toString() {% K( V: k$ \( w1 V
            return "DbConfig{" +; t' C+ K. X- K# L0 x# v
                    "url='" + url + '\'' +
    5 l" N7 M. ]: v/ i                ", username='" + username + '\'' +- S! L7 i3 o/ i
                    ", password='" + password + '\'' +# b" F4 k; Z  C4 r
                    '}';
    0 J, S. Q* I7 G( e8 q, \    }
    0 `5 u& d% L1 a$ n}6 K/ o- C5 G$ F+ o* ]
    上面重点在于注解@Value注解,注意@Value注解中的9 t" o# A& _3 I6 H
    5 ]8 h- t. e/ p( L, o0 b1 P+ q  y
    来个测试用例% {* {- J' Z2 J4 g( ^
    5 S& k, v6 f. P7 D5 ]. w6 j2 E, X& ^& o
    package com.javacode2018.lesson002.demo18;6 H+ T2 j0 p& U# b& F+ i/ C

    3 O  }$ U4 c8 qimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    5 _- ]) D. m$ ^import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ; m7 o3 O+ z5 j# c9 d6 gimport org.junit.Test;+ p: {& I. n  U. g1 D: m7 a6 m6 ^7 S  A. a
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ; ^' O7 g1 {1 F9 a( f( |5 X" C7 g' U1 O5 t+ X$ D, Q7 i% t
    public class ValueTest {
    7 W6 B8 B# e4 ^5 j  \
    & }* h- j; e; d0 l% t# {& u    @Test
    5 r3 m* O9 a+ t5 R    public void test1() {
    - J& L* L; S+ f        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" z/ g, |7 Z, A$ ^% o7 }" V- c
            context.register(MainConfig1.class);
    " P( ]9 C8 |1 |( C. h. R3 W9 e        context.refresh();: Q7 n+ }9 k3 x( `: E7 t2 f/ H
    ' M: n- v% m9 J/ m; P
            DbConfig dbConfig = context.getBean(DbConfig.class);
    / G' x' h% n% @* ]& }* D        System.out.println(dbConfig);
    ! f0 ~5 ]0 o" C5 I2 M' Z# b! k. p8 q    }
    / ~8 }& F: m# s" W9 m. B; n}( r7 K6 b7 F8 o. t, I: i
    运行输出* T' b2 u) N& b. y/ w0 t: W& k9 {8 c

    7 s, ^* @- d3 Z) v7 C' tDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    " T/ h/ @' i. S( H. o- N上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    5 t9 g$ I! X. c/ x9 j% ]( \- z8 H# d" ]9 \& T! }
    @Value数据来源
    + v5 n4 O& _5 `* w+ H3 p, ]
    / I( ^) R$ u1 s, |+ a通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。) o  b1 z9 y! X1 ~5 |
    . ~, I" f! y7 `; n- M
    我们需要先了解一下@Value中数据来源于spring的什么地方。/ Y. w2 d. F( E* \0 P% n' D- o

    2 p$ @+ d3 d- ^! l7 Qspring中有个类4 h2 `1 B( N3 s; o6 d# G- P+ W$ t
    ; }! F& M) n9 u2 \, X/ G
    org.springframework.core.env.PropertySource) v3 }! m2 w# c; \% O" i7 X; c
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息# }) g; ]3 J/ N: Z6 w7 ]

    " v- p! i% z' x3 u' ~0 Z/ q内部有个方法:
    % `! N5 W1 N5 [  ]+ W. g+ {% o1 \& p4 C1 z2 f9 z1 a: V
    public abstract Object getProperty(String name);8 m5 y* h/ L" b
    通过name获取对应的配置信息。* g) Q+ \& `; D* }+ T
    2 q) {/ }" K( K" M2 W/ \
    系统有个比较重要的接口
    3 j, ]3 @! K& D! b$ B9 t+ d
    , i: ^  b- h: }1 E* J4 {org.springframework.core.env.Environment
    + w  r! U0 P% N  F用来表示环境配置信息,这个接口有几个方法比较重要
    . M5 j  d; H6 \, t! P* |+ ]+ U3 Q+ [1 o$ R% B
    String resolvePlaceholders(String text);4 ~4 n6 b2 o" v
    MutablePropertySources getPropertySources();) t8 ]; N0 X- x  d9 B" m
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。; g. G3 o. p1 s( Z, C
    0 B7 C# F4 ^$ _
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    6 f; d( S9 V2 g+ z0 [, `6 i
    ( W- e( u0 l* D. a/ \public class MutablePropertySources implements PropertySources {
    ' u4 E! g( h; i0 z4 i, U) D0 r3 }
    ; Q3 U# ]$ |! m$ j2 Y! S    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    7 R6 P- X8 Q( ^' A5 @4 Q( t0 S/ J* Y5 _3 J9 n( Q
    }
    4 E0 N9 J2 }8 V- s/ l, M内部包含一个propertySourceList列表。
    " u1 g( v+ G" w9 I; T" n6 K4 ]' [; X1 E3 V
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    8 p7 ]. h+ A# x% ~) }5 q! R+ C" [& V% X6 d$ b. ^8 F
    大家可以捋一下,最终解析@Value的过程:
    * f" e# h- [$ u! V% d6 M0 U5 W8 B" w! K! A' I) o% N; s" N: c
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    # R! t0 Y. ^7 [7 a' Z' G% O2. Environment内部会访问MutablePropertySources来解析
    - p  m8 d9 R, o: K/ q' ]. t. g3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    & {% M8 i( {0 P通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    - G! o; x1 E/ o7 o: H9 e2 _2 o
    / G4 [5 j# [: o1 B% t( m" q下面我们就按照这个思路来一个。, f. x5 o+ N' r

    1 e  u) V& c1 E4 |来个邮件配置信息类,内部使用@Value注入邮件配置信息1 m, M) s. e- z! g" o0 [
    % S; A$ O. W/ i2 z( |6 c  Z: f
    package com.javacode2018.lesson002.demo18.test2;
    2 w# i2 J9 p2 d& N! @- s) C4 |) v1 K% e9 \/ ]2 U: o7 Q/ J9 b
    import org.springframework.beans.factory.annotation.Value;+ \5 u: }8 |6 ?- w& B
    import org.springframework.stereotype.Component;  t$ r- M! s4 ~- k# }9 J

    $ k% @/ F. W# k8 Y* |. q% h/**% z- m2 r  V" {: a  o% E
    * 邮件配置信息, w5 N0 M: T7 g5 h: h% t  O7 X
    */; W# H- l& \4 Z$ X" @
    @Component2 L$ M2 X( V0 W, V7 l( p
    public class MailConfig {* u. Q6 w6 N% ~3 G3 E+ m

    & {5 n4 t% _0 ]. E    @Value("${mail.host}"), L/ F. q8 |2 C  H6 x
        private String host;
    9 E/ [; V- O# E* M1 S: \1 X
    6 T/ p2 A% O4 E* N8 G9 [    @Value("${mail.username}")+ k% s+ r- Z6 c. d( |3 K
        private String username;  M8 G$ P9 A' s  c" `' q0 m/ @

    / e& L: M- m5 K, |# A# s    @Value("${mail.password}")( I8 L/ m& F6 U' a0 ]9 x
        private String password;
    $ `8 P  L" x  Y% k( S8 j' S; P2 |1 D+ w
        public String getHost() {
    8 {) l0 ?. W: t" C, T: R- k        return host;
    0 @) n4 a& D, l8 V' h/ y4 l    }/ x3 ?  }6 a  a, G6 x
    * \8 Q2 W; G% @! v7 `
        public void setHost(String host) {: K# T3 n6 Q: e; S  W! V; ~" a. }
            this.host = host;* ~, r' `. W# W- y) X
        }0 l6 p1 e7 H. B% S' }3 _

      L+ N" J& X2 g4 l. k# n    public String getUsername() {8 y) o; t2 S9 r6 g9 T9 O
            return username;
    / m: g, _# }" i* }    }
    # {1 c. z! f" {! I! j
    2 c1 S3 E, a0 N# S8 ?% r    public void setUsername(String username) {% m2 J7 [6 c+ {: F& d) R
            this.username = username;
    ( f3 o/ X6 G0 A    }# N  u% D7 r9 p+ N, i

    3 }1 h, @, C: c% o, J  D4 I    public String getPassword() {4 ]1 n$ T* U8 T3 z. N" B$ F
            return password;
    6 f: q" O; W) m! R6 c) P5 [    }
    ) S3 _0 J9 P" S: Q/ X" A# T$ J* v  w! k4 i/ Z. R* X) j1 m
        public void setPassword(String password) {
    ; Z9 p7 c5 j2 [; q3 i        this.password = password;
    ! l. H( P  m+ W& H3 S1 ~    }. {: w+ [" a7 k% j# X
    " t. X( v4 ?/ o# f9 ~5 F. N5 Y
        @Override
    $ x8 e9 r  \* Z. G( G+ a5 i    public String toString() {
    7 e/ r3 ?* c& x; R4 m8 E7 |        return "MailConfig{" +
    , e2 Q, U2 I8 z/ d: S                "host='" + host + '\'' +
    9 f/ [' e8 g+ P/ I5 K                ", username='" + username + '\'' +6 |. v4 n; {6 W' }5 r  j
                    ", password='" + password + '\'' +6 i# n8 \' Y4 i( A4 @  g. v
                    '}';  _. X$ M6 O* r: v3 d5 Y6 S9 T6 r
        }
    5 g9 Y0 Y" \- z}
    2 q& @; s9 P( T4 m! p$ K6 d再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中8 v- ~4 h: ]3 @6 l

    ; I- d; k) m' B6 bpackage com.javacode2018.lesson002.demo18.test2;
    8 \3 a* X% Z6 ^7 X7 [/ f' u- h  g" S+ V
    import java.util.HashMap;
    * A" H6 K0 v: X3 i8 v6 vimport java.util.Map;8 l+ X/ e( J+ }2 x

    & [2 n1 X+ }8 T- q* @, v. mpublic class DbUtil {# q* g: z+ O' J3 @4 y1 J
        /**
    3 A& l, M" b* u8 p8 j     * 模拟从db中获取邮件配置信息
    3 K2 F, R8 ^# |' D" r; O' z/ R$ K% H     *
    3 C4 e  w7 x( B0 K; [# p# A     * @return3 N* T7 B; u. m! G. l
         */; `+ ^1 D# O( r; x+ L( k
        public static Map<String, Object> getMailInfoFromDb() {
    ( H7 \0 a! Y7 }! x! O        Map<String, Object> result = new HashMap<>();
    $ O" I* G2 Z8 |! [7 a) g        result.put("mail.host", "smtp.qq.com");4 P) w- S% {' B( v' ]
            result.put("mail.username", "路人");. c& n$ i7 Q. x. E7 o  {5 u9 z; E
            result.put("mail.password", "123");% N4 h( J& V; j3 ^
            return result;8 R3 W& B# L( y8 j7 R/ \
        }
    3 I  Z! j$ e, X  i; z: n, G# I4 |}
    ( _) x4 E4 d. ?0 D7 }来个spring配置类
    3 ?; ?) F9 L# C, ~$ U% D& P8 _- a! E1 G
    package com.javacode2018.lesson002.demo18.test2;) x$ ]9 k" g  v- s

    / G# C& X% [& A; W6 y* a* p9 Aimport org.springframework.context.annotation.ComponentScan;2 a1 P$ p/ F8 k; h% p* [
    import org.springframework.context.annotation.Configuration;
    / S% s, |5 m* k: p4 q! g
    ) I: t+ h5 ~4 N& ]. s9 C% r6 r@Configuration  a5 A$ r6 V" _- Q1 c7 G% R
    @ComponentScan
    4 @/ L5 n+ Q8 }5 s" J# ppublic class MainConfig2 {
    0 H8 J2 z' N6 W}
    : H6 B- M$ r3 \/ t下面是重点代码0 y: e! Y! \! }- F$ G; F7 T1 \- p
    ) E" @5 c  L- g3 k
    @Test
    * F" G  [$ x$ z! O' [! I) ~8 ipublic void test2() {
    - K# Y4 r( E0 Z# R8 f8 j+ c    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();: w: t- E2 a  o+ h* t. j; J* o

    0 X; m4 r; ^6 E" P0 o7 q5 _0 n    /*下面这段是关键 start*/4 }/ _% }) X, \, L0 N" X/ i
        //模拟从db中获取配置信息
    8 }. |5 [2 ]! }& A; a* I. |    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    1 n4 J# C3 t! q    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    8 c3 d2 m4 x# x  T    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);& j! d. ^7 {  g2 O4 w* S
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    # O: o# i" q+ i/ N' n    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);0 F( p9 D% @/ P/ R, N
        /*上面这段是关键 end*/
    7 C% ?( l$ Q0 e1 V, p& }2 a0 R% o3 m; ?3 a1 C, {; T. ]" [
        context.register(MainConfig2.class);: z: ~7 y. D2 K1 z
        context.refresh();# G4 n8 b) T# {9 V: c
        MailConfig mailConfig = context.getBean(MailConfig.class);/ P8 i# N+ B; ~, @
        System.out.println(mailConfig);
    1 M3 |) O4 y" w6 A}
    6 S  h9 w1 K6 {/ n% Y0 ^注释比较详细,就不详细解释了。
    3 n" q; a1 x3 T8 Q7 X& {3 F8 |. k+ L
    直接运行,看效果. Q6 L! p+ {! F2 f
    # X0 x) r6 X$ n4 n& G! s2 a
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    3 h( k4 n& C( k2 ]1 z有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。1 t: f- C: q2 g# \% q
    8 t# Z! v  t3 Q" s6 s  G. C
    上面重点是下面这段代码,大家需要理解
    , c9 Q* F/ O4 d0 _9 J$ G5 R
    3 p7 |2 ?# U) x9 n" U3 V9 o/*下面这段是关键 start*/3 @  C% y5 t( s0 w$ E; Z
    //模拟从db中获取配置信息7 [" w9 x. }, Z$ J$ O7 i1 I
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    3 x7 N  {# G8 H. n! ^//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    % @0 ]0 A& E' q$ o- @0 V9 `MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # r- u/ T! t  i) w4 J" {//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高2 ?9 V- s' }/ U
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! }! @. I# R& m/*上面这段是关键 end*/' _! i8 a6 Z/ q! C
    咱们继续看下一个问题& a0 L$ _& c; q& E

    $ e4 m, i5 }# D2 H2 q4 G+ @如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。! r0 S8 ~' `, E0 ]( ~# k

    ! V3 Y, m1 p2 x/ q" H@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    . J5 g0 O! X5 r
    / g4 E3 [2 w% M6 G$ v8 B5 u实现@Value动态刷新! O0 |$ a: t+ ]( K3 [2 H
    ' d0 ~' Z: i/ t+ q2 f+ T
    先了解一个知识点
    9 a9 ]8 T" x; W/ e4 D- E) j
    / |! y2 K+ D0 g( j这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。9 `* p9 e6 t0 O: \+ q
    , x( A+ j; x( ~, k4 L3 }6 i
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解2 k1 w4 I/ q. X* o# a" a. p0 ]
    6 l8 a1 ?1 I. T+ t, b, L
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:2 o# G* b1 X2 ]% U* m3 r2 a

    5 U4 Z) f6 }, MScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
      s% \  @2 X8 F6 K& c( c2 K/ A这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中. q7 d( z) G+ `  Y3 \

    . i$ M" K7 a% m% Apublic enum ScopedProxyMode {
    + C/ s& i* W" y! E3 D    DEFAULT,& p1 g8 V5 y3 T5 h; b( s, ^
        NO,
    ; z; R! E, ?+ Q    INTERFACES,
    ' n! r8 d. o1 p! G# Q    TARGET_CLASS;
    " A' {  i8 [9 [}: }4 C7 B0 C6 f3 d$ |2 j7 Z
    前面3个,不讲了,直接讲最后一个值是干什么的。
    2 Q3 G, D6 U( Q0 s1 t, |: J: _3 b
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    1 X4 ?" L8 q, z- M  }% k4 N9 L
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    ) W9 [% c* s* k4 x' E, x( p! l' S" P9 E
    自定义一个bean作用域的注解
    ; H2 A" u. f$ V% O. K: j
    3 _% k& p- @- Tpackage com.javacode2018.lesson002.demo18.test3;
    1 I" Z0 f1 P5 A& y% U" U; y1 L0 [( M/ w4 ^: T1 n! I
    import org.springframework.context.annotation.Scope;7 G' l, v1 ^/ d+ c3 i
    import org.springframework.context.annotation.ScopedProxyMode;
    ) T1 A9 G5 @1 t) s1 n# o. g" f' M/ L: ?1 r1 h/ P! |* _3 _' ^+ e
    import java.lang.annotation.*;# ], f/ L1 B2 x% r9 y- s

    6 R( f7 y3 N5 j3 I  Y@Target({ElementType.TYPE, ElementType.METHOD}); [9 {2 q. R- o& a8 t
    @Retention(RetentionPolicy.RUNTIME)! N4 L$ G/ ~0 `! j! l
    @Documented2 n% v( j  f/ o, I
    @Scope(BeanMyScope.SCOPE_MY) //@1
    " T  U( x3 s) U% S' _public @interface MyScope {
    2 t+ l' X, r6 h    /**1 H7 Z) H: y6 Z4 H' f
         * @see Scope#proxyMode()8 N5 w/ h" N, k7 u3 _2 Z
         */
    6 e) E6 V  x% u$ O6 _    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    5 X/ G7 b- c. `: B, \5 \}! m0 S, X# |- B; m
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。! @# x& e5 r+ B
    5 D- B+ z; h- @
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ! |* C+ h9 r1 S; }# Z' _) [3 D% y( I7 t/ N+ k* j! G
    @MyScope注解对应的Scope实现如下/ r, z, a, }/ N! V& r. j( C

    / D8 A" H5 E2 K% u! {6 e9 lpackage com.javacode2018.lesson002.demo18.test3;
    1 m1 [  |5 {+ G0 G# R5 g
    ) t. C+ R9 g+ h$ H$ oimport org.springframework.beans.factory.ObjectFactory;
    7 r9 m. E1 @4 W$ T3 yimport org.springframework.beans.factory.config.Scope;
    4 B1 @# j0 ~: iimport org.springframework.lang.Nullable;. ?0 ]  ]+ R( Z( n) ^+ R" M

    $ j& ~; ^/ n3 h5 c9 a4 W1 ~8 ]/**
    8 ^' O8 |2 S7 N0 p8 H- ~# F/ q * @see MyScope 作用域的实现
    $ a( R; J& x4 N% ] */
    ( p& U  b& N. ^public class BeanMyScope implements Scope {" S" S! h2 ?9 J; V  z! D" {" y
    0 Z, Q: V7 ^- ?- Y- a! x7 r
        public static final String SCOPE_MY = "my"; //@1
    : b* F7 S" k! H, g. B! p4 K! \4 z' i/ k8 I% ~* m# n) e( m# Z
        @Override1 b  }$ l3 J" T4 x& [
        public Object get(String name, ObjectFactory<?> objectFactory) {
    & A* |  J. i$ N' j4 ?4 X- z* e  O( k        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    : g  v& n! C) [. w5 k        return objectFactory.getObject(); //@3
    & Y: W3 g$ e* i. F, b1 ^    }
    " f, B1 ]9 g! Z' H8 Y
    ) q3 \, `, W5 z, l$ x; Q# i3 i    @Nullable
    ' V1 ~. ~% @- V    @Override; q% x9 P8 r8 e4 X) s7 W6 h
        public Object remove(String name) {: m! o- V4 `8 u
            return null;, X6 i7 u8 G; p) G8 z
        }5 F6 G* H* m5 D5 n8 L" S/ R7 }3 w

    $ H7 s' D! O5 Q% C    @Override
    1 @1 g1 r* p0 m6 `  p6 }4 i    public void registerDestructionCallback(String name, Runnable callback) {
    : ~& ~7 x3 |. V' J* G2 V" c# A
      F- F& d" V) l    }4 y. N3 L: k- }& V. j" G

    ! z6 I5 q* a$ b2 Y/ g6 f    @Nullable0 f2 G- |7 ]0 ]1 @" r
        @Override
    ; G" G: P/ y9 J- D9 V" Z  y    public Object resolveContextualObject(String key) {. }5 G# Y9 b; _$ x  h, I- B
            return null;6 ^: G1 B: D% ?$ ]. Y
        }' `8 q/ E" v  ]( _5 V/ f8 d2 T
    6 g' D$ x6 ^+ d8 A- n
        @Nullable
    ; M; e% ^* I7 G1 [    @Override4 l- U. v; P% G3 a
        public String getConversationId() {4 G  |2 b4 [' L+ F! o
            return null;' v& w7 x- Z5 c* o
        }
    ! X$ v# o& Y, ~4 v% a# \3 D, {0 \}
    , X+ R: n5 ^5 B/ W5 H; i@1:定义了一个常量,作为作用域的值
    ; ?' p1 w/ m' \2 |- P# _1 a
    ! O3 i. ?9 x8 v( b1 |5 o@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果! T* F' v5 D8 [& T7 X5 a0 F8 J9 o* w
      e% g3 {$ N( T( U2 n  _
    @3:通过objectFactory.getObject()获取bean实例返回。
    6 i* [! Y. H* l0 }2 m- N, n5 _+ |( N; C4 X5 o. b/ f1 O
    下面来创建个类,作用域为上面自定义的作用域: o  r' C. o; P9 ?; B3 j

    8 t# @6 A' f! ]% x  ]package com.javacode2018.lesson002.demo18.test3;
    " q% f' I: t( z
    # z* M, g" x. j1 ~( s  `import org.springframework.stereotype.Component;
    - f( D% M; i; E( _
    ' h  f( P! a$ _import java.util.UUID;' n) l' T. g8 J7 p

    * H1 ]: |9 t7 `- W3 d9 a$ I% I@Component4 w6 o1 v5 E) B; N
    @MyScope //@1 ) I! Z1 o+ p7 K" m; f8 N
    public class User {
    # ~( o" D% b3 C7 e
    ( t( b8 `, v2 H, m4 c9 _$ |0 c    private String username;
    8 X& r) U$ p7 I) Y$ T+ o) h' N' j& e! U3 H$ p7 `; V( E
        public User() {
    2 w& i' k+ Q, T7 H8 [& Q* E        System.out.println("---------创建User对象" + this); //@25 C4 Z* `1 s4 I4 r, d! e8 I! G4 Q
            this.username = UUID.randomUUID().toString(); //@3# r* M- F& ?. b4 _
        }
    ' s5 l' B$ x8 F( Z
    ! X2 `3 b, I& l, N' `1 P2 @    public String getUsername() {
    * F1 p% k- k( `+ h2 D4 P& }3 M        return username;
    & }9 v5 S9 l  ?  r% C! [, g2 u: c    }: `) `. o7 s$ r

    ' Z0 R" L, ^/ D. u1 B4 O# G! i    public void setUsername(String username) {
      g  S6 I" w1 `        this.username = username;
    % S( r7 E" Q; P5 I    }$ A6 D8 `, b& G% D
    $ }+ t9 A. Y% I' g  ^
    }! _. n! i6 i  P5 ?' V
    @1:使用了自定义的作用域@MyScope
    6 C! C' t0 P1 [" r8 @; I: O( `0 i0 U, ?- t' N
    @2:构造函数中输出一行日志, j5 S, P9 W( D& Z2 g

    , ^7 O* y; Q: g9 P7 A1 n$ S@3:给username赋值,通过uuid随机生成了一个4 `1 h& I; K8 {# t

    $ S% i- n$ }2 X来个spring配置类,加载上面@Compontent标注的组件6 Q& R# \8 p. [& O: [( k: f2 R
    # S& l1 J  b# m3 ~, I0 ?% b
    package com.javacode2018.lesson002.demo18.test3;
    0 O# r% y6 k. ?1 d
    ) Q+ R) V: u2 Ximport org.springframework.context.annotation.ComponentScan;: Z0 `% h8 h! @3 z. u3 l. h# O
    import org.springframework.context.annotation.Configuration;
    2 y: W. L) i0 v: \) W+ H# u% H  w# h, h0 ?& B* n
    @ComponentScan
    6 J. r# H4 Z# t6 m# v' {@Configuration! ]8 R7 z; _" P9 i- p3 n4 m! f' o
    public class MainConfig3 {
    - o9 ]/ }$ G. W" C}
    9 t) O8 A' R* O0 ]. h下面重点来了,测试用例
    % t* I  j1 W: h/ R
    6 x- h3 F  {& m) {* d1 Q# \@Test
    2 {: n+ t5 }, b9 ipublic void test3() throws InterruptedException {" e- \7 J! p5 I8 O
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" K/ F' W. c8 [- N' i3 J* C
        //将自定义作用域注册到spring容器中
    9 H, n8 ^7 B( q0 J2 I9 b& [    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    & J  s+ q& y# B# Y6 t$ j    context.register(MainConfig3.class);8 G+ x- {& }8 Q5 ~  \
        context.refresh();
    5 y" Z8 ~" ~0 J" a. S7 ~6 n& h  i, [7 b6 f
        System.out.println("从容器中获取User对象");/ R" S2 ~3 R) E  @& Z9 T! K. }
        User user = context.getBean(User.class); //@2
    : ^4 Q3 _, \. u; y( h    System.out.println("user对象的class为:" + user.getClass()); //@3
    " M, F1 |$ A- ]+ h/ A" C+ Q
    : {% W  v/ i- ^9 g9 P$ A+ `2 f( n    System.out.println("多次调用user的getUsername感受一下效果\n");' z1 ?7 ?  [/ j
        for (int i = 1; i <= 3; i++) {
    ( H) ?! M6 j0 P5 M2 B$ L1 o        System.out.println(String.format("********\n第%d次开始调用getUsername", i));& K8 ~" v# _; n2 ~3 w5 N
            System.out.println(user.getUsername());$ g+ ]0 [* O6 d" I+ v
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    $ G! L# k" d+ B9 l    }* q/ h( M! @3 h
    }
    ! U( Q5 g" C* _: Z# ^# S0 U@1:将自定义作用域注册到spring容器中2 ]* r" @) Z- o, w6 r
    1 Z9 N8 _5 S2 P& `/ v* @
    @2:从容器中获取User对应的bean" a; P2 C. C1 r, o
    1 b7 d; o, i( m9 M3 _% _) P7 _
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的- u( z6 q6 |. A( Q4 j' v
    ( ^8 M! a8 y. r7 P
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    ( \) e9 x, r, F+ |& h/ G" Z) x' H, k7 i- g
    见证奇迹的时候到了,运行输出
    ' k0 s  f0 X5 a, K8 S* s1 d
    7 q# |/ H4 a+ q  u1 K0 b6 v/ Q从容器中获取User对象$ m( H& ], u! A7 J
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    # `" N' o) i( M! [多次调用user的getUsername感受一下效果7 p( Q8 S' W, G! L# N+ Q* _8 N
    # h: U6 v0 o$ V1 m
    ********
    ! E2 O$ T( e7 `第1次开始调用getUsername/ ~0 e7 U3 N0 D) @4 l4 L- _
    BeanMyScope >>>>>>>>> get:scopedTarget.user7 g" g/ ~$ |$ p/ h3 c
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    # [6 q$ |# d4 l& d" V7b41aa80-7569-4072-9d40-ec9bfb92f4388 T$ K: o! E3 z+ r0 ?
    第1次调用getUsername结束
    0 l2 H# t+ N  F8 r. L8 U********# R% K) W4 l3 E& e* |

    * i+ M8 R7 v( B3 L1 o& f2 J1 n+ U6 v% ^********
    + W; I% @. {% ^. i第2次开始调用getUsername
    * B$ c1 g7 l- s) d$ f/ n0 {& CBeanMyScope >>>>>>>>> get:scopedTarget.user
    + I+ i# I, O- F! Y---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b* q# o  ^! i7 O, t6 t" H6 s
    01d67154-95f6-44bb-93ab-05a34abdf51f; V, S' T. `1 ]. O, J
    第2次调用getUsername结束0 A  T* J. A/ o7 v! R
    ********
    # Z+ t1 d7 o* N% q( D) _" b: Y  b0 {' {  g) S5 u  }- X
    ********0 i5 l" T# I+ h$ j
    第3次开始调用getUsername
    # Y; E% A, K. H6 Q1 kBeanMyScope >>>>>>>>> get:scopedTarget.user- ^& D$ P3 D& Q4 |8 V
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d154 o# j. m' T7 y, n: l4 M
    76d0e86f-8331-4303-aac7-4acce0b258b80 P" v( c/ j" F7 ]
    第3次调用getUsername结束, h5 j. o! b5 ^- G6 h# Z1 F
    ********- e# d$ D; A, f% `3 v7 `
    从输出的前2行可以看出:
    ) d8 t" \( {  ~4 R& |: T  o* O
    * H+ P  y# i  ^5 k2 L调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    5 n/ @  |" p5 ]7 T6 `5 w
    ' J+ D; J; k9 g7 V1 @! `, w6 C  d第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    # b0 e+ x! |( R1 u
    , e& l1 D6 y- f1 s( l  _后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。' s2 u" N% w+ }

    & ]: o) u% h; F; b通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    * A) D& G) j& P8 Q' o9 c+ h# H9 M4 S5 r" m
    动态刷新@Value具体实现2 L/ r4 d1 A) o, S4 u3 a

    0 r8 E; E) {/ c5 V1 y  D1 J6 v; s那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。. J, O" v) X4 `  |

    0 M9 s% g# \( c8 _! m先来自定义一个Scope:RefreshScope! @2 e! U3 Y! A
    ) c9 `7 A7 r7 M. b
    package com.javacode2018.lesson002.demo18.test4;
    8 e! L5 `" W) C) V" D
    ! D, S& p% F0 E0 Ximport org.springframework.context.annotation.Scope;
    ( W$ q1 R0 A! Gimport org.springframework.context.annotation.ScopedProxyMode;  S, d8 z& P6 n) E

    3 R0 e; @8 ?+ Limport java.lang.annotation.*;
    * |! {, T4 E  I8 a; W
    . a4 r- W# ?# I/ C@Target({ElementType.TYPE, ElementType.METHOD}), A- I( _0 N" P1 X1 A4 r
    @Retention(RetentionPolicy.RUNTIME)
    $ S1 e2 W% s: V: k2 M@Scope(BeanRefreshScope.SCOPE_REFRESH)0 Z: ~6 i; z' h/ \) T/ @' u
    @Documented1 R& ~2 I( k" J, ]* w7 x
    public @interface RefreshScope {
    6 f' x# z( r, B! Q5 H: `) @    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    0 A4 c( B  V* A$ m4 b3 s& [}
    & K2 ]1 q! l8 Y! T8 }要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    6 l! E. r6 g" z( d
    . m( n' q1 h& E* k9 A& m@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS1 [' x; K4 ?* q( V( I' Y
    # O2 d7 K: n( D9 a; c
    这个自定义Scope对应的解析类
    " T4 f) c" L. v3 _: s( l& T
    4 S- I% v/ \( Y$ Q: o9 a7 }% H下面类中有几个无关的方法去掉了,可以忽略
    ' `. j' _! I' q) A- o, A+ i3 Q+ d4 j: e/ j' v8 |$ u' G1 c5 C
    package com.javacode2018.lesson002.demo18.test4;
    5 m% x0 [  L2 A! W% e$ ?
    , A9 x: D9 Q8 |' O# W/ o- x' o$ v9 f, x, x( ~( W, l$ g
    import org.springframework.beans.factory.ObjectFactory;
    0 ~- O& i7 V; E5 nimport org.springframework.beans.factory.config.Scope;7 o* F' j6 _& v! k
    import org.springframework.lang.Nullable;' {1 E' ^/ O8 z3 H) e! L; g( Y1 N% y; w

    - q/ u+ r& f; m5 [import java.util.concurrent.ConcurrentHashMap;* ^0 O% U' x# X# F2 a( z  U4 N  S

      B* k* q5 R! Lpublic class BeanRefreshScope implements Scope {
    ' R  `2 K# Z$ J# Q7 Q3 S+ y8 P
    ! \/ U$ E/ w! p: }: R( Y7 R    public static final String SCOPE_REFRESH = "refresh";8 g7 x" z9 Y9 {# i: S$ P" \
    $ }# u! R, {% \, }
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();' [, m1 n. K. g' x
    9 V2 `0 Y3 t' h% l. t( c- _
        //来个map用来缓存bean( U- y7 d2 T1 D; A6 m4 C
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@14 N; B3 R: {! W
    ( _! p7 x; ]" M# G6 \
        private BeanRefreshScope() {
    1 V8 E3 Y2 k. f6 R2 b0 j    }
    : [1 X( m. s1 B3 E2 Z' M  ?. i. I$ w! q" |* D' \/ J6 m; d
        public static BeanRefreshScope getInstance() {$ [8 k0 R* b$ S3 ^
            return INSTANCE;" M8 e+ k: r# j1 v. D6 \& M# [
        }
    ' s) a: L0 y. G+ j6 }0 T
    9 B# M9 Z7 u* |, C+ `7 B& z& q7 A  a    /**  L- V' [- r$ c6 |3 G2 t( x, \
         * 清理当前1 {9 x1 V6 V0 M0 r4 Y
         */
    1 o) |+ l# F' g. o7 K5 r    public static void clean() {. n) Z" Q7 ]1 g4 g& H& [. E( _
            INSTANCE.beanMap.clear();' _6 n! L3 H2 s& A; c) }/ _
        }4 I* P3 @) j% e/ k9 P; ^% {

    ) j: p. ^# m/ r( d* s, J5 T1 D    @Override9 [* x( n: ^  c) ?- ^  @! a
        public Object get(String name, ObjectFactory<?> objectFactory) {
    . p. p4 W% _4 z        Object bean = beanMap.get(name);9 ]$ b0 d# Z; ?; A+ l
            if (bean == null) {
    % w+ N8 A2 Y1 g& {/ v            bean = objectFactory.getObject();
    1 G( W# v; H6 V3 s" f5 p            beanMap.put(name, bean);0 f( X8 H, |8 v) h" J% J( I
            }
    4 _) N2 X  A) A( f2 n# o. i        return bean;% z9 R1 D2 _! Q+ A  y& f
        }& o( V( Q: _: c9 r
    ) t, R. w6 e. L) _! n4 \
    }
    ' }1 a8 v) t5 a( C上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    : _; c9 C7 R: }- e; [" y  O' |
    . }& I: `1 P  M/ I6 P( z/ m9 w上面的clean方法用来清理beanMap中当前已缓存的所有bean! W2 _# L+ }3 k9 z

    $ |' @; w! K+ M$ D8 s来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope- r  Y- M! F+ C" C, h1 g

    , a& d- N8 b% ?) O  I" o# Apackage com.javacode2018.lesson002.demo18.test4;
    1 U1 M6 r# {  u) }( w2 L5 B0 a, p% N# S+ a6 \6 r- a6 G
    import org.springframework.beans.factory.annotation.Value;9 |& o. X. x) K$ W" K7 H
    import org.springframework.stereotype.Component;
    # g1 A3 m) y+ _" B9 s( Z' @9 D1 T, I% k
    /**
    ) L  I! b, u/ x; o' b) T$ @- K7 M * 邮件配置信息  y2 A6 B4 a' a. d7 K5 o' I
    */) I" {4 ~4 G+ E9 v& e. b& @
    @Component
    " G9 h3 I( Z: D# n: ?4 ^5 H( I+ V@RefreshScope //@15 J! e, N' j2 X, p, x3 ^, J& M
    public class MailConfig {+ z! E5 Q9 j6 W, K; P1 d; C- L5 |

    % i4 ^# `9 \4 {6 \8 V8 z    @Value("${mail.username}") //@2
    ' r  J& g/ C2 D+ c1 {+ N    private String username;
    9 i9 s2 [" ~# @6 G- D% i
    , e+ v% Z! j' c( j    public String getUsername() {
    ( F& l5 W  J5 p( g        return username;* A3 J" q; n0 C
        }# s: H2 u1 A$ O) s8 g  U! w% e

    7 f) g7 U; e+ t, Z1 w+ S; t    public void setUsername(String username) {
    * J& h1 X$ _# y- f5 @/ G        this.username = username;3 G* b4 {, o1 a! \+ Y
        }/ |' W& t0 ?2 e3 S5 I- W: g

    9 N4 I  N  F* V. b, K% T) _2 ^    @Override2 a5 K3 P. g6 l8 n9 I0 c. T+ l0 e
        public String toString() {
    " l/ @/ V, y  ^2 ~/ F        return "MailConfig{" +
    $ q7 |- ^( {4 Q( I: @" c2 A+ }3 w2 s                "username='" + username + '\'' +
    # @3 j6 `; J% [2 \) K                '}';' v8 a( o4 s3 M/ b1 i
        }
    : z9 ]% o1 t8 K/ b( |}: B* z, j( J; N( m  _6 Z) ?) B1 t5 j
    @1:使用了自定义的作用域@RefreshScope
    / c, h1 O0 C7 i5 w0 Z% }' |3 s+ O; y9 i' p
    @2:通过@Value注入mail.username对一个的值* h( k% F& u6 E% ?& o, `/ M% t
    - @; A% ?7 F& \  U* x
    重写了toString方法,一会测试时候可以看效果。5 s# b  ?, T7 ~/ D8 U) ~

    ! N1 `; p, B" L再来个普通的bean,内部会注入MailConfig
    0 d& q6 ~  Y1 Y1 {8 }: f% K
    3 z/ c, u$ u" w% rpackage com.javacode2018.lesson002.demo18.test4;
    % r& R1 h/ _( N- j" J  G. {# }6 j& B4 p2 E3 n! P0 e! L) K) \
    import org.springframework.beans.factory.annotation.Autowired;
    , J5 N: c8 Q/ |7 x3 V1 Rimport org.springframework.stereotype.Component;- `; I* Y, u- p

    0 {0 x: E% ^6 d. `/ F@Component
    * G& i: G2 g6 o$ x( J2 tpublic class MailService {
    # @5 b( D' Y# i; J7 y0 f4 x. m    @Autowired) S/ E3 a' @+ H" A& |
        private MailConfig mailConfig;
    0 |3 @2 i6 {* k
    ) G0 v1 k% D/ L  y  G; j, v, O  z    @Override+ ?7 J& D3 k4 \( ]# l% U
        public String toString() {
    ) ]7 T& s- ?5 R, m" P" \        return "MailService{" +
    9 k: I( u( V4 ~8 `' l                "mailConfig=" + mailConfig +5 g& N4 Y4 j8 u% O+ _$ C
                    '}';
    8 J( A# Z% W) W    }
    4 F3 f3 X, x1 r}
    4 N0 x, F0 ?8 w9 ?. E9 @+ C代码比较简单,重写了toString方法,一会测试时候可以看效果。+ w' H; A* `: B
    4 b, X" `% Z. v7 ^3 i; y
    来个类,用来从db中获取邮件配置信息7 z: F1 N& q; v% G

    - ~4 i! w$ D& p: x$ [package com.javacode2018.lesson002.demo18.test4;$ s" |2 f2 e% A: _* N9 ?! M  R
    . }% m5 _2 A5 i, o4 x
    import java.util.HashMap;9 a$ [. }9 x  A
    import java.util.Map;
    " q& P4 J4 R/ [import java.util.UUID;0 W( o2 W) e4 @
    3 C: A) ~0 t, @# x
    public class DbUtil {1 {  i0 S3 c1 c$ ]. [6 ]
        /**
    / |# I: W' |; h) Z; E     * 模拟从db中获取邮件配置信息
    0 @$ @% B( \; h/ J- B" W9 w     *
    * A: z8 Z2 K# H     * @return" |1 P9 }' a. L" j. \4 L4 T  J5 U
         */
    4 j. E# a' q( @1 J/ {4 r    public static Map<String, Object> getMailInfoFromDb() {7 @6 d1 R  [+ y* A* ^
            Map<String, Object> result = new HashMap<>();
    # x+ p. q. v; \9 ]; a( X: p        result.put("mail.username", UUID.randomUUID().toString());0 x' X. O/ {% u" H" q, c  x
            return result;; h5 L( j9 O+ _' h, L* P' i4 B
        }# |+ k0 O- |+ j2 Q5 C. d
    }5 T* K' w" C! L
    来个spring配置类,扫描加载上面的组件& ]" l' H) b1 L! A/ Q/ S7 Z) ~

    * f+ f( Z: M7 A, p% Y( _package com.javacode2018.lesson002.demo18.test4;7 Q4 m, d# x9 I5 {' M) n/ L7 u; c
    . k; M, d. i, H7 N
    import org.springframework.context.annotation.ComponentScan;
    - H4 M$ [2 \* A7 w$ u9 f6 aimport org.springframework.context.annotation.Configuration;
    + R% D3 X' \6 U3 g* H
    " Y9 F9 E( o, j: T: A) X( Z# e@Configuration7 q, w9 ]* n& K* c! R
    @ComponentScan( r2 v1 h2 V- w6 d2 ~6 ]. q
    public class MainConfig4 {3 G. a4 E' h; M
    }
    ! T3 ]# r! U/ ]! f  T6 P: A2 ^3 }来个工具类) W5 k) g5 s; i" F% y

    1 Q5 q, y  b2 |+ e/ m% Y内部有2个方法,如下:
    / `7 q5 b) x' ?$ `: q) a1 O2 Q; @9 h! ?2 X, r  L! u
    package com.javacode2018.lesson002.demo18.test4;
    8 B6 W6 z& i4 F% x* b" p3 g9 }, C. D; M' R9 U. a
    import org.springframework.context.support.AbstractApplicationContext;6 S3 ?2 u( `+ h
    import org.springframework.core.env.MapPropertySource;; N* L0 Q7 {. o) S; @
    8 I/ l7 n) f/ t/ v
    import java.util.Map;
    4 R( I2 r/ }$ M4 r5 @- O# F2 r" ]) ~! w9 A
    public class RefreshConfigUtil {
    ( Z  A: y' H( |* H, k4 G. @5 y    /**! [* ^7 V  _: l) u! ]2 c5 D3 _
         * 模拟改变数据库中都配置信息& {5 x) y- K& J* R, V4 t: Y
         */
    6 E, O; R2 N9 T. ]    public static void updateDbConfig(AbstractApplicationContext context) {
    & e* f9 o( z7 ]7 B. [7 W$ v) m        //更新context中的mailPropertySource配置信息1 I2 \& I/ x7 s- y
            refreshMailPropertySource(context);
    . B$ Y% V3 V6 u! O0 P
    / l% G3 o! w5 F% t5 H* K( G        //清空BeanRefreshScope中所有bean的缓存
    + r- G3 o; G% C6 {, @: U+ q        BeanRefreshScope.getInstance().clean();
    5 g* S. c- X; `, i/ V' X$ f. t    }" G0 w) g0 D. B$ L# E/ |; o5 S
    ; e2 @1 d5 \0 W  N- J) l# x) L8 \0 T
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    ) A9 f! _- @# @$ _1 L& V7 D        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    " P3 N9 P8 `" g; O: W4 L' \) a        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). z5 V' ?3 a) N5 N, ]8 `
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    / k$ m( H  [/ B) s        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);# O# t6 J3 G! [( x
        }; W8 ~9 |# ~* v% |0 H4 |  q) V9 T

    9 J9 W: A& N5 i}7 v- |& F% e# J6 W2 V
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    8 A2 }* Z( a) _4 H+ A. l- x2 j0 Y9 x! h( m
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    2 N0 t* m0 F& G. P0 {) t9 [; r8 I7 r( z" `8 Z9 t
    来个测试用例
    3 n) @* g1 K5 B( M. c( {
    : x- B8 e3 P. b6 ?@Test
    + O5 A& e$ F* |% t- epublic void test4() throws InterruptedException {
    3 J. F& b# N# }6 B& J- l5 o    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();: _+ S5 k- Y& }1 ]
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());$ S8 ?2 \8 [/ \/ ?4 p
        context.register(MainConfig4.class);0 m( N7 G  t2 w7 ^
        //刷新mail的配置到Environment
    8 B' B/ d2 X- b" I$ l    RefreshConfigUtil.refreshMailPropertySource(context);# a5 ?# q- n" z# \* g% u6 R8 V
        context.refresh();
    8 {& H% O, J. }5 L3 `
    9 [; Q' A3 o: t; ]% ]    MailService mailService = context.getBean(MailService.class);: x) J1 W0 Z  J/ h1 @
        System.out.println("配置未更新的情况下,输出3次");, ^$ K3 b+ E( _  X8 U1 z1 Q" D
        for (int i = 0; i < 3; i++) { //@1
    " w* Q9 \" a( S! E3 V' b, }( g. L        System.out.println(mailService);9 q1 @' P2 z  T
            TimeUnit.MILLISECONDS.sleep(200);
    7 b9 H+ R) f: o- Q) }, ?0 ]2 b1 _    }
    2 J: r1 o  N$ ?( W, }1 q" r
    ( |9 n1 D  ?6 e* l$ H$ H1 W2 i    System.out.println("模拟3次更新配置效果");4 ]7 w" u- O' f3 A& L, b
        for (int i = 0; i < 3; i++) { //@2
    : T+ Q( s. V( y% D5 l0 X) f        RefreshConfigUtil.updateDbConfig(context); //@3
    * d# l5 d; C/ @# ^. H6 [        System.out.println(mailService);
    & h# C" C* X4 W9 [        TimeUnit.MILLISECONDS.sleep(200);
    + P3 K$ u" {% ~1 d    }" Y- l  I+ W* i0 V3 |5 l$ N
    }8 P$ `* c, W; {7 F, Q4 W" C
    @1:循环3次,输出mailService的信息* @3 z9 |0 |3 e) A, p) A; S
    $ Q; Y$ j) g% E2 _6 K, R& e
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息: _( _4 R8 c2 h* X! g  V
    7 h+ Q: L3 o5 ]- _- ~& t
    见证奇迹的时刻,来看效果
    * X& m0 m* ?0 k4 b' Z( {; T7 z/ I0 s4 I3 S4 f! y1 v
    配置未更新的情况下,输出3次
    & J/ q0 [2 }: tMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    , I3 q# v! H% I3 Z" q3 o+ aMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}( v$ `6 D8 i, [, c8 v' L/ t4 ~: W) n
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    $ r! v% X% X) A- A/ R模拟3次更新配置效果
    ; s, Z* s: g. ^. H( n- vMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}6 R7 m* x% R) v1 s- ]9 B- F
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}+ K9 ]3 e- a5 o8 u; }' ^
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    4 N- X# A+ B) v! w: Q/ A# I上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    / O4 P. J# t8 [+ ~& o
    # ~- Y" X' i9 W) v; ^小结
    1 O. l1 g+ d1 R& r; P+ Q
    $ `* G  `5 j, U: Z" G8 T动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。3 T5 r" w! K+ Q2 T1 O
    % Z& U) Z/ p, U. Z- G  Y$ v
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    % }% d2 M5 O3 @) p* _, F3 A7 ^! M; c; c# h. F0 I' o
    总结
    0 x8 y8 z- e; O: ^; n7 o. C- G: r3 w$ D/ Z+ v: [
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!7 q2 t8 C- G2 _* ]9 n
    : N4 N6 C4 P5 Y/ v. S
    案例源码! ]5 u8 V2 w- |0 F0 x% x: i

    ! ~8 b" V% V( M+ N) shttps://gitee.com/javacode2018/spring-series* P7 @/ _: ~4 X$ n, A
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    6 B  r2 X. X% I* H# I
    " Z3 G' U2 r+ V8 G  }* @3 d. eSpring系列# d2 F6 Q9 Q$ g! S% C/ k6 e+ g
    ) d/ G7 C/ a4 T6 @: a
    Spring系列第1篇:为何要学spring?" x' r* B" o6 z# R1 x
    9 a& R5 \# j- C$ L) [: V- @
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    8 Q: x* t  B/ {2 g9 l: G( i( }" u( E" e) O) k' w7 h
    Spring系列第3篇:Spring容器基本使用及原理
    4 `* E$ ]! O5 ?+ K2 s
    " \( o& O! L# `$ s3 M2 K8 hSpring系列第4篇:xml中bean定义详解(-)
    4 Z: h* u2 ]" }5 ~1 E/ P) a7 S+ u* R2 l( x& a. L
    Spring系列第5篇:创建bean实例这些方式你们都知道?/ W% \1 A; K$ R* f

    0 g4 P# g2 a9 a1 R& @0 [  N& e7 jSpring系列第6篇:玩转bean scope,避免跳坑里!
    $ ~* |' {1 p: F' m* a
    ' X: k" h: W: ~6 T' FSpring系列第7篇:依赖注入之手动注入
    * p0 o, ^4 M% ~1 ?6 [" {4 o0 r/ Y8 ~: ]1 M0 V; ?
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    1 U7 F  Z0 C' d/ {& k
    " D9 P- a8 F, G" jSpring系列第9篇:depend-on到底是干什么的?; B3 u7 z; K  @$ s
    # l3 t/ |! _' Q1 f/ j1 [
    Spring系列第10篇:primary可以解决什么问题?
    ; i: n' T4 ~3 H0 R( V0 s7 L/ J1 ^# A+ V" J5 b$ t
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    $ Q" P% F# F1 j& u9 j
    0 q! r5 \. }" `+ C! y- SSpring系列第12篇:lazy-init:bean延迟初始化
    ( a+ G. y. x+ ^" Z7 a* c0 I! v& `2 G. S# ^8 [. h" @) h. Y
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)" g" t2 n8 g3 _1 W  k( H6 o

    4 S1 M7 ]- O0 B" o3 bSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    3 G* t% k3 p* D! B
    3 w) Z( ~' _  GSpring系列第15篇:代理详解(Java动态代理&cglib代理)?- {! H  \' u8 l) C) B; Y2 L' [0 M

    3 @3 q( i9 S' rSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    , i, t9 \* j& e* }, S- F" n  Y, ?3 h  x; }4 \; d+ a0 G2 R6 k8 O
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册): c8 S& x: W/ I# ~

    $ L% t% J7 r  [4 cSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    2 [& @8 B7 g, N4 q' z" X' N$ D! j& Q! I( U
    Spring系列第18篇:@import详解(bean批量注册)/ e5 i. M+ A: _  b2 k
    + D8 ~7 j+ }. m
    Spring系列第20篇:@Conditional通过条件来控制bean的注册* K' Y4 _$ e  m$ A; ~
    0 L9 e5 y  M: p* z& D. x
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    , {3 Q( k7 R& S- j& K. m: u$ d$ A9 V' Z
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解& G. ^% M  ~6 x8 }- |) V; }. T
    3 M$ a# w5 W) t# L7 E
    Spring系列第23篇:Bean生命周期详解
    : N( I" X4 d* t& N  [" U8 Q7 Y$ u8 z7 E* s+ E$ A
    Spring系列第24篇:父子容器详解, A. F: w  H, b. z$ M
    1 l* w3 P% v1 r. ^0 L+ ~
    更多好文章8 e# X7 |# C; i' V! M+ n. I& x( q
    , g6 k: `- I, }& u; E) [
    Java高并发系列(共34篇)
    ) X9 d  z' c$ i+ e% I- A" v6 z. }. |; g0 s% O( k7 O. \$ o
    MySql高手系列(共27篇)" p: y) j& q1 t. p$ w5 u6 E6 Y$ y3 V
    , w9 d8 g- Y; o
    Maven高手系列(共10篇)- O- m/ F6 X  z- }( `

    8 l2 S% `2 l8 f, q! t. yMybatis系列(共12篇)
    & b/ }& h; @0 U; I. p2 |, k5 T6 \0 `4 y+ v( u
    聊聊db和缓存一致性常见的实现方式
    - z: d6 |  U' I. a8 V+ L8 {
    # C) Y" K1 m3 ^' U$ ~接口幂等性这么重要,它是什么?怎么实现?) a  ~$ O+ c7 P( [
    : N/ `& E$ V9 Q: ]/ Y8 O3 h  b
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!- h- d3 j' p+ c% v
    ————————————————
    $ e( c0 T5 g. Y; S版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' W. s$ Q: B  W: s; N原文链接:https://blog.csdn.net/likun557/article/details/105648757: y# b( e/ W% U2 B
    , o  U2 Z5 p9 H! F1 l4 m

    , V2 B4 y' r! D, |
    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-9-24 13:52 , Processed in 1.305750 second(s), 50 queries .

    回顶部