- Quarkus - Contexts and Dependency Injection
- 1. Bean Discovery
- 2. Private Members
- 3. Supported Features
- 4. Limitations
- 5. Build Time Extension Points
- 5.1. Portable Extensions
- 5.2. Additional Bean Defining Annotations
- 5.3. Resource Annotations
- 5.4. Additional Beans
- 5.5. Synthetic Beans
- 5.6. Annotation Transformations
- 5.7. Additional Interceptor Bindings
- 5.8. Injection Point Transformation
- 5.9. Bean Deployment Validation
- 5.10. Custom Contexts
- 5.11. Available Build Time Metadata
- 6. Removing Unused Beans
- 7. Default Beans
Quarkus - Contexts and Dependency Injection
Quarkus DI solution is based on the Contexts and Dependency Injection for Java 2.0 specification.However, it is not a full CDI implementation verified by the TCK.Only a subset of the CDI features is implemented - see also the list of supported features and the list of limitations.
Most of the existing CDI code should work just fine but there are some small differences which follow from the Quarkus architecture and goals. |
1. Bean Discovery
Bean discovery in CDI is a complex process which involves legacy deployment structures and accessibility requirements of the underlying module architecture.However, Quarkus is using a simplified bean discovery.There is only single bean archive with the bean discovery mode annotated
and no visibility boundaries.
The bean archive is synthesized from:
the application classes,
dependencies that contain a
beans.xml
descriptor (content is ignored),dependencies that contain a Jandex index -
META-INF/jandex.idx
,dependencies referenced by
quarkus.index-dependency
inapplication.properties
,and Quarkus integration code.
Bean classes that don’t have a bean defining annotation are not discovered.This behavior is defined by CDI.But producer methods and fields and observer methods are discovered even if the declaring class is not annotated with a bean defining annotation (this behavior is different to what is defined in CDI).In fact, the declaring bean classes are considered annotated with @Dependent
.
Quarkus extensions may declare additional discovery rules. For example, @Scheduled business methods are registered even if the declaring class is not annotated with a bean defining annotation. |
1.1. How to Generate a Jandex Index
A dependency with a Jandex index is automatically scanned for beans.To generate the index just add the following to your pom.xml
:
<build>
<plugins>
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>1.0.6</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
If you can’t modify the dependency, you can still index it by adding quarkus.index-dependency
entries to your application.properties
:
quarkus.index-dependency.<name>.group-id=
quarkus.index-dependency.<name>.artifact-id=
quarkus.index-dependency.<name>.classifier=(this one is optional)
For example, the following entries ensure that the org.acme:acme-api
dependency is indexed:
quarkus.index-dependency.acme.group-id=org.acme (1)
quarkus.index-dependency.acme.artifact-id=acme-api (2)
1 | Value is a group id for a dependency identified by name acme . |
2 | Value is an artifact id for a dependency identified by name acme . |
2. Private Members
Quarkus is designed with Substrate VM in mind.One of the limitations is the usage of Reflection.Substrate VM does support reflective calls but for a price of a bigger native executable.Quarkus must use reflection fallback to access private members.That’s why Quarkus users are encouraged not to use private members in their beans.This involves injection fields, constructors and initializers, observer methods, producer methods and fields, disposers and interceptor methods.
You can use for example package-private modifiers:
@ApplicationScoped
public class CounterBean {
@Inject
CounterService counterService; (1)
void onMessage(@Observes Event msg) { (2)
}
}
1 | A package-private injection field. |
2 | A package-private observer method. |
Or constructor injection:
@ApplicationScoped
public class CounterBean {
private CounterService service;
CounterBean(CounterService service) { (1)
this.service = service;
}
}
1 | A package-private constructor injection. |
3. Supported Features
- Programming model
- Managed beans implemented by a Java class
-
@PostConstruct
and @PreDestroy
lifecycle callbacks
Producer methods and fields, disposers
Qualifiers
Alternatives
Stereotypes
- Dependency injection and lookup
Field, constructor and initializer/setter injection
Type-safe resolution
Programmatic lookup via
javax.enterprise.inject.Instance
Client proxies
Injection point metadata
- Scopes and contexts
@Dependent
,@ApplicationScoped
,@Singleton
,@RequestScoped
and@SessionScoped
Custom scopes and contexts
- Interceptors
Business method interceptors:
@AroundInvoke
Interceptors for lifecycle event callbacks:
@PostConstruct
,@PreDestroy
,@AroundConstruct
- Events and observers, including asynchronous events
4. Limitations
@ConversationScoped
is not supportedDecorators are not supported
Portable Extensions are not supported
BeanManager
- only the following methods are implemented:getBeans()
,createCreationalContext()
,getReference()
,getInjectableReference()
,resolve()
,getContext()
,fireEvent()
,getEvent()
andcreateInstance()
Specialization is not supported
beans.xml
descriptor content is ignoredPassivation and passivating scopes are not supported
Interceptor methods on superclasses are not implemented yet
BEFORE_COMPLETION
,AFTER_COMPLETION
,AFTER_FAILURE
andAFTER_SUCCESS
transactional observers are not implemented yet
5. Build Time Extension Points
5.1. Portable Extensions
Quarkus incorporates build-time optimizations in order to provide instant startup and low memory footprint.The downside of this approach is that CDI Portable Extensions cannot be supported.Nevertheless, most of the functionality can be achieved using Quarkus extensions.
5.2. Additional Bean Defining Annotations
As described in Bean Discovery bean classes that don’t have a bean defining annotation are not discovered.However, BeanDefiningAnnotationBuildItem
can be used to extend the set of default bean defining annotations (@Dependent
, @Singleton
, @ApplicationScoped
, @RequestScoped
and @Stereotype
annotations):
@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
return new BeanDefiningAnnotationBuildItem(DotName.createSimple("javax.ws.rs.Path")));
}
Bean registrations that are result of a BeanDefiningAnnotationBuildItem are unremovable by default. See also Removing Unused Beans. |
5.3. Resource Annotations
ResourceAnnotationBuildItem
is used to specify resource annotations that make it possible to resolve non-CDI injection points, such as Java EE resources.
An integrator must also provide a corresponding io.quarkus.arc.ResourceReferenceProvider implementation. |
@BuildStep
void setupResourceInjection(BuildProducer<ResourceAnnotationBuildItem> resourceAnnotations, BuildProducer<GeneratedResourceBuildItem> resources) {
resources.produce(new GeneratedResourceBuildItem("META-INF/services/io.quarkus.arc.ResourceReferenceProvider",
JPAResourceReferenceProvider.class.getName().getBytes()));
resourceAnnotations.produce(new ResourceAnnotationBuildItem(DotName.createSimple(PersistenceContext.class.getName())));
}
5.4. Additional Beans
AdditionalBeanBuildItem
is used to specify additional bean classes to be analyzed during discovery.Additional bean classes are transparently added to the application index processed by the container.
@BuildStep
List<AdditionalBeanBuildItem> additionalBeans() {
return Arrays.asList(
new AdditionalBeanBuildItem(SmallRyeHealthReporter.class),
new AdditionalBeanBuildItem(HealthServlet.class));
}
A bean registration that is a result of an AdditionalBeanBuildItem is removable by default. See also Removing Unused Beans. |
5.5. Synthetic Beans
Sometimes it is very useful to register a synthetic bean, i.e. a bean that doesn’t need to have a corresponding java class.In CDI this could be achieved using AfterBeanDiscovery.addBean()
methods.In Quarkus we produce a BeanRegistrarBuildItem
and leverage the io.quarkus.arc.processor.BeanConfigurator
API to build a synthetic bean definition.
@BuildStep
BeanRegistrarBuildItem syntheticBean() {
return new BeanRegistrarBuildItem(new BeanRegistrar() {
@Override
public void register(RegistrationContext registrationContext) {
registrationContext.configure(String.class).types(String.class).qualifiers(new MyQualifierLiteral()).creator(mc -> mc.returnValue(mc.load("foo"))).done();
}
}));
}
The output of a BeanConfigurator is recorded as bytecode. Therefore there are some limitations in how a synthetic bean instance is created. See also BeanConfigurator.creator() methods. |
If an extension needs to produce other build items during the "bean registration" phase it should use the BeanRegistrationPhaseBuildItem
instead.The reason is that injected objects are only valid during a @BuildStep
method invocation.
@BuildStep
void syntheticBean(BeanRegistrationPhaseBuildItem beanRegistrationPhase,
BuildProducer<MyBuildItem> myBuildItem,
BuildProducer<BeanConfiguratorBuildItem> beanConfigurators) {
beanConfigurators.produce(new BeanConfiguratorBuildItem(beanRegistrationPhase.getContext().configure(String.class).types(String.class).qualifiers(new MyQualifierLiteral()).creator(mc -> mc.returnValue(mc.load("foo")))));
myBuildItem.produce(new MyBuildItem());
}
See BeanRegistrationPhaseBuildItem javadoc for more information. |
5.6. Annotation Transformations
A very common task is to override the annotations found on the bean classes.For example you might want to add an interceptor binding to a specific bean class.Here is how to do it - use the AnnotationsTransformerBuildItem
:
@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;
}
public void transform(TransformationContext context) {
if (contex.getTarget().asClass().name().toString().equals("com.foo.Bar")) {
context.transform().add(MyInterceptorBinding.class).done();
}
}
});
}
5.7. Additional Interceptor Bindings
In rare cases it might be handy to programmatically register an existing annotation as interceptor binding.This is similar to what pure CDI achieves through BeforeBeanDiscovery#addInterceptorBinding()
.Though here we are going to use InterceptorBindingRegistrarBuildItem
to get it done.Note that you can register multiple annotations in one go:
@BuildStep
InterceptorBindingRegistrarBuildItem addInterceptorBindings() {
InterceptorBindingRegistrarBuildItem additionalBindingsRegistrar = new InterceptorBindingRegistrarBuildItem(new InterceptorBindingRegistrar() {
@Override
public Collection<DotName> registerAdditionalBindings() {
Collection<DotName> result = new HashSet<>();
result.add(DotName.createSimple(MyAnnotation.class.getName()));
result.add(DotName.createSimple(MyOtherAnnotation.class.getName()));
return result;
}
});
return additionalBindingsRegistrar;
}
5.8. Injection Point Transformation
Every now and then it is handy to be able to change 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
:
@BuildStep
InjectionPointTransformerBuildItem transform() {
return new InjectionPointTransformerBuildItem(new InjectionPointsTransformer() {
public boolean appliesTo(Type requiredType) {
return requiredType.equals(Type.create(DotName.createSimple(Foo.class.getName()), Type.Kind.CLASS));
}
public void transform(TransformationContext transformationContext) {
if (transformationContext.getQualifiers().stream()
.anyMatch(a -> a.name().equals(DotName.createSimple(MyQualifier.class.getName())))) {
transformationContext.transform().removeAll()
.add(DotName.createSimple(MyOtherQualifier.class.getName()))
.done();
}
}
});
}
5.9. Bean Deployment Validation
Once the bean deployment is ready an extension can perform additional validations and inspect the found beans, observers and injection points.Register a BeanDeploymentValidatorBuildItem
:
@BuildStep
BeanDeploymentValidatorBuildItem beanDeploymentValidator() {
return new BeanDeploymentValidatorBuildItem(new BeanDeploymentValidator() {
public void validate(ValidationContext validationContext) {
for (InjectionPointInfo injectionPoint : validationContext.get(Key.INJECTION_POINTS)) {
System.out.println("Injection point: " + injectionPoint);
}
}
});
}
See also io.quarkus.arc.processor.BuildExtension.Key to discover the available metadata. |
If an extension needs to produce other build items during the "validation" phase it should use the ValidationPhaseBuildItem
instead.The reason is that injected objects are only valid during a @BuildStep
method invocation.
@BuildStep
void validate(ValidationPhaseBuildItem validationPhase,
BuildProducer<MyBuildItem> myBuildItem,
BuildProducer<ValidationErrorBuildItem> errors) {
if (someCondition) {
errors.produce(new ValidationErrorBuildItem(new IllegalStateException()));
myBuildItem.produce(new MyBuildItem());
}
}
See ValidationPhaseBuildItem javadoc for more information. |
5.10. Custom Contexts
An extension can register a custom InjectableContext
implementation by means of a ContextRegistrarBuildItem
:
@BuildStep
ContextRegistrarBuildItem customContext() {
return new ContextRegistrarBuildItem(new ContextRegistrar() {
public void register(RegistrationContext registrationContext) {
registrationContext.configure(CustomScoped.class).normal().contextClass(MyCustomContext.class).done();
}
});
}
If an extension needs to produce other build items during the "context registration" phase it should use the ContextRegistrationPhaseBuildItem
instead.The reason is that injected objects are only valid during a @BuildStep
method invocation.
@BuildStep
void addContext(ContextRegistrationPhaseBuildItem contextRegistrationPhase,
BuildProducer<MyBuildItem> myBuildItem,
BuildProducer<ContextConfiguratorBuildItem> contexts) {
contexts.produce(new ContextConfiguratorBuildItem(contextRegistrationPhase.getContext().configure(CustomScoped.class).normal().contextClass(MyCustomContext.class)));
myBuildItem.produce(new MyBuildItem());
}
See ContextRegistrationPhaseBuildItem javadoc for more information. |
5.11. 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
List<InjectionPointInfo>
containing all injection points
BEANS
List<BeanInfo>
containing all beans
OBSERVERS
List<ObserverInfo>
containing all observers
SCOPES
List<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, ClassInfo>
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 build before they 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 this is one of the first CDI extensions invoked
ContextRegistrar
- Has access to
ANNOTATION_STORE
InjectionPointsTransformer
- Has access to
ANNOTATION_STORE
,QUALIFIERS
,INTERCEPTOR_BINDINGS
,STEREOTYPES
BeanRegistrar
- Has access to all build metadata
BeanDeploymentValidator
- Has access to all build metadata
6. Removing Unused Beans
The container attempts to remove all unused beans during build by default.This optimization can be disabled by setting quarkus.arc.remove-unused-beans
to none
or false
.
An unused bean:
is not a built-in bean or an interceptor,
is not eligible for injection to any injection point,
is not excluded by any extension,
does not have a name,
does not declare an observer,
does not declare any producer which is eligible for injection to any injection point,
is not directly eligible for injection into any
javax.enterprise.inject.Instance
orjavax.inject.Provider
injection point
This optimization applies to all forms of bean declarations: bean class, producer method, producer field.
Users can instruct the container to not remove any of their specific beans (even if they satisfy all the rules specified above) by annotating them with io.quarkus.arc.Unremovable
.This annotation can be placed on the types, producer methods, and producer fields.
Furthermore, extensions can eliminate possible false positives by producing UnremovableBeanBuildItem
.
Finally, Quarkus provides a middle ground for the bean removal optimization where application beans are never removed whether or not they are unused,while the optimization proceeds normally for non application classes. To use this mode, set quarkus.arc.remove-unused-beans
to fwk
or framework
.
When using the dev mode (running mvn clean compile quarkus:dev
), you can see more information about which beans are being removedby enabling additional logging via the following line in your application.properties
.
- quarkus.log.category."io.quarkus.arc.processor".level=DEBUG
7. Default Beans
Quarkus adds a capability that CDI currently does not support which is to conditionally declare a bean if no such bean has been declared in the typical ways Quarkus supports.This is done using the @io.quarkus.arc.DefaultBean
annotation and is best explained with an example.
Say there is a Quarkus extension that among other things declares a few CDI beans like the following code does:
@Dependent
public class TracerConfiguration {
@Produces
public Tracer tracer(Reporter reporter, Configuration configuration) {
return new Tracer(reporter, configuration);
}
@Produces
@DefaultBean
public Configuration configuration() {
// create a Configuration
}
@Produces
@DefaultBean
public Reporter reporter(){
// create a Reporter
}
}
The idea is that the extension auto-configures things for user, eliminating a lot of boilerplate - we can just @Inject
a Tracer
wherever it is needed.Now imagine that in our application we would like to utilize the configured Tracer
, but we need to customize it a little, for example by providing a custom Reporter
.The only thing that would be needed in our application would be something like the following:
@Dependent
public class CustomTracerConfiguration {
@Produces
public Reporter reporter(){
// create a custom Reporter
}
}
@DefaultBean
allows extensions (or any other code for that matter) to provide defaults while backing off if beans of that type are supplied in anyway Quarkus supports.