CDI Integration Guide

Arc,Quarkus 中的 CDI 容器,是在构建时自举的。为了与容器集成,可以使用 CDI Build Compatible Extensions,以及 Quarkus 特定的扩展 API。CDI 可移植扩展不能被支持,并且不支持。本指南重点关注 Quarkus 特定的扩展 API。

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:

  1. Initialization

  2. Bean discovery

  3. Registration of synthetic components

  4. Validation

initialization 阶段,预备工作正在进行,并且注册了自定义上下文。Bean discovery 然后是容器分析所有应用程序类、识别 bean 并根据提供的元数据将它们全部连接在一起的过程。随后,扩展可以注册 synthetic components。这些组件的属性完全由扩展控制,即,不是从现有类派生的。最后,是 deployment is validated。例如,容器验证应用程序中的每个注入点,并且如果没有满足给定所需类型和限定符的 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.

通过启用附加日志记录,你可以看到有关 bootstrap 更多信息。只需使用 -X--debug 运行 Maven 构建,然后 grep 包含 io.quarkus.arc 的行。在 dev mode 中,你可以使用 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG 并且会自动注册两个特殊端点,以 JSON 格式提供一些基本的调试信息。

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 构建步骤可以生成和使用各种构建项,并连接到每个阶段。在以下部分中,我们将描述所有相关的构建项和常见场景。

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.

Metadata Sources

类和注释是 bean 级元数据的来源。初始元数据从 bean archive index 中读取,这是一个不可变的 Jandex index,它在 bean discovery 期间由不同的来源构建。然而,扩展可以在 bootstrap 的某些阶段添加、移除或转换元数据。此外,扩展还可以注册 synthetic components。在 Quarkus 中集成 CDI 组件时,意识到这一点非常重要。

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_beans. This is an important aspect to realize when integrating CDI components in Quarkus.

通过这种方式,扩展可以将原本会被忽略的类转换成 bean,反之亦然。例如,声明 @Scheduled 方法的类始终注册为 bean,即使它未使用定义 bean 的注释进行注释,并且通常会被忽略。

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.

Use Case - My Class Is Not Recognized as a Bean

UnsatisfiedResolutionException 表示 typesafe resolution 期间有问题。有时即使类路径上存在一个似乎有资格注入的类,某个注入点也无法得到满足。一个类无法被识别的因素有很多,修复方法也有很多。在第一步中,我们应该识别 reason

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.

Reason 1: Class Is Not discovered

Quarkus 有 simplified discovery。有可能这个类不属于应用程序索引。例如,Quarkus 扩展的 runtime module 中的类不会自动编入索引。

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:使用 AdditionalBeanBuildItem。此构建项可用于指定在发现期间要分析的一个或多个附加类。附加 bean 类将透明地添加到容器处理的应用程序索引中。

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.

cdi-reference.adoc#enable_build_profilecdi-reference.adoc#enable_build_properties 中所述,无法通过 @IfBuildProfile@UnlessBuildProfile@IfBuildProperty@UnlessBuildProperty 注释有条件地启用/禁用附加 bean。扩展应检查配置或当前配置文件,并且仅在真正需要时才会生成 AdditionalBeanBuildItem

It is not possible to conditionally enable/disable additional beans via the @IfBuildProfile, @UnlessBuildProfile, @IfBuildProperty and @UnlessBuildProperty annotations as described in cdi-reference.adoc#enable_build_profile and cdi-reference.adoc#enable_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.

默认情况下,通过 AdditionalBeanBuildItem 添加的 bean 类会被 removable。如果容器认为它们 unused,它们就会被忽略。但是,你可以使用 AdditionalBeanBuildItem.Builder.setUnremovable() 方法指示容器永远不要删除通过此构建项注册的 bean 类。有关更多详细信息,请参见 Removing Unused BeansReason 3: Class Was Discovered and Has a Bean Defining Annotation but Was Removed

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.

也可以通过 AdditionalBeanBuildItem.Builder#setDefaultScope() 设置默认范围。仅当 bean 类上未声明范围时才使用默认范围。

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.

如果没有指定默认范围,则使用 @Dependent 伪范围。

If no default scope is specified the @Dependent pseudo-scope is used.

Reason 2: Class Is Discovered but Has No Bean Defining Annotation

在 Quarkus 中,应用程序由具有 bean discovery mode annotated 的单个 bean 存档表示。因此,忽略了没有 bean defining annotation 的 bean 类。bean 定义注释在类级别上声明,包括范围、构造型和 @Interceptor

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:使用 AutoAddScopeBuildItem。此构建项可用于向满足特定条件的类添加范围。

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:如果你需要处理带有特定注释的类,那么可以通过 BeanDefiningAnnotationBuildItem 扩展 bean 定义注释集。

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.

默认情况下,通过 BeanDefiningAnnotationBuildItem 添加的 bean 类会被 not removable,即,即使结果 bean 被认为未使用,也不得删除。但是,你可以更改默认行为。有关更多详细信息,请参见 Removing Unused BeansReason 3: Class Was Discovered and Has a Bean Defining Annotation but Was Removed

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.

还可以指定默认范围。默认范围仅在没有在 bean 类上声明范围时使用。

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.

如果没有指定默认范围,则使用 @Dependent 伪范围。

If no default scope is specified the @Dependent pseudo-scope is used.

Reason 3: Class Was Discovered and Has a Bean Defining Annotation but Was Removed

容器默认尝试在生成期间执行 remove all unused beans。这一优化有助于 framework-level dead code elimination。在少数特殊情况下,无法正确标识未使用的 bean。特别是,Quarkus 尚不能检测到 `CDI.current()`静态方法的使用。扩展可以通过生成 `UnremovableBeanBuildItem`来消除可能出现的误报。

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.

Use Case - My Annotation Is Not Recognized as a Qualifier or an Interceptor Binding

很有可能是注释类不属于应用程序索引。例如,来自 Quarkus 扩展的 _runtime module_中的类不会自动编入索引。

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:根据 Reason 1: Class Is Not discovered中所述,使用 AdditionalBeanBuildItem

Solution: Use the AdditionalBeanBuildItem as described in Reason 1: Class Is Not discovered.

Use Case - I Need To Transform Annotation Metadata

在某些情况下,能够修改注释元数据非常有用。Quarkus 提供了 jakarta.enterprise.inject.spi.ProcessAnnotatedTypejakarta.enterprise.inject.build.compatible.spi.Enhancement的强大替代方法。借助 AnnotationsTransformerBuildItem,可以覆盖 bean 类上存在的注释。

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.

请记住,注释转换器必须在 _before_开始 bean 发现后生成。

Keep in mind that annotation transformers must be produced before the bean discovery starts.

例如,您可能希望向特定 bean 类添加拦截器绑定。可以使用方便的生成器 API 来创建转换实例:

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:

Builder Example
@BuildStep
AnnotationsTransformerBuildItem transform() {
    return new AnnotationsTransformerBuildItem(AnnotationTransformation.forClasses() (1)
        .whenClass(DotName.createSimple("org.acme.Bar")) (2)
        .transform(t -> t.add(MyInterceptorBinding.class))); (3)
}
1 The transformer is only applied to classes.
2 Only apply the transformation if the class is 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 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 The transformer is only applied to classes.
2 If the class name equals to org.acme.Bar then add @MyInterceptorBinding.

仍然支持 ArC 中旧的 `AnnotationsTransformer`API,但更建议使用 Jandex 中新的 `AnnotationTransformation`API。

The previous AnnotationsTransformer API from ArC is still supported, but the new AnnotationTransformation API from Jandex is preferred.

生成步骤可以通过 `TransformedAnnotationsBuildItem`对给定注释目标进行转换后的注释进行查询。

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.

还有其他一些专门用于转换的生成项目: Use Case - Additional Interceptor BindingsUse Case - Injection Point Transformation

There are other build items specialized in transformation: Use Case - Additional Interceptor Bindings and Use Case - Injection Point Transformation.

How to Enable Trace Logging for Annotation Transformers

可以为类别 `io.quarkus.arc.processor`设置 `TRACE`级别,然后尝试分析日志输出。

You can set the TRACE level for the category io.quarkus.arc.processor and try to analyze the log output afterwards.

application.properties Example
quarkus.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.

Use Case - Inspect Beans, Observers and Injection Points

Solution 1: BeanDiscoveryFinishedBuildItem

`BeanDiscoveryFinishedBuildItem`的使用者可以轻松检查应用程序中注册的所有基于类的 bean、观察者和注入点。但是,虚假 bean 和观察者是 _not included_的,因为此生成项目是在 _before_注册虚假组件后生成的。

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.

此外,还可以使用 BeanDiscoveryFinishedBuildItem#getBeanResolver() 返回的 Bean 解析器应用类型安全解析规则,例如,找出是否存在某个 Bean 将满足特定组合的必需类型和限定符。

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.

Solution 2: SynthesisFinishedBuildItem

SynthesisFinishedBuildItem 的使用方可以轻松地检查应用程序中注册的所有 Bean、观察者和注入点。包含合成 Bean 和观察者,这是因为已注册合成组件时生成了此生成项目 after

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.

另外,可以利用 SynthesisFinishedBuildItem#getBeanResolver() 返回的 bean 解析器应用类型安全的解析规则,比如说,找出是否存在某个 bean 能够满足特定类型组合和限定符条件。

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.

Use Case - The Need for Synthetic Beans

有时候,注册 synthetic bean.Bean 是实用的。合成 bean 的属性不是来自 Java 类、方法或字段。而是,所有属性都由扩展定义。在常规 CDI 中,可以使用 AfterBeanDiscovery.addBean()SyntheticComponents.addBean() 方法实现这一点。

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:如果你需要注册一个合成 bean,那么使用 SyntheticBeanBuildItem.

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.

bean 配置器的输出被记录为字节码。因此,在运行时创建合成 bean 实例的方式存在一些限制。你可以:

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:

  1. Generate the bytecode of the Contextual#create(CreationalContext<T>) method directly via ExtendedBeanConfigurator.creator(Consumer<MethodCreator>).

  2. Pass a subclass of io.quarkus.arc.BeanCreator via ExtendedBeanConfigurator#creator(Class<? extends BeanCreator<U>>), and possibly specify some build-time parameters via ExtendedBeanConfigurator#param() and synthetic injection points via ExtendedBeanConfigurator#addInjectionPoint().

  3. Produce the runtime instance through a proxy returned from a @Recorder method and set it via ExtendedBeanConfigurator#runtimeValue(RuntimeValue<?>), ExtendedBeanConfigurator#runtimeProxy(Object), ExtendedBeanConfigurator#supplier(Supplier<?>) or ExtendedBeanConfigurator#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.

可以在 RUNTIME_INIT 期间标记一个合成 Bean 以便被初始化。更多有关 STATIC_INITRUNTIME_INIT 之间差异的信息,请参见 Three Phases of Bootstrap and Quarkus Philosophy

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.

RUNTIME_INIT 期间初始化的合成 Bean 在 STATIC_INIT 期间不得被访问。访问运行时初始化合成 Bean 的 RUNTIME_INIT 构建步骤应该使用 SyntheticBeansRuntimeInitBuildItem

Synthetic beans initialized during RUNTIME_INIT must not be accessed during STATIC_INIT. RUNTIME_INIT build steps that access a runtime-init synthetic bean should consume the SyntheticBeansRuntimeInitBuildItem:

@BuildStep
@Record(RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class) 1
void accessFoo(TestRecorder recorder) {
   recorder.foo(); 2
}
1 This build step must be executed after syntheticBean() completes.
2 This recorder method results in an invocation upon the Foo bean instance and thus we need to make sure that the build step is executed after all synthetic beans are initialized.

还可以使用 BeanRegistrationPhaseBuildItem 注册合成 bean。不过,我们建议扩展作者坚持使用 SyntheticBeanBuildItem,这对 Quarkus 来说更合乎惯例。

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.

Synthetic Injection Points

合成 bean 可通过 ExtendedBeanConfigurator#addInjectionPoint() 方法注册合成注入点。此注入点在构建时进行验证,并在 detecting unused beans 时考虑。注入的引用可通过 SyntheticCreationalContext#getInjectedReference() 方法在运行时访问。

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.

Synthetic Injection Point - Build Step Example
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.
Synthetic Injection Point - Recorder Example
@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.

Use Case - Synthetic Observers

synthetic beans 类似,合成观察器方法的属性不是从 Java 方法派生的。相反,所有属性均由扩展定义。

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:如果需要注册合成观察器,请使用 ObserverRegistrationPhaseBuildItem

Solution: If you need to register a synthetic observer, use the ObserverRegistrationPhaseBuildItem.

使用 ObserverRegistrationPhaseBuildItem 的构建步骤始终应该生成 ObserverConfiguratorBuildItem,或者至少为此构建项目注入 BuildProducer,否则它可能会被忽略或在错误的时间处理(例如,在正确的 CDI 引导阶段之后)。

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());
}

ObserverConfigurator 的输出记录为字节码。因此,在运行时如何调用合成观察器存在一些限制。目前,你必须直接生成方法体的字节码。

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.

Use Case - I Have a Generated Bean Class

没问题。你可以手动生成 bean 类的字节码,然后你需要做的就是生成 GeneratedBeanBuildItem,而不是 GeneratedClassBuildItem

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 { }.

Use Case - I Need to Validate the Deployment

有时扩展需要检查 bean、观察器和注入点,然后执行其他验证,并在出现问题时使构建失败。

Sometimes extensions need to inspect the beans, observers and injection points, then perform additional validations and fail the build if something is wrong.

Solution:如果扩展需要验证部署,应使用 ValidationPhaseBuildItem

Solution: If an extension needs to validate the deployment it should use the ValidationPhaseBuildItem.

使用 ValidationPhaseBuildItem 的构建步骤始终应该生成 ValidationErrorBuildItem,或者至少为此构建项目注入 BuildProducer,否则它可能会被忽略或在错误的时间处理(例如,在正确的 CDI 引导阶段之后)。

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());
   }
}

你可以从 ValidationPhaseBuildItem.getContext().beans() 方法返回的便捷 BeanStream 轻松筛选所有已注册的 bean。

You can easily filter all registered beans via the convenient BeanStream returned from the ValidationPhaseBuildItem.getContext().beans() method.

Use Case - Register a Custom CDI Context

有时扩展需要扩展内置 CDI 上下文的集合。

Sometimes extensions need to extend the set of built-in CDI contexts.

Solution: 如果需要注册自定义上下文,请使用 ContextRegistrationPhaseBuildItem.

Solution: If you need to register a custom context, use the ContextRegistrationPhaseBuildItem.

会消耗 ContextRegistrationPhaseBuildItem 的构建步骤始终应产生 ContextConfiguratorBuildItem 或至少为该构建项目注入 BuildProducer,否则可能会忽略此构建项目或者在错误的时间(例如在正确的 CDI 自举阶段之后)处理此构建项目。

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 示例

ContextRegistrationPhaseBuildItem Example

@BuildStep
ContextConfiguratorBuildItem registerContext(ContextRegistrationPhaseBuildItem phase) {
      return new ContextConfiguratorBuildItem(phase.getContext().configure(TransactionScoped.class).normal().contextClass(TransactionContext.class));
}

此外,每个通过 ContextRegistrationPhaseBuildItem 注册自定义 CDI 上下文的扩展也应生成 CustomScopeBuildItem,以便将自定义作用域注释名称贡献到 bean 定义注释的集合。

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 示例

CustomScopeBuildItem Example

@BuildStep
CustomScopeBuildItem customScope() {
   return new CustomScopeBuildItem(DotName.createSimple(TransactionScoped.class.getName()));
}

What if I Need to Know All the Scopes Used in the Application?

Solution: 你可以在构建步骤中注入 CustomScopeAnnotationsBuildItem,并使用方便的方法,例如 CustomScopeAnnotationsBuildItem.isScopeDeclaredOn()

Solution: You can inject the CustomScopeAnnotationsBuildItem in a build step and use the convenient methods such as CustomScopeAnnotationsBuildItem.isScopeDeclaredOn().

Use Case - Additional Interceptor Bindings

在罕见的情况下,可能很方便以编程方式注册未通过 @jakarta.interceptor.InterceptorBinding 注释为拦截器绑定的现有注释。这类似于 CDI 通过 BeforeBeanDiscovery#addInterceptorBinding() 实现的效果。我们要使用 InterceptorBindingRegistrarBuildItem 来完成此操作。

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));
        }
    });
}

Use Case - Additional Qualifiers

有时可能需要注册未通过 @jakarta.inject.Qualifier 注释为 CDI 限定符的现有注释。这类似于 CDI 通过 BeforeBeanDiscovery#addQualifier() 实现的效果。我们要使用 QualifierRegistrarBuildItem 来完成此操作。

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());
        }
    });
}

Use Case - Additional Stereotypes

有时将未通过 @jakarta.enterprise.inject.Stereotype 注释为 CDI 刻板模式的现有注释进行注册很有用。这类似于 CDI 通过 BeforeBeanDiscovery#addStereotype() 实现的效果。我们要使用 StereotypeRegistrarBuildItem 来完成此操作。

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()));
        }
    });
}

如果新注册的刻板模式注释没有适当的元注释,例如作用域或拦截器绑定,请使用 annotation transformation 来添加它们。

If the newly registered stereotype annotation doesn’t have the appropriate meta-annotations, such as scope or interceptor bindings, use an annotations_transformer_build_item to add them.

Use Case - Injection Point Transformation

每隔一段时间就有必要能够以编程方式更改注入点的限定符。你可以使用 InjectionPointTransformerBuildItem 来执行此操作。以下示例展示了如何对包含限定符 MyQualifier 的类型为 Foo 的注入点应用转换:

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();
            }
        }
    });
}

理论上,可以使用 an AnnotationsTransformer 来实现相同目标。然而,InjectionPointsTransformer 更适合这项特殊任务,这是因为存在几个导致此情况的原因:(1)在 bean 发现期间将注释转换器应用于所有类,而 InjectionPointsTransformer 仅在 bean 发现之后应用于已发现的注入点;(2) 使用 InjectionPointsTransformer,你不需要处理各种类型的注入点(字段、初始值设定方法的参数等)。

In theory, you can use annotations_transformer_build_item 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.).

Use Case - Resource Annotations and Injection

ResourceAnnotationBuildItem 可用于指定资源注释,这样便于解析非 CDI 注入点,例如 Jakarta EE 资源。集成商还必须提供相应的 io.quarkus.arc.ResourceReferenceProvider 服务供应商实施。

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())));
}

Available Build Time Metadata

使用 BuildExtension.BuildContext 运行的上述任何扩展都可以利用构建期间生成的一些构建时间元数据。位于 io.quarkus.arc.processor.BuildExtension.Key 中的内置键为:

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 all AnnotationTarget 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

要掌握这些键,只需针对给定的键查询扩展上下文对象。请注意,这些元数据在构建过程中作为可用信息,这意味着扩展只能利用在调用扩展之前构建的元数据。如果你的扩展尝试检索尚未生成的元数据,则会返回 null。下面总结了哪些扩展可以访问哪些元数据:

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