1. 程式人生 > 程式設計 >Spring 覆盤(三) | Bean 的生命週期

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" …>
複製程式碼

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

*2、*Spring 覆盤(1) | IOC

*3、*Spring 覆盤(2) | AOP

*4、*SpringBoot | 啟動原理

5、SpringBoot | 自動配置原理

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

一個優秀的廢人