面試官:小夥子,你說一下java 對象創建和 Spring Bean 的生命週期吧

理解對象和Bean的關係

java 是一種面向對象的語言,簡而言之,一切皆對象。Bean自然也是對象,只不過它是託管給 Bean 工廠管理着的對象。

java 對象如何被創建

在寫代碼時,我們通常用下面的語句來創建一個對象:

    A a=new A();

那麼在創建對象的過程中,究竟發生了什麼呢。其實上面簡單的一句話,在程序中發生了很多很多的事情。
首先,一個對象是需要內存去存放的。所以會有一個分配內存的過程。分配了內存之後,jvm便會開始創建對象,並將它賦值給 a 變量。然後再去初始化A中的一些屬性,並執行A的構造方法。在初始化的過程中,會先執行 static 代碼塊,再執行構造方法。除此之外,如果有父類,會優先父類的進行執行。大致如下圖(圖一)所示。


如何驗證對象初始化的過程呢?用下面一段代碼驗證。這段代碼很簡單,有靜態變量的初始化,有構造方法,有繼承。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InitTest {
    private static final Logger logger = LoggerFactory.getLogger(InitTest.class);
    // 1.靜態變量初始化
    static String staticWord = "hello";
    // 2.靜態代碼塊
    static  {
        logger.info("staticWord = "+staticWord);
    }
    public InitTest(){
        logger.info("father construct method invoke...");
    }
    public static void main(String[] args) {
        new Son();
    }
    static class Son extends InitTest{
        static  {
            logger.info("son staticWord init in static");
        }
        public Son(){
            logger.info("son construct method invoke...");
        }
    }
}

運行打印的日誌如下,通過分析日誌,我們可以得出,靜態代碼塊先於構造方法,父類先於子類的執行順序。

00:55:18.869 [main] INFO com.fc.study.InitTest - staticWord = hello
00:55:18.877 [main] INFO com.fc.study.InitTest - son staticWord init in static
00:55:18.877 [main] INFO com.fc.study.InitTest - father construct method invoke...
00:55:18.877 [main] INFO com.fc.study.InitTest - son construct method invoke...

Spring Bean 的生命週期

好的,有了對象的初始化順序,我們就可以繼續分析 bean 的生命週期了。我們可以先回憶一下自己平時是怎麼定義一個 bean的。

@Component
public class TestBean{
}

@Bean
public Object myObject(){
}

常用的是上面這兩種:第一種是通過Component註解標註類;第二中方式是在方法上做@Bean的註解。我們都知道,註解標註的方法或者類,便會被spring掃描,並最終生成一個bean。本文不詳細討論bean掃描的過程,只分析bean初始化過程中的一些接口。
那麼,Spring 創建 Bean 就可以分爲兩大步驟,第一步是由Springboot 掃描並獲取BeanDefinition;第二部,是初始化Bean。spring 在bean的初始化過程爲我們提供了很多的接口,我們可以用它們在bean的生成過程中做一些事情。這些接口均採用回調的方式,以下是部分接口的介紹和回調時機。

接口 說明 回調時機
BeanNameAware 如果你的bean實現了該接口的 setName 方法,則可以通過這個方法獲取到bean名 發生在bean生命週期初期,早於構造方法
ApplicationContextAware 如果一個bean實現了該接口的setApplicationContext 方法,則可以通過此方法獲取到ApplicationContext 調用於生命週期初期,在BeanNameAware和構造方法之間
InitializingBean 此接口的方法爲 afterPropertiesSet 在bean工廠設置完bean的所有屬性之後,會回調此方法。回調時機在構造方法之後
BeanPostProcessor 此接口有 postProcessBeforeInitialization、postProcessAfterInitialization兩個方法,分別對應了Bean生命週期的兩個回調 這兩個方法也在構造方法之後,不過分別在 InitializingBean 前後

如果將上面的接口加入,則 bean 生命週期大致如下圖(圖二):


同樣,我們用代碼來驗證一下這個回調順序。用來測試的Bean代碼如下,這個測試 bean 沒有繼承其他父類,僅用來驗證springboot的接口在bean生命週期的調用時機:

package com.fc.study.beanLife;

import com.fc.study.InitTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
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.stereotype.Component;

@Component
public class TestBean implements BeanNameAware, InitializingBean, ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(InitTest.class);
    private String beanName;
    static String staticWord;
    static  {
        logger.info("father staticWord init in static");
        staticWord="hi";
    }
    public TestBean(){
        logger.info("testBean construct method invoke...");
    }

    public void setBeanName(String name) {
        logger.info("setBeanName");
        this.beanName = name;
    }

    public void afterPropertiesSet() throws Exception {
        logger.info("afterProperties Set");
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        logger.info("applictionContextAware");
    }
}

同時,我定義了一個BeanPostProcessor 如下:

package com.fc.study.beanLife;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {

    private static Logger logger = LoggerFactory.getLogger(DefaultBeanPostProcessor.class);
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("testBean")) {
            logger.info(beanName + " postProcessBeforeInitialization 執行");
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("testBean")) {
            logger.info(beanName + " postProcessAfterInitialization 執行");
        }
        return bean;
    }

    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

接下來是啓動類:

package com.fc.study.beanLife;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.fc.study")
public class SimpleSpringBoot {

    private static final Logger logger = LoggerFactory.getLogger(SimpleSpringBoot.class);

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(SimpleSpringBoot.class);

        logger.info("before get Bean");
        context.getBean(TestBean.class);
        logger.info("after get bean");
    }
}

運行啓動類,打印出日誌如下:

2021-01-23 02:18:09,764  INFO InitTest:29 - father staticWord init in static
2021-01-23 02:18:09,768  INFO InitTest:34 - testBean construct method invoke...
2021-01-23 02:18:09,768  INFO InitTest:38 - setBeanName
2021-01-23 02:18:09,768  INFO InitTest:48 - applictionContextAware
2021-01-23 02:18:09,768  INFO DefaultBeanPostProcessor:27 - testBean postProcessBeforeInitialization 執行
2021-01-23 02:18:09,768  INFO InitTest:44 - afterProperties Set
2021-01-23 02:18:09,768  INFO DefaultBeanPostProcessor:34 - testBean postProcessAfterInitialization 執行
2021-01-23 02:18:11,449  INFO SimpleSpringBoot:24 - before get Bean
2021-01-23 02:18:11,449  INFO SimpleSpringBoot:26 - after get bean

看看這個日誌,印證了圖二對各個接口調用時機結論。

總結

對象初始化,就是創建對象,並且初始化其屬性的過程。首先是加載類文件,其次對象所需要的內存。然後靜態代碼塊會被調用,最後是構造方法。
Spring Bean的初始化,除了創建對象這些步驟之外,還在其中穿插了一些生命週期的接口。首先在類加載完成後,會得到BeanDefinition,然後通過這個定義來初始化,而不是直接通過加載後的類對象來生成對象。在靜態代碼塊和構造方法中間,Spring提供了幾個Aware接口,如表格中的BeanNameAware和ApplicationContextAware。在構造方法調用結束,並且springboot給bean set了所有屬性之後,會調用Initializing接口和BeanPostProcessor。
以上,便是我理解的 spring bean 生命週期,它就是 spring 在幫我們初始化對象管理對象的過程中額外做了一些事情。

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