Spring 覆盤(三) | Bean 的生命週期
繼續 Spring 覆盤,今天看了下 Spring 的 Bean 生命週期。
1、典型的 Spring 生命週期
在傳統的 Java 應用中,bean 的生命週期很簡單,使用 Java 關鍵字 new 進行Bean 的例項化,然後該 Bean 就能夠使用了。一旦 bean 不再被使用,則由 Java 自動進行垃圾回收,簡直不要太簡單。
相比之下,Spring 管理 Bean 的生命週期就複雜多了,正確理解 Bean 的生命週期非常重要,因為 Spring 對 Bean 的管理可擴充套件性非常強,下面展示了一個 Bea 的構造過程。
以上圖片出自 《Spring 實戰(第四版)》一書,圖片描述了一個經典的 Spring Bean 的生命週期,書中隨他的解釋如下:
1.Spring對bean進行例項化; 2.Spring將值和bean的引用注入到bean對應的屬性中; 3.如果bean實現了BeanNameAware介面,Spring將bean的ID傳遞給 setBean-Name()方法; 4.如果bean實現了BeanFactoryAware介面,Spring將調 用setBeanFactory()方法,將BeanFactory容器例項傳入; 5.如果bean實現了ApplicationContextAware介面,Spring將調 用setApplicationContext()方法,將bean所在的應用上下文的 引用傳入進來; 6.如果bean實現了BeanPostProcessor介面,Spring將呼叫它們 的post-ProcessBeforeInitialization()方法; 7.如果bean實現了InitializingBean介面,Spring將呼叫它們的 after-PropertiesSet()方法。類似地,如果bean使用init- method宣告瞭初始化方法,該方法也會被呼叫; 8.如果bean實現了BeanPostProcessor介面,Spring將呼叫它們 的post-ProcessAfterInitialization()方法; 9.此時,bean已經準備就緒,可以被應用程式使用了,它們將一直 駐留在應用上下文中,直到該應用上下文被銷燬; 10.如果bean實現了DisposableBean介面,Spring將呼叫它的 destroy()介面方法。同樣,如果bean使用destroy-method宣告 了銷燬方法,該方法也會被呼叫。
2、驗證 Spring Bean 週期
寫了下程式碼驗證以上說法,首先建立一個 Person 類,它就是我們要驗證的 Bean ,為方便測試,他實現了 BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean。程式碼如下:
package com.nasus.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
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.context.annotation.Scope;
/**
* Project Name:review_spring <br/>
* Package Name:PACKAGE_NAME <br/>
* Date:2019/9/1 16:29 <br/>
*
* @author <a href="[email protected]">chenzy</a><br/>
*/
@Scope("ProtoType")
public class Person implements BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(Person.class);
private String name;
public Person(){
System.out.println("1、開始例項化 person ");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("2、設定 name 屬性");
}
@Override
public void setBeanName(String beanId) {
System.out.println("3、Person 實現了 BeanNameAware 介面,Spring 將 Person 的 "
+ "ID=" + beanId + "傳遞給 setBeanName 方法");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4、Person 實現了 BeanFactoryAware 介面,Spring 調"
+ "用 setBeanFactory()方法,將 BeanFactory 容器例項傳入");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("5、Person 實現了 ApplicationContextAware 介面,Spring 調"
+ "用 setApplicationContext()方法,將 person 所在的應用上下文的"
+ "引用傳入進來");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("8、Person 實現了 InitializingBean 介面,Spring 呼叫它的"
+ "afterPropertiesSet()方法。類似地,如果 person 使用 init-"
+ "method 宣告瞭初始化方法,該方法也會被呼叫");
}
@Override
public void destroy() throws Exception {
System.out.println("13、Person 實現了 DisposableBean 介面,Spring 呼叫它的"
+ "destroy() 介面方法。同樣,如果 person 使用 destroy-method 宣告"
+ "了銷燬方法,該方法也會被呼叫");
}
/**
* xml 中宣告的 init-method 方法
*/
public void initMethod(){
System.out.println("9、xml 中宣告的 init-method 方法");
}
/**
* xml 中宣告的 destroy-method 方法
*/
public void destroyMethod(){
System.out.println("14、xml 中宣告的 destroy-method 方法");
System.out.println("end---------------destroy-----------------");
}
// 自定義初始化方法
@PostConstruct
public void springPostConstruct(){
System.out.println("7、@PostConstruct 呼叫自定義的初始化方法");
}
// 自定義銷燬方法
@PreDestroy
public void springPreDestory(){
System.out.println("12、@PreDestory 呼叫自定義銷燬方法");
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalize 方法");
}
}
複製程式碼
除此之外,建立了一個 MyBeanPostProcessor 類繼承自 BeanPostProcessor 這個類只關心 Person 初始化前後要做的事情。比如,初始化之前,載入其他 Bean。程式碼如下:
package com.nasus.lifecycle;
import com.nasus.bean.Person;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* Project Name:review_spring <br/>
* Package Name:PACKAGE_NAME <br/>
* Date:2019/9/1 16:25 <br/>
*
* @author <a href="[email protected]">chenzy</a><br/>
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
// 容器載入的時候會載入一些其他的 bean,會呼叫初始化前和初始化後方法
// 這次只關注 Person 的生命週期
public Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {
if(bean instanceof Person){
System.out.println("6、初始化 Person 之前執行的方法");
}
return bean;
}
public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {
if(bean instanceof Person){
System.out.println("10、初始化 Person 完成之後執行的方法");
}
return bean;
}
}
複製程式碼
resource 資料夾下新建一個 bean_lifecycle.xml 檔案注入相關 bean ,程式碼如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 掃描bean -->
<context:component-scan base-package="com.nasus"/>
<!-- 實現了使用者自定義初始化和銷燬方法 -->
<bean id="person" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<!-- 注入bean 屬性名稱 -->
<property name="name" value="nasus" />
</bean>
<!--引入自定義的BeanPostProcessor-->
<bean class="com.nasus.lifecycle.MyBeanPostProcessor"/>
</beans>
複製程式碼
測試類,獲取 person 這個 Bean 並使用它,程式碼如下:
import com.nasus.bean.Person;
import java.awt.print.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Project Name:review_spring <br/>
* Package Name:PACKAGE_NAME <br/>
* Date:2019/9/1 16:38 <br/>
*
* @author <a href="[email protected]">chenzy</a><br/>
*/
public class lifeCycleTest {
@Test
public void testLifeCycle(){
// 為面試而準備的Bean生命週期載入過程
ApplicationContext context = new ClassPathXmlApplicationContext("bean_lifecycle.xml");
Person person = (Person)context.getBean("person");
// 使用屬性
System.out.println("11、例項化完成使用屬性:Person name = " + person.getName());
// 關閉容器
((ClassPathXmlApplicationContext) context).close();
}
}
複製程式碼
lifeCycleTest 方法最後關閉了容器,關閉的同時控制檯日誌輸出如下:
1、開始例項化 person
2、設定 name 屬性
3、Person 實現了 BeanNameAware 介面,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法
4、Person 實現了 BeanFactoryAware 介面,Spring 呼叫 setBeanFactory()方法,將 BeanFactory 容器例項傳入
5、Person 實現了 ApplicationContextAware 介面,Spring 呼叫 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來
6、初始化 Person 之前執行的方法
7、@PostConstruct 呼叫自定義的初始化方法
8、Person 實現了 InitializingBean 介面,Spring 呼叫它的afterPropertiesSet()方法。類似地,如果 person 使用 init-method 宣告瞭初始化方法,該方法也會被呼叫
9、xml 中宣告的 init-method 方法
10、初始化 Person 完成之後執行的方法
11、例項化完成使用屬性:Person name = nasus
12、@PreDestory 呼叫自定義銷燬方法
13、Person 實現了 DisposableBean 介面,Spring 呼叫它的destroy() 介面方法。同樣,如果 person 使用 destroy-method 宣告瞭銷燬方法,該方法也會被呼叫
14、xml 中宣告的 destroy-method 方法
end---------------destroy-----------------
複製程式碼
由以上日誌可知,當 person 預設是單例模式時,bean 的生命週期與容器的生命週期一樣,容器初始化,bean 也初始化。容器銷燬,bean 也被銷燬。那如果,bean 是非單例呢?
3、在 Bean 例項化完成後,銷燬前搞事情 有時我們需要在 Bean 屬性值 set 好之後和 Bean 銷燬之前做一些事情,比如檢查 Bean 中某個屬性是否被正常的設定好值了。Spring 框架提供了多種方法讓我們可以在 Spring Bean 的生命週期中執行 initialization 和 pre-destroy 方法。這些方法我在上面已經測試過了,以上程式碼實現了多種方法,它是重複,開發中選以下其一即可,比如:
- 在配置檔案中指定的 init-method 和 destroy-method 方法
- 實現 InitializingBean 和 DisposableBean 介面
- 使用 @PostConstruct 和 @PreDestroy 註解(牆裂推薦使用)
4、多例項模式下的 Bean 生命週期 上面測試中的 person 預設是 singleton 的,現在我們將 person 改為 protoType 模式,bean_lifecycle.xml 做如下程式碼修改,其餘類保持不變:
<!-- 實現了使用者自定義初始化和銷燬方法 -->
<bean id="person" scope="prototype" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<!-- 注入bean 屬性名稱 -->
<property name="name" value="nasus" />
</bean>
複製程式碼
此時的日誌輸出如下:
1、開始例項化 person
2、設定 name 屬性
3、Person 實現了 BeanNameAware 介面,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法
4、Person 實現了 BeanFactoryAware 介面,Spring 呼叫 setBeanFactory()方法,將 BeanFactory 容器例項傳入
5、Person 實現了 ApplicationContextAware 介面,Spring 呼叫 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來
6、初始化 Person 之前執行的方法
7、@PostConstruct 呼叫自定義的初始化方法
8、Person 實現了 InitializingBean 介面,Spring 呼叫它的afterPropertiesSet()方法。類似地,如果 person 使用 init-method 宣告瞭初始化方法,該方法也會被呼叫
9、xml 中宣告的 init-method 方法
10、初始化 Person 完成之後執行的方法
11、例項化完成使用屬性:Person name = nasus
複製程式碼
此時,容器關閉,person 物件並沒有銷燬。原因在於,單例項模式下,bean 的生命週期由容器管理,容器生,bean 生;容器死,bean 死。而在多例項模式下,Spring 就管不了那麼多了,bean 的生命週期,交由客戶端也就是程式設計師或者 JVM 來進行管理。
5、多例項模式下 Bean 的載入時機
首先說說單例項,單例項模式下,bean 在容器載入那一刻起,就已經完成例項化了,證明如下,我啟用 debug 模式,在 20 行打了一個斷點,而日誌卻如下所示,說明瞭 bean 在 19 行,初始化容器的時候,已經完成例項化了。
再說多例項模式下,這個模式下,bean 在需要用到 bean 的時候才進行初始化,證明如下,同樣執行完 19 行,多例項模式下,控制檯一片空白,說明此時的 bean 是未被載入的。
debug 走到 23 行時,需要用到 bean 時,bean 才被載入了,驗證如下。在開發中,我們把這種載入叫做懶載入,它的用處就是減輕程式開銷,等到要用時才載入,而不是一上來就載入全部。
6、單例項 bean 如何實現延遲載入
只需在 xml 中加上 lazy-init 屬性為 true 即可。如下,它的載入方式就變成了懶載入。
<!-- 實現了使用者自定義初始化和銷燬方法 -->
<bean id="person" lazy-init="true" class="com.nasus.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<!-- 注入bean 屬性名稱 -->
<property name="name" value="nasus" />
</bean>
複製程式碼
如果想對所有的預設單例 bean 都應用延遲初始化,可以在根節點 beans 設定 default-lazy-init 屬性為 true,如下所示:
<beans default-lazy-init="true" …>
複製程式碼
推薦閱讀: *1、*java | 什麼是動態代理