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

コンテキストと依存関係の注入

Quarkus DI ソリューション (ArC とも呼ばれます) は Contexts and Dependency Injection for Java 2.0 仕様に基づいています。ただし、これは TCK によって検証された完全な CDI 実装ではありません。CDI 機能のサブセットのみが実装されています。以下も合わせて参照下さい: the list of supported features および the list of limitations

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

1. Bean 検出

CDI での Bean 検出は、レガシーなデプロイメント構造と、基礎となるモジュールアーキテクチャーのアクセシビリティ要件を含む複雑なプロセスです。しかし、Quarkusは 簡素化された Bean 検出 を使用しています。 annotated bean検出モード と可視性の境界がない単一の Bean アーカイブのみが存在します。

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

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

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

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

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

  • Quarkus の統合コード

bean定義アノテーション を持たない Bean クラスは検出されません。この動作は CDI で定義されています。しかし、producer メソッドやフィールド、observer メソッドは、宣言クラスが Bean 定義アノテーションを持たない場合でも検出されます (この動作は CDI で定義されているものとは異なります)。実際には、Bean クラスの宣言は @Dependent でアノテーションされていると見なされます。

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

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

Jandex インデックスを持つ依存関係は、自動的に Bean をスキャンします。インデックスを生成するには、以下を pom.xml に追加してください。

<build>
  <plugins>
    <plugin>
      <groupId>org.jboss.jandex</groupId>
      <artifactId>jandex-maven-plugin</artifactId>
      <version>1.2.3</version>
      <executions>
        <execution>
          <id>make-index</id>
          <goals>
            <goal>jandex</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

If you are using gradle, you can apply the following plugin to your build.gradle:

plugins {
    id 'org.kordamp.gradle.jandex' version '0.11.0'
}

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

quarkus.index-dependency.<name>.group-id=
quarkus.index-dependency.<name>.artifact-id=
quarkus.index-dependency.<name>.classifier=(this one is optional)

たとえば、次のエントリーは、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. 値の例

バリューe

説明

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. サポートされている機能

  • プログラミングモデル

    • Java クラスによって実装されたマネージド Bean

      • ライフサイクルコールバック @PostConstruct および @PreDestroy

    • producer のメソッドおよびフィールド、ディスポーザー

    • 修飾子

    • オルタナティブ

    • ステレオタイプ

  • 依存関係の注入とルックアップ

    • フィールド、コンストラクター、イニシャライザー/セッター注入

    • タイプセーフ解決

    • javax.Enterprise.inject.instance を介したプログラムによる検索

    • クライアントのプロキシー

    • 注入ポイントメタデータ

  • スコープとコンテキスト

    • @Dependent, @ApplicationScoped, @Singleton, @RequestScoped and @SessionScoped

    • カスタムスコープとコンテキスト

  • インターセプター

    • ビジネスメソッドインターセプタ―: @AroundInvoke

    • ライフサイクルイベントコールバックのためのインターセプター: @PostConstruct@PreDestroy@AroundConstruct

  • デコレーター

  • 非同期イベントとトランザクションオブザーバーメソッドを含むイベントとオブザーバーメソッド

4. 制約事項

  • @ConversationScoped はサポートされていません。

  • ポータブル・エクステンションはサポートされていません。

  • BeanManager - メソッドでは、getBeans()createCreationalContext()getReference()getInjectableReference()resolve()getContext()fireEvent()getEvent()、および createInstance() のみが実装されています。

  • 特殊化(Specialization)はサポートされていません。

  • beans.xml 記述子の内容は無視されます。

  • パッシベーションおよびパッシベーションスコープはサポートされていません。

  • スーパークラスのインターセプターメソッドは実装されていません。

  • @Interceptors はサポートされていません。

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

5. 標準外の機能

5.1. Bean のインスタンス化

5.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 のインスタンス化をトリガーします。

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

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

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

    @ApplicationScoped
    class CoolService {
      void startup(@Observes StartupEvent event) { (1)
      }
    }
    1 CoolService は、起動時に作成され、オブザーバーメソッドの呼び出しを処理します。
  • Use the bean in an observer of the StartupEvent - normal scoped beans must be used as described in デフォルトではレイジー:

    @Dependent
    class MyBeanStarter {
    
      void startup(@Observes StartupEvent event, AmazingService amazing, CoolService cool) { (1)
        cool.toString(); (2)
      }
    }
    1 AmazingService は注入時に作成されます。
    2 The CoolService is a normal scoped bean, so we have to invoke a method upon the injected proxy to force the instantiation.
  • 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 ユーザーは、Application Initialization and Termination のガイドで説明されているように、常に @Initialized(ApplicationScoped.class) よりも @Observes StartupEvent を選択することが推奨されます。

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

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

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

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

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

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

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

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

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

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

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

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

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

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService() { // dummy constructor needed
  }

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

There is no need to declare dummy constructors for normal scoped bean in Quarkus - they are generated automatically. Also, if there’s only one constructor there is no need for @Inject.

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

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

5.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 に設定します。

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

Quarkus は最初に、依存関係ツリーのルートを形成する、いわゆる unremovable Bean を識別します。良い例は、JAX-RS リソースクラスまたは @Scheduled メソッドを宣言する Bean です。

unremovable Bean:

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

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

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

未使用の Bean:

  • unremovable ではなく、

  • 依存関係ツリーのどの注入ポイントにも注入する資格がなく、かつ

  • 任意の注入ポイントへの注入の対象となるプロデューサーを宣言せず、

  • javax.enterprise.inject.Instance または javax.inject.Provider の注入ポイントに注入することが出来ないこと

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

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

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

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

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

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

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

Table 2. 値の例

バリューe

説明

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 を生成することで、可能性のある誤検出を排除することができます。

5.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
    }
}

The idea is that the extension autoconfigures things for the user, eliminating a lot of boilerplate - we can just @Inject a Tracer wherever it is needed. Now imagine that in our application we would like to utilize the configured Tracer, but we need to customize it a little, for example by providing a custom Reporter. The only thing that would be needed in our application would be something like the following:

@Dependent
public class CustomTracerConfiguration {

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

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

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

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

Imagine for instance that an application contains a bean named Tracer, which needs to do nothing when in tests or in dev mode, but works in its normal capacity for the production artifact. An elegant way to create such beans is the following:

@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();
    }
}

If instead, it is required that the Tracer bean also works in dev mode and only default to doing nothing for tests, then @UnlessBuildProfile would be ideal. The code would look like:

@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 解決には影響を及ぼしません。

5.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 and @UnlessBuildProperty are repeatable annotations, i.e. a bean will only be enabled if all the conditions defined by these annotations are satisfied.

代わりに、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 の解決に全く影響しません。

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

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

@javax.annotation.Priority の欠点は、@Target({ TYPE, PARAMETER }) であるため、プロデューサーメソッドおよびフィールドには使用できません。この問題は、Common Annotations 2.1 で修正する必要があります。Quarkus がこのバージョンの jakarta.annotation-api にアップグレードするまでは、代わりに @io.quarkus.arc.Priority を使用することをお勧めします。

ただし、統一された設定を使用して、アプリケーションの代替を選択することもできます。quarkus.arc.selected-alternatives プロパティーは、代替 Bean を照合するために使用される文字列値のリストを受け入れます。一致する値がある場合は、関連する Bean に Integer#MAX_VALUE の優先順位が使用されます。@Priority または @AlternativePriority で宣言された優先度は上書きされます。

Table 3. 値の例

バリューe

説明

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

5.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);
  }
}

5.11. Interception of Static Methods

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

class Services {

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

5.11.1. 制約事項

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

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

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

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

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

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

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

5.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 が発生します。

5.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 {
  // ...
}

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

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

In certain situations, it is practical to obtain a bean instance programmatically via an injected javax.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() を介してキャッシュされた値をクリアすることも可能です。この場合、javax.enterprise.inject.Instance の代わりに Quarkus 固有の io.quarkus.arc.InjectableInstance を注入する必要があります。

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

javax.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());
    }
 }

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

CDI では、java.lang.Iterable を実装する javax.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 であり、追加の修飾子は宣言されていません。
リストは、io.quarkus.arc.InjectableBean#getPriority() で定義されている優先度で並べ替えられます。優先度の高い方が優先されます。一般に、@javax.annotation.Priority および @io.quarkus.arc.Priority アノテーションを使用して、クラス Bean、プロデューサーメソッド、またはプロデューサーフィールドに優先度を割り当てることができます。

インジェクションポイントが @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 インスタンスハンドルのリストを取得することもできます。

5.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) にのみ影響します。

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

非同期オブザーバーによって例外が出力された場合、fireAsync() メソッドによって返される CompletionStage は例外的に完了し、イベントプロデューサーが適切に反応できるようにします。ただし、イベントプロデューサーがこれを問題にしない場合、例外は黙示的に無視されます。したがって、Quarkus はデフォルトでエラーメッセージをログに記録します。カスタムの AsyncObserverExceptionHandler を実装することも可能です。このインターフェイスを実装する Bean は、@javax.inject.Singleton または @javax.enterprise.context.ApplicationScoped である必要があります。

NoopAsyncObserverExceptionHandler
@Singleton
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {

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

}

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

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

7. 開発モード

開発モードでは、JSON 形式で基本的なデバッグ情報を提供するために、2 つの特別なエンドポイントが自動的に登録されます。

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

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

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

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

これらのエンドポイントは開発モードでのみ使用できます。つまり mvn quarkus:dev (または ./gradlew quarkusDev) 経由でアプリケーションを実行したときです。

8. ArC 設定リファレンス

ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能

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 javax.enterprise.inject.Instance injection point

Environment variable: QUARKUS_ARC_REMOVE_UNUSED_BEANS

string

all

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

Environment variable: QUARKUS_ARC_AUTO_INJECT_FIELDS

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

boolean

true

If set to true, 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

boolean

false

The default naming strategy for ConfigProperties.NamingStrategy. The allowed values are determined by that enum

Environment variable: QUARKUS_ARC_CONFIG_PROPERTIES_DEFAULT_NAMING_STRATEGY

from-config, verbatim, kebab-case

kebab-case

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 javax.annotation.Priority or io.quarkus.arc.AlternativePriority is overridden.

Environment variable: QUARKUS_ARC_SELECTED_ALTERNATIVES

list of string

If set to true then javax.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

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

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

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

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 @javax.ejb.Singleton which is often confused with @javax.inject.Singleton. As a result a component annotated with @javax.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

boolean

true

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

Environment variable: QUARKUS_ARC_DEV_MODE_MONITORING_ENABLED

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

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

list of string

Artifacts that should be excluded from discovery

タイプ

デフォルト

The maven groupId of the artifact.

Environment variable: QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__GROUP_ID

string

required

The maven artifactId of the artifact.

Environment variable: QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__ARTIFACT_ID

string

required

The maven classifier of the artifact.

Environment variable: QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__CLASSIFIER

string