CDI 統合ガイド
Quarkus の CDI コンテナである ArC は、ビルド時にブートストラップされます。 コンテナと統合するには、Quarkus固有のエクステンションAPIだけでなく、 CDI ビルド五感エクステンション も使用できます。 CDI Portable Extensions はサポートされていません。 このガイドでは、Quarkus 固有のエクステンション API を中心に説明します。
コンテナーは複数のフェーズでブートストラップされます。高レベルの視点から見ると、これらのフェーズは以下のようになります。
-
初期化
-
Beanディスカバリ
-
合成コンポーネントの登録
-
バリデーション
In the initialization phase, the preparatory work is being carried out, and custom contexts are registered. Bean discovery is then the process where the container analyzes all application classes, identifies beans and wires them all together based on the provided metadata. Subsequently, the extensions can register synthetic components. Attributes of these components are fully controlled by the extensions, i.e. are not derived from an existing class. Finally, the deployment is validated. For example, the container validates every injection point in the application and fails the build if there is no bean that satisfies the given required type and qualifiers.
追加のロギングを有効にすることで、ブートストラップに関するより多くの情報を表示できます。そのためには、 -X または --debug で Maven ビルドを実行し、 io.quarkus.arc が含まれる行を grep します。dev mode では、 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG を使用でき、2 つの特別なエンドポイントも自動的に登録され、JSON 形式でいくつかの基本的なデバッグ情報が提供されます。
|
Quarkus build steps can produce and consume various build items and hook into each phase. In the following sections, we will describe all the relevant build items and common scenarios.
1. メタデータソース
クラスとアノテーションは、Bean レベルのメタデータの主要なソースです。 初期のメタデータは、bean discovery の間にさまざまなソースからビルドされる不変の Jandex インデックス である _Beanアーカイブインデックス から読み込まれます。 しかし、エクステンションは、ブートストラップの特定の段階でメタデータを追加、削除、変換できます。 さらに、エクステンションは synthetic components を登録することもできます。 これは、CDI コンポーネントを Quarkus に統合する際に実現すべき重要な側面です。
このようにして、エクステンションは、そうでなければ無視されていたクラスをBeanに変えたり、その逆を行ったりすることができます。例えば、 @Scheduled メソッドを宣言するクラスは、たとえそれがBean定義アノテーションでアノテーションされておらず、通常は無視されるようなクラスであっても、常にBeanとして登録されます。
2. ユースケース - クラスがBeanとして認識されません
An UnsatisfiedResolutionException indicates a problem during typesafe resolution.
Sometimes an injection point cannot be satisfied even if there is a class on the classpath that appears to be eligible for injection.
There are several reasons why a class is not recognized and also several ways to fix it.
In the first step, we should identify the reason.
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 定義のアノテーションがあるが削除された も参照してください。
It is also possible to set the default scope via AdditionalBeanBuildItem.Builder#setDefaultScope().
The default scope is only used if there is no scope declared on the bean class.
デフォルトスコープが指定されていない場合は @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 を追加。既にスコープでアノテーションされているクラスは自動的にスキップされます。 |
Solution 2: If you need to process classes annotated with a specific annotation, then it’s possible to extend the set of bean defining annotations via the BeanDefiningAnnotationBuildItem.
BeanDefiningAnnotationBuildItem の例@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
return new BeanDefiningAnnotationBuildItem(Annotations.GRAPHQL_API); (1)
}
| 1 | Bean 定義アノテーションのセットに org.eclipse.microprofile.graphql.GraphQLApi を追加します。 |
Bean classes added via BeanDefiningAnnotationBuildItem are not removable by default, i.e. the resulting beans must not be removed even if they are considered unused.
However, you can change the default behaviour.
See also Removing Unused Beans and 理由3: クラスが検出され、Bean 定義のアノテーションがあるが削除された for more details.
デフォルトスコープを設定することもできます。デフォルトスコープは、Bean クラスにスコープが宣言されていない場合にのみ使用されます。
デフォルトスコープが指定されていない場合は @Dependent 擬似スコープが使用されます。
|
2.3. 理由3: クラスが検出され、Bean 定義のアノテーションがあるが削除された
The container attempts to remove all unused beans during the build by default.
This optimization allows for framework-level dead code elimination.
In a few special cases, it’s not possible to correctly identify an unused bean.
In particular, Quarkus is not able to detect the usage of the CDI.current() static method yet.
Extensions can eliminate possible false positives by producing an 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()で合成注入ポイントを指定します。 -
@Recordermethod から返されたプロキシー経由でランタイムインスタンスを生成し、それを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());
}
The output of an ObserverConfigurator is recorded as bytecode.
Therefore, there are some limitations in how a synthetic observer is invoked at runtime.
Currently, you must generate the bytecode of the method body directly.
8. ユースケース - 生成された Bean クラスがある
No problem.
You can generate the bytecode of a bean class manually, and then all you need to do is to produce a GeneratedBeanBuildItem instead of GeneratedClassBuildItem.
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、オブザーバー、およびインジェクションポイントを検査し、さらに追加の検証を実行して、何か問題がある場合はビルドを失敗にする必要があります。
Solution: If an extension needs to validate the deployment, it should use the 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. ユースケース - 追加のインターセプターバインディング
In rare cases, it might be handy to programmatically register an existing annotation that is not annotated with @jakarta.interceptor.InterceptorBinding as an interceptor binding.
This is similar to what CDI achieves through BeforeBeanDiscovery#addInterceptorBinding().
We are going to use InterceptorBindingRegistrarBuildItem to get it done.
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. ユースケース - インジェクションポイントの変換
Every now and then, it is handy to be able to change the qualifiers of an injection point programmatically.
You can do just that with InjectionPointTransformerBuildItem.
The following sample shows how to apply a transformation to injection points with type Foo that contain a qualifier MyQualifier:
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>
To get hold of these, simply query the extension context object for given key.
Note that these metadata are made available as build proceeds, which means that extensions can only leverage metadata that were built before the extensions are invoked.
If your extension attempts to retrieve metadata that wasn’t yet produced, null will be returned.
Here is a summary of which extensions can access which metadata:
- 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
-
すべてのビルドメタデータにアクセスできます