理解對(duì)象和Bean的關(guān)系
java 是一種面向?qū)ο蟮恼Z(yǔ)言痢虹,簡(jiǎn)而言之被去,一切皆對(duì)象主儡。Bean自然也是對(duì)象,只不過(guò)它是托管給 Bean 工廠管理著的對(duì)象惨缆。
java 對(duì)象如何被創(chuàng)建
在寫(xiě)代碼時(shí)糜值,我們通常用下面的語(yǔ)句來(lái)創(chuàng)建一個(gè)對(duì)象:
A a=new A();
那么在創(chuàng)建對(duì)象的過(guò)程中,究竟發(fā)生了什么呢坯墨。其實(shí)上面簡(jiǎn)單的一句話寂汇,在程序中發(fā)生了很多很多的事情。
首先捣染,一個(gè)對(duì)象是需要內(nèi)存去存放的骄瓣。所以會(huì)有一個(gè)分配內(nèi)存的過(guò)程。分配了內(nèi)存之后耍攘,jvm便會(huì)開(kāi)始創(chuàng)建對(duì)象榕栏,并將它賦值給 a 變量畔勤。然后再去初始化A中的一些屬性,并執(zhí)行A的構(gòu)造方法扒磁。在初始化的過(guò)程中庆揪,會(huì)先執(zhí)行 static 代碼塊,再執(zhí)行構(gòu)造方法。除此之外妨托,如果有父類缸榛,會(huì)優(yōu)先父類的進(jìn)行執(zhí)行。大致如下圖(圖一)所示兰伤。
如何驗(yàn)證對(duì)象初始化的過(guò)程呢内颗?用下面一段代碼驗(yàn)證。這段代碼很簡(jiǎn)單敦腔,有靜態(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...");
}
}
}
運(yùn)行打印的日志如下负懦,通過(guò)分析日志,我們可以得出柏腻,靜態(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 的生命周期
好的五嫂,有了對(duì)象的初始化順序颗品,我們就可以繼續(xù)分析 bean 的生命周期了。我們可以先回憶一下自己平時(shí)是怎么定義一個(gè) bean的沃缘。
@Component
public class TestBean{
}
@Bean
public Object myObject(){
}
常用的是上面這兩種:第一種是通過(guò)Component注解標(biāo)注類躯枢;第二中方式是在方法上做@Bean的注解。我們都知道槐臀,注解標(biāo)注的方法或者類锄蹂,便會(huì)被spring掃描,并最終生成一個(gè)bean水慨。本文不詳細(xì)討論bean掃描的過(guò)程得糜,只分析bean初始化過(guò)程中的一些接口。
那么晰洒,Spring 創(chuàng)建 Bean 就可以分為兩大步驟朝抖,第一步是由Springboot 掃描并獲取BeanDefinition;第二部谍珊,是初始化Bean治宣。spring 在bean的初始化過(guò)程為我們提供了很多的接口,我們可以用它們?cè)赽ean的生成過(guò)程中做一些事情。這些接口均采用回調(diào)的方式侮邀,以下是部分接口的介紹和回調(diào)時(shí)機(jī)缆巧。
接口 | 說(shuō)明 | 回調(diào)時(shí)機(jī) |
---|---|---|
BeanNameAware | 如果你的bean實(shí)現(xiàn)了該接口的 setName 方法,則可以通過(guò)這個(gè)方法獲取到bean名 | 發(fā)生在bean生命周期初期豌拙,早于構(gòu)造方法 |
ApplicationContextAware | 如果一個(gè)bean實(shí)現(xiàn)了該接口的setApplicationContext 方法陕悬,則可以通過(guò)此方法獲取到ApplicationContext | 調(diào)用于生命周期初期,在BeanNameAware和構(gòu)造方法之間 |
InitializingBean | 此接口的方法為 afterPropertiesSet | 在bean工廠設(shè)置完bean的所有屬性之后按傅,會(huì)回調(diào)此方法捉超。回調(diào)時(shí)機(jī)在構(gòu)造方法之后 |
BeanPostProcessor | 此接口有 postProcessBeforeInitialization唯绍、postProcessAfterInitialization兩個(gè)方法拼岳,分別對(duì)應(yīng)了Bean生命周期的兩個(gè)回調(diào) | 這兩個(gè)方法也在構(gòu)造方法之后,不過(guò)分別在 InitializingBean 前后 |
如果將上面的接口加入况芒,則 bean 生命周期大致如下圖(圖二):
同樣惜纸,我們用代碼來(lái)驗(yàn)證一下這個(gè)回調(diào)順序。用來(lái)測(cè)試的Bean代碼如下绝骚,這個(gè)測(cè)試 bean 沒(méi)有繼承其他父類耐版,僅用來(lái)驗(yàn)證springboot的接口在bean生命周期的調(diào)用時(shí)機(jī):
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");
}
}
同時(shí),我定義了一個(gè)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;
}
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
接下來(lái)是啟動(dòng)類:
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");
}
}
運(yùn)行啟動(dòng)類压汪,打印出日志如下:
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
看看這個(gè)日志粪牲,印證了圖二對(duì)各個(gè)接口調(diào)用時(shí)機(jī)結(jié)論。
總結(jié)
對(duì)象初始化止剖,就是創(chuàng)建對(duì)象腺阳,并且初始化其屬性的過(guò)程。首先是加載類文件穿香,其次對(duì)象所需要的內(nèi)存亭引。然后靜態(tài)代碼塊會(huì)被調(diào)用,最后是構(gòu)造方法皮获。
Spring Bean的初始化焙蚓,除了創(chuàng)建對(duì)象這些步驟之外,還在其中穿插了一些生命周期的接口魔市。首先在類加載完成后主届,會(huì)得到BeanDefinition,然后通過(guò)這個(gè)定義來(lái)初始化待德,而不是直接通過(guò)加載后的類對(duì)象來(lái)生成對(duì)象。在靜態(tài)代碼塊和構(gòu)造方法中間枫夺,Spring提供了幾個(gè)Aware接口将宪,如表格中的BeanNameAware和ApplicationContextAware。在構(gòu)造方法調(diào)用結(jié)束,并且springboot給bean set了所有屬性之后较坛,會(huì)調(diào)用Initializing接口和BeanPostProcessor印蔗。
以上,便是我理解的 spring bean 生命周期丑勤,它就是 spring 在幫我們初始化對(duì)象管理對(duì)象的過(guò)程中額外做了一些事情华嘹。