The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

コンテキストと依存性インジェクション

Quarkus DI solution (also called ArC) is based on the Jakarta Contexts and Dependency Injection 4.0 specification. It implements the CDI Lite specification, with selected improvements on top, and passes the CDI Lite TCK. It does not implement CDI Full. See also the list of supported features and limitations.

CDI が初めての方は、最初に コンテキストと依存性注入(CDI)の紹介 を読むことをお勧め致します。
既存の CDI コードのほとんどは問題なく動作するはずですが、Quarkus のアーキテクチャや目標との間には、わずかな相違点がいくつかあります。

1. Bean の検出

Bean discovery in CDI is a complex process which involves legacy deployment structures and accessibility requirements of the underlying module architecture. However, Quarkus is using a simplified bean discovery. There is only single bean archive with the bean discovery mode annotated and no visibility boundaries.

Bean のアーカイブは、次のものから合成されます。

  • アプリケーションクラス

  • beans.xml 記述子を含む依存関係 (内容は無視される)

  • Jandex インデックスを含む依存関係 (META-INF/jandex.idx)

  • application.propertiesquarkus.index-dependency で参照される依存関係

  • Quarkus の統合コード

Bean classes that don’t have a bean defining annotation are not discovered. This behavior is defined by CDI. But producer methods and fields and observer methods are discovered even if the declaring class is not annotated with a bean defining annotation (this behavior is different to what is defined in CDI). In fact, the declaring bean classes are considered annotated with @Dependent.

Quarkus エクステンションは、追加のディスカバリールールを宣言することができます。たとえば、@Scheduled ビジネスメソッドは、宣言するクラスが Bean 定義アノテーションでアノテーションされていなくても登録されます。

1.1. Jandex インデックスを生成する方法

Jandex インデックスを持つ依存関係は、Bean に対して自動的にスキャンされます。インデックスを生成するには、次のプラグインをビルドファイルに追加するだけです。

Maven
<build>
  <plugins>
    <plugin>
      <groupId>io.smallrye</groupId>
      <artifactId>jandex-maven-plugin</artifactId>
      <version>3.1.6</version>
      <executions>
        <execution>
          <id>make-index</id>
          <goals>
            <goal>jandex</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
Gradle (Groovy DSL)
plugins {
    id 'org.kordamp.gradle.jandex' version '1.0.0'
}

You can find the latest plugin version in the Gradle Plugin Portal

Gradle (Kotlin DSL)
plugins {
    id("org.kordamp.gradle.jandex") version "1.0.0"
}

You can find the latest plugin version in the Gradle Plugin Portal

依存関係を変更できなくても、quarkus.index-dependency エントリーを application.properties に追加することでインデックスを作成できます。

quarkus.index-dependency.<name>.group-id=
quarkus.index-dependency.<name>.artifact-id=(this one is optional)
quarkus.index-dependency.<name>.classifier=(this one is optional)
If no artifact-id is specified then all dependencies with the specificed group-id are indexed.

たとえば、次のエントリーは、org.acme:acme-api 依存関係が確実にインデックス化されるようにします。

application.properties の例
quarkus.index-dependency.acme.group-id=org.acme (1)
quarkus.index-dependency.acme.artifact-id=acme-api (2)
1 acme は、名前で識別される依存関係のグループ ID です。
2 値は、名前 acme で識別される依存関係のアーティファクト ID です。

1.2. ディスカバリーから型と依存関係を除外する方法

サードパーティーのライブラリーからのいくつかの Bean が Quarkus で正しく動作しないことがあります。典型的な例は、ポータブル拡張機能を注入する Bean です。このような場合は、型や依存関係を Bean の検出から除外することができます。quarkus.arc.exclude-types プロパティーは、除外すべきクラスに一致するために使用される文字列値のリストを受け入れます。

Table 1. 値の例

Description

org.acme.Foo

クラスの完全修飾名と一致させる

org.acme.*

org.acme パッケージとクラスを一致させる

org.acme.**

パッケージが org.acme で始まるクラスを一致させる

Bar

クラスのシンプルな名前に一致する

application.properties の例
quarkus.arc.exclude-types=org.acme.Foo,org.acme.*,Bar (1)(2)(3)
1 タイプ org.acme.Foo を除外します。
2 org.acme パッケージからすべてのタイプを除外します。
3 シンプルな名前が Bar であるすべてのタイプを除外します。

また、除外しなければ Bean をスキャンする、依存関係のあるアーティファクトを除外することも可能です。たとえば、beans.xml 記述子を含んでいる場合です。

application.properties の例
quarkus.arc.exclude-dependency.acme.group-id=org.acme (1)
quarkus.arc.exclude-dependency.acme.artifact-id=acme-services (2)
1 acme は、名前で識別される依存関係のグループ ID です。
2 値は、名前 acme で識別される依存関係のアーティファクト ID です。

2. ネイティブ実行可能ファイルとプライベートメンバー

Quarkus is using GraalVM to build a native executable. One of the limitations of GraalVM is the usage of Reflection. Reflective operations are supported but all relevant members must be registered for reflection explicitly. Those registrations result in a bigger native executable.

また、Quarkus DI がプライベートメンバーにアクセスする必要がある場合は、リフレクションを使用しなければなりません。そのため、Quarkus ユーザーは、プライベートメンバーを Bean で 使用しないこと が推奨されています。これには、インジェクションフィールド、コンストラクターとイニシャライザー、observer メソッド、producer メソッドおよび producer フィールド、disposers メソッドおよび interceptor メソッドが含まれます。

プライベートメンバーの使用を回避するには、package-private 修飾子を使うことができます。

@ApplicationScoped
public class CounterBean {

    @Inject
    CounterService counterService; (1)

    void onMessage(@Observes Event msg) { (2)
    }
}
1 package-private injection フィールド。
2 package-private observer メソッド。

あるいはコンストラクター注入も可能です:

@ApplicationScoped
public class CounterBean {

    private CounterService service;

    CounterBean(CounterService service) { (1)
      this.service = service;
    }
}
1 パッケージプライベートコンストラクター注入。この特定の場合、@Inject は任意です。

3. Supported Features and Limitations

The CDI Lite specification is fully supported. The following features from CDI Full are also supported:

  • デコレーター

    • Event などのビルトイン Bean の装飾はサポートされていません

  • BeanManager

    • In addition to the BeanContainer methods, the following methods are supported: getInjectableReference(), resolveDecorators()

  • @SessionScoped

    • Only with the Undertow extension; see here for details

These additional features are not covered by the CDI Lite TCK.

4. 標準外の機能

4.1. Bean の即時インスタンス化

4.1.1. デフォルトでは遅延

デフォルトでは、CDI Bean は必要なときに作成されます。何を正確に「必要とされる」かは、Bean のスコープに依存します。

  • 注入されたインスタンス (仕様に応じたコンテキスト参照) からメソッドが呼び出される場合は、通常のスコープ付き Bean (@ApplicationScoped@RequestScoped など) が必要になります。

    言い換えれば、通常のスコープ付き Bean を注入しても、Bean のコンテキストに基づいたインスタンスの代わりに クライアントプロキシー が注入されるため、十分ではありません。

  • 注入時に 疑似スコープを持つ Bean (@Dependent および @Singleton) が作成されます。

遅延インスタンス化の例
@Singleton // => pseudo-scope
class AmazingService {
  String ping() {
    return "amazing";
  }
}

@ApplicationScoped // => normal scope
class CoolService {
  String ping() {
    return "cool";
  }
}

@Path("/ping")
public class PingResource {

  @Inject
  AmazingService s1; (1)

  @Inject
  CoolService s2; (2)

  @GET
  public String ping() {
    return s1.ping() + s2.ping(); (3)
  }
}
1 注入は、AmazingService のインスタンス化をトリガーします。
2 注入自体は、CoolService のインスタンス化にはなりません。クライアントプロキシーが注入されます。
3 注入されたプロキシーに対する最初の呼び出しは、CoolService のインスタンス化をトリガーします。

4.1.2. スタートアップイベント

ただし、Bean の即時(eagerly)インスタンス化が必要な場合は、次のことができます。

  • StartupEvent のオブザーバーを宣言します。この場合、Bean のスコープは重要ではありません。

    @ApplicationScoped
    class CoolService {
      void startup(@Observes StartupEvent event) { (1)
      }
    }
    1 CoolService は、起動時に作成され、オブザーバーメソッドの呼び出しを処理します。
  • StartupEvent のオブザーバーで Bean を使用します - デフォルトでは遅延 で説明されているように、通常のスコープ Bean を使用する必要があります。

    @Dependent
    class MyBeanStarter {
    
      void startup(@Observes StartupEvent event, AmazingService amazing, CoolService cool) { (1)
        cool.toString(); (2)
      }
    }
    1 AmazingService は注入時に作成されます。
    2 CoolService は通常のスコープ付き Bean であるため、強制的にインスタンス化するために挿入されたプロキシーにメソッドを呼び出さなければなりません。
  • Startup annotation: で説明したように、@io.quarkus.runtime.Startup で Bean をアノテーションします。

    @Startup (1)
    @ApplicationScoped
    public class EagerAppBean {
    
       private final String name;
    
       EagerAppBean(NameGenerator generator) { (2)
         this.name = generator.createName();
       }
    }
    1 @Startup でアノテーションされた各Beanに対して、 StartupEvent の合成オブザーバが生成されます。デフォルトの優先度が使用されます。
    2 Beanのコンストラクタは、アプリケーションの起動時に呼び出され、結果として得られるコンテキストインスタンスがアプリケーションのコンテキストに格納されます。
Quarkus ユーザーは、 アプリケーションの初期化と終了 のガイドで説明されているように、常に @Initialized(ApplicationScoped.class) よりも @Observes StartupEvent を選択することが推奨されます。

4.2. リクエストコンテキストのライフサイクル

リクエストコンテキストは次の場合もアクティブになっています:

  • 同期オブザーバメソッドの通知中

リクエストコンテキストは破棄されます:

  • 通知が開始したときにまだアクティブではなかった場合はイベントのオブザーバー通知が完了した後

オブザーバー通知のためにリクエストコンテキストが初期化されると、修飾子 @Initialized(RequestScoped.class) を持つイベントが発生します。さらに、修飾子 @BeforeDestroyed(RequestScoped.class) および @Destroyed(RequestScoped.class) を持つイベントは、リクエストコンテキストが破棄されたときに発生します。

4.2.1. リクエストコンテキストのアクティブ化のトレースログを有効にする方法

ロガー io.quarkus.arc.requestContextTRACE レベルを設定し、後でログ出力の分析を試みることができます。

application.properties の例
quarkus.log.category."io.quarkus.arc.requestContext".min-level=TRACE (1)
quarkus.log.category."io.quarkus.arc.requestContext".level=TRACE
1 また、関連するカテゴリーの最小ログレベルを調整する必要があります。

4.3. 修飾された注入フィールド

CDI では、フィールド注入ポイントを宣言する場合は @Inject と任意で修飾子のセットを使用する必要があります。

  @Inject
  @ConfigProperty(name = "cool")
  String coolProperty;

Quarkus では、注入されたフィールドが少なくとも 1 つの修飾子を宣言している場合は、@Inject アノテーションを完全にスキップすることができます。

  @ConfigProperty(name = "cool")
  String coolProperty;
後述する特別なケースを除いて、コンストラクターとメソッドの注入には @Inject が必要です。

4.4. 簡略化されたコンストラクター注入

CDI では、通常のスコープ付き Bean は常に no-args コンストラクターを宣言しなければなりません (このコンストラクターは、他のコンストラクターを宣言しない限り、通常はコンパイラーによって生成されます)。しかし、この要件はコンストラクターの注入を複雑にします。CDI で動作させるためにはダミーの no-args コンストラクターを提供する必要があります。

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService() { // dummy constructor needed
  }

  @Inject // constructor injection
  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}

Quarkus では、通常のスコープ付き Bean のためにダミーのコンストラクターを宣言する必要はありません。自動的に生成されます。また、コンストラクターが 1 つしかない場合は、@Inject の必要性はありません。

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}
変数無しコンストラクターを宣言していないクラスを Bean クラスが継承している場合は、変数無しコンストラクターは自動的に生成されません。

4.5. 未使用の Bean の削除

コンテナーは、デフォルトでビルド中に未使用の Bean、インターセプター、デコレーターをすべて削除しようとします。この最適化は、生成されるクラスの量を最小限に抑え、メモリーを節約するのに役立ちます。ただし、Quarkus は、CDI.current() staticメソッドを介して実行されたプログラムによるルックアップを検出できません。したがって、削除すると誤検知エラーが発生する可能性があります。つまり、Bean は実際に使用されていても、削除されます。このような場合、ログに大きな警告が表示されます。ユーザーとエクステンションの作成者にはいくつかのオプションがあります: how to eliminate false positives

この最適化は、quarkus.arc.remove-unused-beansnone または false に設定することで無効にすることができます。Quarkus では、アプリケーションの Bean は未使用かどうかにかかわらず削除されず、アプリケーション以外のクラスは通常通りに最適化が行われるという中間モードも提供されています。このモードを使用するには、quarkus.arc.remove-unused-beansfwk または framework に設定します。

4.5.1. 何が削除されましたか?

Quarkusはまず、依存関係ツリーのルートを形成する、いわゆる 削除不可能な Beanを特定します。良い例は、Jakarta RESTリソースクラスや、 @Scheduled メソッドを宣言している Bean です。

unremovable Bean:

  • エクステンションによる削除から除外されている、または

  • @Named を通して指定された名前を持っているか、

  • オブザーバーメソッドを宣言している場合。

unused Bean:

  • unremovable ではなく、

  • 依存関係ツリーの挿入ポイントへの挿入に適格ではなく、

  • 依存関係ツリー内の挿入ポイントへの挿入に適格なプロデューサーを宣言していません。

  • is not eligible for injection into any jakarta.enterprise.inject.Instance or jakarta.inject.Provider injection point.

未使用のインターセプターとデコレーターは、どの Bean とも関連付けられていません。

開発モードを使用している場合(例: ./mvnw clean compile quarkus:dev を実行している場合)、どの Bean が削除されているかの詳細な情報を見ることができます。

  1. コンソールで - application.properties で DEBUG レベルを有効にするだけです。例: quarkus.log.category."io.quarkus.arc.processor".level=DEBUG

  2. 関連する開発 UI ページ

4.5.2. 誤検知を排除する方法

ユーザーは、コンテナーに @io.quarkus.arc.Unremovable をアノテーションすることで、(上で指定したルールをすべて満たしていても) 特定の Bean を削除しないように指示することができます。このアノテーションは、クラス、producer メソッド、producer フィールドに置くことができます。

これは常に可能ではないので、application.properties を通して同じことを実現するオプションがあります。quarkus.arc.unremovable-types プロパティーは、Bean の名前やパッケージに基づいて一致させるための文字列値のリストを受け付けます。

Table 2. 値の例

Description

org.acme.Foo

bean クラスの完全修飾名と一致させる

org.acme.*

Bean クラスのパッケージが org.acme である Bean に一致します。

org.acme.**

Bean クラスのパッケージが org.acme で開始する Bean に一致します。

Bar

Bean クラスのシンプルな名前に一致します。

application.properties の例
quarkus.arc.unremovable-types=org.acme.Foo,org.acme.*,Bar

さらに、エクステンションは UnremovableBeanBuildItem を生成することで、可能性のある誤検出を排除することができます。

4.6. デフォルトの Bean

Quarkus は、CDI が現在サポートしていない機能を追加します。これは、利用可能な手段 (Beanクラス、producer、合成 Bean など) で同等の型と修飾子を持つ他の Bean が宣言されていない場合に、条件付きで Bean を宣言することです。これは、@io .quarkus.Arc.DefaultBean アノテーションを使用して行われ、例を挙げて説明するのが最善です。

以下のコードのようにいくつかの CDI Bean を宣言する Quarkus エクステンションがあるとします。

@Dependent
public class TracerConfiguration {

    @Produces
    public Tracer tracer(Reporter reporter, Configuration configuration) {
        return new Tracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Configuration configuration() {
        // create a Configuration
    }

    @Produces
    @DefaultBean
    public Reporter reporter(){
        // create a Reporter
    }
}

アイデアは、エクステンションがユーザーのために自動設定を行い、多くのボイラープレートを排除するということです。必要な場所であれば、@InjectTracer にすることができます。私たちのアプリケーションで、設定された Tracer を利用しようとする場合は、、カスタムの Reporter を提供するなど、少しカスタマイズする必要があります。アプリケーションで必要になるのは、次のようなものだけです。

@Dependent
public class CustomTracerConfiguration {

    @Produces
    public Reporter reporter(){
        // create a custom Reporter
    }
}

@DefaultBean では、エクステンション (またはそのための他のコード) が Quarkus がサポートする何らかの方法でその型の Bean が提供されている場合、バックオフ中にデフォルトを提供することができます。

Default beans can optionally declare @jakarta.annotation.Priority. If there is no priority defined, @Priority(0) is assumed. Priority value is used for bean ordering and during typesafe resolution to disambiguate multiple matching default beans.

@Dependent
public class CustomizedDefaultConfiguration {

    @Produces
    @DefaultBean
    @Priority(100)
    public Configuration customizedConfiguration(){
        // create a customized default Configuration
        // this will have priority over previously defined default bean
    }
}

4.7. Quarkus ビルドプロファイルの Bean の有効化

Quarkus は、CDI が現在サポートしていない機能を追加しました。これは、Quarkus のビルドタイムプロファイルが有効になっているときに条件付きで Bean を有効にするというもので、@io.quarkus.arc.profile.IfBuildProfile@io.quarkus.arc.profile.UnlessBuildProfile のアノテーションを使用します。@io.quarkus.arc.DefaultBean と合わせて使用すると、これらのアノテーションにより、異なるビルドプロファイルに対して異なる Bean 構成を作成することができます。

たとえば、アプリケーションが Tracer という名前の Bean を含んでいるとします。この Bean は、テストや開発モードでは何もする必要はありませんが、本番の成果物に対しては通常の能力で動作します。このような Bean を作成する洗練された方法は以下の通りです。

@Dependent
public class TracerConfiguration {

    @Produces
    @IfBuildProfile("prod")
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}

代わりに、Tracer Bean も開発モードで動作し、デフォルトではテストのために何もしないことが要求される場合は、@UnlessBuildProfile が理想的です。コードは次のようになります。

@Dependent
public class TracerConfiguration {

    @Produces
    @UnlessBuildProfile("test") // this will be enabled for both prod and dev build time profiles
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}
ランタイムプロファイルは、@IfBuildProfile および @UnlessBuildProfile を使用した Bean 解決には影響を及ぼしません。
It is also possible to use @IfBuildProfile and @UnlessBuildProfile on stereotypes.

4.8. Quarkus ビルドプロパティーの Bean を有効にする

Quarkus には、CDI が現在サポートしていない機能が追加されています。それは、@io.quarkus.arc.properties.IfBuildProperty@io.quarkus.arc.properties.UnlessBuildProperty アノテーションを使用して、ビルドタイムプロパティーが特定の値であるかないときにBeanを条件付きで有効にするものです。このアノテーションを @io.quarkus.arc.DefaultBean と一緒に使用すると、ビルドプロパティーごとに異なるBean設定を作成することが可能になります。

Tracer を使用して上で述べたシナリオも、以下のように実装することができます。

@Dependent
public class TracerConfiguration {

    @Produces
    @IfBuildProperty(name = "some.tracer.enabled", stringValue = "true")
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}
@IfBuildProperty@UnlessBuildProperty は繰り返し可能なアノテーションです。つまり、これらのアノテーションで定義された条件の すべて が満たされた場合にのみBeanが有効になります。

代わりに、some.tracer.enabled プロパティーが false でない場合にのみ RealTracer Bean が使用されることが要求される場合は、@UnlessBuildProperty が理想的です。コードは以下のようになります。

@Dependent
public class TracerConfiguration {

    @Produces
    @UnlessBuildProperty(name = "some.tracer.enabled", stringValue = "false")
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}
実行時に設定されたプロパティーは、 @IfBuildProperty を使用しても Bean の解決に全く影響しません。
It is also possible to use @IfBuildProperty and @UnlessBuildProperty on stereotypes.

4.9. 選択された代替の宣言

CDI では、代替の Bean は、@Priority を使用してアプリケーションに対してグローバルに選択することも、beans.xml 記述子を使用した Bean アーカイブ用に選択することもできます。Quarkus には単純化された Bean 検出があり、`beans.xml`の内容は無視されます。

However, it is also possible to select alternatives for an application using the unified configuration. The quarkus.arc.selected-alternatives property accepts a list of string values that are used to match alternative beans. If any value matches then the priority of Integer#MAX_VALUE is used for the relevant bean. The priority declared via @Priority or inherited from a stereotype is overridden.

Table 3. 値の例

Description

org.acme.Foo

Bean クラス、または producer を宣言する Bean の Bean クラスの完全修飾名に一致します。

org.acme.*

Bean クラスのパッケージが org.acme である Bean に一致します。

org.acme.**

Bean クラスのパッケージが org.acme で開始する Bean に一致します。

Bar

Bean クラス、または producer を宣言する Bean の Bean クラスの単純名に一致します。

application.properties の例
quarkus.arc.selected-alternatives=org.acme.Foo,org.acme.*,Bar

4.10. 簡略化された Producer メソッドの宣言

CDI では、producer メソッドは常に @Produces とアノテーションされていなければなりません。

class Producers {

  @Inject
  @ConfigProperty(name = "cool")
  String coolProperty;

  @Produces
  @ApplicationScoped
  MyService produceService() {
    return new MyService(coolProperty);
  }
}

Quarkus では、producer メソッドにスコープアノテーション、ステレオタイプ、または修飾子が付いている場合は、@Produces アノテーションを完全に省略できます。

class Producers {

  @ConfigProperty(name = "cool")
  String coolProperty;

  @ApplicationScoped
  MyService produceService() {
    return new MyService(coolProperty);
  }
}

4.11. 静的メソッドのインターセプション

インターセプターの仕様は、around-invoke メソッドを静的宣言してはならないことは明らかです。しかし、この制限は、主に技術的な制限によって設定されました。Quarkus は追加のクラス変換を可能にするビルド時指向のスタックであるため、この制限は適用されなくなりました。インターセプタ―バインディングで非プライベートの静的メソッドにアノテーションを付けることができます。

class Services {

  @Logged (1)
  static BigDecimal computePrice(long amount) { (2)
    BigDecimal price;
    // Perform computations...
    return price;
  }
}
1 Logged はインターセプターバインディングです。
2 各メソッドの呼び出しは、Logged に関連付けられたインターセプターがある場合に傍受されます。

4.11.1. 制約事項

  • 下位互換性の理由から メソッドレベルのバインディング のみが考慮されます (そうでないとクラスレベルのバインディングを宣言している Bean クラスのStaticメソッドが突然傍受されてしまいます)。

  • プライベートなスタティックメソッドは決して傍受されません。

  • InvocationContext#getTarget() は明白な理由により null を返します。したがって、Staticメソッドを傍受するときに既存のインターセプターがすべて正しく動作するとは限りません。

    インターセプターは InvocationContext.getMethod() を使用してStaticメソッドを検出し、それに応じて動作を調整することができます。

4.12. final クラスとメソッドを処理する能力

通常の CDI では、final としてマークされているクラス、または final メソッドを持つクラスは、プロキシー作成の対象になりません。これは、インターセプターと通常のスコープ Bean が正しく動作しないことを意味します。このような状況は、クラスおよびメソッドがデフォルトで `final`である Kotlin のような代替 JVM 言語で CDI を使用しようとするときに非常に一般的です。

しかし、Quarkus では、quarkus.arc.transform-unproxyable-classestrue (デフォルト値) に設定すると、これらの制限を抑制することができます。

4.13. コンテナー管理型の並行処理

CDI Bean の標準的な同時実行制御メカニズムはありません。それにもかかわらず、Bean インスタンスは、複数のスレッドから同時に共有およびアクセスすることができます。その場合は、スレッドセーフでなければなりません。標準の Java コンストラクト (volatilesynchronizedReadWriteLock など) を使用するか、コンテナーに同時アクセスを制御させることができます。Quarkus は、@io.quarkus.arc.Lock と、このインターセプタ―バインディング用の組み込みインターセプターを提供します。傍受された Bean のコンテキストインスタンスに関連付けられた各インターセプターインスタンスは、公平でない順序付けポリシーを持つ ReadWriteLock を保持しています。

io.quarkus.arc.Lock は通常のインターセプターバインディングであるため、任意のスコープを持つ任意の Bean に使用することができます。しかし、特に「共有」スコープ、たとえば @Singleton@ApplicationScoped に有益です。
コンテナー管理された並行処理の例
import io.quarkus.arc.Lock;

@Lock (1)
@ApplicationScoped
class SharedService {

  void addAmount(BigDecimal amount) {
    // ...changes some internal state of the bean
  }

  @Lock(value = Lock.Type.READ, time = 1, unit = TimeUnit.SECONDS) (2) (3)
  BigDecimal getAmount() {
    // ...it is safe to read the value concurrently
  }
}
1 クラスで宣言された (@Lock (Lock.type.Write) にマッピングされる) @Lock は、任意のビジネスメソッドの呼び出しに対して Bean インスタンスをロックするようにコンテナーに指示します。つまり、クライアントには「排他アクセス」があり、同時呼び出しは許可されません。
2 @Lock(Lock.Type.READ) は、クラスレベルで指定された値を上書きします。これは、Bean のインスタンスが @Lock(Lock.Type.WRITE) によってロックされていない限り、任意の数のクライアントが同時にメソッドを呼び出すことができることを意味します。
3 また、「待ち時間」を指定することもできます。指定した時間内にロックを取得できない場合は LockException が発生します。

4.14. 反復可能なインターセプターバインディング

Quarkusでは、@Repeatable インターセプター結合アノテーションのサポートが制限されています。

インターセプタ―をコンポーネントにバインドする場合は、メソッドに対して複数の @Repeatable アノテーションを宣言できます。インターセプター仕様との相互作用に関する未解決の質問があるため、クラスとステレオタイプで宣言された反復可能なインターセプターバインディングはサポートされていません。これは将来追加される可能性があります。

たとえば、キャッシュをクリアするインターセプタ―があるとします。対応するインターセプタ―バインディングは @CacheInvalidateAll と呼ばれ、@Repeatable として宣言されます。同時に 2 つのキャッシュをクリアしたい場合は、@CacheInvalidateAll を 2 回追加します。

@ApplicationScoped
class CachingService {
  @CacheInvalidateAll(cacheName = "foo")
  @CacheInvalidateAll(cacheName = "bar")
  void heavyComputation() {
    // ...
    // some computation that updates a lot of data
    // and requires 2 caches to be invalidated
    // ...
  }
}

ここまで、インターセプタ―がどのように使用されるかを説明しました。では、インターセプターを作成するにはどうすれば良いでしょうか。

インターセプタ―のインターセプタ―バインディングを宣言する場合は、通常どおり、インターセプタ―クラスに複数の @Repeatable アノテーションを追加できます。@Cached アノテーションの場合と同様に、アノテーションメンバーが @Nonbinding の場合は役に立ちませんが、それ以外の場合は重要になります。

たとえば、メソッド呼び出しを特定のターゲットに自動的に記録できるインターセプタ―があるとします。インターセプタ―バインディングアノテーション @Logged には、ログを保存する場所を指定する target というメンバーがあります。この実装は、コンソールログとファイルロギングに制限することができます。

@Interceptor
@Logged(target = "console")
@Logged(target = "file")
class NaiveLoggingInterceptor {
  // ...
}

他にも、異なるターゲットへのメソッド呼び出しをログに記録するためのインターセプターを提供することができます。

4.15. プログラムによるルックアップの結果をキャッシュする

In certain situations, it is practical to obtain a bean instance programmatically via an injected jakarta.enterprise.inject.Instance and Instance.get(). However, according to the specification the get() method must identify the matching bean and obtain a contextual reference. As a consequence, a new instance of a @Dependent bean is returned from each invocation of get(). Moreover, this instance is a dependent object of the injected Instance. This behavior is well-defined, but it may lead to unexpected errors and memory leaks. Therefore, Quarkus comes with the io.quarkus.arc.WithCaching annotation. An injected Instance annotated with this annotation will cache the result of the Instance#get() operation. The result is computed on the first call and the same value is returned for all subsequent calls, even for @Dependent beans.

class Producer {

  AtomicLong nextLong = new AtomicLong();
  AtomicInteger nextInt = new AtomicInteger();

   @Dependent
   @Produces
   Integer produceInt() {
     return nextInt.incrementAndGet();
   }

   @Dependent
   @Produces
   Long produceLong() {
     return nextLong.incrementAndGet();
   }
}

class Consumer {

  @Inject
  Instance<Long> longInstance;

  @Inject
  @WithCaching
  Instance<Integer> intInstance;

  // this method should always return true
  // Producer#produceInt() is only called once
  boolean pingInt() {
    return intInstance.get().equals(intInstance.get());
  }

  // this method should always return false
  // Producer#produceLong() is called twice per each pingLong() invocation
  boolean pingLong() {
    return longInstance.get().equals(longInstance.get());
  }
}
io.quarkus.arc.InjectableInstance.clearCache() を使って、キャッシュされた値をクリアすることも可能です。この場合、 jakarta.enterprise.inject.Instance の代わりに Quarkus 固有の io.quarkus.arc.InjectableInstance を注入する必要があります。

4.16. プログラムによるルックアップで取得できる Bean を宣言的に選択する

jakarta.enterprise.inject.Instance を介したプログラムによる検索で取得できる Bean の集合を絞り込むことが有用な場合があります。典型的には,ユーザは,実行時設定プロパティに基づいて,インタフェースの適切な実装を選択する必要があります。

インターフェイス org.acme.Service を実装した 2 つの Bean があるとします。実装が CDI 修飾子を宣言していない限り、org.acme.Service を直接注入することはできません。しかし、代わりに Instance<Service> を注入して、すべての実装を繰り返し、正しいものを手動で選択することができます。また、@LookupIfProperty@LookupUnlessProperty アノテーションを利用することもできます。@LookupIfProperty は、実行時設定プロパティーが提供された値と一致する場合にのみ、Bean を取得する必要があることを示します。一方、@LookupUnlessProperty は、実行時設定プロパティーが提供された値と一致しない場合にのみ、Bean を取得する必要があることを示します。

@LookupIfProperty Example
 interface Service {
    String name();
 }

 @LookupIfProperty(name = "service.foo.enabled", stringValue = "true")
 @ApplicationScoped
 class ServiceFoo implements Service {

    public String name() {
       return "foo";
    }
 }

 @ApplicationScoped
 class ServiceBar implements Service {

    public String name() {
       return "bar";
    }
 }

 @ApplicationScoped
 class Client {

    @Inject
    Instance<Service> service;

    void printServiceName() {
       // This will print "bar" if the property "service.foo.enabled" is NOT set to "true"
       // If "service.foo.enabled" is set to "true" then service.get() would result in an AmbiguousResolutionException
       System.out.println(service.get().name());
    }
 }

4.17. 複数の Bean インスタンスを直感的に注入する

CDIでは、 java.lang.Iterable を実装した jakarta.enterprise.inject.Instance を介して、複数のBeanインスタンス(別名:コンテキスト参照)を注入することが可能です。しかし、これは直感的に理解できるものではありません。そこで、Quarkusでは新しい方法を導入しました。 io.quarkus.arc.All 修飾子でアノテーションされた java.util.List を注入することができます。リスト内の要素の型は、検索を実行する際に必要な型として使用されます。

@ApplicationScoped
public class Processor {

     @Inject
     @All
     List<Service> services; (1) (2)
}
1 注入されたインスタンスは、disambiguated bean のコンテキスト参照の immutable list です。
2 このインジェクションポイントに必要なタイプは Service であり、追加の修飾子は宣言されていません。
The list is sorted by priority as defined by io.quarkus.arc.InjectableBean#getPriority(). Higher priority goes first. In general, the @jakarta.annotation.Priority annotation can be used to assign the priority to a class bean, producer method or producer field.

インジェクションポイントが @All 以外の修飾子を宣言していない場合、@Any が使用されます。つまり、動作は @Inject @Any Instance<Service> と同等です。

io.quarkus.arc.InstanceHandle でラップされた Bean インスタンスのリストを注入することもできます。これは、関連する Bean メタデータを検査する必要がある場合に役立ちます。

@ApplicationScoped
public class Processor {

     @Inject
     @All
     List<InstanceHandle<Service>> services;

     public void doSomething() {
       for (InstanceHandle<Service> handle : services) {
         if (handle.getBean().getScope().equals(Dependent.class)) {
           handle.get().process();
           break;
         }
       }
     }
}
型変数もワイルドカードも @All List<> インジェクションポイントのタイプパラメーターとしては有効ではありません。つまり、@Inject @All List<?> all はサポートされておらず、デプロイメントエラーになります。
Arc.container().listAll() メソッドを使用して、プログラムですべての Bean インスタンスハンドルのリストを取得することもできます。

4.18. メソッドとコンストラクターのクラスレベルのインターセプターバインディングを無視する

マネージド Bean がクラスレベルでインターセプターバインディングアノテーションを宣言する場合、対応する @AroundInvoke インターセプターがすべてのビジネスメソッドに適用されます。同様に、対応する @AroundConstruct インターセプターが Bean コンストラクターに適用されます。

たとえば、@Logged バインディングアノテーションを持つロギングインターセプターと @Traced バインディングアノテーションを持つトレースインターセプターがあるとします。

@ApplicationScoped
@Logged
public class MyService {
    public void doSomething() {
        ...
    }

    @Traced
    public void doSomethingElse() {
        ...
    }
}

この例では、doSomethingdoSomethingElse の両方が架空のロギングインターセプターによってインターセプトされます。さらに、doSomethingElse メソッドは、架空のトレースインターセプターによってインターセプトされます。

もし @Traced インターセプターが必要なロギングもすべて行っていた場合、このメソッドでは @Logged インターセプターを省略したいのですが、他のすべてのメソッドではそのままにしておきたいと思います。これを実現するには、メソッドに @NoClassInterceptors というアノテーションを付けます。

@Traced
@NoClassInterceptors
public void doSomethingElse() {
    ...
}

@NoClassInterceptors アノテーションはメソッドとコンストラクターに付けることができ、これらのメソッドとコンストラクターではすべてのクラスレベルのインターセプターが無視されることを意味します。つまり、メソッド/コンストラクターに @NoClassInterceptors アノテーションが付けられている場合、このメソッド/コンストラクターに適用されるインターセプターは、メソッド/コンストラクターで直接宣言されたインターセプターのみです。

このアノテーションは、ビジネスメソッドインターセプター (@AroundInvoke) とコンストラクターライフサイクルコールバックインターセプター (@AroundConstruct) にのみ影響します。

4.19. 非同期オブザーバーメソッドによって出力される例外

If an exception is thrown by an asynchronous observer then the CompletionStage returned by the fireAsync() method completes exceptionally so that the event producer may react appropriately. However, if the event producer does not care then the exception is ignored silently. Therefore, Quarkus logs an error message by default. It is also possible to implement a custom AsyncObserverExceptionHandler. A bean that implements this interface should be @jakarta.inject.Singleton or @jakarta.enterprise.context.ApplicationScoped.

NoopAsyncObserverExceptionHandler
@Singleton
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {

  void handle(Throwable throwable, ObserverMethod<?> observerMethod, EventContext<?> eventContext) {
    // do nothing
  }

}

5. ビルド時エクステンション

Quarkus は、インスタント起動と低メモリフットプリントを提供するために、ビルド時の最適化を取り入れています。このアプローチの欠点は、CDI ポータブル・エクステンションをサポートできないことです。それにもかかわらず、ほとんどの機能は、Quarkus エクステンション を使用して行うことができます。詳細は、integration guide を参照してください。

6. Dev mode

In dev mode, two special endpoints are registered automatically to provide some basic debug info in the JSON format:

  • HTTP GET /q/arc - 要約、Bean の数、設定プロパティーなどを返します。

  • HTTP GET /q/arc/beans - すべての Bean のリストを返します。

  • HTTP GET /q/arc/removed-beans - ビルド中に削除された未使用の Bean のリストを返します。

  • HTTP GET /q/arc/observers - すべてのオブザーバーメソッドのリストを返します。

These endpoints are only available in dev mode, i.e. when you run your application via mvn quarkus:dev (or ./gradlew quarkusDev).

6.1. ビジネスメソッドの呼び出しとイベントの監視

In dev mode, it is also possible to enable monitoring of business method invocations and fired events. Simply set the quarkus.arc.dev-mode.monitoring-enabled configuration property to true and explore the relevant Dev UI pages.

7. ストリクトモード

デフォルトでは、ArC は CDI 仕様で要求される全てのバリデーションを実行するわけではありません。また、多くの方法でCDIの使い勝手を向上させますが、その中には仕様に直接反するものもあります。

To pass the CDI Lite TCK, ArC also has a strict mode. This mode enables additional validations and disables certain improvements that conflict with the specification.

ストリクトモードを有効にするには、次のように設定します:

quarkus.arc.strict-compatibility=true

その他にも、仕様の互換性に影響を与える機能があります:

To get a behavior closer to the specification, these features should be disabled.

アプリケーションは、CDIをより便利に使えるように、デフォルトの非厳密モードを使用することが推奨されます。厳密モードの「厳密さ」(CDI仕様の上に追加される検証のセットと無効にされる改良のセット)は、将来変化する可能性があります。

8. ArC Configuration Reference

ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能

Configuration property

デフォルト

  • If set to all (or true) the container will attempt to remove all unused beans.

  • If set to none (or false) no beans will ever be removed even if they are unused (according to the criteria set out below)

  • If set to fwk, then all unused beans will be removed, except the unused beans whose classes are declared in the application code

An unused bean:

  • is not a built-in bean or interceptor,

  • is not eligible for injection to any injection point,

  • is not excluded by any extension,

  • does not have a name,

  • does not declare an observer,

  • does not declare any producer which is eligible for injection to any injection point,

  • is not directly eligible for injection into any jakarta.enterprise.inject.Instance injection point

Environment variable: QUARKUS_ARC_REMOVE_UNUSED_BEANS

Show more

string

all

If set to true @Inject is automatically added to all non-static non-final fields that are annotated with one of the annotations defined by AutoInjectAnnotationBuildItem.

Environment variable: QUARKUS_ARC_AUTO_INJECT_FIELDS

Show more

boolean

true

If set to true, the bytecode of unproxyable beans will be transformed. This ensures that a proxy/subclass can be created properly. If the value is set to false, then an exception is thrown at build time indicating that a subclass/proxy could not be created. Quarkus performs the following transformations when this setting is enabled:

  • Remove 'final' modifier from classes and methods when a proxy is required.

  • Create a no-args constructor if needed.

  • Makes private no-args constructors package-private if necessary.

Environment variable: QUARKUS_ARC_TRANSFORM_UNPROXYABLE_CLASSES

Show more

boolean

true

If set to true, the bytecode of private fields that are injection points will be transformed to package private. This ensures that field injection can be performed completely reflection-free. If the value is set to false, then a reflection fallback is used to perform the injection.

Environment variable: QUARKUS_ARC_TRANSFORM_PRIVATE_INJECTED_FIELDS

Show more

boolean

true

If set to true (the default), the build fails if a private method that is neither an observer nor a producer, is annotated with an interceptor binding. An example of this is the use of Transactional on a private method of a bean. If set to false, Quarkus simply logs a warning that the annotation will be ignored.

Environment variable: QUARKUS_ARC_FAIL_ON_INTERCEPTED_PRIVATE_METHOD

Show more

boolean

true

The list of selected alternatives for an application.

An element value can be:

  • a fully qualified class name, i.e. org.acme.Foo

  • a simple class name as defined by Class#getSimpleName(), i.e. Foo

  • a package name with suffix .*, i.e. org.acme.*, matches a package

  • a package name with suffix .**, i.e. org.acme.**, matches a package that starts with the value Each element value is used to match an alternative bean class, an alternative stereotype annotation type or a bean class that declares an alternative producer. If any value matches then the priority of Integer#MAX_VALUE is used for the relevant bean. The priority declared via jakarta.annotation.Priority is overridden.

Environment variable: QUARKUS_ARC_SELECTED_ALTERNATIVES

Show more

list of string

If set to true then jakarta.enterprise.inject.Produces is automatically added to all non-void methods that are annotated with a scope annotation, a stereotype or a qualifier, and are not annotated with Inject or Produces, and no parameter is annotated with Disposes, Observes or ObservesAsync.

Environment variable: QUARKUS_ARC_AUTO_PRODUCER_METHODS

Show more

boolean

true

The list of types that should be excluded from discovery.

An element value can be:

  • a fully qualified class name, i.e. org.acme.Foo

  • a simple class name as defined by Class#getSimpleName(), i.e. Foo

  • a package name with suffix .*, i.e. org.acme.*, matches a package

  • a package name with suffix .**, i.e. org.acme.**, matches a package that starts with the value If any element value matches a discovered type then the type is excluded from discovery, i.e. no beans and observer methods are created from this type.

Environment variable: QUARKUS_ARC_EXCLUDE_TYPES

Show more

list of string

List of types that should be considered unremovable regardless of whether they are directly used or not. This is a configuration option equivalent to using io.quarkus.arc.Unremovable annotation.

An element value can be:

  • a fully qualified class name, i.e. org.acme.Foo

  • a simple class name as defined by Class#getSimpleName(), i.e. Foo

  • a package name with suffix .*, i.e. org.acme.*, matches a package

  • a package name with suffix .**, i.e. org.acme.**, matches a package that starts with the value If any element value matches a discovered bean, then such a bean is considered unremovable.

Environment variable: QUARKUS_ARC_UNREMOVABLE_TYPES

Show more

list of string

If set to true then the container attempts to detect "unused removed beans" false positives during programmatic lookup at runtime. You can disable this feature to conserve some memory when running your application in production.

Environment variable: QUARKUS_ARC_DETECT_UNUSED_FALSE_POSITIVES

Show more

boolean

true

If set to true then the container attempts to detect wrong usages of annotations and eventually fails the build to prevent unexpected behavior of a Quarkus application.

A typical example is @jakarta.ejb.Singleton which is often confused with @jakarta.inject.Singleton. As a result a component annotated with @jakarta.ejb.Singleton would be completely ignored. Another example is an inner class annotated with a scope annotation - this component would be again completely ignored.

Environment variable: QUARKUS_ARC_DETECT_WRONG_ANNOTATIONS

Show more

boolean

true

If set to true, the container will perform additional validations mandated by the CDI specification. Some improvements on top of the CDI specification may be disabled. Applications that work as expected in the strict mode should work without a change in the default, non-strict mode.

The strict mode is mainly introduced to allow passing the CDI Lite TCK. Applications are recommended to use the default, non-strict mode, which makes CDI more convenient to use. The "strictness" of the strict mode (the set of additional validations and the set of disabled improvements on top of the CDI specification) may change over time.

Note that transform-unproxyable-classes and remove-unused-beans also has effect on specification compatibility. You may want to disable these features to get behavior closer to the specification.

Environment variable: QUARKUS_ARC_STRICT_COMPATIBILITY

Show more

boolean

false

If set to true then the container monitors business method invocations and fired events during the development mode.

This config property should not be changed in the development mode as it requires a full rebuild of the application

Environment variable: QUARKUS_ARC_DEV_MODE_MONITORING_ENABLED

Show more

boolean

false

If set to true then the dependency graphs are generated and available in the Dev UI.

Environment variable: QUARKUS_ARC_DEV_MODE_GENERATE_DEPENDENCY_GRAPHS

Show more

boolean

true

If set to true then disable StartupEvent and ShutdownEvent observers declared on application bean classes during the tests.

Environment variable: QUARKUS_ARC_TEST_DISABLE_APPLICATION_LIFECYCLE_OBSERVERS

Show more

boolean

false

The list of packages that will not be checked for split package issues.

A package string representation can be:

  • a full name of the package, i.e. org.acme.foo

  • a package name with suffix .*, i.e. org.acme.*, which matches a package that starts with provided value

Environment variable: QUARKUS_ARC_IGNORED_SPLIT_PACKAGES

Show more

list of string

If set to true and the SmallRye Context Propagation extension is present then the CDI contexts will be propagated by means of the MicroProfile Context Propagation API. Specifically, a org.eclipse.microprofile.context.spi.ThreadContextProvider implementation is registered. On the other hand, if set to false then the MicroProfile Context Propagation API will never be used to propagate the CDI contexts. Note that the CDI contexts may be propagated in a different way though. For example with the Vertx duplicated context.

Environment variable: QUARKUS_ARC_CONTEXT_PROPAGATION_ENABLED

Show more

boolean

true

Artifacts that should be excluded from discovery

デフォルト

The maven groupId of the artifact.

Environment variable: QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__GROUP_ID

Show more

string

required

The maven artifactId of the artifact (optional).

Environment variable: QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__ARTIFACT_ID

Show more

string

The maven classifier of the artifact (optional).

Environment variable: QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__CLASSIFIER

Show more

string

関連コンテンツ