QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5368|回复: 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!
    0 C. k( c" i" m$ i! K3 H$ ?4 h/ G) T疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!1 ]$ F5 H  }8 d
    ! v' k+ c9 S, J# k5 E# M
    面试官:Spring中的@Value用过么,介绍一下
    - ]5 g$ A3 A1 P) Y: ]
    / u% ^: Z0 |" O7 S! V. I我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    3 J+ ?! G0 Y, s2 W$ U& B  E4 j; t6 K, O
    面试官:那就是说@Value的数据来源于配置文件了?
    * e. M+ y, E$ ]# u8 x% S0 u2 H: ^/ g0 [' x' N" Q2 U7 _- B6 U
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    6 Q3 Y" {- t7 j6 ]- [  b/ d; {: Y# g2 Y8 z3 r, r. r
    面试官:@Value数据来源还有其他方式么?* y* E7 @1 g) X% D8 m1 h( e* q6 r  }
    9 g7 O+ u  y" n- b. M6 v
    我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。
    " Z' C3 s7 X* j; a( |! f
    - N. o0 E! G% M, |  J面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    # h8 K7 Z; ?% `  S/ w; y5 a8 E
    3 L% r3 R' w7 B4 a5 W我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧
    . W) `1 r! O. j! u9 f  A' p. U  b. e* D8 X; R) L; d0 I
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
      A, x6 t- N+ R4 U' u  v  |# j# Q) Y5 s, @- K  Z4 A
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    4 M8 j; |& W! p/ ]5 S, u
    7 M3 G6 O& l# J1 l面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?" q5 V9 A- _1 D% ?6 L8 r
    $ |1 x7 S* U; \1 g/ Q% p
    我:嗯。。。这个之前看过一点,不过没有看懂+ ]$ b, D* B- t( |0 v% q* P; I8 K
    ) V7 ?! H. h% C. P; r
    面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    4 E9 `/ M( I* S3 d; [& W4 {* H5 w; Z- N' [2 Y# B+ @0 q
    我:3万吧
    6 `6 D7 `. P; R7 h7 Z+ {( \
    & c0 }) g: \- \! Q2 y  S面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?
    # V5 o  g( ~) [8 o7 d9 _4 _* _: J4 K+ Q; B( q  m! R% B# h, N# h- s
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万1 f) z1 I# ]# b& Z7 W7 P8 H6 F; e

    ; |0 M. q" U3 E0 [5 ^面试官:那谢谢你,今天面试就到这里,出门右拐,不送!
    4 u2 T) ]' W8 r4 q. g* v/ }7 y
    : j+ V9 a3 P* L我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。: r- S) i- U: v2 V- ^8 F
    ; w& @0 |' ]% p: J5 ?/ H; X
    这次面试问题如下4 z- ~! m! Q2 B# m% B
    7 t: F) X& k+ a8 w( w, N
    @Value的用法7 o, R; u* ^5 |0 o# s5 f1 K) W
    3 {. n6 _; ]) E
    @Value数据来源+ }5 ~& s& z4 W. g; R3 z/ ?. W
    % m1 h$ F' E& }2 x
    @Value动态刷新的问题
    $ g- e" F# p6 ~& h% R/ h. Q: r' q  y1 F
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    ( `2 y+ x9 y* x8 F' N% {
    % V" E* Q7 C% v7 R7 e2 \@Value的用法
    / X: Z* o; G( l, i( S5 A8 H0 V' v$ x+ j) s0 X& n  u/ P& x
    系统中需要连接db,连接db有很多配置信息。  {/ \) l) [+ K6 _

    : j& [) e/ P9 o  \% B- T- t系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    / u& S: m! ^! ]. r1 O  q( O. S
    : S  m- H3 V$ J5 E- K  H& g4 Y还有其他的一些配置信息。8 p8 n5 o" g: `6 z

    $ o( W! q* B0 ]8 \! c3 {* e我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。
    & Y3 o' X% O1 U' J* A% r" B- z( m6 H5 \/ j
    那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。& W8 q# J% n1 S6 v
    * ?) o) B% s8 e" J# {3 F( F- O
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
    2 [0 C% Q( M9 l8 r9 |% n- Q3 V7 `4 T( R8 D1 C% _% o
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。. |- e) J9 s# c7 n2 N& O, f

    6 w5 D) @1 [0 L' M5 y2 Z@Value使用步骤
    / b5 P$ t- l. H8 u/ S! h. @" I+ e, C& G% g0 C/ u! d% I) V
    步骤一:使用@PropertySource注解引入配置文件
    9 V% j7 d3 y5 U) V: W( Q) X! _6 Y
    & }9 T  P; g  X. s' d5 Q将@PropertySource放在类上面,如下
    ' ~0 \. m% k( S' `( |: u$ x) C9 Y* R$ n: {) @& t6 h) E; m
    @PropertySource({"配置文件路径1","配置文件路径2"...})
      I  F. ~9 E. C* h) c! {" ~% k0 ^4 {@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    0 `0 z9 H% h! y8 L% |) o: A. R) {
    如:. e. W5 \& k2 H, u6 h

    2 T( r  t. H3 u2 A@Component
    ; m: F8 u3 I' y8 q  W& J@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})1 z. a, [$ }! h
    public class DbConfig {
    ) [# U9 H) ?4 Y6 m, m, D( g}) Q2 y* ]$ y; c$ C
    步骤二:使用@Value注解引用配置文件的值' t. d9 d; ~/ b; b+ c

    - [) z  Z- b+ y6 ^: p( d8 h% d通过@Value引用上面配置文件中的值:
    $ z( S, x. S- ^0 m7 {. b# U3 }7 ^1 m6 N. H  v
    语法
    * O! ]$ |* [7 P# b# S+ ~( k9 m5 o
      s8 d$ [8 J/ u4 a6 V# w@Value("${配置文件中的key:默认值}")0 }" n8 M% G+ M$ f. _
    @Value("${配置文件中的key}")
    1 G7 M# a" r6 M, {如:4 [0 O; h: L. m+ A! S0 z3 x4 W

    . Q$ f* J* U+ {3 o$ ?@Value("${password:123}")& J. f' o  P- R
    上面如果password不存在,将123作为值* J5 P6 q- x+ A8 a
    ! k* |+ a# y3 ?# m: [0 W5 T
    @Value("${password}")
    ; [+ B. P) F& N; ]上面如果password不存在,值为${password}# [" K$ m: {: M, U. Z4 Y( U
    1 [  M& Y( E' ^  i, i, ?
    假如配置文件如下
    : j* g/ c; r+ V- v) I. ]
    6 ]$ F1 d; H1 t/ Q2 sjdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    8 Y+ ?5 ]% T2 h) x# E5 pjdbc.username=javacode
    ! X% c6 V+ u  pjdbc.password=javacode2 ?9 k" G3 F  w0 V
    使用方式如下:
    8 Y) s& A: R0 U5 y0 Y0 @  F- p
    # m8 A0 Z' {& b* O; x@Value("${jdbc.url}")1 q7 I3 h) h( S  n+ n4 C% [9 R' @
    private String url;( K( `5 ~& Q5 j3 E( f, F' ]

    % x0 ~8 z9 z. r@Value("${jdbc.username}")
      {. D2 s' J4 M& k9 F0 X4 u- Bprivate String username;
    $ C" v) d6 T- O2 I! g) N  y5 ]. y9 h: I; x+ `0 r( {
    @Value("${jdbc.password}")
    9 |) v* p3 g* b' c( t' p/ Rprivate String password;
    - }+ c3 t1 x! s& X' f. j下面来看案例
    5 r% Q1 ~0 @) |' a$ O9 [, k" C! n' J8 B; r
    案例
    0 C3 O1 v3 {8 T! o+ T, P# r( v7 K( M7 |+ n  U
    来个配置文件db.properties
    - b& a; u' n% V5 b
    & ?2 x2 |& N1 c1 M" l9 b4 Z  ejdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
      k7 `, Z& U1 K0 h; v$ g/ Bjdbc.username=javacode3 t% Y4 ~' Z4 a4 o3 |  X
    jdbc.password=javacode
    ) y9 ~1 b: H0 z+ H0 S2 L8 o来个配置类,使用@PropertySource引入上面的配置文件
    ( M% ^+ F  X1 {! c0 [$ _. ], x+ q! i! Y! m; s) }
    package com.javacode2018.lesson002.demo18.test1;
    - b5 W* D6 V% y) g4 y8 P& R: I' |6 C2 `9 X% H
    import org.springframework.beans.factory.annotation.Configurable;  X/ }9 i" ~; {) G" J9 h0 R$ l
    import org.springframework.context.annotation.ComponentScan;7 T9 P  p3 `$ F' o# R' _0 g
    import org.springframework.context.annotation.PropertySource;8 p  v  L' G# b" p

    & c$ n& `0 A. ?2 u) \@Configurable  P$ u1 P" z2 F" i% W* Q$ x2 Y/ k
    @ComponentScan) w* T  s4 g( R
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})' i* i% e* T# _' e4 W7 ?
    public class MainConfig1 {
    ; r- e8 d" e- J. k! I+ V$ P; w) F# y}# y  {7 u; s+ n* o4 j+ R
    来个类,使用@Value来使用配置文件中的信息4 w  h9 M8 a" h6 l

    " B' f7 u! k5 Zpackage com.javacode2018.lesson002.demo18.test1;% ?# r( H" B% W3 K" I3 A5 ~9 O
      a# r: f% ~2 L
    import org.springframework.beans.factory.annotation.Value;) o9 h* P4 P0 q. e6 V
    import org.springframework.stereotype.Component;
    ' ]4 h4 m# p# ^% M: }. x& _
    4 V3 E# r/ D: O6 n$ Y" V@Component/ T' w" n4 t3 C
    public class DbConfig {
    ( U" [3 Y4 R1 W6 R* S  E% s. w6 i0 Y% {, A+ }  E. M
        @Value("${jdbc.url}")
    6 R) t2 x9 y. D    private String url;& S) _1 ^& m8 G! ^- N3 O. A
    - h# R* a% g0 k1 \4 [
        @Value("${jdbc.username}")
    1 M# y# q. D2 Q, ~- J+ P    private String username;
    / P! J, z# o7 t( ~6 z$ X" M; ^
    5 v! Q- O# K8 f& G    @Value("${jdbc.password}")' V3 O( X3 H; y$ q: ~
        private String password;; e1 L$ K3 F, W  f0 \

    / \5 \4 x* S3 @$ _" L$ R( \    public String getUrl() {) n! ^, u" B. X, {; X3 Q
            return url;
    6 M3 y9 W, X# l( }5 t    }
    , v# d6 {( F0 e% y2 R
    9 i+ F6 G2 q* J- Q+ l1 ?- @    public void setUrl(String url) {
    , v8 _- a2 ], x$ B9 E        this.url = url;( B: ~- p. P+ G
        }
    ; u& g$ s; l9 m6 a
      G: y8 |9 F/ X! v. ^/ D. I5 W8 O% a    public String getUsername() {/ h1 l% y  @& A9 @7 D# y
            return username;1 }& s* a" |! _0 E
        }! U4 L: O; D5 u7 ]8 c. J' q
    : O4 b! F' I8 d! {; K
        public void setUsername(String username) {" z2 |7 k! @$ n- w. |# U+ P" {* Z
            this.username = username;) ]2 r+ C2 ~4 m4 Q
        }
      ~3 B& E4 a1 \+ P$ i; s+ ^4 |
    9 ]( ?( @3 g" D+ d- W. }# L. f    public String getPassword() {( ]! V  D2 g  w# E
            return password;0 J' C5 v, J: i& G9 B
        }
    % x) r/ e. L3 P* I! e, M: X/ k7 }7 L  N  x& k
        public void setPassword(String password) {
    ; [) Z2 {1 n) n9 `$ y6 S! u3 Y        this.password = password;
    / q' L; N" W9 ]/ T1 s6 Q! c    }
    # A! \3 C3 b( D" p  {/ @# ]( H2 x2 v& G/ C1 o
        @Override
    $ Q' r. }: Y. G/ _    public String toString() {4 U9 Z5 o9 V+ ~: L& G
            return "DbConfig{" +6 N9 F4 e/ R6 J9 P* M' R. e) e
                    "url='" + url + '\'' +
    ' s5 N" n( e9 W                ", username='" + username + '\'' +: h$ q$ f9 ~- J! W3 m3 M, A
                    ", password='" + password + '\'' +
    # @8 n* t! [, \6 J" g7 N/ N' v                '}';9 U7 e' B6 Y* Z9 F: k
        }
    1 N! K4 @$ K* M3 R" ]* I. ]2 |1 D}
    4 c! G4 H7 T) q- O9 I+ V上面重点在于注解@Value注解,注意@Value注解中的1 C9 V' z, E) I" P& ~; ]6 l- ]& L

    3 k9 j: b/ l4 ^) I# n- E2 g4 W来个测试用例
    : W" k( T" g( C# K; Z& ~& o
    3 d# A( V* _: c3 @$ y( U) E7 ypackage com.javacode2018.lesson002.demo18;
      a7 Q& U# g! b& O, |
    4 J/ R- T/ Y+ Eimport com.javacode2018.lesson002.demo18.test1.DbConfig;
    : I0 u$ n: q# H  J& ^& r% ^$ wimport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    # Z7 L2 X, i0 b, ]- B0 s" M' V( D$ jimport org.junit.Test;
    & i5 M8 x# A# u4 `import org.springframework.context.annotation.AnnotationConfigApplicationContext;/ V0 F( h1 D- q' p

    % p8 \7 R9 Z" X% Fpublic class ValueTest {2 f) q* f8 P$ k" E3 p

    * Z1 l" U; b. `6 Z    @Test
    5 g1 m  J6 C( G4 e. C  @! L' C    public void test1() {
    6 `0 i5 {$ U7 s: G( T' P. D+ D: \        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    & C# @! z: a5 I; `$ |( s        context.register(MainConfig1.class);
    6 u. I7 B( O: J+ a        context.refresh();; x3 w- K9 \1 T0 V' K1 j2 {7 ]
    * z& k3 q3 ?$ I0 i! t
            DbConfig dbConfig = context.getBean(DbConfig.class);4 P' p1 j9 Z; r2 C7 Q( {7 c& G# O
            System.out.println(dbConfig);5 b  y* u3 b- B
        }
    $ q0 Z  o5 K4 a8 n# ^4 A0 |0 k}
    3 j' W( v( ?0 T- |) S0 [运行输出# U3 Q  h/ y! w! e- v, M
    3 i, q- }- F6 s$ ^/ f. x" C; S0 u
    DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}  O% d& o8 i6 K5 r& P
    上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    % Q1 |0 j0 z' O" Y. i. t) u$ y8 H( y: h6 @- m2 K/ B& k
    @Value数据来源
    % c; o6 y) z$ {( l3 V& O4 ?5 ?  U+ H# _& W$ f, X' u1 K0 q
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    1 M( S- _$ j+ _' G$ c' V
    ! k# d* L- D1 t* V& D3 s我们需要先了解一下@Value中数据来源于spring的什么地方。
    ; A6 b6 ?/ M* ^2 `
    5 G7 f# m& ]8 m, Hspring中有个类
    " o8 ^0 s# F+ ~- ~
    % q# K1 L( _6 H  i: F: {* E3 torg.springframework.core.env.PropertySource
    $ y; E. W. _' i; h5 r- d: b* r7 A可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    3 s$ L+ @( P6 l* S* J& A
    / b1 `* y; W" M$ ~& p; S; x: u. ^内部有个方法:
    % \0 {% f! O$ p3 p& J- y7 q, X
    * u( b% W8 q# z$ dpublic abstract Object getProperty(String name);* }- a. p0 @$ V+ N5 ^+ b
    通过name获取对应的配置信息。/ Q& A! d1 O9 |# q4 D9 @" P, g7 z
    + }4 Y3 V) f8 y  w9 R: d
    系统有个比较重要的接口
    % u, C* |3 q/ c# C
    7 N+ Z  E" o+ B% x$ w8 M6 ?org.springframework.core.env.Environment
    " ]* W- ]* Q$ T8 u' |$ L# F用来表示环境配置信息,这个接口有几个方法比较重要
    / Q# n+ @" ?$ f4 L
    / d7 ?% m% }& R2 SString resolvePlaceholders(String text);( M. u& ?) B  C( H' D4 \# c
    MutablePropertySources getPropertySources();$ N# Q7 A0 e; t, S' \$ u% r
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。
    1 g# N( E- m- f+ A- U, i$ U/ c; m3 O0 K/ x+ q" {) M
    getPropertySources返回MutablePropertySources对象,来看一下这个类1 B  ?8 `+ z8 ^* t- k. a

    : p4 X/ S6 {' T& _$ M2 kpublic class MutablePropertySources implements PropertySources {
    : Z% L/ J+ H- ?! H
    6 ^5 F3 U) j2 s* ~  L% g  T: j    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
      H! u5 Y1 S* g; A  r
    $ ~' ~' D2 S. k$ P: w8 U" T}
    , |" u+ Q, w6 }6 ~* y内部包含一个propertySourceList列表。& Y3 [7 C, C* p. j% b( \
    , i5 h0 z+ \0 S% ^3 M$ Z$ z/ h
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。2 s0 ]! F# s+ \4 N5 U

    1 L: x. Y9 S, [% r: d& @' ?3 s5 n4 R大家可以捋一下,最终解析@Value的过程:
    3 R; G( {0 w; K  _4 j. H
    $ R  B( d! X8 j1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
      r( _* H$ o2 r1 q, N2. Environment内部会访问MutablePropertySources来解析( s: X' a) Z" g0 }( W6 ?
    3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值' Y! s( u. w8 i& O1 F
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。) T* s. A, y2 r- o/ ], \1 a, D

    1 D5 D0 @; U6 h5 g* s0 k8 E下面我们就按照这个思路来一个。" A: X( W, x/ ]. j3 D
    2 U: M6 w8 x5 e. Z5 c3 ~
    来个邮件配置信息类,内部使用@Value注入邮件配置信息
    - r  j9 K2 P: q" h- Q4 w% j" j1 [, E
    package com.javacode2018.lesson002.demo18.test2;
    9 g" z/ `1 @( G$ k) c6 ?
    3 E5 M  z. l, }7 nimport org.springframework.beans.factory.annotation.Value;6 g$ ?9 N; j7 ]/ F! I
    import org.springframework.stereotype.Component;
    ' a+ |* T; f7 R! C1 m3 y; w3 n: w$ o3 T
    /**: X9 p5 o( v- U/ P* K
    * 邮件配置信息
    - @) c/ K$ X4 U% m */
    6 K! T# u/ {% b/ w+ b3 V@Component$ {- X9 W4 F. i! s- }
    public class MailConfig {
    6 k+ r0 ?8 _0 d; i9 r5 i- S( i+ M; b$ B0 a. H+ h  H% Y; l
        @Value("${mail.host}")2 U. C9 C* D7 g' W' C9 X( Q5 j
        private String host;
    . X4 Q8 ]) M3 j* n. S% A( H1 L& P7 C' W" Z
        @Value("${mail.username}")" l6 q4 p! u. Y/ Q$ ^
        private String username;/ V' {4 F6 C: l% P: W$ x
    : g3 A: ]. S% ^* @' o
        @Value("${mail.password}")
    $ ?$ n! f  }, w9 L* Y7 n7 k7 u    private String password;' Q+ @9 ^& x4 g
    5 l$ y2 `$ d  K: _
        public String getHost() {
    2 ]  r2 h; T) V' O        return host;8 I7 D  [/ w- R! T5 U: j# G6 h+ X
        }
    ) b; X9 ~1 h6 T+ s- l5 k! B* E$ Q% H6 \5 J9 I- S1 m. l
        public void setHost(String host) {
    2 u- D6 x1 {( B) f+ j7 @, Y        this.host = host;  ^% ?6 H% C( Q% T2 B* ~; N5 d
        }
    2 D9 [: j. X- |; B, ]  p; Z- S# X
    . |- w8 X' h: D9 x    public String getUsername() {  Q. H! Q0 `$ ^" \. a. L/ }: G" d3 ~
            return username;
    9 z, D, X8 g* A+ V    }+ q2 M' G# [3 a5 z
    ( Z$ D0 \/ r  X$ O# a' I4 y3 _
        public void setUsername(String username) {
    & v) g8 q. O. P4 |, O' k        this.username = username;: v/ D! ?- j3 y+ U, D" @( V
        }4 S% Z" y  u9 a" Y- z0 @
    ' C( Z  s/ Q; X+ N
        public String getPassword() {
    # i+ S9 O/ o0 c# W        return password;
    , r* T0 N; p: l* E    }$ i( P) h, t5 D1 Z- H5 T8 _
    & U0 C- x/ i' m* w' @, S* H5 [, C* c
        public void setPassword(String password) {0 ~6 h( p" L+ }/ ?6 ?; P
            this.password = password;% q! }3 [1 ?6 F. Y0 Z2 j- i* o+ N
        }
    9 x8 b4 I) b: t: z  X
    : I9 I6 h0 o% s5 c7 [' j" `, u    @Override. Q/ b% ]1 `1 _# X' h( v
        public String toString() {
    , e0 N$ r% X& k2 Q        return "MailConfig{" +
    1 e" K- b% X& l7 |                "host='" + host + '\'' +4 M. a1 W2 d% `5 E  `* `" ]
                    ", username='" + username + '\'' +  d9 q9 t7 A7 d! X7 @, G6 t
                    ", password='" + password + '\'' +
    5 E# W$ }4 K: Z+ e3 f8 I3 H/ D0 D                '}';
    * R$ y& C: O6 N# ~/ t- {$ s    }
    3 m! H! N2 i: A7 R5 a8 I3 O}
    + @3 q) |5 r0 m6 M7 H再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中/ k% G" L! h# @9 [( K

    . D5 F% }3 R" M4 u  [' C1 z8 Ipackage com.javacode2018.lesson002.demo18.test2;8 Z  d, e0 F6 H2 v* d) t8 Y' j$ i' V
    - l6 q8 `7 K$ u, q
    import java.util.HashMap;
    4 W, O! ^3 N: k2 c" e* c- T4 g0 Ximport java.util.Map;/ L& j3 \% ^7 w, K% q& @

    * l: s/ b" O1 A. Epublic class DbUtil {9 t) E1 {2 ~; P% }! M( p' K
        /**
    ( ?, W3 s' _% G# y* g6 V     * 模拟从db中获取邮件配置信息* Z) I1 S! J, r% p4 v$ J  {
         *
    8 P. r7 s- ?7 l" u0 p     * @return
    , ^2 ^! L, K# Y1 x, o0 u     */
    6 V3 l. f5 D6 l- Q" o: u" \    public static Map<String, Object> getMailInfoFromDb() {
    ' X( V9 }7 Y- R% n: I7 M        Map<String, Object> result = new HashMap<>();
    7 c' _% G! V3 {- l: r3 J! C        result.put("mail.host", "smtp.qq.com");
    " t; C/ b# E% \1 j; _. }        result.put("mail.username", "路人");
    ! v1 H7 t  `: G* P& J9 F        result.put("mail.password", "123");1 ^$ p, A4 w% v8 T) s, O0 e  b
            return result;
    8 E% r6 B" ^. E& V    }
    0 w3 }+ ?4 T" g: c+ n}
    ; |- \; v% T/ w, w来个spring配置类$ b1 J/ u0 l# X

    2 p& o+ _1 P0 }package com.javacode2018.lesson002.demo18.test2;1 J6 S) |2 l; A1 J( C
    # W" k# Y! p: @. S  p: x( p8 \
    import org.springframework.context.annotation.ComponentScan;
    ! F, q( x+ j; R3 \+ Z+ limport org.springframework.context.annotation.Configuration;( r% D- z% P0 M% A) I  w

    / J2 R4 _" X, ]+ [0 K/ r! N9 S+ y& O@Configuration
    1 h6 \4 C4 \. I" v/ I# k# _- O@ComponentScan  }5 u- y/ x& A' ?9 {
    public class MainConfig2 {7 d. T, q0 k* Q- c# L! e& Y
    }, w( E& m  J! `3 q' z
    下面是重点代码
    9 m2 i1 [; P, P: |% `+ _# W: K& Y) T/ O5 J$ `& {4 c% h( t
    @Test
    , c" O9 R1 Z; c' y- D& |: E' Xpublic void test2() {
    / z) z3 _+ i# ?3 f5 g3 O    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();6 F+ o" S( b' W: I/ |* Y' W" g

    % p" L) K' @6 ?6 i) u: B    /*下面这段是关键 start*/
    ' V* ^8 F; a5 C* ?8 b+ [    //模拟从db中获取配置信息
    0 y$ j9 c8 I1 s" F    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();* q4 f6 x! U) ]& Y* M
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    ! U2 u9 }) G5 q( j+ e    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    + T$ I8 j& J" |) Y8 r0 }8 A    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    + N9 Q, h9 s; R2 [    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    " v$ e. o: o1 V5 d    /*上面这段是关键 end*/, E9 j5 b* @* v; Y
    ( b5 B& j) b- D" L, a! s! V( M9 C/ @( f9 }) s
        context.register(MainConfig2.class);
    & s, d3 p( K' q# r1 B0 `& N7 O* m3 R    context.refresh();
    3 o+ S7 ~/ |8 `' Y    MailConfig mailConfig = context.getBean(MailConfig.class);0 a. O2 Z0 H- E; H- C. E: {' F
        System.out.println(mailConfig);
    0 N- n3 {3 t/ D) F}
    ) ~  }0 o& I" a) D! L0 @# V/ }8 @注释比较详细,就不详细解释了。: S1 I* o1 Q0 j. T4 B
    ! r9 A9 B2 I' f. f8 @( l6 C
    直接运行,看效果- S$ c" E: K" Q' A, B5 o' i  K9 X

    0 j5 o& g$ {+ s8 k. Z3 c. CMailConfig{host='smtp.qq.com', username='路人', password='123'}
    9 V0 K$ j* Y1 l$ O0 e. f( e9 v有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。
    ) x2 C  e4 r8 y" A" b; S( ^' u; c) p2 m4 \0 J1 G9 {
    上面重点是下面这段代码,大家需要理解, w: N/ O/ Y% G& h& h# d
      J$ Z4 F, l* S5 f
    /*下面这段是关键 start*/
    $ f- f; K* ]8 Q//模拟从db中获取配置信息
    - g) |- z7 v/ i  r. x9 }( S1 IMap<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();7 q7 O0 T2 ~4 u3 h/ T
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)( c; }/ u- D+ D2 P1 U' P6 i
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);9 p+ _6 t4 I2 Z! `
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    $ V" v7 L5 L/ j- D/ O# E3 `context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
      n/ M8 P- H  o, g% z) r/ ?/*上面这段是关键 end*/  T$ H) z# _; T( L* O4 g2 m
    咱们继续看下一个问题8 q" K9 [8 J* q# g) J( ]

    % @7 \5 ~3 v% r% M  t0 ?% m1 \! T如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。+ |" b1 ~) b0 @
    , i* p3 Q1 i3 j! M; D2 j. V: b
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。1 Z7 @3 f! Z, i

    8 i4 R- J! X% G  y& Z实现@Value动态刷新
    1 @3 [+ z/ {* ^+ O7 E
    / `! G) H. E1 \1 D7 p( ?' @先了解一个知识点7 n6 N$ N5 L- p
    6 m/ R; h. M- T/ N4 d! N
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。4 b( b4 n* U' _2 f

    - ^  b& ]$ V" P) |- h, ?这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解6 X; e3 u# R# i( q  e* a- O4 T
    8 o! ^! s) u# h$ j( {* g: G+ ~5 K! f
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    * k0 S) F5 f" U% u/ X6 q% i; b! P5 C9 L
    " k' w& }# b6 [; O' aScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
    6 \: M4 ^2 M4 U1 \, D  @# V这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中
    6 o, b5 y( B! W4 V" |7 p: b
    7 l& t) `$ Y* k6 x$ Ipublic enum ScopedProxyMode {
    5 p1 N9 o  P- {4 {6 U    DEFAULT," W# Q' o1 m' z+ A7 R4 e, ~4 ^
        NO,, c  i: ~, G& f# }5 b
        INTERFACES,
    4 {$ r* w4 c( V3 ?$ b    TARGET_CLASS;
    . C$ A- E$ `0 s) ]}- k! y0 @5 D  ^; q: c
    前面3个,不讲了,直接讲最后一个值是干什么的。( Z; [+ K! p" K! H5 g. e9 Q

    5 e7 r0 t. Q  m* O) {当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。* a; q' c4 c8 p# }8 m
    5 Q- B  y/ W. ^0 l
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。2 q& C1 N/ u  P8 ?  S" s, ]* T

    + r" Z" b, S- T# t自定义一个bean作用域的注解
      e3 a: f2 J$ z; r' `. h
    % M8 i7 I6 p$ ^( O; Wpackage com.javacode2018.lesson002.demo18.test3;' a# I. P- G0 Z* z

    4 `) N+ h) z2 n8 ?. c2 Uimport org.springframework.context.annotation.Scope;6 a6 J# R7 J% U4 N& d0 N
    import org.springframework.context.annotation.ScopedProxyMode;
    6 W( ^: F0 y, [" [( l
    , S# C& B6 Y, x4 Y9 S* z7 Wimport java.lang.annotation.*;
    , F+ F/ D: y! o5 K9 d: `! T5 C
    ( o; Y$ x* {/ q0 w+ Q4 V@Target({ElementType.TYPE, ElementType.METHOD}), O; M0 N, M3 v( ]( W
    @Retention(RetentionPolicy.RUNTIME)
    + O/ q7 o- O$ K, S. Y@Documented
    3 J: {; s1 C1 d1 [4 e2 p@Scope(BeanMyScope.SCOPE_MY) //@1. F- ?5 Z. [0 v6 z7 ~
    public @interface MyScope {
    " t, n5 d& r" A6 k1 C8 y    /**
    4 Y( {! E; T! T     * @see Scope#proxyMode()
    0 U; t- e: J- ^     */, Y. ~- n+ T& @2 |0 c% f' B
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
    5 `8 L/ J3 M3 Y}% k  k4 ?, d. S& S( `6 f5 U
    @1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。$ s/ _* F) i/ Y0 x4 [: Z2 l/ N

    - X( J# Q( j; z# N% T& @@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS& `) a0 b5 T: b: g

    ! D8 H0 G! P* d- {4 k9 m@MyScope注解对应的Scope实现如下
    0 g8 g! b: A! n7 @/ F8 f. r7 f' b, I5 B/ v: y" H# q- q
    package com.javacode2018.lesson002.demo18.test3;. C. e' J) x0 Z5 v

    5 f) x, K! n& U( i% u! X  b+ wimport org.springframework.beans.factory.ObjectFactory;# Y5 m/ U* a( w0 T& P& U) s8 y
    import org.springframework.beans.factory.config.Scope;' R2 Q; ]( A2 x  {
    import org.springframework.lang.Nullable;
    % @/ N) B  \' i7 u& R7 n* B( T- f. C& g# v5 r- q6 k
    /**
    ( Q! ~( ?4 D! i- t$ A * @see MyScope 作用域的实现/ Z) Q. R) O* I- B# g+ }$ Z
    */; ~  `3 h# x1 z
    public class BeanMyScope implements Scope {& l" b, }- l1 O3 ?2 Z
    # P% A0 O+ C7 b" B2 J/ t% K) Z! @
        public static final String SCOPE_MY = "my"; //@1
    2 K. O3 N5 c; _/ L5 {; ^) U7 V
    9 s* h: S, {6 s" W; ]4 V7 J    @Override2 G0 w$ b8 a6 G* L  [! j7 \
        public Object get(String name, ObjectFactory<?> objectFactory) { 8 m0 y6 J/ ?- U" ~5 h$ D0 s
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    % H2 k* v; a+ |" _0 t3 m- g        return objectFactory.getObject(); //@39 W/ l: t: Y0 [7 B9 `, a
        }
    * i5 y2 r4 H& \9 @0 u0 m2 [3 B6 h$ E
        @Nullable. [" e0 u+ C% }5 K
        @Override
    5 e1 n! N/ ]4 {! I    public Object remove(String name) {6 A* k2 \! W( e7 d) e8 j: S
            return null;2 Z# k) ~9 y. Q& S' b( i
        }' N" S6 f5 B) n8 h# ~1 L0 H

    7 ^6 j1 E/ j6 O/ s9 e1 G    @Override* o. R6 W' }$ C2 d( H
        public void registerDestructionCallback(String name, Runnable callback) {
    - ~3 n! d) C8 ~5 O/ c) d& ?5 `8 d, [$ I4 z
        }' R6 }0 V2 V' B% d* G0 \

    6 I& u% Q. X5 S  J2 A- k+ f    @Nullable+ [5 [0 y8 Y1 ~# H
        @Override9 }; @/ C- ^5 q
        public Object resolveContextualObject(String key) {8 |% }# Q$ z  H) `% C8 C
            return null;
    & t; [+ {" g  P$ }% H0 B; p    }( N& J( V( ^2 _1 e0 G' s# ~( }; j

    : ^/ e  e/ O& E# W0 w: f    @Nullable
    . z5 L0 M1 E' t/ w3 K    @Override5 K  A) h) r" I# S" N
        public String getConversationId() {! G2 ?+ B! f$ D  }# B: T
            return null;% I$ s1 K# l; \7 w" f1 Q+ w$ L
        }9 i) K2 }: p4 Y2 }$ ^* ^2 @1 E3 g* w
    }
    2 b( C; x$ T7 q% `3 V+ {( L@1:定义了一个常量,作为作用域的值+ m/ d% B0 g: g/ i8 V; c2 C

    ( h, r9 p3 A  z@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果
    8 l( ^  d/ g5 ]: C
    & l7 ~  T: c4 V9 ^+ q, J) F@3:通过objectFactory.getObject()获取bean实例返回。
    1 h: v5 i6 B0 y$ L' ?  o3 p! Z) c+ Q& G* i1 A2 X& n( Y, \& I
    下面来创建个类,作用域为上面自定义的作用域8 Y) p! l& j% R( a- g6 l8 L

    8 i. j6 [, g  T' K' `package com.javacode2018.lesson002.demo18.test3;- q' M) `' c9 I( ]0 G+ q$ ^

    $ F8 F, C5 H0 m/ b$ ?( k: c4 pimport org.springframework.stereotype.Component;1 j5 \  s( g; _1 m. m3 o
    6 M6 ~! e" U! ^, v! I
    import java.util.UUID;! x$ Q1 U! O8 x9 f6 T: [

    3 d; s: f' w, G8 I, U@Component9 A" I0 f: f  x; V" O6 x2 p
    @MyScope //@1   T* o- P3 i9 j6 G3 c" b) c
    public class User {" V/ L+ o, _, |/ [2 @
    3 P% S1 m  S+ u# U0 }
        private String username;' @: z  D# m3 j/ C5 U1 U/ g

    ' f( K1 B* r, P( [    public User() {
    * d* R7 {" f; L, m2 w& q) p        System.out.println("---------创建User对象" + this); //@26 m2 h) z. Q' w1 H. d
            this.username = UUID.randomUUID().toString(); //@38 t$ A2 ~. @2 {2 W8 p3 h4 z; o
        }
    , o, Z: r( {/ k5 k: X" a, h$ t. {% a( t2 e6 Y& b
        public String getUsername() {" y. s# s# P0 d- y5 T; J/ _
            return username;, Y. }4 ]% e. p/ Y8 S7 a7 K2 K2 v
        }
      _% r: X1 |/ Q8 L1 r
    % H- F: F, N8 i) S. V8 C    public void setUsername(String username) {) l  m" O2 @7 z& H
            this.username = username;
    ( _0 P: r7 J- x# h) u4 Z    }
    4 _3 `+ z+ I. K8 C# l3 `6 a% N0 Z0 W1 E9 B; u
    }/ [  M! \  D# \) ^7 [' b
    @1:使用了自定义的作用域@MyScope/ \* @& z4 ]  l" R/ o; ]0 `
    4 _& k* p; ]# u+ I3 U
    @2:构造函数中输出一行日志
    : j1 t) l: v1 h9 b" i. W; N3 c3 J4 K' E- C0 D8 j& U
    @3:给username赋值,通过uuid随机生成了一个( s/ ]! R. c( T& {3 V  ^; |
    7 x3 w7 s2 a. f# ^. _
    来个spring配置类,加载上面@Compontent标注的组件
      j$ n3 ^6 r- G3 ?# K7 F1 X5 x
    4 k4 \( z; B( r/ ?  Tpackage com.javacode2018.lesson002.demo18.test3;* [: @" R9 W' `. V7 D" q
    ! n3 x1 Q! F6 P
    import org.springframework.context.annotation.ComponentScan;8 f7 ~# w! H$ o, X* G! J
    import org.springframework.context.annotation.Configuration;
    4 [& G  Q7 N6 d8 N+ g
    / V( C5 \' S) |* U: ^. Z) e@ComponentScan
    * X* Q" K+ |) Q3 x4 m  _+ T% |1 G@Configuration
    3 H" Z6 h/ c7 Y2 C1 }3 o9 w; W7 Kpublic class MainConfig3 {
      y- b8 Z/ l& p, Q: T3 M) v& T9 {, u}% W: a3 t- {; }8 O
    下面重点来了,测试用例
    ) n+ R  q9 M7 F
    5 Q0 O* @9 _* s) v' E1 }7 L@Test; Z: f1 v8 t5 K0 W! K/ T' l. [
    public void test3() throws InterruptedException {
    8 c. w6 V7 ~& e3 g) |  j6 a9 D    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();* J7 F0 `) X* p5 k# l
        //将自定义作用域注册到spring容器中
    1 A( \+ L7 Q' }- U( }4 Y5 C1 L    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1- d! f2 S( P7 N. Y- o" d& f
        context.register(MainConfig3.class);0 O2 q2 T, M4 S8 w# S
        context.refresh();
    " S5 O, Y0 Q* B
    9 B" y1 L2 {8 t: i2 r    System.out.println("从容器中获取User对象");' b7 z2 O6 @4 D, _" V3 H/ R/ K
        User user = context.getBean(User.class); //@2
    ) y$ N* Q; H8 x    System.out.println("user对象的class为:" + user.getClass()); //@3
    $ z# [  c0 K7 L9 p( O; e+ F0 F3 H1 a; B, e$ r  E$ j$ c7 o8 m
        System.out.println("多次调用user的getUsername感受一下效果\n");; G4 c4 {) K# x4 e
        for (int i = 1; i <= 3; i++) {
    * w/ d# g: I; O( {5 r        System.out.println(String.format("********\n第%d次开始调用getUsername", i));) O0 |) Y8 a8 J% [/ C
            System.out.println(user.getUsername());
    % _0 C% h  Y+ I- E* @7 o. V        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    ! K; B+ _' W( H7 x# g    }
    : u5 }, P% `: f7 m1 L, B$ O}' e  T9 v/ k3 E8 V+ i' z
    @1:将自定义作用域注册到spring容器中8 l/ E  i) Q- N; A% t5 S5 q; @

    ' S. F1 C! Z) X9 M; r@2:从容器中获取User对应的bean; v8 K- ^9 Q" ~; X# ~

    % G- l& q6 |7 C6 K7 H@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    7 Y& r& o1 W6 Z. i; d1 j+ c+ A! ~/ \8 v9 a! ~
    代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。
    1 k8 E) S8 A. H) t4 e9 E  j# ~8 I7 Y5 e
    见证奇迹的时候到了,运行输出
    2 A( \+ R% H/ s1 {0 _$ E& T" a& W; L8 ^. n
    从容器中获取User对象
    5 X$ H* \7 M8 ?: c: C2 iuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127: c2 l+ m+ O) ^% S5 J" ~% Y1 T6 ]
    多次调用user的getUsername感受一下效果9 k( w2 q7 p% Q, }
    % U& o& K, u2 c: Q" _
    ********( K% y3 Q" r1 e; P/ [# f
    第1次开始调用getUsername: k; {* I" u2 K3 u( q9 x: ~/ e
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    - q# A, Y3 J6 Q---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
    " [& ^* o9 E6 t- y* Z5 r  J4 N7b41aa80-7569-4072-9d40-ec9bfb92f4389 c' \: ]! x& @$ A; ?( w1 q
    第1次调用getUsername结束* Y' H4 v  B9 X$ F
    ********% `% f, y) n$ z: F

    , i5 K- e: E0 ^: Q) R% v$ H********
      j% P% X0 ^; z# u1 o: b6 a; |. F第2次开始调用getUsername
    9 B& n: ^. c! I1 }; HBeanMyScope >>>>>>>>> get:scopedTarget.user+ p  w9 j2 }8 G3 w
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
    $ e/ D7 c7 p! P* S8 Y1 Z01d67154-95f6-44bb-93ab-05a34abdf51f
    # V; Z! k& t& b; H. M# P* t  l& G" h第2次调用getUsername结束
    / M0 F- y, ~) n: P; S. f$ t********
    4 o" O2 ?, D/ d
    ! H: D, `) ~8 ~3 y8 n) c********6 Q/ u: W- g+ K: ^
    第3次开始调用getUsername6 ^2 i6 G1 F3 S& T# C( K/ @7 K
    BeanMyScope >>>>>>>>> get:scopedTarget.user* s: [6 `, a6 }
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15( k+ g) X$ A6 x; X0 ~4 z1 N
    76d0e86f-8331-4303-aac7-4acce0b258b85 G/ W. T8 m% s6 x
    第3次调用getUsername结束
      Q  t5 A) ~7 |********
    2 \( s& R9 B, F( q0 ~5 U' e( i从输出的前2行可以看出:  T/ D7 W; R/ K$ r- p" n
    4 k+ i! N  ^6 C- S
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象! F; w6 A$ \. w

    0 s$ R( Q' B3 D) D第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    8 R4 U5 F" j" T
    9 I' `4 ]; [1 g% _后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。
    # E! q% C$ |& Y; h- \
    - q3 v5 E% _, t0 \通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。/ ^. {( m, u0 n5 \; p/ \9 }

    ; s# z' Y/ n# d$ l5 y. O) ^2 z动态刷新@Value具体实现' h% D$ s% P* k# t

    8 G1 o, z" g" h那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。
    $ a% T$ i* E% \, J2 `" t8 ]) v& w; o; n2 v6 M/ ]
    先来自定义一个Scope:RefreshScope3 o0 o) h* k  h9 F6 ~& X

    2 |% ~' A5 M. r3 Lpackage com.javacode2018.lesson002.demo18.test4;  O5 Y4 J6 M- |4 `% V
    - u2 l% X1 J9 T% T
    import org.springframework.context.annotation.Scope;/ f* S  f0 F3 {7 L
    import org.springframework.context.annotation.ScopedProxyMode;- ?9 b# x' T  x9 _) p9 a; e; ?
    " S7 |; B* ?' z+ [: }
    import java.lang.annotation.*;
    ' s6 ]0 m9 P+ [- U; O9 |8 j* [% l: l5 e* q4 l: I
    @Target({ElementType.TYPE, ElementType.METHOD})
    6 O: f0 ]! i2 ^8 o# N$ k/ S@Retention(RetentionPolicy.RUNTIME)
    5 t+ V, W1 ~; I0 b8 x! t6 A7 h7 x@Scope(BeanRefreshScope.SCOPE_REFRESH)& U1 I* z; @5 H5 U
    @Documented
    4 E! i- @. Z" D. rpublic @interface RefreshScope {5 t: J5 v, s7 Y1 c3 C4 G
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    * ]+ a- z. Y7 R* J; F& e- k}. |  o, ~! W3 ~) h
    要求标注@RefreshScope注解的类支持动态刷新@Value的配置
    . d1 X( k# z% y
    ! n6 c, }; N* p) M& F: K- \% R@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS7 ]3 X# {  a! I

    ( _# O0 v* \; Y0 p$ i2 ?这个自定义Scope对应的解析类4 ?: I) _; D1 M2 l! v- @; ^

    6 {$ n8 `. Q3 R* s下面类中有几个无关的方法去掉了,可以忽略
    0 j! D' a$ ?0 ~  ?, A: n: h0 t! \5 I; _  h$ N
    package com.javacode2018.lesson002.demo18.test4;
    9 \  Y" _3 H0 z1 c5 `; J- B) X+ D0 D8 ?+ X( l- c) o- K0 v

    : y+ T6 u6 V9 n% f( [- H; V: t; Fimport org.springframework.beans.factory.ObjectFactory;
    ; I* B9 a+ O3 o; D+ Yimport org.springframework.beans.factory.config.Scope;3 A; e5 R8 u* Q) w% k- z
    import org.springframework.lang.Nullable;
    ' D7 H3 j* G; P
    $ L9 V" }4 ]0 ?8 l# \import java.util.concurrent.ConcurrentHashMap;6 A* y- a5 r- P3 M* n" p( G$ [) \
    , G2 k3 F) O* X- b7 v/ O; ?+ L
    public class BeanRefreshScope implements Scope {
    ( A" [& Q1 z+ t2 D9 `/ f) B' O/ Y5 |, K3 D$ |: `2 a' a0 l7 s+ S% b
        public static final String SCOPE_REFRESH = "refresh";3 L. L: r0 I. G0 c9 m1 x5 d8 A

    3 q9 p' P8 A1 K    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    2 R* K9 G6 V3 U2 f  O' t1 w9 v4 x8 D* G" ?1 U8 H
        //来个map用来缓存bean/ M/ n+ |, \6 K) g% @, L8 b6 U- m% W. i3 t
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1
    $ Z" P  i; H1 ^( E4 r" U
    ( Q$ ~) J( e8 R" O( _0 g# G    private BeanRefreshScope() {5 P4 t5 G5 R  f; f9 R  {
        }7 }4 X) {2 e/ Z! [
    ( v7 x* z6 Z6 S0 _6 w  {- x! |
        public static BeanRefreshScope getInstance() {3 e1 P, M8 k/ F0 Y1 L
            return INSTANCE;
    ) V/ a) x7 u6 p0 j  f7 [/ r    }' B; S/ Z2 N# C" f! {8 H

    8 h+ e( T- g2 A    /**5 _: F4 P0 z; c8 ?$ J) F6 a
         * 清理当前
    ) c/ h3 i; |* [& N# B( I. _7 F     */0 q3 ?3 u) z4 e9 O
        public static void clean() {( S- H1 j7 H0 h) R  x% Z7 q: ?0 x
            INSTANCE.beanMap.clear();
    1 T, N) C% c2 C0 k    }) P) ^) M) O, F, a& g! F4 G
    ; A2 f8 U/ n+ h' o. A3 n/ w9 y
        @Override
    9 p* E& M+ Z  [7 t# t    public Object get(String name, ObjectFactory<?> objectFactory) {) C* o) u! G2 ?$ l- W
            Object bean = beanMap.get(name);& N7 x, u% F; Q) e. B6 M6 W
            if (bean == null) {
    7 Y" a" G5 I2 w' w, y# i6 e            bean = objectFactory.getObject();
    4 x4 J8 I, ~$ ?, y# q            beanMap.put(name, bean);
    - [8 C, @6 m4 w& H7 {( R' l        }& j! h) r4 k: E0 N
            return bean;' S! m& _- L, G, G4 U
        }/ k' [3 O! h0 X; [& I

    0 t2 d; x/ l, F; ?6 b! U}
    + @9 v) @- v6 D' f# t% a# R上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中( Z& K7 \3 d  _

    , Y0 r4 j# ?( [8 R; O; \" r上面的clean方法用来清理beanMap中当前已缓存的所有bean0 q4 w& J, U" t6 O

    7 Y/ f( V  A& s, m来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope4 G2 N* P( W. n4 G( s/ \) @

    7 l2 P, P5 ~3 w2 t( dpackage com.javacode2018.lesson002.demo18.test4;" {: i4 N" L) F

    1 ?* Z1 E3 g/ Simport org.springframework.beans.factory.annotation.Value;, \5 G" P& e2 B+ i  g
    import org.springframework.stereotype.Component;
    $ {# |) U6 P7 q( L  C% a& c) |( s& s4 {' Q* r
    /**3 S& r' N$ V; g- ]" K3 c
    * 邮件配置信息6 \9 I2 v) g0 ~  g+ Q
    */+ a/ I. C' [( u$ Y& A# h. Y
    @Component; H4 P( ~( c; ]& P
    @RefreshScope //@1: A: m$ [3 s4 s4 B0 Y! t& Z. z
    public class MailConfig {& C$ ^2 z+ _9 f
    " A& d& |5 o- q
        @Value("${mail.username}") //@2/ B2 J3 r6 w& k; k; U
        private String username;
    3 b3 b7 K6 D  A( H: H1 _
    ' F2 i) }% I4 \5 v: e- |    public String getUsername() {6 q( U$ g1 S* I5 R
            return username;
    " |  _( K, k! T    }
    + k4 ]! y5 x  q+ ~. i0 G9 i) d" o( j# e! r8 y( L8 M
        public void setUsername(String username) {% H& A& t, ?0 e1 n
            this.username = username;
    , p4 |5 J1 z2 A3 @' ~9 B0 r    }
    , F# ^& C) O& D  q/ E, @7 i8 _. C+ o
        @Override
    : B* l' T% v# o* T' u) V- ^8 h* x    public String toString() {- z! f7 D# U. Q5 c6 a+ D
            return "MailConfig{" +
    ' t- J7 U% Z0 p9 j& \" J2 o; Q3 e# x                "username='" + username + '\'' +- b0 `  r) d, P, ^
                    '}';! H0 D& U1 u* Q
        }. {/ k1 Q4 ]) ^- i9 m
    }; b; Q, I; T: j( F" \
    @1:使用了自定义的作用域@RefreshScope
    2 A3 [3 }% X; P
    ! P$ h( T6 b0 S% v@2:通过@Value注入mail.username对一个的值
    5 A% U2 H3 D. e6 O9 u( n5 Z8 Q) m9 W$ i9 l$ H$ m. F: u) l
    重写了toString方法,一会测试时候可以看效果。
    # _$ `8 p+ r  Q- v
    6 v3 y! y  m3 N再来个普通的bean,内部会注入MailConfig
    % ~% y' @8 }# k6 y/ o
    4 [/ g1 U2 @3 O0 X  M1 Ipackage com.javacode2018.lesson002.demo18.test4;
    . U  {3 W# K4 y& w1 T' j5 m1 S/ b4 T1 f( g# c( h* q5 \
    import org.springframework.beans.factory.annotation.Autowired;
    ! C+ f; c* x$ ]6 Qimport org.springframework.stereotype.Component;6 _5 j3 U4 x* A
    : j5 l; p+ v) [" N/ n) B9 V2 p
    @Component* p, a0 W2 ]  `2 u
    public class MailService {7 }( U$ t6 M5 e" Q
        @Autowired
    # c3 D3 u5 ~$ u$ p5 k, Y6 X    private MailConfig mailConfig;. b6 b/ O- L0 ^3 \+ q. U
    5 Q$ m) Y/ H5 J! s9 {) G
        @Override6 [( ]4 q# [# N: @7 z
        public String toString() {7 P& f: g/ g3 v! ]
            return "MailService{" +
      v7 i9 t- K1 c& p2 p! {5 z4 @                "mailConfig=" + mailConfig +7 z) M  G) h  ~% _
                    '}';& P+ b# L: Z1 G4 ^' {
        }0 Z/ _/ l( a$ t* _. y5 n/ U
    }6 W! |* O# M/ V% O2 {
    代码比较简单,重写了toString方法,一会测试时候可以看效果。
    & N9 f3 R5 |# D
    0 W4 F- T2 Y) v- v* x7 ?& T% Y/ }9 N来个类,用来从db中获取邮件配置信息; A5 Z0 k+ w6 t' }: S4 F5 w" V

    , V# [3 J4 B  n. O) Apackage com.javacode2018.lesson002.demo18.test4;
    7 @1 ~; l# J- W$ T/ l- b, D. _1 `& h* j9 `# g6 o
    import java.util.HashMap;, |6 v# }' w( L3 Y! G" ~  k( L$ N
    import java.util.Map;
    1 f' @$ E4 ~% k8 R9 b5 rimport java.util.UUID;
    ; V1 e  ?# u  v& F
    . \1 U7 W2 H% t) V' Cpublic class DbUtil {
    , `. {; Y; @1 g: h. k( @0 @! I    /**2 F  o% d% n* C* t% z
         * 模拟从db中获取邮件配置信息
    1 F( I$ H- V: ~! Q( g8 o0 h     *. n) w+ J( I6 K: K5 W
         * @return1 e6 D8 \( _1 M, ?$ v. m( s
         */
    : y4 V1 {. _, [  [0 J3 _9 P; Q) {2 a4 y    public static Map<String, Object> getMailInfoFromDb() {
    ' g2 r) ^) e7 k$ d1 s        Map<String, Object> result = new HashMap<>();
    0 d! t. s/ g9 v% G        result.put("mail.username", UUID.randomUUID().toString());
    ) ~6 f: N: k  P/ X' K        return result;) z; A% a; v8 B+ H* K* G+ m
        }
    * m1 e& l1 f1 k0 x}
    & S' I2 F1 i7 n2 W% G来个spring配置类,扫描加载上面的组件
      a2 A1 W6 g% \
    * p% q$ C! ~$ ]# f7 npackage com.javacode2018.lesson002.demo18.test4;
    $ {+ l# O/ y" \- z$ V/ V+ e  X' u; `7 Y& y
    import org.springframework.context.annotation.ComponentScan;
    ; u% X1 a2 p; e% ^3 t3 himport org.springframework.context.annotation.Configuration;  J; Z- J, E. @0 |# d- A
    4 j: ~" f) x0 D
    @Configuration
    * }' M! s- v* H@ComponentScan
    + Z+ j# f2 u9 r2 ^, O9 m5 Q4 cpublic class MainConfig4 {
    * W& l' r1 x. R" O* f& I( ^! S}; s, K8 i6 `- F+ C, ^) Q
    来个工具类0 p7 y* l, b- l, h9 D" J- l9 o* K
    , V* K0 R% t6 P2 V! k, K( {
    内部有2个方法,如下:
    ! a; Z5 {8 M8 X, ?+ G* b& G' T7 H- U+ L- N4 @# a/ {
    package com.javacode2018.lesson002.demo18.test4;/ s; t) m3 O7 {4 L4 y

    , v! {3 a3 R  I  ?import org.springframework.context.support.AbstractApplicationContext;
    2 S" L' t2 O2 a* T* Aimport org.springframework.core.env.MapPropertySource;/ Y& Z  y3 W* r7 V
      e- G7 J, y) X4 O: O. t
    import java.util.Map;
    2 \: ~, W4 @7 ]5 O9 j3 [' a
    1 p: }8 b# F2 o0 e+ E6 c- C. Z$ Q0 ]public class RefreshConfigUtil {
    0 t. Q4 u( S: X, w2 Q7 d/ o' c    /**" \$ p% t, j  I4 g4 {
         * 模拟改变数据库中都配置信息0 q: k9 C- b( L/ u
         */
    & a8 A+ O8 \' k4 n* A1 h    public static void updateDbConfig(AbstractApplicationContext context) {
    9 }9 u) K/ z1 F$ r) T4 O1 l        //更新context中的mailPropertySource配置信息
    2 z6 K3 m3 d* C- `/ ^* @        refreshMailPropertySource(context);
    / L8 Q# F) W" q5 L6 B2 J  w+ {1 N- K. m' M9 r/ i' \
            //清空BeanRefreshScope中所有bean的缓存
    ) d3 I/ j" `" A' B7 Y0 s9 P        BeanRefreshScope.getInstance().clean();/ ?( `, T+ ?3 w6 p. ~. `
        }
    + d4 n) Z% A7 q# t( h" V# a( V5 c) O" v2 F1 L* x
        public static void refreshMailPropertySource(AbstractApplicationContext context) {# b6 f- G8 @& Z+ Z7 q# x  I$ i2 O
            Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();- I8 c! \( n' {* v# H
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)( _( Z0 I# \  |
            MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    2 W, N" D  ^* S* u        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    " V( g4 T: k4 Q) \' J! {- r+ I    }- P; D; q0 M6 ~

    6 `' {8 G0 J1 {  z+ Q}
    + ^& j3 ~+ n4 U: pupdateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    / ?- _7 y7 q% v$ I1 \0 t9 o- V8 V) Q
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。  u- _' L" e7 m2 {0 L" B& @1 H9 M4 o

    ; P) w6 J7 I  `2 K  v: O0 w; }9 B来个测试用例
    % X+ I: J2 t1 w/ `+ s& |9 U5 q; `
    ! R4 Q2 R. {, I7 q% W% G@Test: Q7 s) n* ?" Y7 G6 D- u
    public void test4() throws InterruptedException {
    $ w. W* P- L' V5 p" A2 f. ?    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();( e8 _% Q3 n/ r) ^
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    . ]! Z6 f3 l' `: f    context.register(MainConfig4.class);
    ' Y$ e" h& w) F7 L2 K0 _' R    //刷新mail的配置到Environment
    2 r% L) W; |6 q8 u; N    RefreshConfigUtil.refreshMailPropertySource(context);4 U5 [) L0 U- p5 v- C  n8 |
        context.refresh();5 v4 L  j1 X' f$ _7 _7 w

    , w- U  Y9 _1 c" J    MailService mailService = context.getBean(MailService.class);: s% h1 R6 A& \: N9 w
        System.out.println("配置未更新的情况下,输出3次");
    ( L: ^2 g6 W5 Y+ u% C' |4 }- d& ?8 d) A    for (int i = 0; i < 3; i++) { //@1  g6 m; y' H# ^2 {" m" y5 E5 E
            System.out.println(mailService);$ S7 G1 V* }; A2 ]& }* @
            TimeUnit.MILLISECONDS.sleep(200);
    $ g# |( \# P' w    }
    & d9 r( q9 I8 _2 v1 \# h8 P& |3 P2 O. [# t0 c
        System.out.println("模拟3次更新配置效果");* Q& Z1 b9 D0 f* E2 S" Z
        for (int i = 0; i < 3; i++) { //@2
    5 J3 K2 ~) }6 \( a$ ~4 c        RefreshConfigUtil.updateDbConfig(context); //@3
    ; Y2 u/ {6 X9 u% ?! A" ^' s+ _        System.out.println(mailService);2 e0 i( C9 V' h; P) @
            TimeUnit.MILLISECONDS.sleep(200);
    , ^2 n, D3 |  Q0 u    }5 ]9 {% P) \( Z( l; v3 ]
    }8 Z* K3 w5 y4 t6 o6 [- H
    @1:循环3次,输出mailService的信息6 W4 V; j& w8 P2 ?' n2 R

    / o( s. o# c+ Z@2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息! i5 X3 L& U" \6 C* }

    7 H& i4 a9 N; G7 w0 N" `2 X见证奇迹的时刻,来看效果
    $ W& W( V" y9 a1 b1 W- i. x  }3 [! a, @7 @
    配置未更新的情况下,输出3次' A6 T' h& x+ B4 R: q! U( b' z& ]
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    9 v$ J3 b- @% q2 O0 vMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}1 w9 G9 b" i$ w5 t* C& j9 t
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}% }6 |. T7 t+ B: N, i
    模拟3次更新配置效果' m8 r5 w% v( u3 J
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
    : f5 c' E/ O8 C8 D* c& v4 kMailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    , [, \! M6 ?4 H: ]. q- }+ CMailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    7 {5 G: q0 i; ]7 C! h: Q上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。" e% v/ r& M+ S
    , G( B3 k7 f& q- ]) \+ E9 f
    小结
    # m- j7 p" A! e- p2 K
    ' m1 h! Y& {. l( v; W# y0 A动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    * s" {7 a( a# a' K5 [4 b7 g, c) S' B3 _: [
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。
    0 E  }9 J% Q+ j  Q5 i/ H% _, e+ W; l( N% Q! a4 @
    总结6 P3 q2 \, \" H* h/ W6 g2 \8 C4 a  ^
    1 ?* a) Z) D# q; x7 d
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!. p- Y! E* y# `" f
    . S3 I  l1 W6 D6 T) R# T. E
    案例源码0 J1 a3 l! O9 u. F4 Y/ H
    * R2 H9 P9 B0 s
    https://gitee.com/javacode2018/spring-series
    2 J' J4 W3 z; T路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    2 b: P7 V( c/ B" C3 [6 \# }
    . {4 [3 s9 B! y- U4 lSpring系列
    : w) r; j' ?3 R( B- G! V, V& T
    + N- T2 n/ F; D7 P+ M# {) L! XSpring系列第1篇:为何要学spring?3 c. B( c2 C( c7 \2 H# T: h9 C. C: P
    ( v4 z/ Z- i5 n+ O% d$ Q% X2 B
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)4 c6 L4 g7 {5 L! C+ R1 m6 |. s6 i

    ( L# P" N8 o2 V5 z& B$ e8 [Spring系列第3篇:Spring容器基本使用及原理
    1 D, {/ d2 x3 `0 t* k4 _) G" x  q! V3 S( R3 x3 e2 D0 \) P/ k, k
    Spring系列第4篇:xml中bean定义详解(-)
    8 `# v4 v: q+ k' q+ Q/ [) m1 E$ A6 c* H0 m2 ]
    Spring系列第5篇:创建bean实例这些方式你们都知道?
    # s$ \9 J! i8 F! T5 C
    $ _9 \" x6 L0 D4 I6 ESpring系列第6篇:玩转bean scope,避免跳坑里!. [8 T8 B4 C( B7 f
    5 u- x$ s- C. T' A" {1 i
    Spring系列第7篇:依赖注入之手动注入5 A2 Y* ?. U7 ]* ?9 I' ]# r: I
    4 h3 O  w* g4 j$ ?
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
    6 Q& m" Y1 I- q1 ^7 @# M; o9 p- a: x# _" O+ b" n+ Y  c) Q$ H
    Spring系列第9篇:depend-on到底是干什么的?
    3 |4 v/ r9 B3 }% }: Q$ e4 ~1 U5 K4 Z+ ?8 A
    Spring系列第10篇:primary可以解决什么问题?' D& S0 A9 v& q  v9 f, n
    7 |. n6 \: Y: _7 x, @5 l& H
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?
    . v, z% A; F2 f4 A5 ~% O( Q
    + l! e$ g. [5 @" }. K1 K; ]Spring系列第12篇:lazy-init:bean延迟初始化
    . P* N6 S$ [7 h: x" M2 t, }/ J7 y, O1 p. c
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)& S  D- Q! T& C: V3 ]8 ~
    ( I( s3 E0 \7 v: B$ T3 Q
    Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?- k6 x7 Y% |5 u* U5 m
    ! c& s* S( w) U; C' a" W5 y* i  _
    Spring系列第15篇:代理详解(Java动态代理&cglib代理)?, j4 b! m' E' C2 |2 q5 n3 T

    + v9 I; ?: g7 ISpring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)
    6 C9 K! S, i4 P+ [3 W
    * C! p; {& M* tSpring系列第17篇:@Configration和@Bean注解详解(bean批量注册)
      w2 k! J6 y( n: r) h2 l4 T1 q: n' ]( g/ s
    Spring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)' j# t2 z1 `$ O

      x% N7 n6 S4 W9 R$ nSpring系列第18篇:@import详解(bean批量注册)! o. ~% E6 t2 x/ V5 _' Q

    7 z7 ]# B. n+ d( ^Spring系列第20篇:@Conditional通过条件来控制bean的注册
    9 ]" \1 Y! |; G4 l1 k2 O# [6 v1 x& X" M' j+ P& b+ V2 H$ v/ g2 m  t% R
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    . z. p, `% i' l* ~7 W& b  V7 A0 i' H" N: [  u1 N
    Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解! y7 t- ]: [! ]
    3 W, z5 @  Q0 F. O6 }0 F8 R
    Spring系列第23篇:Bean生命周期详解
    9 B/ F$ [) V  l2 R1 s# |
    3 A/ U, f6 w7 JSpring系列第24篇:父子容器详解
    5 J% `4 o) s8 M1 a8 ?) G! n
    / Z5 Q) w+ `- Y! n& A% ^& H更多好文章9 X/ F( L2 S! `! N: H9 H; f5 a
    ) d1 Y0 ?+ C- w8 p7 S
    Java高并发系列(共34篇): B+ S( ?8 C! q! d, U1 W
    # {# d$ n9 _& p% V) e' \
    MySql高手系列(共27篇)3 H) A1 }( W4 g% I5 J- ?0 K
    2 q' W$ X2 ]7 i2 x7 B7 O
    Maven高手系列(共10篇)
    $ F$ U4 |! r, Y2 a4 [
    4 \7 y( Z& B+ r0 \3 }Mybatis系列(共12篇)
    & l9 `# L- i* R* m! Y, |, l% R
    聊聊db和缓存一致性常见的实现方式, E' t9 @% Y) }, S
    8 P% i( {- |8 e; t& E' R% P
    接口幂等性这么重要,它是什么?怎么实现?
    ( [- H, G2 K: |7 H7 C6 h$ d! {
    : l* O8 i( [; i+ w7 i' Q# s泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!: |- @, j% X" u, Z, g; C# S- v( U. f
    ————————————————( g: q6 K. Q; T9 u1 C4 y0 h( o
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。3 t0 m- A1 e, E( s: o. C1 g
    原文链接:https://blog.csdn.net/likun557/article/details/105648757, f6 Y4 V: \$ p- f  D$ ]7 S+ |

    0 s5 f. N0 V( ?" x" _( P/ t' n
    * K8 h& Z9 F- x7 R+ `; ?
    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-10 07:08 , Processed in 1.509058 second(s), 51 queries .

    回顶部