Spring 覆盤(三) | Bean 的生命週期

bean-lifecycle

繼續 Spring 覆盤,今天看了下 Spring 的 Bean 生命週期。

1、典型的 Spring 生命週期

在傳統的 Java 應用中,bean 的生命週期很簡單,使用 Java 關鍵字 new 進行Bean 的實例化,然後該 Bean 就能夠使用了。一旦 bean 不再被使用,則由 Java 自動進行垃圾回收,簡直不要太簡單。

相比之下,Spring 管理 Bean 的生命週期就複雜多了,正確理解 Bean 的生命週期非常重要,因爲 Spring 對 Bean 的管理可擴展性非常強,下面展示了一個 Bea 的構造過程。
Spring Bean 生命週期

以上圖片出自 《Spring 實戰(第四版)》一書,圖片描述了一個經典的 Spring Bean 的生命週期,書中隨他的解釋如下:

1.Spring對bean進行實例化;
2.Spring將值和bean的引用注入到bean對應的屬性中;
3.如果bean實現了BeanNameAware接口,Spring將bean的ID傳遞給
setBean-Name()方法;
4.如果bean實現了BeanFactoryAware接口,Spring將調
用setBeanFactory()方法,將BeanFactory容器實例傳入;
5.如果bean實現了ApplicationContextAware接口,Spring將調
用setApplicationContext()方法,將bean所在的應用上下文的
引用傳入進來;
6.如果bean實現了BeanPostProcessor接口,Spring將調用它們
的post-ProcessBeforeInitialization()方法;
7.如果bean實現了InitializingBean接口,Spring將調用它們的
after-PropertiesSet()方法。類似地,如果bean使用init-
method聲明瞭初始化方法,該方法也會被調用;
8.如果bean實現了BeanPostProcessor接口,Spring將調用它們
的post-ProcessAfterInitialization()方法;
9.此時,bean已經準備就緒,可以被應用程序使用了,它們將一直
駐留在應用上下文中,直到該應用上下文被銷燬;
10.如果bean實現了DisposableBean接口,Spring將調用它的
destroy()接口方法。同樣,如果bean使用destroy-method聲明
了銷燬方法,該方法也會被調用。

2、驗證 Spring Bean 週期

寫了下代碼驗證以上說法,首先創建一個 Person 類,它就是我們要驗證的 Bean ,爲方便測試,他實現了 BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean。代碼如下:

package com.nasus.bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Scope;

/**
 * Project Name:review_spring <br/>
 * Package Name:PACKAGE_NAME <br/>
 * Date:2019/9/1 16:29 <br/>
 *
 * @author <a href="[email protected]">chenzy</a><br/>
 */
@Scope("ProtoType")
public class Person implements BeanNameAware, BeanFactoryAware,
        ApplicationContextAware, InitializingBean, DisposableBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(Person.class);

    private String name;

    public Person(){
        System.out.println("1、開始實例化 person ");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2、設置 name 屬性");
    }

    @Override
    public void setBeanName(String beanId) {
        System.out.println("3、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 "
                + "ID=" + beanId + "傳遞給 setBeanName 方法");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("4、Person 實現了 BeanFactoryAware 接口,Spring 調"
                + "用 setBeanFactory()方法,將 BeanFactory 容器實例傳入");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("5、Person 實現了 ApplicationContextAware 接口,Spring 調"
                + "用 setApplicationContext()方法,將 person 所在的應用上下文的"
                + "引用傳入進來");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("8、Person 實現了 InitializingBean 接口,Spring 調用它的"
                + "afterPropertiesSet()方法。類似地,如果 person 使用 init-"
                + "method 聲明瞭初始化方法,該方法也會被調用");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("13、Person 實現了 DisposableBean 接口,Spring 調用它的"
                + "destroy() 接口方法。同樣,如果 person 使用 destroy-method 聲明"
                + "了銷燬方法,該方法也會被調用");
    }

    /**
     * xml 中聲明的 init-method 方法
     */
    public void initMethod(){
        System.out.println("9、xml 中聲明的 init-method 方法");
    }

    /**
     * xml 中聲明的 destroy-method 方法
     */
    public void destroyMethod(){
        System.out.println("14、xml 中聲明的 destroy-method 方法");
        System.out.println("end---------------destroy-----------------");
    }

    // 自定義初始化方法
    @PostConstruct
    public void springPostConstruct(){
        System.out.println("7、@PostConstruct 調用自定義的初始化方法");
    }

    // 自定義銷燬方法
    @PreDestroy
    public void springPreDestory(){
        System.out.println("12、@PreDestory 調用自定義銷燬方法");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize 方法");
    }
}

除此之外,創建了一個 MyBeanPostProcessor 類繼承自 BeanPostProcessor 這個類只關心 Person 初始化前後要做的事情。比如,初始化之前,加載其他 Bean。代碼如下:

package com.nasus.lifecycle;

import com.nasus.bean.Person;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * Project Name:review_spring <br/>
 * Package Name:PACKAGE_NAME <br/>
 * Date:2019/9/1 16:25 <br/>
 *
 * @author <a href="[email protected]">chenzy</a><br/>
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

    // 容器加載的時候會加載一些其他的 bean,會調用初始化前和初始化後方法
    // 這次只關注 Person 的生命週期
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof Person){
            System.out.println("6、初始化 Person 之前執行的方法");
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof Person){
            System.out.println("10、初始化 Person 完成之後執行的方法");
        }
        return bean;
    }

}

resource 文件夾下新建一個 bean_lifecycle.xml 文件注入相關 bean ,代碼如下:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 掃描bean -->
    <context:component-scan base-package="com.nasus"/>

    <!-- 實現了用戶自定義初始化和銷燬方法 -->
    <bean id="person" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
        <!-- 注入bean 屬性名稱 -->
        <property name="name" value="nasus" />
    </bean>

    <!--引入自定義的BeanPostProcessor-->
    <bean class="com.nasus.lifecycle.MyBeanPostProcessor"/>

</beans>

測試類,獲取 person 這個 Bean 並使用它,代碼如下:

import com.nasus.bean.Person;
import java.awt.print.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Project Name:review_spring <br/>
 * Package Name:PACKAGE_NAME <br/>
 * Date:2019/9/1 16:38 <br/>
 *
 * @author <a href="[email protected]">chenzy</a><br/>
 */
public class lifeCycleTest {

    @Test
    public void testLifeCycle(){
        // 爲面試而準備的Bean生命週期加載過程
        ApplicationContext context = new ClassPathXmlApplicationContext("bean_lifecycle.xml");
        Person person = (Person)context.getBean("person");
        // 使用屬性
        System.out.println("11、實例化完成使用屬性:Person name = " + person.getName());
        // 關閉容器
        ((ClassPathXmlApplicationContext) context).close();
    }

}

lifeCycleTest 方法最後關閉了容器,關閉的同時控制檯日誌輸出如下:

1、開始實例化 person 
2、設置 name 屬性
3、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法
4、Person 實現了 BeanFactoryAware 接口,Spring 調用 setBeanFactory()方法,將 BeanFactory 容器實例傳入
5、Person 實現了 ApplicationContextAware 接口,Spring 調用 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來
6、初始化 Person 之前執行的方法
7、@PostConstruct 調用自定義的初始化方法
8、Person 實現了 InitializingBean 接口,Spring 調用它的afterPropertiesSet()方法。類似地,如果 person 使用 init-method 聲明瞭初始化方法,該方法也會被調用
9、xml 中聲明的 init-method 方法
10、初始化 Person 完成之後執行的方法
11、實例化完成使用屬性:Person name = nasus
12、@PreDestory 調用自定義銷燬方法
13、Person 實現了 DisposableBean 接口,Spring 調用它的destroy() 接口方法。同樣,如果 person 使用 destroy-method 聲明瞭銷燬方法,該方法也會被調用
14、xml 中聲明的 destroy-method 方法
end---------------destroy-----------------

由以上日誌可知,當 person 默認是單例模式時,bean 的生命週期與容器的生命週期一樣,容器初始化,bean 也初始化。容器銷燬,bean 也被銷燬。那如果,bean 是非單例呢?

3、在 Bean 實例化完成後,銷燬前搞事情

有時我們需要在 Bean 屬性值 set 好之後和 Bean 銷燬之前做一些事情,比如檢查 Bean 中某個屬性是否被正常的設置好值了。Spring 框架提供了多種方法讓我們可以在 Spring Bean 的生命週期中執行 initialization 和 pre-destroy 方法。這些方法我在上面已經測試過了,以上代碼實現了多種方法,它是重複,開發中選以下其一即可,比如:

  • 在配置文件中指定的 init-method 和 destroy-method 方法
  • 實現 InitializingBean 和 DisposableBean 接口
  • 使用 @PostConstruct 和 @PreDestroy 註解(牆裂推薦使用)

4、多實例模式下的 Bean 生命週期

上面測試中的 person 默認是 singleton 的,現在我們將 person 改爲 protoType 模式,bean_lifecycle.xml 做如下代碼修改,其餘類保持不變:

<!-- 實現了用戶自定義初始化和銷燬方法 -->
<bean id="person" scope="prototype" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
     <!-- 注入bean 屬性名稱 -->
     <property name="name" value="nasus" />
</bean>

此時的日誌輸出如下:

1、開始實例化 person 
2、設置 name 屬性
3、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法
4、Person 實現了 BeanFactoryAware 接口,Spring 調用 setBeanFactory()方法,將 BeanFactory 容器實例傳入
5、Person 實現了 ApplicationContextAware 接口,Spring 調用 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來
6、初始化 Person 之前執行的方法
7、@PostConstruct 調用自定義的初始化方法
8、Person 實現了 InitializingBean 接口,Spring 調用它的afterPropertiesSet()方法。類似地,如果 person 使用 init-method 聲明瞭初始化方法,該方法也會被調用
9、xml 中聲明的 init-method 方法
10、初始化 Person 完成之後執行的方法
11、實例化完成使用屬性:Person name = nasus

此時,容器關閉,person 對象並沒有銷燬。原因在於,單實例模式下,bean 的生命週期由容器管理,容器生,bean 生;容器死,bean 死。而在多實例模式下,Spring 就管不了那麼多了,bean 的生命週期,交由客戶端也就是程序員或者 JVM 來進行管理。

5、多實例模式下 Bean 的加載時機

首先說說單實例,單實例模式下,bean 在容器加載那一刻起,就已經完成實例化了,證明如下,我啓用 debug 模式,在 20 行打了一個斷點,而日誌卻如下所示,說明了 bean 在 19 行,初始化容器的時候,已經完成實例化了。

單實例 bean 的加載時機

再說多實例模式下,這個模式下,bean 在需要用到 bean 的時候才進行初始化,證明如下,同樣執行完 19 行,多實例模式下,控制檯一片空白,說明此時的 bean 是未被加載的。

多實例模式下 bean 加載時機

debug 走到 23 行時,需要用到 bean 時,bean 才被加載了,驗證如下。在開發中,我們把這種加載叫做懶加載,它的用處就是減輕程序開銷,等到要用時才加載,而不是一上來就加載全部。

多實例模式下 bean 加載時機

6、單實例 bean 如何實現延遲加載

只需在 xml 中加上 lazy-init 屬性爲 true 即可。如下,它的加載方式就變成了懶加載。

<!-- 實現了用戶自定義初始化和銷燬方法 -->
<bean id="person" lazy-init="true" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
     <!-- 注入bean 屬性名稱 -->
     <property name="name" value="nasus" />
</bean>

如果想對所有的默認單例 bean 都應用延遲初始化,可以在根節點 beans 設置 default-lazy-init 屬性爲 true,如下所示:

<beans default-lazy-init="true" …>

7、源碼地址

https://github.com/turoDog/review_spring

推薦閱讀:
1、java | 什麼是動態代理

2、Spring 覆盤(1) | IOC

3、Spring 覆盤(2) | AOP

4、SpringBoot | 啓動原理

5、SpringBoot | 自動配置原理

6、Spring MVC 覆盤 | 工作原理及配置詳解

一個優秀的廢人

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章