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:
-
Initialization
-
Bean discovery
-
Registration of synthetic components
-
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 更多信息。只需使用 |
You can see more information about the bootstrap by enabling additional logging. Simply run the Maven build with |
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_profile 和 cdi-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 Beans 和 Reason 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.
如果没有指定默认范围,则使用 |
If no default scope is specified the |
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 Beans 和 Reason 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.
如果没有指定默认范围,则使用 |
If no default scope is specified the |
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.ProcessAnnotatedType
和 jakarta.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:
@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 |
生成步骤可以通过 `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 Bindings和 Use 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
Examplequarkus.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:
-
Generate the bytecode of the
Contextual#create(CreationalContext<T>)
method directly viaExtendedBeanConfigurator.creator(Consumer<MethodCreator>)
. -
Pass a subclass of
io.quarkus.arc.BeanCreator
viaExtendedBeanConfigurator#creator(Class<? extends BeanCreator<U>>)
, and possibly specify some build-time parameters viaExtendedBeanConfigurator#param()
and synthetic injection points viaExtendedBeanConfigurator#addInjectionPoint()
. -
Produce the runtime instance through a proxy returned from a
@Recorder
method and set it viaExtendedBeanConfigurator#runtimeValue(RuntimeValue<?>)
,ExtendedBeanConfigurator#runtimeProxy(Object)
,ExtendedBeanConfigurator#supplier(Supplier<?>)
orExtendedBeanConfigurator#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_INIT
和 RUNTIME_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. |
还可以使用 |
It is also possible to use the |
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.
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. |
@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());
}
}
你可以从 |
You can easily filter all registered beans via the convenient |
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 |
In theory, you can use annotations_transformer_build_item to achieve the same goal. However, there are few differences that make |
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 allAnnotationTarget
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