CDI 集成指南
ArC 是 Quarkus 中的 CDI 容器,它在构建时启动。要与容器集成,可以使用CDI Build Compatible Extensions,以及特定于 Quarkus 的扩展 API。CDI Portable Extensions 不支持且无法支持。本指南重点介绍特定于 Quarkus 的扩展 API。
容器在多个阶段启动。从高层次的角度来看,这些阶段如下所示
-
初始化
-
Bean 发现
-
合成组件的注册
-
验证
在初始化阶段,执行准备工作并注册自定义上下文。Bean 发现是容器分析所有应用程序类,识别 Bean 并基于提供的元数据将它们连接在一起的过程。随后,扩展可以注册合成组件。这些组件的属性完全由扩展控制,即不是从现有类派生的。最后,验证部署。例如,容器会验证应用程序中的每个注入点,如果不存在满足给定必需类型和限定符的 Bean,则构建失败。
您可以通过启用其他日志记录来查看有关引导的更多信息。只需使用 -X 或 --debug 运行 Maven 构建,然后 grep 包含 io.quarkus.arc 的行。在 开发模式下,您可以使用 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG ,并且还会自动注册两个特殊端点,以 JSON 格式提供一些基本调试信息。 |
Quarkus 构建步骤可以生成和使用各种构建项,并挂钩到每个阶段。在以下部分中,我们将描述所有相关的构建项和常见场景。
2. 用例 - 我的类未被识别为 Bean
UnsatisfiedResolutionException
指示在 类型安全解析期间出现问题。有时,即使类路径上存在看起来符合注入条件的类,也无法满足注入点。类未被识别的原因有多种,解决方法也有多种。第一步我们应该确定原因。
2.1. 原因 1:未发现类
Quarkus 具有 简化的发现。可能该类不是应用程序索引的一部分。例如,Quarkus 扩展的运行时模块中的类不会自动建立索引。
解决方案:使用 AdditionalBeanBuildItem
。此构建项可用于指定在发现期间要分析的一个或多个其他类。其他 Bean 类以透明方式添加到容器处理的应用程序索引中。
无法像 cdi-reference 和 cdi-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.ProcessAnnotatedType
和 jakarta.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() 将返回一组可能转换的注解。 |
还有其他专门用于转换的构建项:用例 - 其他拦截器绑定 和 用例 - 注入点转换。 |
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 实例存在一些限制。您可以
-
通过
ExtendedBeanConfigurator.creator(Consumer<MethodCreator>)
直接生成Contextual#create(CreationalContext<T>)
方法的字节码。 -
通过
ExtendedBeanConfigurator#creator(Class<? extends BeanCreator<U>>)
传递io.quarkus.arc.BeanCreator
的子类,并且可以通过ExtendedBeanConfigurator#param()
指定一些构建时参数,并通过ExtendedBeanConfigurator#addInjectionPoint()
指定合成注入点。 -
通过从
@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_INIT
和 RUNTIME_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 期间初始化。 |
在
|
也可以使用 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>
@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 | 配置“检查活动”过程。 |
@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()));
}
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_STORE
、QUALIFIERS
、INTERCEPTOR_BINDINGS
、STEREOTYPES
- InjectionPointsTransformer
-
可以访问
ANNOTATION_STORE
、QUALIFIERS
、INTERCEPTOR_BINDINGS
、STEREOTYPES
- ObserverTransformer
-
可以访问
ANNOTATION_STORE
、QUALIFIERS
、INTERCEPTOR_BINDINGS
、STEREOTYPES
- BeanRegistrar
-
可以访问
ANNOTATION_STORE
、QUALIFIERS
、INTERCEPTOR_BINDINGS
、STEREOTYPES
、BEANS
(仅基于类的 Bean)、OBSERVERS
(仅基于类的观察者)、INJECTION_POINTS
- ObserverRegistrar
-
可以访问
ANNOTATION_STORE
、QUALIFIERS
、INTERCEPTOR_BINDINGS
、STEREOTYPES
、BEANS
、OBSERVERS
(仅基于类的观察者)、INJECTION_POINTS
- BeanDeploymentValidator
-
可以访问所有构建元数据