CDI 統合ガイド
Quarkus の CDI コンテナである ArC は、ビルド時にブートストラップされます。 コンテナと統合するには、Quarkus固有のエクステンションAPIだけでなく、 CDI ビルド五感エクステンション も使用できます。 CDI Portable Extensions はサポートされていません。 このガイドでは、Quarkus 固有のエクステンション API を中心に説明します。
コンテナーは複数のフェーズでブートストラップされます。高レベルの視点から見ると、これらのフェーズは以下のようになります。
-
初期化
-
Beanディスカバリ
-
合成コンポーネントの登録
-
バリデーション
初期化 フェーズでは、準備作業が行われ、カスタムコンテキストが登録されます。その後、コンテナーがすべてのアプリケーションクラスを分析し、Beanを識別し、提供されたメタデータに基づいてそれらをすべて繋ぎ合わせるプロセスがBean ディスカバリ です。その後、エクステンションは 合成コンポーネント を登録することができます。これらのコンポーネントの属性はエクステンションによって完全に制御されます。最後に、 デプロイメントが検証されます 。例えば、コンテナーはアプリケーション内のすべての注入ポイントを検証し、与えられた必要な型と修飾子を満たすBeanがない場合はビルドを失敗させます。
追加のロギングを有効にすることで、ブートストラップに関するより多くの情報を表示できます。そのためには、 -X または --debug で Maven ビルドを実行し、 io.quarkus.arc が含まれる行を grep します。dev mode では、 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG を使用でき、2 つの特別なエンドポイントも自動的に登録され、JSON 形式でいくつかの基本的なデバッグ情報が提供されます。
|
Quarkusのビルドステップでは、さまざまなビルドアイテムを生成したり消費したりして、各フェーズにフックすることができます。以下のセクションでは、関連するすべてのビルド項目と一般的なシナリオについて説明します。
1. メタデータソース
クラスとアノテーションは、Bean レベルのメタデータの主要なソースです。 初期のメタデータは、bean discovery の間にさまざまなソースからビルドされる不変の Jandex インデックス である _Beanアーカイブインデックス から読み込まれます。 しかし、エクステンションは、ブートストラップの特定の段階でメタデータを追加、削除、変換できます。 さらに、エクステンションは synthetic components を登録することもできます。 これは、CDI コンポーネントを Quarkus に統合する際に実現すべき重要な側面です。
このようにして、エクステンションは、そうでなければ無視されていたクラスを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の 削除 と [unremovable_builditem] も参照してください。
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 は未使用と見なされても削除しないでください。ただし、デフォルトの動作は変更できます。詳細についてはRemoving Unused Beans および理由3: クラスが検出され、Bean 定義のアノテーションがあるが削除された を参照してください。
デフォルトスコープを設定することもできます。デフォルトスコープは、Bean クラスにスコープが宣言されていない場合にのみ使用されます。
デフォルトスコープが指定されていない場合は @Dependent 擬似スコープが使用されます。
|
2.3. 理由3: クラスが検出され、Bean 定義のアノテーションがあるが削除された
デフォルトで、コンテナーはビルド時に remove all unused beans を試行します。この最適化により、フレームワークレベルでのデッドコードの排除 が可能になります。いくつかの特殊なケースでは、未使用の 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
method から返されたプロキシー経由でランタイムインスタンスを生成し、それを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()
メソッドを介して合成注入ポイントを登録できます。
この注入ポイントはビルド時に検証され、detecting unused beans で検討されます。
注入された参照は、実行時に 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#addQualifier()
を通して実現していることに似ています。ここでは QualifierRegistrarBuildItem
を使ってそれを実現しようとしています。
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();
}
}
});
}
理論的には、an 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
-
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
(クラスベースobserverのみ),INJECTION_POINTS
にアクセスできます。 - ObserverRegistrar
-
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
,BEANS
,OBSERVERS
(クラスベースのオブザーバーのみ),INJECTION_POINTS
にアクセス可能です。 - BeanDeploymentValidator
-
すべてのビルドメタデータにアクセスできます