詳解Spring中Bean的作用域與生命週期

華為雲開發者聯盟發表於2021-07-19
摘要:在利用Spring進行IOC配置時,關於bean的配置和使用一直都是比較重要的一部分,同時如何合理的使用和建立bean物件,也是小夥伴們在學習和使用Spring時需要注意的部分,所以這一篇文章我就來和大家講一下有關Spring中bean的作用域和其生命週期。

本文分享自華為雲社群《詳解Spring中Bean的作用域與生命週期》,原文作者:灰小猿。

在利用Spring進行IOC配置時,關於bean的配置和使用一直都是比較重要的一部分,同時如何合理的使用和建立bean物件,也是小夥伴們在學習和使用Spring時需要注意的部分,所以這一篇文章我就來和大家講一下有關Spring中bean的作用域和其生命週期。

一、Bean的作用域

首先我們來講一下有關於bean的作用域,

一般情況下,我們書寫在IOC容器中的配置資訊,會在我們的IOC容器執行時被建立,這就導致我們透過IOC容器獲取到bean物件的時候,往往都是獲取到了單例項的Bean物件,

這樣就意味著無論我們使用多少個getBean()方法,獲取到的同一個JavaBean都是同一個物件,這就是單例項Bean,整個專案都會共享這一個bean物件。

在Spring中,可以在<bean>元素的scope屬性裡設定bean的作用域,以決定這個bean是單例項的還是多例項的。Scope屬性有四個引數,具體的使用可以看下圖:
image.png

1、單例項Bean宣告

預設情況下,Spring只為每個在IOC容器裡宣告的bean建立唯一一個例項,整個IOC容器範圍內都能共享該例項:所有後續的getBean()呼叫和bean引用都將返回這個唯一的bean例項。該作用域被稱為singleton,它是所有bean的預設作用域。也就是單例項。

為了驗證這一說法,我們在IOC中建立一個單例項的bean,並且獲取該bean物件進行對比:

<!-- singleton單例項bean
  1、在容器建立時被建立
  2、只有一個例項
  -->
<bean id="book02" class="com.spring.beans.Book" scope="singleton"></bean>

測試獲取到的單例項bean是否是同一個:

@Test
public void test09() {
    // 單例項建立時建立的兩個bean相等
    Book book03 = (Book)iocContext3.getBean("book02");
    Book book04 = (Book)iocContext3.getBean("book02");
    System.out.println(book03==book04);
}

得到的結果是true;

2、多例項Bean宣告

而既然存在單例項,那麼就一定存在多例項。我們可以為bean物件的scope屬性設定prototype引數,以表示該例項是多例項的,同時獲取IOC容器中的多例項bean,再將獲取到的多例項bean進行對比,

<!-- prototype多例項bean
1、在容器建立時不會被建立,
2、只有在被呼叫的時候才會被建立
3、可以存在多個例項
 -->
<bean id="book01" class="com.spring.beans.Book" scope="prototype"></bean>

測試獲取到的多例項bean是否是同一個:

@Test
public void test09() {
    // 多例項建立時,建立的兩個bean物件不相等
    Book book01 = (Book)iocContext3.getBean("book01");
    Book book02 = (Book)iocContext3.getBean("book01");
    System.out.println(book01==book02);
}

得到的結果是false

這就說明了,透過多例項建立的bean物件是各不相同的。

在這裡需要注意:

同時關於單例項和多例項bean的建立也有不同,當bean的作用域為單例時,Spring會在IOC容器物件建立時就建立bean的物件例項。而當bean的作用域為prototype時,IOC容器在獲取bean的例項時建立bean的例項物件。

二、Bean的生命週期

1、bean的初始和銷燬

其實我們在IOC中建立的每一個bean物件都是有其特定的生命週期的,在Spring的IOC容器中可以管理bean的生命週期,Spring允許在bean生命週期內特定的時間點執行指定的任務。如在bean初始化時執行的方法和bean被銷燬時執行的方法。

Spring IOC容器對bean的生命週期進行管理的過程可以分為六步:

  1. 透過構造器或工廠方法建立bean例項
  2. 為bean的屬性設定值和對其他bean的引用
  3. 呼叫bean的初始化方法
  4. bean可以正常使用
  5. 當容器關閉時,呼叫bean的銷燬方法

那麼關於bean的初始和銷燬時執行的方法又該如何宣告呢?

首先我們應該在bean類內部新增初始和銷燬時執行的方法。如下面這個javabean:

package com.spring.beans;

public class Book {
    private String bookName;
    private String author;
    /**
     * 初始化方法
     * */
    public void myInit() {
        System.out.println("book bean被建立");
    }
 
    /**
     * 銷燬時方法
     * */
    public void myDestory() {
        System.out.println("book bean被銷燬");
    }
 
    public String getBookName() {
        return bookName;
    }
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    @Override
    public String toString() {
        return "Book [bookName=" + bookName + ", author=" + author + "]";
    }
}

這時我們在配置bean時,可以透過init-method和destroy-method 屬性為bean指定初始化和銷燬方法,

<!-- 設定bean的生命週期
destory-method:結束呼叫的方法
init-method:起始時呼叫的方法
 -->
<bean id="book01" class="com.spring.beans.Book" destroy-method="myDestory" init-method="myInit"></bean>

這樣當我們在透過IOC容器建立和銷燬bean物件時就會執行相應的方法,

但是這裡還是有一點需要注意:

我們上面說了,單例項的bean和多例項的bean的建立時間是不同的,那麼他們的初始方法和銷燬方法的執行時間就稍稍有不同。

  • 單例項下 bean的生命週期

容器啟動——>初始化方法——>(容器關閉)銷燬方法

  • 多例項下 bean的生命週期

容器啟動——>呼叫bean——>初始化方法——>容器關閉(銷燬方法不執行)

2、bean的後置處理器

什麼是bean的後置處理器?bean後置處理器允許在呼叫初始化方法前後對bean進行額外的處理

bean後置處理器對IOC容器裡的所有bean例項逐一處理,而非單一例項。

其典型應用是:檢查bean屬性的正確性或根據特定的標準更改bean的屬性。

bean後置處理器使用時需要實現介面:

org.springframework.beans.factory.config.BeanPostProcessor。
在初始化方法被呼叫前後,Spring將把每個bean例項分別傳遞給上述介面的以下兩個方法:

postProcessBeforeInitialization(Object, String)呼叫前
postProcessAfterInitialization(Object, String)呼叫後
如下是一個實現在該介面的後置處理器:

package com.spring.beans;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * 測試bean的後置處理器
 * 在這裡要注意一點是為了出現bean和beanName,而不是arg0、arg1,需要繫結相應的原始碼jar包
 * */
public class MyBeanPostProcessor implements BeanPostProcessor{

    /**
     * postProcessBeforeInitialization
     * 初始化方法執行前執行
     * Object bean
     * String beanName xml容器中定義的bean名稱
     * */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("【"+ beanName+"】初始化方法執行前...");
        return bean;
    }

    /**
     * postProcessAfterInitialization
     * 初始化方法執行後執行
     * Object bean
     * String beanName xml容器中定義的bean名稱
     * */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        // TODO Auto-generated method stub
        System.out.println("【"+ beanName+"】初始化方法執行後...");
        return bean;
    }

}

將該後置處理器加入到IOC容器中:

<!-- 測試bean的後置處理器 -->
<bean id="beanPostProcessor" class="com.spring.beans.MyBeanPostProcessor"></bean>

由於現在我們的bean物件是單例項的,所以容器執行時就會直接建立bean物件,同時也會執行該bean的後置處理器方法和初始化方法,在容器被銷燬時又會執行銷燬方法。我們測試如下:

//*************************bean生命週期*****************
//    由於ApplicationContext是一個頂層介面,裡面沒有銷燬方法close,所以需要使用它的子介面進行接收
    ConfigurableApplicationContext iocContext01 = new ClassPathXmlApplicationContext("ioc1.xml");
 
    @Test
    public void test01() {
        iocContext01.getBean("book01");
        iocContext01.close();
    }

執行結果:
image.png
image.png

總結一下後置處理器的執行過程:

  1. 透過構造器或工廠方法建立bean例項
  2. 為bean的屬性設定值和對其他bean的引用
  3. 將bean例項傳遞給bean後置處理器的postProcessBeforeInitialization()方法
  4. 呼叫bean的初始化方法
  5. 將bean例項傳遞給bean後置處理器的postProcessAfterInitialization()方法
  6. bean可以使用了
  7. 當容器關閉時呼叫bean的銷燬方法

所以新增bean後置處理器後bean的生命週期為:

容器啟動——後置處理器的before...——>初始化方法——>後置處理器的after...———>(容器關閉)銷燬方法

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章