CDI 統合ガイド
Quarkus の CDI コンテナーである ArC は、ビルド時にブートストラップされます。コンテナーと統合するには、Quarkus 固有の extension API の他に、 CDI Build Compatible Extensions を使用できます。CDI Portable Extensions はサポートされておらず、サポートすることはできません。このガイドでは、Quarkus 固有の extension API に焦点を当てます。
コンテナーは複数のフェーズでブートストラップされます。高いレベルから見ると、これらのフェーズは次のようになります。
-
初期化
-
Bean ディスカバリ
-
合成コンポーネントの登録
-
バリデーション
初期化 フェーズでは、準備作業が実行され、カスタムコンテキストが登録されます。次に Bean ディスカバリ は、コンテナーがすべてのアプリケーションクラスを分析し、Bean を識別し、提供されたメタデータに基づいてそれらすべてを結合するプロセスです。その後、エクステンションは 合成コンポーネント を登録できます。これらのコンポーネントの属性はエクステンションによって完全に制御されます。つまり、既存のクラスから派生したものではありません。最後に、デプロイメントが検証されます。たとえば、コンテナーはアプリケーション内のすべての注入ポイントを検証し、指定された必要な型と修飾子を満たす Bean がない場合、ビルドを失敗させます。
追加のロギングを有効にすることで、ブートストラップに関するより多くの情報を表示できます。Maven ビルドを -X または --debug で実行し、 io.quarkus.arc を含む行を grep するだけです。開発モード では、 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG を使用でき、2つの特別なエンドポイントも自動的に登録され、JSON 形式でいくつかの基本的なデバッグ情報を提供します。
|
Quarkus のビルドステップは、さまざまなビルドアイテムを生成および消費し、各フェーズにフックできます。以下のセクションでは、すべての関連するビルドアイテムと一般的なシナリオについて説明します。
1. メタデータソース
クラスとアノテーションは、Bean レベルのメタデータの主要なソースです。初期メタデータは、Bean アーカイブインデックス から読み込まれます。これは、Bean ディスカバリー 時にさまざまなソースから構築される、不変の Jandex インデックス です。しかし、エクステンションは、ブートストラップの特定の段階でメタデータを追加、削除、または変換できます。さらに、エクステンションは 合成コンポーネント を登録することもできます。これは、Quarkus に CDI コンポーネントを統合する際に認識すべき重要な側面です。
このようにして、エクステンションは、そうでなければ無視されていたクラスを Bean に変換したり、その逆を行うことができます。たとえば、 @Scheduled メソッドを宣言するクラスは、Bean 定義アノテーションでアノテーションされておらず、通常は無視される場合でも、常に Bean として登録されます。
2. ユースケース - クラスが Bean として認識されない
UnsatisfiedResolutionException は、タイプセーフな解決 中に問題が発生したことを示します。クラスパス上に注入に適しているように見えるクラスがあっても、注入ポイントが満たされない場合があります。クラスが認識されない理由はいくつかあり、それを修正する方法もいくつかあります。最初のステップでは、理由 を特定する必要があります。
2.1. 理由 1: クラス未検出
Quarkus には、簡素化されたディスカバリ があります。クラスがアプリケーションインデックスの一部ではない場合があります。たとえば、Quarkus エクステンションの ランタイムモジュール のクラスは自動的にインデックス化されません。
解決策: AdditionalBeanBuildItem を使用します。このビルドアイテムは、ディスカバリ中に分析する 1 つ以上の追加クラスを指定するために使用できます。追加の Bean クラスは、コンテナーによって処理されるアプリケーションインデックスに透過的に追加されます。
cdi-reference および cdi-reference で説明されているように、 @IfBuildProfile 、 @UnlessBuildProfile 、 @IfBuildProperty 、および @UnlessBuildProperty アノテーションを使用して、追加の Bean を条件付きで有効/無効にすることはできません。エクステンションは、設定または現在のプロファイルを検査し、本当に必要な場合にのみ AdditionalBeanBuildItem を生成する必要があります。
|
AdditionalBeanBuildItem の例@BuildStep
AdditionalBeanBuildItem additionalBeans() {
return new AdditionalBeanBuildItem(SmallRyeHealthReporter.class, HealthServlet.class); (1)
}
| 1 | AdditionalBeanBuildItem.Builder は、より複雑なユースケースに使用できます。 |
AdditionalBeanBuildItem を介して追加された Bean クラスは、デフォルトで 削除可能 です。コンテナーがそれらを 未使用 とみなした場合、それらは単に無視されます。しかし、AdditionalBeanBuildItem.Builder.setUnremovable() メソッドを使用することで、このビルドアイテムを介して登録された Bean クラスを絶対に削除しないようにコンテナーに指示できます。詳細は、未使用の Bean の削除 および 理由 3: クラスは検出され、Bean 定義アノテーションがあるが削除済み も参照してください。
AdditionalBeanBuildItem.Builder#setDefaultScope() を介してデフォルトスコープを設定することも可能です。デフォルトスコープは、Bean クラスにスコープが宣言されていない場合にのみ使用されます。
デフォルトスコープが指定されていない場合は、 @Dependent 擬似スコープが使用されます。
|
2.2. 理由 2: クラスは検出されたが、Bean 定義アノテーションなし
Quarkus では、アプリケーションは Bean 検出モード annotated でアノテーションされた単一の Bean アーカイブで表現されます。したがって、 Bean 定義アノテーション を持たない Bean クラスは無視されます。Bean 定義アノテーションはクラスレベルで宣言され、スコープ、ステレオタイプ、および @Interceptor が含まれます。
解決策 1: AutoAddScopeBuildItem を使用します。このビルドアイテムは、特定の条件を満たすクラスにスコープを追加するために使用できます。
AutoAddScopeBuildItem の例@BuildStep
AutoAddScopeBuildItem autoAddScope() {
return AutoAddScopeBuildItem.builder().containsAnnotations(SCHEDULED_NAME, SCHEDULES_NAME) (1)
.defaultScope(BuiltinScope.SINGLETON) (2)
.build();
}
| 1 | @Scheduled でアノテーションされたすべてのクラスを検索します。 |
| 2 | デフォルトスコープとして @Singleton を追加します。既にスコープでアノテーションされているクラスは自動的にスキップされます。 |
解決策 2: 特定のアノテーションが付けられたクラスを処理する必要がある場合は、 BeanDefiningAnnotationBuildItem を介して Bean 定義アノテーションのセットを拡張できます。
BeanDefiningAnnotationBuildItem の例@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
return new BeanDefiningAnnotationBuildItem(Annotations.GRAPHQL_API); (1)
}
| 1 | Bean 定義アノテーションのセットに org.eclipse.microprofile.graphql.GraphQLApi を追加します。 |
BeanDefiningAnnotationBuildItem を介して追加された Bean クラスは、デフォルトでは 削除不可 です。つまり、結果の Bean は未使用とみなされても削除しないでください。ただし、デフォルトの動作は変更できます。詳細は、未使用の Bean の削除 および 理由 3: クラスは検出され、Bean 定義アノテーションがあるが削除済み も参照してください。
デフォルトスコープを指定することも可能です。デフォルトスコープは、Bean クラスにスコープが宣言されていない場合にのみ使用されます。
デフォルトスコープが指定されていない場合は、 @Dependent 擬似スコープが使用されます。
|
2.3. 理由 3: クラスは検出され、Bean 定義アノテーションがあるが削除済み
コンテナーは、デフォルトでビルド中に すべての未使用 Bean を削除 しようとします。この最適化により、フレームワークレベルでのデッドコード削除 が可能になります。いくつかの特殊なケースでは、未使用の Bean を正しく識別することはできません。特に、Quarkus は CDI.current() 静的メソッドの使用をまだ検出できません。エクステンションは UnremovableBeanBuildItem を生成することで、誤検出の可能性を排除できます。
UnremovableBeanBuildItem の例@BuildStep
UnremovableBeanBuildItem unremovableBeans() {
return UnremovableBeanBuildItem.targetWithAnnotation(STARTUP_NAME); (1)
}
| 1 | @Startup でアノテーションされたすべてのクラスを削除不可にします。 |
3. ユースケース - アノテーションが修飾子またはインターセプターバインディングとして認識されない
アノテーションクラスがアプリケーションインデックスに含まれていない可能性があります。たとえば、Quarkus エクステンションの ランタイムモジュール のクラスは自動的にインデックス化されません。
解決策: 理由 1: クラス未検出 で説明されているとおりに AdditionalBeanBuildItem を使用します。
4. ユースケース - アノテーションのメタデータを変換する必要がある
場合によっては、アノテーションメタデータを変更できると便利です。Quarkus は、 jakarta.enterprise.inject.spi.ProcessAnnotatedType および jakarta.enterprise.inject.build.compatible.spi.Enhancement の強力な代替手段を提供します。AnnotationsTransformerBuildItem を使用すると、Bean クラスに存在するアノテーションをオーバーライドできます。
| アノテーショントランスフォーマーは、Bean ディスカバリが始まる 前に 生成されなければならないことに注意してください。 |
たとえば、特定の Bean クラスにインターセプターバインディングを追加したい場合があります。便利なビルダー API を使用して変換インスタンスを作成できます。
@BuildStep
AnnotationsTransformerBuildItem transform() {
return new AnnotationsTransformerBuildItem(AnnotationTransformation.forClasses() (1)
.whenClass(DotName.createSimple("org.acme.Bar")) (2)
.transform(t -> t.add(MyInterceptorBinding.class))); (3)
}
| 1 | トランスフォーマーはクラスにのみ適用されます。 |
| 2 | クラスが org.acme.Bar の場合にのみ変換を適用します。 |
| 3 | @MyInterceptorBinding アノテーションを追加します。 |
上記の例は、匿名クラスを使用して書き直すことができます。
AnnotationsTransformerBuildItem の例@BuildStep
AnnotationsTransformerBuildItem transform() {
return new AnnotationsTransformerBuildItem(new AnnotationTransformation() {
public boolean supports(AnnotationTarget.Kind kind) {
return kind == AnnotationTarget.Kind.CLASS; (1)
}
public void apply(TransformationContext context) {
if (context.declaration().asClass().name().toString().equals("org.acme.Bar")) {
context.add(MyInterceptorBinding.class); (2)
}
}
});
}
| 1 | トランスフォーマーはクラスにのみ適用されます。 |
| 2 | クラス名が org.acme.Bar と同じ場合は、 @MyInterceptorBinding を追加します。 |
ArC の以前の AnnotationsTransformer API も引き続きサポートされますが、Jandex の新しい AnnotationTransformation API が推奨されます。
|
ビルドステップでは、 TransformedAnnotationsBuildItem を介して、特定のアノテーションターゲットの変換済みアノテーションをクエリーできます。
TransformedAnnotationsBuildItem の例@BuildStep
void queryAnnotations(TransformedAnnotationsBuildItem transformedAnnotations,
BuildProducer<MyBuildItem> myBuildItem) {
ClassInfo myClazz = ...;
if (transformedAnnotations.getAnnotations(myClazz).isEmpty()) { (1)
myBuildItem.produce(new MyBuildItem());
}
}
| 1 | TransformedAnnotationsBuildItem.getAnnotations() は、変換された可能性のあるアノテーションのセットを返します。 |
| 変換に特化した他のビルドアイテム (ユースケース - 追加のインターセプターバインディング および ユースケース - インジェクションポイントの変換) もあります。 |
4.1. Annotation Transformer の Trace Logging を有効にする方法
io.quarkus.arc.processor のカテゴリーに TRACE レベルを設定し、その後、ログ出力の解析を試みてください。
application.properties の例quarkus.log.category."io.quarkus.arc.processor".min-level=TRACE (1)
quarkus.log.category."io.quarkus.arc.processor".level=TRACE
| 1 | また、関連するカテゴリーの最小ログレベルを調整する必要があります。 |
5. ユースケース - Bean、オブザーバー、インジェクションポイントの検査
5.1. 解決策1: BeanDiscoveryFinishedBuildItem
BeanDiscoveryFinishedBuildItem のコンシューマーは、アプリケーションに登録されているすべてのクラスベースの Bean、オブザーバー、およびインジェクションポイントを簡単に検査できます。しかし、合成 Bean とオブザーバーは、このビルドアイテムが合成コンポーネントが登録される 前 に生成されるため、含まれません。
さらに、 BeanDiscoveryFinishedBuildItem#getBeanResolver() から返された Bean リゾルバーを使用して、タイプセーフな解決ルールを適用できます。たとえば、必要な型と修飾子の特定の組み合わせを満たす Bean があるかどうかを確認できます。
BeanDiscoveryFinishedBuildItem の例@BuildStep
void doSomethingWithNamedBeans(BeanDiscoveryFinishedBuildItem beanDiscovery, BuildProducer<NamedBeansBuildItem> namedBeans) {
List<BeanInfo> namedBeans = beanDiscovery.beanStream().withName().collect(toList())); (1)
namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
| 1 | 結果のリストに @Named 合成 Bean は含まれません。 |
5.2. 解決策2: SynthesisFinishedBuildItem
SynthesisFinishedBuildItem のコンシューマーは、アプリケーションに登録されているすべての Bean、オブザーバー、およびインジェクションポイントを簡単に検査できます。このビルドアイテムは合成コンポーネントが登録された 後 に生成されるため、合成 Bean とオブザーバーも含まれます。
さらに、 SynthesisFinishedBuildItem#getBeanResolver() から返された Bean リゾルバーを使用して、タイプセーフな解決ルールを適用できます。たとえば、必要な型と修飾子の特定の組み合わせを満たす Bean があるかどうかを確認できます。
SynthesisFinishedBuildItem の例@BuildStep
void doSomethingWithNamedBeans(SynthesisFinishedBuildItem synthesisFinished, BuildProducer<NamedBeansBuildItem> namedBeans) {
List<BeanInfo> namedBeans = synthesisFinished.beanStream().withName().collect(toList())); (1)
namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
| 1 | 結果のリストには、 @Named 合成 Bean が含まれます。 |
6. ユースケース - 合成 Bean の必要性
場合によっては、合成 Bean を登録できると便利です。合成 Bean の Bean 属性は、Java クラス、メソッド、またはフィールドから派生したものではありません。代わりに、すべての属性はエクステンションによって定義されます。通常の CDI では、これは AfterBeanDiscovery.addBean() メソッドと SyntheticComponents.addBean() メソッドを使用して実現できます。
解決策: 合成 Bean を登録する必要がある場合は、 SyntheticBeanBuildItem を使用します。
SyntheticBeanBuildItem の例 1@BuildStep
SyntheticBeanBuildItem syntheticBean() {
return SyntheticBeanBuildItem.configure(String.class)
.qualifiers(AnnotationInstance.builder(MyQualifier.class).build())
.creator(mc -> mc.returnValue(mc.load("foo"))) (1)
.done();
}
| 1 | jakarta.enterprise.context.spi.Contextual#create(CreationalContext<T>) 実装のバイトコードを生成します。 |
Bean Configurator の出力はバイトコードとして記録されます。したがって、実行時に合成 Bean インスタンスを作成する方法にはいくつかの制限があります。次のことができます。
-
Contextual#create(CreationalContext<T>)メソッドのバイトコードを、ExtendedBeanConfigurator.creator(Consumer<MethodCreator>)を介して直接生成します。 -
ExtendedBeanConfigurator#creator(Class<? extends BeanCreator<U>>)でio.quarkus.arc.BeanCreatorのサブクラスを渡し、場合によってはExtendedBeanConfigurator#param()でビルド時のパラメーターを、ExtendedBeanConfigurator#addInjectionPoint()で合成注入ポイントを指定します。 -
@Recorderメソッド から返されたプロキシー経由でランタイムインスタンスを生成し、それをExtendedBeanConfigurator#runtimeValue(RuntimeValue<?>)、ExtendedBeanConfigurator#runtimeProxy(Object)、ExtendedBeanConfigurator#supplier(Supplier<?>)またはExtendedBeanConfigurator#createWith(Function<SyntheticCreationalContext<?>, <?>)を介して設定します。
SyntheticBeanBuildItem の例 2@BuildStep
@Record(STATIC_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
.runtimeValue(recorder.createFoo()) (2)
.done();
}
| 1 | デフォルトでは、合成 Bean は STATIC_INIT の間に初期化されます。 |
| 2 | Bean インスタンスは、レコーダーメソッドから返される値によって提供されます。 |
汎用合成 Bean Foo<Bar> を作成することもできます。
SyntheticBeanBuildItem の例 3@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class)
.types(ParameterizedType.create(Foo.class, ClassType.create(Bar.class)))) (1)
.scope(Singleton.class)
.runtimeValue(recorder.createFooBar())
.done();
}
| 1 | 汎用型を指定するには、 types() または addType() を使用する必要があります。 |
RUNTIME_INIT の間に初期化される合成 Bean をマークできます。 STATIC_INIT と RUNTIME_INIT の違いの詳細については、Three Phases of Bootstrap and Quarkus Philosophy を参照してください。
RUNTIME_INIT SyntheticBeanBuildItem の例@BuildStep
@Record(RUNTIME_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
.setRuntimeInit() (2)
.runtimeValue(recorder.createFoo())
.done();
}
| 1 | レコーダーは、 ExecutionTime.RUNTIME_INIT フェーズで実行する必要があります。 |
| 2 | Bean インスタンスは、 RUNTIME_INIT の間に初期化されます。 |
|
|
BeanRegistrationPhaseBuildItem を使用して合成 Bean を登録することもできます。ただし、エクステンション作成者には、Quarkus にとってより慣用的な SyntheticBeanBuildItem を使用することをお勧めします。
|
6.1. 合成注入ポイント
合成 Bean は、 ExtendedBeanConfigurator#addInjectionPoint() メソッドを介して合成注入ポイントを登録できます。この注入ポイントはビルド時に検証され、未使用の Bean の検出 の際に考慮されます。注入された参照は、実行時に SyntheticCreationalContext#getInjectedReference() メソッドを通じてアクセスできます。
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
@BuildStep
@Record(RUNTIME_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class)
.scope(Singleton.class)
.addInjectionPoint(ClassType.create(DotName.createSimple(Bar.class))) (2)
.createWith(recorder.createFoo()) (3)
.done();
}
| 1 | Bean インスタンスは、 RUNTIME_INIT の間に初期化されます。 |
| 2 | 必須の Bar 型の合成注入ポイントが追加されました。これは @Inject Bar と同じです。 |
| 3 | Bean インスタンスは、レコーダーメソッドから返される関数を使用して作成されます。 |
@Recorder
public class TestRecorder {
public Function<SyntheticCreationalContext<Foo>, Foo> createFoo() {
return (context) -> {
return new Foo(context.getInjectedReference(Bar.class)); (1)
};
}
}
| 1 | Bar のコンテキスト参照を Foo のコンストラクターに渡します。 |
6.2. 非アクティブな合成 Bean
ビルド時に複数の合成 Bean を登録する必要があるが、実行時にはそのうちのサブセットのみをアクティブにする場合、合成 Bean を 非アクティブ としてマークできると便利です。
これは、"check active" 手順を設定して行い、レコーダーから取得した Supplier<ActiveResult> になります。
@BuildStep
@Record(RUNTIME_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class)
.scope(Singleton.class)
.startup() (1)
.checkActive(recorder.isFooActive()) (2)
.createWith(recorder.createFoo())
.done();
}
| 1 | 通常、非アクティブである可能性のある Bean は、アプリケーションの起動時に必ずエラーが出力されるように、即時初期化が行われます。 Bean が実際には非アクティブであるが、常にアクティブな Bean に注入されていない場合は、即時初期化がスキップされ、エラーは出力されません。 |
| 2 | "check active" 手順を設定します。 |
@Recorder
public class TestRecorder {
public Supplier<ActiveResult> isFooActive() {
return () -> {
if (... should not be active ...) { (1)
return ActiveResult.inactive("explanation"); (2)
}
return ActiveResult.active();
};
}
public Function<SyntheticCreationalContext<Foo>, Foo> createFoo() {
return (context) -> {
return new Foo();
};
}
}
| 1 | 合成 Bean が非アクティブになる条件。 |
| 2 | Bean が非アクティブである理由の適切な説明。
この Bean の非アクティブが別の Bean の非アクティブに起因する場合、別の非アクティブな ActiveResult も原因として提供されることがあります。 |
非アクティブな Bean がどこかに注入されるか、動的に検索されると、 InactiveBeanException が出力されます。
エラーメッセージには、理由 (ActiveResult から)、原因のチェーン (同じく ActiveResult から) が含まれ、この Bean に解決されるすべての注入ポイントのリストがさらに含まれる場合もあります。
非アクティブなケースを適切に処理する必要がある場合は、必ず Instance<> を使用して非アクティブな可能性のある Bean を注入する必要があります。
実際のインスタンスを取得する前に、以下も確認する必要があります。
import io.quarkus.arc.InjectableInstance;
@Inject
InjectableInstance<Foo> foo;
if (foo.getHandle().getBean().isActive()) {
Foo foo = foo.get();
...
}
アクティブな Bean のみを使用する場合は、 InjectableInstance<> を注入し、 getActive() を呼び出して 1 つのインスタンスを取得するか、 listActive() を呼び出してすべてのインスタンスを取得します。
import io.quarkus.arc.InjectableInstance;
@Inject
@Any
InjectableInstance<Foo> foos;
for (Foo foo : foos.listActive())
...
}
7. ユースケース - 合成オブザーバー
synthetic beans と同様に、合成オブザーバーメソッドの属性は Java メソッドから派生しません。代わりに、すべての属性がエクステンションによって定義されます。
解決策: 合成オブザーバーを登録する必要がある場合は、 ObserverRegistrationPhaseBuildItem を使用します。
ObserverRegistrationPhaseBuildItem を消費するビルドステップでは、常に ObserverConfiguratorBuildItem を生成するか、少なくともこのビルドアイテムに BuildProducer を 挿入する必要があります。でなければ、無視されるか、間違ったタイミングで処理される可能性があります (例: 正しい CDI ブートストラップフェーズの後)。
|
ObserverRegistrationPhaseBuildItem の例@BuildStep
void syntheticObserver(ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
BuildProducer<MyBuildItem> myBuildItem,
BuildProducer<ObserverConfiguratorBuildItem> observerConfigurationRegistry) {
observerConfigurationRegistry.produce(new ObserverConfiguratorBuildItem(observerRegistrationPhase.getContext()
.configure()
.beanClass(DotName.createSimple(MyBuildStep.class.getName()))
.observedType(String.class)
.notify(mc -> {
// do some gizmo bytecode generation...
})));
myBuildItem.produce(new MyBuildItem());
}
ObserverConfigurator の出力はバイトコードとして記録されます。
したがって、実行時に合成オブザーバーを呼び出す方法にはいくつかの制限があります。
現時点では、メソッド本体のバイトコードを直接生成する必要があります。
8. ユースケース - 生成された Bean クラスがある
問題ありません。
Bean クラスのバイトコードを手動で生成し、その後に GeneratedClassBuildItem ではなく GeneratedBeanBuildItem を生成してください。
GeneratedBeanBuildItem の例@BuildStep
void generatedBean(BuildProducer<GeneratedBeanBuildItem> generatedBeans) {
ClassOutput beansClassOutput = new GeneratedBeanGizmoAdaptor(generatedBeans); (1)
ClassCreator beanClassCreator = ClassCreator.builder().classOutput(beansClassOutput)
.className("org.acme.MyBean")
.build();
beanClassCreator.addAnnotation(Singleton.class);
beanClassCreator.close(); (2)
}
| 1 | io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor を使用すると、Gizmo コンストラクトから GeneratedBeanBuildItem を簡単に作成できます。 |
| 2 | 結果の Bean クラスは、次のようになります: public class @Singleton MyBean { }。 |
9. ユースケース - デプロイメントの検証
エクステンションは、Bean、オブザーバー、およびインジェクションポイントを検査し、さらに追加の検証を実行して、何か問題がある場合はビルドを失敗にする必要があります。
解決策: エクステンションがデプロイメントを検証する必要がある場合、 ValidationPhaseBuildItem を使用します。
ValidationPhaseBuildItem を消費するビルドステップでは、常に ValidationErrorBuildItem を生成するか、少なくともこのビルドアイテムに BuildProducer を 挿入する必要があります。でなければ、無視されるか、間違ったタイミングで処理される可能性があります (例: 正しい CDI ブートストラップフェーズの後)。
|
@BuildStep
void validate(ValidationPhaseBuildItem validationPhase,
BuildProducer<MyBuildItem> myBuildItem,
BuildProducer<ValidationErrorBuildItem> errors) {
if (someCondition) {
errors.produce(new ValidationErrorBuildItem(new IllegalStateException()));
myBuildItem.produce(new MyBuildItem());
}
}
ValidationPhaseBuildItem.getContext().beans() メソッドから返される便利な BeanStream を使用して、登録されているすべての Bean を簡単にフィルタリングできます。
|
10. ユースケース - カスタム CDI コンテキストの登録
時々、エクステンションは組み込みCDI コンテキストのセットを拡張する必要があります。
解決策: カスタムコンテキストを登録する必要がある場合は、 ContextRegistrationPhaseBuildItem を使用します。
ContextRegistrationPhaseBuildItem を消費するビルドステップでは、常に ContextConfiguratorBuildItem を生成するか、少なくともこのビルドアイテムに BuildProducer を 挿入する必要があります。でなければ、無視されるか、間違ったタイミングで処理される可能性があります (例: 正しい CDI ブートストラップフェーズの後)。
|
ContextRegistrationPhaseBuildItem の例
@BuildStep
ContextConfiguratorBuildItem registerContext(ContextRegistrationPhaseBuildItem phase) {
return new ContextConfiguratorBuildItem(phase.getContext().configure(TransactionScoped.class).normal().contextClass(TransactionContext.class));
}
さらに、 ContextRegistrationPhaseBuildItem を介してカスタム CDI コンテキストを登録する各エクステンションは、Bean 定義アノテーションのセットにカスタムスコープアノテーション名を提供するために、 CustomScopeBuildItem も生成する必要があります。
CustomScopeBuildItem の例
@BuildStep
CustomScopeBuildItem customScope() {
return new CustomScopeBuildItem(DotName.createSimple(TransactionScoped.class.getName()));
}
11. ユースケース - 追加のインターセプターバインディング
まれに、インターセプターバインディングとし て @jakarta.interceptor.InterceptorBinding でアノテーションが付けられていない既存のアノテーションをプログラムで登録すると便利な場合があります。これは、CDI が BeforeBeanDiscovery#addInterceptorBinding() で達成するものと似ています。これを行うには、 InterceptorBindingRegistrarBuildItem を使用します。
InterceptorBindingRegistrarBuildItem の例@BuildStep
InterceptorBindingRegistrarBuildItem addInterceptorBindings() {
return new InterceptorBindingRegistrarBuildItem(new InterceptorBindingRegistrar() {
@Override
public List<InterceptorBinding> getAdditionalBindings() {
return List.of(InterceptorBinding.of(NotAnInterceptorBinding.class));
}
});
}
12. ユースケース - 追加の修飾子
場合によっては、 @jakarta.inject.Qualifier でアノテーションされていない既存のアノテーションを CDI 修飾子として登録しておくと便利です。
これは CDI が BeforeBeanDiscovery#addQualifier() を通して実現していることに似ています。
ここでは QualifierRegistrarBuildItem を使ってそれを実現しようとしています。
QualifierRegistrarBuildItem の例@BuildStep
QualifierRegistrarBuildItem addQualifiers() {
return new QualifierRegistrarBuildItem(new QualifierRegistrar() {
@Override
public Map<DotName, Set<String>> getAdditionalQualifiers() {
return Collections.singletonMap(DotName.createSimple(NotAQualifier.class.getName()),
Collections.emptySet());
}
});
}
13. ユースケース - 追加のステレオタイプ
場合によっては、 @jakarta.enterprise.inject.Stereotype でアノテーションされていない既存のアノテーションを CDI ステレオタイプとして登録しておきます。
これは CDI が BeforeBeanDiscovery#addStereotype() を通して実現していることに似ています。ここでは StereotypeRegistrarBuildItem を使ってそれを実現しようとしています。
StereotypeRegistrarBuildItem の例@BuildStep
StereotypeRegistrarBuildItem addStereotypes() {
return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() {
@Override
public Set<DotName> getAdditionalStereotypes() {
return Collections.singleton(DotName.createSimple(NotAStereotype.class.getName()));
}
});
}
新しく登録されたステレオタイプアノテーションに、スコープやインターセプターバインディングなどの適切なメタアノテーションがない場合、 アノテーション変換 を使用して追加します。
14. ユースケース - インジェクションポイントの変換
プログラムでインジェクションポイントの修飾子を変更できると便利な場合があります。それは InjectionPointTransformerBuildItem で実行できます。次のサンプルは、修飾子 MyQualifier を含むタイプ Foo のインジェクションポイントに変換を適用する方法を示しています。
InjectionPointTransformerBuildItem の例@BuildStep
InjectionPointTransformerBuildItem transformer() {
return new InjectionPointTransformerBuildItem(new InjectionPointsTransformer() {
public boolean appliesTo(Type requiredType) {
return requiredType.name().equals(DotName.createSimple(Foo.class.getName()));
}
public void transform(TransformationContext context) {
if (context.getQualifiers().stream()
.anyMatch(a -> a.name().equals(DotName.createSimple(MyQualifier.class.getName())))) {
context.transform()
.removeAll()
.add(DotName.createSimple(MyOtherQualifier.class.getName()))
.done();
}
}
});
}
理論的には、AnnotationsTransformer を使用して同じ目標を達成できます。ただし、いくつかの違いにより、InjectionPointsTransformer がこの特定のタスクにより適しています。(1) アノテーショントランスフォーマーは Bean 検出中にすべてのクラスに適用されますが、InjectionPointsTransformer は Bean 検出後に検出されたインジェクションポイントにのみ適用されます。(2) InjectionPointsTransformer を使用すると、さまざまなタイプのインジェクションポイント (フィールド、初期化メソッドのパラメーターなど) を処理する必要がありません。
|
15. ユースケース - リソースアノテーションとインジェクション
ResourceAnnotationBuildItem は、Jakarta EE リソースなどの非 CDI インジェクションポイントを解決可能にするリソースアノテーションを指定するために使用できます。インテグレーターは、対応する io.quarkus.arc.ResourceReferenceProvider サービスプロバイダー実装も提供する必要があります。
ResourceAnnotationBuildItem の例@BuildStep
void setupResourceInjection(BuildProducer<ResourceAnnotationBuildItem> resourceAnnotations, BuildProducer<GeneratedResourceBuildItem> resources) {
resources.produce(new GeneratedResourceBuildItem("META-INF/services/io.quarkus.arc.ResourceReferenceProvider",
MyResourceReferenceProvider.class.getName().getBytes()));
resourceAnnotations.produce(new ResourceAnnotationBuildItem(DotName.createSimple(MyAnnotation.class.getName())));
}
16. 利用可能なビルド時メタデータ
BuildExtension.BuildContext で動作する上記のエクステンションは、ビルド時に生成される特定のビルド時メタデータを活用できます。io.quarkus.arc.processor.BuildExtension.Key にある組み込みキーは次のとおりです。
- ANNOTATION_STORE
-
アノテーショントランスフォーマーの適用後、すべての
AnnotationTargetアノテーションに関する情報を保持するAnnotationStoreを含みます。 - INJECTION_POINTS
-
すべてのインジェクションポイントを含む
Collection<InjectionPointInfo> - BEANS
-
すべての Bean を含む
Collection<BeanInfo> - REMOVED_BEANS
-
削除されたすべての Bean を含む
Collection<BeanInfo>。詳細については、未使用の Bean の削除 を参照してください。 - OBSERVERS
-
すべてのオブザーバーを含む
Collection<ObserverInfo> - SCOPES
-
カスタムスコープも含め、すべてのスコープを含む
Collection<ScopeInfo> - QUALIFIERS
-
すべての修飾子を含む
Map<DotName, ClassInfo> - INTERCEPTOR_BINDINGS
-
すべてのインターセプターバインディングを含む
Map<DotName, ClassInfo> - STEREOTYPES
-
すべてのステレオタイプを含む
Map<DotName, StereotypeInfo>
これらを取得するには、指定されたキーのエクステンションコンテキストオブジェクトをクエリするだけです。これらのメタデータはビルドの進行とともに利用可能になるため、エクステンションは、エクステンションが呼び出される前にビルドされたメタデータのみを活用できることに注意してください。エクステンションがまだ生成されていないメタデータを取得しようとすると、null が返されます。どのエクステンションがどのメタデータにアクセスできるかを次に示します。
- AnnotationsTransformer
-
ブートストラップのどのフェーズでもいつでも使用できるため、どのメタデータにも依存すべきではありません。
- ContextRegistrar
-
ANNOTATION_STORE、QUALIFIERS、INTERCEPTOR_BINDINGS、STEREOTYPESにアクセスできます。 - InjectionPointsTransformer
-
ANNOTATION_STORE、QUALIFIERS、INTERCEPTOR_BINDINGS、STEREOTYPESにアクセスできます。 - ObserverTransformer
-
ANNOTATION_STORE、QUALIFIERS、INTERCEPTOR_BINDINGS、STEREOTYPESにアクセスできます。 - BeanRegistrar
-
ANNOTATION_STORE、QUALIFIERS、INTERCEPTOR_BINDINGS、STEREOTYPES、BEANS(クラスベースの Bean のみ)、OBSERVERS(クラスベースのオブザーバーのみ)、INJECTION_POINTSにアクセスできます。 - ObserverRegistrar
-
ANNOTATION_STORE、QUALIFIERS、INTERCEPTOR_BINDINGS、STEREOTYPES、BEANS、OBSERVERS(クラスベースのオブザーバーのみ)、INJECTION_POINTSにアクセスできます。 - BeanDeploymentValidator
-
すべてのビルドメタデータにアクセスできます。