QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 5110|回复: 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!
    , u& a0 c9 @1 V( l3 I, y# n6 V疫情期间找工作确实有点难度,想拿到满意的薪资,确实要点实力啊!+ g) U1 O! F, f

    ! x. k2 r9 O: z4 U, @7 a面试官:Spring中的@Value用过么,介绍一下4 o2 i" M& \2 g8 h5 ~( t; j4 B* E

    " p# ^! t2 E, D$ r+ {我:@Value可以标注在字段上面,可以将外部配置文件中的数据,比如可以将数据库的一些配置信息放在配置文件中,然后通过@Value的方式将其注入到bean的一些字段中
    4 F/ X) s7 T* G( ~/ B- ~! {+ h; G' \. ?0 u4 H0 V
    面试官:那就是说@Value的数据来源于配置文件了?
    5 O* I& _! Z% A6 c* z8 s) @6 Q( x! u2 }1 b: c+ D9 B1 ^6 n
    我:嗯,我们项目最常用更多就是通过@Value来引用Properties文件中的配置
    % {* f. r8 r! d1 o; @/ X
    ' [) n: [, X3 _7 J; ?面试官:@Value数据来源还有其他方式么?$ I: |" c1 L8 S: m& e

    / k- r  E. O9 P% }4 H2 t6 Y我:此时我异常开心,刚好问的我都研究过,我说:当然有,可以将配置信息放在db或者其他存储介质中,容器启动的时候,可以将这些信息加载到Environment中,@Value中应用的值最终是通过Environment来解析的,所以只需要扩展一下Environment就可以实现了。. T- D1 s9 ~  m7 O# X% C6 D8 c  {
    % v1 g8 E- A+ r" w0 k8 J% Q5 l
    面试官:不错嘛,看来你对spring研究的还是可以,是不是喜欢研究spring源码?
    / j# |' H  y2 Q0 f7 x! e* m' r  k* t7 v, {  t
    我:笑着说,嗯,平时有空的时候确实喜欢捣鼓捣鼓源码,感觉自己对spring了解的还可以,不能算精通,也算是半精通吧( B/ j$ G0 y$ z/ Z1 h5 L
    # C% @6 ~( N3 p  F4 c+ m' l
    面试官:看着我笑了笑,那@Value的注入的值可以动态刷新么?
    1 s- n' x. J" T" W4 ~5 F% |; _2 a. ]. A
    我:应该可以吧,我记得springboot中有个@RefreshScope注解就可以实现你说的这个功能
    * u  @, z  _5 m3 n+ ~4 ]
    ) P7 M; ^' W- h1 N% ~+ j面试官:那你可以说一下@RefreshScope是如何实现的么,可以大概介绍一下?
      e6 e: c' B! U2 z/ s
    4 F% v+ F$ V( ~& E$ S1 [5 v/ }9 c, c我:嗯。。。这个之前看过一点,不过没有看懂
    7 G6 L. m4 h, |$ L' [
    + Q1 Z4 G' @6 M5 k/ \面试官:没关系,你可以回去了再研究一下;你期望工资多少?
    - C9 V( u: D  i& J7 y# F9 D' P" i9 y. U
    我:3万吧4 s4 V( P( \0 L& S+ g+ J
    ! N+ b. B5 m7 _7 N
    面试官:今天的面试还算是可以的,不过如果@RefreshScope能回答上来就更好了,这块是个加分项,不过也确实有点难度,2.5万如何?* R; Z  |+ e' x7 W* x8 Z
    # h, i, A8 w& y& N0 |% m) t9 a' {& G
    我:(心中默默想了想:2.5万,就是一个问题没有回答好,砍了5000,有点狠啊,我要回去再研究研究,3万肯定是没问题的),我说:最低2.9万
    $ C  n, c1 `- S- j4 R, J2 v5 v
    ) t5 w' j/ I2 f+ q( V面试官:那谢谢你,今天面试就到这里,出门右拐,不送!  F. V) r. E, K
    # \0 g! L2 s5 l1 c$ e
    我有个好习惯,每次面试回去之后,都会进行复盘,把没有搞定的问题一定要想办法搞定,这样才不虚。" q9 C: ]1 j1 f1 X; D; ~/ F
    ) e9 P& j+ u3 w* {4 v! i
    这次面试问题如下2 Y% `; p" f  e$ r2 L* B$ |" u

    & \, ^/ ?  [0 Z% E+ v( I  k@Value的用法
    ! N9 k7 [+ S, _+ y7 l5 m+ u+ G2 `5 ]; J$ V) T
    @Value数据来源; R% T- L- V4 Y- n

    $ t& x/ T% g, D/ [" }; e@Value动态刷新的问题& r% ]2 }, l2 G" Y9 F
    ; O, Q0 o3 ~; I; P  J9 _& h# f3 _
    下面我们一个个来整理一下,将这几个问题搞定,助大家在疫情期间面试能够过关斩将,拿高薪。
    4 x$ I1 I* t, _+ z0 t+ V9 O
    # |' n! z6 u  N( x6 W@Value的用法1 L4 H" G  k" s4 G9 M& N6 I0 j

    & e$ l/ q( c* n+ D系统中需要连接db,连接db有很多配置信息。4 K; \  E9 w2 m$ v. A1 S. |; o

    8 G+ G# P6 ?( y系统中需要发送邮件,发送邮件需要配置邮件服务器的信息。
    + g% k  F# u1 _$ X2 I1 G8 R/ n3 [
    3 }, g0 O/ l( t3 [0 w* @  n, Z+ y" m还有其他的一些配置信息。
    7 p( r/ f0 r8 z2 H  {* ~
    $ W. R( {! v8 p0 }我们可以将这些配置信息统一放在一个配置文件中,上线的时候由运维统一修改。9 J! ~2 x+ w; j0 |) ?

    % X4 j& t& v% o- ]那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
    . q% X( t" [! n( l, E. b5 K/ O2 N  I6 Q" |
    通常我们会将配置信息以key=value的形式存储在properties配置文件中。
      R. J5 f) W; {6 C) I/ i" [0 R/ d3 b. @
    通过@Value("${配置文件中的key}")来引用指定的key对应的value。
    7 S1 t. I# c1 z+ i8 Q; g, O; W" t
    : e  N5 w5 u- G7 u- ^# G# a@Value使用步骤
    5 d- @  ~4 f) h0 o+ ], Q# ^/ m- G( ]$ _  D3 Y) }% G
    步骤一:使用@PropertySource注解引入配置文件
    : g+ c0 D( n. c  U$ G2 a& e3 [5 C; t0 z! {) D( ~% {1 m8 ]
    将@PropertySource放在类上面,如下
    6 ?7 q+ F) \$ n& l4 H$ ~) W8 @( e' W  |) h2 L
    @PropertySource({"配置文件路径1","配置文件路径2"...})
    ( n5 w( X+ d  B2 }3 B) Y" [@PropertySource注解有个value属性,字符串数组类型,可以用来指定多个配置文件的路径。
    ! g6 `+ _6 c2 [( ^& h  S5 L" ]5 b/ b! Q/ Q1 g: s* J
    如:3 T5 C; L3 z7 G' b$ S7 V% n7 Z

    " {9 z- J. l9 E@Component! _) d% U6 [: ^) t3 T6 ?% B' }$ y
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})5 [- L4 P5 X% N& I+ y: `$ G1 S7 {
    public class DbConfig {
    : h- t$ ]1 O1 E' t6 i, |}
      G6 G  E! K: d1 L# m7 k  f8 N步骤二:使用@Value注解引用配置文件的值
    5 s. }) Y# f& @7 @1 N) d; K8 D
    ; q& x% Y2 o' N+ r) V3 @通过@Value引用上面配置文件中的值:
    - X" q  Z" Y! O3 r+ N- Z5 W7 H0 t, u( T
    语法
    : `" m$ V7 L6 A8 W8 @5 a. ^3 ]
    * I$ [- l" i; L@Value("${配置文件中的key:默认值}")6 F4 m% t' p/ Y7 Y5 {) D- Q. p
    @Value("${配置文件中的key}")
    1 |( i' r. |. X6 D0 u如:
    9 n+ e4 n2 j9 i& b5 E& G, X# M
    ( Q$ ~) d# S1 G( a/ B5 W@Value("${password:123}")( s4 F: E0 p" G! t2 V+ c
    上面如果password不存在,将123作为值9 A" ~( s0 n$ @$ e) i0 g
    * z$ i6 S9 l8 s/ ?! j3 B
    @Value("${password}")
    - d8 [7 {/ }/ m& l4 K上面如果password不存在,值为${password}3 ~# J$ f' n% s' a" N

    . R2 T( U7 f; [5 r" v# K假如配置文件如下
    0 ^" \( H: E) L  R/ |
    , r( f4 `: H7 o) ^2 x- h0 n! Ojdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-81 l. }1 T0 l! p% K5 A7 i8 G
    jdbc.username=javacode. n9 }2 B" q6 c3 @+ T  q  p
    jdbc.password=javacode
    7 B/ `, b. J  W9 m1 }0 p$ Q6 C使用方式如下:
    / a8 g! r# A( B8 @7 `2 r( Y' Z9 F  ~1 K+ G
    @Value("${jdbc.url}")
    " B) y% _: E: ?  K) K, ~/ rprivate String url;
    / B% K8 b7 p2 @! _! [# J* k+ |7 N" S7 l( Y0 |9 O
    @Value("${jdbc.username}")
    1 S; b0 Q, ^  P! H, vprivate String username;6 i% M7 o4 D  U' [$ B4 `
    1 A  J9 _2 n* s3 U6 t
    @Value("${jdbc.password}")8 O1 ^( S% {( |, k' _! w4 u7 R0 a
    private String password;: ]8 o) O8 @" f8 S( p2 `1 S
    下面来看案例  c/ a" m: o- n- j" ]5 F

    & h7 I' T' H/ s$ c案例) B% k' R% J  c! |

    ' Y$ t; V! ~/ {! W: C' R来个配置文件db.properties( B6 J% e' K* u! q1 S

    ( U7 B% Y9 p7 |jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
    & s& ?; s0 h6 t4 y' u: K& I7 C' Ajdbc.username=javacode
    $ E  e5 ~; N7 d% g5 H" hjdbc.password=javacode4 w% Y/ z, X: H6 a  H
    来个配置类,使用@PropertySource引入上面的配置文件
    ( x( ]! G9 u. y9 X
    9 {: Q* a# w& z% Z5 V" kpackage com.javacode2018.lesson002.demo18.test1;
    " _! Q6 u) A. D. M4 y, Y+ W5 o. X4 Q( x$ \, T0 x# a
    import org.springframework.beans.factory.annotation.Configurable;
    & Q: B& W  ~  Z4 k9 e8 i% @import org.springframework.context.annotation.ComponentScan;2 n$ p0 h1 b, g1 C
    import org.springframework.context.annotation.PropertySource;, v  d/ A) q1 f# r
    - S9 M3 [9 v1 ]! T9 f8 _) t- |% V
    @Configurable& T) C6 M2 E9 ^7 T( r
    @ComponentScan: c$ _" v) d# R& W8 X5 Y
    @PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
    # V+ M; U, r0 spublic class MainConfig1 {( v2 G1 O' r* z0 q# x
    }4 ^  p- G7 [5 n/ ^1 s7 b- R7 [) G: L
    来个类,使用@Value来使用配置文件中的信息3 B- M% m4 k* a

    $ n* k- f$ ?. g4 N: x  kpackage com.javacode2018.lesson002.demo18.test1;/ j* O7 m$ Y  x

    " Q. w) r0 i, _% U+ h6 gimport org.springframework.beans.factory.annotation.Value;
    / h6 r3 V3 ^6 Z% l: r  l/ {import org.springframework.stereotype.Component;8 G# l$ u4 T. f' Q' G5 Y. L7 U

    2 h: _/ O2 q, u8 A. x( P@Component
    ( X, N! |* y5 V6 {% V. ^$ }public class DbConfig {
    ! |, P( l  Q) Q! B" i
    , H6 Z/ K, O. p1 |2 V: b    @Value("${jdbc.url}")  c! P, x! f1 P0 |. Q4 n
        private String url;" t" I2 E7 V( Q/ C: L; ^. c
    4 a! m6 S# a7 i* B/ e* M
        @Value("${jdbc.username}")
    0 B5 }2 [' _9 e3 T/ a: _  e! v8 b    private String username;) ?  y! _1 Y# v' x# Z
    & k% c2 j9 h/ m, x3 y1 c8 ]
        @Value("${jdbc.password}")  X1 K( C' ~. ^+ {- h- i
        private String password;
    / q" M5 t' w9 L! f& X1 I) w7 O* m. I$ p7 y7 ~( s- e
        public String getUrl() {8 t" w6 e9 M) }; u4 o- E+ l8 W
            return url;
    0 W8 u( u2 ]* s1 H( O5 r5 D    }
    7 k) r. B3 Z  b  l# C- l7 G
    " B/ u+ i% h7 v" c' c6 N" n    public void setUrl(String url) {
    3 n2 }& d; Y1 ?0 H, s, b        this.url = url;2 Q" c8 g$ n6 \9 c3 w, l
        }6 H2 |! J+ J) J% I. S1 }0 \

    % k1 r2 l: J2 h  J& @, ~    public String getUsername() {
    9 D$ W& b: B5 O! B0 Z" B        return username;; V3 d( k$ j, M4 L
        }; Z2 y/ I, K: U3 k- r: Y1 A. c
    0 A/ {" z7 {: W& ]/ t% A
        public void setUsername(String username) {8 T1 {. M, j$ [" y/ u
            this.username = username;: i* A# E7 G/ G3 F9 d
        }" J; j& k, I; Y* X

    9 K5 y% G  k4 V  _    public String getPassword() {
    $ l4 P$ F# f, Y8 L3 ?        return password;
    9 R7 [' \" [+ I$ T% ^  `) o    }1 u! U6 P( T7 B: \+ ^* v% K
    % F# _/ x. w! ^: K1 r
        public void setPassword(String password) {
    / e9 U4 a! ?7 I0 s, q        this.password = password;
    ! G5 i' P2 l, {0 y6 }& h    }" m2 D' W9 t3 q. n" G+ n# H

    + {# H$ s$ E+ s1 p6 c1 f+ u    @Override% i0 _1 i' q  `
        public String toString() {
    3 V" W' r9 L9 d        return "DbConfig{" +
    ) [; m) a: @% E9 q                "url='" + url + '\'' +
    / S* Y8 I, N+ N& Q6 l, ?                ", username='" + username + '\'' +
    * d% s0 p2 H2 a5 f  o* W# d                ", password='" + password + '\'' +
    : G/ \7 c0 n4 @8 P                '}';
    , l5 \) M$ i& [: d/ e! e4 r    }9 P7 B9 |/ ^2 i) Y
    }
    & H% s* M+ o" a0 s9 q上面重点在于注解@Value注解,注意@Value注解中的& w) E/ L& }' s+ X7 p' U
    3 X0 Z0 u/ V4 j, n* W
    来个测试用例
    ( a6 |6 f: p$ y9 K& z
      Z$ m, X. W7 x: p; O# bpackage com.javacode2018.lesson002.demo18;
    " P& e6 k# [, k; f+ K. C) l: c8 j- E9 }& s# X, A/ q& B& [, G
    import com.javacode2018.lesson002.demo18.test1.DbConfig;
    + M# b7 r! i7 H$ j8 Ximport com.javacode2018.lesson002.demo18.test1.MainConfig1;
    ' Y% M# B& A$ b* S5 |import org.junit.Test;8 U! a! `. k+ E3 u; f
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;9 z* |  V1 k" \! Z

    1 v- j% @  n4 W0 t- Jpublic class ValueTest {
    + Y1 B6 f  U% U. n+ y
    & e7 S8 B' a* \0 e( x/ p; D2 I$ C    @Test
    ; X7 X9 W0 h! H    public void test1() {- r" u! g0 v7 q8 r
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();+ t2 s& p; {1 _- a2 j7 X
            context.register(MainConfig1.class);
    9 x9 Q! b) T# h- v' Q/ O3 t        context.refresh();
    $ M$ t# c* W2 ]  ~( F5 B9 [" ^0 Y0 M" }/ U$ D# V. u6 y
            DbConfig dbConfig = context.getBean(DbConfig.class);
    1 w" O" ~+ F; W3 E) ^. j$ Z        System.out.println(dbConfig);# p0 h; B/ I/ @3 I
        }: ]6 ^( C5 D* r: w5 E1 Z8 L
    }. u7 n( t, s0 p* l! I0 W! r. s
    运行输出  p5 O5 @2 z6 i

    # U& ^( t% T3 Y2 v6 k2 \" XDbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}
    ( t5 _- C0 b# B, W1 \/ \上面用起来比较简单,很多用过的人看一眼就懂了,这也是第一个问题,多数人都是ok的,下面来看@Value中数据来源除了配置文件的方式,是否还有其他方式。
    0 X1 n& v/ [- h3 ]* c/ z  X
    / @# l" i; y  }  ?@Value数据来源3 C+ M; {- d2 x% [  r6 t
    - s$ a$ F7 }8 g6 S+ K* L, ^" s
    通常情况下我们@Value的数据来源于配置文件,不过,还可以用其他方式,比如我们可以将配置文件的内容放在数据库,这样修改起来更容易一些。
    4 P* X. s' C5 x* F
      [2 ^& R; u# H我们需要先了解一下@Value中数据来源于spring的什么地方。% g- E  y  Z) {. y

    6 P) V. G6 b6 w5 s2 pspring中有个类6 g4 W( i9 A& j) x- X
    " @( O' e. Z5 U# O$ r  G
    org.springframework.core.env.PropertySource
    2 m1 L( K: M' C9 r9 K" U- V+ o可以将其理解为一个配置源,里面包含了key->value的配置信息,可以通过这个类中提供的方法获取key对应的value信息
    1 x  R) P# Z0 d3 h# t) n& v8 w
    ) k0 u( {/ R, n) i内部有个方法:5 w8 I$ r$ S! f2 T' z  G

    ( v/ `- u6 _2 T2 t# Tpublic abstract Object getProperty(String name);
    6 q7 v7 i6 {! H通过name获取对应的配置信息。
    6 U# P* E2 ~9 ?
    , d7 A7 X& w5 d8 A5 E4 o2 k. s/ @* x# }系统有个比较重要的接口
    6 W) e5 D+ z* P+ G& X% _
    ! f0 n( w: z+ o7 e7 S: @: ?org.springframework.core.env.Environment3 o( E+ [+ H5 T0 J' K
    用来表示环境配置信息,这个接口有几个方法比较重要
    # y8 b, B+ \% O. b! g: W  I- ]! s  @5 G  v
    String resolvePlaceholders(String text);
    " U# R$ _4 y2 cMutablePropertySources getPropertySources();8 d- B8 s( c8 P/ ^! }. z& O
    resolvePlaceholders用来解析${text}的,@Value注解最后就是调用这个方法来解析的。+ ]+ h8 }; ~. E1 t$ F! R
    $ F/ j, e' k$ z- E& W/ v% t/ Y
    getPropertySources返回MutablePropertySources对象,来看一下这个类
    2 m2 B' _9 P% d6 Y: I2 \2 d$ N
    0 S4 ?3 B+ E6 W8 L- V# W# V8 k2 }public class MutablePropertySources implements PropertySources {
    5 s/ R1 ?% N5 Q
    . i" I3 L' z9 {3 }1 X4 l" g3 F    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    $ H( b! \, u1 |
    . h4 S! I+ {; h& M4 a9 o3 W}
    : F4 g2 l7 _* Q/ d# J内部包含一个propertySourceList列表。0 s" ^" C3 q' J, S
      q6 V# i9 D0 p  ^' w2 ^& z
    spring容器中会有一个Environment对象,最后会调用这个对象的resolvePlaceholders方法解析@Value。
    + C' R+ P1 E9 D* r% b  l! p+ @
    % G6 @8 j2 }# _8 m* I% Z  F大家可以捋一下,最终解析@Value的过程:
    ; `3 u6 G3 w% S) D7 `6 k1 D7 t1 u9 M
    8 |+ B, d( z& k7 [# e0 f2 v3 j7 w3 f1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
    9 Z) U7 e7 ?# D6 q  r4 w2 ]2. Environment内部会访问MutablePropertySources来解析
    4 \, Y9 U! ^9 `( @/ O; `' d3 i3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值& @/ [& b. J* I, P- f
    通过上面过程,如果我们想改变@Value数据的来源,只需要将配置信息包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。$ h2 j! ~  ?. \* [% k

    / C+ m* m" R+ i2 u  Q) L4 h5 U下面我们就按照这个思路来一个。+ B: k$ O9 W! r# C; V: @3 ~

    - r. r0 Y4 o. Y0 K; N5 W来个邮件配置信息类,内部使用@Value注入邮件配置信息
    5 |3 \& C! h# R" J+ ~% _# T$ H' |4 J
    package com.javacode2018.lesson002.demo18.test2;
    % k1 S4 J7 W# a. S) l1 ~( s2 \' C9 C' l. _  H9 M
    import org.springframework.beans.factory.annotation.Value;' V7 a8 D1 w5 B" x+ s2 @
    import org.springframework.stereotype.Component;
    / [& E) i0 ?; O/ T. k
    : a. l* \* p% j. d& i6 y/**
    " D! w% I- i/ @" v' |+ {0 Z * 邮件配置信息
    ; _8 G- |7 Q! @6 Q; }2 j; Y$ w  L  |4 v */
    8 c* l- W2 N" P  A$ z@Component
    * K' m; K9 R# ?public class MailConfig {" q- M' m$ d( d& j0 w
      H' @4 C3 H# p$ H) ?
        @Value("${mail.host}")5 l3 v9 C% v2 R$ r% A3 A" l5 y0 ?
        private String host;
    & Z4 A# V  x/ p9 t5 r  I
    % |$ Q# r, ^) Q1 u- ]1 V    @Value("${mail.username}")1 P* @9 L6 B( y
        private String username;
    1 {& K" D! L8 F$ R6 L' y/ y* S8 ?+ u4 @  z; y, k
        @Value("${mail.password}")1 Y" L7 |. N, O7 W9 V
        private String password;
    9 x% u3 U& A# L( ]4 f& F/ u2 a& [, g" G+ ]  \8 a
        public String getHost() {. X" G" l$ _* L6 n1 R1 O
            return host;  N/ F# M) H7 ?
        }" c' j8 a* C+ {$ Y+ H

    , p( F9 q+ q8 C    public void setHost(String host) {
    $ E3 N% P5 v" V& N        this.host = host;
    9 w- \0 o* d$ @! O& ~  M    }
    ! }  Z1 q- W6 G  T
    9 ^. L) R! |9 S. v) [    public String getUsername() {0 s  I2 V% y5 `* l
            return username;& p& [9 m6 F. f+ V# y! [1 z
        }
    5 s1 [! Z9 b7 Q4 T. @, D1 w5 R$ ^! T2 h1 j8 N$ o
        public void setUsername(String username) {
      P4 U, c- ?/ K) C        this.username = username;
    % X8 B- V) l: w- u7 V& B    }  Q8 N' w6 @8 I8 G1 D7 b' n

    % G! N+ l* x0 K/ v3 {: V    public String getPassword() {; @+ b1 z% V; h
            return password;2 k1 N, r, r: ?
        }
    % f3 J( L4 M& M( O" Z4 ?- @) I8 a  }& Y# K3 t5 }. b) V4 z. r
        public void setPassword(String password) {) Z4 V- e; q; I  z( e2 ]8 |- r
            this.password = password;
    ! {1 ~5 g+ Y0 q1 X    }
    9 P. K6 W- B( L! Q& J4 J1 {  i! ]% ?( L6 i3 D
        @Override
    ( |, i( T' n: W. a    public String toString() {
    # R; c+ D0 j. |( i3 n2 D/ |8 c3 B        return "MailConfig{" +
    1 w- b. W2 o2 ?+ y$ \7 A% W                "host='" + host + '\'' +
      x3 V- h8 b( O) A% a                ", username='" + username + '\'' +5 c1 K' J  ^/ [
                    ", password='" + password + '\'' +1 D; I' _2 Y7 ^. h! k) [
                    '}';
    ! V! f- E  }3 W% E, `/ o    }
    9 a3 p) u% K# C" ^* J' |}# s6 R0 z+ A3 A7 V- `$ K
    再来个类DbUtil,getMailInfoFromDb方法模拟从db中获取邮件配置信息,存放在map中  \( r1 a8 ]6 ~% e
    + k8 v; v& C5 t/ }
    package com.javacode2018.lesson002.demo18.test2;
    & K6 M" |' c2 |/ x. J: n* {( q, p$ s- Q
    import java.util.HashMap;
    : A/ ^$ w1 h3 W9 }import java.util.Map;, v+ h& C4 S: P, S

    0 a& q2 F( y( |/ ?public class DbUtil {
    / W, `# H* |+ ~. v    /**
    : m/ V6 x, [  h% ?- ^" z     * 模拟从db中获取邮件配置信息- t+ J  l0 \( {
         *' u$ w8 |; {  R) Y; ]9 ~
         * @return' I0 y6 m7 K* ^+ F' h) t# F
         */2 d8 j* y6 L1 f- |1 K: t/ g% |
        public static Map<String, Object> getMailInfoFromDb() {4 ?; H2 R+ O- {: e+ V
            Map<String, Object> result = new HashMap<>();1 w* w$ j# i$ r) g4 q
            result.put("mail.host", "smtp.qq.com");
    . H- w+ w5 ]/ i! h* R        result.put("mail.username", "路人");
    6 z3 T6 ?4 ]/ a5 M* M        result.put("mail.password", "123");
    + i  H& `. _0 s4 H8 `/ p* U, ^        return result;
    & ]$ K6 n; c2 k0 I+ r6 f& a    }! G/ f& K! z; ?9 O7 l$ h
    }
    2 r! ?& F( w; R3 h; @: p+ f& o来个spring配置类8 p) G3 L# |" N! w1 S
      D4 h2 c. E1 }9 N: z
    package com.javacode2018.lesson002.demo18.test2;
    ' S$ u0 d7 g- v( X
    6 o9 w. t, Y, yimport org.springframework.context.annotation.ComponentScan;- C7 B7 n% }- h  Y7 p
    import org.springframework.context.annotation.Configuration;
    0 X* r, t4 P- O& @: W* y, ?% P7 |- Q' R$ m8 y
    @Configuration7 Q8 z$ u9 N& x
    @ComponentScan- R( ]/ P& k2 r2 K
    public class MainConfig2 {
    " ~7 `( p  v3 s  S  c}
    , S; H! @7 T) @0 x# _& d, S  t下面是重点代码
    8 ]5 C  q7 M! n9 a: n$ P1 z  i
    @Test
    9 S( I" ^0 K$ O5 N+ Zpublic void test2() {- S1 N" m; R6 i/ |
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    ) j. [9 p7 s* h/ c  W' I0 l+ O
    , N  m3 K- K3 B    /*下面这段是关键 start*/
    7 P( D  E7 @6 l    //模拟从db中获取配置信息# ]3 ]6 y) m' M! F
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    " [- M7 r' H- }3 r$ \0 f6 T# T    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)- ]% {+ A1 l+ R+ x2 f7 L9 K3 |
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);' r: B  M" ?4 I
        //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高+ i' k2 p0 O5 K: r$ [
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);" g: e4 x$ {+ E  @
        /*上面这段是关键 end*/1 J3 k9 g# k3 o) t

    * Q% K8 h* p5 e* C4 `  }    context.register(MainConfig2.class);
      K4 l+ U% z! \- \    context.refresh();
    , Z+ E2 k0 W: X8 U* P* }    MailConfig mailConfig = context.getBean(MailConfig.class);
    9 t( J9 B) b9 q' w    System.out.println(mailConfig);! C2 ?" y, Y# c! p9 F8 h
    }
      k+ Z& Z- e6 n* A5 R注释比较详细,就不详细解释了。* ?! F1 X. J& `) L' T& f. Q6 M

    : c. }6 F  U3 C* v  U$ W7 B9 h. q: C直接运行,看效果
    , r( n. o/ m  @# ^0 Q$ h. a/ O7 G; Z5 q+ H7 x1 u. ]- T
    MailConfig{host='smtp.qq.com', username='路人', password='123'}
    % S, t$ U+ N5 Z6 M3 L$ |( L有没有感觉很爽,此时你们可以随意修改DbUtil.getMailInfoFromDb,具体数据是从db中来,来时从redis或者其他介质中来,任由大家发挥。: O: J; i$ O0 o4 `1 J- I) c

    " J5 j/ F+ m5 a( R上面重点是下面这段代码,大家需要理解
    4 E. [0 N# `: p# B) y& e1 {* @+ M9 i( E) B, q
    /*下面这段是关键 start*/( W1 I+ d- D0 n7 \& l
    //模拟从db中获取配置信息' p3 F  ^9 n  G, ?' D2 u
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    5 [0 h1 k% [& F; P8 X/ u- c9 ?1 r//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
      l$ Y. B6 p8 }# N8 SMapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    4 ]& m( Z& T7 e6 {- m1 b. }7 `2 `//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    & t9 h  M8 L$ Mcontext.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    ) R" O5 d3 o7 P4 _3 R/ G/*上面这段是关键 end*/
    5 A' z1 F/ ^& v" A* @$ l咱们继续看下一个问题
    ' i8 A5 X( v; M9 [: B9 H5 w
    . O7 ~2 a" ^+ [3 t- ^: {3 f如果我们将配置信息放在db中,可能我们会通过一个界面来修改这些配置信息,然后保存之后,希望系统在不重启的情况下,让这些值在spring容器中立即生效。
    , Z6 F1 J/ O& t1 R7 W9 W- q/ P! U/ ^$ g# s  q3 v# B
    @Value动态刷新的问题的问题,springboot中使用@RefreshScope实现了。
    : E* B9 F0 w* T" y; `  a
      R, \. W5 H. @, A, L9 K实现@Value动态刷新! l+ o. g& _: ?( [; |. Y* z  G! I4 B
    . ^# x) b7 T& Y$ t8 e$ p: B. S' U
    先了解一个知识点
    9 H: V1 [. L4 p+ q+ W  ]3 z  k2 v9 v, C6 f7 l
    这块需要先讲一个知识点,用到的不是太多,所以很多人估计不太了解,但是非常重要的一个点,我们来看一下。  Y) b2 Y2 f* U- h8 ~; w+ [
    6 M3 q. ^: [* g  E/ S
    这个知识点是自定义bean作用域,对这块不了解的先看一下这篇文章:bean作用域详解
    , B- i- [6 c, @+ E, ~2 k# s" g8 G2 N% n8 Q  u! @$ E5 Z/ }& z0 M
    bean作用域中有个地方没有讲,来看一下@Scope这个注解的源码,有个参数是:
    : V: u2 q. V1 m+ ^9 e" ^& v4 {3 a6 h9 P* v/ I" ~; t
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;5 j1 s7 d0 q1 b6 E  \! u' E& x
    这个参数的值是个ScopedProxyMode类型的枚举,值有下面4中, K8 W  g+ ?1 z) E9 v. d. [* P

    0 b0 D- U/ N0 u6 a. h0 N6 q$ rpublic enum ScopedProxyMode {
    . o% `+ Y' X7 d; R0 P* b) o. k    DEFAULT,
    & O) Z+ V0 F% z* s% B/ ^    NO,. h6 K/ `4 j$ M( M7 `
        INTERFACES,9 H7 P" O9 {6 b% b( `( R
        TARGET_CLASS;4 V; O4 A! z( [7 L7 T
    }
    & R$ _* R$ Y+ t9 ~& M前面3个,不讲了,直接讲最后一个值是干什么的。
    5 E2 m( l0 d4 b7 q1 O  O
      d+ f* `+ Z1 X2 p: r4 C2 W' a当@Scope中proxyMode为TARGET_CLASS的时候,会给当前创建的bean通过cglib生成一个代理对象,通过这个代理对象来访问目标bean对象。
    : m: }" C. ~/ K& l7 O$ l) k; v* ^- C, [  j" t7 O
    理解起来比较晦涩,还是来看代码吧,容易理解一些,来个自定义的Scope案例。
    3 U8 ?' O/ y! S- D7 ^, G6 @% ~' k, ?( w2 b# w
    自定义一个bean作用域的注解$ V/ b) d4 n4 ]- v2 ?
    0 ^3 l1 n6 l% @- S1 Y) s
    package com.javacode2018.lesson002.demo18.test3;  t$ p2 F: f' e' s: X2 x
    3 _$ Z& N2 u; M1 m; c# F  p" }. ~
    import org.springframework.context.annotation.Scope;
    $ R0 r9 F# x2 H, W; Y1 Cimport org.springframework.context.annotation.ScopedProxyMode;
    3 {5 C" h4 n3 j: s7 `- N) \7 G0 v7 P3 a
    import java.lang.annotation.*;
    & y3 [! g- t& [! N, ?+ M4 x8 D/ i# g9 ~  ]/ ~0 _" k2 ^
    @Target({ElementType.TYPE, ElementType.METHOD})
    . L5 @& P/ e  i% C" l@Retention(RetentionPolicy.RUNTIME)4 O  o( o% _* F0 S
    @Documented- E; P/ I+ r9 Z1 n$ k2 l+ G* `4 J
    @Scope(BeanMyScope.SCOPE_MY) //@1
    ' |% W- I+ Q# S- w" y$ U# Bpublic @interface MyScope {2 x5 K9 ]7 X$ z. [( j
        /**  F% P& l& ^& ?! d( u* ]
         * @see Scope#proxyMode()9 `  e2 ]' Q5 Q8 z8 b9 A' A
         */
    ) D4 n3 M, S9 r3 W3 F8 a" [    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2! b, `" L, ^3 \* G' R+ e5 z
    }
    6 L- u4 K; b5 D& H9 U7 E@1:使用了@Scope注解,value为引用了一个常量,值为my,一会下面可以看到。
    " `: j8 c1 m# ~: k! ^- w
    : x: l$ v5 @8 d* l5 X4 Y@2:注意这个地方,参数名称也是proxyMode,类型也是ScopedProxyMode,而@Scope注解中有个和这个同样类型的参数,spring容器解析的时候,会将这个参数的值赋给@MyScope注解上面的@Scope注解的proxyMode参数,所以此处我们设置proxyMode值,最后的效果就是直接改变了@Scope中proxyMode参数的值。此处默认值取的是ScopedProxyMode.TARGET_CLASS1 K9 s% X& k1 k
    , c3 Y9 I& i& b! W$ D' h' t
    @MyScope注解对应的Scope实现如下8 a: ~* E/ ~+ X  J/ s8 T

    $ a- ?" C/ T. B; E% Gpackage com.javacode2018.lesson002.demo18.test3;( V6 @1 O! f- E1 O  N( n6 T

    3 _. J9 E, o2 E- O7 nimport org.springframework.beans.factory.ObjectFactory;% y, d7 w/ p: @7 K, U
    import org.springframework.beans.factory.config.Scope;4 [3 z2 q) ]& l. A
    import org.springframework.lang.Nullable;" @/ W$ B0 x, s. |
    1 p& l% x) _) p# W4 J+ G
    /**
    , ^) r  D: V5 N, G# z * @see MyScope 作用域的实现; P8 z& d/ t$ N6 r
    */
    & n9 B, v! N1 V, \; Ypublic class BeanMyScope implements Scope {/ R1 K  {1 k' Z* r, L

    7 q' ]) W; f2 r: s8 ~$ d    public static final String SCOPE_MY = "my"; //@1. t0 t9 ~5 D- U/ Q
    ' N& _% _$ O2 c3 `- C$ j! P. K, @
        @Override
    , j/ S' q) L# W" S% A; H    public Object get(String name, ObjectFactory<?> objectFactory) { , p! G' D3 B& r2 R
            System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
    9 D/ ^7 x' U, `0 g+ I        return objectFactory.getObject(); //@3# @& }0 X; B7 a2 U3 b2 y# h
        }2 d$ }  H. t1 d& h/ J
    * V: K" M& W' a
        @Nullable
    7 T/ g& a0 d/ j0 z1 L    @Override
    $ x  U9 A+ g# O8 {  J3 h    public Object remove(String name) {
    ' W& @, E7 m) T% u        return null;( e/ N$ u0 g) u8 U3 B
        }1 g8 h4 I3 D0 L4 M# m: U, i9 {4 }
    " q# w3 ~* ~7 u
        @Override
    ) j8 D: P0 @% g! |6 `! y- u    public void registerDestructionCallback(String name, Runnable callback) {* N& _# s( W/ [( ]

    ( `- \% e2 Z. T; X& U  \: q) S/ H    }$ G( _, H( O5 y8 ^5 i, l

    4 y8 Y$ s% T/ B, l. B    @Nullable; F- C$ N5 u1 w, k6 u1 l* c
        @Override
    / x- u) a+ q, ^4 O: {# v! N' R    public Object resolveContextualObject(String key) {+ F9 T4 R/ L" ?1 i; @0 K  @
            return null;* I4 Z7 O; l' C- e9 s, \* Y
        }
    3 T1 g+ t$ V2 I7 B8 w9 l( [
    , m& V! F- k2 ~4 {) g    @Nullable- D; F0 @' Y9 {* C2 T# t6 l
        @Override4 N2 h3 V: c1 i$ r; ~3 k: c' M
        public String getConversationId() {
    ! ?2 w1 i; ?; j, d! ]% Y8 Z        return null;, t+ V4 X# `  h; G5 b
        }
    6 V( j- U0 x: v}
    ' ?; R6 x9 B& h- z9 ?' i1 z6 k( R@1:定义了一个常量,作为作用域的值! J/ _, [* o) C! }

    6 l4 R. C/ \8 f3 a& u6 [@2:这个get方法是关键,自定义作用域会自动调用这个get方法来创建bean对象,这个地方输出了一行日志,为了一会方便看效果4 Z- q1 d8 T+ B$ m# D  r

    " ^' T; |) @, M6 u3 c7 s7 Z@3:通过objectFactory.getObject()获取bean实例返回。  K% o1 `+ J1 d. d' y

    ; t0 G" A7 }- L1 `# z  h下面来创建个类,作用域为上面自定义的作用域
    7 X: B5 c3 H; c" @5 H; ~! X. S2 M  ]  }2 w% ^4 ^7 d* Y
    package com.javacode2018.lesson002.demo18.test3;" W1 {0 M0 S8 U$ Z

    5 }6 h& C, y' o$ Rimport org.springframework.stereotype.Component;
    ' Z1 t/ i2 I6 I- b4 I* X1 V) D# Z% ^8 R. ~- c( d1 S5 n' n6 n; n7 Q
    import java.util.UUID;2 I4 |3 D* i' X, ~

    $ E" M( ]' s/ v& C; b& J& p6 X@Component
    5 K  B' Q6 e3 A@MyScope //@1
    + o- V4 E  U/ U2 hpublic class User {' ?+ X, s& B$ q6 R! A' A/ B
    1 q/ w5 L3 v) z8 ]
        private String username;2 h, ^9 j* X! p9 ^' I  d
    2 D1 p" b+ g4 }& ]& c" G- A
        public User() {
    / j0 ~% n# W, q4 i        System.out.println("---------创建User对象" + this); //@2" r( U! y0 m+ g: R& a& ?
            this.username = UUID.randomUUID().toString(); //@3
    % e' O. I- }5 ]. h8 e    }+ A6 t6 ^  @/ h; D0 J2 {; q

    - ]) j2 E1 `1 @% J6 o    public String getUsername() {
    3 @8 c8 h- ^& G4 S6 e- N- G$ V        return username;
    2 _" R+ _. ?& H: Z    }
    + z* h) y: J6 c1 ?. l' S2 V2 j( u% [2 c  ]3 P6 k9 w- a7 M1 n
        public void setUsername(String username) {* ]  B* G, G2 S; N) R3 t
            this.username = username;
    4 r4 Q/ @) R: [( `    }
    3 {& n9 s; C: L. B$ }& ]1 n3 i" i) U9 \( g) I+ P: f" v
    }
    4 _! J2 O& ?" b+ m. z3 |) u! l@1:使用了自定义的作用域@MyScope
    # [5 Q# n+ F- D1 i4 h% V5 T8 u& y# g, |9 T
    @2:构造函数中输出一行日志- B/ d2 z, r3 N

    7 D( B/ I, ?# _" T2 n$ A@3:给username赋值,通过uuid随机生成了一个
    3 @8 ^/ J+ w2 h7 V! |3 O- j* ?( l2 s8 `3 r3 L+ f0 L4 t: p
    来个spring配置类,加载上面@Compontent标注的组件, w# _2 }; u% [& n
    / F: ~! b6 Y0 i  ^. }+ ?' t$ f3 [! L
    package com.javacode2018.lesson002.demo18.test3;1 i, @! u% b; s2 Q1 G  n

    , |# ^: r" i( X8 g; h8 ^) cimport org.springframework.context.annotation.ComponentScan;- {5 L9 p  t9 C' z' a* b
    import org.springframework.context.annotation.Configuration;
    2 l: ^$ {. w; p! E+ Q2 o0 r7 S
    * P; _0 S( p- s& R5 ?! d@ComponentScan
    6 u% q" f* ]& x' U* ~@Configuration7 `! L* y) F7 n+ K1 ^- |& }, Z  ^3 h
    public class MainConfig3 {1 e6 E) i) t0 M7 V$ V% f
    }; ?5 }$ p$ @. C. \& ~
    下面重点来了,测试用例
    , x% ?; }- f+ e* M
    " O- O+ s. W, ^@Test
      e& V1 a+ @. _$ @' O5 Upublic void test3() throws InterruptedException {
    3 r) o/ R4 r" G: Z; q    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();% u6 U$ Z4 o; ]
        //将自定义作用域注册到spring容器中
    3 ], j. G: |( r9 K; \5 j9 f    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    * r) Y5 E- }+ f0 [# ^* Z    context.register(MainConfig3.class);% T% k0 ]9 J4 t/ f2 C
        context.refresh();/ i1 @, P; h4 ~+ p+ z2 |% y, M
    9 a1 Z7 ^! b0 g( w: f. x' X( [
        System.out.println("从容器中获取User对象");
    6 ?& C# e% Y$ t$ M1 b7 Q    User user = context.getBean(User.class); //@2
    $ s# [! M3 c1 A4 n3 D, @    System.out.println("user对象的class为:" + user.getClass()); //@3
    ( Q5 l! u) S" l/ D
    . u9 c: I) Y8 Y2 _8 a    System.out.println("多次调用user的getUsername感受一下效果\n");
    + b# G) [/ y8 f) A    for (int i = 1; i <= 3; i++) {, w3 D; e4 U1 r# H
            System.out.println(String.format("********\n第%d次开始调用getUsername", i));
    # V0 z; z* R" v- [% s        System.out.println(user.getUsername());3 @1 y4 a# z3 [& a" F9 F
            System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));# t% y: O9 c% g
        }
    % g" P. M; X& @" B}5 o1 G1 K: J# G1 K) S: P7 X
    @1:将自定义作用域注册到spring容器中3 G( |; O; |+ b- _1 J2 g
      f& t$ |* s& e* N
    @2:从容器中获取User对应的bean
    * `1 W' v3 H/ v; h- d  E6 L
    3 K2 ~/ G5 ?* i' f@3:输出这个bean对应的class,一会认真看一下,这个类型是不是User类型的
    ( Y5 H0 }4 I! r
    3 Q* ~6 N7 s4 K6 \. {0 q3 ~代码后面又搞了3次循环,调用user的getUsername方法,并且方法前后分别输出了一行日志。  d+ i- ~6 k3 v/ g) O
    1 v- B. C9 e5 |" I0 b! [+ o
    见证奇迹的时候到了,运行输出
    - r, ?$ e4 ~, D" e: u' G! T# @3 a1 L  [$ Q6 V5 {' [' d
    从容器中获取User对象
    7 H8 A! `) m" A, Kuser对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
      @# L8 ~0 a  w9 n- A$ j, T& [多次调用user的getUsername感受一下效果& t1 [+ L+ v0 \  P
    * T. y4 c- _- }0 G, d
    ********
    ) v( p0 E, k4 K5 R3 Q第1次开始调用getUsername# }( x. W. _' s7 s5 `
    BeanMyScope >>>>>>>>> get:scopedTarget.user/ S. x5 o9 _" g/ [: ]
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4; o0 H4 ?  ~. j7 ~% I" I  f
    7b41aa80-7569-4072-9d40-ec9bfb92f438* r3 Z1 m, Z; {7 \% Q
    第1次调用getUsername结束  R* j- D+ t3 U" L6 `
    ********
    1 M% C# E' u. x' V  ~( o3 V, p$ A& C) _
    ********
    " _1 ]5 ?+ e& }& E第2次开始调用getUsername: E! x  b; I6 B1 R/ U) h+ j% h
    BeanMyScope >>>>>>>>> get:scopedTarget.user: p1 O  t# O& L) B8 E4 S8 F7 o1 ]
    ---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b6 i. \( k+ k. x& U* W3 l8 t4 A, n, u/ ^
    01d67154-95f6-44bb-93ab-05a34abdf51f
    # `* S* s9 q0 L$ M! Q. X1 z第2次调用getUsername结束; [4 e* Y- r+ S/ E$ y3 X" `! ]
    ********
    0 f% Z8 K- F. \1 M) j# n- R
    6 }. E" p2 N3 f, ]********. }% Y2 P- Y3 x" g+ m, r
    第3次开始调用getUsername( H1 J! X9 l* j/ [5 I
    BeanMyScope >>>>>>>>> get:scopedTarget.user
    8 w/ Q) @* n! v7 ]# X7 G---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15! }# V' P/ D7 T$ h6 X) I6 G
    76d0e86f-8331-4303-aac7-4acce0b258b8
      a- V& j: a* {/ ?$ ?第3次调用getUsername结束
    # _% {. V! y& T- U  l; c% X********% U- S7 H" U' f0 N! J8 `
    从输出的前2行可以看出:; j2 n+ o+ |' b* @9 p
    " g$ _$ k2 i  d3 @/ g0 P6 T
    调用context.getBean(User.class)从容器中获取bean的时候,此时并没有调用User的构造函数去创建User对象
    ; `: e$ g+ R/ q0 N; S
    ) J3 f5 D) A& U第二行输出的类型可以看出,getBean返回的user对象是一个cglib代理对象。
    5 A, n: v* |4 L  ?' g" L/ f# R3 C7 R7 r, g% o
    后面的日志输出可以看出,每次调用user.getUsername方法的时候,内部自动调用了BeanMyScope#get 方法和 User的构造函数。+ w" o1 z7 ^; ?
    * G9 O8 ~) G9 a' Q* ?" K$ D
    通过上面的案例可以看出,当自定义的Scope中proxyMode=ScopedProxyMode.TARGET_CLASS的时候,会给这个bean创建一个代理对象,调用代理对象的任何方法,都会调用这个自定义的作用域实现类(上面的BeanMyScope)中get方法来重新来获取这个bean对象。% `+ V3 f; A$ p2 z4 t$ P
      ~* e) {+ Z0 G. _
    动态刷新@Value具体实现1 F- E9 f3 i. P# Y+ Z$ |3 A2 i5 Q( G# Q" S

    + Z  i- T  X/ X  o3 O! z那么我们可以利用上面讲解的这种特性来实现@Value的动态刷新,可以实现一个自定义的Scope,这个自定义的Scope支持@Value注解自动刷新,需要使用@Value注解自动刷新的类上面可以标注这个自定义的注解,当配置修改的时候,调用这些bean的任意方法的时候,就让spring重启初始化一下这个bean,这个思路就可以实现了,下面我们来写代码。9 g, B. i3 }8 U; z, q
    ; d% ~! q1 X/ K* a; I: @0 H4 U
    先来自定义一个Scope:RefreshScope
    0 X) B/ N. \9 \) k2 D1 P; F) F; i$ V" l
    package com.javacode2018.lesson002.demo18.test4;: p% {2 v8 P4 w3 V' F
    . G: U( g+ }, y/ w! m! \
    import org.springframework.context.annotation.Scope;1 K' \- W6 i  H! R' d9 G
    import org.springframework.context.annotation.ScopedProxyMode;
    ) g6 g8 u8 x  G' V* l7 @
    & L. L6 f# |5 z% m* {import java.lang.annotation.*;* Z! s/ a/ \1 }2 s+ q+ h
    2 {  {& W9 n7 G4 K8 u
    @Target({ElementType.TYPE, ElementType.METHOD})
    # V  [! b* i0 O6 {7 ~@Retention(RetentionPolicy.RUNTIME)" {" [- Y$ z" P4 m6 E: ^7 c- N
    @Scope(BeanRefreshScope.SCOPE_REFRESH)
    ; J4 u3 ]4 z/ P9 T& l6 ]  u@Documented
    , d9 F" ^- C( g% u% tpublic @interface RefreshScope {( y+ r  H4 k( x
        ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
    4 Z; T; q+ _3 F: Q}
    7 |0 G+ Z. h$ f' {* i: A) q4 W要求标注@RefreshScope注解的类支持动态刷新@Value的配置+ b' W2 R  i7 G

    $ T* C* @3 q4 r! _0 }- v@1:这个地方是个关键,使用的是ScopedProxyMode.TARGET_CLASS
    * }# ]7 c* V$ J
    7 o) A+ D. o4 w8 H" o$ D5 @这个自定义Scope对应的解析类
    ' t& _" k: [, G6 v5 t9 X/ {/ s& T- A: l; u* i
    下面类中有几个无关的方法去掉了,可以忽略
    ; W' {! B4 y: f0 E$ r4 r
    ' r/ E, B; t$ upackage com.javacode2018.lesson002.demo18.test4;
    ( P3 f: q* K+ Z- `+ I8 N1 v
    - W3 S# n/ H1 @5 G; k: L! a7 j
      U3 l# g. W8 r6 f( d7 Kimport org.springframework.beans.factory.ObjectFactory;
    ! ~; O" E2 K: Y( gimport org.springframework.beans.factory.config.Scope;
    # s0 Z+ k1 W# U. Q5 wimport org.springframework.lang.Nullable;9 v& F1 e( W8 r& _  I
    0 V/ a. P6 c6 Z. W' A# a
    import java.util.concurrent.ConcurrentHashMap;. v" i( T% v% d/ N" e) f& \
    ' b7 _% M: a! W) M
    public class BeanRefreshScope implements Scope {) X$ z+ N+ N  |  }/ a" U/ y7 G: T

    , M  N9 ]9 K* f1 B4 u    public static final String SCOPE_REFRESH = "refresh";# n. A2 r" d  c, s1 g' q

    . I% y7 K3 w/ n7 A0 l    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    : B8 R" T9 G4 Y, N# |) I6 f2 u  L# S. W' P4 w7 g: P% ^
        //来个map用来缓存bean1 A0 G% T$ N; f+ Q/ L- u" d: B
        private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1* L: X" T" i" y3 C& \

    4 g- d% l2 s8 Q# F5 R% E! y    private BeanRefreshScope() {% h' v: _2 `( H/ B0 l5 w
        }5 A( _& f0 P3 |% _  [
    0 c4 N; a) t3 U- d/ Q
        public static BeanRefreshScope getInstance() {% N4 L/ O+ y& o0 E
            return INSTANCE;  V& k. j( W# r0 X% w
        }1 h1 W- q, O1 p4 ~8 {  N  r
    - x, U7 B: J' k- w
        /**2 K9 I* }& r2 z- ^$ p# N
         * 清理当前
      ]) q  Z5 {8 [' {1 d: m     */, G5 d7 A, F6 u& K1 e
        public static void clean() {. F7 C: e# Q! K1 Q9 H% k
            INSTANCE.beanMap.clear();
    0 F$ C. D; Q- }/ L, d4 R    }+ R% V4 u2 `9 G$ }) Q

    # ]) @8 b2 Q5 w    @Override) `/ w6 \% v: {6 u
        public Object get(String name, ObjectFactory<?> objectFactory) {
    3 j; j0 T1 d: c  h9 \/ s$ Q        Object bean = beanMap.get(name);
      z- _+ ~% g: p+ e, M        if (bean == null) {1 u7 S- [3 Z: Y
                bean = objectFactory.getObject();
    # T0 \& c+ a, o+ G8 t6 |3 ]& A            beanMap.put(name, bean);9 s4 e% v2 d" _$ J' i  g
            }
    $ l& v% c% d% y7 V. u        return bean;
    1 ~1 ?5 V9 ^# a- A    }
    : b3 q2 ]* m, t9 Q* {# ?. K# ]. n; q# p7 s. g/ c
    }5 O. Z, o4 O: b2 J) A4 U
    上面的get方法会先从beanMap中获取,获取不到会调用objectFactory的getObject让spring创建bean的实例,然后丢到beanMap中# u6 g" l# e! p- W1 W" m

    2 ]7 l3 t. S7 s4 T% V' Z+ |上面的clean方法用来清理beanMap中当前已缓存的所有bean- A( W- K0 b0 \; I/ H
    ( J3 Q( |; m9 b  L: q# s1 l# s
    来个邮件配置类,使用@Value注解注入配置,这个bean作用域为自定义的@RefreshScope
    ' X7 j. k5 h1 B' r! U
    ' a; J+ c+ ?, j: \( Tpackage com.javacode2018.lesson002.demo18.test4;
    6 Z1 G8 @/ Q- Q6 N# O4 y# e
    . }0 X9 h/ E: K$ |import org.springframework.beans.factory.annotation.Value;
    ' C; X! H/ D- |9 \$ {import org.springframework.stereotype.Component;
    2 U1 q6 c9 l1 O7 A
    % S; f7 h3 U6 c6 y/**
    # Y  F1 _* w8 t  z7 A, ? * 邮件配置信息
    ! x2 z& I1 `% H# s7 u */( s) r9 r5 P7 v
    @Component
    $ M; x0 c1 ^/ x2 ]2 N1 n  g. }2 P@RefreshScope //@10 v( O' a8 ]3 X: u
    public class MailConfig {
    , O  T, O4 p) B# o2 P
    $ O2 S) D  A/ L. M2 f6 z  m    @Value("${mail.username}") //@2
    8 _/ ?5 N  A6 j* S  Y+ V    private String username;5 v) O" ^! o4 R" i% i. ]

    & q  c, r7 d* f' |& d! a    public String getUsername() {
    4 D- a% Y% P" ]! e& x( N2 A# J1 \        return username;
    " l2 m( H% R, c6 _2 o8 a+ }) ~    }3 m2 d, s3 }4 ^9 p7 Z2 ^
    ( j8 j# c, F+ K: n7 W
        public void setUsername(String username) {
    ) m& b( c# s% t; X7 b        this.username = username;
    / j' a8 o4 a7 \; k* |8 p    }
    * s' O8 M1 |) L
    7 e: I7 \2 m) k    @Override
    ( S; S6 i; ?- K$ N3 X    public String toString() {! M  P4 f/ K4 K# N
            return "MailConfig{" +
    % j9 A) t0 L. |& z) {: t1 j/ D: y; v                "username='" + username + '\'' +; R7 L& I  n, x! T
                    '}';
    ; ~5 l5 s& Q# y    }
    6 N+ ~% v: _# \: a5 a}! Q4 s3 @  O  U+ ?+ B2 d# {
    @1:使用了自定义的作用域@RefreshScope" R# S. S( k! U6 {3 g; T3 M
    0 J$ _; X7 ~& T5 o  Y
    @2:通过@Value注入mail.username对一个的值
    6 r8 h- T7 w8 K1 ~: s" k! ]: P1 }6 z$ `) N& d
    重写了toString方法,一会测试时候可以看效果。
    : X- j6 E0 B' O# h* M9 X# a8 C. R9 |6 O9 {3 u3 V+ S+ a% v
    再来个普通的bean,内部会注入MailConfig
    3 Q" S/ l3 k" u2 ~
    * X7 Y4 q: }( u: @2 O' E. Ppackage com.javacode2018.lesson002.demo18.test4;. ^) X: X' h4 I3 ]" ?) X' t# U3 U
    1 t& z% s7 s0 ^4 j
    import org.springframework.beans.factory.annotation.Autowired;* X! @$ [: I7 J' l3 u1 U% M
    import org.springframework.stereotype.Component;+ h2 b1 S8 V, X+ Y1 _# T
    1 s, z$ k1 D7 G2 `; U; I
    @Component- m0 |4 J0 p; }" F$ U& B4 h6 [0 e
    public class MailService {
    * U! d7 S! u# j/ M9 C7 ?4 W4 o. ?    @Autowired  Y& u, ?. l' N* H9 }- b2 F, @
        private MailConfig mailConfig;+ s# U+ ~' f/ m# @$ S
      s* y7 |) b4 R( h7 s5 I/ m5 I
        @Override
    5 Q. n& P7 Z1 b: K    public String toString() {
      ?# \3 `: g7 M$ ]3 \        return "MailService{" +" |( C8 O  }$ ?
                    "mailConfig=" + mailConfig +
    & g% o# |8 {5 u# ]1 r$ x( G$ u                '}';
    : o  _) Z; i. m# n    }
    " A7 d; L8 l: D, l}
    + k% ~& F, |  K3 e3 b2 ?% M代码比较简单,重写了toString方法,一会测试时候可以看效果。; Z( B& S3 b* H3 e3 P

    9 s6 k0 @  c' T; a3 {- \9 P1 @; z来个类,用来从db中获取邮件配置信息
    & ?- @2 }( A( |  ~4 c, G) [
    , {7 L$ ]  u3 ?3 z( A1 v2 M! F( k8 Cpackage com.javacode2018.lesson002.demo18.test4;& m/ ]7 u/ q* S3 Q- y8 ~. s
    $ F/ B0 a/ @8 O2 L4 F5 q
    import java.util.HashMap;
    . ^: ^+ ~0 Z5 K( ^  c6 simport java.util.Map;5 [6 h* A6 {& g
    import java.util.UUID;. ]& _- ~+ _% H

    & g) t6 b# c* R' _; p3 b$ H! `public class DbUtil {- }& C& R# r" j  v. ~$ U* p
        /**
    ' I  b- c; x! s- X     * 模拟从db中获取邮件配置信息
    8 ~" T& C0 V$ L' \' K     *9 D3 D1 w( f; }* ?' W
         * @return
      o9 u8 T5 H" D4 [0 \     */
    # l2 r2 |) V# ^  I# [( a/ I0 B    public static Map<String, Object> getMailInfoFromDb() {& o; T: M) y3 _2 D' D
            Map<String, Object> result = new HashMap<>();; s6 Z$ x6 E* b
            result.put("mail.username", UUID.randomUUID().toString());. V* U2 u4 r" I- e) ]  `
            return result;
    9 p1 H4 Y/ ~" z! W" F; M$ l- y- J    }" b% }% P# f+ s& W; ~
    }$ |# O( c; `: D9 x$ |
    来个spring配置类,扫描加载上面的组件
      G7 o, E1 h/ J4 ?1 N. R/ u! p( K# |2 I" c! f
    package com.javacode2018.lesson002.demo18.test4;
    9 z# u1 E6 d! j5 H
    + C* O4 U' \; cimport org.springframework.context.annotation.ComponentScan;7 \. L& t1 h1 \: P6 `* t
    import org.springframework.context.annotation.Configuration;: Q4 Z: x1 q) [% C  u! g

    7 k" _) I! S8 x, G- \@Configuration
    2 b$ M  v* d/ |@ComponentScan* ~. f, f# {; W7 }
    public class MainConfig4 {
    % ?( m& D' x" i& S7 f4 ^}
    & ^; ~( {/ v" v# b/ O' f来个工具类
    & r' w4 @3 i, N) L$ H4 M' B6 _" g5 ~( ^+ }
    内部有2个方法,如下:
    ; f. O: s0 K9 y; ~) I0 }1 h1 o* j% k- F
    package com.javacode2018.lesson002.demo18.test4;( O: y1 @; d8 p) p1 n2 ]

    + m# \& `* g( Zimport org.springframework.context.support.AbstractApplicationContext;7 V5 S0 B% c  c
    import org.springframework.core.env.MapPropertySource;
    ) C* p/ ]9 ]" c# A1 _9 S2 B4 P
    / D8 w8 T* `- Z& s9 [; Ximport java.util.Map;# E; i4 g; y  W! K/ A

    : O8 m* Q& k3 n; t4 Fpublic class RefreshConfigUtil {
    6 Q' B; k( Z; Z    /**& a3 B8 x8 Q, j* J' t8 b. M; R
         * 模拟改变数据库中都配置信息
    $ H4 J. F9 {% |* L) h" F- [     */
      l9 q0 }6 I, N: r0 a5 m& v" {    public static void updateDbConfig(AbstractApplicationContext context) {
    ; ^' J# w3 H& I3 g1 @' \" W        //更新context中的mailPropertySource配置信息
    ' X4 G8 ], U# w# h        refreshMailPropertySource(context);
    ! o& W$ ^6 b. L" U, @! g9 L5 k3 x6 j3 a# V9 O7 h( b
            //清空BeanRefreshScope中所有bean的缓存
    , p, u# i# k/ I- N  ?" t. K. y        BeanRefreshScope.getInstance().clean();% |$ K+ b7 R8 T, V# v* z. v
        }( g0 |# `& T$ v7 N( y& k

    ; w* a  u, E7 ?- D    public static void refreshMailPropertySource(AbstractApplicationContext context) {
    * c4 L/ h. U( T6 r& j1 [        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();4 p) f2 N: y  X6 ~7 z. a& A* ~# J3 u
            //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    # W4 `' r8 p) C) p7 R        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    2 U  d- t- {7 F1 {  K; j        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    % L+ d$ H+ @5 R  C2 a    }5 z9 |/ g( ~- g' D/ z. d2 ^

    / D' @. A& G- u" o" B8 ^' a}% c8 M  j( C$ s+ x
    updateDbConfig方法模拟修改db中配置的时候需要调用的方法,方法中2行代码,第一行代码调用refreshMailPropertySource方法修改容器中邮件的配置信息
    + K0 q+ O3 b) d: B' @% G; s' v! u1 J0 }
    BeanRefreshScope.getInstance().clean()用来清除BeanRefreshScope中所有已经缓存的bean,那么调用bean的任意方法的时候,会重新出发spring容器来创建bean,spring容器重新创建bean的时候,会重新解析@Value的信息,此时容器中的邮件配置信息是新的,所以@Value注入的信息也是新的。
    2 R4 X; A, C5 j( H- L7 |* ~8 n+ U- p9 }2 B
    来个测试用例8 d5 ~2 D) Q- _+ g/ p: M
    & s0 P. d) z6 k: M6 N7 Z* Q" O
    @Test4 o$ H  @: `0 O2 o. h7 E# u9 [
    public void test4() throws InterruptedException {
    ) B8 i& m5 j" |    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();, E7 x& o4 J! w- q5 S" U3 o9 V
        context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    + i8 O$ M% W9 M5 H: P    context.register(MainConfig4.class);
    0 ]2 x) Z/ `2 o3 Z) \! W: e, d    //刷新mail的配置到Environment& T1 z6 n3 H; T; @( A
        RefreshConfigUtil.refreshMailPropertySource(context);
    , x& f' M- [$ |$ s+ v/ x    context.refresh();
    - A/ K! @- k# j6 o) o3 I
    ) A& ?6 Z- V+ l) k8 t    MailService mailService = context.getBean(MailService.class);
    " k' K$ W; ?5 Q- I' L) q    System.out.println("配置未更新的情况下,输出3次");. l1 B  a4 a7 b6 t3 {
        for (int i = 0; i < 3; i++) { //@1' a" ~2 O, f7 _6 i" g
            System.out.println(mailService);
    - e' O. V: ]4 q9 K& t% {        TimeUnit.MILLISECONDS.sleep(200);
    8 G; Z" h, w) k4 t    }" I* q! G) L$ q. F) i
    : R$ R+ ]/ a' L* z: J
        System.out.println("模拟3次更新配置效果");+ A% [( J- p4 F3 L) s( y' }+ ~1 w
        for (int i = 0; i < 3; i++) { //@2
    % U' @; W$ w% k5 e; s! F; B        RefreshConfigUtil.updateDbConfig(context); //@3  s& E# w- J5 T/ f3 a7 q
            System.out.println(mailService);
    0 e# X: F$ I. J8 l        TimeUnit.MILLISECONDS.sleep(200);8 T" T+ S7 c6 B3 f( F7 o: H
        }
    9 |' d* D/ i, G$ P# L$ O}
    2 H; \& s3 Q1 x5 S' j) _@1:循环3次,输出mailService的信息3 k( F  T, C/ [
    " c, Z5 U. H4 o" X7 P( E" o8 @
    @2:循环3次,内部先通过@3来模拟更新db中配置信息,然后在输出mailService信息
    $ y1 }- x$ W+ w1 h& N" n5 s6 o
    + L1 D; z1 @  k, P$ l& S, r8 h见证奇迹的时刻,来看效果' p9 ?' Y# K% w% E; y) m. X

    : d2 k# t" P1 }4 U0 e配置未更新的情况下,输出3次
    4 X# B; I+ `! `MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
    ! N5 @( l& v3 k+ r, MMailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}7 w) D* T$ O6 n
    MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}- Z9 n$ ~8 E! G& q" z
    模拟3次更新配置效果) ^# h2 \& r0 F" G( J* ~/ D
    MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}* \$ e% k/ v+ {+ Z( X+ v- f$ v
    MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
    / O& V6 I: T% ^' Z7 u$ {" _! }/ \MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}
    % t% H/ d, d; A4 R上面MailService输出了6次,前3次username的值都是一样的,后面3次username的值不一样了,说明修改配置起效了。
    - |; k+ l4 l0 j+ o+ ^% z
    3 `4 |9 N% W. q' U2 {小结+ N! H& Q- G6 `, O2 L* [" W6 g
    7 }# d% [( W: ]2 \1 \! Q" K
    动态@Value实现的关键是@Scope中proxyMode参数,值为ScopedProxyMode.DEFAULT,会生成一个代理,通过这个代理来实现@Value动态刷新的效果,这个地方是关键。
    6 G2 ]4 t; m  x* u. H! j" i9 R: D4 R6 y7 G1 O
    有兴趣的可以去看一下springboot中的@RefreshScope注解源码,和我们上面自定义的@RefreshScope类似,实现原理类似的。1 F" k5 W' P) |; Q) f; f
    1 c, @( t4 j; y" ?( s" z, z5 D3 C# `
    总结+ B4 I/ ~" T8 z2 a8 ^; }8 C$ R1 |
    , Z( L: h3 g1 L+ d' d7 F, t, S
    本次面试过程中3个问题,我们都搞定了,希望你也已经掌握了,有问题的欢迎给我留言,交流!
    : u5 Q8 {' [, B: ?$ A# `
    , |3 ^2 @$ u' g$ |% d案例源码
    - N) ~6 ^1 C; Y' g. E3 m6 y: ]  F+ d0 Q# O# M( `7 K
    https://gitee.com/javacode2018/spring-series# V& L) m7 T8 _8 |/ r, h7 y
    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
    4 x7 Y& F6 O4 ~0 R. ]0 w$ Y0 D2 d) J5 _* A
    Spring系列
    + q7 e# }4 ]4 ~$ C4 [  I
    $ N; P8 N1 @) Z/ |# xSpring系列第1篇:为何要学spring?8 m6 e! B  Q! z0 H2 i0 H5 ~
    1 s4 g' G" I- L* A
    Spring系列第2篇:控制反转(IoC)与依赖注入(DI)) G( Y5 n8 C4 E. n6 I" y) f
    % L8 U% {# y- Z! Z5 _
    Spring系列第3篇:Spring容器基本使用及原理
    2 s# [1 ?/ t) i) e. M2 I1 [% _! Q$ B0 y
    " i) j3 J- \" G0 J: a1 e4 j9 ?Spring系列第4篇:xml中bean定义详解(-)8 b" Q% a$ k( ~- P9 ^: e

    ' \  i8 D& J  `! wSpring系列第5篇:创建bean实例这些方式你们都知道?
    3 A& _6 X1 z2 h  B. s- u! r
    ' A7 Z9 ]0 {; ~8 ?Spring系列第6篇:玩转bean scope,避免跳坑里!6 [2 }4 \3 y* u" d

    ! m9 ?6 n; ?( P' I# z+ B8 GSpring系列第7篇:依赖注入之手动注入
    0 D4 n: B( e; n$ m( E) \" O' M) w+ c, K7 b4 c, x+ l
    Spring系列第8篇:自动注入(autowire)详解,高手在于坚持
      N8 T6 b' ~5 u) a3 Q- E' A
    & l& T5 r( w  z* c/ r& fSpring系列第9篇:depend-on到底是干什么的?
    7 [+ X- h8 ^, u# m9 J+ D
    ) }9 U0 M  P' GSpring系列第10篇:primary可以解决什么问题?
    " k9 n+ A' G* H- ]6 J; A$ F) n7 k8 ?+ a6 p$ W
    Spring系列第11篇:bean中的autowire-candidate又是干什么的?+ p* Z8 W% }) J8 D) {- H) n) `
    1 X/ W6 J/ d9 N2 N
    Spring系列第12篇:lazy-init:bean延迟初始化
    9 E! y  S4 H* z9 ?# S' }8 h. [1 q
    Spring系列第13篇:使用继承简化bean配置(abstract & parent)
    6 K5 _1 B0 g6 h  x) \' ]
    - n+ z+ B# e' c0 X2 @  bSpring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?
    9 ~) ^1 d( u$ l* o) o
    2 g1 [% ~2 K7 Q0 g, }4 xSpring系列第15篇:代理详解(Java动态代理&cglib代理)?
    / M: A$ g- W9 Q; V! w) Z& q
    % p. W1 x0 `. V4 X6 ~Spring系列第16篇:深入理解java注解及spring对注解的增强(预备知识)1 z+ C2 [! C+ P, S7 o
    - x* l2 A: }7 A4 N& }
    Spring系列第17篇:@Configration和@Bean注解详解(bean批量注册)- k4 y  p3 {3 {8 a

    7 q. n8 `" K: c1 ^' i3 eSpring系列第18篇:@ComponentScan、@ComponentScans详解(bean批量注册)+ U3 ?6 f) }6 u, U. z$ H- {
    : c6 k$ p0 Z/ O8 ~0 b9 k- l4 }! M+ |
    Spring系列第18篇:@import详解(bean批量注册)
    1 X9 U- r( k/ K: O
    - Y% t! c5 j/ K6 [Spring系列第20篇:@Conditional通过条件来控制bean的注册
    7 x( \5 b! p: o$ J2 P$ S7 b/ v( g1 o# b7 R1 k9 n' m
    Spring系列第21篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
    ; P" N' {  F8 M* H1 Z6 I
    4 J( |' b, i' n* A/ K; w$ Q9 ~Spring系列第22篇:@Scope、@DependsOn、@ImportResource、@Lazy 详解, q) f5 k* A8 a+ K! _$ q/ q4 S

    0 h/ }: |' R, I8 K7 FSpring系列第23篇:Bean生命周期详解
    / |8 I0 }: \+ I* J: A4 c, p  _( |+ o/ G
    Spring系列第24篇:父子容器详解/ \/ M/ C% n$ ?8 P" W% U0 R, }1 m2 G

    : c. D: ]5 B, }4 B( j: P更多好文章. e& g( x5 U* s4 \( D
    0 x$ L* |8 ^/ L2 d
    Java高并发系列(共34篇)
    ) e( v3 ]* ?" o5 d* B0 c5 Z, ]4 N; W$ U8 {7 M
    MySql高手系列(共27篇)( O' i) _6 S' S  Q3 Q6 M- v' K

    , P7 _# v) P. iMaven高手系列(共10篇)/ v7 I' C  ~! V5 t4 G
    7 T' f0 S; J$ m$ Q
    Mybatis系列(共12篇)
    . A# k+ S; a( P1 h: i* J, [6 B6 I- ~3 O1 W
    聊聊db和缓存一致性常见的实现方式  ^; S; n2 w$ W7 F# [. R' n

    # N8 S& q' {4 e6 u6 ^) Z接口幂等性这么重要,它是什么?怎么实现?
    0 J" m6 ^. E7 u+ Z' Z8 B1 r& W- D7 J2 i  K
    泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!. |' H# T1 v3 y5 t) r
    ————————————————! Z& u' X% r! g2 u) v% r0 R
    版权声明:本文为CSDN博主「路人甲Java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。  y( n1 M0 \2 r0 t/ i, A, P
    原文链接:https://blog.csdn.net/likun557/article/details/105648757! h% f: {5 V, Q: M1 c8 I% G# A5 r
    . a/ x7 o0 C) F5 U' y

    % M3 W3 u/ Z* x6 P2 [5 `
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2025-7-23 12:31 , Processed in 0.648856 second(s), 51 queries .

    回顶部