quarkus依賴註入之四:選擇註入bean的高級手段

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

### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《quarkus依賴註入》系列的第 ...


歡迎訪問我的GitHub

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

本篇概覽

  • 本文是《quarkus依賴註入》系列的第四篇,在應用中,一個介面有多個實現是很常見的,那麼依賴註入時,如果類型是介面,如何準確選擇實現呢?前文介紹了五種註解,用於通過配置項、profile等手段選擇註入介面的實現類,面對複雜多變的業務場景,有時候僅靠這兩種手段是不夠的,最好是有更自由靈活的方式來選擇bean,這就是本篇的內容,通過註解、編碼等更多方式選擇bean
  • 本篇涉及的選擇bean的手段有以下四種:
  1. 修飾符匹配
  2. Named註解的屬性匹配
  3. 根據優先順序選擇
  4. 寫代碼選擇

關於修飾符匹配

  • 為了說明修飾符匹配,先來看一個註解Default,其源碼如下
@Target({ TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
@Documented
@Qualifier
public @interface Default {
    public static final class Literal extends AnnotationLiteral<Default> implements Default {
    	public static final Literal INSTANCE = new Literal();
			private static final long serialVersionUID = 1L;
    }
}
  • Default的源碼在這裡不重要,關鍵是它被註解Qualifier修飾了,這種被Qualifier修飾的註解,咱們姑且稱之為Qualifier修飾符
  • 如果咱們新建一個註解,也用Qualifier來修飾,如下所示,這個MyQualifier也是個Qualifier修飾符
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MyQualifier {
    @Nonbinding String value();
}
  • 在quarkus容器中的每一個bean都應該有一個Qualifier修飾符在修飾,如下圖紅框,如果沒有,就會被quarkus添加Default註解
    在這裡插入圖片描述
  • 依賴註入時,直接用Qualifier修飾符修飾註入對象,這樣quarkus就會去尋找被這個Qualifier修飾符修飾的bean,找到就註入(找不到報錯,找到多個也報錯,錯誤邏輯和之前的一樣)
  • 所以用修飾符匹配來選擇bean的實現類,一共分三步:
  1. 假設有名為HelloQualifier的介面,有三個實現類:HelloQualifierA、HelloQualifierB、HelloQualifierC,業務需求是使用HelloQualifierA
  2. 第一步:自定義一個註解,假設名為MyQualifier,此註解要被Qualifier修飾
  3. 第二步:用MyQualifier修飾HelloQualifierA
  4. 第三步:在業務代碼的註入點,用MyQualifier修飾HelloQualifier類型的成員變數,這樣成員變數就會被註入HelloQualifierA實例
  • 僅憑文字描述,很難把信息準確傳遞給讀者(畢竟欣宸文化水平極其有限),還是寫代碼實現上述場景吧,聰明的您一看就懂

編碼演示修飾符匹配:準備工作

  • 先按照前面的假設將介面和實現類準備好,造成一個介面有多個實現bean的事實,然後,再用修飾符匹配來準確選定bean

  • 首先是介面HelloQualifier,如下所示

package com.bolingcavalry.service;

public interface HelloQualifier {
    String hello();
}
  • 實現類HelloQualifierA,返回自己的類名
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloQualifier;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class HelloQualifierA implements HelloQualifier {
    @Override
    public String hello() {
        return this.getClass().getSimpleName();
    }
}
  • 實現類HelloQualifierB、HelloQualifierC的代碼和上面的HelloQualifierA相同,都是返回自己類名,就不貼出來了
  • 關於使用HelloQualifier類型bean的代碼,咱們就在單元測試類中註入吧,如下所示:
package com.bolingcavalry;

import com.bolingcavalry.service.HelloQualifier;
import com.bolingcavalry.service.impl.HelloQualifierA;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.inject.Inject;

@QuarkusTest
public class QualifierTest {

    @Inject
    HelloQualifier helloQualifier;

    @Test
    public void testQualifier() {
        Assertions.assertEquals(HelloQualifierA.class.getSimpleName(),
                helloQualifier.hello());
    }
}
  • 上面的代碼中,成員變數helloQualifier的類型是HelloQualifier,quarkus的bean容器中,HelloQualifierA、HelloQualifierB、HelloQualifierC等三個bean都符合註入要求,此時如果執行單元測試,應該會報錯:同一個介面多個實現bean的問題
  • 執行單元測試,如下圖,黃框中給出了兩個線索:第一,錯誤原因是註入時發現同一個介面有多個實現bean,第二,這些bean都是用Default修飾的,然後是綠框,裡面將所有實現bean列出來,方便開發者定位問題

image-20220320192140670

  • 現在準備工作完成了,來看如何用修飾符匹配解決問題:在註入點準確註入HelloQualifierA類型實例

編碼演示修飾符匹配:實現匹配

  • 使用修飾符匹配,繼續按照前面總結的三步走
  • 第一步:自定義一個註解,名為MyQualifier,此註解要被Qualifier修飾
package com.bolingcavalry.annonation;

import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MyQualifier {
    @Nonbinding String value();
}
  • 第二步:用MyQualifier修飾HelloQualifierA,下圖紅框是新增的代碼
    在這裡插入圖片描述

  • 第三步:在業務代碼的註入點,用MyQualifier修飾HelloQualifier類型的成員變數,下圖紅框是新增的代碼
    在這裡插入圖片描述

  • 改動完成了,再次執行單元測試,順利通過

在這裡插入圖片描述

修飾符匹配要註意的地方

  • 修飾符匹配的邏輯非常簡單:bean定義和bean註入的地方用同一個修飾符即可,使用中有三個地方要註意
  1. 在註入bean的地方,如果有了Qualifier修飾符,可以把@Inject省略不寫了
  2. 在定義bean的地方,如果沒有Qualifier修飾符去修飾bean,quarkus會預設添加Default
  3. 在註入bean的地方,如果沒有Qualifier修飾符去修飾bean,quarkus會預設添加Default

關於預設的@Default

  • 回頭看剛纔的代碼,如果保留HelloQualifierA的MyQualifier修飾,但是刪除QualifierTest的成員變數helloQualifier的MyQualifier修飾,會發生什麼呢?咱們來分析一下:

  • 首先,QualifierTest的成員變數helloQualifier會被quarkus預設添加Default修飾

  • 其次,HelloQualifierB和HelloQualifierC都會被quarkus預設添加Default修飾

  • 所以,註入helloQualifier的時候,quarkus去找Default修飾的bean,結果找到了兩個:HelloQualifierB和HelloQualifierC,因此啟動會失敗

  • 您可以自行驗證結果是否和預期一致

  • 看到這裡,您應該掌握了修飾符匹配的用法,也應該發現其不便之處:要新增註解,這樣下去隨著業務發展,註解會越來越多,有沒有什麼方法來解決這個問題呢?

  • 方法是有的,就是接下來要看的Named註解

Named註解的屬性匹配

  • Named註解的功能與前面的Qualifier修飾符是一樣的,其特殊之處在於通過註解屬性來匹配修飾bean和註入bean

  • 以剛纔的業務代碼為例來演示Named註解,修改HelloQualifierA,如下圖紅框,將@MyQualifier("")換成@Named("A"),重點關註Named註解的屬性值,這裡等於A
    在這裡插入圖片描述

  • 接下來修改註入處的代碼,如下圖紅框,在註入位置也用@Named("A")來修飾,和bean定義處的一模一樣
    在這裡插入圖片描述

  • 如此,bean定義和bean註入的兩個地方,通過Named註解的屬性完成了匹配,至於單元測試您可以自行驗證,這裡就不贅述了

  • 至此,詳細您已經知道了Named註解的作用:功能與前面的Qualifier修飾符一樣,不過bean的定義和註入處的匹配邏輯是Named註解的屬性值

  • 以上就是修飾符匹配的全部內容

根據優先順序選擇

  • 使用優先順序來選擇註入是一種簡潔的方式,其核心是用AlternativePriority兩個註解修飾所有備選bean,然後用Priority的屬性值(int型)作為優先順序,該值越大代表優先順序越高

  • 在註入位置,quarkus會選擇優先順序最高的bean註入

  • 接下來編碼演示

  • 新增演示用的介面HelloPriority.java

public interface HelloPriority {
    String hello();
}
  • HelloPriority的第一個實現類HelloPriorityA.java,註意它的兩個註解AlternativePriority,前者表明這是個可供選擇的bean,後者表明瞭它的優先順序,數字1001用於和其他bean的優先順序比較,數字越大優先順序越高
@ApplicationScoped
@Alternative
@Priority(1001)
public class HelloPriorityA implements HelloPriority {
    @Override
    public String hello() {
        return this.getClass().getSimpleName();
    }
}
  • HelloPriority的第二個實現類HelloPriorityB,可見Priority屬性值是1002,代表選擇的時候優先順序比HelloPriorityA更高
@ApplicationScoped
@Alternative
@Priority(1002)
public class HelloPriorityB implements HelloPriority {
    @Override
    public String hello() {
        return this.getClass().getSimpleName();
    }
}
  • HelloPriority的第二個實現類HelloPriorityC,可見Priority屬性值是1003,代表選擇的時候優先順序比HelloPriorityA和HelloPriorityB更高
@ApplicationScoped
@Alternative
@Priority(1003)
public class HelloPriorityC implements HelloPriority {
    @Override
    public String hello() {
        return this.getClass().getSimpleName();
    }
}
  • 接下來是單元測試,驗證註入的bean是否符合預期,理論上註入的應該是優先順序最高的HelloPriorityC
@QuarkusTest
public class PriorityTest {

    @Inject
    HelloPriority helloPriority;

    @Test
    public void testSelectHelloInstanceA() {
        Assertions.assertEquals(HelloPriorityC.class.getSimpleName(),
                                helloPriority.hello());
    }
}
  • 單元測試結果如下,符合預期

在這裡插入圖片描述

  • 以上就是優先順序選擇bean的操作,如果這還不夠用,那就祭出最後一招:寫代碼選擇bean

寫代碼選擇bean

  • 如果不用修飾符匹配,再回到最初的問題:有三個bean都實現了同一個介面,應該如何註入?
  • 在註入bean的位置,如果用Instance<T>來接收註入,就可以拿到T類型的所有bean,然後在代碼中隨心所欲的使用這些bean
  • 新增演示用的介面HelloInstance.java
package com.bolingcavalry.service;

public interface HelloInstance {
    String hello();
}
  • HelloInstance的第一個實現類HelloInstanceA.java
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloInstance;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class HelloInstanceA implements HelloInstance {
    @Override
    public String hello() {
        return this.getClass().getSimpleName();
    }
}
  • HelloInstance的另外兩個實現類HelloInstanceB、HelloInstanceC,代碼與HelloInstanceA一樣,就不貼出來了
  • 接下來的單元測試類演示瞭如何使用Instance接受註入,以及業務代碼如何使用指定的實現類bean,可見select(Class).get()是關鍵,select方法指定了實現類,然後get取出該實例
package com.bolingcavalry;

import com.bolingcavalry.service.HelloInstance;
import com.bolingcavalry.service.impl.HelloInstanceA;
import com.bolingcavalry.service.impl.HelloInstanceB;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

@QuarkusTest
public class InstanceTest {

    @Inject
    Instance<HelloInstance> instance;

    @Test
    public void testSelectHelloInstanceA() {
        Class<HelloInstanceA> clazz = HelloInstanceA.class;

        Assertions.assertEquals(clazz.getSimpleName(),
                instance.select(clazz).get().hello());
    }

    @Test
    public void testSelectHelloInstanceB() {
        Class<HelloInstanceB> clazz = HelloInstanceB.class;

        Assertions.assertEquals(clazz.getSimpleName(),
                instance.select(clazz).get().hello());
    }
}
  • 執行單元測試,順利通過,符合預期
    在這裡插入圖片描述

  • 至此,連續兩篇關於註入bean的方式全部驗證完畢,如此豐富的手段,相信可以滿足您日常開發的需要

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

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


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

-Advertisement-
Play Games
更多相關文章
  • ## 編譯器 編譯器的作用就是將高級編程語言翻譯為機器代碼。 編譯器工作過程一般分為: - 詞法分析:將高級語言解析成 Token 集合; - 語法分析:將 Token 集合構建成語法樹,在這個過程可以判斷出語法是否有誤,比如 `while` 後面是否 `{` 等等; - 語義分析:判斷語法樹是否有 ...
  • 為了保障整體的穩定性,在改動成本比較小的情況下,達到快速實現,穩定運行,預防這種偶發異常,我們實現了一種輕量級定時任務來進行無縫隙降級 ...
  • 大家好,我是三友~~ 今天來跟大家聊一聊Spring的9大核心基礎功能。 其實最近有小伙伴私信問我怎麼不寫文章了,催更來了 其實我不是不寫,而是一直在寫這篇文章,只不過令我沒想到的是,從前期的選題、準備、翻源碼、動手到寫完,前後跨度接近一個月的時間,花了好幾個周末,寫了三萬字,最終才算完成。 所以如 ...
  • DripTable 是京東零售推出的一款用於企業級中後臺的動態列表解決方案,項目基於 React 和 JSON Schema,旨在通過簡單配置快速生成頁面動態列表來降低列表開發難度、提高工作效率。 DripTable 目前包含以下子項目:drip-table、drip-table-generator ...
  • ## 教程簡介 D3是Data-Driven Documents的縮寫,D3.js是一個基於數據管理文檔的資源JavaScript庫。 D3 是最有效的數據可視化框架之一。它允許開發人員在 HTML、CSS 和 SVG 的幫助下在瀏覽器中創建動態的互動式數據可視化。數據可視化是將過濾後的數據以圖片和 ...
  • 數據類型 1、能直接處理的基本數據類型有5個:整型、浮點型、字元串、布爾值、空 1)整型(int)=整數,例如0至9,-1至-9,100,-8180等,人數、年齡、頁碼、門牌號等 沒有小數位的數字,是整型 2)浮點型(float)=小數,例如金額、身高、體重、距離、長度、π等 精確到小數位的數字,是 ...
  • # Python 3.12 搶先看——關於 f-string 的改動 哈嘍大家好,我是鹹魚 相信小伙伴們對 python 中的 f-string 都不陌生 f-string 是格式化字元串的縮寫,是以小寫或大寫字母 F 為首碼的字元串文本 f-string 提供簡潔明瞭的語法,**允許對變數和表達式 ...
  • > 依賴註入就是對類的屬性進行賦值 ## 4.1、環境搭建 > 創建名為spring_ioc_xml的新module,過程參考[3.1節](https://www.cnblogs.com/Javaer1995/p/17570068.html "3.1節") ### 4.1.1、創建spring配置文 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...