コンテキストと依存性インジェクション
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.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 プロパティーは、除外すべきクラスに一致するために使用される文字列値のリストを受け入れます。
値 |
説明 |
|
クラスの完全修飾名と一致させる |
|
|
|
パッケージが |
|
クラスのシンプルな名前に一致する |
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 は GraalVM を使用してネイティブ実行可能ファイルを構築します。 GraalVM の制限の 1 つは、リフレクション の使用量です。 リフレクション操作はサポートされていますが、関連するすべてのメンバーを明示的にリフレクションに登録する必要があります。 これらの登録により、ネイティブ実行可能ファイルが大きくなります。
また、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. サポートされている機能と制限
CDI Lite 仕様は完全にサポートされています。 CDI Full の次の機能もサポートされています。
-
デコレーター
-
Eventなどのビルトイン Bean の装飾はサポートされていません
-
-
BeanManager-
BeanContainerメソッドに加えて、getInjectableReference()、resolveDecorators()のメソッドがサポートされています。
-
-
@SessionScoped-
Undertow エクステンションのみ。詳細は こちら を参照してください。
-
| これらの追加機能は、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:
-
エクステンションによる削除から除外されている、または
-
@Namedを通して指定された名前を持っているか、 -
オブザーバーメソッドを宣言している場合。
_未使用の_Bean:
-
unremovable ではなく、
-
依存関係ツリーのどの注入ポイントにも注入することが出来ないこと
-
依存関係ツリーのどの注入ポイントにも注入可能なプロデューサーを宣言していないこと
-
is not eligible for injection into any
jakarta.enterprise.inject.Instanceorjakarta.inject.Providerinjection point.
未使用のインターセプターとデコレーターは、どの Bean とも関連付けられていません。
|
開発モードを使用している場合(例:
|
4.5.2. 誤検知を排除する方法
ユーザーは、コンテナーに @io.quarkus.arc.Unremovable をアノテーションすることで、(上で指定したルールをすべて満たしていても) 特定の Bean を削除しないように指示することができます。このアノテーションは、クラス、producer メソッド、producer フィールドに置くことができます。
これは常に可能ではないので、 application.properties を通して同じことを実現するオプションがあります。 quarkus.arc.unremovable-types プロパティーは、Bean の名前やパッケージに基づいて一致させるための文字列値のリストを受け付けます。
値 |
説明 |
|
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 が提供されている場合、バックオフ中にデフォルトを提供することができます。
デフォルトの Bean はオプションで @jakarta.annotation.Priority を宣言できます。
優先度が定義されていない場合は、 @Priority (0) が想定されます。
優先度の値は、Bean の順序付けと、複数の一致するデフォルト Bean の曖昧さを解消するための型安全な解決に使用されます。
@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 解決には影響を及ぼしません。
|
ステレオタイプで @IfBuildProfile と @UnlessBuildProfile を使用することもできます。
|
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 の解決に全く影響しません。
|
ステレオタイプで @IfBuildProperty と @UnlessBuildProperty を使用することもできます。
|
4.9. 選択された代替の宣言
CDI では、代替の Bean は、 @Priority を使用してアプリケーションに対してグローバルに選択することも、 beans.xml 記述子を使用した Bean アーカイブ用に選択することもできます。Quarkus には単純化された Bean 検出があり、 beans.xml の内容は無視されます。
ただし、統合設定を使用して、アプリケーションの代替を選択することもできます。
quarkus.arc.selected-alternatives プロパティーは、代替 Bean を照合するために使用される文字列値のリストを受け入れます。
一致する値がある場合は、関連する Bean に Integer#MAX_VALUE の優先順位が使用されます。
@Priority で宣言された優先度またはステレオタイプで継承された優先度は上書きされます。
値 |
説明 |
|
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. プログラムによるルックアップの結果をキャッシュする
特定の状況では、注入された jakarta.enterprise.inject.Instance と Instance.get() を介してプログラムで Bean インスタンスを取得することが実用的です。
ただし、仕様によれば、 get() メソッドは一致する Bean を識別し、コンテキスト参照を取得する必要があります。
その結果、 get() の各呼び出しから @Dependent Bean の新しいインスタンスが返されます。
さらに、このインスタンスは、注入された Instance の依存オブジェクトです。
この動作は明確に定義されていますが、予期しないエラーやメモリーリークが発生する可能性があります。
そのため、Quarkus には io.quarkus.arc.WithCaching アノテーションが付属しています。
このアノテーションが付けられた挿入された Instance は、 Instance#get() 操作の結果をキャッシュします。
結果は最初の呼び出しで計算され、 @Dependent Bean の場合でも、後続のすべての呼び出しに対して同じ値が返されます。
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 であり、追加の修飾子は宣言されていません。 |
リストは、 io.quarkus.arc.InjectableBean#getPriority() で定義された優先度でソートされます。優先度の高いものが最初に来ます。一般に、 @jakarta.annotation.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 インスタンスハンドルのリストを取得することもできます。
|
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. 非同期オブザーバーメソッドによって出力される例外
非同期オブザーバーによって例外が発生した場合、 fireAsync() メソッドによって返される CompletionStage は例外的に完了し、イベントプロデューサーが適切に対応できるようになります。
ただし、イベントプロデューサーが気にしない場合は、例外は黙って無視されます。
したがって、Quarkus はデフォルトでエラーメッセージをログに記録します。
カスタムの AsyncObserverExceptionHandler を実装することも可能です。
このインターフェイスを実装する Bean は、 @jakarta.inject.Singleton または @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. 開発モード
開発モードでは、JSON 形式で基本的なデバッグ情報を提供するために、2 つの特別なエンドポイントが自動的に登録されます。
-
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- すべてのオブザーバーメソッドのリストを返します。
これらのエンドポイントは、開発モード、つまり mvn quarkus:dev (または ./gradlew quarkusDev) 経由でアプリケーションを実行する場合にのみ使用できます。
|
7. ストリクトモード
デフォルトでは、ArC は CDI 仕様で要求される全てのバリデーションを実行するわけではありません。また、多くの方法でCDIの使い勝手を向上させますが、その中には仕様に直接反するものもあります。
CDI Lite TCK に合格させるために、ArC には strict モードもあります。 このモードでは、追加の検証が有効になり、仕様と競合する特定の改善機能が無効になります。
ストリクトモードを有効にするには、次のように設定します:
quarkus.arc.strict-compatibility=true
その他にも、仕様の互換性に影響を与える機能があります:
仕様に近い動作を得るには、これらの機能を無効にする必要があります。
アプリケーションは、CDIをより便利に使えるように、デフォルトの非厳密モードを使用することが推奨されます。厳密モードの「厳密さ」(CDI仕様の上に追加される検証のセットと無効にされる改良のセット)は、将来変化する可能性があります。
8. ArC Configuration Reference
ビルド時に固定される設定プロパティ - 他のすべての設定プロパティは実行時にオーバーライド可能
タイプ |
デフォルト |
|||
|---|---|---|---|---|
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 |
文字列のリスト |
|||
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 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 |
文字列のリスト |
|||
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 |
文字列のリスト |
|||
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 |
|
||
タイプ |
デフォルト |
|||
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 |