🔥 FREE PRO OFFER OnlyLink.click Pro Version is 100% Free of Cost till 31 December, 2026! Claim Free Pro

CDI を使用した Quarkus での依存性インジェクション: ArC、スコープ、修飾子、ビルドタイム インジェクション

CDI を使用した Quarkus での依存性インジェクション: ArC、スコープ、修飾子、ビルドタイム インジェクション

依存関係の注入 (DI) は、最新のエンタープライズ Java アプリケーション開発の基盤です。長年にわたり、Java 開発者は Spring IoC (制御の反転) または Hibernate Weld や OpenWebBeans などの標準の Jakarta EE CDI (コンテキストと依存関係の挿入) コンテナーに依存して、コンポーネントのライフサイクルを分離し、サービスの依存関係を管理し、構成プロパティを挿入してきました。

しかし、エンタープライズ アプリケーションがクラウドネイティブ マイクロサービス、Docker と Kubernetes によるコンテナ化、およびサーバーレス実行モデル (AWS Lambda、Knative) に移行するにつれて、従来の依存関係注入コンテナーには、重いランタイム リフレクションと高いメモリ オーバーヘッドという重大な欠陥が露呈しました。

Quarkus とその軽量 CDI エンジン ArC を登場させます。 Kubernetes と GraalVM のネイティブ コンパイル用にゼロから構築された Quarkus は、リフレクション、クラスパス スキャン、Bean グラフのワイヤリングをアプリケーションの実行時からビルド時に移行することで、依存関係の注入を再定義します。

この詳細ガイドでは、CDI を使用した Quarkus での依存性注入について詳しく説明し、ArC コンテナの内部機構、CDI スコープ、カスタム修飾子、プロデューサー、インターセプター、イベント駆動型オブザーバー、プログラムによるルックアップ、および高パフォーマンスのクラウドネイティブ Java アプリケーションを作成するためのベスト プラクティスについて説明します。


1. ArC を理解する: Quarkus ビルドタイム CDI エンジン

標準の Jakarta EE CDI コンテナは、アプリケーションの起動時に完全に動作します。レガシー アプリケーションが起動すると、CDI コンテナはクラスパス内のすべての JAR をスキャンし、メタデータを解析し、Java Reflection (java.lang.reflect) を使用して注釈を検査し、メモリ内に動的プロキシを生成し、依存関係グラフを構築します。この動的な反映により、大量の RAM (メタスペース) が消費され、コールド スタート ブート時間が増加します。

Quarkus は、ArC (Jakarta CDI 4.0 仕様に基づくビルドタイム CDI 実装) を使用してこのボトルネックを解決します。

Reflection を使用した従来のランタイム CDI と Quarkus ArC ビルドタイム配線

ArC と標準 CDI の違い:

  1. ビルド時の拡張: mvn package または ./gradlew build 中に、ArC はすべてのアプリケーション Bean をスキャンし、注入ポイントを検証し、依存関係グラフを事前構築します。
  2. Ahead-Of-Time (AOT) バイトコード生成: ArC は、インスタンス化、Bean プロキシ、インターセプター用の直接 Java バイトコードを前もって生成し、実行時のリフレクションを排除します。
  3. 未使用の Bean プルーニング (デッド コードの除去): 動的に挿入または参照されない CDI Bean はコンパイル中に識別され、最終バイナリから自動的に削除され、メモリ フットプリントが削減されます。
  4. GraalVM ネイティブ イメージの最適化: Bean ワイヤリングはビルド時に解決されるため、Quarkus アプリケーションはリフレクション構成ヒントなしでネイティブ実行可能ファイルにシームレスにコンパイルされます。

2. CDI Bean のスコープとライフサイクル管理

CDI では、スコープ によって Bean インスタンスのライフサイクルと可視性が決まります。 Quarkus ArC は、ビルド時の最適化を備えた標準の Jakarta CDI スコープをサポートします。

Quarkus CDI Bean のスコープとライフサイクルの図

Quarkus のプライマリ CDI スコープ:

スコープの注釈 ライフサイクルと動作 プロキシの作成 ベストユースケース
@ApplicationScoped アプリケーションごとに 1 つのインスタンス。最初の呼び出し時の遅延初期化。すべてのスレッド間で共有される状態。 生成されたクライアント プロキシ ステートレス サービス、リポジトリ、構成。
@RequestScoped 単一の HTTP リクエストまたはイベント呼び出しにバインドされたインスタンス。リクエストが完了すると破棄されます。 生成されたクライアント プロキシ ユーザーセッション、リクエストコンテキストデータ、セキュリティトークン。
@依存 何も指定されていない場合のデフォルトのスコープ。インスタンスは、注入元の Bean の存続期間に直接関連付けられます。 プロキシなし (直接インスタンス) ヘルパー ユーティリティ クラス、一時的な状態ホルダー。
@シングルトン アプリケーションごとに 1 つのインスタンス。積極的または遅延的にインスタンス化されます。クライアント プロキシ ラッパーはありません。 プロキシなし (直接インスタンス) クライアント プロキシのオーバーヘッドを回避する必要がある高パフォーマンスのシングルトン。
@取り外し不可 ビルド時の最適化中に Bean を削除しないよう ArC に指示するマーカー アノテーション。 スコープに依存 動的検索ターゲットまたはリフレクション参照 Bean。

コード例: スコープ付き Bean の宣言

package com.ghaznix.services;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import java.util.UUID;

@ApplicationScoped
public class OrderProcessingService {
    
    public String processOrder(String orderId) {
        return "Order " + orderId + " processed successfully.";
    }
}

@RequestScoped
public class RequestContextHolder {
    private final String requestId = UUID.randomUUID().toString();

    public String getRequestId() {
        return requestId;
    }
}

3. Quarkus での依存性注入パターン

Quarkus は、標準の jakarta.inject.Inject アノテーションを使用して複数の注入スタイルをサポートします。

A. コンストラクターのインジェクション (推奨されるベスト プラクティス)

コンストラクター インジェクションは不変性 (final フィールド) を保証し、CDI コンテナーを必要とせずに単体テストを簡素化し、依存関係を明示します。

package com.ghaznix.resources;

import com.ghaznix.services.OrderProcessingService;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;

@Path("/orders")
public class OrderResource {

    private final OrderProcessingService orderService;

    // In Quarkus, if a class has a single constructor, @Inject is OPTIONAL!
    @Inject
    public OrderResource(OrderProcessingService orderService) {
        this.orderService = orderService;
    }

    @POST
    @Path("/{id}")
    public String checkout(@PathParam("id") String id) {
        return orderService.processOrder(id);
    }
}

プロのヒント: Quarkus ArC では、Bean に 1 つのコンストラクターしかない場合、@Inject アノテーションを完全に省略できます。 ArC はこれをインジェクション コンストラクターとして自動的に認識します。

B. フィールド注入

フィールド挿入は簡潔ですが、フィールドが変更可能になり、スタンドアロンの単体テストが複雑になります。

@ApplicationScoped
public class NotificationService {

    @Inject
    EmailClient emailClient; // Field injection
}

4. 修飾子と複数の実装の曖昧さの解消

複数の Bean が同じインターフェイスを実装している場合、CDI はビルド時にあいまいな依存関係例外をスローします。 Quarkus は 修飾子 を使用して、ターゲット Bean 実装を安全に解決します。

カスタム修飾子の定義

package com.ghaznix.payment;

import jakarta.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 PaymentType {
    Mode value();

    enum Mode {
        CREDIT_CARD,
        PAYPAL,
        CRYPTO
    }
}

修飾された Bean の実装

public interface PaymentProcessor {
    void process(double amount);
}

@ApplicationScoped
@PaymentType(PaymentType.Mode.CREDIT_CARD)
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void process(double amount) {
        System.out.println("Processing Credit Card payment: $" + amount);
    }
}

@ApplicationScoped
@PaymentType(PaymentType.Mode.PAYPAL)
public class PayPalProcessor implements PaymentProcessor {
    @Override
    public void process(double amount) {
        System.out.println("Processing PayPal payment: $" + amount);
    }
}

修飾された Bean の注入

@ApplicationScoped
public class CheckoutService {

    private final PaymentProcessor creditCardProcessor;
    private final PaymentProcessor payPalProcessor;

    @Inject
    public CheckoutService(
            @PaymentType(PaymentType.Mode.CREDIT_CARD) PaymentProcessor creditCardProcessor,
            @PaymentType(PaymentType.Mode.PAYPAL) PaymentProcessor payPalProcessor) {
        this.creditCardProcessor = creditCardProcessor;
        this.payPalProcessor = payPalProcessor;
    }
}

5. プロデューサーとディスポーザー: サードパーティのオブジェクトの挿入

場合によっては、@ApplicationScoped アノテーションが付けられていないサードパーティ ライブラリ (AWS SDK クライアント、Redis 接続、カスタム構成 Bean など) からクラスを注入する必要があることがあります。 Quarkus は、プロデューサー メソッド と ディスポーザー を使用してこれを処理します。

package com.ghaznix.config;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Disposes;
import jakarta.enterprise.inject.Produces;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

@ApplicationScoped
public class DatabaseConnectionProducer {

    @Produces
    @ApplicationScoped
    public Connection createDatabaseConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:postgresql://localhost:5432/mydb", "user", "pass");
    }

    // Disposer method called automatically when bean is destroyed
    public void closeConnection(@Disposes Connection connection) throws SQLException {
        if (connection != null && !connection.isClosed()) {
            connection.close();
        }
    }
}

6. CDI インターセプター: アスペクト指向プログラミング (AOP)

インターセプタを使用すると、ビジネス ロジックを汚染することなく、横断的な懸念事項 (ロギング、監査証跡、メトリクス、レート制限) を Bean メソッドに付加できます。

ステップ 1: インターセプター バインディングを定義する

package com.ghaznix.interceptors;

import jakarta.interceptor.InterceptorBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface LogExecutionTime {
}

ステップ 2: インターセプター ロジックの実装

package com.ghaznix.interceptors;

import jakarta.annotation.Priority;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;

@LogExecutionTime
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class LogExecutionTimeInterceptor {

    @AroundInvoke
    public Object logInvocation(InvocationContext context) throws Exception {
        long startTime = System.currentTimeMillis();
        try {
            return context.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            System.out.printf("[AUDIT] Method %s executed in %d ms%n",
                    context.getMethod().getName(), duration);
        }
    }
}

ステップ 3: ターゲット Bean にアノテーションを付ける

@ApplicationScoped
public class AnalyticsService {

    @LogExecutionTime
    public void computeAggregates() {
        // Business computation
    }
}

Quarkus はビルド時に静的インターセプター プロキシを生成するため、Spring Boot の動的 CGLIB ランタイム プロキシと比較して、メソッド呼び出しのオーバーヘッドは事実上ゼロです。


7. CDI イベントを使用したイベント駆動型アーキテクチャ

CDI は、分離されたインプロセス通信のための組み込みの同期および非同期イベント発行を提供します。

イベントの公開と監視

// 1. Define Event Payload
public record OrderCreatedEvent(String orderId, double totalAmount) {}

// 2. Publish Event
@ApplicationScoped
public class OrderService {

    @Inject
    Event<OrderCreatedEvent> orderEventPublisher;

    public void createOrder(String orderId, double amount) {
        // Create order logic...
        orderEventPublisher.fire(new OrderCreatedEvent(orderId, amount));
    }
}

// 3. Observe Event Synchronously or Asynchronously
@ApplicationScoped
public class InventoryObserver {

    public void onOrderCreated(@Observes OrderCreatedEvent event) {
        System.out.println("Updating inventory for order: " + event.orderId());
    }

    public void onOrderCreatedAsync(@ObservesAsync OrderCreatedEvent event) {
        System.out.println("Async audit log for order: " + event.orderId());
    }
}

アプリケーションのライフサイクル イベント

Quarkus は、アプリケーションの起動時または終了時に初期化コードを実行するための起動イベントとシャットダウン イベントを公開します。

@ApplicationScoped
public class ApplicationLifecycleBean {

    void onStart(@Observes StartupEvent ev) {
        System.out.println("Quarkus Application starting up...");
    }

    void onStop(@Observes ShutdownEvent ev) {
        System.out.println("Quarkus Application shutting down...");
    }
}

8. Instance<T> および Arc.container() を使用したプログラムによる Bean ルックアップ

ターゲット実装を実行時 (または従来の非 CDI インスタンス内) で動的に選択する必要がある場合は、Instance<T> または Quarkus Arc.container() 静的ルックアップを使用します。

@ApplicationScoped
public class DynamicPluginRegistry {

    @Inject
    Instance<PaymentProcessor> processors;

    public void executeAll(double amount) {
        for (PaymentProcessor processor : processors) {
            processor.process(amount);
        }
    }
}

9. アーキテクチャの比較: Quarkus ArC、Spring IoC、Weld

特徴 クォーカスArC Spring IoCコンテナ 標準ジャカルタ溶接
依存関係の解決 ビルド時間 (AOT) ランタイムリフレクション ランタイムリフレクション
メタスペース RAM オーバーヘッド 非常に低い (メタデータの場合は約 0MB) 高 (動的プロキシ クラス キャッシュ) 中程度から高程度
起動時間 ミリ秒 (< 0.05 秒) 秒 (2.5秒~8秒) 秒(1.5秒~4秒)
GraalVM ネイティブ サポート すぐに使えるネイティブ Spring ネイティブ ヒントが必要 複雑な構成
未使用豆の剪定 ビルド時の自動プルーニング クラスパスに保持される クラスパスに保持される
単一コンストラクターのインジェクション 暗黙的 (注釈は必要ありません) サポートされています @Inject が必要です

10. @QuarkusTest および @InjectMock を使用した CDI Bean の単体テスト

Quarkus では、JUnit 5 および RestAssured を使用して CDI Bean をシームレスにテストできます。 @InjectMock を使用して、テスト中に依存関係 Bean を Mockito モックに自動的に置き換えます。

package com.ghaznix;

import com.ghaznix.services.OrderProcessingService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

@QuarkusTest
public class OrderServiceTest {

    @Inject
    OrderService orderService;

    @InjectMock
    OrderProcessingService orderProcessingService;

    @Test
    public void testOrderCheckout() {
        Mockito.when(orderProcessingService.processOrder("ORD-101"))
               .thenReturn("Mocked Success");

        String result = orderService.checkout("ORD-101");
        Assertions.assertEquals("Mocked Success", result);
    }
}

##11.結論

CDI (ArC) を使用した Quarkus の依存性注入は、エンタープライズ Java 設計における画期的な変化を示します。 Quarkus は、Bean メタデータのスキャン、プロキシ生成、および注入グラフの解決をコンパイル時に移すことで、超音速の起動速度、亜原子的なメモリ フットプリント、シームレスな GraalVM ネイティブ バイナリ サポートを実現します。

Spring Boot から移行する場合でも、Kubernetes 上でグリーンフィールドのクラウドネイティブ マイクロサービスを構築する場合でも、Quarkus CDI をマスターすることで、最新のソフトウェア アーキテクチャに必要なパフォーマンスと開発者の生産性を確保できます。

Ghaznix Ecosystem Products

Empower Your Digital Presence & Workflows

Explore top-tier tools built by Ghaznix to streamline your links, surveys, and brand growth.