QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5398|回复: 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!
    - y, Z9 }' S8 T, k: i疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!
    7 K2 }( d2 B1 {- U" v3 Q  Y; C5 N
    面试官:Spring中的@Value用过么,介绍一下9 H( Y& k2 I, M, j3 W  D' @

    9 k, u8 j1 ]* v我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    " y' ^+ ~1 l. j& S0 @( U0 g- ?# M+ y6 O) |
    面试官:那就是说@Value的数据来源于配置文件了?) g) D& J5 n2 R8 ?" t
    - B/ g3 [$ V7 e1 B/ [; b; m# ?3 k
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    / {  v) P- p" P' a. Q, o( `1 e' h3 h6 H6 D: G- k
    面试官:@Value数据来源还有其他方式么?
    / E3 i) V) n5 u& e$ B  K9 O* }9 P- c& Q2 o9 ^
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。: s3 @+ {' x# T! t/ A; P1 G

    1 X, w; _6 G3 l: B" |. {, D$ o: j9 I面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    ; ?8 m4 Z' s( k; i
    ' M" g4 c( f5 c! Z! C我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    ! `- |$ }2 `2 S, b6 R
    2 U+ r* X3 d: o: Z2 C面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    ' O6 C4 i  H! n, D0 q. W; c
    8 e; Q; K! o8 g& m我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能5 K' i0 G; C! |% g  i8 }2 V1 \
    ( {2 a: ~0 h3 r" J8 J" A( r4 z
    面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
    # X1 [3 H) Y8 ^8 ~6 \  B
    % \; \6 w# b- y- N3 b我:嗯。。。这个之前看过一点,不过没有看懂
    0 V' r$ }6 G3 o1 j3 u  F$ e* K  O+ t! d' Y; C  Z
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?; @  d; N+ M) o$ i

    & G+ Z! K+ E6 q, w9 }& X我:3万吧
    4 j3 Y+ U  J. |3 Z3 Z1 t" C1 |2 E" r9 W7 `
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    0 |& e+ y  f2 f1 D: V
    2 g4 S3 a( e" A) @: B; c我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万' Q, r' V* e( w

    9 l: z1 }5 a  B; F3 r面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    " a6 [( C# r) ~6 r0 E  [
    ! S1 k0 ~" h( ^% i( P/ N我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。
    + _& A& @( X6 u) m; u" O8 i9 t5 o* ^( w2 @' _1 S$ Z; G5 U
    这次面试问题如下) j# A7 h$ I' j; s
    " j& @/ q- k3 b4 ?2 s, ~" x2 b0 n
    @Value的用法
    $ y8 w0 Z0 Q: S5 q7 a- q, g0 A5 T- x) P& `/ [. t+ J
    @Value数据来源" \/ V/ y: ^# w5 S( O$ c
    & V/ {1 P6 o" b- J, l7 A' y
    @Value动态刷新的问题: Q. X) x0 L0 y0 f+ E: A

    9 [: X. D- ?3 p6 |下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。" j# Q# ]# J4 N) w
    ) t6 |/ O! O4 ^2 f& [" `
    @Value的用法+ X% X9 C. o; C0 z  C& P
    , U  @7 r& q9 X( n2 x
    系统中需要连接db,连接db有很多配置信息。9 Z0 b4 A3 |5 |4 s+ u
    3 {6 z0 L2 Z. K, l4 b/ }
    系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。* t9 V9 B4 X# [
    ; H) `, N; W4 e2 L8 g  P
    还有其他的一些配置信息。
    & T$ ?* }' m, Q: B  f# |7 H% f3 N4 |: f5 z0 b' p6 \4 F: d' w
    我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。. m* J# L" d$ ^. f
    9 C3 ]) [6 m3 r7 z" G
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    ' e# r1 [- d" U" V
    9 K4 c" a/ Z. p6 \通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    # V& l4 A$ ^+ r: f6 t& @/ l  q
    3 N( z9 e/ E3 g2 d6 H7 Q2 Z通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    3 \; b: W# f4 I/ r' z& w3 @2 B- W
    % E& W8 m7 g7 S  c& |@Value使用步骤, C: H# x7 m9 H4 O8 _+ J) ^
    5 P; x5 S* w* }/ Y/ U4 A5 F- {
    步骤一:使用@PropertySource注解引入配置文件4 i' G( u% P9 y. J3 g& c

    3 z* `/ P7 l" h' o7 G$ h- S将@PropertySource放在类上面,如下
    5 F1 S( ^  K! t- y4 O+ z* k3 k2 P. b5 H  c1 X/ k& b7 G
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    + o: _" [0 i( K4 F@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。4 ]+ F6 U& H5 d
    7 }- I$ @3 F, D1 V% H4 O- @( D: g
    如:
    : T. v( N# B& ]0 M+ P
    1 G$ ~, k3 }, K) P& P1 X@Component& x; z/ S  x5 k/ P# ~$ m7 q% U
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})/ \% g) K* o& [. T7 Y: @
    public class DbConfig {
    6 A* @! w3 p3 P}
    6 @) }8 i/ J+ k$ w; \4 ]( b! k步骤二:使用@Value注解引用配置文件的值) g3 v- ^& J6 O0 B- g  G

    ! ^7 ~# V* c% P, c% v9 c通过@Value引用上面配置文件中的值:
    9 Z4 M) G$ k# ^( h" h2 m+ k5 x" `8 t( Q; s. e
    语法
    2 Z" N* [& @  z& E2 C+ k+ D7 s' l# D3 h
    @Value("${配置文件中的key:默认值}")  j! s+ a. L, A( ?0 }. |- d4 ]
    @Value("${配置文件中的key}")7 {2 ]+ R1 j: k6 }( c1 K; _
    如:
    ; K# f4 n& u6 [5 S  a: N) z  v3 ]# ^# Z# ~7 |2 X7 e
    @Value("${password:123}")6 i* d" @& {* }. A' e% J
    上面如果password不存在,将123作为值( C( f2 M2 L/ t7 |3 J
    - Z7 x7 ^6 [/ D# @1 j
    @Value("${password}")5 d5 ?" s6 R8 {& ]& {2 M( G+ N8 Z
    上面如果password不存在,值为${password}  N) B7 q+ y( F! U( J
    * @8 d! w* _% _' H
    假如配置文件如下
    1 {4 K& f, s8 U
    ) c4 U0 o% o  Pjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-81 c' a2 D8 l! U8 Z, T
    jdbc.username=javacode5 z1 C0 |- v8 ~5 ?
    jdbc.password=javacode3 m* F4 U- y( x$ K2 o
    使用方式如下:; y9 E- M6 Y  K6 M" h# X7 _

    ) g8 c: ?$ @! u9 m3 Y! r' s@Value("${jdbc.url}")" X; R( }! k; r: C" ~
    private String url;2 d/ I) \- g9 f# @9 ?1 m. ^" N* J

    ' y0 d* U4 C. \& W& M@Value("${jdbc.username}")7 O6 C( f& g, A: c
    private String username;
    7 H" T2 Q* Z( C; V7 F
    , E  Y8 U1 x+ ]6 `& u2 k@Value("${jdbc.password}")
    ! B3 X# Z6 @6 K+ J1 v& C% p: o3 Qprivate String password;
    ; P4 e. ~: g% f& B9 @/ @# o$ u下面来看案例( ]. d8 Q, u  Q% E# Q  n  M3 K& t

    9 P. O# w$ F6 j$ S案例5 X0 s% O/ L" i4 W4 l8 t$ C) o$ }% |
    9 B  Q5 O. O2 R; w* g7 b
    来个配置文件db.properties
    # g2 w: O8 V* f8 N  v5 {
    , X$ H0 Q8 z: {  P, ]/ G$ o; b: ~jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8: |: U* p2 S& P8 A* b" O
    jdbc.username=javacode
    1 D$ [8 q9 N& a! njdbc.password=javacode9 r$ |5 ~, ^8 H1 p9 f. ]3 m
    来个配置类,使用@PropertySource引入上面的配置文件
    + Z# a: p' U- a  \# D! [" R
    8 `8 a1 \9 Z. n, r# tpackage com.javacode2018.lesson002.demo18.test1;; E) c3 K4 X( ?/ g9 N0 g1 \9 _% X6 H1 q

    ; m% u2 o2 B, f; d( H  nimport org.springframework.beans.factory.annotation.Configurable;
    4 `. ~# P' t: i5 C. A0 |9 {import org.springframework.context.annotation.ComponentScan;+ f# d; A0 I; d5 B
    import org.springframework.context.annotation.PropertySource;; W$ |2 Q, G4 ^% f5 b  z

    - ~/ _/ ^6 x  f2 ~( P2 |3 k@Configurable
    8 q" {" z, U. i/ X7 c# d@ComponentScan
    + B4 w  u+ P! c@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})6 A) K, l$ ~6 ~
    public class MainConfig1 {! F8 Q8 b7 E6 A' ]1 D! N8 g, P$ }
    }
    0 n3 H6 o' `" P$ ^来个类,使用@Value来使用配置文件中的信息! _2 V, O' E7 @5 [" F- V; H
    ! S" J( w. n+ H' b+ E* Q' I
    package com.javacode2018.lesson002.demo18.test1;( [& x% I' t. q
    2 X& r2 X/ \5 p" A: x9 o
    import org.springframework.beans.factory.annotation.Value;
    ' ?# H9 [# t+ M1 |import org.springframework.stereotype.Component;+ ^6 `2 b: ]- o
    ' s( F$ B& ?8 }
    @Component% v/ P$ R! _( M* B% W; D9 d
    public class DbConfig {
    9 H5 J6 k4 j3 J/ a: w( L
    1 _3 j+ J3 i1 g, k, ?    @Value("${jdbc.url}")
    # O0 L; D0 r9 ~8 J    private String url;8 V/ d9 N0 R0 P& k6 u* p

    7 a9 f; T4 b% C( Y; w    @Value("${jdbc.username}")
    9 _  v$ w! h" ^    private String username;+ L  V4 Y. k8 F1 ]

    3 `+ g$ h1 p$ D7 _4 V* W9 u    @Value("${jdbc.password}")& K: G! D. s) b* q4 G% e
        private String password;: k" Z; s5 B; K6 Y5 l% h

    : T, U% `, ^9 L5 x7 @: I$ m4 K    public String getUrl() {% s+ c# c6 F/ S: T: b5 }
            return url;
    , K! r3 t2 ?, y. e6 a9 S    }
    1 y2 u, Z# b% T+ M5 h$ c9 {& P- f) w" [+ e; }
        public void setUrl(String url) {
    ' S* }* O. c1 \( K3 Z) r8 o- K- g        this.url = url;
    8 |* m# ^# L% q    }
    1 d3 v$ |6 [6 h: ?" S' F, ?: K8 F9 L/ a
        public String getUsername() {' |! Y" C6 N4 n8 X( h, R7 r# K4 o6 M* E" C
            return username;* m! U' C7 g% s+ R( F
        }2 L1 v0 M) B; c1 k" ]! Z" c/ Y

    . k% h  ~. v4 x1 b8 |- P8 a    public void setUsername(String username) {
    5 T& p  F9 C  o$ }! U        this.username = username;0 u. u9 V9 S: N8 C
        }6 r9 W+ ]5 o6 s9 ]

    " j6 a% j/ t" @) J/ J4 s- {" M    public String getPassword() {1 b; N) Q0 T' d. z
            return password;8 |+ |6 v: G# E/ e
        }
    " P6 \0 E4 y: r/ t9 {8 L: E' i, U) J9 H: X
        public void setPassword(String password) {
    ' O$ G8 v- O( S+ H! w+ Y+ S        this.password = password;
    / g6 o5 x5 X& u    }; w( Z3 k& c2 s! a" L# O8 `+ q
    # [( \  F7 G: X5 ]2 L* e" `8 f: t
        @Override3 s2 g! o6 s2 x" w, d; a* @
        public String toString() {
    ( D( Q4 _- q0 ~9 _5 n        return "DbConfig{" +: i) n+ {! Y! B5 x+ f4 i! D) k6 j
                    "url='" + url + '\'' +
    4 G3 b0 p6 m& W1 X7 I1 p                ", username='" + username + '\'' +1 U, Q: V, r& H, u& h, F0 ]
                    ", password='" + password + '\'' +
    - _3 T3 @# P  d+ i% T                '}';
    7 W9 \3 W7 {+ \    }6 B" P+ q( C% J! E& G
    }
    # f6 a' I3 |" E7 b1 q/ l上面重点在于注解@Value注解,注意@Value注解中的1 J5 {* z1 d* _0 {( M3 a% \: E

    / H  G3 h; B  v! O  u来个测试用例1 J* B. K: V5 o0 u8 m6 \
    ! W9 ]/ D; k2 E& ^% w. Y9 ?
    package com.javacode2018.lesson002.demo18;: N  s3 B4 U6 G% j9 e9 {( \2 J
    ( _: S& P+ [8 ?. Q/ g
    import com.javacode2018.lesson002.demo18.test1.DbConfig;( v6 w8 X) P1 R3 o! U
    import com.javacode2018.lesson002.demo18.test1.MainConfig1;
    % N& t4 z; I1 d% y9 `  C+ x3 zimport org.junit.Test;
    7 j2 o/ c3 ?5 S/ \, c6 S0 Rimport org.springframework.context.annotation.AnnotationConfigApplicationContext;
    ) ?, F- L; S% [* A
    ' w7 Z1 k3 ~2 S6 {public class ValueTest {
    / M) ^! N" [- y  ]8 @8 U
    4 q9 S! Z. a+ E; D' c# T    @Test. j- x8 x! a$ H( `7 L5 d/ p5 h4 Z, A
        public void test1() {% ~' }0 _/ N- K1 n; |
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();7 l! F+ c; {8 ^+ @& b, V
            context.register(MainConfig1.class);
    4 g$ A/ Z5 ^. A! o; G1 l) o        context.refresh();: b8 V: P" t7 c% s0 l

    4 H+ s& X; q: i; L( x8 [        DbConfig dbConfig = context.getBean(DbConfig.class);5 \8 }$ u3 O4 R5 D" i
            System.out.println(dbConfig);
    ' a% b, C* F+ W* t2 ~  C. r    }4 s' O1 c1 m7 j, i( f  `# L
    }$ [) k3 M. r7 R; F* H
    运行输出2 [6 g4 R% r- a0 @$ m/ j
    - d- }3 d4 C  T) O5 w
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}$ u: N8 S  \# L# n( N* t% e
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。! W% k6 Q( r4 M0 m+ H

    2 A! ~. m& B$ _/ x0 O1 B, |1 \, P@Value数据来源- o) Z% u* s1 M7 w. d' q

    9 B$ o/ s  i3 D; _2 z通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    1 j* q2 i! I2 n4 K9 e7 U7 D2 Y6 e, p
    我们需要先了解一下@Value中数据来源于spring的什么地方。" o: m- O# ]) s

    4 {& A3 c3 W8 {4 J  [3 v! ~6 pspring中有个类
    ; u6 e' O4 i$ z- J3 Y* r
    : p$ W* r2 m# T5 z" c5 p# ^; Dorg.springframework.core.env.PropertySource
    4 A! q5 z7 f  O) U- k可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    0 L' C. z( N3 [( \7 o7 X" O- H* Q) y4 d9 i2 |
    内部有个方法:
    5 B) \& r* j/ s6 V& p% g7 i% a- v  A7 Z, g  @4 F: g
    public abstract Object getProperty(String name);
    7 L! S" ^+ U8 z5 G% |" E通过name获取对应的配置信息。
    - ]. U! z/ U5 F" a, J$ S8 a; Z' \, O- L( u; g8 {
    系统有个比较重要的接口
    & B# h7 Y2 s5 p7 r- @2 l# K+ ]' s3 d. b  e& u: n
    org.springframework.core.env.Environment
    & m7 _! N  n0 h6 V用来表示环境配置信息,这个接口有几个方法比较重要. t9 |$ @( o3 M* p+ v

    ) V, q1 t  u9 `1 y# q- LString resolvePlaceholders(String text);+ p2 |/ A4 k# S! l# E8 E
    MutablePropertySources getPropertySources();
      p. W; Z7 X( e, e+ gresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。  q4 B( Z4 V, c5 T+ r

    , s- ^. T1 r  T4 y( n4 U5 |getPropertySources返回MutablePropertySources对象,来看一下这个类
    8 e4 H  c) Q7 @1 M
    * {4 l6 N% X6 ]; wpublic class MutablePropertySources implements PropertySources {
    # _# L% F. D/ r) H, @" n' K4 R7 T3 i4 U+ v
        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();3 R0 d- W, u2 v' ?* n/ B% C" G6 ]
    $ C. G/ C8 [8 ~7 m8 b
    }
    ) X) V9 v7 q* w* R+ x" r$ M3 k内部包含一个propertySourceList列表。$ l8 m5 m/ L( c
    4 b$ W  W( T( q4 t
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    / W0 C% @, }9 F* Q( T! y4 @6 a
    ; A) L. O: L1 f: h$ l; d大家可以捋一下,最终解析@Value的过程:& o, t' S* W. V9 C1 [9 [0 u3 e
    2 B- K; t- c, A# l2 K& W/ L
    1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析- X* N3 n9 b$ r5 m( V+ M
    2. Environment内部会访问MutablePropertySources来解析# L- u9 x5 {  Y, S( b
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    % h( o6 N  @; o/ a7 g+ m通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
    ! G! j4 `' Z; A/ D2 {9 {, R5 ]' R8 b" L) z9 o
    下面我们就按照这个思路来一个。$ ^7 W( U; x0 A4 t4 ~( ~
    3 |. ^, v0 X- [1 |. L
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    * ?# x" X) N; f4 D  i
    ! _' ~5 \; t% }. X, jpackage com.javacode2018.lesson002.demo18.test2;
    3 o* _( @, ]) R' h0 G1 l
    5 N: Z; t! h* Z4 X7 r8 \+ r0 @7 cimport org.springframework.beans.factory.annotation.Value;
    & G% x% |5 Q. g! Q( {" vimport org.springframework.stereotype.Component;4 I* t, n  _$ Z0 |! f1 q

    9 E( D5 s2 S$ b5 ~" r/**8 o3 q* g% ?+ F$ c% n% W& s5 ~
    * 邮件配置信息8 m9 E) R& ^. W8 L+ N
    */
    : |' t& d( z, ^! k  r) E4 H8 S4 b6 H@Component! w( I, c/ L$ ?1 }4 _
    public class MailConfig {/ S# e1 g# b9 I: ^

    0 x, d7 X7 x  w1 y" `    @Value("${mail.host}")
    # q% u, i, l9 V- S    private String host;
    6 y, e* ~* ]9 i! A
      ^( o; x9 g. ?% I' o    @Value("${mail.username}")
    8 K4 K3 F- g: a' J" B' O    private String username;
    $ B; w- t1 y# X) N
    9 k" @$ N/ Q1 x. K( S    @Value("${mail.password}")- T! E0 }& o7 a# a
        private String password;
    & |6 c2 N- h/ I/ E. p1 r
    9 ~2 G* j5 G$ A* H: x    public String getHost() {
    * |, ]1 E5 |8 \" A        return host;
    1 U* S; c1 [9 ^7 G5 Y/ X    }4 ]! o6 V5 O6 B8 ~0 P2 Y$ o+ v

    4 o$ F! `# E1 s4 _; n& g4 p    public void setHost(String host) {
    ; F$ A3 z7 @' Q, p8 z! u        this.host = host;- w, R$ p" V8 B9 {# p
        }  U0 ^2 D. l* k
    6 T% e7 i9 E$ U- d8 ^! c! t% m6 P/ j
        public String getUsername() {
    8 ~7 R4 ]5 f7 ~% C; {# Z        return username;# z' j7 h0 y$ _( y2 k+ u
        }! Z5 `+ |& Y& \

    : d- W9 d/ K/ ]0 [; I3 U: y    public void setUsername(String username) {2 O4 G; @+ o7 c. S% i3 B' T
            this.username = username;* x2 H9 m7 m$ U: `" [& n
        }3 r8 c4 Y& s, E4 g

    3 j8 I1 K$ k+ N    public String getPassword() {" p' m5 y: R, Y# Z' p! m! D
            return password;/ k) z3 n# i* c* t+ m. k- \! h
        }9 D" S5 F) X+ O* e9 O
    ) N+ _: g) W6 T: G0 E1 k
        public void setPassword(String password) {
    ( c* d; G0 ]5 {8 e6 c3 c, t' A- M        this.password = password;
    1 Q- A8 s+ i) `/ M$ H    }
    7 L1 `1 K# S( ?+ D: g2 w5 M
    5 ?6 z- J$ k3 p5 J3 e    @Override% w5 A3 t1 F# D8 ?9 O! j1 U' B
        public String toString() {- a1 p% z% y. l( o( `1 {/ E  Q/ _
            return "MailConfig{" +, n( W0 U! H, f6 a+ ]  {+ m5 ^
                    "host='" + host + '\'' +  {: f* Q9 Z+ M7 e6 f8 U. o
                    ", username='" + username + '\'' +3 g9 d0 v( Y0 p4 I7 [' X
                    ", password='" + password + '\'' +
    ; _# M7 I- f- \4 \9 E                '}';- d) O" `( f; N$ _- _
        }
    " w0 S( K: v0 \0 ?/ t5 S}
    : [3 [. Y2 {* C- ]. q+ f; \( t再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    % \& `2 F- q; R+ x1 `( S& K! L, \2 ^( Y8 z
    package com.javacode2018.lesson002.demo18.test2;
    7 p4 [) Q2 k5 Q5 ~+ s" M" p" ~/ _0 o, g8 Z' w; W6 }9 B0 [
    import java.util.HashMap;
    ) }! t( Z% s/ Y% b, H/ ?, t9 G/ wimport java.util.Map;) p/ i: L) P0 M/ S8 w9 H0 p8 R  z

    ' Z, Q0 d: T" b$ I' ?public class DbUtil {/ F" |# _; u) d1 n5 a% X. t& `
        /**
    ; X5 E2 w$ }8 r! s     * 模拟从db中获取邮件配置信息6 B( S* x9 n  g# f$ J. n/ {
         *# n: a  Z* u% Z: i
         * @return+ J, y$ ~. i# P# j
         */7 X7 o2 [9 J' {
        public static Map<String, Object> getMailInfoFromDb() {
    , V5 t0 u' H" j        Map<String, Object> result = new HashMap<>();$ P7 o/ Z1 G! b) j
            result.put("mail.host", "smtp.qq.com");. y8 T# P& l+ q2 O: `8 |& C+ z
            result.put("mail.username", "路人");$ G3 C# V, U+ c% R, }- ?
            result.put("mail.password", "123");
    / R8 [* C$ H& t0 f' m0 Z* Q        return result;
    3 @8 V5 b4 K: b    }, k5 f# Y) S4 s" l8 f9 g3 F
    }
    1 o. W2 ?% W1 ^来个spring配置类( `# t/ g3 Q) [. N; W9 b# J
    7 |0 A' N3 I' q; z3 M2 ?
    package com.javacode2018.lesson002.demo18.test2;( {7 h9 ^- m0 g/ `6 q9 q
    4 Q2 P, `' H" G# f
    import org.springframework.context.annotation.ComponentScan;
    ! n+ L9 Q: |% c) E# L1 Q0 F/ Ximport org.springframework.context.annotation.Configuration;
    ! b  }% @% V! m
    0 y+ D( W3 n0 ~& [, s@Configuration
    " l6 X* ?% e$ {7 e# E; \1 v@ComponentScan7 |* o) V. m3 \1 q8 }: f
    public class MainConfig2 {
    0 ]# `; Y* a' ]) V0 S6 g* \* {}: h! H: C* R% {0 @
    下面是重点代码
    & ?( b. k% I" w8 R/ d; l: v) C# W$ M0 U9 E
    @Test+ |' ^9 D/ C5 g
    public void test2() {
    . x% s$ d4 [# m, E    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    1 ~/ N; m0 i9 D, D/ ?& i5 p5 d
        /*下面这段是关键 start*/
    & r0 o6 ^; a# s( e* U- v    //模拟从db中获取配置信息6 M8 m# l, c  S/ i7 ~) s
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();% E9 O5 V/ r! ~& v
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& ]' J2 V; M4 q$ N# k' D
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    6 m7 M4 y; S" _( _& E    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高6 g. x3 b( e6 ?2 ]8 q! |
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    4 w$ L4 j4 X# O' \# b8 y: k/ u    /*上面这段是关键 end*/
    # M, H3 n5 a6 W$ O1 }% n2 d1 Q6 J6 a( T, Y' x2 S: a0 S2 w+ f& D
        context.register(MainConfig2.class);
    ) r! k  N) a' ]' w  e1 O& S    context.refresh();( s2 L5 X, U/ N- U% O, d
        MailConfig mailConfig = context.getBean(MailConfig.class);0 |' n! h: _6 q7 ?
        System.out.println(mailConfig);% [0 ]9 E' O( N+ X9 N* T& B7 p
    }7 u, F" ^0 {2 N! L2 a3 J. N
    注释比较详细,就不详细解释了。
    & G' E- y' H" b: R) _+ @
    $ ~+ g6 y3 p4 S$ d' Z直接运行,看效果
    9 T% \5 h% U( }8 s- J
    8 N! _! \7 A* r2 ^2 `% gMailConfig{host='smtp.qq.com', username='路人', password='123'}
    6 R7 X9 c+ T; j$ z7 F有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。( Y4 `! M% D3 U% d% P* C

    ; V& G( F* b7 ]) K上面重点是下面这段代码,大家需要理解
    ' W  \. m0 |8 V9 |5 t8 d7 }
    - Z/ S) @0 V! A  K- M* @( c* T/*下面这段是关键 start*/
    & p- N& T, X4 H1 y8 W! A, c- V//模拟从db中获取配置信息
    ) B; o; D1 X2 g! G0 ?/ UMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
      T6 i# }5 l7 ]: p- F//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)6 h, B& _; q) U9 \4 F
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    + l( m' A- T0 W0 k' J//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    6 P8 S# f' m) w% X5 v6 @context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    3 _5 P4 m) o6 r/ W* ]/*上面这段是关键 end*/- |6 s) H4 m+ V1 y1 [
    咱们继续看下一个问题6 K7 N8 _4 d; ~1 u6 M' A

    ' K( m2 ]0 c. i+ x% {) d0 \& u如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。4 D+ r  m  e' `4 Y( Q8 M, B& U
    0 X: C6 \9 L1 m. Q; M4 k
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。& B, ^7 T5 Q# ?" N6 Y2 Y: x

    ) z0 J" g8 D7 L9 L1 ?2 c/ i% |4 L" z实现@Value动态刷新
    & k6 |, Q( _$ o& p
    & P6 m) Q7 B' A+ l! Z! d先了解一个知识点8 s8 V& L/ m. @9 ~9 S

    6 v# [; m0 c8 s9 y* I6 g- t; {' k这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。
    ) S* a2 E3 B3 I+ w1 n
    4 S7 V& E2 I1 H6 a这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解2 C* Q! V- j4 P9 y

    : H9 d* h. R: H1 f  E1 vbean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:) S. [3 H! S, n$ @8 J

    2 `8 A: D$ C' r, G2 y+ ~! O  iScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    9 ?! _3 i+ @+ w9 O# I  J这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    - k& ]  t5 C1 ^6 d* o- ]  U4 Y9 {4 F& e$ h2 U, v: M
    public enum ScopedProxyMode {9 P7 \6 b" R) Q$ I( M; I6 X
        DEFAULT,# S' g: f. h# g; R4 t- E) @9 X( W& c6 I
        NO,
    6 g! l/ Z7 M- u    INTERFACES,2 ]/ D" a, O3 z. c/ q& I4 }5 W! Z
        TARGET_CLASS;/ a! P9 T( h& B
    }1 t- _" o: @% K- o  i6 m
    前面3个,不讲了,直接讲最后一个值是干什么的。
    1 d9 P* o4 r) r6 U7 _. @. i
    6 k, Y3 U' O3 g+ b- [当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。7 D9 \9 q/ v: D% o

    : X8 q# g) i3 r, u5 c理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    . l; ?% R; f; Y; ?1 Z- X, g5 t. @5 B6 O4 x% E, o
    自定义一个bean作用域的注解
    * R( ?/ q: r$ n; s3 N# k
    4 r/ C* [$ F) p: \1 O/ Npackage com.javacode2018.lesson002.demo18.test3;
    5 Q: y) {+ f  }; ]2 O1 y& G9 e0 N- ^% A' V) d
    import org.springframework.context.annotation.Scope;2 I1 g; [  o5 B/ ^
    import org.springframework.context.annotation.ScopedProxyMode;
    & L  `) C. u) Z/ a. l, z2 h7 X) z- K! e& f$ m' E
    import java.lang.annotation.*;5 K/ w( U7 |& O" J+ j0 V

    ) \- S; @0 M4 Y+ a0 }3 b0 b@Target({ElementType.TYPE, ElementType.METHOD}): l# I5 X: y! ]3 G. v4 I
    @Retention(RetentionPolicy.RUNTIME)7 D$ \- d; |- |# X" U. M9 U6 `
    @Documented
    2 n0 L8 H/ w3 O3 T6 B, N  T9 x@Scope(BeanMyScope.SCOPE_MY) //@15 v* l0 q2 q$ \( K( \# n( q. l
    public @interface MyScope {
    3 T) g! ^- p6 q  h/ \    /**
    ( v$ s, M; h. g* w2 R: A1 B     * @see Scope#proxyMode()
    # k. c. k; X: z- a9 `' p     */
    - J* E* s/ B" T( Q) `    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    5 m+ J$ a6 w/ q1 C5 E}. X3 m1 v9 @9 _0 W  w. a& B3 x3 X* _
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。+ W  C1 i2 X% z" {1 j/ j6 q: x! B

    7 g1 S& u# E2 f( W: [@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ' d5 L$ G& I& g. Q" w
    ) J) R6 J* w8 Q( M! ?" }@MyScope注解对应的Scope实现如下
    2 ^4 `; v( m) ^" Y
    0 U+ O' D+ I8 I0 p+ b" Cpackage com.javacode2018.lesson002.demo18.test3;
    5 i9 ?$ F/ r% n3 X' I$ `7 I7 N6 I6 U0 x8 a4 u' X/ `' ]
    import org.springframework.beans.factory.ObjectFactory;
    4 B$ A8 \1 K6 p0 E; w+ Mimport org.springframework.beans.factory.config.Scope;
      {9 Y2 z( m3 p% i7 iimport org.springframework.lang.Nullable;+ j* |% g+ J# k  [: V; W
    ' C4 \8 i1 ~' \+ p, P: T
    /**
    8 i3 O$ T4 ^) ~( U * @see MyScope 作用域的实现+ I7 W# @! l) {" C- U' M
    */
    / ^. h& v! p* G8 A; s/ ?) _public class BeanMyScope implements Scope {* C. c# Z1 \1 C* i# E

    4 i; D! p; V. h% D5 _) }" {2 X    public static final String SCOPE_MY = "my"; //@1" ~6 A$ t9 e1 |* d
    & u, ^+ [" C( R2 K1 l7 y
        @Override
    " c, T& ?" P% t    public Object get(String name, ObjectFactory<?> objectFactory) { ! L0 a% h# N! L" ^% p; h2 C1 B
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@29 A6 X. K% k% Z, h. @* m% w: L! |
            return objectFactory.getObject(); //@3
      h3 y9 Z2 m$ Z- L6 s    }
    + @9 l! B3 [- n2 P
    9 h( T7 U7 @* i4 ~; [    @Nullable% y$ r& [6 J6 I& d: z# }
        @Override
    & X6 |1 E) ^0 W# \0 ^7 E    public Object remove(String name) {: X7 C# ?" x; P9 }6 {5 ~
            return null;
    ! X8 v) N# D* ^& p& C) \/ A; O    }) ^. G" P; k# L$ D* s$ }
    ( r+ t6 X2 o) c1 p; l$ H- L! h
        @Override
    ! M6 H' q4 h/ K6 Z3 v) @    public void registerDestructionCallback(String name, Runnable callback) {# Q( a6 v7 }# w. t$ E, l! ^& z- t

    * C/ o: W6 m. v$ j& q# W    }: y2 h. C8 v6 @% o
    % {# x9 U+ J( T
        @Nullable3 _' t$ A( k& f6 i( j! P. B6 [
        @Override; n1 K3 L; o, O. w5 E. L
        public Object resolveContextualObject(String key) {1 V& h  |0 h8 h: {, @2 `
            return null;7 y% Z% ?- ?( ^" V9 i# G7 L
        }* ]5 v" x9 ~& U! D6 ~4 L9 B+ F9 z; K
    - d2 V. A& C2 T
        @Nullable- o( R$ R  F* Y5 S
        @Override
    " w7 E9 i2 N8 G" h; T    public String getConversationId() {( P! v' t9 N' T3 R
            return null;
    0 b0 q2 I' x2 d8 ~, m    }
    7 u' s+ P7 q7 n$ u/ q6 [}
    ' e  t' V3 i; i% F" ^@1:定义了一个常量,作为作用域的值
    ) m: m  y: ]5 t9 O; l1 t. ^( o3 h$ Q& `% w- y4 |% e6 E
    @2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    1 p: N; y) B4 \  a
    2 e! W+ o$ K  ~, I@3:通过objectFactory.getObject()获取bean实例返回。
    ! t7 Q1 i( `* A5 i- P% N2 f) a
    下面来创建个类,作用域为上面自定义的作用域& ~7 @) f* [2 y4 G
    / \1 p( [# T: v4 c8 ^
    package com.javacode2018.lesson002.demo18.test3;' o9 H  O* ?' j* p$ h9 |4 C
    7 _& m! M2 g; M- @% J$ O
    import org.springframework.stereotype.Component;# _9 N0 g- A  @% H' b; m
    : J# }* R$ f( N! D- Q9 u0 c, B
    import java.util.UUID;8 D: i% a( r: {- _8 q4 L/ m

    - Y5 }6 V" m( q@Component1 B3 q2 |: ~/ }# c. s# m$ b
    @MyScope //@1 6 a# ]/ W" {9 u: _
    public class User {1 j% Q! P; j- Z2 N. W
    1 S$ s5 l' K% U" A% R8 z) I
        private String username;
    8 q9 y. I, h  H6 W# X4 i, T: M9 P; c: \4 U" i1 I6 y0 z5 g
        public User() {
    # H* _# V2 ^! A3 a/ v$ m0 P: N        System.out.println("---------创建User对象" + this); //@2
    1 M) N  G. ^4 y7 s        this.username = UUID.randomUUID().toString(); //@3
    , u$ h* m7 _0 Y6 m( |    }
    * u7 o3 ^& j3 I) {, Y& D$ C
    6 b. i5 |! \# b, Z/ y& D! a    public String getUsername() {
    $ u; l% `+ j. E2 q/ _& L* t        return username;' ^5 K5 u- h6 V
        }
    ; J5 v$ M" ~. |0 @1 X5 w/ c$ b; |* Z
        public void setUsername(String username) {1 f0 b' V2 @! v1 y8 d) h! V
            this.username = username;6 m2 M' j# O* |' y( O7 g/ q/ N
        }" l2 }! F+ @1 {9 o5 p: j; h% P4 t/ x

    - ~$ d' Q2 b& q, N- B}6 g8 Y1 o& A; O5 o9 \) D# t9 {
    @1:使用了自定义的作用域@MyScope; B2 y  ~, g# X  V- j8 S

    % p( ]) T! E" E! A4 _8 a! }# u@2:构造函数中输出一行日志9 H: K# Q+ v# u! J# k$ x( E! S, N; y

    ( d& {( }2 J! _* N@3:给username赋值,通过uuid随机生成了一个
      _6 z+ l6 c/ e8 c$ o
    2 ?/ P, `' V! Z/ d) C  b来个spring配置类,加载上面@Compontent标注的组件
    7 K  M/ g. F$ I$ U# b) Y0 j- E3 k, F, F! e/ M, }  W, W
    package com.javacode2018.lesson002.demo18.test3;; W5 Z0 R* o) B9 d" O

    1 h7 \( b/ m0 Z+ e0 ?import org.springframework.context.annotation.ComponentScan;
    , ~: F9 ]4 e6 m$ oimport org.springframework.context.annotation.Configuration;+ V: M0 d7 m2 ^( f% U& d9 P: ]/ M& D. a

    ! L- K* V) Z* }@ComponentScan
    2 t1 J; W! e' u@Configuration" m$ `1 M+ L! ^. G- R) h( d! q
    public class MainConfig3 {
    . K$ L5 ?6 R4 s5 _}
    / l5 P/ L. m) ?( S% d# f下面重点来了,测试用例
    ( U: ?0 C. I0 k8 ?$ X
    4 G7 i! y& I9 v) b" i" q@Test1 |* B" q) X1 a
    public void test3() throws InterruptedException {- B1 O* p! L% S8 Y/ e* U) v
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ' B2 M* }2 l1 x) X1 k+ J- l    //将自定义作用域注册到spring容器中0 d  d( M- E7 G) v9 {7 k3 ~6 A# W
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    ' U- Q9 h9 I3 P% R! c% b; D    context.register(MainConfig3.class);& w) A+ Q4 D8 o5 Z1 T7 ~) U" ~
        context.refresh();
    ! s# m% f- q+ F3 ^/ s
    1 C7 u4 f' i, B! D8 [: J1 s" p    System.out.println("从容器中获取User对象");
    . i% ?  P/ Q, ^0 _: }    User user = context.getBean(User.class); //@2
    % }- s# m" Y) f0 n    System.out.println("user对象的class为:" + user.getClass()); //@3  ?+ @9 e0 }  t  t, g
    * i0 P) K( e- A
        System.out.println("多次调用user的getUsername感受一下效果\n");
    0 Y" X$ M3 N7 F: Y, V    for (int i = 1; i <= 3; i++) {! a: _3 l7 e% L3 Z
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    / _$ T5 X8 E8 u  o        System.out.println(user.getUsername());# n7 T% W0 ?! Q1 z% X% K
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    5 Q; t9 I( T2 C' Q/ s    }. X8 E9 S* ]" j0 M
    }
    , w5 W& P  w% i: F' T@1:将自定义作用域注册到spring容器中' o9 Q2 N5 u3 t- Y

    0 g: ~" e7 X- x  B8 w@2:从容器中获取User对应的bean$ C: P* W5 c6 R+ N0 B) Z+ I

    * U: u- q+ z5 ]4 x, U@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的" p  d8 v! H' X+ @# e4 B6 C
    7 B; q, l+ \5 H' F7 x# Y6 }7 R
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    8 @) [3 O/ r- [$ z; q/ R7 i6 m; A! o; I" u# L- n
    见证奇迹的时候到了,运行输出0 p( z# _# V3 L" u4 S5 X- |6 K- `
    6 D4 s2 B* I4 X/ F! m5 T' j" N& k) _
    从容器中获取User对象, ^/ H9 e( ]: k
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
    ! F9 A7 k: E  ~6 ^! i  I" D- k多次调用user的getUsername感受一下效果' b5 u  I/ A1 v& O5 z' |$ {
    ) N+ |. _. ~( E3 b' W- g% w
    ********
    $ {% h" N) e: x5 a0 X第1次开始调用getUsername
    6 P, |) [9 p9 v) HBeanMyScope >>>>>>>>> get:scopedTarget.user9 U& O; _& ^) @
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    # |) R8 P3 v" H( h- n7b41aa80-7569-4072-9d40-ec9bfb92f4389 ^; k0 Y" b  _$ h; z- P0 g- }) p
    第1次调用getUsername结束/ _8 d  B2 b0 }1 A& k" A5 t
    ********- h0 \+ K% M# v( Y0 U

    5 x; H! L8 H# O0 M3 V' T********
      D  A# X6 @* A  u: b第2次开始调用getUsername
    + b) M- ]% F( h5 b: X. ?3 B! ~BeanMyScope >>>>>>>>> get:scopedTarget.user& B- ]) C3 B: r6 R! \/ t$ l# @
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    ( C5 A3 S, G  ?01d67154-95f6-44bb-93ab-05a34abdf51f
    / h: N5 F8 M0 ^  c# ?! y4 N第2次调用getUsername结束
    4 H4 p" y& l  l% e& e********
    7 x/ c2 g3 U* ?7 r- t+ w4 Y, B9 e8 a  U
    ********8 r$ j% g. Z( {+ P: n5 B& A% H
    第3次开始调用getUsername
    ; S1 o0 Q$ P+ Z* c$ aBeanMyScope >>>>>>>>> get:scopedTarget.user
    4 m2 _% ~1 }& \; |7 A; ]9 r---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
    ) G2 h, s7 T  o5 k# o76d0e86f-8331-4303-aac7-4acce0b258b8* C+ W! H7 Z' m# |% h; [
    第3次调用getUsername结束7 j( z8 `% Z% P7 h! x( q
    ********# e1 h# u& }$ W
    从输出的前2行可以看出:
    0 Z4 F+ O4 E, ]5 F( [2 G* G" j8 f+ F4 G
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象7 L. e0 \- t% j# L% T

    ; Z4 p( n& ~2 w- `3 B4 `第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    8 U' y8 z7 X3 _3 r5 w+ ~
    ( m0 A* p) O2 [后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    ; Z' ^2 o- h3 z  |$ Z9 e+ e0 I* J4 s( K8 B# H* |+ c
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。( y: A7 B& |, o- x

    7 p4 W. }' i4 t# ~动态刷新@Value具体实现7 Y! `, `* Z3 }& x

    * R6 f$ Q0 p' p; O+ M! P' y' t5 `那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    / ]7 D* @: r& h
    2 z9 @. b+ \1 R' b7 w先来自定义一个Scope:RefreshScope3 k! j# a! V. C. f- g

    4 z& r- J, F2 R0 [package com.javacode2018.lesson002.demo18.test4;
    . s' E: {; c' o' @7 u( G/ q3 ]) U- Q" i5 S& Z9 l
    import org.springframework.context.annotation.Scope;( F  O# \& F! V" D) f2 C
    import org.springframework.context.annotation.ScopedProxyMode;  P" J; X3 W0 n$ E) W: p" O
    ! l% @4 Y3 u4 R. k7 s1 l
    import java.lang.annotation.*;+ O9 z9 f3 P( U' y; k

    . }, S& V, x! g- ]" A@Target({ElementType.TYPE, ElementType.METHOD})
    4 P" n2 I3 c1 m/ p0 h& x@Retention(RetentionPolicy.RUNTIME)  ?0 d& G- m6 J+ x: H0 r" G
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    2 N9 j8 V" Y1 _@Documented
    4 q5 l8 `) x: @; q* npublic @interface RefreshScope {! |6 c( c" u9 v7 A0 D% w
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1" K3 S# W# q! ^% S" ^% s+ T+ ^
    }' I- }6 E* [- S
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    / S" v8 }5 ?8 y
    ; j( z! |' g& v@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    $ E" \/ E) u) k* T' `1 N( S4 @2 u9 B0 I
    这个自定义Scope对应的解析类
    / [5 E2 E! I* }0 T( j8 T- r7 H: w/ c, {- v2 `! g
    下面类中有几个无关的方法去掉了,可以忽略% C  V, ]' K2 ^* @+ g" E: H2 c
    8 {, q7 \5 x& X/ c* X
    package com.javacode2018.lesson002.demo18.test4;
    # l0 }! O, E1 ~  P
    ) H' `- u, I5 ~
    1 m9 i8 O7 c. t% b, e) S8 J# timport org.springframework.beans.factory.ObjectFactory;
    8 I3 {- G, s: C# Aimport org.springframework.beans.factory.config.Scope;, _  X" g% M, q) i9 L( ^) c
    import org.springframework.lang.Nullable;
    ; x. g, b; }0 ?  A, [1 s8 c- o2 o3 c; I9 k
    import java.util.concurrent.ConcurrentHashMap;
    " \$ f4 [' m, d7 c! }1 j- \
    " s. h6 ]  M, K( f3 @, Dpublic class BeanRefreshScope implements Scope {/ n, I: R3 Y! d' z5 G! C0 I. U
    ' D# ?- ]6 H+ m$ N; U
        public static final String SCOPE_REFRESH = "refresh";
    . l0 T% r1 Z/ z
      U4 d1 Q% v" J% E9 T8 \    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();, y- f: j8 k) O2 @9 w# Q

    , \  U- T" j- I! s! K: y    //来个map用来缓存bean
    4 z# o- x3 z/ G3 k4 Q7 G    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1- t( J6 j  P. O; }6 }$ l

    $ I/ L! G: L8 H+ n: X/ b    private BeanRefreshScope() {
    4 p$ o5 S/ ], t  l! S& n) ]: A1 }    }
    $ S* v& ^! G* p; k( _. ~, s8 h# P, u% v( D
        public static BeanRefreshScope getInstance() {" P. T) Y( D, |: T, r" ^# g7 `2 ]: [
            return INSTANCE;7 W$ b0 F5 F- g9 u
        }! ?/ ?4 Z- k( l& F; v* ?3 f

      i# ^8 _4 Y0 K2 |* J! r: Q3 b    /**- e% N: H3 t1 `# ^) Q. E
         * 清理当前9 ^- T$ f3 B+ E2 B
         */
    - I5 G5 t# J+ J: A0 F9 D    public static void clean() {
    4 V7 o  G9 `  _& ~, `5 U        INSTANCE.beanMap.clear();
    + e# w8 H, ~: a    }
    0 K7 o% e" o7 S; N5 K, E
    1 B6 b& V0 z0 g7 t, |: R  c% l    @Override
    8 P8 N$ c. C1 G# \' K    public Object get(String name, ObjectFactory<?> objectFactory) {
    7 ]; r3 {0 s3 z5 r+ y$ |- F$ j        Object bean = beanMap.get(name);
    ! Z( q1 O7 a0 `" l0 t0 b. x( k        if (bean == null) {
    * L7 N) k6 B2 y            bean = objectFactory.getObject();
    & A, Y% r( k7 I3 c            beanMap.put(name, bean);5 s+ S- @; I+ {( x" n
            }( Q+ C  q: K, N" ]7 k  Q/ F" H+ x6 m
            return bean;, P3 h% p" L  _* ^  V
        }
    + j4 n6 V  m0 X- ?$ u: Y
    # q, ^$ o, M; T* a}
    0 v. P3 ~! j/ k/ E8 h上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中1 ]8 ^  l$ @8 N$ D$ C9 ^' f+ f
    : \+ Q6 F1 Z- M
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    ! `" o' `+ t/ w+ G
    , N$ [: |3 |" A6 Z来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope; Q3 j8 D' P( _/ ^

    - C7 Q# c  U* |6 O2 N5 ?& J2 E/ Xpackage com.javacode2018.lesson002.demo18.test4;
    - b3 j* l/ v; r$ A/ {
    # ~- O. {. X' i& v' D: @: D1 M/ H) B7 Qimport org.springframework.beans.factory.annotation.Value;, Z2 M7 \$ K: d5 |8 m; s0 E. B
    import org.springframework.stereotype.Component;& J/ f7 e5 W) Q7 p! _/ O- x

    2 L( E# G6 p* j* \2 _  U/**% ^! _; g7 O/ t9 F4 P, E
    * 邮件配置信息
    7 ]9 T. V9 h2 p, A. m# @ */" D, z" D4 G3 ]  j1 e
    @Component
    ; u  I# d1 G$ |  z@RefreshScope //@13 M( o5 \" ?3 {
    public class MailConfig {+ l1 l8 a, l( _+ X: x, F
    , p) L  ?/ w; N* P2 N
        @Value("${mail.username}") //@23 G, s, O6 M6 h$ Q, p
        private String username;
    5 u$ ~1 N3 j4 _$ s" K% s' M: Z+ b. {$ b. c3 y) Y# \8 H1 F
        public String getUsername() {  f+ m& L: X5 Q: c
            return username;
    0 s- `$ j; {* S- k& ]( Q    }; f- \) i  ]  d/ p+ s) K% O# |1 C  z

    $ R( M: v1 M; {4 [4 b7 r    public void setUsername(String username) {
    2 `5 P# I' ?+ V        this.username = username;* b  A! Y: T2 [3 W
        }
    + \0 A/ ~% S( J' f3 M
    0 D$ c; W6 g  P/ i3 }    @Override
    & T" d& O6 i* L$ G+ `' p    public String toString() {
    3 l( }, Y* l4 v) O! [% d1 z        return "MailConfig{" +8 B& o- D  q' ~$ V8 p% G. w
                    "username='" + username + '\'' +; ^( l$ L3 N0 s: w+ C+ M: a
                    '}';
    - Y6 W: g  v! `! v- N+ _    }
    * p$ ], S" u  [1 T0 }}  x4 V& I# X, S. Q
    @1:使用了自定义的作用域@RefreshScope! U9 d4 T4 o6 n% u: M& f* `0 m6 t

    , O6 z4 G) F3 ~' U, k9 F( N# p@2:通过@Value注入mail.username对一个的值) o' G/ g2 F  U8 A
    5 h% P$ T- A( [& d1 \  W9 s7 P2 k2 s
    重写了toString方法,一会测试时候可以看效果。( O) r+ n4 j" P8 y9 R2 v6 p8 _
    * c. t2 `& y2 a! b) w$ m
    再来个普通的bean,内部会注入MailConfig
    7 d, N+ H- \8 b# P4 R* h. K0 c1 g) M6 {
    package com.javacode2018.lesson002.demo18.test4;
    ( z8 B6 L# X( n& \- V& k, @7 S5 F
    ) I( w; ?. }' g/ m; ximport org.springframework.beans.factory.annotation.Autowired;+ ?  Z' k/ Y: o' x
    import org.springframework.stereotype.Component;" b3 q4 Y$ v9 u/ s. o
    / l/ q" ]2 W  o% t# ?+ U
    @Component
    * _8 P! ]4 a& F- L7 t! a; Gpublic class MailService {
    5 W) G/ D9 Z( b  A9 T; A* k- u    @Autowired* ~5 P6 N: Q4 @' {: {
        private MailConfig mailConfig;. n% `+ ], i' V% d5 c4 y6 A; @; H2 u
    2 ], ?2 M  g$ A& b
        @Override
    & f9 ]: j4 l! w1 o$ E    public String toString() {7 m, i1 C+ g* |, k, i
            return "MailService{" +
    : r& e8 w9 F! F                "mailConfig=" + mailConfig +
    0 T2 F5 V" g+ k, W- s4 s/ j                '}';
    % ~- z1 S! x6 ~  D' W/ C" ~    }
    " s3 e& p6 j/ I}* }* d& v- R, v' d% I4 W: T5 \) Z
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    8 `& R3 ^) n! t
    ! V' q6 |& m3 t1 S! N来个类,用来从db中获取邮件配置信息; X# Z& i& i" |5 f

    9 @( I* M: @* |  v* r0 zpackage com.javacode2018.lesson002.demo18.test4;
    # R1 m7 Q' D& F8 I; J  N! ?$ d7 v+ h& h" z
    import java.util.HashMap;
    9 o0 Q: }! _4 q4 Y: T5 }import java.util.Map;: |! _. I7 G. O$ j9 u2 W" y5 n+ t
    import java.util.UUID;
    ' n- ?7 U) N+ x% u8 K
    0 Y$ r7 \' p6 Z, lpublic class DbUtil {2 T) f  ^0 W3 _! F
        /**
    * \7 {& i0 E# k  b: s7 a: l/ R     * 模拟从db中获取邮件配置信息, [7 W: S9 T& e, [; h0 M' ?7 }
         *( U1 R7 U+ e$ U6 R; B
         * @return/ L& |4 b0 [6 P6 W0 w% g
         */
    3 }+ r: {0 W! }4 A" ?) k) q    public static Map<String, Object> getMailInfoFromDb() {
    ! A3 _1 s* d6 N        Map<String, Object> result = new HashMap<>();
    " Z4 z( S/ a7 Q8 `& p* g        result.put("mail.username", UUID.randomUUID().toString());
    , g4 p3 y9 U: i! O  d$ @        return result;0 v2 o. R7 Q2 ]+ L
        }
    , V  |# `2 H- C+ v2 e& a}
    " ^  k8 U; L8 x来个spring配置类,扫描加载上面的组件
    , ^! H- R1 a. F/ p: w! a# X! e) c+ K% n- j
    package com.javacode2018.lesson002.demo18.test4;
    ( J' B0 B  z& R; C7 U% v) J+ a5 Z$ @7 L8 a  r
    import org.springframework.context.annotation.ComponentScan;
    # x9 R& W2 I+ U( u1 Q4 k0 Dimport org.springframework.context.annotation.Configuration;5 A4 |8 X5 u$ X. J( W

    8 X' `. K! y* C: H4 W" t@Configuration
    , x9 q6 ^2 J, k1 q" Z* |@ComponentScan
    1 `# b+ W9 T+ b4 H# Y* Y: h7 R5 `public class MainConfig4 {/ v$ j  k! P) E& K/ @
    }3 H8 H  w/ C7 S. U- |% t5 e, U
    来个工具类
    ' i$ J" ?. p( |' \( V" D' y' ?
    5 n4 h; P( p' S, r: n. ]4 B6 @内部有2个方法,如下:+ G1 `/ h3 O" \/ W  _: j3 L  ^+ [

    / Z/ q. c2 e3 }( _5 F. Hpackage com.javacode2018.lesson002.demo18.test4;6 N) |+ C' ?5 }; t" U9 @6 Q

    . |3 [( F, g8 uimport org.springframework.context.support.AbstractApplicationContext;
    + Q! o! i4 @2 }7 L" N6 q) L, Himport org.springframework.core.env.MapPropertySource;2 Q( i7 L/ E0 f8 L) r( h# u( |& ?

    / F% E7 E" c% E2 P0 }2 R' bimport java.util.Map;
    % ~7 `* j8 l& J2 z: `0 g$ Y+ y' r; F4 ?& m8 f
    public class RefreshConfigUtil {
    ! P5 z& V# t3 T% V( j/ }: O& N    /**
    # Y, X  h2 O# `! e+ n9 H; w     * 模拟改变数据库中都配置信息& K  x1 w' b, q8 b/ D2 E
         */8 C2 p& ]* r& d1 K+ p7 Y5 q
        public static void updateDbConfig(AbstractApplicationContext context) {
      B6 r$ {) c, i6 q        //更新context中的mailPropertySource配置信息- @0 [( w/ p5 O1 J
            refreshMailPropertySource(context);
    . R9 h4 P/ |* r, E: c
    2 p% @0 V' d% m" B2 ?7 m) Z        //清空BeanRefreshScope中所有bean的缓存
    & g, a/ g+ _4 P! F4 ^        BeanRefreshScope.getInstance().clean();
      |4 \9 a8 ^- R* S, d: c) Q    }
    3 O& U" j1 U6 `" s! G; T# P5 w; d& W+ k! D8 H. X# y2 C& e0 A
        public static void refreshMailPropertySource(AbstractApplicationContext context) {
    + h) R$ p" Q2 h3 O& i# O# y0 N        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    3 I+ i, M5 A( Y0 S        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类): I. ^+ l3 y" d: o0 l
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    0 |$ ]' e$ m8 U# L        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    & @' C+ t. z) H8 R% |5 z0 \    }' d1 d' U" u$ d7 Y

    9 k! h. U& ?3 S  ^}
    7 K, |# u; B# Y, s" {( m# t7 \  t6 |updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息: \% S# c( E# q. D+ U$ q- X, x

    / r$ N1 c+ P  V$ L) eBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。) N$ |: ], N( v- i3 f, E8 \; z
    + L8 c3 ~$ w3 }3 I
    来个测试用例! A7 |5 n' s# K

      b7 d4 E  y& |0 u@Test
    ! I+ s0 Y. p: F/ Hpublic void test4() throws InterruptedException {3 n+ S' u3 F7 u
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();4 x; k( E" w: I- t( z
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());, m$ o2 M' E7 A; n7 F
        context.register(MainConfig4.class);6 B) T$ m3 e9 q3 J  A4 @
        //刷新mail的配置到Environment
    5 g6 n: M6 d  y" L& I; ]2 w5 M. k0 v    RefreshConfigUtil.refreshMailPropertySource(context);1 a$ Q! i! F& o; G2 n9 V7 U& M  u
        context.refresh();
    - h8 e* j& t/ k$ u8 d) }7 j5 s
      s$ @) d' H# W    MailService mailService = context.getBean(MailService.class);+ P/ Q. ~% ]9 m; c% T) e4 T
        System.out.println("配置未更新的情况下,输出3次");  y9 v) p1 y' c& [
        for (int i = 0; i < 3; i++) { //@15 A( i  ]! P% z; T# D
            System.out.println(mailService);$ M# d  V# V- G& p4 j0 t  ^
            TimeUnit.MILLISECONDS.sleep(200);, n( ]. H, U" q2 P/ E" Z
        }
    ! r1 E/ C9 n9 y* ]% f; f
    * p/ O; x) K- i# u0 o/ P    System.out.println("模拟3次更新配置效果");
    * G2 q& I# A* b; O    for (int i = 0; i < 3; i++) { //@2
    2 b0 o1 Z& z: K+ _0 ?1 Q        RefreshConfigUtil.updateDbConfig(context); //@3
    3 P1 e6 Q; i0 X$ u5 S        System.out.println(mailService);
    2 c6 P, p& a7 r  e! J. a8 n        TimeUnit.MILLISECONDS.sleep(200);4 _2 C/ S$ d6 V$ q: E& g. ^
        }
    / G$ X$ l" S3 ~' L* B( z}
    ; Y; {6 u! |" ^5 A  A$ [4 i7 Z@1:循环3次,输出mailService的信息) j4 S0 x) |' \  z4 E+ T" N7 G( i
    4 e# w$ |" p% m9 d% E% ]) ^
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    * T3 W+ Z; F; e% a. F* L: i; U) `% X6 J; i
    见证奇迹的时刻,来看效果
    : u$ R3 K& @# L& s  K, v3 ?
    6 w% Y% N9 ^) R! |. _3 c3 O配置未更新的情况下,输出3次  q9 ^& ~5 x5 t: m, ?' `  p0 o
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    % D2 G2 p& X/ zMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}$ S) e; |4 ^, O) h$ n/ [0 K
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    . [/ C+ X- ~- a0 i1 j1 H模拟3次更新配置效果
    ( _) T2 x  t" P! Y& a/ \! g( aMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    ' N1 b/ T$ F2 t2 `4 Z2 kMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}, i/ [+ T. P, A3 W( B; P! R, B* e
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    1 q7 s: ^  {/ W上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    2 b( T! J1 w4 O0 n: h2 E5 z( P; Y4 }  ^- ^- O3 G5 x1 j
    小结
    3 F( b. _# [' r; c6 s1 n  G9 g
    9 M) @  i! R1 M* f$ x6 e, T动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    % }% Z6 ?8 B/ a
    0 L4 x! u, e, W9 r+ W  O有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    " Z8 _' D# P, d! H' \: M+ s/ P' |% Y3 o: {) E$ X
    总结
    * o: p1 {1 t" B% j: Y' n$ h" b% `
    6 R4 |8 M( V7 v本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!3 j/ U( N. |5 K: e) K: u
    1 k0 r; W) w" g7 Z+ l. X
    案例源码+ B) @! u  N/ ^
    4 w8 f: n* V/ C% g) D# o/ q
    https://gitee.com/javacode2018/spring-series  t" o3 A; ~2 D) F
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    ! [9 j; F0 }! n4 l# h/ R
    0 K$ w* s( l- J* ?5 ]Spring系列7 O( T: V9 w  v: O- ~
    3 }+ O" i* d! V0 K4 B
    Spring系列第1篇:为何要学spring?
    3 B6 i! c% M0 [: H4 a0 ?6 {  z- o+ e, {, l8 b: ^
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)
    4 O5 j/ a' Q: s+ O. |" `
    # [( H4 Q2 U+ U& j, r% }Spring系列第3篇:Spring容器基本使用及原理
    ' b: |9 n1 ]+ b2 k8 e4 s; r5 E0 y$ m* T% D3 N9 A3 A
    Spring系列第4篇:xml中bean定义详解(-)
    ! q1 H- |4 U' ]" B8 v7 U% y% o4 L% y6 F; R9 G
    Spring系列第5篇:创建bean实例这些方式你们都知道?) Z* H9 z( }7 \. y) E: l
    ' _% n0 M6 ~) d7 \! G+ }; \
    Spring系列第6篇:玩转bean scope,避免跳坑里!1 }1 s* _2 L6 c/ I" S. U, ?
    * t! V& U& v8 [7 m5 ^3 S& Q( G
    Spring系列第7篇:依赖注入之手动注入$ h9 ^, w, [& k- d3 Q) {& K

    5 S4 C; n6 t3 Z0 v: e$ W6 dSpring系列第8篇:自动注入(autowire)详解,高手在于坚持
    & v8 c  K& d  \+ c1 g$ o- p, T3 ?. h& z8 ~. B0 r0 L, \* b
    Spring系列第9篇:depend-on到底是干什么的?
    * N! I! V7 {' t( Y5 B; V& [7 X9 v  h4 N% y& C8 `
    Spring系列第10篇:primary可以解决什么问题?; X! p# [/ _! K9 Y0 i7 U: k( g% b

    . d6 A; ]7 U5 T! B7 }Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    7 g. g( v  J6 m% f  T3 z
    / u4 Y. p4 ]6 [# eSpring系列第12篇:lazy-init:bean延迟初始化
    , ]2 C" ]" N: A: h' I
    % p  O  @2 q& N$ ISpring系列第13篇:使用继承简化bean配置(abstract & parent)4 m; s" U1 K: Z; Z9 N

      U: Q% D" m  }3 f; U2 `Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    " }9 D, |: B: ?# C4 b% J5 l4 k; G( l2 b: I
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?
    5 G/ W/ t; U$ ]. o3 Y% E
    ; }  ]' M9 W( a/ P' xSpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    8 m. k  M) ~* C/ \2 Z, J0 z, r% A) |/ J5 p
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册); }: I. e, U# a! G% `2 ?% _5 D

    " R, g. {4 ?  h6 t- ^" {Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)0 M! I. G' v! @% l0 x% l2 T

    9 ^. S, Z4 M* NSpring系列第18篇:@import详解(bean批量注册)
    3 Y8 E* v" [% D# i. K+ w- E+ L+ S2 y) J( T
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    : T$ ]3 v: i0 P# U$ X
    ( k0 y, U( c& h4 U8 X4 dSpring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)- O* r7 x3 u! J% [/ Z; J/ F8 ^9 p* O

    & ?1 J, D5 q7 E- @, X1 PSpring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
      P. B" G0 j& r1 ~- n/ b4 b, B8 Z0 n
    Spring系列第23篇:Bean生命周期详解
    3 v+ @% M1 D/ ?3 S2 V3 u1 y: v/ M' L5 u
    Spring系列第24篇:父子容器详解5 F0 J! O4 O; o, m( Z4 {4 F

    $ B# P- @& R  E3 W8 s更多好文章
      e1 Q) U5 |5 Q* u( i  s$ I, o+ s$ o) A0 b
    Java高并发系列(共34篇)
    - }; |& a; H# Y9 [" `
    # ^1 [9 ~6 O/ P0 O  [/ T0 V- ?  eMySql高手系列(共27篇)6 k* ]; K/ @* Q( O8 X

    * B( e1 x/ `! V+ D. PMaven高手系列(共10篇)
    $ l% G0 }9 a0 O3 r+ }3 J2 |# y. b7 h& u) u4 h, M1 n$ c+ k# w5 {
    Mybatis系列(共12篇)1 d% X/ ?  G8 U2 f# K" i
    : l- ^; e7 `+ n! G/ O5 d
    聊聊db和缓存一致性常见的实现方式
    ! _$ R. z* M* D
    ' S; {. q# J3 V& a8 [0 T接口幂等性这么重要,它是什么?怎么实现?& u9 m& W! O5 S1 ~, h
    , S  m2 O% a4 K2 K* M
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    ( B/ A' z! l6 w8 I; J————————————————& `5 V0 t) o1 u! z
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ! {# K2 d# v) y, v原文链接:https://blog.csdn.net/likun557/article/details/105648757
      \% w) [% R6 x5 a
    1 H; Q+ U. f8 w
    2 w# _( [$ p" Z$ t" A& ~5 F
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-20 19:38 , Processed in 0.742864 second(s), 51 queries .

    回顶部