quarkus依賴註入之七:生命周期回調

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

### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇的知識點是bean的生命周期回調:在 ...


歡迎訪問我的GitHub

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

本篇概覽

  • 本篇的知識點是bean的生命周期回調:在bean生命周期的不同階段,都可以觸發自定義代碼的執行
  • 觸發自定義代碼執行的具體方式,是用對應的註解去修飾要執行的方法,如下圖所示:
流程圖 - 2022-04-05T094019.781
  • 有兩種模式可以實現生命周期回調:攔截器模式和自定義模式,接下來通過編碼依次學習

攔截器模式

流程圖 (19)
  • 如果要自定義bean的生命周期回調,也是遵照上述步驟執行,接下來編碼實現
  • 首先定義攔截器,名為TrackLifeCycle,就是個普通攔截器,需要用註解InterceptorBinding修飾
package com.bolingcavalry.interceptor.define;

import javax.interceptor.InterceptorBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;

@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackLifeCycle {
}
  • 然後是實現攔截器的功能,有幾處要註意的地方稍後會提到
package com.bolingcavalry.interceptor.impl;

import com.bolingcavalry.interceptor.define.TrackLifeCycle;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.interceptor.AroundConstruct;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@TrackLifeCycle
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 1)
public class LifeCycleInterceptor {

    @AroundConstruct
    void execute(InvocationContext context) throws Exception {
        Log.info("start AroundConstruct");
        try {
            context.proceed();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.info("end AroundConstruct");
    }

    @PostConstruct
    public void doPostConstruct(InvocationContext ctx) {
        Log.info("life cycle PostConstruct");
    }

    @PreDestroy
    public void doPreDestroy(InvocationContext ctx) {
        Log.info("life cycle PreDestroy");
    }
}
  • 上述代碼有以下幾點需要註意
  1. 用註解InterceptorTrackLifeCycle修飾,說明這是攔截器TrackLifeCycle的實現
  2. 被攔截bean實例化的時候,AroundConstruct修飾的方法execute就會被執行,這和《攔截器》一文中的AroundInvoke的用法很相似
  3. 被攔截bean創建成功後,PostConstruct修飾的方法doPostConstruct就會被執行
  4. 被攔截bean在銷毀之前,PreDestroy修飾的方法doPreDestroy就會被執行
  • 接下來是使用攔截器TrackLifeCycle了,用於演示的bean如下,用TrackLifeCycle修飾,有構造方法和簡單的helloWorld方法
@ApplicationScoped
@TrackLifeCycle
public class Hello {

    public Hello() {
        Log.info(this.getClass().getSimpleName() + " at instance");
    }

    public void helloWorld() {
        Log.info("Hello world!");
    }
}
  • 最後再寫個單元測試類驗證
@QuarkusTest
public class LifeCycleTest {

    @Inject
    Hello hello;

    @Test
    public void testLifyCycle() {
        hello.helloWorld();
    }
}
  • 執行單元測試,控制台輸出如下,可見攔截器的日誌輸出都符合預期
15:26:32,447 INFO  [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 2.899s. Listening on: http://localhost:8081
15:26:32,448 INFO  [io.quarkus] (main) Profile test activated. 
15:26:32,448 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
15:26:32,483 INFO  [com.bol.lif.Hello] (main) Hello_ClientProxy at instance
15:26:33,040 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) start AroundConstruct
15:26:33,040 INFO  [com.bol.lif.Hello] (main) Hello_Subclass at instance
15:26:33,040 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) end AroundConstruct
15:26:33,041 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PostConstruct
15:26:33,041 INFO  [com.bol.lif.Hello] (main) Hello world!
15:26:33,097 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PreDestroy
15:26:33,128 INFO  [io.quarkus] (main) Quarkus stopped in 0.075s
  • 以上就是通過攔截器製作的bean生命周期回調的全過程,接下來再看另一種方式:不用攔截器的方式

自定義模式

  • 剛纔的攔截器模式有個明顯問題:如果不同bean的生命周期回調有不同業務需求,該如何是好?為每個bean做一個攔截器嗎?隨著bean的增加會有大量攔截器,似乎不是個好的方案
  • 如果您熟悉spring,對下麵的代碼要改不陌生,這是來自spring官網的內容,直接在bean的方法上用PostConstruct和PreDestroy修飾,即可在bean的創建完成和銷毀前被調用
public class CachingMovieLister {

  @PostConstruct
  public void populateMovieCache() {
      // populates the movie cache upon initialization...
  }

  @PreDestroy
  public void clearMovieCache() {
      // clears the movie cache upon destruction...
  }
}
  • 實際上,quarkus也支持上述方式,不過和攔截器相比有兩個差異:
  1. 在bean的內部,只能用PostConstruct和TrackLifeCycle,不能用AroundConstruct,只有攔截器才能用AroundConstruct
  2. 在攔截器中,PostConstruct和TrackLifeCycle修飾的方法必須要有InvocationContext類型的入參,但是在bean內部則沒有此要求
  • 咱們來改造Hello.java的源碼,修改後如下,增加了兩個方法,分別被PostConstruct和PreDestroy修飾
@ApplicationScoped
@TrackLifeCycle
public class Hello {

    public Hello() {
        Log.info(this.getClass().getSimpleName() + " at instance");
    }

    @PostConstruct
    public void doPostConstruct() {
        Log.info("at doPostConstruct");
    }

    @PreDestroy
    public void doPreDestroy() {
        Log.info("at PreDestroy");
    }

    public void helloWorld() {
        Log.info("Hello world!");
    }
}
  • 再次運行單元測試,控制台輸出如下,可見Hello自定義的兩個生命周期回調都執行了,同時原攔截器的三個回調也都正常執行
16:27:54,134 INFO  [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 2.529s. Listening on: http://localhost:8081
16:27:54,135 INFO  [io.quarkus] (main) Profile test activated. 
16:27:54,135 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
16:27:54,147 INFO  [com.bol.lif.Hello] (main) Hello_ClientProxy at instance
16:27:54,710 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) start AroundConstruct
16:27:54,711 INFO  [com.bol.lif.Hello] (main) Hello_Subclass at instance
16:27:54,711 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) end AroundConstruct
16:27:54,711 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PostConstruct
16:27:54,712 INFO  [com.bol.lif.Hello] (main) at doPostConstruct
16:27:54,712 INFO  [com.bol.lif.Hello] (main) Hello world!
16:27:54,747 INFO  [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PreDestroy
16:27:54,747 INFO  [com.bol.lif.Hello] (main) at PreDestroy
16:27:54,765 INFO  [io.quarkus] (main) Quarkus stopped in 0.044s

dispose註解:實現銷毀前自定義操作,dispose是另一種可選方案

  • 試想這樣的場景:我的bean在銷毀前要做自定義操作,但是如果用之前的兩種方案,可能面臨以下問題:
  1. 不適合修改bean的代碼,bean的類可能是第三方庫
  2. 也不適合修改生命周期攔截器代碼,攔截器可能也是第三方庫,也可能是多個bean共用,若修改會影響其他bean
  • 好在quarkus為我們提供了另一個方案,不用修改bean和攔截器的代碼,用註解dispose修飾指定方法即可,接下來編碼驗證
  • 增加一個普通類ResourceManager.java,假設這是業務中的資源管理服務,可以打開和關閉業務資源,稍後會在配置類中將其指定為bean
package com.bolingcavalry.service.impl;

import io.quarkus.logging.Log;

/**
 * @author [email protected]
 * @Title: 資源管理類
 * @Package
 * @Description:
 * @date 4/10/22 10:20 AM
 */
public class ResourceManager {

    public ResourceManager () {
        Log.info("create instance, " + this.getClass().getSimpleName());
    }

    /**
     * 假設再次方法中打開資源,如網路、文件、資料庫等
     */
    public void open() {
        Log.info("open resource here");
    }

    /**
     * 假設在此方法中關閉所有已打開的資源
     */
    public void closeAll() {
        Log.info("close all resource here");
    }
}
  • 配置類SelectBeanConfiguration.java,指定了ResourceManager的生命周期是每次http請求
package com.bolingcavalry.config;

import com.bolingcavalry.service.impl.ResourceManager;
import javax.enterprise.context.RequestScoped;

public class SelectBeanConfiguration {

    @RequestScoped
    public ResourceManager getResourceManager() {
        return new ResourceManager();
    }  
}
  • 再寫一個web服務類ResourceManagerController.java,這裡面使用了ResourceManager
package com.bolingcavalry;

import com.bolingcavalry.service.impl.ResourceManager;

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;

@Path("/resourcemanager")
public class ResourceManagerController {

    @Inject
    ResourceManager resourceManager;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String get() {
        resourceManager.open();
        return "success";
    }
}
  • 由於ResourceManager的生命周期是RequestScoped,因此每次請求/resourcemanager都會實例化一個ResourceManager,請求結束後再將其銷毀
  • 現在,業務需求是每個ResourceManager的bean在銷毀前,都要求其closeAll方法被執行
  • 重點來了,在SelectBeanConfiguration.java中新增一個方法,入參是bean,而且要用Disposes註解修飾,如此,ResourceManager類型的bean在銷毀前此方法都會被執行
/**
 * 使用了Disposes註解後,ResourceManager類型的bean在銷毀前,此方法都會執行
 * @param resourceManager
 */
public void closeResource(@Disposes ResourceManager resourceManager) {
    // 在這裡可以做一些額外的操作,不需要bean參與
    Log.info("do other things that bean do not care");

    // 也可以執行bean的方法
    resourceManager.closeAll();
}
  • 最後是單元測試類DisposeTest.java,這裡用了註解RepeatedTest表示重覆執行,屬性值為3,表示重覆執行3次
@QuarkusTest
public class DisposeTest {

    @RepeatedTest(3)
    public void test() {
        given()
                .when().get("/resourcemanager")
                .then()
                .statusCode(200)
                // 檢查body內容
                .body(is("success"));
    }
}
  • 執行單元測試,控制台輸出如下圖,可見每次請求都有bean創建,也伴隨著bean銷毀,每次銷毀都會執行closeResource方法,符合預期

image-20220410173202641

  • 至此,生命周期回調相關的實戰就完成了,希望能給您一些參考,接下來的文章會繼續深入學習依賴註入相關的知識點

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

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


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

-Advertisement-
Play Games
更多相關文章
  • # DOM事件 1.DOM中的事件可以分為兩類 - 1.瀏覽器行為 如:文檔載入完成,圖片載入完成 - 2.用戶行為 如:輸入框輸入數據,點擊按鈕 (2).常見的DOM事件 ```bash onload 瀏覽器已完成頁面的載入 支持事件的對象 window image onchange HTML 元 ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230804111644939-2134490730.png) # 1. 控制層囊括所有在後臺運行的成功處理生產負載的軟體和服務 ## 1.1. 處理用戶生產數據的那些軟體,就 ...
  • ## 目錄 - 封裝變化 - 針對介面編程,不針對實現編程 - 多用組合(has-a),少用繼承(is-a) - 為交互對象之間的松耦合設計而努力 - 最少知識原則 LKP / 迪米特法則 Law of Demeter - 好萊塢原則 - SOLID 原則 - 單一職責原則 SRP - 開放關閉原則 ...
  • > 本質上:所有一切的操作都是Java代碼來完成的,XML和註解只是告訴框架中的Java代碼如何執行。 ## 7.1、環境搭建 > 創建名為spring_ioc_annotation的新module,過程參考[3.1節](https://www.cnblogs.com/Javaer1995/p/17 ...
  • 8.03周四 一大早電話吵醒,著急給我媽送卡,早上坐車去延安,順便下來玩玩,和以前的的高中同學打了兩個小時的撞球,又吃了自助,晚上還看了電影,在延安我哥哥家快2點才回去。 8.04周五 昨天睡覺的遲,已經中午快12點才起床,起來吃點,就在家裡玩電腦,因為一直在下雨,晚上雨小了,才出去吃了紙包魚,晚上 ...
  • 如何在Go中使用Makefile 1.Makefile是什麼 Makefile是一種構建工具,用於在項目中定義和執行一系列命令。它通常包含了一些規則和目標,用於編譯、測試、運行和清理項目。 2.Makefile可以用於哪些語言的構建過程 Makefile最初是為了 C程式的構建而設計的,但由於其簡潔 ...
  • 目錄 一、面向對象編程快速入門 二、深刻認識面向對象 三、對象在電腦中的執行原理 四、類和對象的一些註意事項 五、其他語法:this 六、其他語法:構造器 七、其他語法:封裝 八、其他語法:實體JavaBean 九、面向對象編程綜合案例 十、補充知識:成員變數、局部變數的區別小結 前言 Stude ...
  • 昨天看到個視頻,彈幕挺有意思的,於是想著用Python給他全部扒下來。 代碼非常簡單,接下來我們看看 具體操作。 需要準備這些 軟體 Python 3.8 Pycharm 模塊使用 import requests 數據請求 import jieba 分詞 import wordcloud 詞雲 im ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...