quarkus依賴註入之一:創建bean

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/07/30/17589391.html
-Advertisement-
Play Games

《quarkus依賴註入》系列聚焦quarkus框架下bean的創建、使用、配置等場景的知識點,本文是系列的開篇,介紹CDI,實戰創建bean ...


歡迎訪問我的GitHub

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

關於依賴註入

  • 對一名java程式員來說,依賴註入應該是個熟悉的概念,簡單的說就是:我要用XXX,但我不負責XXX的生產
  • 以下代碼來自spring官方,serve方法要使用MyComponent類的doWork方法,但是不負責MyComponent對象的實例化,只要用註解Autowired修飾成員變數myComponent,spring環境會負責為myComponent賦值一個實例
@Service
public class MyService {
    
    @Autowired
    MyComponent myComponent;
    
    public String serve() {
        myComponent.doWork();
        return "success";
    }
}
  • 關於依賴註入,網上有很多優秀文章,這裡就不展開了,咱們要關註的是quarkus框架的依賴註入

關於《quarkus依賴註入》系列

  • 《quarkus依賴註入》共六篇文章,整體規划上隸屬於《quarkus實戰》系列,但專註於依賴註入的知識點和實戰

  • 如果您熟悉spring的依賴註入,那麼閱讀本系列時會發現quarkus與spring之間有太多相似之處,很多地方一看就懂

本篇概覽

  • 作為《quarkus依賴註入》的開篇,本文先介紹CDI,再學習如何創建bean實例,全文內容如下
graph LR L1(本篇內容) --> L2-1(官方提醒) L1 --> L2-2(CDI) L1 --> L2-3(創建bean) L2-2 --> L3-1(關於CDI) L2-2 --> L3-2(關於bean) L2-3 --> L3-3(註解修飾在類上) L2-3 --> L3-4(註解修飾在方法上) L2-3 --> L3-5(註解修飾在成員變數上) L2-3 --> L3-6(擴展組件中的synthetic bean)
  • 學習quarkus的依賴註入之前,來自官方的提醒非常重要

官方提醒

  • 在使用依賴註入的時候,quankus官方建議不要使用私有變數(用預設可見性,即相同package內可見),因為GraalVM將應用製作成二進位可執行文件時,編譯器名為Substrate VM,操作私有變數需要用到反射,而GraalVM使用反射的限制,導致靜態編譯的文件體積增大
Quarkus is designed with Substrate VM in mind. For this reason, we encourage you to use *package-private* scope instead of *private*.

關於CDI

  • Contexts and Dependency Injection for Java 2.0》,簡稱CDI,該規範是對JSR-346的更新,quarkus對依賴註入的支持就是基於此規範實現的
  • 從 2.0 版開始,CDI 面向 Java SE 和 Jakarta EE 平臺,Java SE 中的 CDI 和 Jakarta EE 容器中的 CDI 共用core CDI 中定義的特性。
  • 簡單看下CDI規範的內容(請原諒欣宸的英語水平):
  1. 該規範定義了一組強大的補充服務,有助於改進應用程式代碼的結構
  2. 給有狀態對象定義了生命周期,這些對象會綁定到上下文,上下文是可擴展的
  3. 複雜的、安全的依賴註入機制,還有開發和部署階段選擇依賴的能力
  4. 與Expression Language (EL)集成
  5. 裝飾註入對象的能力(個人想到了AOP,你拿到的對象其實是個代理)
  6. 攔截器與對象關聯的能力
  7. 事件通知模型
  8. web會話上下文
  9. 一個SPI:允許攜帶型擴展容器的集成(integrate cleanly )

關於CDI的bean

  • CDI的實現(如quarkus),允許對象做這些事情:
  1. 綁定到生命周期上下文

  2. 註入

  3. 與攔截器和裝飾器關聯

  4. 通過觸發和觀察事件,以鬆散耦合的方式交互

  • 上述場景的對象統稱為bean,上下文中的 bean 實例稱為上下文實例,上下文實例可以通過依賴註入服務註入到其他對象中

  • 關於CDI的背景知識就介紹到這裡吧,接下來要寫代碼了

源碼下載

名稱 鏈接 備註
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議
  • 這個git項目中有多個文件夾,本次實戰的源碼在quarkus-tutorials文件夾下,如下圖紅框
    image-20220312091203116
  • quarkus-tutorials是個父工程,裡面有多個module,本篇實戰的module是basic-di,如下圖紅框
    image-20220312091404031

創建demo工程

package com.bolingcavalry;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/actions")
public class HobbyResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {

        return "Hello RESTEasy, " + LocalDateTime.now();
    }
}
  • 接下來,從最基礎的創建bean實例創建開始

創建bean實例:註解修飾在類上

  • 先來看看spring是如何創建bean實例的,回顧文章剛開始的那段代碼,myComponent對象來自哪裡?
  • 繼續看spring官方的demo,如下所示,用Component註解修飾在類上,spring就會實例化MyComponent對象並註冊在bean容器中,需要用此bean的時候用Autowired註解就可以註入了
@Component
public class MyComponent {
    public void doWork() {}
}
  • quarkus框架下也有類似方式,演示類ClassAnnotationBean.java如下,用註解ApplicationScoped去修飾ClassAnnotationBean.類,如此quarkus就會實例化此類並放入容器中
package com.bolingcavalry.service.impl;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ClassAnnotationBean {

    public String hello() {
        return "from " + this.getClass().getSimpleName();
    }
}
  • 這種註解修飾在類上的bean,被quarkus官方成為class-based beans
  • 使用bean也很簡單,如下,用註解Inject修飾ClassAnnotationBean類型的成員變數即可
package com.bolingcavalry;

import com.bolingcavalry.service.impl.ClassAnnotationBean;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/classannotataionbean")
public class ClassAnnotationController {

    @Inject
    ClassAnnotationBean classAnnotationBean;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                classAnnotationBean.hello());
    }
}
  • 如何驗證上述代碼是否有效?運行服務,再用瀏覽器訪問classannotataionbean介面,肉眼判斷返回內容是否符合要求,這樣雖然可行,但總覺得會被嘲諷低效...
  • 還是寫一段單元測試代碼吧,如下所示,註意要用QuarkusTest註解修飾測試類(不然服務啟動有問題),測試方法中檢查了返回碼和body,如果前面的依賴註入沒問題,則下麵的測試應該能通過才對
package com.bolingcavalry;

import com.bolingcavalry.service.impl.ClassAnnotationBean;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
class ClassAnnotationControllerTest {

    @Test
    public void testGetEndpoint() {
        given()
                .when().get("/classannotataionbean")
                .then()
                .statusCode(200)
                // 檢查body內容,是否含有ClassAnnotationBean.hello方法返回的字元串
                .body(containsString("from " + ClassAnnotationBean.class.getSimpleName()));
    }
}
  • 執行命令mvn clean test -U開始測試,控制台輸出如下,提示測試通過
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.702 s
[INFO] Finished at: 2022-03-12T15:48:45+08:00
[INFO] ------------------------------------------------------------------------
  • 如果您的開發工具是IDEA,也可以用它的圖形化工具執行測試,如下圖,能得到更豐富的測試信息

image-20220312155757867

  • 掌握了最基礎的實例化方式,接著看下一種方式:修飾在方法上

創建bean實例:註解修飾在方法上

  • 下一種創建bean的方式,我們還是先看spring是怎麼做的,有了它作對比,對quarkus的做法就好理解了
  • 來看spring官方文檔上的一段代碼,如下所示,用Bean註解修飾myBean方法,spring框架就會執行此方法,將返回值作為bean註冊到容器中,spring把這種bean的處理過程稱為lite mode
@Component
 public class Calculator {
     public int sum(int a, int b) {
         return a+b;
     }

     @Bean
     public MyBean myBean() {
         return new MyBean();
     }
 }
  • kuarkus框架下,也能用註解修飾方法來創建bean,為了演示,先定義個普通介面
package com.bolingcavalry.service;

public interface HelloService {
    String hello();
}
  • 以及HelloService介面的實現類
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloService;

public class HelloServiceImpl implements HelloService {
    @Override
    public String hello() {
        return "from " + this.getClass().getSimpleName();
    }
}
  • 註意,HelloService.java和HelloServiceImpl.java都是普通的java介面和類,與quarkus沒有任何關係
  • 下麵的代碼演示了用註解修飾方法,使得quarkus調用此方法,將返回值作為bean實例註冊到容器中,Produces通知quarkus做實例化,ApplicationScoped表明瞭bean的作用域是整個應用
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloService;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

public class MethodAnnonationBean {

    @Produces
    @ApplicationScoped
    public HelloService getHelloService() {
        return new HelloServiceImpl();
    }
}
  • 這種用於創建bean的方法,被quarkus稱為producer method
  • 看過上述代碼,相信聰明的您應該明白了用這種方式創建bean的優點:在創建HelloService介面的實例時,可以控制所有細節(構造方法的參數、或者從多個HelloService實現類中選擇一個),沒錯,在SpringBoot的Configuration類中咱們也是這樣做的
  • 前面的getHelloService方法的返回值,可以直接在業務代碼中依賴註入,如下所示
package com.bolingcavalry;

import com.bolingcavalry.service.HelloService;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.time.LocalDateTime;

@Path("/methodannotataionbean")
public class MethodAnnotationController {

    @Inject
    HelloService helloService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                helloService.hello());
    }
}
  • 單元測試代碼如下
package com.bolingcavalry;

import com.bolingcavalry.service.impl.HelloServiceImpl;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
class MethodAnnotationControllerTest {

    @Test
    public void testGetEndpoint() {
        given()
                .when().get("/methodannotataionbean")
                .then()
                .statusCode(200)
                // 檢查body內容,HelloServiceImpl.hello方法返回的字元串
                .body(containsString("from " + HelloServiceImpl.class.getSimpleName()));
    }
}
  • 測試通過

image-20220312173152223

  • producer method有個特性需要重點關註:如果剛纔生產bean的getHelloService方法有個入參,如下所示,入參是OtherService對象,那麼,這個OtherService對象也必須是個bean實例(這就像你用@Inject註入一個bean的時候,這個bean必須存在一樣),如果OtherService不是個bean,那麼應用初始化的時候會報錯,(其實這個特性SpringBoot中也有,相信經驗豐富的您在使用Configuration類的時候應該用到過)
public class MethodAnnonationBean {

    @Produces
    @ApplicationScoped
    public HelloService getHelloService(OtherService otherService) {
        return new HelloServiceImpl();
    }
}
  • quarkus還做了個簡化:如果有了ApplicationScoped這樣的作用域註解,那麼Produces可以省略掉,寫成下麵這樣也是正常運行的
public class MethodAnnonationBean {

    @ApplicationScoped
    public HelloService getHelloService() {
        return new HelloServiceImpl();
    }
}

創建bean實例:註解修飾在成員變數上

  • 再來看看最後一種方式,註解在成員變數上,這個成員變數就成了bean
  • 先寫個普通類用於稍後測試
package com.bolingcavalry.service.impl;

import com.bolingcavalry.service.HelloService;

public class OtherServiceImpl {

    public String hello() {
        return "from " + this.getClass().getSimpleName();
    }
}

  • 通過成員變數創建bean的方式如下所示,給otherServiceImpl增加兩個註解,Produces通知quarkus做實例化,ApplicationScoped表明瞭bean的作用域是整個應用,最終OtherServiceImpl實例會被創建後註冊到bean容器中
package com.bolingcavalry.service.impl;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

public class FieldAnnonationBean {

    @Produces
    @ApplicationScoped
    OtherServiceImpl otherServiceImpl = new OtherServiceImpl();
}
  • 這種用於創建bean的成員變數(如上面的otherServiceImpl),被quarkus稱為producer field

  • 上述bean的使用方法如下,可見與前面的使用並無區別,都是從quarkus的依賴註入

@Path("/fieldannotataionbean")
public class FieldAnnotationController {

    @Inject
    OtherServiceImpl otherServiceImpl;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        return String.format("Hello RESTEasy, %s, %s",
                LocalDateTime.now(),
                otherServiceImpl.hello());
    }
}
  • 測試代碼與前面類似就不贅述了,請您自行完成編寫和測試

關於synthetic bean

  • 還有一種bean,quarkus官方稱之為synthetic bean(合成bean),這種bean只會在擴展組件中用到,而咱們日常的應用開發不會涉及,synthetic bean的特點是其屬性值並不來自它的類、方法、成員變數的處理,而是由擴展組件指定的,在註冊syntheitc bean到quarkus容器時,常用SyntheticBeanBuildItem類去做相關操作,來看一段實例化synthetic bean的代碼
@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
                .runtimeValue(recorder.createFoo("parameters are recorder in the bytecode")) 
                .done();
}
  • 至此,《quarkus依賴註入》的開篇已經完成,創建bean之後還有更精彩的內容為您奉上,敬請期待

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

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


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

-Advertisement-
Play Games
更多相關文章
  • ## 泛型的引入 看下麵這段代碼: ```java private static int add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static float ...
  • # lab util ## sleep 1. 介紹:主要用來熟悉下環境以及代碼結構。 > - See `kernel/sysproc.c` for the xv6 kernel code that implements the `sleep` system call (look for `sys_s ...
  • 看到一篇講hashmap的文章,講的很不錯,但是有一點我覺得作者沒有講清楚,這裡我說一下自己的理解。 原文,先看原文: https://blog.csdn.net/woshimaxiao1/article/details/83661464 前文概述,該博客的主要內容如下: 1. 什麼是哈希表(主幹為 ...
  • Spring Boot是基於Spring Framework構建應用程式的框架,Spring Framework是一個廣泛使用的用於構建基於Java的企業應用程式的開源框架。Spring Boot旨在使創建獨立的、生產級別的Spring應用程式變得容易,您可以"只是運行"這些應用程式。 ...
  • 功能說明: 將試卷導出word,並可以列印,裝訂,效果圖: ![](https://img2023.cnblogs.com/blog/2699913/202307/2699913-20230718094848710-642780924.png) 下麵是實現代碼: ``` package com.xx ...
  • # Java 中 == 與 equals() 的區別 # 1. == ## == 是一個比較運算符,在使用時有可以判斷兩種情況 > ## 在用於基本類型時,即判斷兩邊數據的值是否相等。 > > ## 在用於引用類型時,即判斷兩邊是否為同一個對象即有相同的地址。 # 2. equals() 方法 ## ...
  • 原文在[這裡](https://go.dev/security/vuln/) ## 概述 Go幫助開發人員檢測、評估和解決可能被攻擊者利用的錯誤或弱點。在幕後,Go團隊運行一個管道來整理關於漏洞的報告,這些報告存儲在Go漏洞資料庫中。各種庫和工具可以讀取和分析這些報告,以瞭解特定用戶項目可能受到的影 ...
  • ## 教程簡介 Django是一個開放源代碼的Web應用框架,由Python寫成。採用了MTV的框架模式,即模型M,視圖V和模版T。它最初是被開發來用於管理勞倫斯出版集團旗下的一些以新聞內容為主的網站的,即是CMS(內容管理系統)軟體。Django是高水準的Python編程語言驅動的一個開源模型.視 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...