カテゴリ: Quarkus 更新日: 2026/04/05

Quarkus拡張開発入門!独自アノテーションの作り方とビルド時処理を徹底解説

Quarkus拡張用アノテーションの作り方
Quarkus拡張用アノテーションの作り方

先生と生徒の会話形式で理解しよう

生徒

「Quarkusで自分専用のアノテーションを作って、特定の処理を自動化したいのですが、普通のJavaのアノテーションと同じ作り方で良いのでしょうか?」

先生

「アノテーション自体の定義はJava標準と同じですが、Quarkus拡張(Extension)として機能させるには、ビルド時(Build Time)にそのアノテーションをどう処理するかを記述する必要があります。」

生徒

「ビルド時処理……。難しそうですね。具体的にどうやってアノテーションを検知して、ロジックを組み込むんですか?」

先生

「QuarkusにはJandexというインデックス機能や、BuildStepという強力な仕組みがあります。初心者の方でも分かりやすく、ステップバイステップで解説していきましょう!」

1. Quarkus拡張におけるアノテーションの役割

1. Quarkus拡張におけるアノテーションの役割
1. Quarkus拡張におけるアノテーションの役割

Quarkus(クオーカス)は「Supersonic Subatomic Java」と呼ばれる通り、圧倒的な起動速度とメモリ効率を誇るJavaフレームワークです。この性能を実現している最大の理由は、従来のフレームワークが実行時に行っていたリフレクションやクラスパスのスキャンを、ビルド時に前倒しして処理する仕組みにあります。 独自のアノテーションを作成する場合も、この「ビルド時に解析する」という考え方が非常に重要です。開発者が作成したアノテーションをQuarkusがビルド中に見つけ出し、それに基づいてプロキシを生成したり、依存注入の挙動を変えたりすることで、実行時の負荷を最小限に抑えています。

2. 拡張機能プロジェクトの構成と準備

2. 拡張機能プロジェクトの構成と準備
2. 拡張機能プロジェクトの構成と準備

Quarkus拡張を開発するには、通常「runtime」と「deployment」という二つのモジュールに分かれた構造を作成します。アノテーション自体の定義は「runtime」モジュールに配置し、そのアノテーションをスキャンして処理するロジックは「deployment」モジュールに記述します。 まずは、アノテーションを定義するための基本的な依存関係がプロジェクトに含まれているか確認しましょう。Quarkusのプロジェクト作成コマンドやMavenアーキタイプを使用すると、必要な構造が自動的に生成されます。

3. 独自アノテーションを定義する

3. 独自アノテーションを定義する
3. 独自アノテーションを定義する

まずは、Java標準の機能を使ってアノテーションを定義します。ここでは例として、メソッドの実行時間をログに出力するための「@LogExecutionTime」というアノテーションを作成してみましょう。このアノテーション自体は非常にシンプルです。 注意点として、Quarkusでビルド時にスキャン対象とするためには、対象のクラスがJandexインデックスに含まれている必要があります。拡張機能内のクラスは自動的にインデックス化されますが、利用者が作成するアプリ側で使う場合は設定が必要になることもあります。


package org.acme.extension.runtime;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
    String value() default "Execution";
}

4. Jandexを使用してアノテーションをスキャンする

4. Jandexを使用してアノテーションをスキャンする
4. Jandexを使用してアノテーションをスキャンする

次に、deploymentモジュール側でこのアノテーションが付与されたメソッドを見つけ出す処理を書きます。Quarkusは「Jandex」というライブラリを使用して、クラスファイルを直接ロードせずにメタデータを高速に検索します。 BuildStepの中でCombinedIndexBuildItemを受け取ることで、アプリケーション全体のクラス情報を参照できます。ここで特定のアノテーションがどこで使われているかを特定し、次の処理に繋げます。以下のコードは、特定のアノテーションが付与されたメソッドの情報を取得する一例です。


package org.acme.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;
import java.util.Collection;

class MyExtensionProcessor {
    private static final DotName LOG_ANNOTATION = DotName.createSimple("org.acme.extension.runtime.LogExecutionTime");

    @BuildStep
    void scanAnnotations(CombinedIndexBuildItem indexBuildItem) {
        Collection<AnnotationInstance> annotations = indexBuildItem.getIndex().getAnnotations(LOG_ANNOTATION);
        for (AnnotationInstance annotation : annotations) {
            System.out.println("発見した対象: " + annotation.target().toString());
        }
    }
}

5. ビルドステップとレコーダーの連携

5. ビルドステップとレコーダーの連携
5. ビルドステップとレコーダーの連携

アノテーションを見つけただけでは何も起こりません。実行時に何らかの動作をさせるには、「Recorder(レコーダー)」という仕組みを使います。レコーダーは、ビルド時に決定した情報を実行時のバイトコードとして記録する役割を担います。 例えば、アノテーションが付いたメソッドに対してインターセプターを有効にする設定をビルド時に行い、実際のロジックはランタイム側に任せます。これにより、実行時に「どのアノテーションがどこにあるか」を探す手間が省けるのです。

6. インターセプターの実装とアノテーションの紐付け

6. インターセプターの実装とアノテーションの紐付け
6. インターセプターの実装とアノテーションの紐付け

アノテーションがメソッドに付与された際、実際に動作するロジックを実装します。QuarkusはCDI(Contexts and Dependency Injection)をベースにしているため、標準的なインターセプターの仕組みをそのまま利用できます。 作成したアノテーションをインターセプターバインディングとして定義し、その背後で動作するInterceptorクラスを作成します。以下のサンプルは、実際にメソッドの実行前後に処理を差し込む方法を示しています。


package org.acme.extension.runtime;

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
    Object logInvocation(InvocationContext context) throws Exception {
        long start = System.currentTimeMillis();
        try {
            return context.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;
            System.out.println(context.getMethod().getName() + " の実行時間: " + duration + "ms");
        }
    }
}

7. アノテーションを認識させるためのbeans.xmlの設定

7. アノテーションを認識させるためのbeans.xmlの設定
7. アノテーションを認識させるためのbeans.xmlの設定

Quarkus拡張では、多くの設定が自動化されていますが、作成したインターセプターがCDI Beanとして適切に認識されるようにする必要があります。通常、runtimeモジュールの `META-INF/beans.xml` に設定を記述したり、Jandexインデックスを生成するためのMavenプラグインを適用したりします。 また、拡張機能のdeploymentモジュールにおいて、そのインターセプターが「追加のBean」として登録されるようにビルドステップを記述することも一般的です。これにより、利用者がわざわざ設定ファイルを書かなくても、アノテーションを付けるだけで機能が有効になります。


<!-- runtime/src/main/resources/META-INF/beans.xml -->
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_2_0.xsd"
       bean-discovery-mode="annotated">
</beans>

8. 実行時の動作確認とデバッグ方法

8. 実行時の動作確認とデバッグ方法
8. 実行時の動作確認とデバッグ方法

独自拡張が完成したら、サンプルアプリケーションで実際にアノテーションを使用してみましょう。QuarkusのDev Mode(mvn quarkus:dev)を起動すると、拡張機能の変更が即座に反映されるため、非常に効率的に開発が進められます。 もしアノテーションが効かない場合は、Jandexインデックスが正しく生成されているか、あるいはBuildStepでスペルミスがないかを確認してください。ログ出力に独自のメッセージを仕込むことで、ビルド時にどのクラスがスキャンされたかを追跡することも可能です。


[INFO] Scanning for projects...
[INFO] --- quarkus:3.15.0:dev (default-cli) @ my-app ---
[INFO] MyExtensionProcessor: 発見した対象: void org.acme.AppResource.hello()
[INFO] Listening for transport dt_socket at address: 5005
[INFO] Quarkus 3.15.0 started in 1.234s.

9. 高度なカスタマイズ:アノテーションにパラメータを持たせる

9. 高度なカスタマイズ:アノテーションにパラメータを持たせる
9. 高度なカスタマイズ:アノテーションにパラメータを持たせる

アノテーションに引数を追加して、動作をカスタマイズすることもできます。例えば、ログレベルを指定できるようにしたり、特定の条件下でのみ実行されるようにフィルターをかけたりすることが可能です。 これらのパラメータ値は、ビルド時にAnnotationInstanceから取得し、レコーダーを通じて実行時のインスタンスに渡されます。この「ビルド時に設定を読み取り、実行時のオーバーヘッドをゼロにする」という流れこそが、Quarkus拡張開発の醍醐味と言えるでしょう。大規模なマイクロサービス開発においては、こうした共通処理の隠蔽化が生産性を大きく向上させます。

カテゴリの一覧へ
新着記事
New1
Micronaut
Micronautのイベント発行とリスナーの使い方!アプリ内通信の基本をやさしく解説
New2
Quarkus
QuarkusのREST APIでJSONレスポンスを返す方法を完全解説!初心者向けJackson・JSON-B入門
New3
Quarkus
Quarkus拡張開発入門!GraalVMネイティブイメージ統合の仕組みを徹底解説
New4
Micronaut
Micronautの@Requiresとは?条件付きBeanの読み込み方法をやさしく解説【DIとアノテーション入門】
人気記事
No.1
Java&Spring記事人気No1
Java
Javaのコンパイルと実行の流れを解説!JVM・JDK・JREの違いも初心者向けに整理
No.2
Java&Spring記事人気No2
Quarkus
Quarkus拡張開発を徹底解説!仕組みから自作エクステンションの作り方まで
No.3
Java&Spring記事人気No3
Quarkus
Quarkus入門!GitHub ActionsでCI/CDパイプラインを構築して自動ビルドを実現する方法
No.4
Java&Spring記事人気No4
Java
JavaのString検索方法を完全ガイド!contains・indexOf・startsWith・endsWithを徹底解説
No.5
Java&Spring記事人気No5
Java
JavaのString比較を徹底解説!equalsと==の違い、初心者が陥る罠とは?
No.6
Java&Spring記事人気No6
Java
Java Functionインタフェースの使い方を完全ガイド!map変換と処理チェーンを理解する
No.7
Java&Spring記事人気No7
Quarkus
Quarkus拡張開発入門!自作Extensionを作る基本ステップと仕組みを徹底解説
No.8
Java&Spring記事人気No8
Quarkus
Quarkus拡張開発をマスター!ビルドプロセスの仕組みと内部構造を徹底解説