為了降低啟動時間,quarkus下的常規作用域bean遵循懶載入規則,但有時我們希望bean可以更早實例化,本篇,咱們一起來瞭解懶載入規則和改變規則的方法 ...
歡迎訪問我的GitHub
這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
- 本篇是《quarkus依賴註入》系列的第十篇,來看一個容易被忽略的知識點:bean的懶載入,咱們先去瞭解quarkus框架下的懶載入規則,然後更重要的是掌握如何改變規則,以達到提前實例化的目標
- 總的來說本篇由以下內容構成
- 關於懶載入
- 編碼體驗懶載入
- 改變懶載入規則的第一種手段
- 改變懶載入規則的第二種手段(居然和官方資料有出入)
- 小結
關於懶載入(Lazy Instantiation)
- CDI規範下的懶載入規則:
- 常規作用域的bean(例如ApplicationScoped、RequestScoped),在註入時,實例化的是其代理類,而真實類的實例化發生在bean方法被首次調用的時候
- 偽作用域的bean(Dependent和Singleton),在註入時就會實例化
- quarkus也遵循此規則,接下來編碼驗證
編碼驗證懶載入
- 為了驗證bean的懶載入,接下來會寫這樣一些代碼
- NormalApplicationScoped.java:作用域是ApplicationScoped的bean,其構造方法中列印日誌,帶有自己的類名
- NormalSingleton.java:作用域是Singleton的bean,其構造方法中列印日誌,帶有自己的類名
- ChangeLazyLogicTest.java:這是個單元測試類,裡面註入了NormalApplicationScoped和NormalSingleton的bean,在其ping方法中依次調用上面兩個bean的方法
- 以上就是稍後要寫的代碼,咱們根據剛剛提到的懶載入規則預測一下要輸出的內容和順序:
- 首先,在ChangeLazyLogicTest的註入點,NormalSingleton會實例化,NormalApplicationScoped的代理類會實例化
- 然後,在ChangeLazyLogicTest#ping方法中,由於調用了NormalApplicationScoped的方法,會導致NormalApplicationScoped的實例化
- 接下來開始寫代碼,第一個bean,NormalApplicationScoped.java
package com.bolingcavalry;
import com.bolingcavalry.service.impl.NormalApplicationScoped;
import com.bolingcavalry.service.impl.NormalSingleton;
import io.quarkus.logging.Log;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
@QuarkusTest
class ChangeLazyLogicTest {
@Inject
NormalSingleton normalSingleton;
@Inject
NormalApplicationScoped normalApplicationScoped;
@Test
void ping() {
Log.info("start invoke normalSingleton.ping");
normalSingleton.ping();
Log.info("start invoke normalApplicationScoped.ping");
normalApplicationScoped.ping();
}
}
- 第二個bean,NormalSingleton.java
package com.bolingcavalry.service.impl;
import io.quarkus.logging.Log;
import javax.inject.Singleton;
@Singleton
public class NormalSingleton {
public NormalSingleton() {
Log.info("Construction from " + this.getClass().getSimpleName());
}
public String ping() {
return "ping from NormalSingleton";
}
}
- 然後是單元測試類ChangeLazyLogicTest,可見NormalApplicationScoped構造方法的日誌應該在start invoke normalApplicationScoped.ping這一段之後
package com.bolingcavalry;
import com.bolingcavalry.service.impl.NormalApplicationScoped;
import com.bolingcavalry.service.impl.NormalSingleton;
import io.quarkus.logging.Log;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;
@QuarkusTest
class ChangeLazyLogicTest {
@Inject
NormalSingleton normalSingleton;
@Inject
NormalApplicationScoped normalApplicationScoped;
@Test
void ping() {
Log.info("start invoke normalSingleton.ping");
normalSingleton.ping();
Log.info("start invoke normalApplicationScoped.ping");
normalApplicationScoped.ping();
}
}
- 編碼完成,運行單元測試類,驗證我們之前的預測,控制台輸出結果如下圖所示,符合預期
- 至此,懶載入基本規則咱們已經清楚了,聰明的您應該想到了此規則的弊端:如果在構造方法中有一些耗時操作,必須等到第一次調用bean的方法時才會執行,這可能不符合我們的預期,有時候我們希望應用初始化的時候把耗時的事情做完,這樣執行bean方法的時候就沒有影響了
- 顯然,quarkus也意識到了這個問題,於是,給出了兩中改變懶載入規則的方法,使得bean的實例化可以更早完成,接下來咱們逐個嘗試
改變懶載入規則的第一種手段
-
讓bean儘早實例化的第一種手段,是讓bean消費StartupEvent事件,這是quarkus框架啟動成功後發出的事件,從時間上來看,此事件的時間比註入bean的時間還要早,這樣消費事件的bean就會實例化
-
咱們給NormalApplicationScoped增加下圖紅框中的代碼,讓它消費StartupEvent事件
- 運行代碼前,先預測一下修改後的結果
- 首先應該是NormalApplicationScoped的實例化
- NormalApplicationScoped實例收到StarttupEvent事件,列印日誌
- 開始註入bean到ChangeLazyLogicTest,引發NormalApplicationScoped代理類和NormalSingleton的實例化
- 簡單地說:原本最晚實例化的NormalApplicationScoped,由於消費StarttupEvent事件,現在變成了最早實例化的
- 現在運行代碼驗證,如下圖,符合預期
改變懶載入規則的第二種手段(居然和官方資料有出入)
- 第二種方法更簡單了:用StartupEvent修飾類,下圖是完整NormalApplicationScoped代碼,可見改動僅有紅框位置
- 在運行代碼前,先預測一下運行結果,理論上應該和第一種手段的結果差不多:NormalApplicationScoped、NormalApplicationScoped代理、NormalSingleton,
- 上述推測的依據來自Startup源碼中的註釋,如下圖,官方表示StartupEvent和Startup效果一致
-
官方都這麼說了,我豈敢不信,不過流程還是要完成的,把修改後的代碼再運行一遍,截個圖貼到文中,走走過場...
-
然而,這次運行的結果,卻讓人精神一振,StartupEvent和Startup效果是不一樣的!!!
-
運行結果如下圖,最先實例化的居然不是被Startup註解修飾的NormalApplicationScoped,而是它的代理類!
- 由此可見,Startup可以將bean的實例化提前,而且是連帶bean的代理類的實例化也提前了
- 回想一下,雖然結果與預期不符合,而預期來自官方註釋,但這並不代表官方註釋有錯,人家只說了句functionally equivalent,從字面上看並不涉及代理類的實例化
- 另外Startup也有自己的獨特之處,一共有以下兩點
- Startup註解的value屬性值,是bean的優先順序,這樣,多個bean都使用Startup的時候,可以通過value值設置優先順序,以此控制實例化順序(實際上控制的是事件observer的創建順序)
- 如果一個類只有Startup註解修飾,而沒有設置作用域的時候,quarkus自動將其作用域設置為ApplicationScoped,也就是說,下麵這段代碼中,ApplicationScoped註解寫不寫都一樣
@ApplicationScoped
@Startup
public class NormalApplicationScoped {
小結
- 懶載入、StartupEvent、Startup這三種情況下的實例化順序各不相同,最好是有個對比讓大家一目瞭然,方便選擇使用
- 接下來就畫個對比圖,圖中有懶載入、StartupEvent、Startup三個場景,每個場景都是三個階段:quarkus框架初始化、註入bean、bean的方法被調用,每個階段都有哪些對象被實例化就是它們最大的區別,如下所示
- 至此,懶載入相關的知識點學習完畢,個人認為這是個很重要的技能,用好了它對業務有不小的助力,希望能給您一些參考吧