java 是一種面向對象的語言,簡而言之,一切皆對象。Bean天然也是對象,只不過它是託管給 Bean 工廠管理着的對象。java
在寫代碼時,咱們一般用下面的語句來建立一個對象:spring
A a=new A();
那麼在建立對象的過程當中,究竟發生了什麼呢。其實上面簡單的一句話,在程序中發生了不少不少的事情。
首先,一個對象是須要內存去存放的。因此會有一個分配內存的過程。分配了內存以後,jvm便會開始建立對象,並將它賦值給 a 變量。而後再去初始化A中的一些屬性,並執行A的構造方法。在初始化的過程當中,會先執行 static 代碼塊,再執行構造方法。除此以外,若是有父類,會優先父類的進行執行。大體以下圖(圖一)所示。
springboot
如何驗證對象初始化的過程呢?用下面一段代碼驗證。這段代碼很簡單,有靜態變量的初始化,有構造方法,有繼承。app
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..."); } } }
運行打印的日誌以下,經過分析日誌,咱們能夠得出,靜態代碼塊先於構造方法,父類先於子類的執行順序。jvm
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...
好的,有了對象的初始化順序,咱們就能夠繼續分析 bean 的生命週期了。咱們能夠先回憶一下本身平時是怎麼定義一個 bean的。post
@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 生命週期大體以下圖(圖二):
this
一樣,咱們用代碼來驗證一下這個回調順序。用來測試的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 以下:code
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 在幫咱們初始化對象管理對象的過程當中額外作了一些事情。