天天看點

Spring 架構中 @PostConstruct 注解詳解

Spring 架構中 @PostConstruct 注解詳解

在Spring項目經常遇到@PostConstruct注解,首先介紹一下它的用途: 被注解的方法,在對象加載完依賴注入後執行。

此注解是在Java EE5規範中加入的,在Servlet生命周期中有一定作用,它通常都是一些初始化的操作,但初始化可能依賴于注入的其他元件,是以要等依賴全部加載完再執行。

與之對應的還有@PreDestroy,在對象消亡之前執行,原理差不多,這裡不做過多介紹。

那麼首先看下源碼注釋

Spring 架構中 @PostConstruct 注解詳解

PostConstruct介紹

總體概括如上,注意其中幾個點

1. 要在依賴加載後,對象使用前執行,而且隻執行一次,原因在上面已經說了。

2. 所有支援依賴注入的類都要支援此方法。

首先,我們可以看到這個注解是在javax.annotation包下的,也就是java拓展包定義的注解,并不是spring定義的,但至于為什麼不在java包下,是因為java語言的元老們認為這個東西并不是java核心需要的工具,是以就放到擴充包裡(javax中的x就是extension的意思),而spring是支援依賴注入的,是以spring必須要自己來實作@PostConstruct的功能。

3. 文檔中說一個類隻能有一個方法加此注解,但實際測試中,我在一個類中多個方法加了此注解,并沒有報錯,而且都執行了,我用的是 Spring Boot 架構。

再往下看,這個注解有一些使用條件,挑一些重點的說一下

Spring 架構中 @PostConstruct 注解詳解

PostConstruct注釋規則

1. 除了攔截器這個特殊情況以外,其他情況都不允許有參數,否則spring架構會報IllegalStateException;而且傳回值要是void,但實際也可以有傳回值,至少不會報錯,隻會忽略

2. 方法随便你用什麼權限來修飾,public、protected、private都可以,反正功能是由反射來實作

3. 方法不可以是static的,但可以是final的

是以,綜上所述,在spring項目中,在一個bean的初始化過程中,方法執行先後順序為

Constructor > @Autowired > @PostConstruct

先執行完構造方法,再注入依賴,最後執行初始化操作,是以這個注解就避免了一些需要在構造方法裡使用依賴元件的尴尬。

以上是對@PostConstruct的簡單介紹,下面會從spring源碼分析其具體實作原理。Spring 最常用的 7 大類注解這篇推薦看下。

PostConstruct實作原理

spring遵守了JSR-250标準,實作了javax.annotation包裡面的各種注解功能,首先我們在GitHub下載下傳spring-framework源碼,我下的是5.0.x分支代碼,導入到idea中,下面就開始動手分析。

首先代碼中搜尋"import javax.annotation.PostConstruct",慶幸的是隻有CommonAnnotationBeanPostProcessor這一個類有引用PostConstruct類,看名字八九不離十就是它了,它是在org.springframework.context.annotation包下,大緻介紹如下:

Spring 架構中 @PostConstruct 注解詳解

CommonAnnotationBeanPostProcessor

看來沒什麼營養,隻是一些簡單介紹說明了我們在什麼版本,基于什麼标準,實作了這幾個注解,那麼看代碼。

Spring 架構中 @PostConstruct 注解詳解

CommonAnnotationBeanPostProcessor構造方法

看來隻有CommonAnnotationBeanPostProcessor的構造方法使用了這個注解,聲明了這個BeanPostProcessor要支援PostConstruct初始化注解。

關注微信公衆号:Java技術棧,在背景回複:Spring,可以擷取我整理的 N 篇最新 Spring 教程,都是幹貨。

跟進去setInitAnnotationType這個方法,是父類InitDestroyAnnotationBeanPostProcessor中的方法,隻是簡單的将PostConstruct.class指派給成員變量initAnnotationType。

那麼誰去使用了這個變量,再次意外的發現,隻有buildLifecycleMetadata一個方法使用了這個變量。

Spring 架構中 @PostConstruct 注解詳解

buildLifecycleMetadata方法

這個方法做的事情也很簡單,輸入一個類,檢查它或者它的祖先類是否有初始化方法以及銷毀方法,如果有,把這些資訊封裝成一個LifecycleMetadata類,裡面大概資訊就是類名、初始化和銷毀方法清單,友善bean注冊或消亡的時候去調用。

偶然看到LifecycleMetadata中初始化方法清單是List ,LifecycleElement類裡面的構造方法有限制方法不能有參數,否則報錯IllegalStateException,和前文測試結果對應上了。

Spring 架構中 @PostConstruct 注解詳解

LifecycleElement構造方法

這是題外話了,接着看buildLifecycleMetadata方法中while循環裡,不斷周遊父類,找PostConstruct注解,每找完一個父類,往initMethods中累加,最後注冊到與這個bean相應的initMethods中。

前文說了 “我在一個類中多個方法加了此注解,并沒有報錯,而且都執行了”,看過上述代碼後就知道了,spring根本沒有按照javax的要求做限制,可能認為沒必要吧。那麼多個PostConstruct注解或父類也有此注解,他們是什麼順序執行的呢?

1. 首先父類的初始化方法是先于子類的先執行,但注意不要被子類方法重寫,那父類初始化方法就不會執行了,因為中間有一步是用LinkedHashSet存了method的名字。

2. 同一類内,多個PostConstruct注解方法不是按聲明順序執行的,看了一下代碼邏輯,雖然存儲方法的集合都是有序集合,看起來應該可以順序執行,但實際上是以一種非常詭異的順序來執行。

為了看一下spring的初始化過程,在application.properties中設定trace=true,在控制台看debug日志後發現,跟存儲方法的集合沒聲明關系,最開始反射取方法的時候順序就打亂了。

罪魁禍首就是ReflectionUtils.doWithLocalMethods 這個方法啦!看了一下JDK的API,發現它強調了Class類不能保證getDeclaredMethods()的順序,因為JVM有權在編譯時,自行決定類成員的順序。

好了,是以現在知道了buildLifecycleMetadata這個方法,就是将bean生命周期的中繼資料組裝一下傳回,在類中也隻有下面一個方法調用了

Spring 架構中 @PostConstruct 注解詳解

findLifecycleMetadata方法

它把bean的LifecycleMetadata放到一個ConcurrentHashMap儲存。然後再往上找,就是AbstractAutowireCapableBeanFactory對bean的初始化和消亡操作了。

在注冊完之後就會invoke方法,這是另外一個話題了,此處不再過多介紹,是以本文到此為止。

綜上,通過源碼來學習還是很高效的嘛,主要是學習大神們的代碼精髓。