Spring Bean生命週期,好像人的一生。。

三分惡發表於2022-03-15

大家好,我是老三,上節我們手擼了一個簡單的IOC容器五分鐘,手擼一個Spring容器!,這節我們來看一看Spring中Bean的生命週期,我發現,和人的一生真的很像。

簡單說說IoC和Bean

IoC,控制反轉,想必大家都知道,所謂的控制反轉,就是把new物件的權利交給容器,所有的物件都被容器控制,這就叫所謂的控制反轉。

控制反轉

Bean,也不是什麼新鮮玩意兒,它們就是一幫身不由己的Java物件,生命週期受到容器控制。

Bean生命週期和人生

Bean生命週期四大階段

我們知道,bean的作用域有好幾種,這篇文章只討論完全被IoC容器控制的單例Bean。

對於普通的Java物件來說,它們的生命週期就是:

  • 例項化
  • 物件不再被使用時通過垃圾回收機制進行回收

這就像是生活在大自然裡的動物,悄然出生,悄然死亡。

大象-圖片來源網路

而對於Spring Bean的生命週期來說,可以分為四個階段,其中初始化完成之後,就代表這個Bean可以使用了:

  • 例項化 Instantiation
  • 屬性賦值 Populate
  • 初始化 Initialization
  • 銷燬 Destruction

人和動物不一樣,存在非常複雜的社會。

高樓大廈中的行人

我們來看看社會裡的人,一生要經歷哪些階段,是不是和Bean的生命週期很像呢?

  • 出生:作為一個自然人降臨在這個世界
  • 登記:登記身份證號,姓名,正式成為人類社會的一份子
  • 成長:接受教育,成為對社會有用的人
  • 工作:為社會創造價值
  • 死亡:人死如燈滅,不過人這盞燈滅了,還要把燈臺埋起來

image-20220303101042089

Bean例項化的時機也分為兩種,BeanFactory管理的Bean是在使用到Bean的時候才會例項化Bean,ApplicantContext管理的Bean在容器初始化的時候就回完成Bean例項化。

BeanFactory就是相對不那麼健全的原始一些的社會,ApplicantContext是發達健全的現代社會。

BeanFactory和Applicantcontext

Bean詳細生命週期

我們講到了Bean容器四個階段,會有一些容器級的方法,進行前置和後置的處理,比如InstantiationAwareBeanPostProcessor、BeanPostProcessor介面方法。這些方法獨立於Bean之外,並且會註冊到Spring容器中,在Spring容器建立Bean的時候,進行一些處理。

後處理器

這就好像,孩子出生之前,需要做一些準備,比如備孕、養胎、備產什麼的,出生之後,需要做一些護理。孩子上學前後,也需要做一些學籍的管理。

那麼有了各種各樣的擴充套件之後,我們再接著看看Bean的詳細的生命週期。首先,我們面臨一個問題——Bean的生命週期從什麼時候開始的呢?

上面寫了,Bean例項化前後,可以進行一些處理,但是如果從Bean例項化前算開始,那麼就要追溯到容器的初始化、beanDefiinition的載入開始。

所以這篇文章裡,我們取生命週期直接從Bean例項化開始,但是大家也要知道,Bean例項化前後,可以使用後處理器進行處理,例如BeanFactoryPostProcessor、InstantiationAwareBeanPostProcessor。

大家也不要困擾,就像計算人生的起點,是從母親懷孕算起,還是從孩子出生算起?我們這裡取了出生開始而已。

Bean生命週期

  • 例項化:第 1 步,例項化一個 Bean 物件
  • 屬性賦值:第 2 步,為 Bean 設定相關屬性和依賴
  • 初始化:初始化的階段的步驟比較多,5、6步是真正的初始化,第 3、4 步為在初始化前執行,第 7 步在初始化後執行,初始化完成之後,Bean就可以被使用了
  • 銷燬:第 8~10步,第8步其實也可以算到銷燬階段,但不是真正意義上的銷燬,而是先在使用前註冊了銷燬的相關呼叫介面,為了後面第9、10步真正銷燬 Bean 時再執行相應的方法

我們發現Bean生命週期的詳細過程,是不是也像人生的歷程,出生、登記,不過是很短的事情。慢慢長大成人,要經歷人生的四分之一,而成長,來源於教育,不管是學校的還是社會的,接受教育前,要登記學籍,上學的時候,自己還要努力……,到最後,要發一紙薄薄的文憑,標誌著我們成為可以捶打的“社會人”。

然後,為社會奉獻四十年。最後老去,離世。不過Bean的世界,沒有退休——當然,也許,人的世界也沒有退休。

人的曲線

我們發現中間的一些擴充套件過程也可以分四類:

Bean週期四類過程

  • 一:獲取社會資源/Aware介面:Aware介面的作用是讓Bean能拿到容器的一些資源,例如BeanNameAware可以拿到BeanName。就好像上學之前,要取一個學名——不知道多少人上學之前不知道自己大名叫什麼,是吧?二毛。

  • 二:必備各種手續和證/後處理器:在Bean的生命週期裡,會有一些後處理器,它們的作用就是進行一些前置和後置的處理,就像上學之前,需要登記學籍,上學之後,會拿到畢業證。

  • 三:個人選擇/生命週期介面:人可能無法選擇如何出生,但也許可以選擇如何活著和如何死去,InitializingBean和DisposableBean 介面就是用來定義初始化方法和銷燬方法的。

  • 四:主觀能動/配置生命週期方法:環境影響人,人也在影響環境,成長的時候認真努力,衰亡的時候也可以豁達樂觀。可以通過配置檔案,自定義初始化和銷燬方法。

PersonBean的一生

話不多說,接下來我們拿一個例子,來看看PersonBean的一生,我們先來看一下它的流程!

PersonBean的一生

用文字描述一下這個過程:

  1. Bean容器在配置檔案中找到Person Bean的定義,這個可以說是媽媽懷上了。
  2. Bean容器使用Java 反射API建立Bean的例項,孩子出生了。
  3. Person宣告瞭屬性no、name,它們會被設定,相當於註冊身份證號和姓名。如果屬性本身是Bean,則將對其進行解析和設定。
  4. Person類實現了BeanNameAware介面,通過傳遞Bean的名稱來呼叫setBeanName()方法,相當於起個學名。
  5. Person類實現了BeanFactoryAware介面,通過傳遞BeanFactory物件的例項來呼叫setBeanFactory()方法,就像是選了一個學校。
  6. PersonBean實現了BeanPostProcessor介面,在初始化之前呼叫用postProcessBeforeInitialization()方法,相當於入學報名。
  7. PersonBean類實現了InitializingBean介面,在設定了配置檔案中定義的所有Bean屬性後,呼叫afterPropertiesSet()方法,就像是入學登記。
  8. 配置檔案中的Bean定義包含init-method屬性,該屬性的值將解析為Person類中的方法名稱,初始化的時候會呼叫這個方法,成長不是走個流程,還需要自己不斷努力。
  9. Bean Factory物件如果附加了Bean 後置處理器,就會呼叫postProcessAfterInitialization()方法,畢業了,總得拿個證。
  10. Person類實現了DisposableBean介面,則當Application不再需要Bean引用時,將呼叫destroy()方法,簡單說,就是人掛了。
  11. 配置檔案中的Person Bean定義包含destroy-method屬性,所以會呼叫Person類中的相應方法定義,相當於選好地兒,埋了。

我們來看看程式碼!

PersonBean類

建立一個PersonBean,讓它實現幾個特殊的介面,我們來觀察一下它的生命週期的流轉。

public class PersonBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {

    /**
     * 身份證號
     */
    private Integer no;

    /**
     * 姓名
     */
    private String name;

    public PersonBean() {
        System.out.println("1.呼叫構造方法:我出生了!");
    }

    public Integer getNo() {
        return no;
    }

    public void setNo(Integer no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2.設定屬性:我的名字叫"+name);
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("3.呼叫BeanNameAware#setBeanName方法:我要上學了,起了個學名");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("4.呼叫BeanFactoryAware#setBeanFactory方法:選好學校了");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("6.InitializingBean#afterPropertiesSet方法:入學登記");
    }

    public void init() {
        System.out.println("7.自定義init方法:努力上學ing");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("9.DisposableBean#destroy方法:平淡的一生落幕了");
    }

    public void destroyMethod() {
        System.out.println("10.自定義destroy方法:睡了,別想叫醒我");
    }

    public void work(){
        System.out.println("Bean使用中:工作,只有對社會沒有用的人才放假。。");
    }

}

  • 實現了InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean四個介面
  • 定義了no、name兩個屬性和對應的getter、setter方法
  • 定義了一個例項方法work

MyBeanPostProcessor

自定義了一個後處理器MyBeanPostProcessor:

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5.BeanPostProcessor.postProcessBeforeInitialization方法:到學校報名啦");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("8.BeanPostProcessor#postProcessAfterInitialization方法:終於畢業,拿到畢業證啦!");
        return bean;
    }
}

配置檔案

定義一個配置檔案spring-config.xml:

  • 使用setter注入
  • 定義init-method和destroy-method
<?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 name="myBeanPostProcessor" class="cn.fighter3.spring.life.MyBeanPostProcessor" />
    <bean name="personBean" class="cn.fighter3.spring.life.PersonBean"
          init-method="init" destroy-method="destroyMethod">
        <property name="idNo" value= "80669865"/>
        <property name="name" value="張鐵鋼" />
    </bean>

</beans>

測試

最後測試一下,觀察PersonBean的生命週期的流轉:

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        PersonBean personBean = (PersonBean) context.getBean("personBean");
        personBean.work();
        ((ClassPathXmlApplicationContext) context).destroy();
    }
}

執行結果:

1.呼叫構造方法:我出生了!
2.設定屬性:我的名字叫張鐵鋼
3.呼叫BeanNameAware#setBeanName方法:我要上學了,起了個學名
4.呼叫BeanFactoryAware#setBeanFactory方法:選好學校了
5.BeanPostProcessor#postProcessBeforeInitialization方法:到學校報名啦
6.InitializingBean#afterPropertiesSet方法:入學登記
7.自定義init方法:努力上學ing
8.BeanPostProcessor#postProcessAfterInitialization方法:終於畢業,拿到畢業證啦!
Bean使用中:工作,只有對社會沒有用的人才放假。。
9.DisposableBean#destroy方法:平淡的一生落幕了
10.自定義destroy方法:睡了,別想叫醒我

看看,是不是和我們圖中的流程一致。

這篇文章就不帶大家跟進更多的原始碼了,如果大家對原始碼級別的Bean的生命週期感興趣,可以看看AbstractApplicationContext類裡的refresh方法,這個方法是AplicationContext容器初始化的關鍵點。在這個方法裡,呼叫了finishBeanFactoryInitialization方法,這個方法裡呼叫了getBean方法,getBean方法裡呼叫了AbstractBeanFactorygetBean方法。

Bean生命週期原始碼追蹤

最終經過一陣七拐八繞,到達了我們的目標——Bean建立的方法:doGetBean方法,在這個方法裡可以看到Bean的例項化,賦值、初始化的過程,至於最終的銷燬,可以看看ConfigurableApplicationContext#close()

結語

到這,這篇Bean的生命週期文章就走向destory了,自定義destory方法——回顧一下這篇文章的“一生”。

  • Bean的生命週期大致可以分為四個階段:例項化、屬性賦值、初始化、銷燬,對應人生的出生、登記、成長、離世。
  • Bean生命週期中可以有很多擴充套件,就像人生的走向,會受很多影響,社會的環境、自身的選擇、自己的努力。


參考:

[1]. 《Spring揭祕》

[2]. Spring官網

[3].《精通Spring4.X企業應用開發實戰》

[4] .Spring Bean 生命週期 (例項結合原始碼徹底講透)

[5].一文讀懂 Spring Bean 的生命週期

[6].如何記憶 Spring Bean 的生命週期

相關文章