QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5099|回复: 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!
    . _, e9 D  j' q7 E8 W2 b3 ^7 X疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    # Z9 M, ^" U$ j2 ]
    3 ~( {( ^- c- R  m% ~+ h面试官:Spring中的@Value用过么,介绍一下
    5 A7 c; d8 w5 Y7 p
    3 T6 y  s; l$ h9 k0 p0 G: c我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中- j) `3 d& a3 {

    " l  g& T; m+ H. b4 X5 W面试官:那就是说@Value的数据来源于配置文件了?
    3 P- s( R+ y( m* h9 Z' [+ w8 @9 |$ s( M, Q2 h( c. K! S6 G) `
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    ( c: {8 f4 f: ]" l; r9 T' |3 H/ D
    1 l7 o1 L  {7 E面试官:@Value数据来源还有其他方式么?
    $ @0 _2 Z+ ]8 n8 ~% v, K
    % V- S  i, M$ t5 t- l; H我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。; x4 ?$ z( b9 w, Y9 z

    8 D0 `8 w. l% D8 I# G4 g2 D1 ^面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    / y8 ?  e5 B3 O9 \' s4 Q' Y4 B9 q" ?! d9 u0 T( Z" J5 u- E
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧: ?) ]$ o- q- p3 T9 Y. ~9 w. x9 h
    0 w9 r8 v( t% Q8 A0 b
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    7 U7 l; d0 W9 g
    8 M. \. U- f4 b. T1 Y我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    , K. G' c' }! _( x
    - A5 h4 n( _: {/ E面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?6 j9 j0 O- w9 S4 Z6 b

    3 W. f, ]/ A* V( H- w) z我:嗯。。。这个之前看过一点,不过没有看懂
    ; Z$ \* B' Z0 i- R6 S
    ' F1 f/ H9 ]) m1 u面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    . o5 E  o; T! r, G( q, W( F  h8 m* o& Q
    我:3万吧
    % V" a/ ~6 k4 D& W# h7 g
    ( K+ }( E5 W6 U+ H# l面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    4 T# A) S# |7 ?/ Z' G
    7 V6 h, ~( ]9 ]9 k. y4 B. v; b; i我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    " n6 c( c5 [' t; k1 T/ v5 z" p* h* b  h* Z
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!$ t  }6 U" t4 ?# g  ~# x( U" J) x

    * H8 X1 v0 O+ @+ [, F我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    , \" U. w* b6 a/ `, l$ m7 G- t
    . o# O* C1 z# U/ d# Q这次面试问题如下" }' r7 c' l4 Y3 c3 |8 M
    , [& i$ k8 C3 H8 M' G
    @Value的用法
    " w/ @  _8 l6 f9 w# m( a  b3 j; x; S6 M, c
    @Value数据来源% ?* S/ C2 y! e+ h& L$ V

    $ s2 I, N. N( K2 m, H" \@Value动态刷新的问题& B% T. \2 e7 _, [+ j  M) ~1 w, N

    # X$ S8 G, H. x0 v* H( U下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    6 V7 _4 L2 T# P. u  T0 O* s5 u
    3 e. i$ Z% L5 V@Value的用法3 D% `! _7 J) _% s9 b
    9 ]/ s/ A5 |. G1 P0 ?
    系统中需要连接db,连接db有很多配置信息。- R" E9 k8 f+ ~6 J+ _

    3 o$ \' C- }$ M, [+ N! T. o系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    " E/ y  }$ O/ z( I! J& X3 F, T! F& p# n$ t, @* u; ]
    还有其他的一些配置信息。  u/ }# }& R( P/ E
    , y# c0 L/ S  k% x) d" C& J) W: Y* o; d
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    * ?0 r" v( Y# z/ V& F' U  ^9 h' B/ V- [8 A- Q. r
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    5 |( |/ ]" l. L1 F9 ]; ]; ^6 P# E" W# t3 Y4 N8 e
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。4 o: X2 i/ f0 `9 b# N% y; h( ^) b
    2 Z5 i2 P6 w/ ]6 c' O; z; _- A
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    ; m0 S) |# Y- Q; O9 ]
    6 ]0 y8 r/ k1 p7 _( d9 B2 ^@Value使用步骤
    9 X- `& x- I/ M" M/ r% u" ?  i) R( Q% L& K2 P# t
    步骤一:使用@PropertySource注解引入配置文件
    1 {! F$ \4 x4 h) }$ D1 g/ }
    & c+ {) n% \7 X. g! j将@PropertySource放在类上面,如下$ m5 N# n& _( V' {
    ' o2 l8 |9 P, X# b8 h
    @PropertySource({"配置文件路径1","配置文件路径2"...})0 v2 n: ^5 G/ u4 K( _0 d
    @PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。( p& H" U; S: L: ~$ ]
    5 U; k& b  |! \7 E
    如:
    3 `" u& ]8 r) Q
    + J  A( G* ^5 S/ x3 s4 g: i@Component% P. j2 h# `, u
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}), ^% o' z/ k$ @* r% ]
    public class DbConfig {  S: a" l5 i% {% X0 K0 m: |
    }
    0 C( Y" p" r, x2 S5 o步骤二:使用@Value注解引用配置文件的值0 V6 p) {' X+ f% ?

    ; G9 z$ {% E8 G7 ?! y3 [通过@Value引用上面配置文件中的值:
    3 ?6 Q( J  l. p9 [' d% j* e
    # [4 P% ~5 W7 r3 l: h3 q6 W语法
    ( M6 y4 c& F! V6 \* M! `- y1 S+ o7 w) m7 T1 r, p8 E
    @Value("${配置文件中的key:默认值}")
    8 w2 b: ]+ K, Z( H  W: g) c& }@Value("${配置文件中的key}")
    $ R) y2 ^. W- r+ A% h如:
    ( s0 U! ?  b9 E9 V5 E4 U
    5 I; d: [4 m6 y5 D- K& J, E( Z@Value("${password:123}")" q4 B) N; t* U2 J# l: `
    上面如果password不存在,将123作为值0 [% M7 y# Y: r1 U
    3 f, r2 o1 g/ V) b- }
    @Value("${password}")
    8 [' ?; ?/ i6 @* Z上面如果password不存在,值为${password}4 K. y" C$ s& q- e

    : A) t4 G3 ~6 B3 A) x, i假如配置文件如下
    + b4 s7 o4 K. q' x
    3 s2 @) z, F  F3 }3 G2 x; h1 Xjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    1 C) ]- J- M' D* ]( Qjdbc.username=javacode: F  C2 S- x$ G, O# a/ a1 p9 I9 w
    jdbc.password=javacode
    6 b0 f/ P5 X: C/ D4 Q' ?使用方式如下:! w0 j' N0 Q3 j+ `4 C3 z: \
    9 m1 g5 ?/ J1 q+ a; J. _
    @Value("${jdbc.url}")& f2 c0 [* e2 T+ N7 P
    private String url;$ Y: G9 y) I1 Q% Q7 H$ C
    + `* Y5 I7 r7 q8 w5 _# s
    @Value("${jdbc.username}")
    9 f9 h; Z4 m4 i- z: H& t* pprivate String username;1 c1 F  K, u6 x7 M9 O% {
    4 R% c8 U  z* K7 @6 `2 c
    @Value("${jdbc.password}")
    + X  b$ W$ ]) Q; ?" X" a2 e7 Q2 ]private String password;' E1 c+ u2 G& ]1 S9 X1 c3 Y0 c
    下面来看案例
    8 M# V" H4 L% m6 A* E" e7 b- p- g3 {
    案例$ m& i7 e( m8 d% v2 S- C* `  M

    1 s* Q2 x! f6 N7 w$ Z0 L- @来个配置文件db.properties
    7 i* e( ^6 A3 U/ j$ i/ b
    $ o% _0 I( V+ @& B4 t" f; Mjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    % T1 K7 [, \) p  Jjdbc.username=javacode* x" R# R) c6 v. H! w
    jdbc.password=javacode6 Y* V. `, D! [- [3 G
    来个配置类,使用@PropertySource引入上面的配置文件# `, Y0 t9 R; q9 A/ A  X

    8 C* _+ H7 e1 ?' Mpackage com.javacode2018.lesson002.demo18.test1;) `/ I, l  O* @# d7 f/ u9 C
    . \* X1 f. K! {7 C. U0 e
    import org.springframework.beans.factory.annotation.Configurable;+ m& b+ v7 w4 B5 K+ V3 S+ Z
    import org.springframework.context.annotation.ComponentScan;
    % u0 T; d) o' {; V0 Simport org.springframework.context.annotation.PropertySource;7 H. Y4 Q1 E+ h, t) S  u3 S
      i& e5 y7 i+ S+ h# P
    @Configurable9 o. ]1 M" d% q/ K% @' }
    @ComponentScan
    ( m) u  i, N- m8 ]- ?7 I@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    ' s3 @3 P  _5 k/ C4 C! V8 ^public class MainConfig1 {& Y& h- {1 l6 O9 i
    }
    + }1 n+ ?! j9 i$ o% o2 ^9 p来个类,使用@Value来使用配置文件中的信息
    9 v( n' j3 X* d) S9 g
    + ~# H. n3 T" A5 [' S7 Spackage com.javacode2018.lesson002.demo18.test1;
    * X9 w' X8 m5 S' h
    * Q$ H- ]0 ]: i* p! q2 Cimport org.springframework.beans.factory.annotation.Value;/ ^2 T0 o1 k' e7 A
    import org.springframework.stereotype.Component;
    + Z. ]+ N, F' {/ e8 S0 k3 ]
    % l; S# H9 i! C@Component8 a% c  T7 A6 f2 ~9 l! S- N
    public class DbConfig {( E9 G8 e6 w6 |2 f) j
    4 h7 ^: `& J( @5 R6 U
        @Value("${jdbc.url}")  v: M6 T' Z( H/ v3 T/ ~
        private String url;6 E4 b" U( G+ f. D% |" X5 L4 v9 J

      v9 M0 S) i# g$ J2 D& r    @Value("${jdbc.username}")' H& u$ c% o( n8 a$ G
        private String username;
    0 F, m7 _  ?3 d' h7 Z& J: R" N! f9 F  j& L/ u5 c/ g" I) M! A
        @Value("${jdbc.password}")
    , ?) N+ R/ _4 q' ~. i9 B    private String password;/ [1 ~0 b' a8 z2 {3 e
    # E! A6 u& w+ t, x! l
        public String getUrl() {) P, J7 N* ?: ^: w/ g
            return url;
    * n6 |  Q/ A# d+ g; u    }; h. N: ~1 d- b+ {" I% c

    1 `7 E; a3 j3 I8 g    public void setUrl(String url) {
    2 j' y: S, \0 `3 l1 k* P        this.url = url;! W5 D6 Q. U+ G5 k2 g/ J% ^
        }
    + ~3 L2 y+ l: d6 r3 }- Z* u' U3 Z5 o
        public String getUsername() {
    : {& @, o/ V5 Z# y        return username;
    5 S- ]1 E- X' v/ v    }
    2 ^+ ~9 \3 Q; @  q8 u# r7 g% ]2 _! b5 z: P, M& a
        public void setUsername(String username) {  S* ~$ b1 B. V6 u; z
            this.username = username;1 [' E% o4 a! {/ T
        }' M% I; |; m7 K
    1 p: h# M7 ^8 P0 K4 B5 q" T2 X  x# |
        public String getPassword() {9 r% T9 v' \$ Q' l( p, u8 y
            return password;' h9 |9 J9 \, q
        }
    & K; C; g3 i, N' ?3 Y9 Z5 O
    & P7 H5 b3 g0 K: b0 Q5 S, F: h$ R    public void setPassword(String password) {( ]) h* z$ l: {- [
            this.password = password;% J) L; u8 k3 Q7 b: {
        }
    : u! a4 v" U- v2 |- c, X9 `$ D4 S  A
        @Override
    : k% r& z* ?: Z7 x3 R    public String toString() {
    $ E* E6 K  n% W" j1 r  K; H* |        return "DbConfig{" +0 ^9 \$ u; X1 A0 `1 W0 _/ E
                    "url='" + url + '\'' +) R$ a  ^3 W' [4 m" {
                    ", username='" + username + '\'' +$ P4 K4 S# E% b  H
                    ", password='" + password + '\'' +% z% J$ P% b9 q, \( s9 ?" }( w- c
                    '}';2 T9 j7 u+ P; k  N; Z# h
        }8 B9 `. g& G' Q* f9 `
    }
    . C9 J  Y) Y. x) }4 i, Z' f上面重点在于注解@Value注解,注意@Value注解中的2 W. U# r6 A6 ]# A- q! |  O

    ( J2 P' `6 w. [$ D6 r来个测试用例% i0 P8 W9 W& q5 [. L2 [* f
    8 f3 j  [+ \! X; b; f9 P/ o- F
    package com.javacode2018.lesson002.demo18;
    % X) w) |( i$ Y1 K9 O5 [8 `5 H+ W, ~* G$ }
    import com.javacode2018.lesson002.demo18.test1.DbConfig;% i# o4 I. R1 y& Y
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    : m+ A, Y* u% B) w& Q9 T8 w- ximport org.junit.Test;6 j1 v1 r! M5 h- p. q$ `
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    : w% n7 ^9 w0 |' `
      P* b1 u1 ~7 Fpublic class ValueTest {
    7 u4 s, J, {! o; k$ D0 P9 T3 m/ w9 K2 Q; v
        @Test
    ; M+ ?/ u' B8 m0 }    public void test1() {: a* g4 s8 U. N2 r. K) A% r, w5 ~8 s
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
      J' l; F' K: W! o* O        context.register(MainConfig1.class);
      O7 c  _  K0 J- S8 s" r        context.refresh();) S+ ?# Y; H6 G/ p

    6 ^# s4 I( A8 V" d6 e2 d        DbConfig dbConfig = context.getBean(DbConfig.class);  l7 s6 P- B, i7 \8 \
            System.out.println(dbConfig);" _. e+ o' _" i
        }
    . D/ {% {4 L& u' ?  u  L}. f5 u" [1 Q+ x' B0 ?
    运行输出
    9 F! q0 m2 E  s9 E' N( \- G$ G5 b
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}6 I0 I4 Y6 V/ ?0 x( M
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。3 v" q0 r1 H. I$ R( i' {$ u2 }
    ) K$ _0 u  Y# k# ^2 e& T
    @Value数据来源8 R) O, }2 d% |9 s
    6 a/ L' w- W$ `/ \
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。$ q; }, a% B& Q0 y5 }
    & D. ?  Z$ B2 ]  `! _# _! o/ I
    我们需要先了解一下@Value中数据来源于spring的什么地方。
    & H3 y5 K" M' N$ U/ R( y! U8 e7 S
    % x: L6 \2 R/ K; h2 b0 Cspring中有个类
      v( d5 x7 d$ c3 O$ z) \8 O' L1 n! \  W/ }2 P2 d7 o: |
    org.springframework.core.env.PropertySource
    5 @8 D- c9 V# y& p8 z2 P可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息! f$ J  H! k. E# j
    " H, p2 b; G" e
    内部有个方法:" Y2 {; k8 i1 }  b8 b7 H0 W# ]

    9 B3 R$ X0 i5 H7 A4 Ppublic abstract Object getProperty(String name);
      e6 M* l8 k; E4 @! S通过name获取对应的配置信息。
    $ E0 Q; a! j7 x2 w$ N/ H3 ?6 E7 i# R0 v7 h' ^
    系统有个比较重要的接口2 }, c4 |7 |" r! p& j
    3 P! f+ [; A& e/ }( M
    org.springframework.core.env.Environment" e! H' x$ @* q- H% s
    用来表示环境配置信息,这个接口有几个方法比较重要5 ]# k; A9 S, P* t( [- z4 U: k
    ' k( E4 }7 e6 O/ H+ w
    String resolvePlaceholders(String text);
    ' R, i+ E# ?. d: t  W6 \MutablePropertySources getPropertySources();
    ; j; g5 i# W4 @3 IresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    $ g5 |3 ?2 C, s% T4 _, V# ]6 j/ y2 h
    getPropertySources返回MutablePropertySources对象,来看一下这个类% l! O6 A3 J6 f
      W) v7 D2 h1 E) H& [. y4 y
    public class MutablePropertySources implements PropertySources {
    2 h( q6 _) z! e' `' g  c$ W* J' A2 j
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ( z+ K& j! v. i  Y! |( a  |+ ]+ A4 _7 S  D% v: ?/ A) v
    }/ E  f5 \' [! B. I  K
    内部包含一个propertySourceList列表。
    / \) y) S* ?3 u. r) N) B$ Y3 I- L" k' T6 ?% w$ E
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    . [+ U; r! D0 a! d6 r+ [" T
    ' _. F4 Q; G. ~4 L4 c* C. R! v6 N3 @大家可以捋一下,最终解析@Value的过程:) `2 y; F8 `' [* T
    1 W, E: l2 D: L6 w: r1 _
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    % N9 i. z/ V( X" i& `, o4 c2. Environment内部会访问MutablePropertySources来解析; o1 i9 e( u! v0 u% Y
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值5 W, T) o6 T. f9 k
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。8 \. T4 Z- U% m

    8 }! W1 @! o+ v  S" f6 h下面我们就按照这个思路来一个。
    + V0 \3 S6 s9 P2 j' }: C' B  S( i# g5 t) s
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    $ ]6 Z0 ]) |" J! D; y1 `; _& O6 m3 V6 u) p; _0 j9 e# _
    package com.javacode2018.lesson002.demo18.test2;
    # x& }; Z0 o& I
    ; W9 g( i# V0 ^6 W4 j( d; ~3 jimport org.springframework.beans.factory.annotation.Value;
    ; v# @2 M" J$ Zimport org.springframework.stereotype.Component;& s0 ]* \+ E% @$ _* Y

    6 R" k, Z+ r- s* p0 R* O/**
    0 e; t' T4 Z, s5 u" Q * 邮件配置信息. e9 G3 N7 d2 D" e( o  l
    */
    8 F" Q+ C1 ?% I2 w@Component4 }9 N2 I1 m+ n. i
    public class MailConfig {  X; ]( f+ F: Z+ q2 Y
    ! Y8 b  j- m8 f2 |" g: g* R6 J
        @Value("${mail.host}")3 Y' V0 _  }+ K
        private String host;
    + o8 J6 N. q- K: [* G2 \5 ]  ]. O8 W2 a! {
        @Value("${mail.username}")
    ) W* r0 ~- M; J0 R6 A, J  t    private String username;. A/ n! m# z# f4 }$ ^
    8 r8 z1 V) q! v' H% L. i
        @Value("${mail.password}")
    # F" \0 W0 x  ]* M  [' n- w. Q    private String password;
    8 @9 ~9 F9 m0 m7 W8 z
    : v# a/ P5 u& ]3 x    public String getHost() {' {) t, O: V/ ~1 F( n' n$ A( R$ s
            return host;' A& T$ \' n" o7 [/ c9 r/ |
        }
    & j. g3 n3 d. \3 N" R) k. V6 T5 W7 F* P( J% t/ \  [, T  b: w7 p
        public void setHost(String host) {
    2 J; _3 _& W% @- F# {        this.host = host;3 r! ]( z. M5 @# j( Z
        }
    0 J# q  @/ X8 s' i$ s& b9 m
    ) A# X% D+ _2 D! Y    public String getUsername() {
    4 `% |+ E! M! X        return username;: h& Z  c4 e, x# r: n; w$ H/ X
        }- _& `/ U) S* v5 M  i
    1 N) j* |" ?. R
        public void setUsername(String username) {2 U: ~6 Q0 q; S3 z% ~' K4 [, j
            this.username = username;
    ) E8 K1 o5 s' C$ q. d) T    }
    : X7 b2 H  M8 H/ Z' Q' }5 j: K, q
    1 n) x5 O5 N1 `: Z    public String getPassword() {& \4 P& K; o) R/ m9 W1 F% h- V9 Q& n
            return password;
    2 F* `+ @% m1 v    }
    , ^* b! ~1 C2 C4 ?& }& r. w3 `
    ' T" }5 Z( m  p3 ~! [    public void setPassword(String password) {4 w; D( N6 l# f, E
            this.password = password;
    $ _$ F: H6 ~$ |. K5 ?8 d    }
    6 I; Z1 s( X! t1 b, E( [
    0 l. v9 n4 Z4 f$ S1 g6 N    @Override8 r' L5 D$ c  P; ]: X2 `! `" ~5 F
        public String toString() {( F" o3 t# X( h, w  x6 _
            return "MailConfig{" +1 s# X6 @7 A8 j: J) o+ @. x/ z
                    "host='" + host + '\'' +* H7 p0 c& O. J$ u
                    ", username='" + username + '\'' +
    % x* v  \+ m1 S9 P0 z& c) z+ y$ ]                ", password='" + password + '\'' +
    1 ]. ^+ T% [7 E  l& l6 O$ k* t7 Q7 u, O                '}';6 f) t$ t. a* P: l
        }
    $ @% n/ o3 `' H2 m; m3 w}
    3 U0 \" R8 u! }. b再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    - E! L& \4 R% `+ h4 S5 W( H8 V
    4 L& m5 X1 [/ Ipackage com.javacode2018.lesson002.demo18.test2;4 g- [/ u1 V& y% Y& f

    1 w+ P+ d% ^2 |, A  v) {! P1 Simport java.util.HashMap;7 |/ A7 B& F7 R
    import java.util.Map;
    " W! i5 s# p1 }) i
    / U+ r" }. A# ^' ]' W9 c0 w/ xpublic class DbUtil {1 s2 `: `1 J7 h
        /**/ h" q+ n+ q/ n' g/ X; \& t( y
         * 模拟从db中获取邮件配置信息& k: j$ ?8 s- Z" s! g, t
         *3 X# @. \. J. U
         * @return
    ) M% a, P( ]" L/ _& c& I) Q     */5 \! I9 k+ ^' U' V6 l! X, c
        public static Map<String, Object> getMailInfoFromDb() {
    ) g( j3 Z* k* p0 N        Map<String, Object> result = new HashMap<>();
    ' q- [5 P/ }% q3 v) W3 G* e        result.put("mail.host", "smtp.qq.com");
    ; `3 }1 s- R4 @) F! ~        result.put("mail.username", "路人");- a; T" `7 P, s9 }, S" [
            result.put("mail.password", "123");! p/ u" L1 A# a1 C' J
            return result;
    ! n. \) [: H' U6 w5 b    }
    # h9 n, l6 r7 X7 y/ e}
    ; {& l# p9 j' j来个spring配置类
    # U3 Y% \/ u! ]3 w4 V( q. `2 v- K* N
    package com.javacode2018.lesson002.demo18.test2;
    . `9 @/ s0 R2 z) S) ~
    ) F$ t. ]' g) N3 h8 V0 C4 wimport org.springframework.context.annotation.ComponentScan;
    3 _; s0 }7 U5 T5 jimport org.springframework.context.annotation.Configuration;+ @* [8 U5 r) o8 U# q- w& i4 u

    + _$ d- V: l- A0 l8 w/ z@Configuration# K- J( ?( e/ q
    @ComponentScan2 r6 Z5 S5 o; t8 N+ a' e
    public class MainConfig2 {
    - Z/ c0 I: u7 C' k* m. w" _. d}3 n: F/ @. v. w3 C
    下面是重点代码* X  h; X8 E4 J' E+ P/ T

    4 |4 K9 o6 k' C+ W( f@Test! ^/ D; }1 a: s/ d+ i
    public void test2() {* t# s# |$ _: J  y% r/ M: T: J, a: M
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    # n3 P5 Y" J2 b3 L8 N- |" M) ~. X* r) O
        /*下面这段是关键 start*/
    . e. t' N# f9 Z$ t$ _% m1 q) L    //模拟从db中获取配置信息8 t. n4 O2 [2 x
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    0 Y/ Z8 B6 g) T. ?    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)3 s3 p3 z6 v; ]2 ^, m
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    % H# K# X- _. r9 L; v- s    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    ' h5 \" M% x' u9 ]2 Y: L    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    $ {. _' A7 ?1 q6 o1 D: g4 P4 h5 m8 Y; m4 X    /*上面这段是关键 end*/
    0 L( n  ]4 T  ~1 s1 I
    / t0 v6 i, |1 R& B( A. H( _  t    context.register(MainConfig2.class);, ^& ~0 N: r5 Y; T
        context.refresh();
    ) u" p" N4 a& J* m+ s0 A8 ^) p( r    MailConfig mailConfig = context.getBean(MailConfig.class);+ h2 t% Z' d, D2 [- @
        System.out.println(mailConfig);
    ! z* |4 `$ y# s8 j" f, y' Z}: o2 H6 @, ~) O. i. b
    注释比较详细,就不详细解释了。, a# H. i8 Y- p7 b1 u) K9 W' y6 Y" {

    " p9 E8 @1 m) q3 o- O  W: u直接运行,看效果! }* L5 j/ p$ q* ]: T
    ) V) f/ z- R$ C  P
    MailConfig{host='smtp.qq.com', username='路人', password='123'}" R5 [0 N. a5 p. C7 v+ m, Y! n
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。! t* N7 R, c8 M" E9 F# _1 E
    / G6 c7 C2 z8 O9 a- l+ M9 b
    上面重点是下面这段代码,大家需要理解
    : G7 j# D6 y+ [. E* }% u' X' p9 {8 W* c
    /*下面这段是关键 start*/
    " W" F% I2 t# _//模拟从db中获取配置信息
    5 S. ]1 j4 ]5 J3 uMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    5 F! E8 I5 Z+ I! q//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类); K8 t  z2 q1 j& b* t  _
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    & Q9 Z2 [" o, B//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高/ s; k4 a6 i/ }/ X4 d3 G
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    / x2 @+ b* M3 ]. \( x1 f/*上面这段是关键 end*/7 J# C! U1 V! n' v8 D8 M7 \
    咱们继续看下一个问题: D( S# S& |/ b" c8 G& e
    9 c" t2 k7 v7 }3 ~) A2 S, ~; i7 s
    如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
      Z4 [4 \$ k5 _+ l2 `5 o6 z# u
      F  d9 g( v! l" d: U; I" h  p0 g@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
      h2 x! M0 Y, e. w
    4 w4 m( _/ ]; g4 [3 K" [4 H实现@Value动态刷新
    " L9 |* j/ ]  l* E# X4 T- Z+ x
    / l' D# ^# ]  T$ r' K先了解一个知识点, Q( a. H/ X0 i, s
    * E/ |+ ~" A# e5 x5 }& s
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。/ r  c% o9 g0 h) P5 [: Q( h
    0 v& t+ A' e' X" ]
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    1 [4 ]# j  f' p8 o! W# Y+ R) D
    1 Q7 K( g  K& u. C& e2 I; Nbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:6 Z! @* a0 f+ r+ U/ A; C

    9 d% }6 O9 r4 qScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;5 U/ j6 g. [6 I/ @; m+ a$ z4 e
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中1 k- O5 q# l3 b' Q: [  I* ~( k
    3 q; h; j2 j3 ~8 M- n3 D
    public enum ScopedProxyMode {$ `; \8 V, S$ R7 j  M( i/ o
        DEFAULT,
    ( g! ^/ r5 g! }& H( ^/ S( v    NO,1 Z7 s0 I  e2 M+ r7 G& X
        INTERFACES,% v; M! e3 H" p* ~+ ~% W/ Y$ }$ z
        TARGET_CLASS;4 S+ H, a8 D" [3 `& H; X
    }
    ; _4 j, o' ~4 P6 K( v6 t前面3个,不讲了,直接讲最后一个值是干什么的。
    1 H, O5 i+ Y, y' m9 l6 v
    9 g$ _  W$ i/ s, W当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。9 p$ O$ S* q9 I* k: z/ m# M. k6 O
    5 c; W& r2 A$ S! A
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    " Q: Q1 E. P2 G% @1 `" F9 Y5 |0 u- Q  t% c& o  y
    自定义一个bean作用域的注解
    4 R4 q' [: i8 M. j$ a$ v3 k) g' K; Q- l
    package com.javacode2018.lesson002.demo18.test3;
    ! T- q, ~" m( w1 p4 [' U
    ) j* o- n! p+ H3 N. R; timport org.springframework.context.annotation.Scope;
    " p9 g) E: {$ c. W' d0 W" Bimport org.springframework.context.annotation.ScopedProxyMode;
    5 S: y3 B+ E. j4 M$ [
    0 V% Y7 K* G3 c* m/ M) |/ o. N. a0 x0 v! qimport java.lang.annotation.*;# b9 h# J: g5 N1 f
    0 F% T  V- V' u# f. D) H
    @Target({ElementType.TYPE, ElementType.METHOD})8 U$ ?" x& ]3 L* ^! p. p& }
    @Retention(RetentionPolicy.RUNTIME)
    6 W& W/ B! I( |3 `1 v/ ]" J@Documented$ A# h0 {7 K4 Q2 r* _
    @Scope(BeanMyScope.SCOPE_MY) //@1
    6 J% s2 u$ y! a+ u+ s% J; k3 }/ cpublic @interface MyScope {, b' N4 Y2 B) c* w
        /**
    9 e& I9 k9 D: ~. a) Y     * @see Scope#proxyMode()
    9 v/ b- F8 P5 H) A. G% f: d: H     */& X1 M- k+ Q# u$ [& ?, Z+ r* ?
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2& ~  f7 _9 e& e$ a: c5 ~
    }
    3 E7 w2 A: s2 E1 O$ \! \# y3 A@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。0 j1 T. b  v% z& |

    1 N1 F2 s8 L( `: N/ u@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    / X4 ]' I  T. j& S
    ( P7 o: v* z6 t@MyScope注解对应的Scope实现如下
    7 ]3 F, P8 E& D4 E' Q' P* C$ K) R& N4 x1 l$ x" \
    package com.javacode2018.lesson002.demo18.test3;9 ]$ J, ^$ P9 I" c1 y

    0 L6 F0 n+ c- kimport org.springframework.beans.factory.ObjectFactory;
    ( g4 y% f+ J/ I- f7 k, ~import org.springframework.beans.factory.config.Scope;
      p) [# I& O# A) B! X3 q# Mimport org.springframework.lang.Nullable;
    $ a  d7 ~. q! O- w& \$ p
    : c7 ?. y1 G5 x+ O2 Z6 U$ p/**
    6 |8 H  Y. g) E * @see MyScope 作用域的实现
      Q; n: z6 e0 x  G/ [* s */. T, M' H1 m6 H3 S/ H; |: ?3 H4 i
    public class BeanMyScope implements Scope {
    * b( |4 \, Y4 l6 X. Z0 y, ?) M; F9 X1 G- _1 B' t! T
        public static final String SCOPE_MY = "my"; //@1
    ; K& C, E  u) [, ?7 v8 o4 G8 {9 k5 \' s7 R' e( b
        @Override4 t# J$ D8 A7 a2 u# e$ t$ X3 A+ L
        public Object get(String name, ObjectFactory<?> objectFactory) { 5 {% V/ g8 ]# _* `! E
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@26 ]. v5 v4 d8 N' a
            return objectFactory.getObject(); //@3
    % H8 g3 n+ g+ P    }2 @) i) n/ N( X2 ?6 R
    5 I- [# y0 R5 d) z+ d% V
        @Nullable
    $ c1 `2 {2 [; \# y- G. T0 h4 |1 W    @Override
    # K, B, l4 H" ~; n+ r. n5 C2 B0 d    public Object remove(String name) {
    3 ?2 t% D# `: E  X7 U        return null;& v1 t9 I: O9 x, h/ a+ J. w$ r9 m
        }- B1 f- @( B4 ]5 O% z' ]; |5 R$ F

    , k" @9 l. H5 b  S" y    @Override% y; I/ D& w8 M0 f% Z- F: F" `
        public void registerDestructionCallback(String name, Runnable callback) {
    & C: u. V" Y; y" W9 J% S# i6 z; C% L! ?8 _! m( }" c$ q
        }
    ! y. v( t6 L3 \  E' U# ]3 T5 o* m) _
        @Nullable
    ( \" Y+ P% L- o" G  U    @Override
    ( o* a- x& J7 M' q, A- w    public Object resolveContextualObject(String key) {/ d  N9 B7 C* T, _% s& o' t
            return null;6 w. X' X$ W' s" }; {" @5 m6 S1 u  W3 s
        }: j* }" f& q3 ~7 f& ^
    % }, e  \6 m* t
        @Nullable9 V5 M0 e0 d- V# F) P
        @Override
    7 R5 ]) D& R% I0 I+ B8 ?    public String getConversationId() {
    % O, C2 d% L6 s2 }        return null;
    " N3 K1 X" X9 y- K% F    }# k: F/ I% _6 K( q& t
    }5 A4 Y0 n' I' D9 w6 T0 M& Y* _
    @1:定义了一个常量,作为作用域的值
    ; y: U* m7 e7 X# K0 J& |* X; V4 i, |7 Z+ D5 g( _+ J
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果/ j) f! t* K' B

    $ n9 W- R. J3 X; @% y@3:通过objectFactory.getObject()获取bean实例返回。
    * l$ C) \2 V  v' Q0 ?; X' W7 U7 r+ F
      u' z) Z4 r% C1 n) U2 g下面来创建个类,作用域为上面自定义的作用域
    2 ~+ K7 l2 B0 l: |- g9 l1 w; {: G
    package com.javacode2018.lesson002.demo18.test3;
    " f/ h7 I7 w) e3 l) z  Y$ ?0 o* Y$ P: e/ n" ]1 E- k6 K# N
    import org.springframework.stereotype.Component;$ C" H( d% S$ B7 E5 O

      e3 t/ k, o. Z  ~' [( O$ nimport java.util.UUID;
    . R0 A; \' B4 I6 Q& g: \; R4 g) t/ [1 ~' ~' c
    @Component) L- k& o2 N8 h
    @MyScope //@1 . |. s! j, a# W3 e; U/ c8 x. Q
    public class User {6 n/ {$ \% r7 i( J4 `2 M! a

    * o7 q3 ~, J( U# }; \    private String username;
    : S) ^1 z* S# e3 j: A0 j: W0 o# q, @
      W% K0 u6 E; w    public User() {
    ; [2 ^, l+ X% `$ Q$ M( Y# m        System.out.println("---------创建User对象" + this); //@2
    " \1 h. j3 [; i; b        this.username = UUID.randomUUID().toString(); //@3' g3 V2 S! r# ]8 I* [1 ~9 [/ G
        }
    - S2 Y- F3 J& X# c6 ^# f$ n, I. Q! J& D6 D
        public String getUsername() {- P) w3 {7 x. K1 N  t
            return username;" w3 q( G; `# R  ?9 J
        }
    # s+ k) i) I$ ?  C9 E6 w4 y4 `
    2 g$ n% ?% }: K9 j5 d- I    public void setUsername(String username) {$ }# B- s- x- J' w! e3 b7 y  P
            this.username = username;
    9 \2 U- s( A% D5 R$ g4 C7 v    }
    ' \& m% A# Y8 A8 ^; \+ M2 V3 l0 m
    ! m( m5 y& K, D3 f- c" @7 `}
    1 H$ h; k% W+ M' E! x@1:使用了自定义的作用域@MyScope
    & `; ?' ~" K/ b  L5 {1 r4 ]6 e3 d" r' g. R( m- V
    @2:构造函数中输出一行日志
    $ L+ m" j+ @) ~$ F5 S
    - X/ X; l4 r+ ?: y1 m" E@3:给username赋值,通过uuid随机生成了一个! T" \, \/ z7 g, E' P
      Q; I: \/ E- e+ m$ e0 {' j8 v* v* U
    来个spring配置类,加载上面@Compontent标注的组件9 \$ D0 R: F' X/ _+ s+ p+ T9 c7 Y, C
      M" R: [# _4 k, ~9 v" p( Z% F
    package com.javacode2018.lesson002.demo18.test3;
      T& m5 s# z2 ~; k: W
    9 a1 b) X/ T" g6 G- i' t( c# A$ cimport org.springframework.context.annotation.ComponentScan;
    ) v2 V  ~1 n! _  v8 D; `import org.springframework.context.annotation.Configuration;  k( K9 B. z% n: ~; {( \
    % |$ @  ~" ?% F7 f
    @ComponentScan  S# H* _& g& Y& p
    @Configuration
    , F( a6 p5 J8 T, |public class MainConfig3 {
    3 C9 Y4 h; _0 P. ?; u- R" F3 }}
    9 ^8 s. Y* m+ w) S3 C, R9 h# p* D, a下面重点来了,测试用例
    5 Y/ ?% M! o# E4 `( z: q+ T2 r
    % T" X: B$ v( `; {@Test, {3 p& f( X" u% |+ j
    public void test3() throws InterruptedException {
    6 s0 }% s( Q4 T    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();8 [' f% @8 O- a9 B" T
        //将自定义作用域注册到spring容器中/ a% `/ i$ Z+ J3 R+ r
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@19 e3 C1 F2 c& m+ m
        context.register(MainConfig3.class);
    ( X, ~' y" g+ r, b# E: m0 y    context.refresh();
    ( \" k6 v8 G2 L" X' R, T
    : E5 z/ @. K: y3 A# k' W' H: Y    System.out.println("从容器中获取User对象");% X+ G3 F  e4 ?" j& f
        User user = context.getBean(User.class); //@2
    ; h" `8 d0 e" _. r2 X: Q: F    System.out.println("user对象的class为:" + user.getClass()); //@3
    % ]- e4 m1 I+ V6 {/ \
    " d# O3 ^+ o, O$ x    System.out.println("多次调用user的getUsername感受一下效果\n");
    & Q+ n# E/ R/ E" F. F5 V# O    for (int i = 1; i <= 3; i++) {" i4 B9 }6 M- ]2 \5 J" s* x
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));3 b% R4 [4 d( F* X9 C# k+ g9 `
            System.out.println(user.getUsername());
      _* _' A# I8 l        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    " X4 w9 R& O$ T) c6 X4 c    }
    * {+ w7 S0 E7 @$ ?, D7 C. O}
    8 r$ o4 Y  ?, [( b, j3 @. a! E8 \; w2 m@1:将自定义作用域注册到spring容器中# U  E/ r: Z% t
    # ^* p  I; w, J  Y
    @2:从容器中获取User对应的bean& Q9 i0 D" @. W' I
    : ?/ C) E7 C/ p- @& W
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ' ]+ X' M6 a& s5 e! e$ q6 z8 T. U
    ; e4 W' [6 F0 k: g7 D; c3 G代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。. D5 G2 \! |6 g/ v: _
    % b2 ]( ?2 [/ S7 D
    见证奇迹的时候到了,运行输出' R* W3 r" g5 N$ t: N
    " i5 y/ t' F1 c5 |9 Y
    从容器中获取User对象" ]) E% i0 |) b) y
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    , `& X/ ]( A9 Y8 H6 I多次调用user的getUsername感受一下效果
    ) i) U& A: p" N  H; A  W3 P1 m* T
    ********
    8 L- v6 w$ _5 }7 i# ?. D3 D第1次开始调用getUsername4 h) E5 S3 n7 e+ ]) l" G# I/ b
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    # u# H$ r5 _9 a: S---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4  {2 x6 D% T- C
    7b41aa80-7569-4072-9d40-ec9bfb92f438
    ! h9 c  c# U8 |$ K第1次调用getUsername结束" C6 r+ e6 P1 C+ O3 q7 a- Q
    ********7 T9 c) b( c! `" T
    4 k; C7 U9 D& f  p
    ********
    3 Y7 r$ \2 e4 s4 P第2次开始调用getUsername
    : X% ]" u! e$ t$ z. tBeanMyScope >>>>>>>>> get:scopedTarget.user4 d8 [) J/ D/ j2 j: E" K) U# E
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b- f. v8 m& n3 |0 y2 z. z
    01d67154-95f6-44bb-93ab-05a34abdf51f
    $ K/ w9 L& S; k# F/ u8 j6 D+ p第2次调用getUsername结束$ {: `' m# A0 L$ Y% i& R
    ********8 p+ j2 z2 s& @# a9 G1 a

    4 I. ]/ x; u! ^7 V: j- k********: T' t' u" H8 I: x
    第3次开始调用getUsername
    , s" X( u0 E- O0 w7 b0 B! NBeanMyScope >>>>>>>>> get:scopedTarget.user
    * j( P% N% e5 A# H---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    7 K" t5 ~2 C. X! N* {  B6 {76d0e86f-8331-4303-aac7-4acce0b258b89 _0 w3 f  v  D5 L% k8 z  F
    第3次调用getUsername结束- N" z, J1 ^$ i- l( \$ Z* A
    ********
    % y/ x0 c7 ]3 u5 v% y9 x1 t从输出的前2行可以看出:
    : T( b3 v) r8 a: c  L; c3 }6 r: U4 C3 g- s" }3 [' @
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    2 k8 z# K+ S0 A" Y# g. r7 I/ l9 N8 `* Z, b; q5 h: D5 S2 B
    第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    ( ~% p5 y) \1 g8 L$ X
    + M( [3 m' ?# y$ x! @  ?后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    / V6 w  \. r9 z$ q: H% Z+ a! d
    , c6 I. R$ S) `$ {, Y; Z* y通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    , i: b' l% D  X4 T4 e0 ?5 p, J) B/ v0 W+ \4 u0 K* x& _8 a) ?
    动态刷新@Value具体实现
    4 f3 Z/ i7 k3 O) v: n$ ]$ s) g3 K' W3 C  x6 d" k/ |
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。+ L& v0 n/ x0 v- |; w2 G

    ; f% x' {# r! ?; H8 {! F2 H0 Z先来自定义一个Scope:RefreshScope# c8 I3 D8 P* f# l4 Q7 [

    6 p* r6 o: K4 f0 B* X7 b' K/ z( o7 D; cpackage com.javacode2018.lesson002.demo18.test4;
    / N& W  H  [( D0 n; n6 H) Z0 e
    ! K0 S* J7 b# I' eimport org.springframework.context.annotation.Scope;
    5 c( W3 K" p2 s$ D7 [0 O# X) bimport org.springframework.context.annotation.ScopedProxyMode;9 K; @% b$ g7 u9 Y7 l4 `7 t
      j$ V  y* D4 d. N$ g
    import java.lang.annotation.*;
    4 y( i6 \) j  I1 K7 b: a8 J6 S: v* T: d( _
    @Target({ElementType.TYPE, ElementType.METHOD})
    - s' F( y# H+ a7 G0 Q) O& O: T@Retention(RetentionPolicy.RUNTIME)! M7 h$ I  F' f, U, F, ~+ W+ q3 c% O
    @Scope(BeanRefreshScope.SCOPE_REFRESH)5 J# \; N9 O1 s
    @Documented, D$ e( L% K, y. @7 \3 ]: w
    public @interface RefreshScope {
    " a' @3 j: E" i) [    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    ' Z$ _# P5 v1 d1 \6 J}0 d2 k. w' h8 \7 }* \* a) u
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置& O9 [$ m% p! r
    6 [  J7 w7 J' W
    @1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    4 G# q8 D2 I# U. a  D* `& D3 l" C! w# p$ T
    这个自定义Scope对应的解析类. `7 d& r9 F+ n% y2 u

    % w4 \* {* _2 I3 e: Z2 B下面类中有几个无关的方法去掉了,可以忽略
    : p- ^8 r8 R# H) C2 l0 ]5 ]
    6 j& f$ u' I% i& epackage com.javacode2018.lesson002.demo18.test4;
    ; K- M# d- B* `5 a. v; |4 O! ^* O1 n

    3 m, l+ ]) w3 Y1 w( D. v; gimport org.springframework.beans.factory.ObjectFactory;5 @- y9 s+ v9 C! B% y3 l) L# ]
    import org.springframework.beans.factory.config.Scope;
    1 u$ X' s; W. Wimport org.springframework.lang.Nullable;! U7 A7 j: c. o5 U7 W. R
    % n* Z# C3 G6 m4 T/ V2 T! z: Z
    import java.util.concurrent.ConcurrentHashMap;
    ) }8 Y! [8 Y/ s- O# z; W9 J
    ) v; F- _; f" R; Qpublic class BeanRefreshScope implements Scope {1 @5 z; P& S. o! G8 X$ Y
    ) G7 A# h$ H+ ]4 J. \$ Y, ~
        public static final String SCOPE_REFRESH = "refresh";, l9 F' f% J3 ^+ {& ?

    0 S$ w6 c" S2 J; F* a    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    3 _: B3 u3 k: x. W/ c& ?; h$ k& R' Z, Q
        //来个map用来缓存bean. c! p( @6 D5 i; E% }4 f
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1. _  Y5 A) k! a7 w! U% ]: I

    - H( ?# X5 ?; L) p( z" ~    private BeanRefreshScope() {
    , @9 [" w6 c. f" y5 R, d# O    }0 D" U) L% J) {6 n) G5 y7 W& I- i
    - K1 a" B5 \/ z5 k
        public static BeanRefreshScope getInstance() {. @$ z! e' a; {; a, p0 C
            return INSTANCE;4 g% o1 ^! d% ~0 ^* K2 `$ h( p% H
        }& E; e; s' e( U5 e$ {4 H

    3 e7 X- Q* w  e! K    /**
    8 g2 y2 b8 W9 F% Y, R     * 清理当前
    - ?7 Q/ T  L! i- L( E7 z& a     */
    7 ~9 @6 k# z. D% K. x6 c    public static void clean() {5 A; @: ~& M$ {$ P) O  C# I% ]
            INSTANCE.beanMap.clear();3 h6 l" Y, K# g( [0 \; B; v
        }& _# }5 `( q* B- p8 r9 I$ c) p
    ' K" y5 A9 `7 k. Y! X+ l2 V
        @Override2 V. q2 u4 f( o8 S4 L/ |
        public Object get(String name, ObjectFactory<?> objectFactory) {  ]8 z' j* h5 `
            Object bean = beanMap.get(name);: f0 E8 |/ w% Y. q8 _$ L; T
            if (bean == null) {" J: j8 W' |- H; C% ]) ]) J
                bean = objectFactory.getObject();
    % p$ Z6 P, L2 c            beanMap.put(name, bean);
    $ j+ x) y. A1 e: o        }/ B) P( r; G- Y; v
            return bean;
    3 _& u. m& t+ d0 t1 n, `( q* \/ P    }# b+ J* l$ c+ P; F
    7 L, R9 Z/ }3 ?0 {: A) V) p
    }. {) k+ h. I& ^; r, z1 s, h; c
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中, |, F5 @3 h- K+ S- `1 j) }& E
    & ?5 {. e2 L  W. F/ |
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    . |: I0 f* X9 P2 l/ k& |: G( [! Q! R; H& v; ^) Y1 s
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope5 p2 j  n$ s; S: N1 X
    ; X: c& X, ^- f/ g
    package com.javacode2018.lesson002.demo18.test4;, ?4 b3 }8 J% Y* B$ n& N2 k3 I3 k

    8 E2 b# o& y" |+ F6 i. C5 t0 ^import org.springframework.beans.factory.annotation.Value;+ M+ m" y# z- X- T& T
    import org.springframework.stereotype.Component;
    $ F$ W" w+ L, G* y% B$ I: r
    / D+ C8 {; w. K' Q/**2 ?+ N$ `5 j# `) L( m( S/ s7 r
    * 邮件配置信息8 u% A2 }) \$ V: X
    */
    8 Z- _! W- X4 T5 O6 j1 k@Component; y5 K& n9 W2 U1 ^. u) `
    @RefreshScope //@1
    * P( k9 @  V0 F) vpublic class MailConfig {, ^! y6 S% h6 P- G3 K% o2 n1 t5 k

    9 M- q9 A. M! c. ~# g3 m* _- L    @Value("${mail.username}") //@2% S- d+ i# J6 X& V+ O2 X
        private String username;6 F4 n! |' g! j/ @* ^/ A
    $ Q2 m) I4 v3 W" T
        public String getUsername() {
    : B  f" C1 i! q  f' S) p        return username;6 d9 {' \+ B7 C" u4 W
        }
    0 {# ?$ N$ o; f  D2 P" N1 Z6 P. G4 p: v: D
        public void setUsername(String username) {
    * L$ @9 C8 g/ g" J$ m3 r7 W        this.username = username;
    8 |1 D! F: d: Q# `5 k1 J    }
    ; x% c  n. Z- h9 Y4 h- J. ?+ h8 h2 p( N, f9 b- G
        @Override" m* ~4 f+ {* _  ^; \8 r
        public String toString() {0 B& q( w, y( a- h; t$ ]# ~) R
            return "MailConfig{" +2 F2 a9 j( h6 O' k0 H
                    "username='" + username + '\'' +
    6 o5 L. N  e7 z; t3 s                '}';
    9 Q5 ^) A$ I5 ^; E  ]9 N' N% M    }
    ; @& d8 y& c8 e1 n# `' K}
    ( i  L4 h9 C& l# T2 y. }; p@1:使用了自定义的作用域@RefreshScope
    & u6 C# B  g- U+ ~( I/ J, P" |8 S% O6 y
    @2:通过@Value注入mail.username对一个的值- X3 W: _& A8 C, s2 A$ D" x- `3 V

    5 \+ J( Z! O+ z1 t. I$ i8 h重写了toString方法,一会测试时候可以看效果。
    % l$ C* S+ X* v- c5 ]! l" ~- z" V7 L! D! o. r
    再来个普通的bean,内部会注入MailConfig
    5 ]: L# D- s- q0 v/ w6 q% P. |0 D" j' N6 {/ M+ \+ s
    package com.javacode2018.lesson002.demo18.test4;
    3 d9 p8 U! S( x; y. c  y4 f" Q4 x$ K4 e: s  a( [# W
    import org.springframework.beans.factory.annotation.Autowired;" |0 |" x9 D3 y6 u6 S" M+ z0 i: F
    import org.springframework.stereotype.Component;- q* |, s) s1 D

    4 V& N2 {9 N; ?2 t0 L' o@Component" p  f9 Q4 G( {, s6 N
    public class MailService {6 u6 W5 D  u! l) ]5 P5 j- X) W
        @Autowired8 i0 H4 O3 q, r: C- B1 }  `
        private MailConfig mailConfig;* J; r1 \# U; U5 O/ p

    5 ^% ~9 c+ s. L    @Override
    . A/ y" o; b/ K    public String toString() {
    ' h* V5 `$ q! ?, D3 ]        return "MailService{" +
    % r6 A  h! E1 C5 I  ^3 K# h; C                "mailConfig=" + mailConfig +0 y4 L  ?' A9 S. l0 x
                    '}';
    " U9 C( _( g2 ?. y    }3 a, ?: E1 N, j% Y; k4 E9 a- Z
    }
    2 N3 j9 Z7 y3 e+ q2 V0 q1 E+ r' z代码比较简单,重写了toString方法,一会测试时候可以看效果。% `' q+ |# L, M) K1 G
    * N; q( C% n! N, k( Z. h, F0 m
    来个类,用来从db中获取邮件配置信息
    ) X( W0 w5 B) ^1 ]! J( P6 M& `9 H: t8 G; v
    package com.javacode2018.lesson002.demo18.test4;
    - ?+ s; U& Y; t
    # y' m- `" y3 A# V! ~import java.util.HashMap;
    " n4 v9 L. w7 M  }import java.util.Map;4 D2 k' k( w/ o8 l  a1 g
    import java.util.UUID;- f6 s) E$ _5 J2 a
    3 Y5 A5 F0 h/ f- H
    public class DbUtil {* j, h7 Q( o/ D) `
        /**3 a1 C0 i1 K5 ]6 `3 H7 s3 F3 ~
         * 模拟从db中获取邮件配置信息
    ) F& G3 ^- L3 k* z; _% X     *& ~2 j2 o* J1 K6 E: }, W9 r
         * @return
    ' J) Z" r" A. K' B% }6 H7 E     */3 G# F6 |- N$ A$ u7 M$ k
        public static Map<String, Object> getMailInfoFromDb() {( g" |& i5 x  f1 E7 R
            Map<String, Object> result = new HashMap<>();7 R; X% |3 G2 w: C" l, z) U
            result.put("mail.username", UUID.randomUUID().toString());/ u% |) n: q) O1 w
            return result;
    ! p; `  f( x8 I    }
    + ^0 H. |, J* w7 J+ V}# P0 }2 U% A& C: f3 ^
    来个spring配置类,扫描加载上面的组件0 @6 u- A7 \- F
    - @$ k: [" E9 Y3 V/ U
    package com.javacode2018.lesson002.demo18.test4;3 @( k3 F8 {4 i$ t

    ; p! ?$ E3 Y/ |' W4 ~! l2 k, Q6 Gimport org.springframework.context.annotation.ComponentScan;
    3 N- E4 @4 T' O3 O5 F5 H; Pimport org.springframework.context.annotation.Configuration;
    ( g2 I2 Q9 R2 `# }. }2 p: X4 c, j" v) @8 ?& x% D3 Z
    @Configuration
    # w# j- q4 f0 x, I@ComponentScan
    . o8 B7 _2 P/ Q7 Wpublic class MainConfig4 {
    ! Q  r8 i% M1 r, l/ i2 ^- ~}; Y/ W" I4 t" }7 J
    来个工具类7 b1 M$ W9 v3 k
    / q; Y! k8 W& M) J8 t0 p
    内部有2个方法,如下:
      i: Q6 f2 B% q! K& u: x: L  H' o* a; B  t
    package com.javacode2018.lesson002.demo18.test4;5 K! X+ Q% e  P2 @# O% e9 U

      N2 P; X% @) d% v& wimport org.springframework.context.support.AbstractApplicationContext;: L9 p* y2 K+ c' }( P! c
    import org.springframework.core.env.MapPropertySource;
    1 O* ?/ }1 B1 j0 Z
    $ g4 [4 e) k( M, R$ V; q& uimport java.util.Map;
    + y5 P( p* g, a7 i; x2 x+ N: `3 a1 I) i( t# b
    public class RefreshConfigUtil {
    0 {0 O7 M0 ?; r* f6 ]# }    /**
    ' q( o4 |  g2 F- j+ ?- m; x- J     * 模拟改变数据库中都配置信息
    + y- z" I; l  l/ g# @9 a' V     */2 I& e! {; s2 w+ g/ W
        public static void updateDbConfig(AbstractApplicationContext context) {
    ( r6 I6 R8 G8 M        //更新context中的mailPropertySource配置信息
    2 N2 l7 o/ P4 X0 F' X) b* f* I        refreshMailPropertySource(context);
    ! f8 o. n  H& D9 D: u) U& ?" R: n" |" O# I8 [7 R, @
            //清空BeanRefreshScope中所有bean的缓存; q4 Z# U' {  D9 i# V
            BeanRefreshScope.getInstance().clean();  P2 S6 Y9 I$ X% x4 E' [
        }: a5 j9 a# y  k" C4 \7 \
    ) s" f/ R) X3 H; ]% e
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    + I+ r( t" {, `3 p8 N' O7 o% v" p: \        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    . p$ O( I  ?% ]4 L1 |        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    + j$ f! N& W4 K' r& P5 t# ^2 x: J        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    # C) H' f7 b8 U% F* P4 y2 n        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ! j  ]; [9 b0 f1 ^' @    }' K* q- ?& F& M, k7 M& {

    2 \" a. \$ J" Z- r$ ~) J1 c}
    2 G8 F  w: _! C5 xupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
      j6 |5 K) p; e
    0 r0 _; s% Z1 |  f+ ]) LBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    ) ~* [/ D& {- U& j. G- a' _7 x. U7 v4 U7 W2 o. x
    来个测试用例
    4 ]. L* S5 V  H+ H( H" Y) x! ]1 f" W1 J/ H9 z
    @Test
    0 {3 B' I. Z4 a! [6 O9 Zpublic void test4() throws InterruptedException {0 |& @! ^6 B8 Q! j
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();. O% X( H2 A0 V% A$ ]+ {
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());) h$ i$ i# z7 {! N- ]' y
        context.register(MainConfig4.class);; B6 _+ q. r6 @/ g; x2 G  g
        //刷新mail的配置到Environment
    * x* ]: Q0 M7 _0 g# f- @+ }* t! _8 I    RefreshConfigUtil.refreshMailPropertySource(context);1 [& F, p# ^* E+ c
        context.refresh();. x. }" X2 V) p+ F' S
    5 K" r7 Q. p+ s) p$ B) j& \1 ?
        MailService mailService = context.getBean(MailService.class);- c& |# {6 m4 a. F
        System.out.println("配置未更新的情况下,输出3次");8 U( Q$ D# T4 C4 x5 |3 d. j' H* Y9 B
        for (int i = 0; i < 3; i++) { //@1
    ' \, H6 C" a* ~) O' P        System.out.println(mailService);
    ; f+ m$ d4 h1 r  P( T2 V        TimeUnit.MILLISECONDS.sleep(200);
    0 q  `9 o' h- z7 y0 t& F    }
    ' S8 F& c& W  _# T- A7 w: A( h: m. ]0 b
        System.out.println("模拟3次更新配置效果");
    ; i# g' P0 |- B  j4 M    for (int i = 0; i < 3; i++) { //@2
    + H& V3 x; Y3 L+ k) c        RefreshConfigUtil.updateDbConfig(context); //@3" O8 Q  |( ]5 ]2 i  P! g1 h
            System.out.println(mailService);
    2 W0 |0 }# v# |        TimeUnit.MILLISECONDS.sleep(200);
    8 L: @  @/ P3 Y8 m! x+ W' c: s    }: \% O" _9 Q& H$ x% i
    }
    . {5 B$ u9 D- B* W@1:循环3次,输出mailService的信息
    - q, |6 Y( a- p* K2 h6 x6 w: Y
    ! N( |7 U/ ^. X: ?@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    1 N6 i0 I0 l! }3 y6 c. q
    * e, I) |% F; ]* m见证奇迹的时刻,来看效果# s: Q% G' F* M. M

    2 ^" B; |$ }* h  E% P8 R" y# l配置未更新的情况下,输出3次) H4 r$ T" Y* J! v& ?. P
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    + z! O& n+ b! k- Z7 FMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    . [5 C$ r; A7 Q3 i% i- M: o$ kMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    # d- ]4 |$ O4 o/ |1 E) Q  v模拟3次更新配置效果
    * F3 r- p* J0 d* _MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}0 W8 v3 J3 i3 k$ y+ l
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}# A) p- m- `* F2 ]1 R1 G. I
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    # o0 N) b6 r# U8 F# v6 a上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。! X5 c/ q% p) ~$ s: k

    ! a' B' \7 a0 o+ |" b小结
    ! K3 n; f( O3 M/ d2 \4 g4 P
    % n1 M  p6 W, Y7 \# S( ]动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。  M' a& B3 m0 F

    % Z2 }% `4 u* ^有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    6 i! b( W1 B  o( J  N* B! t' \! A, p/ H# L8 a8 j6 q. O" i0 u' ~
    总结
      H" U; p7 e6 x  o1 ?
    4 ]/ m/ ]$ e: v本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    - }, M$ g" g' V+ o0 s/ A( p3 a- |" K6 A$ y9 J5 B& Q  p
    案例源码
    " F& R/ S" M0 T5 ?& S  {  m' j4 T0 s
    8 O0 U7 y1 n$ Yhttps://gitee.com/javacode2018/spring-series
    * a6 ?* A8 U- b5 j. t0 d1 x$ l路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    2 q: U0 t' ]# U
    : R, J; H: v+ K. `Spring系列4 y$ g" `- `2 ]! y
    " V. o& D+ W7 I/ [( ~1 H0 k! P6 Q9 F
    Spring系列第1篇:为何要学spring?: d$ `+ A& N7 N  O/ b6 j1 N% b

    5 c% e! [1 u1 l  w( D' V! ~Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    8 X3 i7 P6 U* Q: {1 _1 d  a: [6 N3 |  P, P$ j2 |
    Spring系列第3篇:Spring容器基本使用及原理
    5 E% \  O% O8 `' T4 R6 ~; [4 _: d
    Spring系列第4篇:xml中bean定义详解(-)0 D% W5 N$ t+ x, n! |/ |

    2 J0 E3 J4 I6 x  @Spring系列第5篇:创建bean实例这些方式你们都知道?
    $ ?5 K' p+ a+ n4 O- R  ]
    ) G- d  {4 x- O0 A  \Spring系列第6篇:玩转bean scope,避免跳坑里!" m) i$ T5 s+ t! B+ c

    . L5 E/ u! M& |4 B* ESpring系列第7篇:依赖注入之手动注入
    4 n1 {  }( f$ r! N5 h4 x1 t" \2 e% k8 c2 O$ u
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    % i: T" `/ W( N, t. }
    * ]. i) [  [1 L2 e, y# DSpring系列第9篇:depend-on到底是干什么的?
    4 Y' F2 f( |; h. Q, [6 Z8 D8 o
    * U/ e1 v/ t9 }+ b* r9 ?; T1 |) `6 sSpring系列第10篇:primary可以解决什么问题?( U6 ]5 O" T1 x

    3 T1 ?. x% H9 LSpring系列第11篇:bean中的autowire-candidate又是干什么的?
    - i$ `# l. M# _0 V3 Z$ o) j, u6 e; f) R5 Q, K
    Spring系列第12篇:lazy-init:bean延迟初始化9 X; c! q1 z& i- m7 R6 z4 T

    6 X- ^9 [+ H3 ~  g% \6 e# ESpring系列第13篇:使用继承简化bean配置(abstract & parent), t& H( ?  O; z: Z5 K, ?. {/ h

    3 T- u& K+ W6 V1 `5 Z+ RSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?9 [* q0 ]+ [: m, `) P; ?

    0 J2 b7 G9 e9 K9 ~; R- h/ A; zSpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    & x) g! a( T9 |( h
    ; M0 @( `/ O# M  BSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    ) C" I# H+ |) K6 R  _
    2 z4 s8 ?; f+ H: sSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)- G& L7 o* X, Q8 {, g( c

    1 ]% e8 l/ P: G* bSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册): G1 |. P* z1 o! w
      J: p" b; [9 j/ I
    Spring系列第18篇:@import详解(bean批量注册)" _( _* {0 h- y- P6 }' Y4 z) v

    4 n1 L& y6 k: G6 a& T5 {Spring系列第20篇:@Conditional通过条件来控制bean的注册
    7 M2 Z  o* T" B' m5 N: S
    $ i, E% u  |6 `7 M2 L8 PSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)9 s0 m+ J# w/ u4 b
    , g1 `9 g/ t+ d/ ^; A7 e6 ?
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    2 i! s2 [9 D" E% O3 L5 C1 q
    - S1 h& P1 L0 u" b+ Y; YSpring系列第23篇:Bean生命周期详解
    ' h& P+ j. @) @$ N  G7 H0 H: k' j% M7 ]* X3 k* W1 H0 q
    Spring系列第24篇:父子容器详解
      n  x6 K4 h, p
    ( d9 _5 n' H- e& ?/ k, q' H更多好文章
    * G% C3 R2 b# S/ a
    ! j" g* v9 r) I4 f6 uJava高并发系列(共34篇)
    ' G8 |" d" M4 A# @4 a
    . ^& z" I) o4 H0 h) I) `& wMySql高手系列(共27篇)* R0 i( F) a, B! R& K
    & i: h% J/ W$ z' s& p
    Maven高手系列(共10篇)# O: r  y( H% i9 F( c7 M
    8 u5 f" @, f1 K+ E+ E
    Mybatis系列(共12篇)
    4 l, u& `# {6 m, e
    ( F$ o# s  y* A& N$ B5 |' _  L聊聊db和缓存一致性常见的实现方式
    7 S% h# \0 K2 z* {) h; Z& n7 }3 |8 m* v- x* e; d2 }6 O
    接口幂等性这么重要,它是什么?怎么实现?
    8 `3 P/ i* v2 K$ R; O! O6 ^) s
    2 t* j6 G" M: u) M: O9 Y' P+ [2 q泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ! y$ \. W( X- }/ d& L————————————————' b! _4 G) g& F% n5 H! f
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    & h  k5 N7 w) X' k; L6 ~3 T* X原文链接:https://blog.csdn.net/likun557/article/details/105648757
    ! V1 N- @+ [1 h5 E5 m
    6 f. A( i2 @. d* N& U" m% Q
    - U0 ^; J+ T- S9 N5 d/ 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, 2025-7-19 10:28 , Processed in 0.593824 second(s), 50 queries .

    回顶部