Spring 高級編程(第五版)-第四章讀書筆記

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


概覽:

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


bean的生命周期

對于bean的生命周期,之前自己的理解僅僅局限init-methoddestroy-method,知道在這個過程中會注入依賴項,最終執(zhí)行到init-method然后銷毀的時候執(zhí)行destroy-method,對于整個bean的生命周期缺少一個完整詳細(xì)的理解,整個呈現(xiàn)出碎片化,所以在這里先整理一下。

在本書中作者將Bean的整個生命周期概括為了4個階段:bean實例化和Di、檢查Spring Awareness、創(chuàng)建bean的生命周期回調(diào)、銷毀bean生命周期回調(diào),具體如下圖:

Spring bean生命周期.png

從上面的圖中可以看到整個bean的整個生命周期是從掃描bean開始到自動裝配,屬性設(shè)置最后調(diào)用初始化方法以及調(diào)用銷毀方法,在這個過程中有一個階段叫檢查Spring Awareness,那么Spring的Awareness到底是一個什么樣的存在呢?第一反應(yīng)是在開發(fā)中有看見過Aware結(jié)尾的類,但是一直不知道其具體的作用,帶著好奇心到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的源碼中其實現(xiàn)類有多達(dá)695個,這里就不一一分析了,從接口的原注釋中可以得知Aware是一個空的接口,其實際的方法應(yīng)該尤其子類去實現(xiàn),同時子類的實現(xiàn)方法應(yīng)該是一個返回void的單參數(shù)的方法,并且Aware接口是Spring內(nèi)置的一些功能供Spring內(nèi)部使用。因為這些功能的存在,使得我們可以去獲取,修改beanName,事件發(fā)布等等,Aware的原譯:意識到的;知道的;有....方面知識的,所以Aware的實際意義是讓Spring 容器內(nèi)的bean可以感知到一些Sping內(nèi)部的屬性,在這里我嘗試使用一個簡單bean去實現(xiàn)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的實現(xiàn):兩個類的屬性都只有簡單的name,age,address三個屬性,同時都重寫了toString(這里重寫toString僅僅是為了后面的日志),唯一的區(qū)別在于AwareDemoTwo實現(xiàn)了BeanNameAware接口,并重寫了setBeanName方法,在setBeanName方法中對name進(jìn)行了一個賦值


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方法中僅僅進(jìn)行了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);

    }

}

打印結(jié)果:


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

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

我們可以看到實現(xiàn)了BeanNameAware接口的AwareDemoTwo實例獲取到了Spring容器內(nèi)的beanId而沒有實現(xiàn)這個接口的AwareDemoOne則打印出了null,所以BeanNameAware在這里的作用就是讓AwareDemoTwo感知到了Spring容器內(nèi)的ID屬性。

在Spring內(nèi)部對于Aware的實現(xiàn)還有比較典型的BeanClassLoaderAware(用來感知bean的類加載器)ApplicationContextAware(用來感知bean的上下文)等等,在這里就不一一去分析了,了解到Aware提供了讓bean感知內(nèi)部屬性的能力就算達(dá)成目標(biāo)了。


  所以作者所概括的檢查Spring Awareness階段其實是在bean創(chuàng)建之后檢查是bean是否需要進(jìn)行Spring內(nèi)部屬性的注入,在這個階段如果發(fā)現(xiàn)bean類型有實現(xiàn)某些Aware接口,那么將會在這個階段調(diào)用對應(yīng)的實現(xiàn)來完成一些內(nèi)部屬性的暴露。

分析了Spring Awareness階段下面來看創(chuàng)建bean的生命周期階段,創(chuàng)建bean的生命階段算是比較熟悉的一塊了,如果把bean的生命周期創(chuàng)建階段比喻成女人生孩子,那么@PostConstruct就好比進(jìn)入產(chǎn)房等待分娩的階段,調(diào)用InitializingBean接口的afterPropertiesSet()方法就好比醫(yī)生對小孩剪去臍帶的階段,最后醫(yī)生將小孩抱出來見你的時候就是調(diào)用init-method的階段了,至于調(diào)用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實例實現(xiàn):


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("醫(yī)生剪掉了小王子的臍帶...");

    }

    public void initMethod(){

        System.out.println("小王子被醫(yī)生抱了出來...");

    }

可以看到該實例實現(xiàn)了InitializingBean接口,同時實現(xiàn)了afterPropertiesSet()方法,那么按照上面的說法,期望的調(diào)用順序是:init()-》afterPropertiesSet()-》initMethod(),在這里忽略了main方法的代碼,因為就只有一行簡單的getBean觸發(fā)了該實例的加載而已,結(jié)果如下:


小王子誕生了...

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

醫(yī)生剪掉了小王子的臍帶...

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

小王子被醫(yī)生抱了出來...

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


所以在bean的整個生命周期中其實有著嚴(yán)格的解析順序,這里摘抄書中一段原話:

  所有初始化機(jī)制都可以在同一個bean實例上使用,在這種情況下,Spring首先調(diào)用使用了@PostConstruct注解的方法,然后調(diào)用afterPropertiesSet()方法,最后調(diào)用配置文件中指定的初始化方法。該順序是由一個技術(shù)原因決定的,從圖上我們可以注意到bean在創(chuàng)建過程中主要完成以下步驟:   
(1)首先調(diào)用構(gòu)造函數(shù)來創(chuàng)建bean

(2)注入依賴項(調(diào)用setter)

(3)現(xiàn)在bean已經(jīng)存在并提供了依賴項,預(yù)初始化的BeanPostProcessor基礎(chǔ)結(jié)構(gòu)bean將被查詢,以查看它們是否想從創(chuàng)建的bean中調(diào)用任何東西。這些都是特性于Spring的基礎(chǔ)架構(gòu)bean,它們在創(chuàng)建后執(zhí)行bean修改操作,@PosConstruct注解由CommonAnnotaionBeanPostProcessor注冊,所以該bean將調(diào)用使用了@PostConstruct注解的方法,該方法在bean被創(chuàng)建之后,在類被投入使用之前且在bean的實際初始化之前(即在afterPropertiesSet()和init-method方法之前)執(zhí)行

(4)InitializingBean的afterPropertiesSet()方法在注入依賴項后立即執(zhí)行,如果BeanFactory設(shè)置了提供的所有Bean屬性并且滿足BeanFactoryAware和ApplicationContextAware,將會調(diào)用afterPropertiesSet()方法

(5)最后執(zhí)行init-method屬性,這是因為它是bean的實際初始化方法

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

本章對于Spring event的講解非常基礎(chǔ),僅僅只是提到了實現(xiàn)ApplicationListener接口以及MessageEvent就一筆帶過了,待后面詳解的時候我再順便結(jié)合JMS來做SpringEvent的筆記,關(guān)于SpringBoot相關(guān)的也僅僅是講了基礎(chǔ)的配置例如xml配置的lazy-init="true" 等同于@Lazy注解等,而FactoryBean相關(guān)的在第三章的拓展中已經(jīng)分析過了,這里也不再記錄了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容