QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5366|回复: 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!8 |' V: Q) t' F0 O
    疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!5 y! Z8 Z6 e8 x& v

    5 n$ k/ p* a- y) ~) J6 v1 ]4 [! x面试官:Spring中的@Value用过么,介绍一下: e* ~: l7 ^& T) [  B

    9 {# y: o, i! I. X; o! i我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    ! h  {4 L# B5 W" j  b3 |6 F4 ?$ S' y5 s9 y" A/ e
    面试官:那就是说@Value的数据来源于配置文件了?
    8 c7 ~+ ~' F; j& J8 {" d7 U9 D1 V( j0 `" a
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置& r1 l: R7 Q/ [  v( P7 N( _% D

    3 Y' _( k; Q6 w面试官:@Value数据来源还有其他方式么?6 D/ L3 j+ @) @( Z$ M: K" Y
      Q$ \' f; w, s$ O  S1 g
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    ) y1 n6 u& a6 M" L2 G4 L; x) R: J3 i2 E- `3 q9 C
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    ! M7 q9 q) E5 k0 e8 {/ o( z: \% U% x, M) d. W
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    3 O0 L$ D* V3 T6 F# V  `9 u, ]
    - P% h2 [% S5 I" \" z( g) [面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    4 e2 N  m; S* |/ {8 ^  j7 d
    ! ?, p& T1 {6 ]9 v! F1 t我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    " U& E4 _1 i( @
    % K6 k7 g3 V/ s! p7 K面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?! p( H) b2 A' a0 _) {4 _

    9 Z8 h) ?( k: ~( o. V我:嗯。。。这个之前看过一点,不过没有看懂
    + u8 k$ _' Q" Y* M) ^
    2 p% Q' s* T* P: F, ~* [8 f面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    ; |/ [0 h; j  v) q4 ?. w' @, o6 R1 }0 m# ]9 ^
    我:3万吧
    , k7 }6 ]) w+ |: L( v6 R4 S" J! F; f8 h/ O7 n) E5 T
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    * r: `1 K6 B) N/ l1 p% ^+ P1 i4 q+ @& j: v+ ^2 U
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万6 n* i& x! u+ d+ n2 ~6 \, M
    7 U3 P4 y# d8 F$ x' X% u- j# k
    面试官:那谢谢你,今天面试就到这里,出门右拐,不送!; [$ c$ M( `) d. X+ ^- ]3 x
    8 C- o8 U% f% D% i8 J2 W- p% I
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。8 B, x4 P& O9 ]

    3 D5 r. e$ ?6 o% a1 q1 F这次面试问题如下
    3 j8 {6 e5 C/ D' S, R
    / T  }+ }* m/ q" x1 |8 U+ ]@Value的用法6 p  E" h+ |; I; I" Q* P) Z
    + S% U' o- W, f6 A5 Z
    @Value数据来源  `- R5 {5 \" @3 G3 K7 P4 K

    . q1 j, c% a( D: s! q  W7 r@Value动态刷新的问题+ A' d; O4 w4 x6 u
    # h0 {0 ^. i' L: q+ ]
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    ) ], R* [/ k( y! z4 _4 J  k! R# V+ F4 Q  H
    @Value的用法5 {1 }: k. v; y- w
      \; N# \- i; e5 R% l# Q
    系统中需要连接db,连接db有很多配置信息。- z' M, f. ^! I& q/ Q, `

    7 V  {4 E3 ?2 B1 J# N# s' r系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    : N3 E- Z* p$ p, Q. ^$ n9 |4 E/ b5 V% x8 W
    还有其他的一些配置信息。
    - N: S2 s& K; s" r; ~$ ^6 w5 G
    2 o+ A0 T8 s+ N/ I" x我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。- }+ {/ W& R( v/ Y& s! j& S

      C6 Q8 g$ j$ Y8 T那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。5 _2 M! M+ A( M7 K0 L( u  |) ^  q
    ! e4 q. _* M# \$ K  w8 Q8 M% E
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    3 ]; i5 T% P$ `9 c; u, k+ S5 _  S$ [
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。7 P  {# F; v' w$ Q8 `8 A

    # T( L3 W/ G4 L: h@Value使用步骤
    % k  d% E" P: r  h1 ^7 _( L. K" }5 d7 [: z( A# w
    步骤一:使用@PropertySource注解引入配置文件" U* I9 Q8 k& a; W& X& K

    # w- g+ H$ B7 g, a5 Z! H将@PropertySource放在类上面,如下3 q3 J& p. h7 z+ B2 h8 H

    8 Z' W' X8 z3 X' U0 J) ^@PropertySource({"配置文件路径1","配置文件路径2"...})
    . V1 y9 c& L7 r4 {4 q& l' C@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。7 C" I7 y( X7 Q" m+ w' _& i

    5 T. ?4 {& P* `! B如:0 b- n+ @7 t# d" O2 S
    + s4 L' M7 U( o4 Z
    @Component: o$ S: L6 I6 q0 M
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"}); H3 U( z% o/ V4 z% {6 Q, r
    public class DbConfig {
    4 h- t" r/ Z) @0 n% c% ^9 [$ \}9 B, `$ e7 Y3 H  w" [
    步骤二:使用@Value注解引用配置文件的值3 u+ G" n3 R- C9 |* V

    5 O% `5 U2 d& r/ {* z通过@Value引用上面配置文件中的值:
      p2 B8 J) K. N( E
    7 Z' }* h7 E/ K语法
    & r& @2 O2 L, ^' w" q7 N
    / W7 x/ {% y/ {; G7 g% ^! o& n; C@Value("${配置文件中的key:默认值}")
    * O: m( [5 c8 F6 i4 K' U  ]@Value("${配置文件中的key}")$ f" F9 c! M% |7 d: V
    如:: \$ W: r3 M) w' t6 {2 p; f
    2 I& N' o* B  s0 n2 u2 C
    @Value("${password:123}")
    ( V( v3 E' I' Y0 }) a. ^上面如果password不存在,将123作为值# {! R2 x/ O( d

    5 s# Z+ q2 j( E8 `2 Z@Value("${password}")1 e, ]+ A; \, z& I
    上面如果password不存在,值为${password}
    * U7 V# u3 y8 K7 ~. O
    8 ]) d* B* X: m! D% l& H假如配置文件如下9 \  \- R! m7 Q3 L7 u; H/ F: B( ]

    9 u! o/ p) Y( }3 W5 kjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ( G/ i! ]! D' X0 S. hjdbc.username=javacode' p- Y5 m  I8 E
    jdbc.password=javacode
    ( B9 g% F! i( v/ Z, E7 @3 c4 F使用方式如下:& C6 v% K' m$ T; \

    4 d3 P! v) X% o  \' x8 {' I@Value("${jdbc.url}")6 ?6 Y# k5 \3 H6 z8 H; M
    private String url;# i' H" @5 g! k0 W% C; n: Y) A

    ( x, [# k$ L0 H2 X5 Y, ?  \2 V@Value("${jdbc.username}"): x9 A6 o1 |" a% u
    private String username;& U2 k7 ]0 b4 ~+ g3 X: x
    : v6 l7 r1 Q- Q! w% A1 m; A
    @Value("${jdbc.password}")
    # r1 u5 ?; t+ [$ d0 f% P, s* Rprivate String password;2 W+ ]$ Z  B. i0 K; I' [- `! U* R
    下面来看案例
    ) N/ T4 U; s3 y* R) U3 v! P
    / j( G7 S0 F! ?$ y- G& B% R案例
    ) ?. K) V3 I4 L6 X! J
    - c' U4 Z" e2 e9 o* j( S5 A$ M来个配置文件db.properties0 b4 O# C2 Y) B. F& A" M& v

    # f2 P9 H" f- y; ?3 h6 G( V8 Sjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    ' Z1 z  v+ Z) Yjdbc.username=javacode0 r. e: F, s0 D* }8 ~( C
    jdbc.password=javacode
    1 E" n" y0 c* U0 W0 u0 s1 S! S% N来个配置类,使用@PropertySource引入上面的配置文件
    8 N, M; s! @8 w
    8 k# @. m! b$ n9 f5 d3 l$ O# d8 V4 Fpackage com.javacode2018.lesson002.demo18.test1;
    , m+ I, i5 P2 S- c
    ; c8 O' {) b% c( T* |" e. Wimport org.springframework.beans.factory.annotation.Configurable;: S" f" S6 n; |6 `( ~& E* w
    import org.springframework.context.annotation.ComponentScan;
    5 O7 C7 a- F% H  @# `$ T  ?import org.springframework.context.annotation.PropertySource;
    2 `% \1 s  s- U
    $ ?- E6 k# p5 D: l@Configurable
    . a3 }1 r: }: p0 g4 F: [2 L- z@ComponentScan$ d0 k8 `4 b! x' v* W$ Y
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})" G+ d2 c& J/ r* p+ G" ^
    public class MainConfig1 {
    ! Q; Z) m  D7 V, ^  @}- ^% w: v8 M% j$ V7 i: A
    来个类,使用@Value来使用配置文件中的信息
    ( d5 @3 E: E3 ]0 Q0 x& D9 ~9 G- {- w$ u
    package com.javacode2018.lesson002.demo18.test1;4 z/ N# C; d3 P5 q& @

    7 M% D9 z' y; o9 i$ a: rimport org.springframework.beans.factory.annotation.Value;& d2 x8 z4 d7 j  _
    import org.springframework.stereotype.Component;4 y) g# Y5 X, f$ S$ {
    # a/ a% ?9 k5 F
    @Component
    6 I/ b5 t# u7 `! B) n4 Upublic class DbConfig {+ X6 J5 J6 L0 J+ v+ j

    5 G2 s2 x8 s7 X2 A  e" ~    @Value("${jdbc.url}")9 z: ~0 `- d4 r; l& h: q
        private String url;/ F# W  J4 j' I# Y3 q

    ' q1 ]+ p& }6 Y# S- [8 Y    @Value("${jdbc.username}"): s  H8 D; \# f9 N3 S, p
        private String username;8 Q/ B4 t3 k1 q8 V8 z  ~

    . P- n. A3 f7 M+ C    @Value("${jdbc.password}")
    * L5 b$ ~& J6 i3 `    private String password;; M- a1 d& N# q
    3 d& J4 P6 i# G# `0 N8 b$ @( g
        public String getUrl() {# p7 S7 U6 T1 U5 o
            return url;
    3 h2 t1 \! n/ J. g, Q2 B1 |2 W    }5 L* c5 s: H/ Q" Q  C6 T  p6 l5 r

    ; U! I6 s9 S/ j( o7 L. B    public void setUrl(String url) {
    * ?" e/ d( ]0 q/ Z  Z: ?7 P+ m        this.url = url;8 E3 A5 t, ?) X8 P) c( p( k
        }) `2 M  c" \$ m" ^, E' {$ S. c
    * h3 n- w$ e. h& ~0 _$ P: T
        public String getUsername() {
    0 {$ H% S: `9 t7 E$ r1 `6 N9 [        return username;/ {6 p9 i6 a2 O* g2 U
        }
    & U" }! c$ l) w; n7 V  Y
    4 }& O. c! }9 ~- n5 e- A) \    public void setUsername(String username) {. B+ U) R% m) h
            this.username = username;
    7 E" ]- P4 z: P) K    }' u. A9 M8 d& H$ f6 m0 M

    0 X3 I, _- A2 n9 x    public String getPassword() {
    # l& `; A! U. J8 q+ @/ @% r        return password;
    4 [) U5 l& w1 ]0 {6 p    }
    ) `' k1 L7 Z2 z  U1 f6 @7 E$ k5 V5 H# l* c' {9 x) ?$ o; ~2 D. \
        public void setPassword(String password) {6 h- F$ A( E+ s
            this.password = password;
    2 X6 A& j! q8 `    }
    7 H7 a5 x" M$ y  D+ j" l0 C, d1 w. a
        @Override
    ; R; ~8 Z# M. R" G    public String toString() {
    ' Q8 ], \& z7 I( d4 y0 h* F        return "DbConfig{" +
    0 I6 M0 }! I$ Y4 E" u% R+ _                "url='" + url + '\'' +
    1 s5 m9 ^" E0 M$ W                ", username='" + username + '\'' +
    ' P1 R! q$ X5 f; ~# N                ", password='" + password + '\'' +1 B& {3 `% y: e! m! L
                    '}';9 C9 ?) y& v2 D. \" g1 P
        }( t4 ?* Z! @) D+ @& E+ a8 ~
    }
    3 \* R9 \4 j, u2 N9 w上面重点在于注解@Value注解,注意@Value注解中的
    # N$ E. H3 m& H0 R0 ?- a; o8 x$ r  X; E5 F
    来个测试用例5 [$ m1 v; n' g& C8 s3 a) G

    ' q0 P. E0 }+ e3 O' H4 O$ P5 Zpackage com.javacode2018.lesson002.demo18;
    ' R7 V; |7 f8 i! U$ H& H
    3 h) S; C( J9 v8 t( limport com.javacode2018.lesson002.demo18.test1.DbConfig;
    " i& y  @! J  L# z! ^1 Rimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    4 X* p9 t% B, L5 w9 ?# ~- y2 I1 Yimport org.junit.Test;- j" o% z: S  `, w; S* A; ~
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;* |  d9 @! z& @4 @# H

    $ q8 R! \! J$ i* q, P& k' T# {public class ValueTest {
    2 C' N7 Q( X- |: J  J
    5 [2 s# S; m  Y& n4 [    @Test
    & |; F; O! q" L    public void test1() {, ?% X* A2 a0 c% L3 f
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    - T" _- H* e3 O, |% m* t% _; l  e        context.register(MainConfig1.class);" P6 s' h( n4 F- X" c+ ~
            context.refresh();$ {; v, S. C9 b
    / S% _/ U, d( j" ?3 @
            DbConfig dbConfig = context.getBean(DbConfig.class);: G& X- K8 \. S, c  X& X& ~9 ]; m
            System.out.println(dbConfig);
    3 F9 M3 M9 V& p& f3 d    }2 Q' ^6 M! D3 A3 A% D) f
    }
    . i7 I9 W" ]3 I1 g& `运行输出
      x- |# G3 p, }/ ]! S% h; F! P5 C
    # e6 ?" _$ g& V" Z# A: pDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}2 h+ ^5 ^+ Z* S% M* t
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    , x+ q$ G$ q) P, Z  H* H. X. ]; @
    @Value数据来源
    0 H% k2 M6 l0 R; S% ^7 m) Y' e4 f9 P( {3 H
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    & T5 |) M: H+ a( B; w! g. m* f
    我们需要先了解一下@Value中数据来源于spring的什么地方。: _# v" r/ }! x. `! {9 T7 D. g) K
    $ z. T8 e  g2 o2 F  N* P
    spring中有个类/ I; t' W* z) I
    , {4 q' D7 R3 t" R0 Z
    org.springframework.core.env.PropertySource
    9 k3 N: t, Q- Z  K/ l可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息- w0 e! e/ D6 {; W2 Z" a

    6 R+ {. o! h! {9 N  ]内部有个方法:
    ; Z* o: t+ K* Q: B' F
      l0 z* P4 }' Fpublic abstract Object getProperty(String name);
    / ~- F  m6 q" r: O2 ]: X: O; o通过name获取对应的配置信息。
    & W, k6 D1 E- A' X' ^
    ) ~  p" s$ b" P& D系统有个比较重要的接口
    5 _( Q% q% u$ w+ J
    9 _# X2 e) K4 F& c/ Eorg.springframework.core.env.Environment
      h/ L/ p1 e7 U4 Y' o2 y用来表示环境配置信息,这个接口有几个方法比较重要, w& R9 ?! l5 q5 {' b
    - D. n# Y6 d$ D5 |% i) m
    String resolvePlaceholders(String text);. S1 ]0 `4 O! {6 z! d
    MutablePropertySources getPropertySources();
    ( ~6 [, W. a5 V! R( O* HresolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    0 H! Q$ a9 U3 Z4 ^
    - q8 }, j/ C: u' kgetPropertySources返回MutablePropertySources对象,来看一下这个类: F' z+ K% X& T& E

    - U5 A: K( t9 [- T" \  ]public class MutablePropertySources implements PropertySources {& G3 \- l4 s- N% v7 J) v

    * J% `7 n( q# c    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();( K6 M: J- }* I+ W; A
    3 K7 k9 b- M+ Q8 Z0 K# l
    }
    8 u: l9 L. n- A  t1 P) B4 W% }8 p内部包含一个propertySourceList列表。
    4 N0 z! L( E! H& k  ~+ I7 M7 B# q, \% w8 w" `
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。8 Y# W' m. k4 @9 o8 \

    7 U$ _0 k+ N3 P2 I大家可以捋一下,最终解析@Value的过程:
    & K, o; G3 `" {( o2 @# O
    # K$ o$ n; o, t* Y1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析: I. l) w4 P* l
    2. Environment内部会访问MutablePropertySources来解析. x+ j) }$ f% f) `7 e4 m
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值
    % H4 J8 W) a4 @! g% Q  ~# H% v通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。& m$ w  Y7 @( ~3 O' r0 d
    4 \% @! D% _" M2 m
    下面我们就按照这个思路来一个。! O; s' v. D$ z, x8 \4 C8 X

    7 j! L# i3 N6 `3 _; i来个邮件配置信息类,内部使用@Value注入邮件配置信息
    2 K7 |: L% p5 v. C& B; L7 w( N5 t/ a2 p1 ]1 ?
    package com.javacode2018.lesson002.demo18.test2;
    1 K! L) p( |# B/ t
    ) h* H5 v# U# O$ T% u0 e0 E" h7 {import org.springframework.beans.factory.annotation.Value;
      n: X; z8 K6 Z* oimport org.springframework.stereotype.Component;) {( P6 l7 s6 [" U

      J! }! [1 w. a+ B! t+ r/**
    ' l$ Z6 q; @: C+ s7 y- C" v2 ?* ~ * 邮件配置信息
    " b* f( q5 M- N1 x */
      u% {8 P" k& s6 D- I+ o3 s. I@Component; y" {) q  x* O4 `( N
    public class MailConfig {( J7 R/ @7 p9 j+ n. a/ I& K+ L

    4 W$ N5 L5 {& v& f    @Value("${mail.host}")
    - g  T. e3 q7 ?    private String host;
    6 I, x+ i$ m8 e5 C. ]$ Z. k8 C: f6 ]4 n: P# d- P2 h
        @Value("${mail.username}")
    . I; Q# r% ~/ j& t, _  s. a    private String username;
    / c& C9 Y9 @! l9 h: N' F" z% n% p" P
        @Value("${mail.password}")9 r( A5 T) k/ J9 \* Y
        private String password;6 k- r8 D6 H! S
    # w8 K! q$ `% k; f1 T1 `
        public String getHost() {& `# K$ D, w; q; f
            return host;  ?- n) v$ ~1 @$ T' M
        }0 y, b0 v3 Q$ I0 [7 s$ `

    : W0 v. U2 w- k8 }    public void setHost(String host) {$ d/ K( z. j  m, a% D8 ]2 \# B* V
            this.host = host;  G4 t8 l! {, {8 F5 p
        }
    ! b5 r/ y! ?$ r- I: |9 o3 A) l0 _6 F0 m4 i, B
        public String getUsername() {
    0 I, m: E' W6 D) ]3 g        return username;: G7 F% r1 D; j( g8 b& c  w5 S
        }; M$ g8 g8 \, M& |
    & ^+ W8 _* b( Y4 Z' ~$ N' f% v
        public void setUsername(String username) {; O+ ^0 {5 c+ \: a) l
            this.username = username;
    ! q* i( Q3 X$ X% v9 J    }5 w8 y$ J5 w5 q3 J/ E
    6 E$ {  u+ R" _, q. |' R
        public String getPassword() {8 r" p! |! m, F2 [2 w$ }
            return password;! O) I1 |0 u: S$ b: Z5 L2 d5 M
        }
      E  q, I9 M" D2 Q: ~. ~/ c
    / ^6 f/ _  D8 T, r    public void setPassword(String password) {+ W4 P+ }$ r& ~* O  h" G  j' J
            this.password = password;& T( i' J9 g" N( [. f- J
        }! d8 G2 `& C" o( ~, K

    2 N0 s+ N1 p3 ?/ [9 b    @Override. Z/ E; R3 Z, h# V# K
        public String toString() {4 h, r% Y) H3 O* X1 c
            return "MailConfig{" +
    : \, F# G; @; V& b: j1 z/ \( K6 n                "host='" + host + '\'' +3 X! X& S+ u0 V$ X2 ~
                    ", username='" + username + '\'' +1 X9 D1 F/ T( B6 E/ {
                    ", password='" + password + '\'' +
      }. T3 F6 c" H* G! h                '}';# b8 f- ?( k& u, F% G
        }
      z8 _$ v: u  j+ U}
    3 {7 S' L! s& s6 E5 n再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中
    ; P! d: C0 [8 Y- F# @. u- l4 I5 I4 k7 i0 w7 z/ E
    package com.javacode2018.lesson002.demo18.test2;- o+ i7 H6 k) Q: Y5 L8 f" n+ h) s

    " h' z: `$ m+ W6 \# B8 |. `import java.util.HashMap;5 V! y, r  m3 X: F# r
    import java.util.Map;
    # S/ O* M. z5 j: [* J
    7 P4 M, E/ Z4 W5 k6 T3 @' T% U* epublic class DbUtil {' D" r: `  \( i$ H0 d; H( u
        /**
    0 E+ o. B* S1 n. I     * 模拟从db中获取邮件配置信息' V; @8 l) T7 c$ k/ ?/ b
         *8 ~1 v1 H$ u2 _- |6 ]" z( }6 L
         * @return
    1 Z* ]. |) a; v, O     */- X5 D" m5 I. b
        public static Map<String, Object> getMailInfoFromDb() {: o: r8 k5 X* ~7 \
            Map<String, Object> result = new HashMap<>();
    * N5 h4 l" Z1 V: b7 N; m+ }1 c        result.put("mail.host", "smtp.qq.com");- X  F- B* w) G* x
            result.put("mail.username", "路人");3 \4 m- x& v/ R6 G$ s6 {7 J
            result.put("mail.password", "123");
    3 a; `& \' g7 I' }        return result;2 @3 C# t7 `/ T$ `* C) Z. i
        }
    9 y. g; j. G9 ~9 }" U}$ H, k; N4 u7 N
    来个spring配置类0 l+ g; P( f; b0 w3 ^- i

    ; V: ]6 f: T/ r- g$ W+ ppackage com.javacode2018.lesson002.demo18.test2;, e: n+ H' b: M. x1 M  p
    3 {9 P. W' U1 ^5 F8 @1 I9 A# j
    import org.springframework.context.annotation.ComponentScan;
    ( b4 }1 P5 I: ~3 \" M3 Y: timport org.springframework.context.annotation.Configuration;! v4 o0 p. ]5 e/ E+ a7 l; w

    ! i0 v) o9 f- p% Q, f8 k@Configuration8 N0 W3 ]* X4 `* Y6 V
    @ComponentScan/ w9 u" `& y; A4 q9 |4 H
    public class MainConfig2 {! ]  ^3 n6 o) s( I# t) R
    }: t" q* L- W$ j# a: b8 [5 @
    下面是重点代码$ m' E8 [  F- ^8 t! _9 }8 s8 h
    ! s+ h! Y3 i9 ?2 g8 p! f: F
    @Test9 _! w' B& \! H& d
    public void test2() {, S8 ]! N4 w6 N9 U$ D, _, N
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();# A1 y8 t4 X$ D0 Q- f8 f( V
    $ c8 E( h& F) u: x( b, g
        /*下面这段是关键 start*/9 {. o: @! M) l" F0 D" z4 M
        //模拟从db中获取配置信息& y0 ~, V! V- J6 P' ]! x0 }/ A
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();3 b. q' S3 z+ N1 c7 |8 e( r* L1 M3 B
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)& D( ?0 g0 @3 i8 a2 y
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    * I* E. z  L; z3 r& ]' |, X1 s    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    0 n! ^+ m% t+ J6 m$ Y% S    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ; [! K1 T. R" U# @3 P- D    /*上面这段是关键 end*/
    # M* o- J7 Y, W5 R4 n# c& r$ d1 {
        context.register(MainConfig2.class);; I8 T3 O0 \( ^, y* H0 W" I
        context.refresh();# k# H3 X- m0 v# T- y( d+ t
        MailConfig mailConfig = context.getBean(MailConfig.class);% Z' Q  p* Y8 W, S% b: v
        System.out.println(mailConfig);
    1 L/ ]9 C1 y! t+ v3 S: r! a: d}* U# |! s$ Q& i
    注释比较详细,就不详细解释了。- W2 y3 ^1 E4 |- ^3 I  b0 [

    ! }5 }: N: e% G4 Z6 x! z4 d直接运行,看效果( c' t8 g% F" f: X2 C

    # G% d- z/ Y3 dMailConfig{host='smtp.qq.com', username='路人', password='123'}3 M0 l0 K$ L3 k: D8 I& k
    有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。& ~! B6 f7 X2 B  j! h" U6 _3 Y2 i: {

    ; |0 M! ^* T8 U0 A6 r8 `上面重点是下面这段代码,大家需要理解
    $ ]: \  o8 Y0 J( O
    * k* p* C" n& j/*下面这段是关键 start*/
    / P6 [7 h5 B; J7 h$ ?//模拟从db中获取配置信息  _+ g9 u$ T( O. i: V) C# ?* {" [
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    9 E4 O( _* Q, d0 r) f: L% w//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)$ K) s$ U5 K9 J4 v
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    1 L) J% A- @8 I! P& D  D# `//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高  f6 a  x8 h& t1 x5 B
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    " L# ?2 ^) Q9 E; E5 [& ]/*上面这段是关键 end*/
    : p1 m& U* J" G4 c: Z咱们继续看下一个问题
    7 |- p& ?9 k# d# x; s1 }! B
      J" Q6 Q+ o- Y( C如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。# G4 p6 x2 U" [: A

    1 K/ Z& [5 T, s@Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。- @' n' Y6 u. G3 X8 [( w
    ) C/ m+ A+ V( E& y' ?9 t8 K( @
    实现@Value动态刷新
    ; f( d+ M% L0 W! o4 {
    * T! s# F% V- F4 z& M先了解一个知识点. G1 Z0 ?# @# @

    - d) j4 J" G/ p; ?- a* a这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。/ L; R" ^. V  Q  O
    0 a9 {% a( q7 g7 }: [* Q
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    - H" m( g( }/ r/ D, g
    0 t' k3 V7 L/ y9 e: ~bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    # m7 d# t. ~% p/ s2 ~7 C3 G" P6 P- l/ l* r9 ~8 F
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;7 f- x) D7 P: w7 A$ x2 a. ?) v6 M
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
      r  T( W2 ?8 P4 Y" P" ^5 @4 ^. ^! p/ R6 q$ `* w6 s$ k
    public enum ScopedProxyMode {
    ' |) \# s( |8 Y    DEFAULT,3 k* ~2 D. _5 a6 O5 y; F$ p
        NO,
    # t7 w. Z" `- v6 x/ ?7 ?1 j    INTERFACES,
    % m0 x9 F' H, [" j. ?* ^    TARGET_CLASS;. A3 G. M  H5 Z+ p7 l! [
    }
    ' S% X7 I& a) t1 f前面3个,不讲了,直接讲最后一个值是干什么的。
    8 }/ R6 B, d; j2 `9 q. z3 |5 u- p  y4 v
    当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。( P6 l! f3 a) r* t5 _' _7 q
    " F! h' P( v3 A6 K3 v  r
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    1 Z1 m# q- M1 p8 ]( x8 n8 n- }, U; b; e% ~& v* j3 S
    自定义一个bean作用域的注解
    6 @, @4 c& l( c7 s
    ; ^! ~4 \3 R  X) f% d" k" D% }package com.javacode2018.lesson002.demo18.test3;
    & i" h2 ]" k- f
    2 l8 v) ?: T$ v* s" m) u9 b( r. wimport org.springframework.context.annotation.Scope;
    # ]* \6 \0 X, x5 timport org.springframework.context.annotation.ScopedProxyMode;
    0 i2 C# G# |, g1 J1 D( H! D1 }7 t# X7 ]( \
    import java.lang.annotation.*;
    , [, z! w+ e% i5 z; ^9 G& k
    6 W  D3 l! H+ u) n6 E! O1 j@Target({ElementType.TYPE, ElementType.METHOD})0 {4 e& F4 T% i# {% Z, h. E) j
    @Retention(RetentionPolicy.RUNTIME)( b& K/ x% H: a4 I# f. j$ T7 C1 q
    @Documented
      W3 J( l6 E( D( {( u@Scope(BeanMyScope.SCOPE_MY) //@1
    8 T1 L" v! q0 y! T! K) V' Ppublic @interface MyScope {
      A( x. g+ B, v5 @    /**
    - _& C! [# s% E4 n9 C     * @see Scope#proxyMode()$ U. L/ a: @5 V# q1 D+ O
         */) U8 d& M) F* H5 y: M9 e
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@27 l5 q; @# G# ~% v7 _
    }
    9 o; _( C' D6 c, T7 x@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。- u& l4 u# A- o, W% l
    4 m! z; y& X: x+ s! D
    @2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS
    ; D$ r' B% v% z- ~) z/ N7 ]
    8 I! l  k- A6 S  T" K0 [@MyScope注解对应的Scope实现如下1 i/ B# P  r; [( l, C
    ( W* U% O/ U7 g: [1 [4 d9 _
    package com.javacode2018.lesson002.demo18.test3;" g  w3 l* r, y, A$ @5 ^7 A6 ~
    : b: j1 j0 S- S, _' u
    import org.springframework.beans.factory.ObjectFactory;
    & U8 F! u5 H/ t) v( p" ximport org.springframework.beans.factory.config.Scope;# c1 [* X. v: P
    import org.springframework.lang.Nullable;
    & X% S* X; U1 L! U$ l, k
    6 y8 D0 Y  L/ Q6 I, \* E/**
    1 @4 f$ R1 `* K- p * @see MyScope 作用域的实现
    ! |% J" W& D8 |6 P, Q- W */
    8 D. e3 k$ f3 wpublic class BeanMyScope implements Scope {) Y  ?$ G9 C  V$ Q* O, A
    ) G7 I% }. y, H3 ]
        public static final String SCOPE_MY = "my"; //@1/ u" _, }! K6 ]" v! C

    3 p: v. d+ V$ C: k; @, Y2 p    @Override: Q& z+ g3 K8 g) G* f8 D* q
        public Object get(String name, ObjectFactory<?> objectFactory) { ) J  t, U. J1 Q( c1 U4 a. t
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@27 N, f# v" p7 `; c+ Y
            return objectFactory.getObject(); //@3$ ]+ G. x1 ?- X7 O. ~
        }# Y. c3 h5 j: w# ?6 b2 N
    ; H  K1 w: g/ g. Q2 w+ }
        @Nullable
    8 r: D3 ?: \, S* D' W' ~    @Override
    5 P9 R  ^% z! y( I3 I" U7 N! e    public Object remove(String name) {2 r3 m* i) K! |1 K
            return null;& Q/ O. u6 P( L3 |7 V
        }
    & f2 |( B( Y3 p' ]: u# y
    / E3 I* N3 r8 Z) N" m+ p4 D    @Override
    5 l! n& F4 ?% n. c! a- }    public void registerDestructionCallback(String name, Runnable callback) {: N: Q. M/ v7 Y. c/ l. g4 o) s2 l

    , y9 }+ ?5 w, W6 m/ C1 o    }+ }" @  a$ a" M; h+ }1 N9 M

    : X  U, f" f- J. E6 e. l    @Nullable
    . u$ \# l7 [+ h3 v7 r    @Override
    . K. n2 D6 o9 T" l- L& ]    public Object resolveContextualObject(String key) {
    - O$ u5 e- b# L+ p/ ~        return null;
    3 e& z% |1 ]0 K* Z9 K    }' a( v% E9 j4 e" W6 S4 @

    # l' F. N# D; ~9 Z0 K2 j' n) R1 S, O    @Nullable8 D' y6 X8 n( W+ X4 W
        @Override% U, C! l4 X7 o
        public String getConversationId() {: H) B4 K4 K) {) \& u
            return null;
    # o! a/ G6 m( v- F# w& [; ~- `    }
    5 T1 S6 g  e0 {& ]3 v}
    1 }: |5 V  E. b' r1 p( P% m: Y3 J( E) W@1:定义了一个常量,作为作用域的值& Z( O1 \$ L; U$ {. s- d$ H8 o

    7 i9 [$ G8 ?% u& Q" h@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    $ e$ c/ U6 y  w* K) e
    4 k9 d6 C& {# I@3:通过objectFactory.getObject()获取bean实例返回。, _" I/ @$ [. i( Q5 m" C" s  k

    - D8 _6 x0 Z' U( ?# O2 i下面来创建个类,作用域为上面自定义的作用域" k9 `$ R- D0 B, x1 S+ ?- L" G

    , ~9 D- F4 ?" t* p% _* zpackage com.javacode2018.lesson002.demo18.test3;* d! t+ s& y* J) V/ R7 Q
    , Z: T1 b6 L0 c+ K
    import org.springframework.stereotype.Component;
    ; G$ t& w; K' q% V1 _0 [2 `" C9 i2 Y7 ~/ J/ k/ s' }8 s
    import java.util.UUID;( ?8 ?: G, e$ ?: g& E9 e- w1 [
    1 @4 P* W$ F1 S" R% h! j
    @Component
    , P9 y9 ]) G0 P2 o3 z" L@MyScope //@1
    7 D! n9 R: ~( D8 ypublic class User {
    * ?  @7 b& ?3 r4 U" j$ F
    " w7 l+ S, y, a7 [    private String username;- |' {+ B4 O1 v4 M( j" d
    ( E+ E+ _3 A2 i4 }/ ]7 v
        public User() {
    ( C- k" ?# W- J        System.out.println("---------创建User对象" + this); //@25 o+ G/ Y; g4 R  R  A# U: p' c
            this.username = UUID.randomUUID().toString(); //@3" x6 P& N) C% ~$ `
        }
    4 O9 c# L) H* h  ^8 _( P$ G8 `% v7 e: |
        public String getUsername() {
    " ]9 m1 P- m* A$ p: I9 h        return username;
    4 L* v9 p, b6 z, [. m    }
    8 h6 D6 a+ H$ ?# |; J
    " m5 O3 E0 M% }0 T* @  ^' I+ {% H3 i    public void setUsername(String username) {! _/ Z0 c) }& q, j6 i
            this.username = username;
    9 l. ^. Z( M3 g    }
      ^, Q! F1 a3 l
    3 j8 C! u; Q2 H7 Y% y, W}
    4 Y, Q* c) a' d# B+ A& q. V@1:使用了自定义的作用域@MyScope4 J: z, }( Z  T, M# j
    " J$ {5 J5 z! h6 r" f/ o
    @2:构造函数中输出一行日志  C( f% j* a$ Z1 P7 l! }

    - b$ J3 }7 P  C+ [5 }6 k  U@3:给username赋值,通过uuid随机生成了一个2 [1 Q" ^& L4 e+ @) n( N( e2 x/ S

    $ M& _3 V2 V" e8 D6 q来个spring配置类,加载上面@Compontent标注的组件5 }# e& g  k& x" r  r1 w/ v
    0 Q2 Q; ~3 O* k) B
    package com.javacode2018.lesson002.demo18.test3;& }4 z& E1 [% Q% b
    9 S4 j0 [- a( k0 b8 a: I( J
    import org.springframework.context.annotation.ComponentScan;
    8 o9 Q" v0 s. `3 c  ]9 S. y3 e! r8 Vimport org.springframework.context.annotation.Configuration;0 t* r: T* E3 ?5 J6 j  ?2 Y
    0 ?" y6 p2 h5 G. J& ]; c
    @ComponentScan0 f8 o: |. N4 `# [9 a. U! O
    @Configuration7 u( s9 _3 B8 Q$ C
    public class MainConfig3 {+ }* v- c" Z+ v+ y* c7 k* f
    }- V8 O" G" l: G) O
    下面重点来了,测试用例
      m' D. g6 e2 z2 ?  o; L5 `* a2 }- w3 f' ^
    @Test6 \3 E5 j4 ?7 S+ T8 v+ v1 U
    public void test3() throws InterruptedException {
    ' X) E  m/ {) S3 X2 s    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();" c- }) q8 z9 c' ]: u6 l
        //将自定义作用域注册到spring容器中3 V6 i( Z# [* X$ C% ], {. Y
        context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1: |& U- u; H: c3 C0 N2 o& @
        context.register(MainConfig3.class);  p) R1 o& I# s9 U/ S, `5 R
        context.refresh();
    0 ?, G7 J7 E+ r3 D/ I
    ) j4 Z6 o: W( O( V! Y    System.out.println("从容器中获取User对象");
    , m% Z8 [/ d7 }+ R$ q. ]. L* E    User user = context.getBean(User.class); //@23 E, G0 @9 ?& G( n" a
        System.out.println("user对象的class为:" + user.getClass()); //@3
    % h0 ^6 H% a6 S- v: h
    8 L! n7 n4 n1 [. d# C' ~$ S    System.out.println("多次调用user的getUsername感受一下效果\n");# _& E. x+ I( P  T) V
        for (int i = 1; i <= 3; i++) {
    4 X+ }8 O5 _0 R5 r        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    . E! P0 v# @7 y  p0 x1 N3 ^* D* [        System.out.println(user.getUsername());
    % h, G& n" p! S3 [        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    2 K8 b2 }  q: W- B    }
    ( R5 R3 U9 d! T7 ?/ g}
    0 a7 G) F. r/ D4 C@1:将自定义作用域注册到spring容器中
    ! U, Z6 W6 ?" c* F2 q
    ! U, J4 U" y& y@2:从容器中获取User对应的bean
    0 F* V* \6 J, G  q# n5 ]6 y* Q2 x4 _4 _- D4 c3 U
    @3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    : r' d0 `: {0 a7 i: Y" A8 C0 `# u+ a7 j6 U) D0 X
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。1 z6 @% z/ p; }

    , W- c7 M. C' a5 Z见证奇迹的时候到了,运行输出
    , P6 Y& w) y8 {4 P# z9 V" q/ P
    4 h* n, }/ O  t# T* W# h, s从容器中获取User对象8 ?2 a) b* U9 F8 }
    user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127! x7 y& r; a2 t: [) R8 X
    多次调用user的getUsername感受一下效果" u* K" v% K/ _! u
    : J; ?5 {. `, ^( @! `6 G" I7 {6 f$ i! O
    ********4 ], M" o2 j5 \3 v5 \4 {6 W
    第1次开始调用getUsername* T) R( _  @% [6 A! b* d
    BeanMyScope >>>>>>>>> get:scopedTarget.user6 c, p* @/ K* [' {. }* D
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    5 X* N) O5 ]3 P7b41aa80-7569-4072-9d40-ec9bfb92f438
    + w! Q3 s2 i* o, n第1次调用getUsername结束9 X$ D+ o  C, T8 N- p
    ********
    3 c3 h% y$ Q- e7 @  }
    2 a, N& q3 w8 \. O! F' i********
    - L4 v" }% x$ l$ v+ `第2次开始调用getUsername, H8 o# Y7 Z7 a+ }5 P# k
    BeanMyScope >>>>>>>>> get:scopedTarget.user: i- N' X3 ~$ ^( t
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b. ?: X$ ~6 C+ r* Q: i# A8 @
    01d67154-95f6-44bb-93ab-05a34abdf51f) P, B4 |: w5 C! O, k$ d7 F
    第2次调用getUsername结束
    / a, Q  A# R( p& X3 ]4 U/ b********2 E9 W$ l$ ^, J1 a" x, D+ Z- b. [
    9 @  E) G% r$ q2 V2 e
    ********
    + O3 H* ^3 H, p& h; v! Q0 `第3次开始调用getUsername$ W9 L" G' n- \
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    # z' M2 K+ F' R: t* b2 ]; \/ n: V---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15% Y4 I( c. E! U$ V  U* _
    76d0e86f-8331-4303-aac7-4acce0b258b8
    3 V( e; C( m4 q/ ]  ^第3次调用getUsername结束* J" M2 w$ z7 z2 S% v
    ********
    + K, E8 J5 N, ^5 T从输出的前2行可以看出:8 [, A9 ?2 A" {0 a
    " G! ~# f# h! u$ u# M  L4 Z; C
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    5 [, N  B% {% K8 r4 h& \# y2 |
    8 S! q* ^  {& k第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    : }) k0 e! c) Z; W# ?8 D, m! q  b5 u, C9 L8 k6 ~7 o4 f0 d) ^' Y. v
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    " p7 C  _& b) N4 p& S
    % E( E% a* Q* Z8 m! x% H通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。
    , g6 R, N3 w- t. f, m- }
    5 `" q; B: ~6 `' o动态刷新@Value具体实现( N0 {' O' j" I
      Z( B6 C8 a7 o3 a( _6 [' @
    那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。( y- a( T( j/ m# W# l5 {

    7 H( N/ [  I% a5 C先来自定义一个Scope:RefreshScope% P: ]$ ~8 F: f" i- X1 \7 c

    6 t7 d9 p, X; L# ]  @* ?package com.javacode2018.lesson002.demo18.test4;
    6 g( N( ]5 {7 ]
    ! F! t' J) {7 h8 R5 mimport org.springframework.context.annotation.Scope;- m) p  X/ J" a9 N! k* K, M
    import org.springframework.context.annotation.ScopedProxyMode;( h- N5 A) t* P$ ^/ ^$ D3 @" Q) Q

    % a; ^+ J% G; |( i/ u5 |import java.lang.annotation.*;* r  Z) C! n$ {! \, b. i2 b) [
    9 R6 |$ c8 b, {6 O
    @Target({ElementType.TYPE, ElementType.METHOD})$ V; ?6 A- C* i
    @Retention(RetentionPolicy.RUNTIME)" f# j. @5 o( V/ R% @
    @Scope(BeanRefreshScope.SCOPE_REFRESH)& \0 K( y& m/ }% V6 M  @, x# |
    @Documented
    : [( ^+ Y3 y6 _& mpublic @interface RefreshScope {
    ' ]& S: `  S8 x+ e2 S# J    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    + x, b. v5 x, R& M1 D}
    ; w0 S' q5 |6 |2 Y3 ]要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    4 i( h8 Q$ b, ^8 ]8 V. v+ K
    3 O3 v3 W, T+ V4 m' W@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS. z! g0 i: D5 i0 F/ d

    8 x0 [1 B! ]! }( R5 l6 G这个自定义Scope对应的解析类1 p3 j7 h* j1 U% H1 ~
    / p! E+ V! G2 N) F* F, s
    下面类中有几个无关的方法去掉了,可以忽略
    7 Q% ]/ B' `  r: l: ?7 u& a
    & `! _/ P: X$ F) O* x' tpackage com.javacode2018.lesson002.demo18.test4;
    . [3 d0 @8 m0 i* J7 L' b/ o, u7 `  S- h  D" b% z. K: n
    + e" ~0 A5 v2 n% a/ n
    import org.springframework.beans.factory.ObjectFactory;
    8 }' X2 J/ G  iimport org.springframework.beans.factory.config.Scope;
    1 x. V4 {4 J3 p/ a  M/ _import org.springframework.lang.Nullable;
    $ `1 p4 V7 J2 i2 w8 w6 ?4 @6 i% ^1 Z1 U
    import java.util.concurrent.ConcurrentHashMap;3 k3 O& {  `1 A. L% F

    & a" O* V; J: Dpublic class BeanRefreshScope implements Scope {
    8 \) i* H  l! e: O, O. j4 t: d" X- Q* W
        public static final String SCOPE_REFRESH = "refresh";9 ~+ e/ z$ e0 H! I2 a5 r( x
    + f! ~( h( E( I- {! H
        private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();. x. u0 s) B# Q/ {

    + ^$ P/ }9 ~; t# M& ]* Y" r" m5 d' w    //来个map用来缓存bean2 C( a( X2 G# z: a/ \
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1$ G$ X6 ?$ F  }
    ; O3 Z* ^! U- Q/ y2 e9 i$ t
        private BeanRefreshScope() {5 G* o# j7 z/ ^( J
        }7 N; r' O1 ]& \+ L9 ~4 M* V5 B
    6 Q5 d6 `! X- d: s: I- e5 O, f
        public static BeanRefreshScope getInstance() {0 I8 u. N* Z6 [* D) N$ D0 n
            return INSTANCE;, }8 j% z0 w' a/ @+ d
        }
    ; q& n7 D; K: f/ Q6 e5 ]. M7 c+ J6 O8 y3 a/ u. V) o% z
        /**
    ( A: z6 Q. `1 s% c+ L6 f. x& e     * 清理当前
    - R) W# W" ~( v0 c) C     */# P4 u+ z. m+ y% w& h5 R
        public static void clean() {; P% Z8 o* k/ e, w9 y
            INSTANCE.beanMap.clear();- `' T$ B' N  J+ ^& t2 R3 O
        }
    7 a- F5 F/ E7 \" c& f; }% E$ M  L# |1 O( }9 N' n7 ~
        @Override
    , F- l* r/ B, y4 F    public Object get(String name, ObjectFactory<?> objectFactory) {
    8 s% f3 ]! R& m# A" T        Object bean = beanMap.get(name);
    ) |. ~9 M0 {- y- b( N* H4 g        if (bean == null) {" r9 ~: E' J3 F* [( g. I9 t1 y1 I
                bean = objectFactory.getObject();0 d( ?9 ^# ?, N* n9 R5 [/ `, D0 o
                beanMap.put(name, bean);
    4 {1 r$ h* M( p$ y        }
    ! ^: _; f- m: R$ }6 ?        return bean;- B$ U. ]* S6 C& M  X5 P
        }& b$ L2 O  R+ R% S$ ?

    ! ~, z% \( f0 p. h7 W0 Y}# E$ X* t7 f# q
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中
    # p) H/ m5 a' y; m3 U% p' m, P0 d" ~; j7 x5 O  e6 K7 |
    上面的clean方法用来清理beanMap中当前已缓存的所有bean
    1 H. ]  Q) r! B1 t
    ' Y% D/ i  L9 M. S' j4 T2 a- F来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope& {( e* n& T& O
      Q/ e3 m7 g9 H+ o" n
    package com.javacode2018.lesson002.demo18.test4;8 L. f9 y* X% v  {
    9 ~9 w4 F) n  O" H5 b8 Y3 D5 ]6 S2 ~
    import org.springframework.beans.factory.annotation.Value;$ ~  b, e8 p( T) ^" b4 r/ Q! i  Z8 S
    import org.springframework.stereotype.Component;/ w- o) n9 O; J8 q# N- u0 L2 ^3 [% U
    7 g* I  {) Y! `! [* j+ J9 @6 H
    /**. S7 X8 b+ m! C7 y% I
    * 邮件配置信息. g+ Q9 @& Q( f! |1 K$ P
    */
    / l/ Y4 Q9 y" \* P@Component
    * |% e2 P+ }' s9 R' A* m6 d@RefreshScope //@1
    & U9 ~. c; t7 xpublic class MailConfig {
    ' ]  n1 L( v3 \; Q* t% [5 S7 R6 Y. l
        @Value("${mail.username}") //@2
    1 W8 R) y7 ?4 x) D- u    private String username;( M" `' s' E+ M/ e8 y/ ~
    % Q2 }& N! \+ i+ T
        public String getUsername() {% c3 Q, W  ~: V& O4 P" U
            return username;3 {* b# t3 v0 t' M
        }& z/ e% z3 v' R( R) L7 v6 F

    - Q/ R& z3 K+ J- C0 a; H2 F6 q    public void setUsername(String username) {" H2 _/ ]$ q  F
            this.username = username;3 P1 G/ O3 |7 V9 x7 B, F: N
        }
    % G4 H9 w( x$ c3 Q8 E, s& h7 c4 r$ X4 n! K' J/ Y. B3 ~
        @Override
    2 b  R( Q: J( i$ b/ C    public String toString() {
    * k; ]( d' o! G/ Q3 }5 Y2 y        return "MailConfig{" +- i8 q( q# L8 r9 b' g" h. F. a
                    "username='" + username + '\'' +5 g1 u1 h  A9 z) h0 T4 i+ O
                    '}';7 _, `) D) I4 T2 Q( F
        }! x5 }) Q( V# ]' _2 ?+ I- s3 ?
    }  A4 N  u6 r  X$ \9 k% t
    @1:使用了自定义的作用域@RefreshScope6 A( Q  J: z' f1 j
    ( [# a2 v: B! @9 y; Q  t
    @2:通过@Value注入mail.username对一个的值
    6 z9 g1 u% ]. {' ~- V' [8 @9 ]
    - m9 l! O2 M* H8 J' |# K0 i- I, u重写了toString方法,一会测试时候可以看效果。
    ) M0 [; v6 }2 Y4 U0 a4 ^9 S
    , ?5 z# u" z" ^再来个普通的bean,内部会注入MailConfig9 i( F! r- r3 S5 d) E
    ; z1 I$ @9 C( q0 l! c' K
    package com.javacode2018.lesson002.demo18.test4;* M7 [  d9 }0 |

    . E0 A# `% m- V' Y3 Q5 t, oimport org.springframework.beans.factory.annotation.Autowired;2 @0 y9 x$ o; X1 u' W! {
    import org.springframework.stereotype.Component;
    : R6 v1 N$ m0 r; u' d4 W) X8 F' l' v2 z3 L8 g
    @Component
    6 \8 j* `8 m. gpublic class MailService {
      G8 l: D! a, V. f; E    @Autowired
    8 c; y. b# _/ Q/ {1 W5 c) i2 V$ W, q. y    private MailConfig mailConfig;& l% I: l1 B1 {% g' G

    ) A, t  r) k1 C7 n" U, ?    @Override
    7 x9 R# L$ B! v/ ]* H    public String toString() {4 V  i: t0 h: N/ ^% f, U+ X& o
            return "MailService{" +& k8 l$ E6 Q; \% o/ x
                    "mailConfig=" + mailConfig +6 i2 ^- {& e- Q# A
                    '}';
    ! ]' u+ s) ?/ S% u: d9 z    }0 n4 |/ }/ ^+ w6 `1 W% f- q
    }
    5 v& [, g& B) A- n7 e代码比较简单,重写了toString方法,一会测试时候可以看效果。
    % K9 M- g7 f" `/ I5 W( ^5 O, L6 J* Q
    来个类,用来从db中获取邮件配置信息2 K: n! ^* m7 D* i7 K, D  n7 K
    0 j% F, M7 G3 B5 j
    package com.javacode2018.lesson002.demo18.test4;# x0 |1 H. U6 v

    : `9 `+ }0 l. rimport java.util.HashMap;( G2 I% V3 A; h
    import java.util.Map;
    : J5 D4 {' Y* @* q0 d! q) e+ A2 Timport java.util.UUID;1 z7 x/ Y3 Q' |

    ! y, G( Q) Y7 P$ xpublic class DbUtil {
    9 O7 l+ L; u! |! W/ i& T    /**, v. w/ V" S, Y( g
         * 模拟从db中获取邮件配置信息. q' o3 |' U/ k! ]$ N1 i6 Y5 G
         *
    3 A) H  d9 U$ Y: }     * @return) E( p7 C, k/ n9 G: Q
         */
    + D1 z/ V% t' _" j0 b3 _4 J7 v( `    public static Map<String, Object> getMailInfoFromDb() {
    $ M3 A: o3 O% s/ \: R3 B- Y7 u        Map<String, Object> result = new HashMap<>();- c: y2 ?3 [! F* i2 l5 T
            result.put("mail.username", UUID.randomUUID().toString());
    4 s  ]" d! {! c, E0 p3 |        return result;
    8 I2 R$ I6 Q' b* P    }
    , d- ^1 E' o% K# i9 k$ P( g% S+ I}8 [+ ~9 ?& V( W: o7 z6 _) M5 c
    来个spring配置类,扫描加载上面的组件3 _( h$ c5 A( P" d2 b, ^' n) J

    3 `2 \. N! a5 _# Z! m0 `% Ppackage com.javacode2018.lesson002.demo18.test4;1 H4 X/ I3 D0 u+ Y' P7 R
    / G, I/ e8 ~4 \5 g8 [' X% {# U
    import org.springframework.context.annotation.ComponentScan;4 A8 G1 |3 c7 G. f6 c& m
    import org.springframework.context.annotation.Configuration;7 g) W5 z+ l* y& X

    6 ~; R; P3 {6 V4 W" `: h) T1 j@Configuration  ], F5 ?8 f3 {) p
    @ComponentScan
    : `+ ^: R0 f8 m: X! `! v0 G: V' I% k1 Apublic class MainConfig4 {' h$ v9 u+ C% b+ j+ A! n: F1 S( `' u
    }
    0 f: G0 L1 P2 O$ A, T来个工具类9 `/ @! J. O: S: P$ N

    ( |: g! M& i$ Z+ I内部有2个方法,如下:- r3 m7 P/ D1 N( j( M

    , I, Y! s8 r2 a/ M* Rpackage com.javacode2018.lesson002.demo18.test4;/ p, \$ l7 Q) S/ ~3 n3 R) x
    ) _; s; ?, S/ h& y
    import org.springframework.context.support.AbstractApplicationContext;* [' E$ Y* F; I! `% @# p* {/ z
    import org.springframework.core.env.MapPropertySource;
    ' n, X7 K4 ~$ M" g& [9 {; a; _1 Y9 F2 n3 q* D6 ?
    import java.util.Map;- G8 k0 R; n3 b
    & v6 @) ]) |9 u+ @  ]
    public class RefreshConfigUtil {
    # A& @/ C% Y& w5 r    /**
    / Z: ^- N! o& V& S     * 模拟改变数据库中都配置信息
    6 G0 `; ?5 S* Y, {* j5 U     */
    ' r& E7 l8 n# W, p0 w9 q6 n- x    public static void updateDbConfig(AbstractApplicationContext context) {
    4 k+ R8 N2 Q  q9 i3 V) {        //更新context中的mailPropertySource配置信息
    * |4 \; d6 B* p9 Q  Z        refreshMailPropertySource(context);
    5 U# h! \# Q, b( k& |% v1 C5 R; [! x8 }& `
            //清空BeanRefreshScope中所有bean的缓存
    9 ?3 \5 N& G5 p, `. [8 M        BeanRefreshScope.getInstance().clean();
    2 Z2 S' y. D4 y    }0 P; B+ R5 G6 b! g7 d3 T

    7 H1 v% T: e" R7 {/ A5 ?; R& c" a    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    $ L  z! S5 a' G% M' w) _+ e        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();& i) u% k# [' n/ e
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类). \) ~# _) B4 z0 W0 v8 y& B. S% v
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    8 b$ z4 z; z3 X3 C7 h4 D8 e        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
      i2 w2 `+ P2 B# N, ^; k: P    }
    % Z4 g$ I  \% P
    . D! u% q* z, b! ~8 P}& r- W& f% g9 x+ e! B* w
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    ) k( V+ Z5 P: b' }8 Y; B9 h8 s
    7 y! A. E" O# @; [) \9 vBeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。0 g0 D( c% s) G

    3 k8 k, F- d. d/ `# H- C9 z2 I来个测试用例
    : B, ]6 c" F' e& ^% ]1 {7 P* n
    @Test# S/ d. K0 ?( M. C4 C! J
    public void test4() throws InterruptedException {. E- ]4 E) W* K( Z+ h- I7 e  F
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();/ b6 ~! i4 Z7 d0 ?" ]& I
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());: E3 f  u4 G$ F
        context.register(MainConfig4.class);+ ?& T& Z% p7 `# [5 j; n
        //刷新mail的配置到Environment  j6 k9 C$ k: M- x1 l& o" I0 M4 L6 d
        RefreshConfigUtil.refreshMailPropertySource(context);
    0 ]0 V0 \+ J; p; l% L: ^    context.refresh();5 u6 k! ?* d2 t: |& Y; Z

      a2 C# d9 D0 P+ K1 }    MailService mailService = context.getBean(MailService.class);# Z, d4 z* r7 R, e# O+ e
        System.out.println("配置未更新的情况下,输出3次");  t4 \) P- ^- X
        for (int i = 0; i < 3; i++) { //@1$ L) {: }  A, [8 W# _
            System.out.println(mailService);! R2 i8 Q7 s% _3 n! ^0 |
            TimeUnit.MILLISECONDS.sleep(200);
    - }2 f% Z1 x* M( `7 U4 V    }" }6 w7 |" B7 t3 R" \

      h) X+ x0 P9 I" u' U    System.out.println("模拟3次更新配置效果");; E/ H* a3 |3 T. d" S
        for (int i = 0; i < 3; i++) { //@2
    : s5 I9 s* n! n  O& h        RefreshConfigUtil.updateDbConfig(context); //@3
    6 u; y; V" }8 }/ K* j$ u        System.out.println(mailService);! A  c; I" {' r, @( Y* g( G  ]
            TimeUnit.MILLISECONDS.sleep(200);
    - W. m/ P2 Q/ ~, b) `8 {+ v6 ^    }
    3 \0 A$ n- j( S' @) j. ^}
    . c- O: a8 Y8 N, r3 x@1:循环3次,输出mailService的信息- J* \  J9 n" E6 _

    3 b( g+ o. H" e* z; f8 r/ h; P. w@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息- k" L3 _% y1 @3 G* ?

    ; h8 B" |4 U. c- J; o见证奇迹的时刻,来看效果
    7 x. Z8 ?' F# |4 {$ m( a- O5 V! l
    1 X  y7 ]2 `6 z配置未更新的情况下,输出3次
    : {5 i& h/ k" |/ s2 `0 v3 W* zMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}9 P* o3 q+ J: E) x0 C; k
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}0 Q1 D5 H7 l3 s2 v! g
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    & M1 I2 [9 i7 a- z" R模拟3次更新配置效果
    4 V" u7 W7 Y! y/ x4 d6 H6 k5 RMailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    9 }* u0 b: T# u! S" v. B' LMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}& B1 w9 ~6 K$ _
    MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    2 g" |( l& a3 C4 |  A上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。  }: x% ]% E" y& q0 J- C, @  ^

    7 u) ]4 j* }2 C3 L7 w# E# Y小结  M  p/ m) o- n& [  S

    * `1 b5 n) y4 R; Y6 N动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。9 K) D4 Z( c* i- B8 k' }; o
    0 {  V3 U& r% J5 v9 c
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。7 _- Z& g; I0 {7 w6 n. S5 C, m  s, B
    0 P4 Y" `& [1 }8 o  W7 X4 b
    总结' Z' W. i. K$ e' L
    " Q" H0 P0 H6 g$ Z0 q$ L
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!: `/ x3 [: C+ _$ {5 H4 D8 t$ M

    4 U  U4 C0 E( \案例源码! z0 ?4 x6 i9 N8 k; |  x! J, Y

    , }% ?. @6 J8 m& [0 K2 Phttps://gitee.com/javacode2018/spring-series
    : U# c$ G$ |; N/ o' G9 W' X路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    / `: b. \; Z1 q/ g0 _8 J5 M' A! E+ u- @! L% s0 w/ E0 W
    Spring系列
    3 Q$ [/ c- {! B, a$ s2 \2 F" Z/ h9 e
    6 S3 M( E9 E* w; e7 wSpring系列第1篇:为何要学spring?
    & U: Z; b) e! A6 D' z" _) E7 T3 m" G+ Z, y
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)8 b  l. D# I2 d0 Z
    3 j5 }, W0 e' h4 [
    Spring系列第3篇:Spring容器基本使用及原理. i: I& E: C3 B* U" y: R7 e4 [6 V
    ! f7 _) @1 ~9 T4 D
    Spring系列第4篇:xml中bean定义详解(-)* [! E% \4 I2 U7 w+ C
    + _* _" @5 K2 M: }* V5 W) o
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    " `& j. H* F" X5 p* \
    7 ], ~# z- A  T% k7 q8 ?Spring系列第6篇:玩转bean scope,避免跳坑里!
    6 w7 b" I- U1 B1 r8 H! x8 f
    - I, }8 q& I4 D5 X5 B; q. H0 oSpring系列第7篇:依赖注入之手动注入
    6 g! w8 x# M& c4 h  n
    # W3 C/ O9 c" P' R' D$ }6 }0 f( C( NSpring系列第8篇:自动注入(autowire)详解,高手在于坚持& u2 `1 s( \) F
    3 J  \  M& a) e
    Spring系列第9篇:depend-on到底是干什么的?
    8 S! [" `9 t. t: j0 |9 S4 K! J6 q" E# ?4 e0 u1 J
    Spring系列第10篇:primary可以解决什么问题?
    2 i& p3 u# V+ N/ Q% i. P' i0 E
    % Z% e4 ?& o; }2 gSpring系列第11篇:bean中的autowire-candidate又是干什么的?, v+ W" v$ d" H6 D
    : a/ A( c2 x6 H9 b. `8 K. t" g( z
    Spring系列第12篇:lazy-init:bean延迟初始化
    6 ?' f& {. F  i0 `4 U
    ) W  h' S' x6 Q7 ~" LSpring系列第13篇:使用继承简化bean配置(abstract & parent)$ u' z  f0 ~2 S1 W9 \. i
    9 k! G1 Z- _* p+ V/ j
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    - t' o( I; s4 {" q. ?1 g  U
      S& u- e7 s: c9 l+ y4 ISpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    + y( }5 W- p2 X9 Z& L7 X# U& Y. T; ?5 s) F, T& P5 k
    Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    + i" s- b' Q  j9 N) v4 ~( h( N. f. M0 D; y% {- e2 y
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)+ k9 u3 P2 G% n3 T: p# g- |3 N" \5 ^
    & Y0 T( k% P+ Y/ P2 y) |/ l
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)
    0 b* I! h, Z4 Q% G0 Q! H' P! ]
    ! u( f& R  |( FSpring系列第18篇:@import详解(bean批量注册)
    $ R) w) d( L/ p& [: e& H0 g2 j2 n! U( c( r4 R
    Spring系列第20篇:@Conditional通过条件来控制bean的注册
    6 T7 y6 w4 a$ O' ?5 R1 u3 h& X' m
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier); T8 q* B, P0 g/ C! M, n8 c
    ! t8 r3 }2 F/ l! @
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解
    3 Y# ]$ @, D1 l% A3 |) c7 O" U! Q4 y% e9 z' u) B4 u
    Spring系列第23篇:Bean生命周期详解
    8 V$ o$ \+ ~$ C) k; I5 Z5 x5 ?" u  G7 @
    Spring系列第24篇:父子容器详解
    % w) {; r; S$ U. Y% i/ m. J9 u
    2 {) i$ ]2 t, h0 }更多好文章9 K8 q9 q" F$ {% U
    0 I0 j) ]8 b1 r$ H
    Java高并发系列(共34篇)* b- y0 Q" w$ g# o

    ' N1 u8 {1 Z$ q! ]* }1 `7 s! j+ G2 JMySql高手系列(共27篇)# L" c; ~# Z; U0 s

    / }/ T3 X2 I% x" m# r' w# iMaven高手系列(共10篇)
    : P6 K) f# X6 Z% M. v2 b
      {* x! h& U" S* ^5 }& AMybatis系列(共12篇)) E7 x) h5 R' W/ `8 p% B
    / O) Z6 S" C* H$ h$ u) `/ o
    聊聊db和缓存一致性常见的实现方式
    ; V" O, B0 Q! z4 a1 e/ j( y% O& J: f
    接口幂等性这么重要,它是什么?怎么实现?
    ; x/ ?3 p& ?3 b! U8 U
      G5 m7 {0 A5 D; S  i泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!
    7 O4 o9 |$ F: i————————————————1 h) U# ^1 h9 |" |! p- u
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    6 f9 d% C' k  U" v* L% k$ T原文链接:https://blog.csdn.net/likun557/article/details/1056487576 ~. e; {/ q. f9 {, H7 c4 p4 [
    5 Q4 W& s' L" X6 e  ~) A& J! k
    ( K0 |  ^6 Q( V7 G. ~
    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-1-8 18:19 , Processed in 0.333215 second(s), 50 queries .

    回顶部