コンテキストと依存性インジェクション
Quarkus DI solution (also called ArC) is based on the Jakarta Contexts and Dependency Injection 4.1 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. Most of the existing CDI code should work just fine but there are some small differences which follow from the Quarkus architecture and goals.
If you’re new to CDI we recommend you to read the Introduction to CDI first. |
The CDI integration guide has more detail on common CDI-related integration use cases, and example code for solutions. |
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.properties
のquarkus.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 に対して自動的にスキャンされます。インデックスを生成するには、次のプラグインをビルドファイルに追加するだけです。
依存関係を変更できなくても、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
依存関係が確実にインデックス化されるようにします。
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
プロパティーは、除外すべきクラスに一致するために使用される文字列値のリストを受け入れます。
値 |
Description |
|
クラスの完全修飾名と一致させる |
|
|
|
パッケージが |
|
クラスのシンプルな名前に一致する |
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
記述子を含んでいる場合です。
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
-
The method invokers implementation supports asynchronous methods.
The following methods are considered asynchronous and @Dependent
instances are only destroyed when the asynchronous action completes:
-
methods that declare a return type of
CompletionStage
,Uni
, orMulti
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.requestContext
の TRACE
レベルを設定し、後でログ出力の分析を試みることができます。
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-beans
を none
または false
に設定することで無効にすることができます。Quarkus では、アプリケーションの Bean は未使用かどうかにかかわらず削除されず、アプリケーション以外のクラスは通常通りに最適化が行われるという中間モードも提供されています。このモードを使用するには、quarkus.arc.remove-unused-beans
を fwk
または framework
に設定します。
4.5.1. 何が削除されましたか?
Quarkusはまず、依存関係ツリーのルートを形成する、いわゆる 削除不可能な Beanを特定します。良い例は、Jakarta RESTリソースクラスや、 @Scheduled
メソッドを宣言している Bean です。
unremovable Bean:
-
is excluded from removal, or
-
@Named
を通して指定された名前を持っているか、 -
オブザーバーメソッドを宣言している場合。
unused Bean:
-
unremovable ではなく、
-
is not eligible for injection to any injection point in the dependency tree of unremovable beans, and
-
依存関係ツリー内の挿入ポイントへの挿入に適格なプロデューサーを宣言していません。
-
is not eligible for injection into any
jakarta.enterprise.inject.Instance
orjakarta.inject.Provider
injection point, and -
is not eligible for injection into any
@Inject @All List<>
injection point.
未使用のインターセプターとデコレーターは、どの Bean とも関連付けられていません。
開発モードを使用している場合(例:
|
4.5.2. 誤検知を排除する方法
ユーザーは、コンテナーに @io.quarkus.arc.Unremovable
をアノテーションすることで、(上で指定したルールをすべて満たしていても) 特定の Bean を削除しないように指示することができます。このアノテーションは、クラス、producer メソッド、producer フィールドに置くことができます。
これは常に可能ではないので、application.properties
を通して同じことを実現するオプションがあります。quarkus.arc.unremovable-types
プロパティーは、Bean の名前やパッケージに基づいて一致させるための文字列値のリストを受け付けます。
値 |
Description |
|
bean クラスの完全修飾名と一致させる |
|
Bean クラスのパッケージが |
|
Bean クラスのパッケージが |
|
Bean クラスのシンプルな名前に一致します。 |
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
}
}
アイデアは、エクステンションがユーザーのために自動設定を行い、多くのボイラープレートを排除するということです。必要な場所であれば、@Inject
を Tracer
にすることができます。私たちのアプリケーションで、設定された 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.
値 |
Description |
|
Bean クラス、または producer を宣言する Bean の Bean クラスの完全修飾名に一致します。 |
|
Bean クラスのパッケージが |
|
Bean クラスのパッケージが |
|
Bean クラス、または producer を宣言する Bean の Bean クラスの単純名に一致します。 |
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-classes
を true
(デフォルト値) に設定すると、これらの制限を抑制することができます。
4.13. コンテナー管理型の並行処理
CDI Bean の標準的な同時実行制御メカニズムはありません。それにもかかわらず、Bean インスタンスは、複数のスレッドから同時に共有およびアクセスすることができます。その場合は、スレッドセーフでなければなりません。標準の Java コンストラクト (volatile
、synchronized
、ReadWriteLock
など) を使用するか、コンテナーに同時アクセスを制御させることができます。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() {
...
}
}
この例では、doSomething
と doSomethingElse
の両方が架空のロギングインターセプターによってインターセプトされます。さらに、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
}
}
4.20. Intercepted self-invocation
Quarkus supports what is known as intercepted self-invocation or just self-interception - a scenario where CDI bean invokes its own intercepted method from within another method while triggering any associated interceptors. This is a non-standard feature as CDI specification doesn’t define whether self-interception should work or not.
Suppose we have a CDI bean with two methods, one of which has the @Transactional
interceptor binding associated with it:
@ApplicationScoped
public class MyService {
@Transactional (1)
void doSomething() {
// some application logic
}
void doSomethingElse() {
doSomething();(2)
}
}
1 | One or more interceptor bindings; @Transactional is just an example. |
2 | Non-intercepted method invoking another method from the same bean that has associated binding(s); this will trigger interception. |
In the above example, any code calling the doSomething()
method triggers interception - in this case, the method becomes transactional.
This is regardless of whether the invocation originated directly from the MyService
bean (such as MyService#doSomethingElse
) or from some other bean.
4.21. Intercepting Producer Methods and Synthetic Beans
By default, interception is only supported for managed beans (also known as class-based beans).
To support interception of producer methods and synthetic beans, the CDI specification includes an InterceptionFactory
, which is a runtime oriented concept and therefore cannot be supported in Quarkus.
Instead, Quarkus has its own API: InterceptionProxy
and @BindingsSource
.
The InterceptionProxy
is very similar to InterceptionFactory
: it creates a proxy that applies @AroundInvoke
interceptors before forwarding the method call to the target instance.
The @BindingsSource
annotation allows setting interceptor bindings in case the intercepted class is external and cannot be changed.
import io.quarkus.arc.InterceptionProxy;
@ApplicationScoped
class MyProducer {
@Produces
MyClass produce(InterceptionProxy<MyClass> proxy) { (1)
return proxy.create(new MyClass()); (2)
}
}
1 | Declares an injection point of type InterceptionProxy<MyClass> .
This means that at build time, a subclass of MyClass is generated that does the interception and forwarding.
Note that the type argument must be identical to the return type of the producer method. |
2 | Creates an instance of the interception proxy for the given instance of MyClass .
The method calls will be forwarded to this target instance after all interceptors run. |
In this example, interceptor bindings are read from the MyClass
class.
Note that InterceptionProxy only supports @AroundInvoke interceptors declared on interceptor classes.
Other kinds of interception, as well as @AroundInvoke interceptors declared on the target class and its superclasses, are not supported.
|
The intercepted class should be proxyable and therefore should not be final
, should not have non-private final
methods, and should have a non-private zero-parameter constructor.
If it isn’t, a bytecode transformation will attempt to fix it if enabled, but note that adding a zero-parameter constructor is not always possible.
It is often the case that the produced classes come from external libraries and don’t contain interceptor binding annotations at all.
To support such cases, the @BindingsSource
annotation may be declared on the InterceptionProxy
parameter:
import io.quarkus.arc.BindingsSource;
import io.quarkus.arc.InterceptionProxy;
abstract class MyClassBindings { (1)
@MyInterceptorBinding
abstract String doSomething();
}
@ApplicationScoped
class MyProducer {
@Produces
MyClass produce(@BindingsSource(MyClassBindings.class) InterceptionProxy<MyClass> proxy) { (2)
return proxy.create(new MyClass());
}
}
1 | A class that mirrors the MyClass structure and contains interceptor bindings. |
2 | The @BindingsSource annotation says that interceptor bindings for MyClass should be read from MyClassBindings . |
The concept of bindings source is a build-time friendly equivalent of InterceptionFactory.configure()
.
Producer method interception and synthetic bean interception only works for instance methods. 静的メソッドのインターセプション is not supported for producer methods and synthetic beans. |
4.21.1. Declaring @BindingsSource
The @BindingsSource
annotation specifies a class that mirrors the structure of the intercepted class.
Interceptor bindings are then read from that class and treated as if they were declared on the intercepted class.
Specifically: class-level interceptor bindings declared on the bindings source class are treated as class-level bindings of the intercepted class.
Method-level interceptor bindings declared on the bindings source class are treated as method-level bindings of a method with the same name, return type, parameter types and static
flag of the intercepted class.
It is common to make the bindings source class and methods abstract
so that you don’t have to write method bodies:
abstract class MyClassBindings {
@MyInterceptorBinding
abstract String doSomething();
}
Since this class is never instantiated and its method are never invoked, this is okay, but it’s also possible to create a non-abstract
class:
class MyClassBindings {
@MyInterceptorBinding
String doSomething() {
return null; (1)
}
}
1 | The method body does not matter. |
Note that for generic classes, the type variable names must also be identical. For example, for the following class:
class MyClass<T> {
T doSomething() {
...
}
void doSomethingElse(T param) {
...
}
}
the bindings source class must also use T
as the name of the type variable:
abstract class MyClassBindings<T> {
@MyInterceptorBinding
abstract T doSomething();
}
You don’t need to declare methods that are not annotated simply because they exist on the intercepted class. If you want to add method-level bindings to a subset of methods, you only have to declare the methods that are supposed to have an interceptor binding. If you only want to add class-level bindings, you don’t have to declare any methods at all.
These annotations can be present on a bindings source class:
-
interceptor bindings: on the class and on the methods
-
stereotypes: on the class
-
@NoClassInterceptors
: on the methods
Any other annotation present on a bindings source class is ignored.
4.21.2. Synthetic Beans
Using InterceptionProxy
in synthetic beans is similar.
First, you have to declare that your synthetic bean injects the InterceptionProxy
:
public void register(RegistrationContext context) {
context.configure(MyClass.class)
.types(MyClass.class)
.injectInterceptionProxy() (1)
.creator(MyClassCreator.class)
.done();
}
1 | Once again, this means that at build time, a subclass of MyClass is generated that does the interception and forwarding. |
Second, you have to obtain the InterceptionProxy
from the SyntheticCreationalContext
in the BeanCreator
and use it:
public MyClass create(SyntheticCreationalContext<MyClass> context) {
InterceptionProxy<MyClass> proxy = context.getInterceptionProxy(); (1)
return proxy.create(new MyClass());
}
1 | Obtains the InterceptionProxy for MyClass , as declared above.
It would also be possible to use the getInjectedReference() method, passing a TypeLiteral , but getInterceptionProxy() is easier. |
There’s also an equivalent of @BindingsSource
.
The injectInterceptionProxy()
method has an overload with a parameter:
public void register(RegistrationContext context) {
context.configure(MyClass.class)
.types(MyClass.class)
.injectInterceptionProxy(MyClassBindings.class) (1)
.creator(MyClassCreator.class)
.done();
}
1 | The argument is the bindings source class. |
5. Pitfalls with Reactive Programming
CDI is a purely synchronous framework. Its notion of asynchrony is very limited and based solely on thread pools and thread offloading. Therefore, there is a number of pitfalls when using CDI together with reactive programming.
5.1. Detecting When Blocking Is Allowed
The io.quarkus.runtime.BlockingOperationControl#isBlockingAllowed()
method can be used to detect whether blocking is allowed on the current thread.
When it is not, and you need to perform a blocking operation, you have to offload it to another thread.
The easiest way is to use the Vertx.executeBlocking()
method:
import io.quarkus.runtime.BlockingOperationControl;
@ApplicationScoped
public class MyBean {
@Inject
Vertx vertx;
@PostConstruct
void init() {
if (BlockingOperationControl.isBlockingAllowed()) {
somethingThatBlocks();
} else {
vertx.executeBlocking(() -> {
somethingThatBlocks();
return null;
});
}
}
void somethingThatBlocks() {
// use the file system or JDBC, call a REST service, etc.
Thread.sleep(5000);
}
}
6. ビルド時エクステンション
Quarkus は、インスタント起動と低メモリフットプリントを提供するために、ビルド時の最適化を取り入れています。このアプローチの欠点は、CDI ポータブル・エクステンションをサポートできないことです。それにもかかわらず、ほとんどの機能は、Quarkus エクステンション を使用して行うことができます。詳細は、integration guide を参照してください。
7. 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 のリストを返します。-
クエリーパラメーターを使用して出力をフィルタリングすることができます。
-
scope
- 指定した値で終わるスコープを持つ Bean を含みます (つまりhttp://localhost:8080/q/arc/beans?scope=ApplicationScoped
)。 -
beanClass
- 与えられた値で始まる Bean クラスを持つ Bean を含みます (つまりhttp://localhost:8080/q/arc/beans?beanClass=org.acme.Foo
)。 -
kind
- 指定された種類の Bean (CLASS
、PRODUCER_FIELD
、PRODUCER_METHOD
、INTERCEPTOR
、またはSYNTHETIC
) を含みます (つまりhttp://localhost:8080/q/arc/beans?kind=PRODUCER_METHOD
)。
-
-
-
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 ).
|
8. ストリクトモード
デフォルトでは、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仕様の上に追加される検証のセットと無効にされる改良のセット)は、将来変化する可能性があります。
9. ArC Configuration Reference
ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能
Configuration property |
型 |
デフォルト |
||
---|---|---|---|---|
An unused bean:
Environment variable: Show more |
string |
|
||
If set to true Environment variable: Show more |
boolean |
|
||
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:
Environment variable: Show more |
boolean |
|
||
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: Show more |
boolean |
|
||
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 Environment variable: Show more |
boolean |
|
||
The list of selected alternatives for an application. An element value can be:
Environment variable: Show more |
list of string |
|||
If set to true then Environment variable: Show more |
boolean |
|
||
The list of types that should be excluded from discovery. An element value can be:
Environment variable: 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 An element value can be:
Environment variable: Show more |
list of string |
|||
型 |
デフォルト |
|||
The maven groupId of the artifact. Environment variable: Show more |
string |
required |
||
The maven artifactId of the artifact (optional). Environment variable: Show more |
string |
|||
The maven classifier of the artifact (optional). Environment variable: Show more |
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: Show more |
boolean |
|
||
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 Environment variable: Show more |
boolean |
|
||
If set to 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 Environment variable: Show more |
boolean |
|
||
If set to true then the container monitors business method invocations and fired events during the development mode.
Environment variable: Show more |
boolean |
|
||
If set to true then the dependency graphs are generated and available in the Dev UI. Environment variable: Show more |
boolean |
|
||
If set to true then disable Environment variable: Show more |
boolean |
|
||
The list of packages that will not be checked for split package issues. A package string representation can be:
Environment variable: 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 Environment variable: Show more |
boolean |
|