繼續 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" …>
7、源碼地址
https://github.com/turoDog/review_spring
推薦閱讀:
1、java | 什麼是動態代理