quarkus依賴注入之十:學習和改變bean懶加載規則

歡迎訪問我的GitHub

這裏分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本篇是《quarkus依賴注入》系列的第十篇,來看一個容易被忽略的知識點:bean的懶加載,咱們先去了解quarkus框架下的懶加載規則,然後更重要的是掌握如何改變規則,以達到提前實例化的目標
  • 總的來說本篇由以下內容構成
  1. 關於懶加載
  2. 編碼體驗懶加載
  3. 改變懶加載規則的第一種手段
  4. 改變懶加載規則的第二種手段(居然和官方資料有出入)
  5. 小結

關於懶加載(Lazy Instantiation

  • CDI規範下的懶加載規則:
  1. 常規作用域的bean(例如ApplicationScoped、RequestScoped),在注入時,實例化的是其代理類,而真實類的實例化發生在bean方法被首次調用的時候
  2. 僞作用域的bean(Dependent和Singleton),在注入時就會實例化
  • quarkus也遵循此規則,接下來編碼驗證

編碼驗證懶加載

  • 爲了驗證bean的懶加載,接下來會寫這樣一些代碼
  1. NormalApplicationScoped.java:作用域是ApplicationScoped的bean,其構造方法中打印日誌,帶有自己的類名
  2. NormalSingleton.java:作用域是Singleton的bean,其構造方法中打印日誌,帶有自己的類名
  3. ChangeLazyLogicTest.java:這是個單元測試類,裏面注入了NormalApplicationScoped和NormalSingleton的bean,在其ping方法中依次調用上面兩個bean的方法
  • 以上就是稍後要寫的代碼,咱們根據剛剛提到的懶加載規則預測一下要輸出的內容和順序:
  1. 首先,在ChangeLazyLogicTest的注入點,NormalSingleton會實例化,NormalApplicationScoped的代理類會實例化
  2. 然後,在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();
    }
}
  • 編碼完成,運行單元測試類,驗證我們之前的預測,控制檯輸出結果如下圖所示,符合預期

image-20220429184753022

  • 至此,懶加載基本規則咱們已經清楚了,聰明的您應該想到了此規則的弊端:如果在構造方法中有一些耗時操作,必須等到第一次調用bean的方法時纔會執行,這可能不符合我們的預期,有時候我們希望應用初始化的時候把耗時的事情做完,這樣執行bean方法的時候就沒有影響了
  • 顯然,quarkus也意識到了這個問題,於是,給出了兩中改變懶加載規則的方法,使得bean的實例化可以更早完成,接下來咱們逐個嘗試

改變懶加載規則的第一種手段

  • 讓bean儘早實例化的第一種手段,是讓bean消費StartupEvent事件,這是quarkus框架啓動成功後發出的事件,從時間上來看,此事件的時間比注入bean的時間還要早,這樣消費事件的bean就會實例化

  • 咱們給NormalApplicationScoped增加下圖紅框中的代碼,讓它消費StartupEvent事件

image-20220501093358565
  • 運行代碼前,先預測一下修改後的結果
  1. 首先應該是NormalApplicationScoped的實例化
  2. NormalApplicationScoped實例收到StarttupEvent事件,打印日誌
  3. 開始注入bean到ChangeLazyLogicTest,引發NormalApplicationScoped代理類和NormalSingleton的實例化
  4. 簡單地說:原本最晚實例化的NormalApplicationScoped,由於消費StarttupEvent事件,現在變成了最早實例化的
  • 現在運行代碼驗證,如下圖,符合預期
image-20220501094806401

改變懶加載規則的第二種手段(居然和官方資料有出入)

  • 第二種方法更簡單了:用StartupEvent修飾類,下圖是完整NormalApplicationScoped代碼,可見改動僅有紅框位置
image-20220501101416574
  • 在運行代碼前,先預測一下運行結果,理論上應該和第一種手段的結果差不多:NormalApplicationScoped、NormalApplicationScoped代理、NormalSingleton,
  • 上述推測的依據來自Startup源碼中的註釋,如下圖,官方表示StartupEvent和Startup效果一致
image-20220501102631368
  • 官方都這麼說了,我豈敢不信,不過流程還是要完成的,把修改後的代碼再運行一遍,截個圖貼到文中,走走過場...

  • 然而,這次運行的結果,卻讓人精神一振,StartupEvent和Startup效果是不一樣的!!!

  • 運行結果如下圖,最先實例化的居然不是被Startup註解修飾的NormalApplicationScoped,而是它的代理類!

image-20220501102150488
  • 由此可見,Startup可以將bean的實例化提前,而且是連帶bean的代理類的實例化也提前了
  • 回想一下,雖然結果與預期不符合,而預期來自官方註釋,但這並不代表官方註釋有錯,人家只說了句functionally equivalent,從字面上看並不涉及代理類的實例化
  • 另外Startup也有自己的獨特之處,一共有以下兩點
  1. Startup註解的value屬性值,是bean的優先級,這樣,多個bean都使用Startup的時候,可以通過value值設置優先級,以此控制實例化順序(實際上控制的是事件observer的創建順序)
  2. 如果一個類只有Startup註解修飾,而沒有設置作用域的時候,quarkus自動將其作用域設置爲ApplicationScoped,也就是說,下面這段代碼中,ApplicationScoped註解寫不寫都一樣
@ApplicationScoped
@Startup
public class NormalApplicationScoped {

小結

  • 懶加載、StartupEvent、Startup這三種情況下的實例化順序各不相同,最好是有個對比讓大家一目瞭然,方便選擇使用
  • 接下來就畫個對比圖,圖中有懶加載、StartupEvent、Startup三個場景,每個場景都是三個階段:quarkus框架初始化、注入bean、bean的方法被調用,每個階段都有哪些對象被實例化就是它們最大的區別,如下所示
流程图 (3)
  • 至此,懶加載相關的知識點學習完畢,個人認爲這是個很重要的技能,用好了它對業務有不小的助力,希望能給您一些參考吧

歡迎關注博客園:程序員欣宸

學習路上,你不孤單,欣宸原創一路相伴...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章