quarkus依賴註入之十三:其他重要知識點大串講(終篇)

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/08/12/17608158.html
-Advertisement-
Play Games

通過編碼實戰瞭解quarkus攔截器的另一個高級特性:禁用類級別攔截器,這樣可以避免類級別和方法級別攔截器的疊加衝突 ...


歡迎訪問我的GitHub

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

本篇概覽

  • 本篇是《quarkus依賴註入》系列的終篇,前面十二篇已覆蓋quarkus依賴註入的大部分核心內容,但依然漏掉了一些知識點,今天就將剩下的內容彙總,來個一鍋端,輕鬆愉快的結束這個系列
  • 總的來說,本篇由以下內容構成,每個段落都是個獨立的知識點
  1. 幾處可以簡化編碼的地方,如bean註入、構造方法等
  2. WithCaching:特定場景下,減少bean實例化次數
  3. 靜態方法是否可以被攔截器攔截?
  4. All註解,讓多個bean的註入更加直觀
  5. 統一處理非同步事件的異常
  • 咱們從最簡單的看起:表達方式的簡化,一共有三個位置可以簡化:bean的註入、bean構造方法、bean生產方法

簡化之一:bean註入

  • quarkus在CDI規範的基礎上做了簡化,可以讓我們少寫幾行代碼

  • 將配置文件中名為greeting.message的配置項註入到bean的成員變數greetingMsg中,按照CDI規範的寫法如下

  @Inject
  @ConfigProperty(name = "greeting.message")
  String greetingMsg;
  • 在quarkus框架下可以略去@Inject,寫成下麵這樣的效果和上面的代碼一模一樣
@ConfigProperty(name = "greeting.message")
String greetingMsg;

簡化之二:bean構造方法

  • 關於bean的構造方法,CDI有兩個規定:首先,必須要有無參構造方法,其次,有參數的構造方法需要@Inject註解修飾,實例代碼如下所示
@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService() { // dummy constructor needed
  }

  @Inject // constructor injection
  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}
  • 但是,在quarkus框架下,無參構造方法可不寫,有參數的構造方法也可以略去@Inject,寫成下麵這樣的效果和上面的代碼一模一樣
@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}

簡化之三:bean生產方法

  • 在CDI規範中,通過方法生產bean的語法如下,可見要同時使用ProducesApplicationScoped註解修飾返回bean的方法
class Producers {
  
  @Produces
  @ApplicationScoped
  MyService produceServ
    ice() {
    return new MyService(coolProperty);
  }
}
  • 在quarkus框架下可以略去@Produces,寫成下麵這樣的效果和上面的代碼一模一樣
class Producers {

  @ApplicationScoped
  MyService produceService() {
    return new MyService(coolProperty);
  }
}
  • 好了,熱身結束,接下來看幾個略有深度的技能

WithCaching註解:避免不必要的多次實例化

  • 在介紹WithCaching註解之前,先來看一個普通場景
  • 下麵是一段單元測試代碼,HelloDependent類型的bean通過Instance的方式被註入,再用Instance#get來獲取此bean
@QuarkusTest
public class WithCachingTest {

    @Inject
    Instance<HelloDependent> instance;

    @Test
    public void test() {
        // 第一次調用Instance#get方法
        HelloDependent helloDependent = instance.get();
        helloDependent.hello();

        // 第二次調用Instance#get方法
        helloDependent = instance.get();
        helloDependent.hello();
    }
}
  • 上述代碼是種常見的bean註入和使用方式,我們的本意是在WithCachingTest實例中多次使用HelloDependent類型的bean,可能是在test方法中使用,也可能在WithCachingTest的其他方法中使用

  • 如果HelloDependent的作用域是ApplicationScoped,上述代碼一切正常,但是,如果作用域是Dependent呢?代碼中執行了兩次Instance#get,得到的HelloDependent實例是同一個嗎?Dependent的特性是每次註入都實例化一次,這裡的Instance#get又算幾次註入呢?

  • 最簡單的方法就是運行上述代碼看實際效果,這裡先回顧HelloDependent.java的源碼,如下所示,構造方法中會列印日誌,這下好辦了,只要看日誌出現幾次,就知道實例化幾次了

@Dependent
public class HelloDependent {

    public HelloDependent(InjectionPoint injectionPoint) {
        Log.info("injecting from bean "+ injectionPoint.getMember().getDeclaringClass());
    }

    public String hello() {
        return this.getClass().getSimpleName();
    }
}
  • 運行單元測試類WithCachingTest,如下圖紅框所示,構造方法中的日誌列印了兩次,所以:每次Instance#get都相當於一次註入,如果bean的作用域是Dependent,就會創建一個新的實例並返回
image-20220427083442714
  • 現在問題來了:如果bean的作用域必須是Dependent,又希望多次Instance#get返回的是同一個bean實例,這樣的要求可以做到嗎?
  • 答案是可以,用WithCaching註解修飾Instance即可,改動如下圖紅框1,改好後再次運行,紅框2顯示HelloDependent只實例化了一次

image-20220427084522435

攔截靜態方法

  • 先回顧一下攔截器的基本知識,定義一個攔截器並用來攔截bean中的方法,總共需要完成以下三步
流程圖 (4)
  • 實現攔截器的具體功能時,還要用註解指明攔截器類型,一共有四種類型
  1. AroundInvoke:攔截bean方法
  2. PostConstruct:生命周期攔截器,bean創建後執行
  3. PreDestroy:生命周期攔截器,bean銷毀前執行
  4. AroundConstruct:生命周期攔截器,攔截bean構造方法
  • 現在問題來了:攔截器能攔截靜態方法嗎?
  • 答案是可以,但是有限制,具體的限制如下
  1. 僅支持方法級別的攔截(即攔截器修飾的是方法)
  2. private型的靜態方法不會被攔截
  3. 下圖是攔截器實現的常見代碼,通過入參InvocationContext的getTarget方法,可以得到被攔截的對象,然而,在攔截靜態方法時,getTarget方法的返回值是null,這一點尤其要註意,例如下圖紅框中的代碼,在攔截靜態方法是就會拋出空指針異常
image-20220501162427008

All更加直觀的註入

  • 假設有個名為SayHello的介面,源碼如下
public interface SayHello {
    void hello();
}
  • 現在有三個bean都實現了SayHello介面,如果想要調用這三個bean的hello方法,應該怎麼做呢?

  • 按照CDI的規範,應該用Instance註入,然後使用Instance中的迭代器即可獲取所有bean,代碼如下

public class InjectAllTest {
    /**
     * 用Instance接收註入,得到所有SayHello類型的bean
     */
    @Inject
    Instance<SayHello> instance;

    @Test
    public void testInstance() {
        // instance中有迭代器,可以用遍歷的方式得到所有bean
        for (SayHello sayHello : instance) {
            sayHello.hello();
        }
    }
}
  • quarkus提供了另一種方式,藉助註解io.quarkus.arc.All,可以將所有SayHello類型的bean註入到List中,如下所示
@QuarkusTest
public class InjectAllTest {
    /**
     * 用All註解可以將SayHello類型的bean全部註入到list中,
     * 這樣更加直觀
     */
    @All
    List<SayHello> list;

    @Test
    public void testAll() {
        for (SayHello sayHello : list) {
            sayHello.hello();
        }
    }
}
  • 和CDI規範相比,使用All註解可以讓代碼顯得更為直觀,另外還有以下三個特點
  1. 此list是immutable的(內容不可變)

  2. list中的bean是按照priority排序的

  3. 如果您需要的不僅僅是註入bean,還需要bean的元數據信息(例如bean的scope),可以將List中的類型從SayHello改為InstanceHandle<SayHello>,這樣即可以得到註入bean,也能得到註入bean的元數據(在InjectableBean中),參考代碼如下

@QuarkusTest
public class InjectAllTest {
    
    @All
    List<InstanceHandle<SayHello>> list;

    @Test
    public void testQuarkusAllAnnonation() {
        for (InstanceHandle<SayHello> instanceHandle : list) {
            // InstanceHandle#get可以得到註入bean
            SayHello sayHello = instanceHandle.get();

            // InjectableBean封裝了註入bean的元數據信息
            InjectableBean<SayHello> injectableBean = instanceHandle.getBean();

            // 例如bean的作用域就能從InjectableBean中取得
            Class clazz = injectableBean.getScope();

            // 列印出來驗證
            Log.infov("bean [{0}], scope [{1}]", sayHello.getClass().getSimpleName(), clazz.getSimpleName() );
        }
    }
}
  • 代碼的執行結果如下圖紅框所示,可見註入bean及其作用域都能成功取得(要註意的是註入bean是代理bean)

image-20220502165300841

統一處理非同步事件的異常

  • 需要提前說一下,本段落涉及的知識點和AsyncObserverExceptionHandler類有關,而《quarkus依賴註入》系列所用的quarkus-2.7.3.Final版本中並沒有AsyncObserverExceptionHandler類,後來將quarkus版本更新為2.8.2.Final,就可以正常使用AsyncObserverExceptionHandler類了

  • 本段落的知識點和非同步事件有關:如果消費非同步事件的過程中發生異常,而開發者有沒有專門寫代碼處理非同步消費結果,那麼此異常就默默無聞的被忽略了,我們也可能因此錯失了及時發現和處理問題的時機

  • 來寫一段代碼復現上述問題,首先是事件定義TestEvent.java,就是個普通類,啥都沒有

public class TestEvent {
}
  • 然後是事件的生產者TestEventProducer.java,註意其調用fireAsync方法發送了一個非同步事件
@ApplicationScoped
public class TestEventProducer {

    @Inject
    Event<TestEvent> event;

    /**
     * 發送非同步事件
     */
    public void asyncProduce() {
        event.fireAsync(new TestEvent());
    }
}
  • 事件的消費者TestEventConsumer.java,這裡在消費TestEvent事件的時候,故意拋出了異常
@ApplicationScoped
public class TestEventConsumer {

    /**
     * 消費非同步事件,這裡故意拋出異常
     */
    public void aSyncConsume(@ObservesAsync TestEvent testEvent) throws Exception {
       throw new Exception("exception from aSyncConsume");
    }
}
  • 最後是單元測試類將事件的生產和消費運行起來
@QuarkusTest
public class EventExceptionHandlerTest {

    @Inject
    TestEventProducer testEventProducer;

    @Test
    public void testAsync() throws InterruptedException {
       testEventProducer.asyncProduce();
    }
}
  • 運行EventExceptionHandlerTest,結果如下圖,DefaultAsyncObserverExceptionHandler處理了這個異常,這是quarkus框架的預設處理邏輯

image-20220502205725214

  • DefaultAsyncObserverExceptionHandler只是輸出了日誌,這樣的處理對於真實業務是不夠的(可能需要記錄到特定地方,調用其他告警服務等),所以,我們需要自定義預設的非同步事件異常處理器
  • 自定義的全局非同步事件異常處理器如下
package com.bolingcavalry.service.impl;

import io.quarkus.arc.AsyncObserverExceptionHandler;
import io.quarkus.logging.Log;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.EventContext;
import javax.enterprise.inject.spi.ObserverMethod;

@ApplicationScoped
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {

    @Override
    public void handle(Throwable throwable, ObserverMethod<?> observerMethod, EventContext<?> eventContext) {
        // 異常信息
        Log.info("exception is - " + throwable);
        // 事件信息
        Log.info("observer type is - " + observerMethod.getObservedType().getTypeName());
    }
}
  • 此刻,咱們再執行一次單元測試,如下圖所示,異常已經被NoopAsyncObserverExceptionHandler#handler處理,異常和事件相關的信息都能拿到,您可以按照實際的業務需求來進行定製了

image-20220502210222786

  • 另外還要說明一下,自定義的全局非同步事件異常處理器,其作用域只能是ApplicationScoped或者Singleton
  • 至此,《quarkus依賴註入》系列全部完成,與bean相關的故事也就此結束了,十三篇文章凝聚了欣宸對quarkus框架bean容器的思考和實踐,希望能幫助您更快的掌握和理解quarkus最核心的領域
  • 雖然《quarkus依賴註入》已經終結,但是《quarkus實戰》系列依然還在持續更新中,有了依賴註入的知識作為基礎,接下來的quarkus之旅會更加輕鬆和高效

歡迎關註博客園:程式員欣宸

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 轉載放在最前 [一文帶你瞭解,虛擬記憶體、記憶體分頁、分段、段頁式記憶體管理](https://zhuanlan.zhihu.com/p/451736494)[[Golang三關-典藏版]一站式Golang記憶體洗髓經 | Go 技術論壇](https://learnku.com/articles/6 ...
  • 垃圾收集器 HotSpot虛擬機包含的所有收集器如圖3-5所示。圖3-5展示了7種作用於不同分代的收集器,如果兩個收集器之間存在連線,就說明它們可以搭配使用。 新生代收集器:Serial、ParNew、Parallel Scavenge,新生代收集器均採用複製演算法 老年代收集器:Serial Old ...
  • 基本環境準備(第一節)2023年8月9日16:37 1.安裝Node.js;Windows 上安裝 Node.js你可以採用以下兩種方式來安裝。1、Windows 安裝包(.msi)本文實例以 v0.10.26 版本為例,其他版本類似, 安裝步驟: 步驟 1 : 雙擊下載後的安裝包 v0.10.26 ...
  • 在一個需要用到flag作為信號控制代碼中一些代碼片段是否運行的,比如"--flag True"或者"--flag False"。 但是古怪的是無法傳入False,無論傳入True還是False,程式裡面都是True的參數,所以這個flag並沒有生效,也就失去了意義。 參考代碼: ```python ...
  • 在本篇文章中,會先介紹 Python 中對象的基礎概念,之後會提到對象的深淺拷貝以及區別。在閱讀後,應該掌握如下的內容: - 理解變數、引用和對象的關係 - 理解 Python 對象中 identity,type 和 value 的概念 - 什麼是 mutable 和 immutable 對象?以及 ...
  • # 《Rust編程之道》學習筆記一 ## 序 ### Rust語言的主要特點 - 系統級語言 - 無GC - 基於LLVM - 記憶體安全 - 強類型+靜態類型 - 混合編程範式 - 零成本抽象 - 線程安全 ### 程式員的快樂 何謂快樂?真正的快樂不僅僅是寫代碼時的“酸爽”,更應該是代碼部署到生產 ...
  • 項目工程中,集成資料庫實現對數據的增曬改查管理,是最基礎的能力,通常涉及三個基礎組件:連接池,持久層框架,數據源。 ...
  • 當我們需要處理一個大量的數據集合時,一次性將其全部讀入記憶體並處理可能會導致記憶體溢出。此時,我們可以採用迭代器`Iterator`和生成器`Generator`的方法,逐個地處理數據,從而避免記憶體溢出的問題。迭代器是一個可以逐個訪問元素的對象,它實現了`python`的迭代協議,即實現了`__iter... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...