Spring 高级编程(第五版)-第四章读书笔记

第四章-详述Spring配置和Spring Boot


概览:

本章主要讲述了bean的生命周期FactoryBean,Spring的event消息,以及PropertyEditor


bean的生命周期

对于bean的生命周期,之前自己的理解仅仅局限init-methoddestroy-method,知道在这个过程中会注入依赖项,最终执行到init-method然后销毁的时候执行destroy-method,对于整个bean的生命周期缺少一个完整详细的理解,整个呈现出碎片化,所以在这里先整理一下。

在本书中作者将Bean的整个生命周期概括为了4个阶段:bean实例化和Di检查Spring Awareness创建bean的生命周期回调销毁bean生命周期回调,具体如下图:

Spring bean生命周期.png

从上面的图中可以看到整个bean的整个生命周期是从扫描bean开始到自动装配,属性设置最后调用初始化方法以及调用销毁方法,在这个过程中有一个阶段叫检查Spring Awareness,那么Spring的Awareness到底是一个什么样的存在呢?第一反应是在开发中有看见过Aware结尾的类,但是一直不知道其具体的作用,带着好奇心到Spring5.x的源码中去搜索了一番,从BeanNameAware类得知Spring源码中有一个名字叫Aware的空接口,代码如下:


/**

 * A marker superinterface indicating that a bean is eligible to be notified by the

 * Spring container of a particular framework object through a callback-style method.

 * The actual method signature is determined by individual subinterfaces but should

 * typically consist of just one void-returning method that accepts a single argument.

 *

 * @author Chris Beams

 * @author Juergen Hoeller

 * @since 3.1

 */

public interface Aware {

}

可以看到Aware是一个空接口,但是在Spring5.x的源码中其实现类有多达695个,这里就不一一分析了,从接口的原注释中可以得知Aware是一个空的接口,其实际的方法应该尤其子类去实现,同时子类的实现方法应该是一个返回void的单参数的方法,并且Aware接口是Spring内置的一些功能供Spring内部使用。因为这些功能的存在,使得我们可以去获取,修改beanName,事件发布等等,Aware的原译:意识到的;知道的;有....方面知识的,所以Aware的实际意义是让Spring 容器内的bean可以感知到一些Sping内部的属性,在这里我尝试使用一个简单bean去实现BeanNameAware接口,具体代码如下:

Spring 配置文件:配置文件中仅仅存在两个简单bean,分别是AwareDemoOne,和AwareDemoTwo


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="AwareDemoOneId" class="com.liuningyun.pojo.AwareDemoOne"/>

    <bean id="AwareDemoTwoId" class="com.liuningyun.pojo.AwareDemoTwo"/>

</beans>

AwareDemoOne和AwareDemoTwo的实现:两个类的属性都只有简单的name,age,address三个属性,同时都重写了toString(这里重写toString仅仅是为了后面的日志),唯一的区别在于AwareDemoTwo实现了BeanNameAware接口,并重写了setBeanName方法,在setBeanName方法中对name进行了一个赋值


public class AwareDemoOne {

    private String name;

    private String age;

    private String address;

    //忽略getter/setter

    @Override

    public String toString() {

        return new StringJoiner(", ", AwareDemoOne.class.getSimpleName() + "[", "]")

                .add("name='" + name + "'")

                .add("age='" + age + "'")

                .add("address='" + address + "'")

                .toString();

    }

}

public class AwareDemoTwo implements BeanNameAware {

    private String name;

    private String age;

    private String address;

    //忽略getter/setter

    @Override

    public void setBeanName(String name) {

       this.name = name;

    }

    @Override

    public String toString() {

        return new StringJoiner(", ", AwareDemoTwo.class.getSimpleName() + "[", "]")

                .add("name='" + name + "'")

                .add("age='" + age + "'")

                .add("address='" + address + "'")

                .toString();

    }

}

测试main方法:在main方法中仅仅进行了getBean并将两个awareDemo实例打印出来


public class Demo {

    public static void main(String[] args) {

        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();

        ctx.load("classpath:/demo-application.xml");

        ctx.refresh();

        AwareDemoOne awareDemoOne = (AwareDemoOne) ctx.getBean("AwareDemoOneId");

        AwareDemoTwo awareDemoTwo = (AwareDemoTwo) ctx.getBean("AwareDemoTwoId");

        System.out.println(awareDemoOne);

        System.out.println(awareDemoTwo);

    }

}

打印结果:


AwareDemoOne[name='null', age='null', address='null']

AwareDemoTwo[name='AwareDemoTwoId', age='null', address='null']

我们可以看到实现了BeanNameAware接口的AwareDemoTwo实例获取到了Spring容器内的beanId而没有实现这个接口的AwareDemoOne则打印出了null,所以BeanNameAware在这里的作用就是让AwareDemoTwo感知到了Spring容器内的ID属性。

在Spring内部对于Aware的实现还有比较典型的BeanClassLoaderAware(用来感知bean的类加载器)ApplicationContextAware(用来感知bean的上下文)等等,在这里就不一一去分析了,了解到Aware提供了让bean感知内部属性的能力就算达成目标了。


  所以作者所概括的检查Spring Awareness阶段其实是在bean创建之后检查是bean是否需要进行Spring内部属性的注入,在这个阶段如果发现bean类型有实现某些Aware接口,那么将会在这个阶段调用对应的实现来完成一些内部属性的暴露。

分析了Spring Awareness阶段下面来看创建bean的生命周期阶段,创建bean的生命阶段算是比较熟悉的一块了,如果把bean的生命周期创建阶段比喻成女人生孩子,那么@PostConstruct就好比进入产房等待分娩的阶段,调用InitializingBean接口的afterPropertiesSet()方法就好比医生对小孩剪去脐带的阶段,最后医生将小孩抱出来见你的时候就是调用init-method的阶段了,至于调用destroy-method的阶段,我这里就不解释了,有人云:人固有一X。

还是贴上一段代码来证实一下:

配置文件:


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:conext="http://www.springframework.org/schema/context"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    //这里加入注解配置是因为需要使用到@PostConstruct注解

    <conext:annotation-config/>

    //注意这里的init-method指定了initMethod方法,启用lazy-init是为了防止日志打印过早

    <bean id="child" class="com.liuningyun.pojo.Child" init-method="initMethod" lazy-init="true"/>

</beans>

Child实例实现:


public class Child implements InitializingBean {

    private String name;

    private String age;

    private String address;

    //忽略getter/setter

    @PostConstruct

    public void init(){

        System.out.println("小王子诞生了...");

    }

    @Override

    public void afterPropertiesSet() throws Exception {

        System.out.println("医生剪掉了小王子的脐带...");

    }

    public void initMethod(){

        System.out.println("小王子被医生抱了出来...");

    }

可以看到该实例实现了InitializingBean接口,同时实现了afterPropertiesSet()方法,那么按照上面的说法,期望的调用顺序是:init()-》afterPropertiesSet()-》initMethod(),在这里忽略了main方法的代码,因为就只有一行简单的getBean触发了该实例的加载而已,结果如下:


小王子诞生了...

23:58:56.818 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name 'child'

医生剪掉了小王子的脐带...

23:58:56.818 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking init method 'initMethod' on bean with name 'child'

小王子被医生抱了出来...

23:58:56.819 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'child'


所以在bean的整个生命周期中其实有着严格的解析顺序,这里摘抄书中一段原话:

  所有初始化机制都可以在同一个bean实例上使用,在这种情况下,Spring首先调用使用了@PostConstruct注解的方法,然后调用afterPropertiesSet()方法,最后调用配置文件中指定的初始化方法。该顺序是由一个技术原因决定的,从图上我们可以注意到bean在创建过程中主要完成以下步骤:   
(1)首先调用构造函数来创建bean

(2)注入依赖项(调用setter)

(3)现在bean已经存在并提供了依赖项,预初始化的BeanPostProcessor基础结构bean将被查询,以查看它们是否想从创建的bean中调用任何东西。这些都是特性于Spring的基础架构bean,它们在创建后执行bean修改操作,@PosConstruct注解由CommonAnnotaionBeanPostProcessor注册,所以该bean将调用使用了@PostConstruct注解的方法,该方法在bean被创建之后,在类被投入使用之前且在bean的实际初始化之前(即在afterPropertiesSet()和init-method方法之前)执行

(4)InitializingBean的afterPropertiesSet()方法在注入依赖项后立即执行,如果BeanFactory设置了提供的所有Bean属性并且满足BeanFactoryAware和ApplicationContextAware,将会调用afterPropertiesSet()方法

(5)最后执行init-method属性,这是因为它是bean的实际初始化方法

PostConstruct官方文档:https://docs.oracle.com/javaee/7/api/javax/annotation/PostConstruct.html

本章对于Spring event的讲解非常基础,仅仅只是提到了实现ApplicationListener接口以及MessageEvent就一笔带过了,待后面详解的时候我再顺便结合JMS来做SpringEvent的笔记,关于SpringBoot相关的也仅仅是讲了基础的配置例如xml配置的lazy-init="true" 等同于@Lazy注解等,而FactoryBean相关的在第三章的拓展中已经分析过了,这里也不再记录了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容