CDI Integration Guide

Arc,Quarkus 中的 CDI 容器,是在构建时自举的。为了与容器集成,可以使用 CDI Build Compatible Extensions,以及 Quarkus 特定的扩展 API。CDI 可移植扩展不能被支持,并且不支持。本指南重点关注 Quarkus 特定的扩展 API。 容器在多个阶段自举。从高级别的角度来看,这些阶段如下:

  1. Initialization

  2. Bean discovery

  3. Registration of synthetic components

  4. Validation

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

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

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

Metadata Sources

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

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

Use Case - My Class Is Not Recognized as a Bean

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

Reason 1: Class Is Not discovered

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

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

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

AdditionalBeanBuildItem Example
@BuildStep
AdditionalBeanBuildItem additionalBeans() {
     return new AdditionalBeanBuildItem(SmallRyeHealthReporter.class, HealthServlet.class); 1
}
1 AdditionalBeanBuildItem.Builder 可用于更复杂的使用案例。

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

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

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

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

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

Solution 1:使用 AutoAddScopeBuildItem。此构建项可用于向满足特定条件的类添加范围。

AutoAddScopeBuildItem Example
@BuildStep
AutoAddScopeBuildItem autoAddScope() {
   return AutoAddScopeBuildItem.builder().containsAnnotations(SCHEDULED_NAME, SCHEDULES_NAME) 1
      .defaultScope(BuiltinScope.SINGLETON) 2
      .build();
}
1 查找带有 @Scheduled 注释的所有类。
2 @Singleton 添加为默认范围。自动跳过已经用范围注释的类。

Solution 2:如果你需要处理带有特定注释的类,那么可以通过 BeanDefiningAnnotationBuildItem 扩展 bean 定义注释集。

BeanDefiningAnnotationBuildItem Example
@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
   return new BeanDefiningAnnotationBuildItem(Annotations.GRAPHQL_API); 1
}
1 org.eclipse.microprofile.graphql.GraphQLApi 添加到 bean 定义注释集。

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

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

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

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`来消除可能出现的误报。

UnremovableBeanBuildItem Example
@BuildStep
UnremovableBeanBuildItem unremovableBeans() {
   return UnremovableBeanBuildItem.targetWithAnnotation(STARTUP_NAME); 1
}
1 让所有使用 `@Startup`注释的类均无法移除。

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

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

Solution:根据 Reason 1: Class Is Not discovered中所述,使用 AdditionalBeanBuildItem

Use Case - I Need To Transform Annotation Metadata

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

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

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

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 该转换器仅适用于类。
2 仅当该类为 `org.acme.Bar`时才应用该转换。
3 Add the @MyInterceptorBinding annotation.

上面的示例可以使用匿名类重写:

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 该转换器仅适用于类。
2 如果类名等于 org.acme.Bar,则添加 @MyInterceptorBinding

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

生成步骤可以通过 `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()`将返回一组可能已转换的注释。

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

How to Enable Trace Logging for Annotation Transformers

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

application.properties Example
quarkus.log.category."io.quarkus.arc.processor".min-level=TRACE 1
quarkus.log.category."io.quarkus.arc.processor".level=TRACE
1 还需要调整相关类别的最低日志级别。

Use Case - Inspect Beans, Observers and Injection Points

Solution 1: BeanDiscoveryFinishedBuildItem

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

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

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 结果列表将不包含 @Named 的合成 Bean。

Solution 2: SynthesisFinishedBuildItem

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

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

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 结果列表将包含 @Named 个合成 bean。

Use Case - The Need for Synthetic Beans

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

Solution:如果你需要注册一个合成 bean,那么使用 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 生成 jakarta.enterprise.context.spi.Contextual#create(CreationalContext&lt;T&gt;) 实现的字节码。

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

  1. 通过 ExtendedBeanConfigurator.creator(Consumer&lt;MethodCreator&gt;) 直接生成 Contextual#create(CreationalContext&lt;T&gt;) 方法的字节码。

  2. 通过 ExtendedBeanConfigurator#creator(Class&lt;? extends BeanCreator&lt;U&gt;&gt;) 传递一个 io.quarkus.arc.BeanCreator 的子类,并可以通过 ExtendedBeanConfigurator#param()ExtendedBeanConfigurator#addInjectionPoint() 指定一些构建时参数还有合成注入点。

  3. 通过从一个 @Recorder method 返回的代理生成运行时实例,并通过 ExtendedBeanConfigurator#runtimeValue(RuntimeValue&lt;?&gt;)ExtendedBeanConfigurator#runtimeProxy(Object)ExtendedBeanConfigurator#supplier(Supplier&lt;?&gt;) 或者 ExtendedBeanConfigurator#createWith(Function&lt;SyntheticCreationalContext&lt;?&gt;, &lt;?&gt;) 设置它。

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 默认情况下,将在 STATIC_INIT 期间初始化合成 Bean。
2 Bean 实例由记录器方法返回的值提供。

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

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 记录器必须在 ExecutionTime.RUNTIME_INIT 阶段执行。
2 Bean 实例在 RUNTIME_INIT 期间被初始化。

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

@BuildStep
@Record(RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class) 1
void accessFoo(TestRecorder recorder) {
   recorder.foo(); 2
}
1 此构建步骤必须在 syntheticBean() 完成之后执行。
2 这种记录器方法导致对 Foo bean 实例进行调用,因此我们需要确保构建步骤在所有合成 bean 初始化后执行。

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

Synthetic Injection Points

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

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 Bean 实例在 RUNTIME_INIT 期间被初始化。
2 已添加必需类型为 Bar 的合成注入点;这与 @Inject Bar 等效。
3 bean 实例是使用记录器方法返回的函数创建的。
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 Bar 的上下文引用传递给 Foo 的构造函数。

Use Case - Synthetic Observers

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

Solution:如果需要注册合成观察器,请使用 ObserverRegistrationPhaseBuildItem

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

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

Use Case - I Have a Generated Bean Class

没问题。你可以手动生成 bean 类的字节码,然后你需要做的就是生成 GeneratedBeanBuildItem,而不是 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 使从 Gizmo 构造生成 GeneratedBeanBuildItem 变得很容易。
2 生成的 bean 类类似于 public class @Singleton MyBean { }

Use Case - I Need to Validate the Deployment

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

Solution:如果扩展需要验证部署,应使用 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。

Use Case - Register a Custom CDI Context

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

Solution: 如果需要注册自定义上下文,请使用 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 上下文的扩展也应生成 CustomScopeBuildItem,以便将自定义作用域注释名称贡献到 bean 定义注释的集合。

CustomScopeBuildItem 示例

@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()

Use Case - Additional Interceptor Bindings

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

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 来完成此操作。

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 来完成此操作。

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 来添加它们。

Use Case - Injection Point Transformation

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

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,你不需要处理各种类型的注入点(字段、初始值设定方法的参数等)。

Use Case - Resource Annotations and Injection

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

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 中的内置键为:

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。下面总结了哪些扩展可以访问哪些元数据:

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