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

bean-lifecycle

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

一、典型的 Spring 生命週期

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

相比之下,Spring 管理 Bean 的生命週期就複雜多了,正確理解 Bean 的生命週期很是重要,由於 Spring 對 Bean 的管理可擴展性很是強,下面展現了一個 Bea 的構造過程。
Spring Bean 生命週期程序員

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

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聲明
了銷燬方法,該方法也會被調用。

二、驗證 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="turodog@foxmail.com">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("一、開始實例化 person ");
    }

    public String getName() {
        return name;
    }

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

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

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

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

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

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

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

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

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

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

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

除此以外,建立了一個 MyBeanPostProcessor 類繼承自 BeanPostProcessor 這個類只關心 Person 初始化先後要作的事情。好比,初始化以前,加載其餘 Bean。代碼以下:spring

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="turodog@foxmail.com">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("六、初始化 Person 以前執行的方法");
        }
        return bean;
    }

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

}

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

<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="turodog@foxmail.com">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("十一、實例化完成使用屬性:Person name = " + person.getName());
        // 關閉容器
        ((ClassPathXmlApplicationContext) context).close();
    }

}

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

一、開始實例化 person 
二、設置 name 屬性
三、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法
四、Person 實現了 BeanFactoryAware 接口,Spring 調用 setBeanFactory()方法,將 BeanFactory 容器實例傳入
五、Person 實現了 ApplicationContextAware 接口,Spring 調用 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來
六、初始化 Person 以前執行的方法
七、@PostConstruct 調用自定義的初始化方法
八、Person 實現了 InitializingBean 接口,Spring 調用它的afterPropertiesSet()方法。相似地,若是 person 使用 init-method 聲明瞭初始化方法,該方法也會被調用
九、xml 中聲明的 init-method 方法
十、初始化 Person 完成以後執行的方法
十一、實例化完成使用屬性:Person name = nasus
十二、@PreDestory 調用自定義銷燬方法
1三、Person 實現了 DisposableBean 接口,Spring 調用它的destroy() 接口方法。一樣,若是 person 使用 destroy-method 聲明瞭銷燬方法,該方法也會被調用
1四、xml 中聲明的 destroy-method 方法
end---------------destroy-----------------

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

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

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

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

四、多實例模式下的 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>

此時的日誌輸出以下:

一、開始實例化 person 
二、設置 name 屬性
三、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法
四、Person 實現了 BeanFactoryAware 接口,Spring 調用 setBeanFactory()方法,將 BeanFactory 容器實例傳入
五、Person 實現了 ApplicationContextAware 接口,Spring 調用 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來
六、初始化 Person 以前執行的方法
七、@PostConstruct 調用自定義的初始化方法
八、Person 實現了 InitializingBean 接口,Spring 調用它的afterPropertiesSet()方法。相似地,若是 person 使用 init-method 聲明瞭初始化方法,該方法也會被調用
九、xml 中聲明的 init-method 方法
十、初始化 Person 完成以後執行的方法
十一、實例化完成使用屬性:Person name = nasus

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

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

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

單實例 bean 的加載時機

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

多實例模式下 bean 加載時機

debug 走到 23 行時,須要用到 bean 時,bean 才被加載了,驗證以下。在開發中,咱們把這種加載叫作懶加載,它的用處就是減輕程序開銷,等到要用時才加載,而不是一上來就加載所有。

多實例模式下 bean 加載時機

六、單實例 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" …>

七、源碼地址

https://github.com/turoDog/review_spring

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

二、Spring 覆盤(1) | IOC

三、Spring 覆盤(2) | AOP

四、SpringBoot | 啓動原理

5、SpringBoot | 自動配置原理

六、Spring MVC 覆盤 | 工做原理及配置詳解

一個優秀的廢人

相關文章
相關標籤/搜索