繼續 Spring 覆盤,今天看了下 Spring 的 Bean 生命週期。java
在傳統的 Java 應用中,bean 的生命週期很簡單,使用 Java 關鍵字 new 進行Bean 的實例化,而後該 Bean 就可以使用了。一旦 bean 再也不被使用,則由 Java 自動進行垃圾回收,簡直不要太簡單。git
相比之下,Spring 管理 Bean 的生命週期就複雜多了,正確理解 Bean 的生命週期很是重要,由於 Spring 對 Bean 的管理可擴展性很是強,下面展現了一個 Bea 的構造過程。程序員
以上圖片出自 《Spring 實戰(第四版)》一書,圖片描述了一個經典的 Spring Bean 的生命週期,書中隨他的解釋以下:github
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聲明
了銷燬方法,該方法也會被調用。
寫了下代碼驗證以上說法,首先建立一個 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="turodog@foxmail.com">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("一、開始實例化 person "); } public String getName() { return name; } public void setName(String name) { this.name = name; System.out.println("二、設置 name 屬性"); } @Override public void setBeanName(String beanId) { System.out.println("三、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 " + "ID=" + beanId + "傳遞給 setBeanName 方法"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("四、Person 實現了 BeanFactoryAware 接口,Spring 調" + "用 setBeanFactory()方法,將 BeanFactory 容器實例傳入"); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("五、Person 實現了 ApplicationContextAware 接口,Spring 調" + "用 setApplicationContext()方法,將 person 所在的應用上下文的" + "引用傳入進來"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("八、Person 實現了 InitializingBean 接口,Spring 調用它的" + "afterPropertiesSet()方法。相似地,若是 person 使用 init-" + "method 聲明瞭初始化方法,該方法也會被調用"); } @Override public void destroy() throws Exception { System.out.println("1三、Person 實現了 DisposableBean 接口,Spring 調用它的" + "destroy() 接口方法。一樣,若是 person 使用 destroy-method 聲明" + "了銷燬方法,該方法也會被調用"); } /** * xml 中聲明的 init-method 方法 */ public void initMethod(){ System.out.println("九、xml 中聲明的 init-method 方法"); } /** * xml 中聲明的 destroy-method 方法 */ public void destroyMethod(){ System.out.println("1四、xml 中聲明的 destroy-method 方法"); System.out.println("end---------------destroy-----------------"); } // 自定義初始化方法 @PostConstruct public void springPostConstruct(){ System.out.println("七、@PostConstruct 調用自定義的初始化方法"); } // 自定義銷燬方法 @PreDestroy public void springPreDestory(){ System.out.println("十二、@PreDestory 調用自定義銷燬方法"); } @Override protected void finalize() throws Throwable { System.out.println("finalize 方法"); } }
除此以外,建立了一個 MyBeanPostProcessor 類繼承自 BeanPostProcessor 這個類只關心 Person 初始化先後要作的事情。好比,初始化以前,加載其餘 Bean。代碼以下:spring
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="turodog@foxmail.com">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("六、初始化 Person 以前執行的方法"); } return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof Person){ System.out.println("十、初始化 Person 完成以後執行的方法"); } return bean; } }
resource 文件夾下新建一個 bean_lifecycle.xml 文件注入相關 bean ,代碼以下:app
<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="turodog@foxmail.com">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("十一、實例化完成使用屬性:Person name = " + person.getName()); // 關閉容器 ((ClassPathXmlApplicationContext) context).close(); } }
lifeCycleTest 方法最後關閉了容器,關閉的同時控制檯日誌輸出以下:ide
一、開始實例化 person 二、設置 name 屬性 三、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法 四、Person 實現了 BeanFactoryAware 接口,Spring 調用 setBeanFactory()方法,將 BeanFactory 容器實例傳入 五、Person 實現了 ApplicationContextAware 接口,Spring 調用 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來 六、初始化 Person 以前執行的方法 七、@PostConstruct 調用自定義的初始化方法 八、Person 實現了 InitializingBean 接口,Spring 調用它的afterPropertiesSet()方法。相似地,若是 person 使用 init-method 聲明瞭初始化方法,該方法也會被調用 九、xml 中聲明的 init-method 方法 十、初始化 Person 完成以後執行的方法 十一、實例化完成使用屬性:Person name = nasus 十二、@PreDestory 調用自定義銷燬方法 1三、Person 實現了 DisposableBean 接口,Spring 調用它的destroy() 接口方法。一樣,若是 person 使用 destroy-method 聲明瞭銷燬方法,該方法也會被調用 1四、xml 中聲明的 destroy-method 方法 end---------------destroy-----------------
由以上日誌可知,當 person 默認是單例模式時,bean 的生命週期與容器的生命週期同樣,容器初始化,bean 也初始化。容器銷燬,bean 也被銷燬。那若是,bean 是非單例呢?post
有時咱們須要在 Bean 屬性值 set 好以後和 Bean 銷燬以前作一些事情,好比檢查 Bean 中某個屬性是否被正常的設置好值了。Spring 框架提供了多種方法讓咱們能夠在 Spring Bean 的生命週期中執行 initialization 和 pre-destroy 方法。這些方法我在上面已經測試過了,以上代碼實現了多種方法,它是重複,開發中選如下其一便可,好比:
上面測試中的 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>
此時的日誌輸出以下:
一、開始實例化 person 二、設置 name 屬性 三、Person 實現了 BeanNameAware 接口,Spring 將 Person 的 ID=person傳遞給 setBeanName 方法 四、Person 實現了 BeanFactoryAware 接口,Spring 調用 setBeanFactory()方法,將 BeanFactory 容器實例傳入 五、Person 實現了 ApplicationContextAware 接口,Spring 調用 setApplicationContext()方法,將 person 所在的應用上下文的引用傳入進來 六、初始化 Person 以前執行的方法 七、@PostConstruct 調用自定義的初始化方法 八、Person 實現了 InitializingBean 接口,Spring 調用它的afterPropertiesSet()方法。相似地,若是 person 使用 init-method 聲明瞭初始化方法,該方法也會被調用 九、xml 中聲明的 init-method 方法 十、初始化 Person 完成以後執行的方法 十一、實例化完成使用屬性:Person name = nasus
此時,容器關閉,person 對象並無銷燬。緣由在於,單實例模式下,bean 的生命週期由容器管理,容器生,bean 生;容器死,bean 死。而在多實例模式下,Spring 就管不了那麼多了,bean 的生命週期,交由客戶端也就是程序員或者 JVM 來進行管理。
首先說說單實例,單實例模式下,bean 在容器加載那一刻起,就已經完成實例化了,證實以下,我啓用 debug 模式,在 20 行打了一個斷點,而日誌卻以下所示,說明了 bean 在 19 行,初始化容器的時候,已經完成實例化了。
再說多實例模式下,這個模式下,bean 在須要用到 bean 的時候才進行初始化,證實以下,一樣執行完 19 行,多實例模式下,控制檯一片空白,說明此時的 bean 是未被加載的。
debug 走到 23 行時,須要用到 bean 時,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" …>
https://github.com/turoDog/review_spring
推薦閱讀:
一、java | 什麼是動態代理