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






    

    



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。

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

配置文件:






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

    

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

    



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相关的在第三章的拓展中已经分析过了,这里也不再记录了。

你可能感兴趣的:(Spring 高级编程(第五版)-第四章读书笔记)