1. 程式人生 > >原始碼解析:init-method、@PostConstruct、afterPropertiesSet孰先孰後

原始碼解析:init-method、@PostConstruct、afterPropertiesSet孰先孰後

Spring 容器中的 Bean 是有生命週期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操作,常用的設定方式有以下三種:

  • 通過實現 InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作方法;
  • 通過 <bean> 元素的 init-method/destroy-method屬性指定初始化之後 /銷燬之前呼叫的操作方法;
  • 在指定方法上加上@PostConstruct 或@PreDestroy註解來制定該方法是在初始化之後還是銷燬之前呼叫。 

這是我們就有個疑問,這三種方式是完全等同的嗎,孰先孰後?

下面我們將帶著這個疑問,試圖通過測試程式碼以及分析Spring原始碼找到答案。

首先,我們還是編寫一個簡單的測試程式碼:

public class InitSequenceBean implements InitializingBean {
 
    public InitSequenceBean() {
       System.out.println("InitSequenceBean: constructor");
    }
   
    @PostConstruct
    public void postConstruct() {
       System.out.println("InitSequenceBean: postConstruct");
    }
   
    public void initMethod() {
       System.out.println("InitSequenceBean: init-method");
    }
   
    @Override
    public void afterPropertiesSet() throws Exception {
       System.out.println("InitSequenceBean: afterPropertiesSet");
    }
}

並且在配置檔案中新增如下Bean定義:

<bean class="InitSequenceBean" init-method="initMethod"></bean>

好了,我們啟動Spring容器,觀察輸出結果,就可知道三者的先後順序了:

InitSequenceBean: constructor

InitSequenceBean: postConstruct

InitSequenceBean: afterPropertiesSet

InitSequenceBean: init-method

通過上述輸出結果,三者的先後順序也就一目瞭然了:

Constructor > @PostConstruct > InitializingBean > init-method

先大致分析下為什麼會出現這些的結果:構造器(Constructor)被率先呼叫毋庸置疑,InitializingBean先於init-method我們也可以理解(在也談Spring容器的生命週期中已經討論過),但是PostConstruct為何率先於InitializingBean執行呢?

我們再次帶著這個疑問去檢視Spring原始碼來一探究竟。

通過Debug並檢視呼叫棧,我們發現了這個類org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,從命名上,我們就可以得到某些資訊——這是一個BeanPostProcessor。想到了什麼?在也談Spring容器的生命週期中,我們提到過BeanPostProcessor的postProcessBeforeInitialization是在Bean生命週期中afterPropertiesSet和init-method之前執被呼叫的。

再次觀察CommonAnnotationBeanPostProcessor這個類,它繼承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顧名思義,就是在Bean初始化和銷燬的時候所作的一個前置/後置處理器。

通過檢視InitDestroyAnnotationBeanPostProcessor類下的postProcessBeforeInitialization方法:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
       try {
           metadata.invokeInitMethods(bean, beanName);
       }
       catch (InvocationTargetException ex) {
           throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
       }
       catch (Throwable ex) {
           throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);
       }
        return bean;
    }
檢視findLifecycleMetadata方法,繼而我們跟蹤到buildLifecycleMetadata這個方法體中,看下buildLifecycleMetadata這個方法體的內容:

private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {
       final LifecycleMetadata newMetadata = new LifecycleMetadata();
       final boolean debug = logger.isDebugEnabled();
       ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
           public void doWith(Method method) {
              if (initAnnotationType != null) {
                  if (method.getAnnotation(initAnnotationType) != null) {
                     newMetadata.addInitMethod(method);
                     if (debug) {
                         logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
                     }
                  }
              }
              if (destroyAnnotationType != null) {
                  if (method.getAnnotation(destroyAnnotationType) != null) {
                     newMetadata.addDestroyMethod(method);
                     if (debug) {
                         logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
                     }
                  }
              }
           }
       });
       return newMetadata;
}

分析這段程式碼發現,在這裡會去判斷某方法有沒有被initAnnotationType/destroyAnnotationType註釋,如果有,則新增到init/destroy佇列中,後續一一執行。

initAnnotationType/destroyAnnotationType註釋是什麼呢,我們在CommonAnnotationBeanPostProcessor的建構函式中看到下面這段程式碼:

public CommonAnnotationBeanPostProcessor() {
       setOrder(Ordered.LOWEST_PRECEDENCE - 3);
       setInitAnnotationType(PostConstruct.class);
       setDestroyAnnotationType(PreDestroy.class);
       ignoreResourceType("javax.xml.ws.WebServiceContext");
}

一切都清晰了吧。一言以蔽之,@PostConstruct註解後的方法在BeanPostProcessor前置處理器中就被執行了,所以當然要先於InitializingBean和init-method執行了。

最後,給出本文的結論,Bean在例項化的過程中:

Constructor > @PostConstruct > InitializingBean > init-method




相關推薦

原始碼解析init-method@PostConstructafterPropertiesSet

Spring 容器中的 Bean 是有生命週期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操作,常用的設定方式有以下三種: 通過實現 InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作

源碼解析init-method@PostConstructafterPropertiesSet

tro strong proc blog tor after stc ini 解析 http://sexycoding.iteye.com/blog/1046993 @PostConstruct註解後的方法在BeanPostProcessor前置處理器中就被執行了,所以當然

Tomcat原始碼解析Jsp檔案的編譯實現

1.Jsp簡介     jsp(java server page),其根本是一個簡化的Servlet技術,是一種動態網頁技術標準。     它是在傳統的網頁HTML頁面中插入java程式碼段,從而形成jsp檔案,字尾為.jsp。  

Redis原始碼解析25叢集(一)握手心跳訊息以及下線檢測

         Redis叢集是Redis提供的分散式資料庫方案,通過分片來進行資料共享,並提供複製和故障轉移功能。 一:初始化 1:資料結構 在原始碼中,通過server.cluster記錄整個叢集當前的狀態,比如叢集中的所有節點;叢集目前的狀態,比如是上線還是下線;

Spring中構造器init-method@PostConstructafterPropertiesSet,自動註入發生時間以及單例多例的區別

但是 lan 構造方法 nes src 調用父類構造方法 pos print 提醒      首先明白,spring的IOC功能需要是利用反射原理,反射獲取類的無參構造方法創建對象,如果一個類沒有無參的構造方法spring是不會創建對象的。在這裏需要提醒一下,如果我們在c

Redis原始碼解析28叢集(四)手動故障轉移從節點遷移

一:手動故障轉移          Redis叢集支援手動故障轉移。也就是向從節點發送”CLUSTER  FAILOVER”命令,使其在主節點未下線的情況下,發起故障轉移流程,升級為新的主節點,而原來的主節點降級為從節點。          為了不丟失資料,向從節點發送”C

Redis原始碼解析27叢集(三)主從複製故障轉移

一:主從複製          在叢集中,為了保證叢集的健壯性,通常設定一部分叢集節點為主節點,另一部分叢集節點為這些主節點的從節點。一般情況下,需要保證每個主節點至少有一個從節點。          叢集初始化時,每個叢集節點都是以獨立的主節點角色而存在的,通過向叢集節點

JDK8HashMap原始碼解析remove方法removeNode方法

一、概述 在HashMap中如果要根據key刪除這個key對應的鍵值對,需要呼叫remove(key)方法,該方法將會根據查詢到匹配的鍵值對,將其從HashMap中刪除,並且返回鍵值對的值。 二、方法解析 我們先來看remove方法 /** * 從HashMap中刪除

jQuery原始碼解析(架構與依賴模組)一理解架構

一、設計原理 輕量級的js庫,相容CSS3,相容各種瀏覽器(IE6.0+,FF1.5+,Safari2.0+,Opera9.0+),Jquery2.0及後續潘奔不在支援IE6/7/8瀏覽器。jQuery一個比較大的優勢是,它的文件說明很全,並且各種應用很詳細,同時還有許多成

jQuery原始碼解析(1)—— jq基礎data快取系統

閒話 jquery 的原始碼已經到了1.12.0 版本,據官網說1版本和2版本若無意外將不再更新,3版本將做一個架構上大的調整。但估計能相容IE6-8的,也許這已經是最後的樣子了。 我學習jq的時間很短,應該在1月,那時的版本還是1.11.3,通過看妙味課堂

JavaScript預解析同名變數和函式同名函式表示式和同名函式宣告,執行哪個和變數提升的坑

先說下預解析的含義,在寫js程式碼呼叫函式的時候,無論你是在呼叫位置的前面或者後面宣告函式,都可以正常呼叫,原因是,JavaScript碰到script標籤,會將var變數(注意是var)宣告和函式宣告(注意是宣告)提升到當前作用域最前面。 要想搞懂預解析,先記住結論:

jQuery原始碼解析(4)—— css樣式定位屬性

閒話 原計劃是沒有這篇博文的,研究animation原始碼的時候遇到了css樣式這個攔路虎。比如jQuery支援“+=10”、“+=10px”定義一個屬性的增量,但是有的屬性設定時可以支援數字,有的必須有單位;在對屬性當前值讀取時,不同的瀏覽器可能返回不同的單

死磕 java同步系列之ReentrantLock原始碼解析(一)——公平鎖非公平鎖

問題 (1)重入鎖是什麼? (2)ReentrantLock如何實現重入鎖? (3)ReentrantLock為什麼預設是非公平模式? (4)ReentrantLock除了可重入還有哪些特性? 簡介 Reentrant = Re + entrant,Re是重複、又、再的意思,entrant是enter的名詞或

Redis原始碼解析15Resis主從複製之從節點流程

Redis原始碼解析:15Resis主從複製之從節點流程   版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/gqtcgq/article/details/51172085        

Java集合類原始碼解析AbstractMap

目錄 引言 原始碼解析 抽象函式entrySet() 兩個集合檢視 操作方法 兩個子類 參考: 引言 今天學習一個Java集合的一個抽象類 AbstractMap ,AbstractMap 是Map介面的 實現類之一,也是HashMap、T

Java集合類原始碼解析HashMap (基於JDK1.8)

目錄 前言 HashMap的資料結構 深入原始碼 兩個引數 成員變數 四個構造方法 插入資料的方法:put() 雜湊函式:hash() 動態擴容:resize() 節點樹化、紅黑樹的拆分 節點樹化

Java集合類原始碼解析Vector

引言 之前的文章我們學習了一個集合類 ArrayList,今天講它的一個兄弟 Vector。 為什麼說是它兄弟呢?因為從容器的構造來說,Vector 簡直就是 ArrayList 的翻版,也是基於陣列的資料結構,不同的是,Vector的每個方法都加了 synchronized 修飾符,是執行緒安全的。 類

jQuery原始碼解析變數與函式

 //原始碼剖析都基於jQuery-2.0.3版本,主要考慮到相容IE 2行:jQuery javaScript Library v2.0.3——jQuery版本 3行:http://jQuery.com——官網 5~6行:Includes Sizzle.js;http://sizzlejs.

Spark2.2.2原始碼解析 3.啟動worker節點啟動流程分析

本文啟動worker節點啟動流程分析   啟動命令: ${SPARK_HOME}/sbin/start-slave.sh spark://sysadmindeMacBook-Pro.local:7077   檢視start-slave.sh  

Spark2.2.2原始碼解析 2.啟動master節點流程分析

本文主要說明在啟動master節點的時候,程式碼的流程走向。   授予檔案執行許可權 chmod755  兩個目錄裡的檔案: /workspace/spark-2.2.2/bin  --所有檔案 /workspace/spark-2.2.2/sb