QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5449|回复: 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!
    - s. a: R8 ]+ t. H% r疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!6 b6 d& G/ e/ N. q) L, R' G
    8 q$ X& K7 S2 w2 v0 j+ J
    面试官:Spring中的@Value用过么,介绍一下# h+ U& [; B' K& w
    " U0 z, w! e  P4 @
    我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    & n4 A; N# r. F! b; e
    : n- C' z8 _+ j+ A8 [面试官:那就是说@Value的数据来源于配置文件了?
    ; O  `$ X1 c+ M! B& e' i! d# l# n1 v; T& o# T# s7 ]6 @
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ! E8 C( m0 o+ g# D8 ^3 m( u3 V# H3 G; _7 P; n! l9 |6 l, ?
    面试官:@Value数据来源还有其他方式么?- Q4 m% D9 O) ]9 ^6 V& B" d# I( F

    ; }% O5 N8 V& E9 b# N3 S; W, z我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    6 y. Y% R  A; k2 _. l9 f
    7 b7 g7 `) u# @( {* o  ]面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?3 T+ ~3 y# a* w
      b, j1 p) z! }# y8 A
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    + E- v2 e# h& W- {
    8 J+ h( l0 v) Y$ z" I面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?( J  T3 N$ N( a, p# p
    7 f# o- f$ `+ F. h2 s
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能( N' q1 q  p5 U8 v1 V; e

    # G, k% E" K9 h$ |& s" x) r' r. [# q面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
      \5 Q; k, J9 ^1 W, h% ?
    ' g: U- W, b  ]* \9 H8 o5 {: ~我:嗯。。。这个之前看过一点,不过没有看懂2 B% U$ P& Q7 t
    $ ]1 x3 X% g4 R* w5 V7 j" q
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    * i6 U% V4 g2 l: }% n+ ]
    " t$ O2 v" s" L( f) W8 {) z我:3万吧: z, L7 w( V2 b9 i; B7 }9 X$ Y0 O

    4 C/ g+ y$ q, f3 M$ `; M3 D面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    5 X0 a6 z/ l; ]: `! b! ~' l' [  ?6 I! i1 Z8 L2 Q0 y, E
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    4 ~' v0 Y1 m+ }  i, F7 Q" E$ ~5 r, G  q. y, R0 \& Z" b
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
      ]! f8 Y2 p9 X! I* n, X- K+ T  x* s0 m7 ^- l
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。' J6 C" j) |$ G9 F9 h
      x: C: q  D! E3 w$ W
    这次面试问题如下# x* d4 K( E$ Q

    # S) Z8 q1 S% R) s! d$ B9 w& i7 t@Value的用法" n; D* V  |  X- u# x
    * X$ C& f6 P/ O
    @Value数据来源7 C3 ^! f+ ?( {! S6 v: V
    & c+ g/ }1 `  o" u9 F
    @Value动态刷新的问题2 l# y7 s) A  M* D) L  v
      C. F6 w) f+ y" C+ v
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。. W4 o8 I4 {5 O" a( i9 `+ }0 ?4 Y
    : A+ q; c5 d! v7 J# o( z
    @Value的用法
    " j2 g$ r" y) d! V
    " T, x0 I. V# [5 Y: w系统中需要连接db,连接db有很多配置信息。5 Y, m& Z1 z1 [/ e1 i8 L
    , }- H$ Z: f( z3 \8 U' p
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。7 X- B7 @9 j# ~9 h+ C: u- p

    ! O+ F/ P  W/ D3 e5 m还有其他的一些配置信息。- p: f/ |! r4 G( H" L6 Y2 Y8 j

    + t6 h7 T6 i. z我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    ' Q0 k+ L  i: |) u2 w* J2 C* {$ s- p$ x5 |
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。% @( ^5 Z* f8 \+ Z( G
    $ t6 V4 m+ g& r2 ?
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    # D6 @/ B* `* n) \- L, s9 R8 u6 d# Z, u
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    2 W1 V3 e2 E5 O/ k, M+ e; K9 {& R1 N9 ~
    @Value使用步骤
    + k6 o9 a* V& K' |' C
    $ @& M- A( I* ^, z3 |! p步骤一:使用@PropertySource注解引入配置文件) W3 ~" u6 B; V$ }

    4 f3 p- A! h/ \+ n5 Q/ P# l+ O将@PropertySource放在类上面,如下
    : y* j9 f- M% s; ]( N; d
    5 S9 ^% [6 \( ?@PropertySource({"配置文件路径1","配置文件路径2"...})
    ( m: W. G& [/ V- P) _@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    ; C9 x# i( t# D0 X5 `! D) o& p: B3 |7 v3 s( l9 k2 Z1 Z
    如:+ @- T: K) w. b/ E4 i: z

    0 Q+ y: u6 p. _4 A7 d  V. G@Component4 Z$ c7 Z$ h. p
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})5 E  Y1 S& @' w9 a
    public class DbConfig {
      d" f4 u3 ~0 d) q$ d}3 Y6 T" p# _$ [* E& X$ ^! O* ^
    步骤二:使用@Value注解引用配置文件的值6 d1 }) Y1 M- k* e2 l3 @, P/ e
    $ w9 J4 L9 H% T7 r0 j5 a
    通过@Value引用上面配置文件中的值:: D& J4 |( a: I3 t$ r

    % g3 ]' O1 C' a语法
    * b" ?$ W9 Z2 K  O, p
    - H# C7 a3 K1 _2 Y2 _1 Z* R% W@Value("${配置文件中的key:默认值}")
    + ]1 M; |  b, t1 J# @5 A  y5 l4 p@Value("${配置文件中的key}")3 F3 l) T5 j& J' v! x, u( z
    如:# N5 V$ m- Q) R3 L4 E
    . Z2 B1 w+ A8 p, o9 s
    @Value("${password:123}"); l- I- `3 ?) u- l5 n! ?4 D
    上面如果password不存在,将123作为值. \( f5 }% n6 U0 @
    2 A( L8 ~" X# O$ ^' t+ T. f
    @Value("${password}")/ P  A3 Z9 J6 W6 i; P; U; i& U: c
    上面如果password不存在,值为${password}
    ; j$ u# W8 p% s8 ~8 A
    4 g/ k: h) p4 r' @" t) m* s  Q假如配置文件如下8 D) F  \2 _; H6 [* I) d: n6 F
    9 ^9 i4 Z: }/ P; x' q  K
    jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-83 j" m: X* [9 _3 p. m, s& ?7 r
    jdbc.username=javacode
    9 M! S& M& {5 o0 d# d+ Xjdbc.password=javacode9 |$ v. X8 m& q7 o/ x9 h4 @! G& ^. d
    使用方式如下:* O) Y/ d, G: F

    % s; H+ E- r" M1 o' r6 l@Value("${jdbc.url}")
    0 F- G' @$ g, s/ R" Wprivate String url;  r. z5 b% z8 O
    ' i$ u  S: ?% G4 @& H
    @Value("${jdbc.username}")8 n% z; b1 L; u) L. W# L, r
    private String username;
    6 Y$ l" z3 j- N- J9 L4 l6 T7 ]
      `) c- M- g" u% W/ V+ D9 f* b* t@Value("${jdbc.password}")5 k: k6 P5 @" V7 C; c. H/ Z) a
    private String password;
    ( e9 X7 ~5 M$ H$ F下面来看案例  m* c7 ?; H, I' w& D5 r  g: e

    3 |& W# R) E. z4 I案例- Y  h9 A  j* z7 A- }+ O' q

    ( D/ i$ E7 y+ Z" s- t1 w  t: p来个配置文件db.properties
    ; b/ `3 e; z' p( m5 o
    3 F) y  ^1 {) V3 s$ @/ Qjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    # k' ^& R8 a- d6 bjdbc.username=javacode+ ^* j& r4 z  Q# K* b3 z/ I
    jdbc.password=javacode( g0 a7 ?2 T5 ^$ T* D, x
    来个配置类,使用@PropertySource引入上面的配置文件
    + I+ U& ]8 s0 p6 _- ^
    7 V* [* T6 S+ X+ W! X  Hpackage com.javacode2018.lesson002.demo18.test1;* {4 q4 r* O7 z0 c5 X( {' P$ N
    & [; O( X1 f) o' |  j
    import org.springframework.beans.factory.annotation.Configurable;
    + s9 ?- W4 V5 ]9 f8 B3 a3 w! timport org.springframework.context.annotation.ComponentScan;
    7 s- P3 `) _5 k/ s. _$ [) iimport org.springframework.context.annotation.PropertySource;7 j3 L0 e; W4 K

    " t+ O( I# H! }. n# Z% [% x9 L3 d@Configurable
    * ^# E- W: O2 B* }% U@ComponentScan
    2 t' n3 z9 K( x@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})5 B$ K. ~- q0 u# {
    public class MainConfig1 {
    ( o, i4 w1 J& L' {* `8 d+ R}
    2 n; b& p  [9 H# `. X7 e来个类,使用@Value来使用配置文件中的信息+ d/ _& s  h3 Q& X

    : h+ d7 H# |/ m: mpackage com.javacode2018.lesson002.demo18.test1;3 @+ N7 Q+ {- Z- H* r1 l
    4 @; Y0 v% b: Q3 ?1 e9 _
    import org.springframework.beans.factory.annotation.Value;
    $ s/ P! N* L7 @$ gimport org.springframework.stereotype.Component;
    " P4 n; w6 B) L6 S; C' `0 z
    1 \+ Y: u$ o/ U9 N@Component
    # d% Q% U/ r1 @: M7 x9 N; h5 Lpublic class DbConfig {  ]) t9 D; L1 T# [

    3 @  _& o! H5 v  J    @Value("${jdbc.url}")5 c5 ]- ~) P& n. Z, |
        private String url;: {; t+ @# A8 b/ Z  Y6 v
    ' o+ P( f0 {$ K3 d4 W  \: m. X
        @Value("${jdbc.username}")
    ! a% _$ c. B! t! D. Q3 m! u    private String username;
    / r1 _: e7 ^- n* Z3 c8 o0 _; r0 B
    # l" K0 J- K1 Q, p9 _2 o1 K    @Value("${jdbc.password}")
    $ @; q. c) m7 `5 q/ {5 W! O% _    private String password;4 T- w- q3 v4 T
    / Z  z, |+ U- ~' S' H& {% D8 I" ^
        public String getUrl() {
    $ ]2 z( T- L7 f3 O$ R3 f        return url;
    7 u' ~1 ]8 x% T' c2 |. b* J    }2 A$ S' W( Q& Z" L. W
    3 C5 l( r5 n5 Q( d( H- Z' H! a" I
        public void setUrl(String url) {+ @; j4 u3 I" P2 s
            this.url = url;
    8 |& P9 I- ^( @. r0 M; @1 i  F* B    }
    * V& ~  F* x4 ^$ [* T
      E+ E; [$ c5 x# U    public String getUsername() {1 v6 z+ ^2 y! J5 t2 ]
            return username;
    . U8 |" D* e: e& X  O( O    }8 y1 p+ Y1 D5 t) o" h& c

    3 ^8 ?% U6 I3 J: A% {0 m    public void setUsername(String username) {
    2 V2 c4 q" K$ J9 S8 b        this.username = username;
    6 n9 u. f2 p" y" F/ ?# c    }
    " @- L' Q  F  U- ?! ~$ J  n8 U- s$ @
        public String getPassword() {* g0 J1 {* |% D  Q6 h
            return password;7 ~3 Q' K+ S3 w' W& M9 k/ }
        }# u1 F" a- B* z3 L6 A8 |0 r

    ' t) r- ?6 y! g( W& q6 R6 F    public void setPassword(String password) {
    4 L% t. Y0 R* {8 G        this.password = password;
    . e# d5 b) b3 }8 @- ~    }; u* k9 w# j  D* d* W/ v) R
    9 y' y, h2 _6 `  O& i
        @Override8 Z, s! }; h' T6 j( J( t
        public String toString() {
    7 [. M. h% p5 U" _* @        return "DbConfig{" +8 T4 E2 y+ V5 Q" a* D- b6 h
                    "url='" + url + '\'' +
    ! v0 [7 N, P, d+ v6 c  D9 a! m                ", username='" + username + '\'' +' q. m( O8 n/ F4 L3 I* y
                    ", password='" + password + '\'' +
    5 Y: R! l4 q6 t8 y- Z' T5 S                '}';
    # F) w% H/ z5 n. Y4 U    }9 Z& K8 M# K6 n! W- S
    }
    3 _( w! }- p' l) P上面重点在于注解@Value注解,注意@Value注解中的
    ' I9 a' B4 j$ z* Z. ~( F! `! k
    来个测试用例3 p4 f) u7 C: p- Q- {# f
    / a& X6 p$ r* |- s: n5 U
    package com.javacode2018.lesson002.demo18;
    - A1 q4 L% ^3 d/ [+ n3 Z3 h4 @
    , X$ N7 @7 q+ ^import com.javacode2018.lesson002.demo18.test1.DbConfig;" \+ Q6 F3 B! b
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    - S9 h0 c, R! uimport org.junit.Test;2 x2 C& P9 `" y! {1 _" C, m3 [. @
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;% \2 h1 W" |$ B% N6 E

    " {! G5 Z/ m7 \public class ValueTest {7 M4 A* y% w' J/ o9 h6 |+ |  m  Y
    6 \3 z3 p$ K1 }( K5 Z
        @Test
    - d: ?* K1 [' @3 l: n    public void test1() {. Y  E' `% y. G# O* s
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();) g$ U) n, P' }1 L: g6 H$ A: P# m
            context.register(MainConfig1.class);
    5 [$ _; ?1 w/ c8 c- H+ w; i" T        context.refresh();
    & @. F2 [' e  q+ h3 N
    : V$ Z2 @  t- M/ O        DbConfig dbConfig = context.getBean(DbConfig.class);4 A9 \; ~5 x3 l; ]
            System.out.println(dbConfig);- _7 g+ K( b" h7 }! w( H+ \$ Z8 W
        }
    5 x) N4 H8 n% S5 }}8 x: {1 x& T( N, j1 y
    运行输出
    ; x/ v) ^3 D, N7 ?' @+ G" }4 ^/ s- A. M
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    * Z8 n) F' b  o+ V5 C0 ]2 u上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。1 p5 \# H; M: q. e+ }* n
    9 W- c' t! \' r8 Q
    @Value数据来源6 \- D) i! T3 U3 J$ s
    / r7 Z. K; P( r: q0 c
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。, V  O4 G1 R  w) l# G

    3 F8 X# E* ?" E, U, ]8 D3 x6 _我们需要先了解一下@Value中数据来源于spring的什么地方。  ?8 Y7 K# L) U9 }6 S! z: Q

    ( \) {3 D" X/ ^5 m2 zspring中有个类8 B+ e: Z. w* ]- m2 S( M
    ! X! C) L8 Z$ x. d$ I
    org.springframework.core.env.PropertySource4 P  v' u- @6 O5 B
    可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    - Q/ L+ {: X* w7 S; V/ {" r) _6 i
    % ^8 Q* K# r  R; Q1 Z内部有个方法:
    ( Y! |' L1 [# b, c! `6 @1 \' A* k' K# o9 P2 V& V
    public abstract Object getProperty(String name);% W* x+ s) v0 J. i0 i* K: o4 W7 S
    通过name获取对应的配置信息。1 u- S; p1 W( I' ?/ e3 ?/ K* S

    1 p! {8 x# m4 Y系统有个比较重要的接口* x8 u# o, e" J4 E+ q1 u. |6 |. ~" v/ O

    / z* ]- @9 j8 E: [3 l# q2 \org.springframework.core.env.Environment
      g+ ~+ v& M) b2 R用来表示环境配置信息,这个接口有几个方法比较重要; A! _1 J, T% t. J4 ?
    4 K4 r7 D" r6 Y$ v4 C
    String resolvePlaceholders(String text);; j+ U. b2 I- b3 @1 w! z
    MutablePropertySources getPropertySources();
    8 M7 a4 l- w  PresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。9 f7 M0 ?+ L* E3 W. x9 U

      O' |* {  S0 M/ v# j: G1 E* S2 J$ BgetPropertySources返回MutablePropertySources对象,来看一下这个类. U: D2 v  l) U7 D( Q  Z

    $ @1 R* t. }/ _) A5 K/ Npublic class MutablePropertySources implements PropertySources {, [8 f5 G8 d+ n; q/ |4 s; C# s# m- a) Q

    ) d$ F) c! s. k, T8 T. E3 q    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();# m+ Y% X; ?  ^$ t! ^/ y8 }0 @

    , W. P0 F2 F/ s4 J' ?: I! e7 `}
    6 w, J* Q6 l  F内部包含一个propertySourceList列表。
    9 T. ^: N3 I3 o9 z1 p
    ( i0 A# Z- u$ yspring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。% X6 m9 \3 g* i! A4 Y- ^& i' s) G3 m
    7 O6 Z# l# N- ]5 H
    大家可以捋一下,最终解析@Value的过程:) g: I9 l- N! O& D

    ' U: d! w: y% {4 t, q. ]9 H) v1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
      w6 \/ e0 T; T/ I2. Environment内部会访问MutablePropertySources来解析( G4 ~1 g' z& u/ M
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值- s) T- B5 I4 ^
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。) Q$ R  x$ p" l: F

    ; b' h2 N8 o' W- h+ J: z* G7 a5 A! r- U下面我们就按照这个思路来一个。1 p! q* Y4 j1 h  H! k  d
    ( {; d7 {( i/ l+ ?5 t
    来个邮件配置信息类,内部使用@Value注入邮件配置信息3 Z1 O. |0 \6 S% Y& U7 ?. U

    9 g2 w6 ?; |$ I; p; Apackage com.javacode2018.lesson002.demo18.test2;0 N$ A5 k0 d  l. m# d# E! S4 E, i- Y

    . U' S3 A2 a7 d3 d1 G- Simport org.springframework.beans.factory.annotation.Value;; W7 m- q0 d- }  n+ _8 F* m1 j1 i
    import org.springframework.stereotype.Component;
    : P& {# m- p* @/ D1 r* U
    $ A9 z5 u, N# U" D' v1 N! S/**2 F- n" g% s- O+ j
    * 邮件配置信息
    / ?/ G# M; O" {4 U$ B */
    5 M; M5 n, e2 N@Component0 Z6 g& n1 y0 \" f/ m4 x1 Q) i
    public class MailConfig {' A9 z3 }  ]! X, d. {, E6 }
    9 w( x  u1 _- U/ X
        @Value("${mail.host}")# }7 }8 h: L  c& ]) X8 H7 i
        private String host;
    % Z  J, H; ]' `4 T' k* U% q4 C
    2 X0 E" h9 G/ b: T    @Value("${mail.username}")
    + D$ A1 L& D% z4 l    private String username;2 @  n2 ?5 Y. {1 r
    " k% y8 X' {: H
        @Value("${mail.password}")) \, \8 ^  Q5 N/ t" H
        private String password;; K- E$ }3 K" t* @5 n+ }& ~

    . q/ [4 u* }2 m! k/ ?    public String getHost() {
    1 h! W9 {2 Q" ^) D2 `        return host;
    " f' H* G1 T# V/ H: O  J" X    }; T$ i: E% T; u
    ; s/ E8 t% l. [! W& [
        public void setHost(String host) {
    9 r$ D0 v9 o0 l* }1 ~. g' E        this.host = host;$ b2 g; n8 Q6 k# v* p$ r
        }
    * w0 c3 D* s0 \5 E, G; ?
    : F; z% C+ R! A6 S8 z4 Z    public String getUsername() {
    , `+ l6 e% }- |# w: F$ ~        return username;
    3 q  k6 _, _& u* u/ m    }
    . [3 B; x+ S) E5 s
    # e( U% b+ W3 ?0 V; [7 N1 {# j    public void setUsername(String username) {/ z, A% I3 @- I( m, `# h' _; g
            this.username = username;
    3 N6 ?" d; e0 e7 W/ t- G& h    }, i  q- [% a3 ^4 k+ Y

    . v5 h* H' t" ?, R    public String getPassword() {# ?3 p# }8 \/ D7 u& w8 N
            return password;- c0 t& B# |% s7 [
        }+ c( O8 Q( q& |2 K) L2 N$ _

    & m6 x8 P- s" {6 K( l- s    public void setPassword(String password) {5 E5 g. f" b1 g' r2 O
            this.password = password;
    + Q; j8 p- y! `7 Z) P0 T    }( [, a$ y' \. M8 ]/ c2 l

    3 m: S6 ~( Y5 h    @Override' I; q, C& J7 [" v
        public String toString() {
    , X& }& K2 @1 J/ n5 r" U  \        return "MailConfig{" +
    / B" ~4 O4 q* K5 @: y, W6 [$ |1 W                "host='" + host + '\'' +% c* E4 j6 p) c4 P5 P* Z
                    ", username='" + username + '\'' +: v9 o1 |6 n+ n1 A4 a% P# c+ C
                    ", password='" + password + '\'' +
    % I3 ~3 l& p  t. \! E                '}';
    # W. {3 ^2 B* h$ p    }  G0 {# J4 i/ Y: W+ m
    }
    5 E  J8 y+ M7 z5 W0 G5 N  K再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    5 _  C' V6 I: a+ Y2 b1 o7 F" n; }
    * T9 X; r* D; v* upackage com.javacode2018.lesson002.demo18.test2;
    ( L/ Y' v; T2 i9 u3 x/ O
    3 _9 N+ p, \9 s" ^$ }4 \) Ximport java.util.HashMap;6 r4 X0 O/ J. }4 A: f
    import java.util.Map;( d3 P. |8 n2 V" Y

    & Z! _5 x' |+ Z1 Ppublic class DbUtil {
    5 h  V, g# g  S. I8 J" `1 l    /**
    * ]; e& m# t4 N! S5 q! N& z     * 模拟从db中获取邮件配置信息
    . |6 ^0 b. {1 e: e, s     *( `, U$ l/ V& O; G
         * @return
    7 u8 e! n  e% ?; y* w7 D     */$ i; ~9 H1 u5 d+ z+ N! [
        public static Map<String, Object> getMailInfoFromDb() {7 s' w- L  P8 V* [* v
            Map<String, Object> result = new HashMap<>();4 J* u7 Z: H) F/ B8 ~) _* A
            result.put("mail.host", "smtp.qq.com");
    ( t3 h6 t0 y! e        result.put("mail.username", "路人");( }/ ?  Q' l, r+ z- x6 x
            result.put("mail.password", "123");, X# Z+ [  Q% a* h- O  D0 W/ E
            return result;7 e4 x9 U0 |; e. G
        }
    ( U8 E5 N, Z' N: a& I}
    $ w+ J/ q: Z% c+ H( t4 Z来个spring配置类# X) B% M! Q" E; L8 m  S2 _5 u
    ) C: C, ]+ T) ^* D7 l) g5 |
    package com.javacode2018.lesson002.demo18.test2;
    ! y+ G/ C( l8 J: F5 f2 ?/ P' s4 r! t- k  B  f( ~( k' [
    import org.springframework.context.annotation.ComponentScan;
    5 y- Z1 e& U! f0 X* n1 {import org.springframework.context.annotation.Configuration;
    + z# ?" O; V( {( t! N% `
    1 B( m! ?1 c; G@Configuration# k0 K: g8 e6 V
    @ComponentScan" O9 N: A* p( y- e2 t1 y, ^
    public class MainConfig2 {6 ^' y$ w# F% w$ _" Y
    }9 x* M, H% p" ?5 n% N' k
    下面是重点代码
    & P: h: D3 e6 I3 b% ?2 T  t6 f  v# K* r! ?% T# z9 n
    @Test
    ! d0 J$ r* Z2 N3 x! Y% i! G& bpublic void test2() {
    " e8 S( n* ?) @( ~    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 {+ h' P& P$ r" ~. b
    ; v; k  f" x/ g+ t' R
        /*下面这段是关键 start*/
    , o7 k0 \! C) a8 A- a    //模拟从db中获取配置信息
    8 ]' D; G7 g9 z- l; K    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();; v, e$ x" B2 U, g+ Q9 W
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    . V8 s# m& a/ i: l    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    . ~9 F* C7 g$ X0 B    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高  Z/ R: ]; e' J  _
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);% S3 z! w! y/ M4 u! L- g
        /*上面这段是关键 end*/
    4 Q% _' s/ J+ K/ J8 U2 i
    - [5 }3 p* p- |4 P# _& E! {    context.register(MainConfig2.class);9 c# W% z. E& ^" Q; m% o6 E
        context.refresh();4 _* e% u$ ~; ?  r! L6 W
        MailConfig mailConfig = context.getBean(MailConfig.class);
    8 o  {- h: t3 j" k' z    System.out.println(mailConfig);
    , h* }( r8 f" T5 A* u( o}
    " R4 S( g; A1 f. ?注释比较详细,就不详细解释了。2 \6 L; [& T1 `( K# G
    * b" f# T$ x  l2 i2 i8 K# F
    直接运行,看效果
    " _( Y* f' R& v% i1 q4 K# p( y# C) ?; S: ?1 m
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    # `0 S) D  b+ Z有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。' }4 k" Z3 u. s- s
    7 G. i0 q( W. I; |2 w; q+ Q3 @
    上面重点是下面这段代码,大家需要理解
    ) t& Y4 k/ p, L; d$ f  K
    6 E$ C7 o' q1 R6 B# |/*下面这段是关键 start*/
    + `6 b6 j" n0 K. t/ L+ U//模拟从db中获取配置信息
    ( I  y0 z" C$ ~& CMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();4 E3 A; u0 M" e! c
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)4 E( n" o' o# D. W
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);* t4 f/ @1 |! n+ s4 a7 L
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    + w9 J/ M+ N; c# C% u% i- ]$ k! Fcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);8 U- H; @+ G- L9 x- i" {5 k
    /*上面这段是关键 end*/
    % ^& i$ y8 X( S2 d3 k& k  u咱们继续看下一个问题( {" [! k7 O- e# E3 C8 a+ P

    ; d3 P& {1 S/ o% A8 L如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    . m- O) P' ^5 K. |  k: S; G3 \" ]" P$ F( [* }
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。$ w0 ^, ]. x& L& \

    2 z: ^# w) Y, a( }0 Q) b实现@Value动态刷新- `8 U6 a  a& V7 g2 g: X

    ; e7 Y. p% B0 ^! A先了解一个知识点
    . u; k2 G5 s1 D9 o: Z: a% ?  Y
    + n. M/ ^& B* D9 M4 p  q9 }4 V& T这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    9 a% G  m9 d, K0 H/ R# _" U7 Y3 p: @3 @
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    ' Q) L: G. m/ V0 {' b, b: P
    ) M$ W0 h/ g7 b' d6 F1 Ibean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    . L! w" i8 z' I4 Z& T8 Q) z% i, C" z0 G2 e8 E
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    7 a* f) h- ]- K; ^% R# A这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中1 e/ X! {! h1 k& w% h0 E" x/ X: [& `/ Z
    ) G) q- I/ k, P; |
    public enum ScopedProxyMode {
    * T, m: U3 K+ R  R    DEFAULT,1 U4 M+ A6 X) D& x8 k
        NO,
    6 f/ Z' M. V8 }/ _$ f% o- I    INTERFACES,
    ' G7 [2 u, P# q2 j    TARGET_CLASS;
    ) X1 |7 I0 @% e( d- P}  r, }3 u; y5 @6 y9 T
    前面3个,不讲了,直接讲最后一个值是干什么的。
    ! U% f/ V8 G+ t- I( X' k
    ( G6 \( I, b, R# U, V; z% y7 B2 |当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。/ F0 u! v, {7 l/ I2 A. x* S: R
    0 |- D1 T. B6 J2 f  ]: I
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    / F& H/ i- p* [: }" ~- W! B$ `
    / }, {, A# f8 V# ~* [* U) V自定义一个bean作用域的注解* G3 k, l! G+ {2 @4 F6 c6 d

    ; c9 d7 I# O+ x$ G! c" S# G/ ypackage com.javacode2018.lesson002.demo18.test3;  r& _/ n6 q' M3 U7 Z: @' k3 P  v

    & B6 \% C) S9 C! O5 ?8 Gimport org.springframework.context.annotation.Scope;
    " _6 r2 d. m) Q  p' \import org.springframework.context.annotation.ScopedProxyMode;& L% J, V3 J; b2 n) s" v7 W6 w5 ^

      f% n2 u- J; v3 u* y* Ximport java.lang.annotation.*;1 G" \6 z  P/ Q" u: q; d3 N: l

    - Q" v1 F7 c. S2 O0 c! w@Target({ElementType.TYPE, ElementType.METHOD})  i4 v& q  ~8 i! l* q7 d4 Z( T
    @Retention(RetentionPolicy.RUNTIME)
    : G$ h& C/ X- n( v+ q* i  m0 N! E@Documented: d- x9 J' G, _) f3 R
    @Scope(BeanMyScope.SCOPE_MY) //@1
    7 P4 g" y% L  r; r3 ipublic @interface MyScope {% A2 O! m3 q  {- R8 h0 n6 |5 V1 b0 j
        /**2 F) N* `% ~8 |6 L
         * @see Scope#proxyMode()) t5 Q+ h5 Y2 ~% T7 E- M
         */! {1 |  ^+ f& e2 [/ o' F0 Y& j
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    ( F% [1 u2 F5 @}: m+ D& L1 [( S: S
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    # P4 z# [0 ^: x2 L# v! N  M1 i, J
      _) ]& ^+ l6 H9 x@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS+ S: h% D9 f4 V9 q3 }" o/ f
    8 E3 R: k: H) t! l% L
    @MyScope注解对应的Scope实现如下
    ( p% T2 N/ e. p% v3 }: A! z  D
    * D/ G4 m! l4 [# {package com.javacode2018.lesson002.demo18.test3;
    ! ^6 w7 U& a1 `' L2 j/ U7 V( Q3 l, V. t' ]5 h! m+ z# q
    import org.springframework.beans.factory.ObjectFactory;2 {1 W0 x3 ]7 L+ w. h3 [) i6 v' ?
    import org.springframework.beans.factory.config.Scope;
    ! ?5 \3 t' _; e7 Eimport org.springframework.lang.Nullable;- r- i. |$ j" D

    5 {+ q  y4 |, `" S) p/**4 A0 @/ K4 r/ {  \+ q
    * @see MyScope 作用域的实现
    0 Y) o& P  z0 x; V& B) U; J */
    : |. x5 L' p$ ?, Y+ w$ @  Tpublic class BeanMyScope implements Scope {: K7 c% ~+ Y" f, e
    / w! b& I8 G+ C8 y
        public static final String SCOPE_MY = "my"; //@1, }" c5 e. f% \4 b

    , x! _" T4 r& G1 F6 c( K    @Override
    : p1 }3 f1 J6 @! r1 U; `& |    public Object get(String name, ObjectFactory<?> objectFactory) {
    5 G& Z# N6 I7 B/ c0 E3 m" t6 \& Y+ p        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2' C9 T- j; r; l; P8 h' S6 ~1 i; c
            return objectFactory.getObject(); //@3+ Z  _" w, p% Y+ D% t
        }% F+ S! B0 b- x- N+ `) p/ d6 _" }
    5 x  t* b/ \6 @5 m! n# Z3 P
        @Nullable8 p  R  ~& N! t1 j
        @Override
    9 a1 L$ ^* J  p7 c    public Object remove(String name) {
    # Z- b2 e. N& d" f8 K( a        return null;! y# f2 E4 K5 s
        }
    " O$ e' ?+ {( Y
    ! y6 [9 g! J: }/ n+ v1 e$ `# r  @    @Override
    0 K, o0 ^; F4 z3 I* v# _5 H9 e6 D7 ~, ~. v    public void registerDestructionCallback(String name, Runnable callback) {: O0 Y2 F2 w4 R" U7 \
    ; j' `6 s% C3 J$ ?0 n" M# e
        }
    2 l' D1 d% D. i( O  s& q1 s
    . [5 r, a: \' v0 K" p9 a    @Nullable5 }  ^3 [! J+ R: i; s5 `; E  B6 f
        @Override* |3 R# F5 s7 H% B2 N
        public Object resolveContextualObject(String key) {
    & R2 ^" c& r5 ?3 O        return null;' j% Z7 V. x! E( W
        }  q8 Q+ j2 _1 l5 Q. V8 |* b
    0 D: M6 g+ ~" e0 ~
        @Nullable6 X4 p) F. L$ Q
        @Override2 w: r( C# u5 k1 {5 T
        public String getConversationId() {
    5 t2 n) ?" X, a+ j        return null;
    3 O+ o0 K: M' `! s/ \% C    }
    5 s- |# [& |- j9 l5 w}
    ' _# b$ u9 \5 X; a& \/ s. B@1:定义了一个常量,作为作用域的值4 ~* g2 C/ d( q7 y0 m
    " |7 m5 b5 C" i
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果' \) ?6 D2 G) F4 }" y2 H# k
    $ T) s- D5 k- h& @
    @3:通过objectFactory.getObject()获取bean实例返回。. M5 s" _+ y/ U' C% c" P  S  @

    8 x; O7 G: r, ~$ H: \% @6 [0 c下面来创建个类,作用域为上面自定义的作用域
    * W0 p8 j. R5 ?: v# Y" R2 R1 r; ?# g* b8 _5 K5 ]$ J/ s$ g
    package com.javacode2018.lesson002.demo18.test3;
    6 G) l$ ~( W% v! o! ~
    * B! M/ A6 d: j+ l* T5 _import org.springframework.stereotype.Component;: u& G1 b5 J' G' C6 b  f' J3 @
    2 |1 n) {! r& R' n+ |! p
    import java.util.UUID;
    0 P1 T) ^: H3 U$ N( x# M- r7 i0 |- R8 y( s
    @Component! r1 G9 `- R6 I; f! h& }* d4 k* v
    @MyScope //@1 2 i! r* O3 A( ?9 f+ f- S3 Z
    public class User {
    2 \! c6 [7 l1 U, s; R1 j/ H2 g' N/ U  ?0 I% J. T: Y
        private String username;
    * w7 Q! u" I5 V" w" X( e* r% t1 H5 s: Q3 C6 U1 ~" ~& a
        public User() {
    + d/ Y% x, b7 V& J9 b' F        System.out.println("---------创建User对象" + this); //@2( N0 C+ k$ \  Z7 T
            this.username = UUID.randomUUID().toString(); //@3
    ( a  _( U4 r2 G" T$ _8 ~    }
    ) K. C& I, l2 G) \" q
    2 e* R  }0 a; g6 t% c2 Y3 q    public String getUsername() {. M6 ?9 e' j* r
            return username;/ |* k4 e( N# @$ d- L7 P; n" O
        }; f' k, l! ]+ u- W4 O! W

    * l8 G% C: K3 B: n/ ]. R    public void setUsername(String username) {
    8 ]; z7 n: @% L9 E( N7 |        this.username = username;
    5 Q! Y6 h3 F# F8 f    }# i! d$ K" F5 f0 Y' M. p3 l+ d. B; M
    / }) d* U( S7 ?8 C
    }
    / \  k* ^7 S6 g% _) x; y3 A* n@1:使用了自定义的作用域@MyScope( i# ]6 H5 p- J

    " I0 W: G$ y9 w- v8 L@2:构造函数中输出一行日志9 r7 C9 }' Y, @

    " J, v3 `3 G2 W! W4 m1 c6 l@3:给username赋值,通过uuid随机生成了一个
    4 v  u2 l% @9 b% O  S& P
    9 M; j9 C) e. ]) j来个spring配置类,加载上面@Compontent标注的组件
      O/ s7 i5 e: S; q9 x7 P- v0 {; E1 \; V$ P8 d" B* J
    package com.javacode2018.lesson002.demo18.test3;
    0 B4 x. m$ a+ ]  o7 W  I7 N0 f. V8 f
    import org.springframework.context.annotation.ComponentScan;* V4 }& K+ s+ O1 T9 ?, G
    import org.springframework.context.annotation.Configuration;
    9 b" y6 s9 M: c" c% b( [( I" t# l
    @ComponentScan( s7 o3 Q( O$ E6 \5 j5 h0 C( c& c& o
    @Configuration
    9 j/ D9 ~: W) ]( p* spublic class MainConfig3 {; [4 ?8 w1 T) k  z# n7 k' d
    }8 h+ L, S* d- v& ^8 [
    下面重点来了,测试用例
    ) b+ U% u8 I+ K
    3 Q! o2 M+ E% ~" }6 @@Test
    7 V$ ~8 @0 L+ Epublic void test3() throws InterruptedException {, _+ [3 Z9 P; K: y, e/ m9 {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();8 v3 j( i1 w  U  o
        //将自定义作用域注册到spring容器中
    , [4 ]- u5 N3 I/ C+ V    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1; D- e5 ^# B8 v" @
        context.register(MainConfig3.class);
    8 D5 ~3 @; ^! I6 f" i: y    context.refresh();1 I, k) I3 Z, \1 E! t8 [

    5 B1 k) A. ~1 d8 I    System.out.println("从容器中获取User对象");
    - O- {* S3 Z1 ~6 P# K; e& |    User user = context.getBean(User.class); //@2  e2 V+ z# T9 E( n6 }
        System.out.println("user对象的class为:" + user.getClass()); //@39 H9 q- y3 C9 b" z5 [+ R8 ]
    ( H' M2 k9 F$ k" f) v' W5 d
        System.out.println("多次调用user的getUsername感受一下效果\n");
    " G. k0 f3 Z+ D) D: t    for (int i = 1; i <= 3; i++) {
    & Z) K* r* r" q# C        System.out.println(String.format("********\n第%d次开始调用getUsername", i));3 Z$ F! O& V" y$ L0 }/ N6 I
            System.out.println(user.getUsername());
    9 G9 K5 c7 L0 y* T4 i1 {        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));0 c. [+ q4 f0 l* g3 ^5 _+ k/ G
        }1 l. y0 A# q, T$ \+ i( F, p2 G
    }
    - O  d+ o8 |; ]" h- J5 M5 h- V@1:将自定义作用域注册到spring容器中+ X  P1 }8 \8 k9 x0 N7 `5 l

    0 a9 b& N  z1 P, n7 C$ P  O@2:从容器中获取User对应的bean9 Q- N* n4 p1 F9 _$ \" J7 Z
    ! ~1 l2 V" |3 c' e; D1 L: ?
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    % }' g* G- [5 A. [' p/ h
    2 K- Y2 O/ ^4 P* g( c代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。6 n/ R2 O" w' J4 z$ H* I
    7 [1 _% k2 W& I( x3 ^# r& T$ d  s7 y( s, m
    见证奇迹的时候到了,运行输出
    # y7 @/ O; {; v. [
      k4 ~2 r: M! c从容器中获取User对象
    % H/ m) m$ d  W# F* y& j* j" g- suser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127- ?& [& s) C' Z3 M6 T
    多次调用user的getUsername感受一下效果
    2 O) j! s& Z3 a+ l( W3 E" O( d; q. }4 `+ |' C+ o, ]
    ********
    : E! R' |& M& R' s第1次开始调用getUsername) {/ r5 X; u1 s* `) {" K2 g
    BeanMyScope >>>>>>>>> get:scopedTarget.user1 N- v3 A! g: N- e& }1 D8 U7 d# Z
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    : n+ ]. I$ I- A5 ?# P5 |$ E7b41aa80-7569-4072-9d40-ec9bfb92f438
    : s- s0 U9 z+ j9 B7 A2 C- r第1次调用getUsername结束
    " I2 Q2 N; @" _7 A********
    8 k6 I+ I1 R+ |( H* p! d! n+ Q# a2 s$ b. n+ y( `( D1 N* ^% O5 c2 E9 E
    ********
    + V9 f; ?2 I) F! v* v第2次开始调用getUsername
    % s9 {( Z  p% k) t1 C+ Z* @9 XBeanMyScope >>>>>>>>> get:scopedTarget.user1 v- v/ w2 W# R! W* ?6 u
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b# R" x  \$ o2 d, X( Y
    01d67154-95f6-44bb-93ab-05a34abdf51f, y/ x9 |1 r' F% Y$ @- K" i# h
    第2次调用getUsername结束
    ( Z% T+ H& D2 G2 w+ \********- ?' A. Q! r5 _' ?8 @- ~

    5 u9 A) i" l4 u5 _( ~1 x. z8 C. I********
    " t6 r- E/ x6 T+ B第3次开始调用getUsername0 c% u0 g% x7 W7 ~: v
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    + B- D1 r2 z' O1 @! G& H' ?6 N3 h---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15( x! r: X# k) H5 _; T
    76d0e86f-8331-4303-aac7-4acce0b258b8
    9 q' _2 r! u6 _& s" C第3次调用getUsername结束
    ( f5 ]4 p3 U/ r' Y********5 u0 T  X9 c4 D7 N- _! r
    从输出的前2行可以看出:; \8 m& X8 j2 v! O! d

    " z% g/ @" ~; s7 y1 _) \/ N8 D4 a调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    4 |& b, O  g6 l# F7 _* k* E$ d! j! u: k2 X. U, s
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。$ I6 F: G! I# ^6 h) i- T: ]1 w! }
    - Z/ E% y+ d* x5 W) r4 j
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    3 r2 M8 ^5 x' _* }& |6 Y1 J" _' i4 Y9 E1 E- m6 V5 ^
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。7 v) v' W7 @8 Y2 y( ]
    ) r" g" T4 V- t+ ^! R2 Z
    动态刷新@Value具体实现
    ' m: E! U- z9 b$ Q9 w% p) a' h' ]9 Z3 D$ V
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    6 K# z# H. R# ^
    1 p+ k4 n& Y2 v) N# z6 [0 ~( B先来自定义一个Scope:RefreshScope
    1 ]+ U' D( J1 n! |( R: U4 X
    0 s% W! {, Z+ s: k" F+ ppackage com.javacode2018.lesson002.demo18.test4;
    9 l! X; T* N! ]8 q$ f2 ~
    8 d2 p5 a: Z8 |( d9 X, ^- [( Rimport org.springframework.context.annotation.Scope;
    # m1 F* s7 ]( ]3 Fimport org.springframework.context.annotation.ScopedProxyMode;
    8 `5 g; y: A. h. s6 o0 W8 O) q6 L8 [' b/ ~# J4 Q" B# L
    import java.lang.annotation.*;0 m5 o: c' @' a2 `0 m) {5 s

    4 M3 C' u1 \7 Y@Target({ElementType.TYPE, ElementType.METHOD})) i+ l1 @# `4 T3 p7 M. Q- `+ T) u; S% \
    @Retention(RetentionPolicy.RUNTIME)8 a0 S  |8 {  }
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
      ^( {' F1 N6 @. w- D) M. y@Documented0 Y+ \/ `3 R- z& Q* O+ C  a
    public @interface RefreshScope {
    8 \; T+ e) A; `- A( ?* u    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    7 o4 K. w7 K- P}
      ^8 y) p8 |" {' s$ S" M要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    & j9 s. M, Y/ r& G5 }) D) {. v' d8 J; ?) N% p
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    4 w2 s) U5 Z$ D( @* P
    & m# E0 f  c% }6 r这个自定义Scope对应的解析类0 O! s5 t) C4 K4 m

    5 [- c( _) P% \9 X. }下面类中有几个无关的方法去掉了,可以忽略
    2 ^- G; J9 O! G' T6 }2 Z# r
    , D' j& Q6 H! D0 h; opackage com.javacode2018.lesson002.demo18.test4;
    " S: F3 F1 B- d9 r9 y( `( }  b
    & e! U# X- }1 H" p4 V
    0 _: ^" K- H! m" a1 Y/ nimport org.springframework.beans.factory.ObjectFactory;
    7 [, y& o& k7 l+ p( o9 qimport org.springframework.beans.factory.config.Scope;: U, G  U4 ^! \: ]6 Z. y
    import org.springframework.lang.Nullable;
    " q( @. K. z3 L' k: y. v! V; Q7 M
    & o4 [: u# o: Y; Mimport java.util.concurrent.ConcurrentHashMap;
    1 W% n# P# a0 V3 Z% s
    $ g3 d& W- t$ Q: `public class BeanRefreshScope implements Scope {. ~+ e* i' J- A  p* u& ~+ I/ Q

      z% Q* W! t1 _: r    public static final String SCOPE_REFRESH = "refresh";
    . ~4 Y: c  E# ]
    0 T# s: m2 y1 ?    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    . w& v) Q( x" v8 m8 w2 [7 a) q( T  B0 ~+ ]
        //来个map用来缓存bean  ]$ M6 t7 i, {( ?2 o$ B
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    1 S9 `. d4 o% k% n
    ( Y5 \) l( y  Y* v; z* m2 e    private BeanRefreshScope() {1 ]( F: o" l; F& T* H! E
        }
    3 C* B4 ]7 M4 G' S6 h& {9 C# @3 N0 ^& p" v$ S1 A: n
        public static BeanRefreshScope getInstance() {
    7 A' u3 O- ?/ G! c% u7 D" d        return INSTANCE;
    " f# w1 F: j+ T, o+ S/ W( G$ \    }2 v. h- V( i* U: ]9 S" B
    2 ]* p! I  x, D  `( B. {' r; c
        /**
    2 K, O6 e6 o# ~+ t( ^) z: {) H     * 清理当前
    ! [6 ]: Y1 x: k: u     */
    7 @9 A4 T+ \' B  l    public static void clean() {
    ( Y4 R8 o7 e  g- Z, x/ ?        INSTANCE.beanMap.clear();; g0 [" a" _* \+ W+ m0 z+ y5 W
        }, h; `+ ^5 i8 K3 z( G3 q1 n9 [
    0 p2 [0 R; U. n' F6 o9 g# m; c/ s
        @Override- T# V* u. ~6 U: ]! u% U. w
        public Object get(String name, ObjectFactory<?> objectFactory) {" d- m/ }  I( w% U$ l
            Object bean = beanMap.get(name);+ c  a* D1 t/ F
            if (bean == null) {/ y% Q' E5 M1 U( F
                bean = objectFactory.getObject();
    2 C8 h6 d5 g0 w            beanMap.put(name, bean);  n5 w! h! {, V
            }
    + J# F2 ]" I( `4 x& y        return bean;( U$ y% ]  {; _6 o
        }! n5 n$ s6 F# [1 e) i' w
    9 B( ^2 S9 _9 ?. E/ P, t, n
    }, {7 n0 o; V( K( H1 R
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中9 [$ X" f8 Q! m4 K
      @5 g% ~  g" |+ C3 U" L) U3 \2 T
    上面的clean方法用来清理beanMap中当前已缓存的所有bean' `9 c' Z) @. O0 T" g$ n( e
    " E9 r2 B* m% X& q3 h6 f6 h6 i( N
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    & H3 ]5 M/ g, P) y. d
    $ D6 q0 {9 u: ?* F% G+ u4 ]5 u  Wpackage com.javacode2018.lesson002.demo18.test4;
    . t8 W2 ?% Z2 U7 I- z. X! I1 ^
    1 w( N( K1 R$ ^! @8 z* himport org.springframework.beans.factory.annotation.Value;
    & x% R4 k- Z7 D' ^( f; Nimport org.springframework.stereotype.Component;5 u5 ^( ^5 u/ M7 [6 C
    7 h7 X- e5 d3 @& {8 G6 T
    /**+ R  @8 x" z, ]4 Z9 i
    * 邮件配置信息4 y1 A6 l) t6 I, Y% o& L5 k/ x
    */) m: l+ }* L$ |+ ^6 l4 C& a6 {
    @Component. b% A4 P1 W8 f4 e. {" t1 g
    @RefreshScope //@1
    . E, x& ]; k( r0 epublic class MailConfig {( J5 Y) {, o# m
    : H  f. a* s% }; x) G
        @Value("${mail.username}") //@2
    $ O2 G4 M  h# W6 Y$ a( e0 j/ G& U    private String username;
    0 V9 ]5 c. _/ W. `, I! ?; `, x4 F& Y1 Z: @$ {
        public String getUsername() {  U* t) j3 @7 g- V) X) R
            return username;
    , ~7 _! t. f+ S7 V1 a    }
    9 a* R& `: r' q% _- ^4 i" O% H
    * o# M5 S& D7 r: \4 f4 r7 M$ l    public void setUsername(String username) {4 x* c6 B' j& }: y% C/ g
            this.username = username;
    5 s0 N  B+ c. C" i# Y* `, ?    }
    " _# V: Z: A& p. `2 j4 Z2 o9 x9 _4 \# T5 X
        @Override2 w- ~3 c9 L, k* d* R5 e
        public String toString() {  P0 a$ o5 h% {5 \7 \; }; J$ i( [
            return "MailConfig{" +4 }' {$ d; W8 r5 r' u1 Q  o" M
                    "username='" + username + '\'' +
    ' L' V; B0 d6 f) O- p                '}';$ M9 p! D) p  O, ]5 y! G
        }
    ( {6 B  K5 M# N0 [}  Y3 e! U9 ?2 O0 m2 E+ w0 B
    @1:使用了自定义的作用域@RefreshScope
    " p" C" u  {$ B% t$ _- a! H0 T+ S% H
    ' }# Q& U+ U; ]2 T6 c@2:通过@Value注入mail.username对一个的值
    / V( H1 J. t; ^& F8 F& a( p9 O" _5 h! w4 G  _( q+ P
    重写了toString方法,一会测试时候可以看效果。
    0 x$ j  @$ F& p7 @9 n" r( ^4 G6 |6 l+ E/ D  d0 Q1 V6 ~% G' v
    再来个普通的bean,内部会注入MailConfig1 E. t/ \1 G" K, e1 \" _4 x0 L
    " _  U, q* ?. N
    package com.javacode2018.lesson002.demo18.test4;" c6 c1 t9 e' m4 D
    9 T* F$ J; s0 X/ _+ h3 p) V
    import org.springframework.beans.factory.annotation.Autowired;2 `/ G* N+ Z) R0 m
    import org.springframework.stereotype.Component;
    0 u% w7 L; O9 `8 ^: |# S3 ^- d; e- ]6 k/ Z
    " _4 e* T5 ~1 l: @: r@Component6 }2 N+ K* O: z5 l2 R
    public class MailService {2 y" \5 P& D: Z+ ^1 Z" u7 S* c% P
        @Autowired
    6 p3 J, C0 p6 }& v    private MailConfig mailConfig;
    2 H$ o0 Y, u! Q) Z. ?4 K0 [
    + v0 [% i! ~  N  \  ^' t. H# f9 R9 a    @Override2 n# \. a+ P5 K; Q
        public String toString() {/ e) Y0 Q" P$ b6 D$ d/ M) I+ O- T
            return "MailService{" +
    0 I. u6 b1 q5 [* Z  Z2 ]: @                "mailConfig=" + mailConfig +
    3 D2 R4 l; q: Z- a                '}';
    $ h- k. d2 S5 ]( X- S+ \    }
    2 H7 A9 k; d) e0 r/ k; a}
    % C$ @' O; T0 k" O& f( V代码比较简单,重写了toString方法,一会测试时候可以看效果。
    / g' V# H9 o5 P6 o; E( f- k% o, E; @0 X* ^% m( `# I$ x' z! c
    来个类,用来从db中获取邮件配置信息5 T$ T) W8 h$ y$ u

    ; B$ A( F: z& L; V1 N, k& K0 l% Spackage com.javacode2018.lesson002.demo18.test4;1 ~! n) z$ |% E, Z3 |

    5 a- p- ~' ?& X6 X, f. {! B5 cimport java.util.HashMap;8 f6 J% P2 m5 `0 S+ z$ N
    import java.util.Map;
    9 }% ~" y1 s1 z% a3 Y6 I8 T+ Himport java.util.UUID;! U  p: z  H. X0 F
    - i' R# F+ G) T3 m0 L
    public class DbUtil {
    - R1 y/ }! m" r. K0 D( ]& Z$ w" V9 d    /**
    4 T+ e" A* |* f  s) J& g. W     * 模拟从db中获取邮件配置信息0 }4 e* Y3 }4 M) v
         *
    : ?5 b0 D3 ~: o5 k* p4 |     * @return
    9 Q# d5 `4 f) z4 _& Z, ?; W     */
    4 q! }+ K! E& e/ G. J- T    public static Map<String, Object> getMailInfoFromDb() {
    , t. X2 O  z7 r7 A' T9 M. ]        Map<String, Object> result = new HashMap<>();
    # e: [/ T5 N7 ^; S        result.put("mail.username", UUID.randomUUID().toString());
    * A4 r, E/ @7 ^% Z) G+ t5 z        return result;5 w! [( B- n" `1 D5 e! f
        }
    ' t; u5 u9 i! X% Q( }" F}+ P& ]7 v" N/ t, D; M- h2 C% P3 [
    来个spring配置类,扫描加载上面的组件
    4 ^! `, c8 v, ?% Z! v& v" m  L8 o
    package com.javacode2018.lesson002.demo18.test4;9 O6 z: E5 J0 H- k
    * F8 V  c6 C  j- m# O# k  W, x
    import org.springframework.context.annotation.ComponentScan;
    ( D, s7 O  `1 O$ B: c' Limport org.springframework.context.annotation.Configuration;
    0 w( t( ^* T" ^. ^7 _
    : _/ E% B( J! B@Configuration
    ) l% x* i, O. d# @& E@ComponentScan
    2 g, z2 z! e) y+ T4 l3 B/ Kpublic class MainConfig4 {
    : u1 c/ n5 |$ F7 D9 d) j+ s}
    ( m8 K2 S% c0 J8 {, B. r  S来个工具类
      @: p4 b; J( t5 Y9 |0 a! F, ^0 z/ V* S. O, W5 B1 ^' n
    内部有2个方法,如下:7 ^  g6 V# S- _0 W

    1 v& L6 W) V) m3 e! r2 @- N. e! vpackage com.javacode2018.lesson002.demo18.test4;) W2 @" F% w, F; S7 {

    . |7 y. e; z; yimport org.springframework.context.support.AbstractApplicationContext;3 c6 M) F  F6 O% e
    import org.springframework.core.env.MapPropertySource;
    : I( c, i% i2 T7 Q/ v
    & n* x/ h+ e* W" qimport java.util.Map;
    2 {! U0 H" a6 @7 S' ?! b; o0 g$ c; R: _/ f, G5 D& `1 R3 @4 C# }6 b
    public class RefreshConfigUtil {
    . O3 ~5 o: R6 x3 @; K    /**" b6 Y: v8 N) O5 ^' H1 X2 ?# H
         * 模拟改变数据库中都配置信息
    " k. `+ s3 X4 g  S$ g     */
    5 J" r7 P7 g5 B- ?( N    public static void updateDbConfig(AbstractApplicationContext context) {
    / |& z% R% B% T( D5 J! q        //更新context中的mailPropertySource配置信息8 ^! W% I% z5 v
            refreshMailPropertySource(context);, ?5 N0 x- `& x9 N- o" C
    , y$ [- d" r9 M, C/ [7 g/ {' d
            //清空BeanRefreshScope中所有bean的缓存' R8 ]6 Q7 I8 a  H1 q* T$ c1 f
            BeanRefreshScope.getInstance().clean();3 D4 Z9 ]9 N1 @( U% u" S- s) v
        }
    7 f" j3 d) N/ [# ~5 L$ f
    3 t1 J  E- p: G: ^  K& @/ r  F, K    public static void refreshMailPropertySource(AbstractApplicationContext context) {  M/ R/ u- O9 n% S' V1 W2 H
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    $ ?1 a) [) x& q0 J; V# ^' ~        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ( w4 @7 x/ z5 Z" n        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    ( a: h: v5 t* ]5 z        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
      {, j- m+ S: d; W/ K6 B    }( s% w" A) g+ [( K( `) x9 _, N

    1 ?( ?, E9 Q6 A5 w9 s& P1 B}: z$ N. Y* v0 y2 P4 z" }4 `" ]
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息2 l( m! r" W8 r7 P
    & M  _5 {0 i3 {. a3 G0 Z/ @; e
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    ' g( z/ O: O2 ]3 {
    : `7 K+ c: O$ G0 \1 c1 Y$ k: r2 o来个测试用例
    : A/ @0 v6 t$ [# o; Z
    " }6 V; ]6 X9 j@Test5 K. j. j4 d, `' _2 \
    public void test4() throws InterruptedException {
    " A: Y+ H  B. y; e" y6 k/ y    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ! p8 H" n3 V' x/ v3 A  Q: p# v    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());6 g7 S- A  w4 P2 f: g
        context.register(MainConfig4.class);
    7 x' m) c# P/ C0 O1 K% u    //刷新mail的配置到Environment6 A( [8 {: L. n1 v9 [% H
        RefreshConfigUtil.refreshMailPropertySource(context);9 x1 b" I2 R# z, C  t: P
        context.refresh();
    2 f- i' ]2 K' v9 N3 F& ^" B; l
    . E+ M- Y  s( P0 |9 w. F    MailService mailService = context.getBean(MailService.class);8 J; m  F  |8 ~# S# F
        System.out.println("配置未更新的情况下,输出3次");
    0 ?# g0 M" d- z    for (int i = 0; i < 3; i++) { //@15 q- R1 v9 r4 C, v
            System.out.println(mailService);
    & P- u9 p/ V" x' ^0 x7 P  `        TimeUnit.MILLISECONDS.sleep(200);" \, B" c2 l; w2 [# e8 U+ T
        }0 t7 {3 {1 K$ z1 r! \, \/ \  H

    + _; q8 }4 v- Q, ?3 S$ m  g    System.out.println("模拟3次更新配置效果");% x* |, p- R/ I) p
        for (int i = 0; i < 3; i++) { //@2
    5 Q& u* U; Q) q6 D- O0 F# |* G        RefreshConfigUtil.updateDbConfig(context); //@3) R2 I+ x0 K$ a; F5 B  l& x' K
            System.out.println(mailService);- I5 k/ B8 l+ R! B- S4 a
            TimeUnit.MILLISECONDS.sleep(200);
    & w' V* |, L! M/ t1 a& K    }
    . h- T8 m" {2 y  M3 ~7 N* W( Z}
    , c* \  _& p! C2 y@1:循环3次,输出mailService的信息1 O  |8 @& i7 |; _/ }* ]; s5 f
    8 \* Z7 o$ Q, f! g, B. Y
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息; k  G8 a" v5 G! W
    . D0 J6 z2 M% p
    见证奇迹的时刻,来看效果
    8 X7 n) _0 h' H6 l9 s' w
    7 J& [7 m7 w. p- F2 K9 {配置未更新的情况下,输出3次
    . m' o! m- q# P! a# _5 X) ]6 R# t5 sMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}  R1 h" j4 }1 ~$ M
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    . D  w$ b8 X: Y* v" d7 C8 YMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    0 z; \2 c4 S2 a0 a( @" ^9 A模拟3次更新配置效果
    2 C4 Z. K5 i( WMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}0 j# M" n- g6 ?0 Z' y. T
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    % K! u3 l6 L' l! _& B! Q: h9 u4 AMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}6 s  v$ m" x: ~  A" S$ o" a
    上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    - E7 S9 c$ b/ d* A! f, v5 Y6 F" K
    小结* K" ]  Z5 s$ n# k: g

    5 Y0 g, o- P" G) X动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
      D5 W/ a; o5 U: l: X& o. q; V
    / Y/ B- i# G$ G5 F有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    , p6 y- y+ W3 y' @( B$ p7 I
    3 j: q0 W" M; |6 g5 j& _4 k- {总结: D7 n& T+ M! O8 L+ b8 L; S- n
    , L5 C6 g6 d$ ]
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    $ \3 M. I2 G1 }( [5 X4 b0 T; \! Y, J8 Z
    案例源码5 T, P4 i/ C$ O4 p, h- C) J
    # u( {! {6 `) g( p! r. J7 N
    https://gitee.com/javacode2018/spring-series# L' Y1 n  H8 U5 H1 }! u
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    & ]# `( B% a( i$ u. P
    : X  E  k' B8 P6 Z! H/ ^. Q) tSpring系列
    5 G+ G* Z% o6 N  l9 U9 V* V. O/ A* f9 T
    Spring系列第1篇:为何要学spring?, K; K8 U& e: I7 x

    , D; @' U2 G$ N2 e/ {Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    9 W( s# H7 x& V# x3 J! n0 q8 M
    Spring系列第3篇:Spring容器基本使用及原理
    ' j8 R3 }1 i  r+ n# g- K
    2 g3 w6 R5 S; v$ ASpring系列第4篇:xml中bean定义详解(-)
      v, i3 z' R3 I8 k8 @7 f- ]' b5 X
    8 o: C$ j& N2 ?Spring系列第5篇:创建bean实例这些方式你们都知道?- r: W0 _. e+ V
    3 I; d+ }$ y" k
    Spring系列第6篇:玩转bean scope,避免跳坑里!
    % Q) \+ H6 m6 x0 Q" b: O* Q1 p3 |7 h+ `
    Spring系列第7篇:依赖注入之手动注入
    ( v( [& ?! w7 V) s6 G
    $ y2 [4 t5 a  I* ?2 O, xSpring系列第8篇:自动注入(autowire)详解,高手在于坚持0 S4 f2 Q. W7 x3 W
    ) D) \" f& e- b
    Spring系列第9篇:depend-on到底是干什么的?
    2 S, u0 s% k/ {1 r/ T/ T$ w, o
    & {$ y" k7 G! f+ M& J; ZSpring系列第10篇:primary可以解决什么问题?
    $ D7 p: q  U: u# G9 l$ u
    # G- ~& ]" S: X  k! M: uSpring系列第11篇:bean中的autowire-candidate又是干什么的?4 U  s5 s! O+ s+ u7 u  v
    & K% e  C" L- v; `8 |6 t% V2 v
    Spring系列第12篇:lazy-init:bean延迟初始化
    8 c1 T8 E8 Z2 P5 a6 H- G; v8 [& U5 V$ R+ S% I! W
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    * K/ \) Y9 g2 F* a  f4 l2 v" z5 b1 k" q/ |5 I' U/ c/ u
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    $ I' D. M8 V3 K  n& O; @1 C4 X+ C0 s0 @5 g4 B
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    + O4 a7 p4 U- k& z0 l5 _
    - G. g; {. \4 X* S) R4 V, `Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)/ L6 f, b7 ~* \- `$ A' T

    " b4 H6 S' u& |9 _Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
    + u+ C4 q9 E4 L+ {( u
    - B6 V9 ~" S' X! F" J6 M. A4 H5 ]Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)6 [% l! V9 g- ~: |% S

    ( k( O. G! j; u8 h0 e, fSpring系列第18篇:@import详解(bean批量注册)
    & E+ M, B5 H$ N4 s+ L1 G
    ( j" g  z5 i' U! PSpring系列第20篇:@Conditional通过条件来控制bean的注册# i8 j/ ~$ C7 D1 ?4 o' C

    ( N& F( z; f/ h; B& fSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    ! X! _" N% i. C, t0 t. s# s3 @% X
    ; W, ?8 e& _# z, `Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解% N8 A( ?$ e- g/ U# N- p4 U$ W' l
    . ]4 j: }8 y5 c" i& {
    Spring系列第23篇:Bean生命周期详解6 A; t9 Y2 Z' K' P( V5 p
    1 P2 b0 M" y5 o3 |4 w
    Spring系列第24篇:父子容器详解
    6 e) ?! ?! U* p! D8 h% J- S4 D9 s, v1 i) Y0 r7 P
    更多好文章
      M# ^+ P) V8 }5 N$ a
    7 d6 i" \- ?6 A4 |, \; q$ R$ H' {Java高并发系列(共34篇): X5 m0 X. @  F+ a2 W% N( B

    + ^8 q0 D5 u* i6 n% ?MySql高手系列(共27篇)
    & L2 p2 T% n8 A: L* X/ o3 k" x- s+ x' Q; h& a; Y$ l% @
    Maven高手系列(共10篇)/ J% W0 P3 z, l' v% g2 t
    - t- T" d+ a  ^4 w( G% {/ L
    Mybatis系列(共12篇): F' n5 L) E, I* ?

    " M/ q$ E! e9 t  N, p聊聊db和缓存一致性常见的实现方式
    1 ]" V" z& \+ j& Q. w- Q3 ?* c
    1 U# n8 `. X( `, Y$ e+ ~接口幂等性这么重要,它是什么?怎么实现?
    6 |' E* P5 Y7 P0 y- Y- {
    9 m8 I1 J9 B" t* C( T1 y& P, h) j泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!  c4 Q6 p0 T* ~# j6 J! L
    ————————————————
    # M9 e, A) V: \7 V( f版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    $ e  V+ H) v! V( [& _# |0 t原文链接:https://blog.csdn.net/likun557/article/details/105648757
    + I9 y& _# H# J9 q% l9 @
    $ n# H3 v/ x7 H3 ^5 ^9 ?1 [, Z0 [4 ~& l0 u# N
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-6-13 08:42 , Processed in 0.548637 second(s), 50 queries .

    回顶部