编辑此页面

CDI 集成指南

ArC 是 Quarkus 中的 CDI 容器,它在构建时启动。要与容器集成,可以使用CDI Build Compatible Extensions,以及特定于 Quarkus 的扩展 API。CDI Portable Extensions 不支持且无法支持。本指南重点介绍特定于 Quarkus 的扩展 API。

容器在多个阶段启动。从高层次的角度来看,这些阶段如下所示

  1. 初始化

  2. Bean 发现

  3. 合成组件的注册

  4. 验证

初始化阶段,执行准备工作并注册自定义上下文。Bean 发现是容器分析所有应用程序类,识别 Bean 并基于提供的元数据将它们连接在一起的过程。随后,扩展可以注册合成组件。这些组件的属性完全由扩展控制,即不是从现有类派生的。最后,验证部署。例如,容器会验证应用程序中的每个注入点,如果不存在满足给定必需类型和限定符的 Bean,则构建失败。

您可以通过启用其他日志记录来查看有关引导的更多信息。只需使用 -X--debug 运行 Maven 构建,然后 grep 包含 io.quarkus.arc 的行。在 开发模式下,您可以使用 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG,并且还会自动注册两个特殊端点,以 JSON 格式提供一些基本调试信息。

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

1. 元数据源

类和注解是 Bean 级别元数据的主要来源。初始元数据从 _bean 存档索引_中读取,这是一个不可变的 Jandex 索引,它是在 Bean 发现期间从各种源构建的。但是,扩展可以在引导的特定阶段添加、删除或转换元数据。此外,扩展还可以注册 合成组件。这是在 Quarkus 中集成 CDI 组件时要意识到的一个重要方面。

这样,扩展可以将原本会被忽略的类变成 Bean,反之亦然。例如,声明 @Scheduled 方法的类始终注册为 Bean,即使它没有使用 Bean 定义注解进行注解,并且通常会被忽略。

2. 用例 - 我的类未被识别为 Bean

UnsatisfiedResolutionException 指示在 类型安全解析期间出现问题。有时,即使类路径上存在看起来符合注入条件的类,也无法满足注入点。类未被识别的原因有多种,解决方法也有多种。第一步我们应该确定原因

2.1. 原因 1:未发现类

Quarkus 具有 简化的发现。可能该类不是应用程序索引的一部分。例如,Quarkus 扩展的运行时模块中的类不会自动建立索引。

解决方案:使用 AdditionalBeanBuildItem。此构建项可用于指定在发现期间要分析的一个或多个其他类。其他 Bean 类以透明方式添加到容器处理的应用程序索引中。

无法像 cdi-referencecdi-reference 中描述的那样,通过 @IfBuildProfile@UnlessBuildProfile@IfBuildProperty@UnlessBuildProperty 注解有条件地启用/禁用其他 Bean。扩展应检查配置或当前配置文件,并且仅在真正需要时才生成 AdditionalBeanBuildItem
AdditionalBeanBuildItem 示例
@BuildStep
AdditionalBeanBuildItem additionalBeans() {
     return new AdditionalBeanBuildItem(SmallRyeHealthReporter.class, HealthServlet.class); (1)
}
1 AdditionalBeanBuildItem.Builder 可用于更复杂的用例。

默认情况下,通过 AdditionalBeanBuildItem 添加的 Bean 类是可删除的。如果容器认为它们 未使用,则它们将被忽略。但是,您可以使用 AdditionalBeanBuildItem.Builder.setUnremovable() 方法来指示容器永远不要删除通过此构建项注册的 Bean 类。另请参阅 删除未使用的 Bean原因 3:已发现类并且具有 Bean 定义注解但已被删除 以了解更多详细信息。

也可以通过 AdditionalBeanBuildItem.Builder#setDefaultScope() 设置默认作用域。默认作用域仅在 Bean 类上未声明作用域时使用。

如果未指定默认作用域,则使用 @Dependent 伪作用域。

2.2. 原因 2:已发现类但没有 Bean 定义注解

在 Quarkus 中,应用程序由单个 Bean 存档表示,具有 Bean 发现模式 annotated。因此,没有 Bean 定义注解的 Bean 类将被忽略。Bean 定义注解在类级别声明,包括作用域、构造型和 @Interceptor

解决方案 1:使用 AutoAddScopeBuildItem。此构建项可用于向满足某些条件的类添加作用域。

AutoAddScopeBuildItem 示例
@BuildStep
AutoAddScopeBuildItem autoAddScope() {
   return AutoAddScopeBuildItem.builder().containsAnnotations(SCHEDULED_NAME, SCHEDULES_NAME) (1)
      .defaultScope(BuiltinScope.SINGLETON) (2)
      .build();
}
1 查找所有使用 @Scheduled 注解的类。
2 添加 @Singleton 作为默认作用域。已使用作用域注解的类将自动跳过。

解决方案 2:如果您需要处理使用特定注解进行注解的类,则可以通过 BeanDefiningAnnotationBuildItem 扩展 Bean 定义注解的集合。

BeanDefiningAnnotationBuildItem 示例
@BuildStep
BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
   return new BeanDefiningAnnotationBuildItem(Annotations.GRAPHQL_API); (1)
}
1 org.eclipse.microprofile.graphql.GraphQLApi 添加到 Bean 定义注解的集合中。

默认情况下,通过 BeanDefiningAnnotationBuildItem 添加的 Bean 类是不可删除的,即即使 Bean 被视为未使用,也不得删除生成的 Bean。但是,您可以更改默认行为。另请参阅 删除未使用的 Bean原因 3:已发现类并且具有 Bean 定义注解但已被删除 以了解更多详细信息。

也可以指定默认作用域。默认作用域仅在 Bean 类上未声明作用域时使用。

如果未指定默认作用域,则使用 @Dependent 伪作用域。

2.3. 原因 3:已发现类并且具有 Bean 定义注解但已被删除

默认情况下,容器会在构建期间尝试 删除所有未使用的 Bean。此优化允许框架级别的死代码消除。在少数特殊情况下,无法正确识别未使用的 Bean。特别是,Quarkus 尚无法检测到 CDI.current() 静态方法的使用。扩展可以通过生成 UnremovableBeanBuildItem 来消除可能的误报。

UnremovableBeanBuildItem 示例
@BuildStep
UnremovableBeanBuildItem unremovableBeans() {
   return UnremovableBeanBuildItem.targetWithAnnotation(STARTUP_NAME); (1)
}
1 使所有使用 @Startup 注解的类不可删除。

3. 用例 - 我的注解未被识别为限定符或拦截器绑定

注解类很可能不是应用程序索引的一部分。例如,Quarkus 扩展的运行时模块中的类不会自动建立索引。

解决方案:使用 原因 1:未发现类 中描述的 AdditionalBeanBuildItem

4. 用例 - 我需要转换注解元数据

在某些情况下,能够修改注解元数据非常有用。Quarkus 提供了一种强大的替代方案来替代 jakarta.enterprise.inject.spi.ProcessAnnotatedTypejakarta.enterprise.inject.build.compatible.spi.Enhancement。使用 AnnotationsTransformerBuildItem 可以覆盖 Bean 类上存在的注解。

请记住,注解转换器必须在 Bean 发现开始之前生成。

例如,您可能想要将拦截器绑定添加到特定的 Bean 类。您可以使用方便的构建器 API 创建转换实例

构建器示例
@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 添加 @MyInterceptorBinding 注解。

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

AnnotationsTransformerBuildItem 示例
@BuildStep
AnnotationsTransformerBuildItem transform() {
    return new AnnotationsTransformerBuildItem(new AnnotationTransformation() {
        public boolean supports(AnnotationTarget.Kind kind) {
            return kind == AnnotationTarget.Kind.CLASS; (1)
        }

        public void apply(TransformationContext context) {
            if (context.declaration().asClass().name().toString().equals("org.acme.Bar")) {
                context.add(MyInterceptorBinding.class); (2)
            }
        }
    });
}
1 转换器仅应用于类。
2 如果类名等于 org.acme.Bar,则添加 @MyInterceptorBinding
来自 ArC 的先前 AnnotationsTransformer API 仍然受支持,但首选来自 Jandex 的新 AnnotationTransformation API。

构建步骤可以通过 TransformedAnnotationsBuildItem 查询给定注解目标的转换注解。

TransformedAnnotationsBuildItem 示例
@BuildStep
void queryAnnotations(TransformedAnnotationsBuildItem transformedAnnotations,
        BuildProducer<MyBuildItem> myBuildItem) {
    ClassInfo myClazz = ...;
    if (transformedAnnotations.getAnnotations(myClazz).isEmpty()) { (1)
        myBuildItem.produce(new MyBuildItem());
    }
}
1 TransformedAnnotationsBuildItem.getAnnotations() 将返回一组可能转换的注解。
还有其他专门用于转换的构建项:用例 - 其他拦截器绑定用例 - 注入点转换

4.1. 如何为注解转换器启用跟踪日志记录

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

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

5. 用例 - 检查 Bean、观察者和注入点

5.1. 解决方案 1: BeanDiscoveryFinishedBuildItem

BeanDiscoveryFinishedBuildItem 的使用者可以轻松地检查应用程序中注册的所有基于类的 Bean、观察者和注入点。但是,不包括合成 Bean 和观察者,因为此构建项在注册合成组件之前生成。

此外,从 BeanDiscoveryFinishedBuildItem#getBeanResolver() 返回的 Bean 解析器可用于应用类型安全解析规则,例如,确定是否存在满足必需类型和限定符的特定组合的 Bean。

BeanDiscoveryFinishedBuildItem 示例
@BuildStep
void doSomethingWithNamedBeans(BeanDiscoveryFinishedBuildItem beanDiscovery, BuildProducer<NamedBeansBuildItem> namedBeans) {
   List<BeanInfo> namedBeans = beanDiscovery.beanStream().withName().collect(toList())); (1)
   namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
1 生成的列表将不包含 @Named 合成 Bean。

5.2. 解决方案 2: SynthesisFinishedBuildItem

SynthesisFinishedBuildItem 的使用者可以轻松地检查应用程序中注册的所有 Bean、观察者和注入点。由于此构建项在注册合成组件之后生成,因此包括合成 Bean 和观察者。

此外,从 SynthesisFinishedBuildItem#getBeanResolver() 返回的 Bean 解析器可用于应用类型安全解析规则,例如,确定是否存在满足必需类型和限定符的特定组合的 Bean。

SynthesisFinishedBuildItem 示例
@BuildStep
void doSomethingWithNamedBeans(SynthesisFinishedBuildItem synthesisFinished, BuildProducer<NamedBeansBuildItem> namedBeans) {
   List<BeanInfo> namedBeans = synthesisFinished.beanStream().withName().collect(toList())); (1)
   namedBeans.produce(new NamedBeansBuildItem(namedBeans));
}
1 生成的列表将包含 @Named 合成 Bean。

6. 用例 - 需要合成 Bean

有时能够注册合成 Bean 是很实用的。合成 Bean 的 Bean 属性不是从 Java 类、方法或字段派生的。相反,所有属性都由扩展定义。在常规 CDI 中,这可以使用 AfterBeanDiscovery.addBean()SyntheticComponents.addBean() 方法来实现。

解决方案:如果您需要注册合成 Bean,请使用 SyntheticBeanBuildItem

SyntheticBeanBuildItem 示例 1
@BuildStep
SyntheticBeanBuildItem syntheticBean() {
   return SyntheticBeanBuildItem.configure(String.class)
             .qualifiers(AnnotationInstance.builder(MyQualifier.class).build())
             .creator(mc -> mc.returnValue(mc.load("foo"))) (1)
             .done();
}
1 生成 jakarta.enterprise.context.spi.Contextual#create(CreationalContext<T>) 实现的字节码。

Bean 配置器的输出记录为字节码。因此,在运行时如何创建合成 Bean 实例存在一些限制。您可以

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

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

  3. 通过从 @Recorder 方法 返回的代理生成运行时实例,并通过 ExtendedBeanConfigurator#runtimeValue(RuntimeValue<?>)ExtendedBeanConfigurator#runtimeProxy(Object)ExtendedBeanConfigurator#supplier(Supplier<?>)ExtendedBeanConfigurator#createWith(Function<SyntheticCreationalContext<?>, <?>) 设置它。

SyntheticBeanBuildItem 示例 2
@BuildStep
@Record(STATIC_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
                .runtimeValue(recorder.createFoo()) (2)
                .done();
}
1 默认情况下,合成 Bean 在 STATIC_INIT 期间初始化。
2 Bean 实例由从记录器方法返回的值提供。

也可以创建一个泛型合成 Bean Foo<Bar>

SyntheticBeanBuildItem 示例 3
@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class)
                .types(ParameterizedType.create(Foo.class, ClassType.create(Bar.class)))) (1)
                .scope(Singleton.class)
                .runtimeValue(recorder.createFooBar())
                .done();
}
1 必须使用 types()addType() 来指定泛型类型。

可以将合成 Bean 标记为在 RUNTIME_INIT 期间初始化。有关 STATIC_INITRUNTIME_INIT 之间的差异的更多信息,请参见 引导的三个阶段和 Quarkus 哲学

RUNTIME_INIT SyntheticBeanBuildItem 示例
@BuildStep
@Record(RUNTIME_INIT) (1)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
   return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class)
                .setRuntimeInit() (2)
                .runtimeValue(recorder.createFoo())
                .done();
}
1 记录器必须在 ExecutionTime.RUNTIME_INIT 阶段执行。
2 Bean 实例在 RUNTIME_INIT 期间初始化。

STATIC_INIT 期间不得访问在 RUNTIME_INIT 期间初始化的合成 Bean。访问运行时初始化合成 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。但是,我们建议扩展作者坚持使用对于 Quarkus 来说更惯用的 SyntheticBeanBuildItem

6.1. 合成注入点

合成 Bean 可以通过 ExtendedBeanConfigurator#addInjectionPoint() 方法注册合成注入点。此注入点在构建时进行验证,并在 检测未使用的 Bean 时考虑。注入的引用可以在运行时通过 SyntheticCreationalContext#getInjectedReference() 方法访问。

合成注入点 - 构建步骤示例
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 实例是使用从记录器方法返回的函数创建的。
合成注入点 - 记录器示例
@Recorder
public class TestRecorder {

   public Function<SyntheticCreationalContext<Foo>, Foo> createFoo() {
     return (context) -> {
        return new Foo(context.getInjectedReference(Bar.class)); (1)
     };
   }
}
1 Bar 的上下文引用传递给 Foo 的构造函数。

6.2. 不活动的合成 Bean

如果需要在构建时注册多个合成 Bean,但只希望在运行时激活其中的一部分,则能够将合成 Bean 标记为不活动是很有用的。这可以通过配置“检查活动”过程来完成,该过程应是从记录器获得的 Supplier<ActiveResult>

不活动的合成 Bean - 构建步骤示例
@BuildStep
@Record(RUNTIME_INIT)
SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) {
    return SyntheticBeanBuildItem.configure(Foo.class)
            .scope(Singleton.class)
            .startup() (1)
            .checkActive(recorder.isFooActive()) (2)
            .createWith(recorder.createFoo())
            .done();
}
1 可能不活动的 Bean 通常会急切地初始化,以确保在应用程序启动时引发错误。如果 Bean 实际上是不活动的,但没有注入到始终活动的 Bean 中,则会跳过急切初始化,并且不会引发任何错误。
2 配置“检查活动”过程。
不活动的合成 Bean - 记录器示例
@Recorder
public class TestRecorder {
    public Supplier<ActiveResult> isFooActive() {
        return () -> {
            if (... should not be active ...) { (1)
                return ActiveResult.inactive("explanation"); (2)
            }
            return ActiveResult.active();
        };
    }

    public Function<SyntheticCreationalContext<Foo>, Foo> createFoo() {
        return (context) -> {
            return new Foo();
        };
    }
}
1 合成 Bean 应不活动的条件。
2 对 Bean 不活动原因的正确解释。如果此 Bean 的不活动源于另一个 Bean 的不活动,则也可以提供另一个不活动的 ActiveResult 作为原因。

如果将不活动的 Bean 注入到某个位置,或者动态查找不活动的 Bean,则会引发 InactiveBeanException。错误消息包含原因(来自 ActiveResult)、原因链(也来自 ActiveResult)以及可能还包含解析为该 Bean 的所有注入点的列表。

如果您想要优雅地处理不活动的情况,则应始终使用 Instance<> 注入可能不活动的 Bean。您还需要在获取实际实例之前进行检查

import io.quarkus.arc.InjectableInstance;

@Inject
InjectableInstance<Foo> foo;

if (foo.getHandle().getBean().isActive()) {
    Foo foo = foo.get();
    ...
}

如果您只想使用活动的 Bean,则可以注入 InjectableInstance<> 并调用 getActive() 来获取单个实例,或调用 listActive() 来获取所有实例

import io.quarkus.arc.InjectableInstance;

@Inject
@Any
InjectableInstance<Foo> foos;

for (Foo foo : foos.listActive())
    ...
}

7. 用例 - 合成观察者

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

解决方案:如果您需要注册合成观察者,请使用 ObserverRegistrationPhaseBuildItem

使用 ObserverRegistrationPhaseBuildItem 的构建步骤应始终生成 ObserverConfiguratorBuildItem,或者至少为此构建项注入 BuildProducer,否则它可能会被忽略或在错误的时间处理(例如,在正确的 CDI 引导阶段之后)。
ObserverRegistrationPhaseBuildItem 示例
@BuildStep
void syntheticObserver(ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
            BuildProducer<MyBuildItem> myBuildItem,
            BuildProducer<ObserverConfiguratorBuildItem> observerConfigurationRegistry) {
   observerConfigurationRegistry.produce(new ObserverConfiguratorBuildItem(observerRegistrationPhase.getContext()
       .configure()
       .beanClass(DotName.createSimple(MyBuildStep.class.getName()))
       .observedType(String.class)
       .notify(mc -> {
           // do some gizmo bytecode generation...
       })));
   myBuildItem.produce(new MyBuildItem());
}

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

8. 用例 - 我有一个生成的 Bean 类

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

GeneratedBeanBuildItem 示例
@BuildStep
void generatedBean(BuildProducer<GeneratedBeanBuildItem> generatedBeans) {
    ClassOutput beansClassOutput = new GeneratedBeanGizmoAdaptor(generatedBeans); (1)
    ClassCreator beanClassCreator = ClassCreator.builder().classOutput(beansClassOutput)
                .className("org.acme.MyBean")
                .build();
    beanClassCreator.addAnnotation(Singleton.class);
    beanClassCreator.close(); (2)
}
1 io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor 可以轻松地从 Gizmo 构造生成 GeneratedBeanBuildItem
2 生成的 Bean 类类似于 public class @Singleton MyBean { }

9. 用例 - 我需要验证部署

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

解决方案:如果扩展需要验证部署,则应使用 ValidationPhaseBuildItem

使用 ValidationPhaseBuildItem 的构建步骤应始终生成 ValidationErrorBuildItem,或者至少为此构建项注入 BuildProducer,否则它可能会被忽略或在错误的时间处理(例如,在正确的 CDI 引导阶段之后)。
@BuildStep
void validate(ValidationPhaseBuildItem validationPhase,
            BuildProducer<MyBuildItem> myBuildItem,
            BuildProducer<ValidationErrorBuildItem> errors) {
   if (someCondition) {
     errors.produce(new ValidationErrorBuildItem(new IllegalStateException()));
     myBuildItem.produce(new MyBuildItem());
   }
}
您可以通过从 ValidationPhaseBuildItem.getContext().beans() 方法返回的便捷 BeanStream 轻松过滤所有注册的 Bean。

10. 用例 - 注册自定义 CDI 上下文

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

解决方案:如果您需要注册自定义上下文,请使用 ContextRegistrationPhaseBuildItem

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

ContextRegistrationPhaseBuildItem 示例

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

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

CustomScopeBuildItem 示例

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

10.1. 如果我需要知道应用程序中使用的所有作用域怎么办?

解决方案:您可以在构建步骤中注入 CustomScopeAnnotationsBuildItem,并使用便捷的方法,例如 CustomScopeAnnotationsBuildItem.isScopeDeclaredOn()

11. 用例 - 其他拦截器绑定

在极少数情况下,以编程方式注册现有注解(该注解未使用 @jakarta.interceptor.InterceptorBinding 进行注解)作为拦截器绑定可能会很方便。这类似于 CDI 通过 BeforeBeanDiscovery#addInterceptorBinding() 实现的功能。我们将使用 InterceptorBindingRegistrarBuildItem 来完成它。

InterceptorBindingRegistrarBuildItem 示例
@BuildStep
InterceptorBindingRegistrarBuildItem addInterceptorBindings() {
    return new InterceptorBindingRegistrarBuildItem(new InterceptorBindingRegistrar() {
        @Override
        public List<InterceptorBinding> getAdditionalBindings() {
            return List.of(InterceptorBinding.of(NotAnInterceptorBinding.class));
        }
    });
}

12. 用例 - 其他限定符

有时,注册现有注解(该注解未使用 @jakarta.inject.Qualifier 进行注解)作为 CDI 限定符可能会很有用。这类似于 CDI 通过 BeforeBeanDiscovery#addQualifier() 实现的功能。我们将使用 QualifierRegistrarBuildItem 来完成它。

QualifierRegistrarBuildItem 示例
@BuildStep
QualifierRegistrarBuildItem addQualifiers() {
    return new QualifierRegistrarBuildItem(new QualifierRegistrar() {
        @Override
        public Map<DotName, Set<String>> getAdditionalQualifiers() {
            return Collections.singletonMap(DotName.createSimple(NotAQualifier.class.getName()),
                                        Collections.emptySet());
        }
    });
}

13. 用例 - 其他构造型

有时,注册现有注解(该注解未使用 @jakarta.enterprise.inject.Stereotype 进行注解)作为 CDI 构造型是很有用的。这类似于 CDI 通过 BeforeBeanDiscovery#addStereotype() 实现的功能。我们将使用 StereotypeRegistrarBuildItem 来完成它。

StereotypeRegistrarBuildItem 示例
@BuildStep
StereotypeRegistrarBuildItem addStereotypes() {
    return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() {
        @Override
        public Set<DotName> getAdditionalStereotypes() {
            return Collections.singleton(DotName.createSimple(NotAStereotype.class.getName()));
        }
    });
}

如果新注册的构造型注解没有适当的元注解(例如作用域或拦截器绑定),请使用 注解转换来添加它们。

14. 用例 - 注入点转换

偶尔,能够以编程方式更改注入点的限定符是很有用的。您可以使用 InjectionPointTransformerBuildItem 来做到这一点。以下示例显示了如何将转换应用于具有包含限定符 MyQualifier 的类型 Foo 的注入点

InjectionPointTransformerBuildItem 示例
@BuildStep
InjectionPointTransformerBuildItem transformer() {
    return new InjectionPointTransformerBuildItem(new InjectionPointsTransformer() {

        public boolean appliesTo(Type requiredType) {
            return requiredType.name().equals(DotName.createSimple(Foo.class.getName()));
        }

        public void transform(TransformationContext context) {
            if (context.getQualifiers().stream()
                    .anyMatch(a -> a.name().equals(DotName.createSimple(MyQualifier.class.getName())))) {
                context.transform()
                        .removeAll()
                        .add(DotName.createSimple(MyOtherQualifier.class.getName()))
                        .done();
            }
        }
    });
}
从理论上讲,您可以使用 AnnotationsTransformer 来实现相同的目标。但是,存在一些差异,使得 InjectionPointsTransformer 更适合此特定任务:(1) 注解转换器在 Bean 发现期间应用于所有类,而 InjectionPointsTransformer 仅在 Bean 发现之后应用于发现的注入点;(2) 使用 InjectionPointsTransformer,您无需处理各种类型的注入点(字段、初始化器方法的参数等)。

15. 用例 - 资源注解和注入

ResourceAnnotationBuildItem 可用于指定资源注解,这些注解可以解析非 CDI 注入点,例如 Jakarta EE 资源。集成器还必须提供相应的 io.quarkus.arc.ResourceReferenceProvider 服务提供程序实现。

ResourceAnnotationBuildItem 示例
@BuildStep
void setupResourceInjection(BuildProducer<ResourceAnnotationBuildItem> resourceAnnotations, BuildProducer<GeneratedResourceBuildItem> resources) {
    resources.produce(new GeneratedResourceBuildItem("META-INF/services/io.quarkus.arc.ResourceReferenceProvider",
        MyResourceReferenceProvider.class.getName().getBytes()));
    resourceAnnotations.produce(new ResourceAnnotationBuildItem(DotName.createSimple(MyAnnotation.class.getName())));
}

16. 可用的构建时元数据

任何上述使用 BuildExtension.BuildContext 的扩展都可以利用在构建期间生成的某些构建时元数据。io.quarkus.arc.processor.BuildExtension.Key 中的内置键包括

ANNOTATION_STORE

包含一个 AnnotationStore,该存储在应用注解转换器后保留有关所有 AnnotationTarget 注解的信息

INJECTION_POINTS

Collection<InjectionPointInfo>,包含所有注入点

BEANS

Collection<BeanInfo>,包含所有 Bean

REMOVED_BEANS

Collection<BeanInfo>,包含所有已删除的 Bean;有关更多信息,请参见 删除未使用的 Bean

OBSERVERS

Collection<ObserverInfo>,包含所有观察者

SCOPES

Collection<ScopeInfo>,包含所有作用域,包括自定义作用域

QUALIFIERS

Map<DotName, ClassInfo>,包含所有限定符

INTERCEPTOR_BINDINGS

Map<DotName, ClassInfo>,包含所有拦截器绑定

STEREOTYPES

Map<DotName, StereotypeInfo>,包含所有构造型

要获取这些,只需为给定的键查询扩展上下文对象。请注意,这些元数据在构建进行时提供,这意味着扩展只能利用在调用扩展之前构建的元数据。如果您的扩展尝试检索尚未生成的元数据,则将返回 null。以下是哪些扩展可以访问哪些元数据的摘要

AnnotationsTransformer

不应依赖于任何元数据,因为它可以在引导的任何阶段的任何时间使用

ContextRegistrar

可以访问 ANNOTATION_STOREQUALIFIERSINTERCEPTOR_BINDINGSSTEREOTYPES

InjectionPointsTransformer

可以访问 ANNOTATION_STOREQUALIFIERSINTERCEPTOR_BINDINGSSTEREOTYPES

ObserverTransformer

可以访问 ANNOTATION_STOREQUALIFIERSINTERCEPTOR_BINDINGSSTEREOTYPES

BeanRegistrar

可以访问 ANNOTATION_STOREQUALIFIERSINTERCEPTOR_BINDINGSSTEREOTYPESBEANS(仅基于类的 Bean)、OBSERVERS(仅基于类的观察者)、INJECTION_POINTS

ObserverRegistrar

可以访问 ANNOTATION_STOREQUALIFIERSINTERCEPTOR_BINDINGSSTEREOTYPESBEANSOBSERVERS(仅基于类的观察者)、INJECTION_POINTS

BeanDeploymentValidator

可以访问所有构建元数据

相关内容