首頁 > 軟體

一文讀懂Spring Bean的生命週期

2023-03-29 06:02:09

一、前言

今天我們來說一說 Spring Bean 的生命週期,小夥伴們應該在面試中經常遇到,這是正常現象。因為 Spring Bean 的生命週期是除了 IoC、AOP 幾個核心概念之外最重要概念,大家務必拿下。可 Spring 原始碼又比較複雜,跟著跟著就不知道跟到哪裡去了,不太好拿下呀。這倒是真的,而且網上一上來就各種貼流程原始碼,對初學者來說是真的一臉懵逼,就像字都看的懂,但連在一塊就不知道意思了,太繞了。

我們講 Spring Bean 的生命週期之前先來了解兩個概念:

1.1 什麼是 Bean

我們來看下 Spring Framework 的官方檔案:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

簡而言之,bean 是由 Spring IoC 容器範例化、組裝和管理的物件。

1.2 什麼是 Spring Bean 的生命週期

對於普通的 Java 物件,當 new 的時候建立物件,然後該物件就能夠使用了。一旦該物件不再被使用,則由 Java 自動進行垃圾回收。

而 Spring 中的物件是 bean,bean 和普通的 Java 物件沒啥大的區別,只不過 Spring 不再自己去 new 物件了,而是由 IoC 容器去幫助我們範例化物件並且管理它,我們需要哪個物件,去問 IoC 容器要即可。IoC 其實就是解決物件之間的耦合問題,Spring Bean 的生命週期完全由容器控制。

二、Spring Bean 的生命週期

這裡老周必須要提一下,這裡我們說的 Spring Bean 的生命週期主要指的是 singleton bean,對於 prototype 的 bean ,Spring 在建立好交給使用者之後則不會再管理後續的生命週期。

我們也來複習下 Spring 中的 bean 的作用域有哪些?

singleton : 唯一 bean 範例,Spring 中的 bean 預設都是單例的。
prototype : 每次請求都會建立一個新的 bean 範例。
request : 每一次 HTTP 請求都會產生一個新的 bean,該 bean 僅在當前 HTTP request 內有效。
session : 每一次 HTTP 請求都會產生一個新的 bean,該 bean 僅在當前 HTTP session 內有效。
global-session: 全域性 session 作用域,僅僅在基於 Portlet 的 web 應用中才有意義,Spring5 已經沒有了。Portlet 是能夠生成語意程式碼(例如:HTML)片段的小型 Java Web 外掛。它們基於 portlet 容器,可以像 servlet 一樣處理 HTTP 請求。但是,與 servlet 不同,每個 portlet 都有不同的對談。

我們知道對於普通的 Java 物件來說,它們的生命週期就是:

  • 範例化
  • 該物件不再被使用時通過垃圾回收機制進行回收

而對於 Spring Bean 的生命週期來說:

  • 範例化 Instantiation
  • 屬性賦值 Populate
  • 初始化 Initialization
  • 銷燬 Destruction

範例化 -> 屬性賦值 -> 初始化 -> 銷燬

只有四個步驟,這樣拆解的話是不是感覺也不難?不像其他人寫的那樣直接一上來就各種 BeanPostProcessor、BeanFactoryPostProcessor 全部懟進流程裡去,別說讀者看著頭大,自己寫的可能短時間內還記得流程,隔個一段時間,你可能都不知道自己寫了個啥。

本來小編想通過 Bean 建立流程入口

AbstractApplicationContext#refresh() 方法的 finishBeanFactoryInitialization(beanFactory) 處帶大家跟一下原始碼,想了想還是不帶入過多的程式碼進來,直接給到最終的主要邏輯。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
    }

    if (instanceWrapper == null) {
    	// 範例化階段
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

    ...

    Object exposedObject = bean;

    try {
    	// 屬性賦值階段
        this.populateBean(beanName, mbd, instanceWrapper);
        // 初始化階段
        exposedObject = this.initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable var18) {
        ...
    }

    ...
}

至於銷燬,是在容器關閉時呼叫的,詳見 ConfigurableApplicationContext#close()

是不是很清爽了?至於 BeanPostProcessor、BeanFactoryPostProcessor 以及其他的類,在老周看來,只不過是對主流程四個步驟的一系列擴充套件點而已。

三、Spring Bean 的生命週期的擴充套件點

Spring Bean 的生命週期的擴充套件點超級多,老周這裡不可能全部列出來,只說核心的擴充套件點。這也就是為什麼 Spring 的擴充套件性很好的原因,開了很多的口子,儘可能讓某個功能高內聚鬆耦合,使用者需要哪個功能就用哪個,而不是直接來一個大而全的東西。

3.1 Bean 自身的方法

比如建構函式、getter/setter 以及 init-method 和 destory-method 所指定的方法等,也就對應著上文說的範例化 -> 屬性賦值 -> 初始化 -> 銷燬四個階段。

3.2 容器級的方法(BeanPostProcessor 一系列介面)

主要是後處理器方法,比如下圖的 InstantiationAwareBeanPostProcessor、BeanPostProcessor 介面方法。這些介面的實現類是獨立於 Bean 的,並且會註冊到 Spring 容器中。在 Spring 容器建立任何 Bean 的時候,這些後處理器都會發生作用。

3.2.1 InstantiationAwareBeanPostProcessor 原始碼分析

我們翻一下原始碼發現 InstantiationAwareBeanPostProcessor 是繼承了 BeanPostProcessor

  • InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 呼叫點

 Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
返回值:如果返回的不為null,那麼後續的Bean的建立流程【範例化、初始化afterProperties】都不會執行,而是直接使用返回的快捷Bean,此時的正常執行順序如下:
InstantiationAwareBeanPostProcessor介面中的postProcessBeforeInstantiation,在範例化之前呼叫。
BeanPostProcessor介面中的postProcessAfterInitialization,在範例化之後呼叫。

總之,postProcessBeforeInstantiation 在 doCreateBean 之前呼叫,也就是在 bean 範例化之前呼叫的,英文原始碼註釋解釋道該方法的返回值會替換原本的 Bean 作為代理,這也是 AOP 等功能實現的關鍵點。

  • InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 呼叫點

 boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException
正常情況下在範例化之後在執行populateBean之前呼叫
返回值:如果有指定的bean的時候返回false,那麼後續的屬性填充和屬性依賴注入【populateBean】將不會執行,同時後續的postProcessPropertyValues將不會執行,但是初始化和BeanPostProcessor的仍然會執行。

 public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
範例化之後呼叫,在方法applyPropertyValues【屬性填充】之前
返回值:如果返回null,那麼將不會進行後續的屬性填充,比如依賴注入等,如果返回的pvs額外的新增了屬性,那麼後續會填充到該類對應的屬性中。
pvs:PropertyValues物件,用於封裝指定類的物件,簡單來說就是PropertyValue的集合,裡面相當於以key-value形式存放類的屬性和值。
pds:PropertyDescriptor物件陣列,PropertyDescriptor相當於儲存類的屬性,不過可以呼叫set,get方法設定和獲取對應屬性的值。

3.2.2 BeanPostProcessor 原始碼分析

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

進入初始化介面:

我們先來看

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization

  •  首先獲取到所有的後置處理器 getBeanPostProcessors()
  • 在 for 迴圈中依次呼叫後置處理器的方法 processor.postProcessBeforeInitialization(result, beanName);
  • 進入 postProcessBeforeInitialization 方法

 org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization

進入 invokeAwareInterfaces(bean); 方法,當前 bean 實現了 ApplicationContextAware 介面。

  • ApplicationContextAwareProcessor#postProcessBeforeInitialization 首先判斷此 bean 是不是各種的Aware,如果是它列舉的那幾個 Aware 就獲取 Bean 工廠的許可權,可以向容器中匯入相關的上下文環境,目的是為了 Bean 範例能夠獲取到相關的上下文,如果不是它列舉的幾個 Aware,那就呼叫 invokeAwareInterfaces(bean),向容器中新增相關介面的上下文環境。

 3.3 工廠後處理器方法(BeanFactoryProcessor 一系列介面)

包括 AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor 等。這些都是 Spring 框架中已經實現好的 BeanFactoryPostProcessor,用來實現某些特定的功能。

我們知道 Spring IoC 容器初始化的關鍵環節就在 org.springframework.context.support.AbstractApplicationContext#refresh 方法中 ,容器建立的主體流程都在這個方法裡面,這個方法是真的重要!!!

對於工廠後處理器方法老周這裡直接帶你看 invokeBeanFactoryPostProcessors(beanFactory); 方法,這個方法處理的是 BeanFactoryPostProcessor 介面的 Bean。呼叫方法如下:

跟到最重要的方法裡去,程式碼雖長,但邏輯中規中矩。

BeanFactoryPostProcessor:一切處理 BeanFactory 的父介面
BeanDefinitionRegistryPostProcessor:實現了 BeanFactoryPostProcessor 介面的介面

流程說明:

  • 呼叫 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(registry) 方法。引數 beanFactoryPostProcessors 傳入的優先處理掉。然後獲取容器註冊的,對於這些 Bean 按照 PriorityOrdered 介面、Ordered、沒有排序介面的範例分別進行處理。
  • 呼叫 BeanFactoryPostProcessor#postProcessBeanFactory(beanFactory) 方法。備註:BeanDefinitionRegistryPostProcessor 屬於 BeanFactoryPostProcessor 子介面。先處理屬於 BeanDefinitionRegistryPostProcessor 介面範例的 postProcessBeanFactory(beanFactory) 方法,然後獲取容器註冊的。對於這些 Bean 按照 PriorityOrdered 介面、Ordered、沒有排序介面的範例分別進行處理。

3.4 Bean 級生命週期方法

可以理解為 Bean 類直接實現介面的方法,比如 BeanNameAware、BeanFactoryAware、ApplicationContextAware、InitializingBean、DisposableBean 等方法,這些方法只對當前 Bean 生效。

3.4.1 Aware 型別的介面

Aware 型別的介面的作用就是讓我們能夠拿到 Spring 容器中的一些資源。基本都能夠見名知意,Aware 之前的名字就是可以拿到什麼資源,例如 BeanNameAware 可以拿到 BeanName,以此類推。呼叫時機需要注意:所有的 Aware 方法都是在初始化階段之前呼叫的。

Aware 介面眾多,這裡同樣通過分類的方式幫助大家記憶。Aware 介面具體可以分為兩組,至於為什麼這麼分,詳見下面的原始碼分析。如下排列順序同樣也是 Aware 介面的執行順序,能夠見名知意的介面不再解釋。

Aware Group1

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware

Aware Group2

  • EnvironmentAware
  • EmbeddedValueResolverAware

 ​​​​​​這個知道的人可能不多,實現該介面能夠獲取 Spring EL 解析器,使用者的自定義註解需要支援 SPEL 表示式的時候可以使用,非常方便。

ApplicationContextAware(ResourceLoaderAware/ApplicationEventPublisherAware/MessageSourceAware)

這幾個介面可能讓人有點懵,實際上這幾個介面可以一起記,其返回值實質上都是當前ApplicationContext 物件,因為 ApplicationContext 是一個複合介面,如下:

Aware 呼叫時機原始碼分析

可以看到並不是所有的 Aware 介面都使用同樣的方式呼叫。Bean××Aware 都是在程式碼中直接呼叫的,而 ApplicationContext 相關的 Aware 都是通過 BeanPostProcessor#postProcessBeforeInitialization() 實現的。感興趣的可以自己看一下 ApplicationContextAwareProcessor 這個類的原始碼,就是判斷當前建立的 Bean 是否實現了相關的 Aware 方法,如果實現了會呼叫回撥方法將資源傳遞給 Bean。

BeanPostProcessor 的呼叫時機也能在這裡體現,包圍住 invokeInitMethods 方法,也就說明了在初始化階段的前後執行。

關於 Aware 介面的執行順序,其實只需要記住第一組在第二組執行之前就行了。

3.4.2 生命週期介面

至於剩下的兩個生命週期介面就很簡單了,範例化和屬性賦值都是 Spring 幫助我們做的,能夠自己實現的有初始化和銷燬兩個生命週期階段。

  • InitializingBean 對應生命週期的初始化階段,在上面原始碼的 invokeInitMethods(beanName, wrappedBean, mbd);方法中呼叫。

有一點需要注意,因為 Aware 方法都是執行在初始化方法之前,所以可以在初始化方法中放心大膽的使用 Aware 介面獲取的資源,這也是我們自定義擴充套件 Spring 的常用方式。
除了實現 InitializingBean 介面之外還能通過註解或者 xml 設定的方式指定初始化方法,至於這幾種定義方式的呼叫順序其實沒有必要記。因為這幾個方法對應的都是同一個生命週期,只是實現方式不同,我們一般只採用其中一種方式。

  • DisposableBean 類似於 InitializingBean,對應生命週期的銷燬階段,以ConfigurableApplicationContext#close()方法作為入口,實現是通過迴圈取所有實現了 DisposableBean 介面的 Bean 然後呼叫其 destroy() 方法,感興趣的可以自行跟一下原始碼。

3.5 Spring Bean 生命週期流程圖

四、常用介面說明

4.1 BeanNameAware

該介面只有一個方法 setBeanName(String name),用來獲取 bean 的id 或者 name

4.2 BeanFactoryAware

該介面只有一個方法 setBeanFactory(BeanFactory beanFactory),用來獲取當前環境中的 BeanFactory

4.3 ApplicationContextAware

該介面只有一個方法 setApplicationContext(ApplicationContext applicationContext),用來獲取當前環境中的 ApplicationContext

4.4 InitializingBean

該介面只有一個方法 afterPropertiesSet(),在屬性注入完成後呼叫

4.5 DisposableBean

該介面只有一個方法 destroy(),在容器銷燬的時候呼叫,在使用者指定的 destroy-method 之前呼叫

4.6 BeanPostProcessor

該介面有兩個方法:

  • postProcessBeforeInitialization(Object bean, String beanName):在初始化之前呼叫此方法
  • postProcessAfterInitialization(Object bean, String beanName):在初始化之後呼叫此方法
  • 通過方法簽名我們可以知道,我們可以通過 beanName 來篩選出我們需要進行個性化客製化的 bean。

4.7 InstantiationAwareBeanPostProcessor

該類是 BeanPostProcessor 的子介面,常用的有如下三個方法:

  • postProcessBeforeInstantiation(Class beanClass, String beanName):在bean範例化之前呼叫
  • postProcessProperties(PropertyValues pvs, Object bean, String beanName):在bean範例化之後、設定屬性前呼叫
  • postProcessAfterInstantiation(Class beanClass, String beanName):在bean範例化之後呼叫

五、程式碼演示

思路:建立一個類 UserBean ,讓其實現幾個特殊的介面,並分別在介面實現的構造器、介面方法中斷點,觀察執行緒呼叫棧,分析出 Bean 物件建立和管理關鍵點的觸發時機。

5.1 UserBean 類

@Component
public class UserBean implements InitializingBean, BeanNameAware, DisposableBean, ApplicationContextAware {
	private int id;

	private String name;

	public UserBean(int id, String name) {
		this.id = id;
		this.name = name;
		System.out.println("2. 呼叫建構函式");
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
		System.out.println("5. 屬性注入 id");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
		System.out.println("5. 屬性注入 name");
	}

	@Override
	public void setBeanName(String name) {
		System.out.println("6. 呼叫 BeanNameAware.setBeanName() 方法");
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		UserBean userBean = (UserBean) applicationContext.getBean("userBean");
		System.out.println(userBean);
		System.out.println("7. 呼叫 BeanNameAware.setBeanName() 方法");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("9. 呼叫 InitializingBean.afterPropertiesSet() 方法");
	}

	public void myInit() {
		System.out.println("10. 呼叫 init-method 方法");
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("12. 呼叫 DisposableBean.destroy() 方法");
	}

	public void myDestroy() {
		System.out.println("13. 呼叫 destroy-method 方法");
	}

	@Override
	public String toString() {
		return "UserBean{" +
				"id=" + id +
				", name='" + name + ''' +
				'}';
	}
}

5.2 InstantiationAwareBeanPostProcessor 介面實現類

@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
	@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("1. 呼叫 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() 方法");
		}
		return null;
	}

	@Override
	public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			UserBean userBean = (UserBean) bean;
			System.out.println("3. 呼叫 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation() 方法");
			System.out.println(userBean);
		}
		return true;
	}

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("4. 呼叫 InstantiationAwareBeanPostProcessor.postProcessProperties() 方法");
		}
		return null;
	}
}

5.3 BeanPostProcessor 介面實現類

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("8. 呼叫 BeanPostProcessor.postProcessBeforeInitialization() 方法");
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if ("userBean".equals(beanName)) {
			System.out.println("11. 呼叫 BeanPostProcessor.postProcessAfterInitialization() 方法");
		}
		return bean;
	}
}

5.4 BeanFactoryPostProcessor 介面實現類

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("0. 呼叫 BeanFactoryPostProcessor.postProcessBeanFactory() 方法");
	}
}

5.5 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="
	    http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
">

	<bean class="com.riemann.test.MyInstantiationAwareBeanPostProcessor" />

	<bean id="userBean" class="com.riemann.test.UserBean" init-method="myInit" destroy-method="myDestroy">
		<!-- 建構函式注入 -->
		<constructor-arg index="0" type="int">
			<value>1</value>
		</constructor-arg>
		<constructor-arg index="1" type="java.lang.String">
			<value>微信公眾號【老周聊架構】</value>
		</constructor-arg>

		<!-- setter方法注入 -->
		<property name="id" value="2"/>
		<property name="name" value="riemann"/>
	</bean>

	<bean class="com.riemann.test.MyBeanPostProcessor" />

	<bean class="com.riemann.test.MyBeanFactoryPostProcessor" />
	
</beans>

5.6 測試類

public class BeanLifeCycleTest {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		UserBean user = (UserBean) applicationContext.getBean("userBean");
		((AbstractApplicationContext) applicationContext).close();
	}
}

5.7 控制檯結果列印

以上就是一文讀懂 Spring Bean 的生命週期的詳細內容,更多關於Spring Bean生命週期的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com