CDI 統合ガイド
ArC, the CDI container in Quarkus, is bootstrapped at build time. To integrate with the container, CDI Build Compatible Extensions can be used, as well as a Quarkus-specific extension API. CDI Portable Extensions are not and cannot be supported. This guide focuses on the Quarkus-specific extensions API.
コンテナーは複数のフェーズでブートストラップされます。高レベルの視点から見ると、これらのフェーズは以下のようになります。
-
初期化
-
Beanディスカバリ
-
合成コンポーネントの登録
-
バリデーション
初期化 フェーズでは、準備作業が行われ、カスタムコンテキストが登録されます。その後、コンテナーがすべてのアプリケーションクラスを分析し、Beanを識別し、提供されたメタデータに基づいてそれらをすべて繋ぎ合わせるプロセスがBean ディスカバリ です。その後、エクステンションは 合成コンポーネント を登録することができます。これらのコンポーネントの属性はエクステンションによって完全に制御されます。最後に、 デプロイメントが検証されます 。例えば、コンテナーはアプリケーション内のすべての注入ポイントを検証し、与えられた必要な型と修飾子を満たすBeanがない場合はビルドを失敗させます。
You can see more information about the bootstrap by enabling additional logging. Simply run the Maven build with -X or --debug and grep the lines that contain io.quarkus.arc . In dev mode, you can use quarkus.log.category."io.quarkus.arc.processor".level=DEBUG and two special endpoints are also registered automatically to provide some basic debug info in the JSON format.
|
Quarkusのビルドステップでは、さまざまなビルドアイテムを生成したり消費したりして、各フェーズにフックすることができます。以下のセクションでは、関連するすべてのビルド項目と一般的なシナリオについて説明します。
1. メタデータソース
Classes and annotations are the primary source of bean-level metadata. The initial metadata are read from the bean archive index, an immutable Jandex index which is built from various sources during bean discovery. However, extensions can add, remove or transform the metadata at certain stages of the bootstrap. Moreover, extensions can also register synthetic components. This is an important aspect to realize when integrating CDI components in 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を定義するアノテーションがない
In Quarkus, the application is represented by a single bean archive with the bean discovery mode annotated
.
Therefore, bean classes that don’t have a bean defining annotation are ignored.
Bean defining annotations are declared on the class-level and include scopes, stereotypes and @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. ユースケース - アノテーションのメタデータを変換する必要がある場合
In some cases, it’s useful to be able to modify the annotation metadata.
Quarkus provides a powerful alternative to jakarta.enterprise.inject.spi.ProcessAnnotatedType
and jakarta.enterprise.inject.build.compatible.spi.Enhancement
.
With an AnnotationsTransformerBuildItem
it’s possible to override the annotations that exist on bean classes.
アノテーショントランスフォーマーは、Beanディスカバリが始まる 前に 生成されなければならないことを覚えておいてください。 |
For example, you might want to add an interceptor binding to a specific bean class. You can use a convenient builder API to create a transformation instance:
@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 | Only apply the transformation if the class is org.acme.Bar . |
3 | @MyInterceptorBinding アノテーションを追加します。 |
The example above can be rewritten with an anonymous class:
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 | If the class name equals to org.acme.Bar then add @MyInterceptorBinding . |
The previous AnnotationsTransformer API from ArC is still supported, but the new AnnotationTransformation API from Jandex is preferred.
|
ビルドステップでは、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 の必要性
Sometimes it is practical to be able to register a synthetic bean.
Bean attributes of a synthetic bean are not derived from a Java class, method or field.
Instead, all the attributes are defined by an extension.
In regular CDI, this could be achieved using the AfterBeanDiscovery.addBean()
and SyntheticComponents.addBean()
methods.
解決策: 合成 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 | Generate the bytecode of the jakarta.enterprise.context.spi.Contextual#create(CreationalContext<T>) implementation. |
Bean Configurator の出力は、バイトコードとして記録されます。したがって、実行時に合成 Bean インスタンスを作成する方法にはいくつかの制限があります。以下が可能です。
-
Contextual#create(CreationalContext<T>)
メソッドのバイトコードを、ExtendedBeanConfigurator.creator(Consumer<MethodCreator>)
を介して直接生成します。 -
Pass a subclass of
io.quarkus.arc.BeanCreator
viaExtendedBeanConfigurator#creator(Class<? extends BeanCreator<U>>)
, and possibly specify some build-time parameters viaExtendedBeanConfigurator#param()
and synthetic injection points viaExtendedBeanConfigurator#addInjectionPoint()
. -
Produce the runtime instance through a proxy returned from a
@Recorder
method and set it viaExtendedBeanConfigurator#runtimeValue(RuntimeValue<?>)
,ExtendedBeanConfigurator#runtimeProxy(Object)
,ExtendedBeanConfigurator#supplier(Supplier<?>)
orExtendedBeanConfigurator#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 インスタンスは、レコーダーメソッドから返される値によって提供されます。 |
It is also possible to create a generic synthetic bean Foo<Bar>
.
SyntheticBeanBuildItem
Example 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() or addType() must be used to specify the generic type. |
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. Synthetic Injection Points
A synthetic bean may register a synthetic injection point via the ExtendedBeanConfigurator#addInjectionPoint()
method.
This injection point is validated at build time and considered when detecting unused beans.
The injected reference is accessible through the SyntheticCreationalContext#getInjectedReference()
methods at runtime.
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 | A synthetic injection point with required type Bar was added; this is an equivalent of @Inject Bar . |
3 | The bean instance is created with a function returned from a recorder method. |
@Recorder
public class TestRecorder {
public Function<SyntheticCreationalContext<Foo>, Foo> createFoo() {
return (context) -> {
return new Foo(context.getInjectedReference(Bar.class)); (1)
};
}
}
1 | Pass a contextual reference of Bar to the constructor of Foo . |
6.2. Inactive Synthetic Beans
In the case when one needs to register multiple synthetic beans at build time but only wants a subset of them active at runtime, it is useful to be able to mark a synthetic bean as inactive.
This is done by configuring a "check active" procedure, which should be a Supplier<ActiveResult>
obtained from a recorder:
@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 | A bean that might be inactive is typically initialized eagerly, to make sure that an error is thrown at application startup. If the bean is in fact inactive, but is not injected into an always-active bean, eager initialization is skipped and no error is thrown. |
2 | Configures the "check active" procedure. |
@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 | The condition when the synthetic bean should be inactive. |
2 | Proper explanation of why the bean is inactive.
Another inactive ActiveResult may also be provided as a cause, if this bean’s inactivity stems from another bean’s inactivity. |
If an inactive bean is injected somewhere, or is dynamically looked up, an InactiveBeanException
is thrown.
The error message contains the reason (from the ActiveResult
), the cause chain (also from the ActiveResult
), and possibly also a list of all injection points that resolve to this bean.
If you want to handle the inactive case gracefully, you should always inject possibly inactive beans using Instance<>
.
You also need to check before obtaining the actual instance:
import io.quarkus.arc.InjectableInstance;
@Inject
InjectableInstance<Foo> foo;
if (foo.getHandle().getBean().isActive()) {
Foo foo = foo.get();
...
}
If you want to consume only active beans, you can inject an InjectableInstance<>
and call getActive()
to get the single instance or listActive()
to get all instances:
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. ユースケース - 追加のインターセプターバインディング
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. 使用例 - 追加の修飾子
Sometimes it might be useful to register an existing annotation that is not annotated with @jakarta.inject.Qualifier
as a CDI qualifier.
This is similar to what CDI achieves through BeforeBeanDiscovery#addQualifier()
.
We are going to use QualifierRegistrarBuildItem
to get it done.
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. 使用例 - 追加のステレオタイプ
It is sometimes useful to register an existing annotation that is not annotated with @jakarta.enterprise.inject.Stereotype
as a CDI stereotype.
This is similar to what CDI achieves through BeforeBeanDiscovery#addStereotype()
.
We are going to use StereotypeRegistrarBuildItem
to get it done.
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
-
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
-
すべてのビルドメタデータにアクセスできます