理解對象和Bean的關(guān)系
java 是一種面向?qū)ο蟮恼Z言,簡而言之儡毕,一切皆對象也切。Bean自然也是對象,只不過它是托管給 Bean 工廠管理著的對象妥曲。
java 對象如何被創(chuàng)建
在寫代碼時贾费,我們通常用下面的語句來創(chuàng)建一個對象:
A a=new A();
那么在創(chuàng)建對象的過程中,究竟發(fā)生了什么呢檐盟。其實上面簡單的一句話,在程序中發(fā)生了很多很多的事情押桃。
首先葵萎,一個對象是需要內(nèi)存去存放的。所以會有一個分配內(nèi)存的過程唱凯。分配了內(nèi)存之后羡忘,jvm便會開始創(chuàng)建對象,并將它賦值給 a 變量磕昼。然后再去初始化A中的一些屬性卷雕,并執(zhí)行A的構(gòu)造方法。在初始化的過程中票从,會先執(zhí)行 static 代碼塊,再執(zhí)行構(gòu)造方法漫雕。除此之外滨嘱,如果有父類,會優(yōu)先父類的進行執(zhí)行浸间。大致如下圖所示太雨。
如何驗證對象初始化的過程呢?用下面一段代碼驗證魁蒜。這段代碼很簡單囊扳,有靜態(tài)變量的初始化,有構(gòu)造方法兜看,有繼承锥咸。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InitTest {
private static final Logger logger = LoggerFactory.getLogger(InitTest.class);
// 1.靜態(tài)變量初始化
static String staticWord = "hello";
// 2.靜態(tài)代碼塊
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...");
}
}//歡迎加入Java開發(fā)交流君樣:909038429
}
運行打印的日志如下,通過分析日志细移,我們可以得出她君,靜態(tài)代碼塊先于構(gòu)造方法,父類先于子類的執(zhí)行順序葫哗。
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 的生命周期
好的缔刹,有了對象的初始化順序,我們就可以繼續(xù)分析 bean 的生命周期了劣针。我們可以先回憶一下自己平時是怎么定義一個 bean的校镐。
@Component
public class TestBean{
}
@Bean
public Object myObject(){
}
常用的是上面這兩種:第一種是通過Component注解標注類;第二中方式是在方法上做@Bean的注解捺典。我們都知道鸟廓,注解標注的方法或者類,便會被spring掃描襟己,并最終生成一個bean引谜。本文不詳細討論bean掃描的過程,只分析bean初始化過程中的一些接口擎浴。
那么员咽,Spring 創(chuàng)建 Bean 就可以分為兩大步驟,第一步是由Springboot 掃描并獲取BeanDefinition贮预;第二部贝室,是初始化Bean。spring 在bean的初始化過程為我們提供了很多的接口仿吞,我們可以用它們在bean的生成過程中做一些事情滑频。這些接口均采用回調(diào)的方式,以下是部分接口的介紹和回調(diào)時機唤冈。
如果將上面的接口加入峡迷,則 bean 生命周期大致如下圖:
同樣,我們用代碼來驗證一下這個回調(diào)順序你虹。用來測試的Bean代碼如下绘搞,這個測試 bean 沒有繼承其他父類彤避,僅用來驗證springboot的接口在bean生命周期的調(diào)用時機:
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 執(zhí)行");
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(beanName.equals("testBean")) {
logger.info(beanName + " postProcessAfterInitialization 執(zhí)行");
}
return bean;
}//歡迎加入Java開發(fā)交流君樣:909038429
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 執(zhí)行
2021-01-23 02:18:09,768 INFO InitTest:44 - afterProperties Set
2021-01-23 02:18:09,768 INFO DefaultBeanPostProcessor:34 - testBean postProcessAfterInitialization 執(zhí)行
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
看看這個日志忠藤,印證了圖二對各個接口調(diào)用時機結(jié)論。
總結(jié)
對象初始化楼雹,就是創(chuàng)建對象模孩,并且初始化其屬性的過程。首先是加載類文件贮缅,其次對象所需要的內(nèi)存榨咐。然后靜態(tài)代碼塊會被調(diào)用,最后是構(gòu)造方法谴供。
Spring Bean的初始化块茁,除了創(chuàng)建對象這些步驟之外,還在其中穿插了一些生命周期的接口桂肌。首先在類加載完成后数焊,會得到BeanDefinition,然后通過這個定義來初始化崎场,而不是直接通過加載后的類對象來生成對象佩耳。在靜態(tài)代碼塊和構(gòu)造方法中間,Spring提供了幾個Aware接口谭跨,如表格中的BeanNameAware和ApplicationContextAware干厚。在構(gòu)造方法調(diào)用結(jié)束,并且springboot給bean set了所有屬性之后螃宙,會調(diào)用Initializing接口和BeanPostProcessor蛮瞄。
以上,便是我理解的 spring bean 生命周期谆扎,它就是 spring 在幫我們初始化對象管理對象的過程中額外做了一些事情挂捅。
最新2020整理收集的一些高頻面試題(都整理成文檔),有很多干貨燕酷,包含mysql籍凝,netty,spring苗缩,線程,spring cloud声诸、jvm酱讶、源碼、算法等詳細講解彼乌,也有詳細的學習規(guī)劃圖泻肯,面試題整理等渊迁,需要獲取這些內(nèi)容的朋友請加Q君樣:909038429
/./*歡迎加入java交流Q君樣:909038429一起吹水聊天