CDI Integration Guide
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.
The container is bootstrapped in multiple phases. From a high level perspective these phases go as follows:
-
Initialization
-
Bean discovery
-
Registration of synthetic components
-
Validation
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.
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 the
development 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 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. Metadata Sources
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.
This way, extensions can turn classes, that would be otherwise ignored, into
beans and vice versa. For example, a class that declares a @Scheduled
method is always registered as a bean even if it is not annotated with a
bean defining annotation and would be normally ignored.
2. Use Case - My Class Is Not Recognized as a 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. Reason 1: Class Is Not discovered
Quarkus has a simplified discovery. It might happen that the class is not part of the application index. For example, classes from the runtime module of a Quarkus extension are not indexed automatically.
Solution: Use the AdditionalBeanBuildItem
. This build item can be used
to specify one or more additional classes to be analyzed during the
discovery. Additional bean classes are transparently added to the
application index processed by the container.
It is not possible to conditionally enable/disable additional beans via the
@IfBuildProfile , @UnlessBuildProfile , @IfBuildProperty and
@UnlessBuildProperty annotations as described in
Enabling Beans for Quarkus
Build Profile and Enabling
Beans for Quarkus Build Properties. Extensions should inspect the
configuration or the current profile and only produce an
AdditionalBeanBuildItem if really needed.
|
AdditionalBeanBuildItem
Example@BuildStep
AdditionalBeanBuildItem additionalBeans() {
return new AdditionalBeanBuildItem(SmallRyeHealthReporter.class, HealthServlet.class)); (1)
}
1 | AdditionalBeanBuildItem.Builder can be used for more complex use cases. |
Bean classes added via AdditionalBeanBuildItem
are removable by
default. If the container considers them
unused, they are just ignored.
However, you can use AdditionalBeanBuildItem.Builder.setUnremovable()
method to instruct the container to never remove bean classes registered via
this build item. See also
Removing Unused Beans and
Reason 3: Class Was Discovered and Has a Bean
Defining Annotation but Was Removed for more details.
It is aso 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.
If no default scope is specified the @Dependent pseudo-scope is used.
|
2.2. Reason 2: Class Is Discovered but Has No Bean Defining Annotation
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
.
Solution 1: Use the AutoAddScopeBuildItem
. This build item can be used
to add a scope to a class that meets certain conditions.
AutoAddScopeBuildItem
Example@BuildStep
AutoAddScopeBuildItem autoAddScope() {
return AutoAddScopeBuildItem.builder().containsAnnotations(SCHEDULED_NAME, SCHEDULES_NAME) (1)
.defaultScope(BuiltinScope.SINGLETON) (2)
.build();
}
1 | Find all classes annotated with @Scheduled . |
2 | Add @Singleton as default scope. Classes already annotated with a scope
are skipped automatically. |
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
Example@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
return new BeanDefiningAnnotationBuildItem(Annotations.GRAPHQL_API); (1)
}
1 | Add org.eclipse.microprofile.graphql.GraphQLApi to the set of bean
defining annotations. |
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 behavior. See also
Removing Unused Beans and
Reason 3: Class Was Discovered and Has a Bean
Defining Annotation but Was Removed for more details.
It is also possible to specify the default scope. The default scope is only used if there is no scope declared on the bean class.
If no default scope is specified the @Dependent pseudo-scope is used.
|
2.3. Reason 3: Class Was Discovered and Has a Bean Defining Annotation but Was Removed
The container attempts to remove
all unused beans during the build by default. This optimization allows for
framework-level dead code elimination. In 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
Example@BuildStep
UnremovableBeanBuildItem unremovableBeans() {
return UnremovableBeanBuildItem.targetWithAnnotation(STARTUP_NAME); (1)
}
1 | Make all classes annotated with @Startup unremovable. |
3. Use Case - My Annotation Is Not Recognized as a Qualifier or an Interceptor Binding
It is likely that the annotation class is not part of the application index. For example, classes from the runtime module of a Quarkus extension are not indexed automatically.
Solution: Use the AdditionalBeanBuildItem
as described in
Reason 1: Class Is Not discovered.
4. Use Case - I Need To Transform Annotation Metadata
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.
Keep in mind that annotation transformers must be produced before the bean discovery starts. |
For example, you might want to add an interceptor binding to a specific bean class. You can use a convenient builder-like API to create a transformer instance:
Builder Example
@BuildStep
AnnotationsTransformerBuildItem transform() {
return new AnnotationsTransformerBuildItem(AnnotationsTransformer.appliedToClass() (1)
.whenClass(c -> c.name().toString().equals("org.acme.Bar")) (2)
.thenTransform(t -> t.add(MyInterceptorBinding.class))); (3)
}
1 | The transformer is only applied to classes. |
2 | Only apply the transformation if the class name equals to org.acme.Bar . |
3 | Add the @MyInterceptorBinding annotation. |
The example above can be rewritten with an anonymous class:
AnnotationsTransformerBuildItem
Example@BuildStep
AnnotationsTransformerBuildItem transform() {
return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) {
return kind == org.jboss.jandex.AnnotationTarget.Kind.CLASS; (1)
}
public void transform(TransformationContext context) {
if (context.getTarget().asClass().name().toString().equals("org.acme.Bar")) {
context.transform().add(MyInterceptorBinding.class).done(); (2)
}
}
});
}
1 | The transformer is only applied to classes. |
2 | If the class name equals to org.acme.Bar then add
@MyInterceptorBinding . Don’t forget to invoke Transformation#done() . |
Build steps can query the transformed annotations for a given annotation
target via the TransformedAnnotationsBuildItem
.
TransformedAnnotationsBuildItem
Example@BuildStep
void queryAnnotations(TransformedAnnotationsBuildItem transformedAnnotations, BuildProducer<MyBuildItem> myBuildItem) {
ClassInfo myClazz = ...;
if (transformedAnnotations.getAnnotations(myClazz).isEmpty()) { (1)
myBuildItem.produce(new MyBuildItem());
}
}
1 | TransformedAnnotationsBuildItem.getAnnotations() will return a possibly
transformed set of annotations. |
There are other build items specialized in transformation: Use Case - Additional Interceptor Bindings and Use Case - Injection Point Transformation. |
4.1. How to Enable Trace Logging for Annotation Transformers
You can set the TRACE
level for the category io.quarkus.arc.processor
and try to analyze the log output afterwards.
application.properties
Examplequarkus.log.category."io.quarkus.arc.processor".min-level=TRACE (1)
quarkus.log.category."io.quarkus.arc.processor".level=TRACE
1 | You also need to adjust the minimum log level for the relevant category. |
5. Use Case - Inspect Beans, Observers and Injection Points
5.1. Solution 1: BeanDiscoveryFinishedBuildItem
Consumers of BeanDiscoveryFinishedBuildItem
can easily inspect all
class-based beans, observers and injection points registered in the
application. However, synthetic beans and observers are not included
because this build item is produced before the synthetic components are
registered.
Additionally, the bean resolver returned from
BeanDiscoveryFinishedBuildItem#getBeanResolver()
can be used to apply the
type-safe resolution rules, e.g. to find out whether there is a bean that
would satisfy certain combination of required type and qualifiers.
BeanDiscoveryFinishedBuildItem
Example@BuildStep
void doSomethingWithNamedBeans(BeanDiscoveryFinishedBuildItem beanDiscovery, BuildProducer<NamedBeansBuildItem> namedBeans) {
List<BeanInfo> namedBeans = beanDiscovery.beanStream().withName().collect(toList())); (1)
namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
1 | The resulting list will not contain @Named synthetic beans. |
5.2. Solution 2: SynthesisFinishedBuildItem
Consumers of SynthesisFinishedBuildItem
can easily inspect all beans,
observers and injection points registered in the application. Synthetic
beans and observers are included because this build item is produced after
the synthetic components are registered.
Additionally, the bean resolver returned from
SynthesisFinishedBuildItem#getBeanResolver()
can be used to apply the
type-safe resolution rules, e.g. to find out whether there is a bean that
would satisfy certain combination of required type and qualifiers.
SynthesisFinishedBuildItem
Example@BuildStep
void doSomethingWithNamedBeans(SynthesisFinishedBuildItem synthesisFinished, BuildProducer<NamedBeansBuildItem> namedBeans) {
List<BeanInfo> namedBeans = synthesisFinished.beanStream().withName().collect(toList())); (1)
namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
1 | The resulting list will contain @Named synthetic beans. |
6. Use Case - The Need for Synthetic Beans
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.
Solution: If you need to register a synthetic bean then use the
SyntheticBeanBuildItem
.
SyntheticBeanBuildItem
Example 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. |
The output of a bean configurator is recorded as bytecode. Therefore, there are some limitations in how a synthetic bean instance is created at runtime. You can:
-
Generate the bytecode of the
Contextual#create(CreationalContext<T>)
method directly viaExtendedBeanConfigurator.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
Example 2@BuildStep
@Record(STATIC_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
.runtimeValue(recorder.createFoo()) (2)
.done();
}
1 | By default, a synthetic bean is initialized during STATIC_INIT . |
2 | The bean instance is supplied by a value returned from a recorder method. |
It is possible to mark a synthetic bean to be initialized during
RUNTIME_INIT
. See the
Three Phases of
Bootstrap and Quarkus Philosophy for more information about the difference
between STATIC_INIT
and RUNTIME_INIT
.
RUNTIME_INIT
SyntheticBeanBuildItem
Example@BuildStep
@Record(RUNTIME_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
.setRuntimeInit() (2)
.runtimeValue(recorder.createFoo())
.done();
}
1 | The recorder must be executed in the ExecutionTime.RUNTIME_INIT phase. |
2 | The bean instance is initialized during RUNTIME_INIT . |
Synthetic beans initialized during
|
It is also possible to use the BeanRegistrationPhaseBuildItem to register
a synthetic bean. However, we recommend extension authors to stick with
SyntheticBeanBuildItem which is more idiomatic for Quarkus.
|
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 | The bean instance is initialized during 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 . |
7. Use Case - Synthetic Observers
Similar to synthetic beans, the attributes of a synthetic observer method are not derived from a Java method. Instead, all the attributes are defined by an extension.
Solution: If you need to register a synthetic observer, use the
ObserverRegistrationPhaseBuildItem
.
A build step that consumes the ObserverRegistrationPhaseBuildItem should
always produce an ObserverConfiguratorBuildItem or at least inject a
BuildProducer for this build item, otherwise it could be ignored or
processed at the wrong time (e.g. after the correct CDI bootstrap phase).
|
ObserverRegistrationPhaseBuildItem
Example@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 a 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. Use Case - I Have a Generated Bean Class
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
Example@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 makes it easy to
produce GeneratedBeanBuildItem s from Gizmo constructs. |
2 | The resulting bean class is something like public class @Singleton MyBean {
} . |
9. Use Case - I Need to Validate the Deployment
Sometimes extensions need to inspect the beans, observers and injection points, then perform additional validations and fail the build if something is wrong.
Solution: If an extension needs to validate the deployment it should use
the ValidationPhaseBuildItem
.
A build step that consumes the ValidationPhaseBuildItem should always
produce a ValidationErrorBuildItem or at least inject a BuildProducer
for this build item, otherwise it could be ignored or processed at the wrong
time (e.g. after the correct CDI bootstrap phase).
|
@BuildStep
void validate(ValidationPhaseBuildItem validationPhase,
BuildProducer<MyBuildItem> myBuildItem,
BuildProducer<ValidationErrorBuildItem> errors) {
if (someCondition) {
errors.produce(new ValidationErrorBuildItem(new IllegalStateException()));
myBuildItem.produce(new MyBuildItem());
}
}
You can easily filter all registered beans via the convenient BeanStream
returned from the ValidationPhaseBuildItem.getContext().beans() method.
|
10. Use Case - Register a Custom CDI Context
Sometimes extensions need to extend the set of built-in CDI contexts.
Solution: If you need to register a custom context, use the
ContextRegistrationPhaseBuildItem
.
A build step that consumes the ContextRegistrationPhaseBuildItem should
always produce a ContextConfiguratorBuildItem or at least inject a
BuildProducer for this build item, otherwise it could be ignored or
processed at the wrong time (e.g. after the correct CDI bootstrap phase).
|
ContextRegistrationPhaseBuildItem
Example
@BuildStep
ContextConfiguratorBuildItem registerContext(ContextRegistrationPhaseBuildItem phase) {
return new ContextConfiguratorBuildItem(phase.getContext().configure(TransactionScoped.class).normal().contextClass(TransactionContext.class));
}
Additionally, each extension that registers a custom CDI context via
ContextRegistrationPhaseBuildItem
should also produce the
CustomScopeBuildItem
in order to contribute the custom scope annotation
name to the set of bean defining annotations.
CustomScopeBuildItem
Example
@BuildStep
CustomScopeBuildItem customScope() {
return new CustomScopeBuildItem(DotName.createSimple(TransactionScoped.class.getName()));
}
11. Use Case - Additional Interceptor Bindings
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
Example@BuildStep
InterceptorBindingRegistrarBuildItem addInterceptorBindings() {
return new InterceptorBindingRegistrarBuildItem(new InterceptorBindingRegistrar() {
@Override
public List<InterceptorBinding> getAdditionalBindings() {
return List.of(InterceptorBinding.of(NotAnInterceptorBinding.class));
}
});
}
12. Use Case - Additional Qualifiers
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
Example@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. Use Case - Additional Stereotypes
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
Example@BuildStep
StereotypeRegistrarBuildItem addStereotypes() {
return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() {
@Override
public Set<DotName> getAdditionalStereotypes() {
return Collections.singleton(DotName.createSimple(NotAStereotype.class.getName()));
}
});
}
If the newly registered stereotype annotation doesn’t have the appropriate meta-annotations, such as scope or interceptor bindings, use an annotation transformation to add them.
14. Use Case - Injection Point Transformation
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 transformation to injection points with type Foo
that contain
qualifier MyQualifier
:
InjectionPointTransformerBuildItem
Example@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();
}
}
});
}
In theory, you can use an
AnnotationsTransformer to achieve the same goal. However, there are few
differences that make InjectionPointsTransformer more suitable for this
particular task: (1) annotation transformers are applied to all classes
during bean discovery, whereas InjectionPointsTransformer is only applied
to discovered injection points after bean discovery; (2) with
InjectionPointsTransformer you don’t need to handle various types of
injection points (field, parameters of initializer methods, etc.).
|
15. Use Case - Resource Annotations and Injection
The ResourceAnnotationBuildItem
can be used to specify resource
annotations that make it possible to resolve non-CDI injection points, such
as Jakarta EE resources. An integrator must also provide a corresponding
io.quarkus.arc.ResourceReferenceProvider
service provider implementation.
ResourceAnnotationBuildItem
Example@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. Available Build Time Metadata
Any of the above extensions that operates with BuildExtension.BuildContext
can leverage certain build time metadata that are generated during build.
The built-in keys located in io.quarkus.arc.processor.BuildExtension.Key
are:
- ANNOTATION_STORE
-
Contains an
AnnotationStore
that keeps information about allAnnotationTarget
annotations after application of annotation transformers - INJECTION_POINTS
-
Collection<InjectionPointInfo>
containing all injection points - BEANS
-
Collection<BeanInfo>
containing all beans - REMOVED_BEANS
-
Collection<BeanInfo>
containing all the removed beans; see Removing unused beans for more information - OBSERVERS
-
Collection<ObserverInfo>
containing all observers - SCOPES
-
Collection<ScopeInfo>
containing all scopes, including custom ones - QUALIFIERS
-
Map<DotName, ClassInfo>
containing all qualifiers - INTERCEPTOR_BINDINGS
-
Map<DotName, ClassInfo>
containing all interceptor bindings - STEREOTYPES
-
Map<DotName, StereotypeInfo>
containing all stereotypes
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
-
Shouldn’t rely on any metadata as it could be used at any time in any phase of the bootstrap
- ContextRegistrar
-
Has access to
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
- InjectionPointsTransformer
-
Has access to
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
- ObserverTransformer
-
Has access to
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
- BeanRegistrar
-
Has access to
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
,BEANS
(class-based beans only),OBSERVERS
(class-based observers only),INJECTION_POINTS
- ObserverRegistrar
-
Has access to
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
,BEANS
,OBSERVERS
(class-based observers only),INJECTION_POINTS
- BeanDeploymentValidator
-
Has access to all build metadata