Contexts and Dependency Injection

基于 Quarkus 的 DI 解决方案(也称为 ArC)基于 Jakarta Contexts and Dependency Injection 4.1 规范。它实现了 CDI Lite 规范,在此基础上做了一些改进,并通过了 CDI Lite TCK。它没有实现完整的 CDI。另请参阅 the list of supported features and limitations

Quarkus DI solution (also called ArC) is based on the Jakarta Contexts and Dependency Injection 4.1 specification. It implements the CDI Lite specification, with selected improvements on top, and passes the CDI Lite TCK. It does not implement CDI Full. See also supported_features_and_limitations.

如果您不熟悉 CDI,我们建议您先阅读 Introduction to CDI

If you’re new to CDI then we recommend you to read the Introduction to CDI first.

大多数现有 CDI 代码应该都能正常工作,但有一些小的差异源于 Quarkus 架构和目标。

Most of the existing CDI code should work just fine but there are some small differences which follow from the Quarkus architecture and goals.

Bean Discovery

CDI 中的 Bean 发现是一个复杂的过程,它涉及底层模块架构的传统部署结构和可访问性要求。但是,Quarkus 正在使用 simplified bean discovery。只有一个带有 bean discovery mode annotated 的 bean 存档,并且没有可见性边界。

Bean discovery in CDI is a complex process which involves legacy deployment structures and accessibility requirements of the underlying module architecture. However, Quarkus is using a simplified bean discovery. There is only single bean archive with the bean discovery mode annotated and no visibility boundaries.

bean 存档是由以下内容合成的:

The bean archive is synthesized from:

  • the application classes,

  • dependencies that contain a beans.xml descriptor (content is ignored),

  • dependencies that contain a Jandex index - META-INF/jandex.idx,

  • dependencies referenced by quarkus.index-dependency in application.properties,

  • and Quarkus integration code.

没有 bean defining annotation 的 Bean 类不会被发现。这种行为是由 CDI 定义的。但是,即使声明的类未用 bean 定义注释作注释,也会发现生产者方法、字段和观察者方法(此行为与 CDI 中定义的行为不同)。事实上,声明 bean 类被视为用 @Dependent 作了注释。

Bean classes that don’t have a bean defining annotation are not discovered. This behavior is defined by CDI. But producer methods and fields and observer methods are discovered even if the declaring class is not annotated with a bean defining annotation (this behavior is different to what is defined in CDI). In fact, the declaring bean classes are considered annotated with @Dependent.

Quarkus 扩展可能声明其他发现规则。例如,即使声明的类未用 bean 定义注释作注释,也会注册 @Scheduled 业务方法。

Quarkus extensions may declare additional discovery rules. For example, @Scheduled business methods are registered even if the declaring class is not annotated with a bean defining annotation.

How to Generate a Jandex Index

为依赖项附上 Jandex 索引,系统会自动扫描是否存在 bean。要生成索引,只需将以下插件添加到构建文件中:

A dependency with a Jandex index is automatically scanned for beans. To generate the index just add the following plugin to your build file:

Maven
<build>
  <plugins>
    <plugin>
      <groupId>io.smallrye</groupId>
      <artifactId>jandex-maven-plugin</artifactId>
      <version>{jandex-version}</version>
      <executions>
        <execution>
          <id>make-index</id>
          <goals>
            <goal>jandex</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
Gradle (Groovy DSL)
plugins {
    id 'org.kordamp.gradle.jandex' version '{jandex-gradle-plugin-version}'
}

您可以在 Gradle Plugin Portal 中找到最新插件版本。

You can find the latest plugin version in the Gradle Plugin Portal

Gradle (Kotlin DSL)
plugins {
    id("org.kordamp.gradle.jandex") version "{jandex-gradle-plugin-version}"
}

您可以在 Gradle Plugin Portal 中找到最新插件版本。

You can find the latest plugin version in the Gradle Plugin Portal

如果您无法更改依赖项,您仍然可以通过向 application.properties 中添加 quarkus.index-dependency 条目来为其建立索引:

If you can’t modify the dependency, you can still index it by adding quarkus.index-dependency entries to your application.properties:

quarkus.index-dependency.<name>.group-id=
quarkus.index-dependency.<name>.artifact-id=(this one is optional)
quarkus.index-dependency.<name>.classifier=(this one is optional)

如果没有指定 artifact-id,则会为具有指定 group-id 的所有依赖项建立索引。

If no artifact-id is specified then all dependencies with the specificed group-id are indexed.

例如,以下条目可确保对 org.acme:acme-api 依赖项进行索引:

For example, the following entries ensure that the org.acme:acme-api dependency is indexed:

Example application.properties
quarkus.index-dependency.acme.group-id=org.acme 1
quarkus.index-dependency.acme.artifact-id=acme-api 2
1 Value is a group id for a dependency identified by name acme.
2 Value is an artifact id for a dependency identified by name acme.

How To Exclude Types and Dependencies from Discovery

第三方库中的某些 bean 在 Quarkus 中可能无法正常工作。一个典型的示例是注入可移植扩展的 bean。在这种情况下,它有可能从 bean 发现中排除类型和依赖项。quarkus.arc.exclude-types 属性接受一个字符串值列表,用于匹配应该被排除的类。

It may happen that some beans from third-party libraries do not work correctly in Quarkus. A typical example is a bean injecting a portable extension. In such case, it’s possible to exclude types and dependencies from the bean discovery. The quarkus.arc.exclude-types property accepts a list of string values that are used to match classes that should be excluded.

Table 1. Value Examples

Value

Description

org.acme.Foo

Match the fully qualified name of the class

org.acme.*

Match classes with package org.acme

org.acme.**

Match classes where the package starts with org.acme

Bar

Match the simple name of the class

Example application.properties
quarkus.arc.exclude-types=org.acme.Foo,org.acme.*,Bar 123
1 Exclude the type org.acme.Foo.
2 Exclude all types from the org.acme package.
3 Exclude all types whose simple name is Bar

也可以排除本应扫描的 bean 依赖项构件。例如,因为它包含一个 beans.xml 描述符。

It is also possible to exclude a dependency artifact that would be otherwise scanned for beans. For example, because it contains a beans.xml descriptor.

Example application.properties
quarkus.arc.exclude-dependency.acme.group-id=org.acme 1
quarkus.arc.exclude-dependency.acme.artifact-id=acme-services 2
1 Value is a group id for a dependency identified by name acme.
2 Value is an artifact id for a dependency identified by name acme.

Native Executables and Private Members

Quarkus 使用 GraalVM 构建一个本地可执行文件。GraalVM 的一个限制是使用 Reflection。支持反射操作,但所有相关成员必须显式地用于反射注册。这些注册会导致更大的本地可执行文件。

Quarkus is using GraalVM to build a native executable. One of the limitations of GraalVM is the usage of Reflection. Reflective operations are supported but all relevant members must be registered for reflection explicitly. Those registrations result in a bigger native executable.

如果 Quarkus DI 需要访问私有成员,则它 has to use reflection。这就是鼓励 Quarkus 用户在 bean 中 not to use private members 的原因。这涉及注入字段、构造函数和初始化程序、观察器方法、生产者方法和字段、处置程序和拦截器方法。

And if Quarkus DI needs to access a private member it has to use reflection. That’s why Quarkus users are encouraged not to use private members in their beans. This involves injection fields, constructors and initializers, observer methods, producer methods and fields, disposers and interceptor methods.

如何避免使用私有成员?您可以使用 package-private 修饰符:

How to avoid using private members? You can use package-private modifiers:

@ApplicationScoped
public class CounterBean {

    @Inject
    CounterService counterService; 1

    void onMessage(@Observes Event msg) { 2
    }
}
1 A package-private injection field.
2 A package-private observer method.

或构造函数注入:

Or constructor injection:

@ApplicationScoped
public class CounterBean {

    private CounterService service;

    CounterBean(CounterService service) { 1
      this.service = service;
    }
}
1 A package-private constructor injection. @Inject is optional in this particular case.

[id="supported_features"][id="limitations"] Supported Features and Limitations

CDI Lite 规范得到充分支持。CDI Full 中的以下功能也受支持:

The CDI Lite specification is fully supported. The following features from CDI Full are also supported:

  • Decorators

    • Decoration of built-in beans, such as Event, is not supported

  • BeanManager

    • In addition to the BeanContainer methods, the following methods are supported: getInjectableReference(), resolveDecorators()

  • @SessionScoped

    • Only with the Undertow extension; see here for details

method invokers 实现支持异步方法。下列方法被认为是异步的,并且仅在异步操作完成时才销毁 @Dependent 实例:

The method invokers implementation supports asynchronous methods. The following methods are considered asynchronous and @Dependent instances are only destroyed when the asynchronous action completes:

  • methods that declare a return type of CompletionStage, Uni, or Multi

这些附加特性未被 CDI Lite TCK 涵盖。

These additional features are not covered by the CDI Lite TCK.

Non-standard Features

Eager Instantiation of Beans

Lazy By Default

默认情况下,CDI bean 在需要时延迟创建。具体“需要”的含义取决于 bean 的范围。

By default, CDI beans are created lazily, when needed. What exactly "needed" means depends on the scope of a bean.

  • A normal scoped bean (@ApplicationScoped, @RequestScoped, etc.) is needed when a method is invoked upon an injected instance (contextual reference per the specification).[.iokays-translated-f69894f13175855092b465b6abd324bc] 换句话说,注入一个正常范围的 bean 是不够的,因为会注入一个 client proxy,而不是 bean 的上下文实例。

In other words, injecting a normal scoped bean will not suffice because a client proxy is injected instead of a contextual instance of the bean. * A bean with a pseudo-scope (@Dependent and @Singleton ) is created when injected.

Lazy Instantiation Example
@Singleton // => pseudo-scope
class AmazingService {
  String ping() {
    return "amazing";
  }
}

@ApplicationScoped // => normal scope
class CoolService {
  String ping() {
    return "cool";
  }
}

@Path("/ping")
public class PingResource {

  @Inject
  AmazingService s1; 1

  @Inject
  CoolService s2; 2

  @GET
  public String ping() {
    return s1.ping() + s2.ping(); 3
  }
}
1 Injection triggers the instantiation of AmazingService.
2 Injection itself does not result in the instantiation of CoolService. A client proxy is injected.
3 The first invocation upon the injected proxy triggers the instantiation of CoolService.

Startup Event

但是,如果你确实需要立即实例化 bean,你可以:

However, if you really need to instantiate a bean eagerly you can:

  • Declare an observer of the StartupEvent - the scope of the bean does not matter in this case:[source, java]

@ApplicationScoped
class CoolService {
  void startup(@Observes StartupEvent event) { 1
  }
}
1 A CoolService is created during startup to service the observer method invocation.
  • Use the bean in an observer of the StartupEvent - normal scoped beans must be used as described in Lazy By Default:[source, java]

@Dependent
class MyBeanStarter {

  void startup(@Observes StartupEvent event, AmazingService amazing, CoolService cool) { 1
    cool.toString(); 2
  }
}
1 The AmazingService is created during injection.
2 The CoolService is a normal scoped bean, so we have to invoke a method upon the injected proxy to force the instantiation.
  • Annotate the bean with @io.quarkus.runtime.Startup as described in Startup annotation:[source, java]

@Startup (1)
@ApplicationScoped
public class EagerAppBean {

   private final String name;

   EagerAppBean(NameGenerator generator) { (2)
     this.name = generator.createName();
   }
}
1 For each bean annotated with @Startup a synthetic observer of StartupEvent is generated. The default priority is used.
2 The bean constructor is called when the application starts and the resulting contextual instance is stored in the application context.

鼓励 Quarkus 用户始终优先使用 @Observes StartupEvent`而不是 `@Initialized(ApplicationScoped.class),如 Application Initialization and Termination指南中所述。

Quarkus users are encouraged to always prefer the @Observes StartupEvent to @Initialized(ApplicationScoped.class) as explained in the Application Initialization and Termination guide.

Request Context Lifecycle

请求上下文也处于活动状态:

The request context is also active:

  • during notification of a synchronous observer method.

请求上下文已销毁:

The request context is destroyed:

  • after the observer notification completes for an event, if it was not already active when the notification started.

当为观察器通知初始化请求上下文时,将触发带有限定符 @Initialized(RequestScoped.class) 的事件。此外,当请求上下文被销毁时,将触发带有限定符 @BeforeDestroyed(RequestScoped.class)@Destroyed(RequestScoped.class) 的事件。

An event with qualifier @Initialized(RequestScoped.class) is fired when the request context is initialized for an observer notification. Moreover, the events with qualifiers @BeforeDestroyed(RequestScoped.class) and @Destroyed(RequestScoped.class) are fired when the request context is destroyed.

How to Enable Trace Logging for Request Context Activation

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

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

application.properties Example
quarkus.log.category."io.quarkus.arc.requestContext".min-level=TRACE 1
quarkus.log.category."io.quarkus.arc.requestContext".level=TRACE
1 You also need to adjust the minimum log level for the relevant category.

Qualified Injected Fields

在 CDI 中,如果您声明字段注入点,则需要使用 @Inject 以及一组限定符(可选)。

In CDI, if you declare a field injection point you need to use @Inject and optionally a set of qualifiers.

  @Inject
  @ConfigProperty(name = "cool")
  String coolProperty;

在 Quarkus 中,如果注入字段声明至少一个限定符,您可以完全跳过 @Inject 注解。

In Quarkus, you can skip the @Inject annotation completely if the injected field declares at least one qualifier.

  @ConfigProperty(name = "cool")
  String coolProperty;

对于下面讨论的一个特殊情况来说,@Inject 仍然是构造函数和方法注入所必需的。

With the notable exception of one special case discussed below, @Inject is still required for constructor and method injection.

Simplified Constructor Injection

在 CDI 中,普通作用域 Bean 必须始终声明无参数构造函数(除非您声明任何其他构造函数,否则此构造函数通常由编译器生成)。但是,此要求使构造注入更复杂了 - 您需要提供一个无参数占位构造函数,以便在 CDI 中使事情正常工作。

In CDI, a normal scoped bean must always declare a no-args constructor (this constructor is normally generated by the compiler unless you declare any other constructor). However, this requirement complicates constructor injection - you need to provide a dummy no-args constructor to make things work in CDI.

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService() { // dummy constructor needed
  }

  @Inject // constructor injection
  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}

在 Quarkus 中,无需为普通作用域 Bean 声明无参数构造函数 - 它们会自动生成。 außerdem, 如果只有一个构造函数,则无需 @Inject

There is no need to declare dummy constructors for normal scoped bean in Quarkus - they are generated automatically. Also, if there’s only one constructor there is no need for @Inject.

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}

如果 Bean 类扩展未声明无参数构造函数的类,我们将不会自动生成无参数构造函数。

We don’t generate a no-args constructor automatically if a bean class extends a class that does not declare a no-args constructor.

Removing Unused Beans

默认情况下,容器尝试在构建期间删除所有未使用的 Bean、拦截器和装饰器。此优化有助于最大程度减少生成类的数量,从而节省内存。但是,Quarkus 无法通过 CDI.current() 静态方法检测执行的编程查找。因此,删除可能会导致误报错误,即删除了 Bean,而实际上该 Bean 仍被使用。在这些情况下,您会在日志中注意到一个大的警告。用户和扩展作者有几个 how to eliminate false positives 选项。

The container attempts to remove all unused beans, interceptors and decorators during build by default. This optimization helps to minimize the amount of generated classes, thus conserving memory. However, Quarkus can’t detect the programmatic lookup performed via the CDI.current() static method. Therefore, it is possible that a removal results in a false positive error, i.e. a bean is removed although it’s actually used. In such cases, you’ll notice a big warning in the log. Users and extension authors have several options eliminate_false_positives.

可以通过将 quarkus.arc.remove-unused-beans 设置为 nonefalse 来禁用优化。此外,Quarkus 还提供了一种折衷方案,其中无论应用程序的 Bean 是否未使用,都不会将其删除,而对于非应用程序类,优化正常进行。要使用此模式,请将 quarkus.arc.remove-unused-beans 设置为 fwkframework

The optimization can be disabled by setting quarkus.arc.remove-unused-beans to none or false. Quarkus also provides a middle ground where application beans are never removed whether or not they are unused, while the optimization proceeds normally for non application classes. To use this mode, set quarkus.arc.remove-unused-beans to fwk or framework.

What’s Removed?

Quarkus 首先识别形成依赖关系树根的所谓 unremovable Bean。一个很好的例子是 Jakarta REST 资源类或声明 @Scheduled 方法的 bean。

Quarkus first identifies so-called unremovable beans that form the roots in the dependency tree. A good example is a Jakarta REST resource class or a bean which declares a @Scheduled method.

unremovable bean:

An unremovable bean:

  • is excluded from removal by an extension, or

  • has a name designated via @Named, or

  • declares an observer method.

unused bean:

An unused bean:

  • is not unremovable, and

  • is not eligible for injection to any injection point in the dependency tree, and

  • does not declare any producer which is eligible for injection to any injection point in the dependency tree, and

  • is not eligible for injection into any jakarta.enterprise.inject.Instance or jakarta.inject.Provider injection point.

未使用的拦截器和装饰器与任何 Bean 无关。

Unused interceptors and decorators are not associated with any bean.

使用 dev 模式(运行 ./mvnw clean compile quarkus:dev)时,可以看到关于要删除哪些 Bean 的更多信息:

When using the dev mode (running ./mvnw clean compile quarkus:dev), you can see more information about which beans are being removed:

  1. In the console - just enable the DEBUG level in your application.properties, i.e. quarkus.log.category."io.quarkus.arc.processor".level=DEBUG

  2. In the relevant Dev UI page

How To Eliminate False Positives

用户可以通过使用 @io.quarkus.arc.Unremovable 注释 Bean(即使它们满足上面指定的所有规则)来指示容器不要删除其任何特定 Bean. 此注释可以在类、生成器方法或字段上声明。

Users can instruct the container to not remove any of their specific beans (even if they satisfy all the rules specified above) by annotating them with @io.quarkus.arc.Unremovable. This annotation can be declared on a class, a producer method or field.

由于这并不总是可能的,因此可以选择通过 application.properties 实现相同的功能. quarkus.arc.unremovable-types 属性接受一个字符串值列表,这些字符串值基于名称或包匹配 Bean。

Since this is not always possible, there is an option to achieve the same via application.properties. The quarkus.arc.unremovable-types property accepts a list of string values that are used to match beans based on their name or package.

Table 2. Value Examples

Value

Description

org.acme.Foo

Match the fully qualified name of the bean class

org.acme.*

Match beans where the package of the bean class is org.acme

org.acme.**

Match beans where the package of the bean class starts with org.acme

Bar

Match the simple name of the bean class

Example application.properties
quarkus.arc.unremovable-types=org.acme.Foo,org.acme.*,Bar

此外,扩展可以通过生成 UnremovableBeanBuildItem 淘汰误报。

Furthermore, extensions can eliminate false positives by producing an UnremovableBeanBuildItem.

Default Beans

Quarkus 添加了 CDI 当前不支持的一种功能,即有条件地声明一个 Bean,如果没有任何可用方法(Bean 类、生成器、合成 Bean……)声明具有相同类型和限定符的 Bean。这是使用 @io.quarkus.arc.DefaultBean 注释完成的,最好通过示例进行说明。

Quarkus adds a capability that CDI currently does not support which is to conditionally declare a bean if no other bean with equal types and qualifiers was declared by any available means (bean class, producer, synthetic bean, …​) This is done using the @io.quarkus.arc.DefaultBean annotation and is best explained with an example.

假设有一个 Quarkus 扩展,它声明了几个 CDI Bean,如下代码所示:

Say there is a Quarkus extension that among other things declares a few CDI beans like the following code does:

@Dependent
public class TracerConfiguration {

    @Produces
    public Tracer tracer(Reporter reporter, Configuration configuration) {
        return new Tracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Configuration configuration() {
        // create a Configuration
    }

    @Produces
    @DefaultBean
    public Reporter reporter(){
        // create a Reporter
    }
}

这个想法是,扩展自动为用户配置内容,消除了大量样板代码 - 我们可以在任何需要的地方 @Inject 一个 Tracer. 现在想象一下,在我们的应用程序中,我们要使用已配置的 Tracer,但我们需要对其进行一些自定义,例如通过提供一个自定义 Reporter. 应用程序中唯一需要的就是类似以下内容的东西:

The idea is that the extension autoconfigures things for the user, eliminating a lot of boilerplate - we can just @Inject a Tracer wherever it is needed. Now imagine that in our application we would like to utilize the configured Tracer, but we need to customize it a little, for example by providing a custom Reporter. The only thing that would be needed in our application would be something like the following:

@Dependent
public class CustomTracerConfiguration {

    @Produces
    public Reporter reporter(){
        // create a custom Reporter
    }
}

@DefaultBean 允许扩展(或其他任何代码)提供默认值,同时在以任何 Quarkus 支持的方式提供该类型的 Bean 时退出。

@DefaultBean allows extensions (or any other code for that matter) to provide defaults while backing off if beans of that type are supplied in any way Quarkus supports.

默认Bean可以可选地声明`@jakarta.annotation.Priority`。如果没有定义优先级,则假定为`@Priority(0)`。优先级值用于Bean排序,并在类型安全解析期间消除多个匹配的默认Bean的歧义。

Default beans can optionally declare @jakarta.annotation.Priority. If there is no priority defined, @Priority(0) is assumed. Priority value is used for bean ordering and during typesafe resolution to disambiguate multiple matching default beans.

@Dependent
public class CustomizedDefaultConfiguration {

    @Produces
    @DefaultBean
    @Priority(100)
    public Configuration customizedConfiguration(){
        // create a customized default Configuration
        // this will have priority over previously defined default bean
    }
}

Enabling Beans for Quarkus Build Profile

Quarkus新增了CDI当前不支持的功能,即通过`@io.quarkus.arc.profile.IfBuildProfile`和`@io.quarkus.arc.profile.UnlessBuildProfile`注解,在启用Quarkus构建时间配置文件时有条件地启用Bean。当与`@io.quarkus.arc.DefaultBean`结合使用时,这些注解允许为不同的构建配置文件创建不同的Bean配置。

Quarkus adds a capability that CDI currently does not support which is to conditionally enable a bean when a Quarkus build time profile is enabled, via the @io.quarkus.arc.profile.IfBuildProfile and @io.quarkus.arc.profile.UnlessBuildProfile annotations. When used in conjunction with @io.quarkus.arc.DefaultBean, these annotations allow for the creation of different bean configurations for different build profiles.

例如,假设一个应用程序包含一个名为`Tracer`的Bean,它在测试或开发模式下不需要执行任何操作,但对生产制品来说工作正常。创建这样的Bean的优雅方法如下:

Imagine for instance that an application contains a bean named Tracer, which needs to do nothing when in tests or in dev mode, but works in its normal capacity for the production artifact. An elegant way to create such beans is the following:

@Dependent
public class TracerConfiguration {

    @Produces
    @IfBuildProfile("prod")
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}

如果相反,需要`Tracer`Bean也在开发模式下工作,并且仅在测试中默认什么也不做,那么`@UnlessBuildProfile`将是理想选择。代码如下所示:

If instead, it is required that the Tracer bean also works in dev mode and only default to doing nothing for tests, then @UnlessBuildProfile would be ideal. The code would look like:

@Dependent
public class TracerConfiguration {

    @Produces
    @UnlessBuildProfile("test") // this will be enabled for both prod and dev build time profiles
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}

运行时配置文件对使用`@IfBuildProfile`和`@UnlessBuildProfile`进行Bean解析绝对没有影响。

The runtime profile has absolutely no effect on the bean resolution using @IfBuildProfile and @UnlessBuildProfile.

还可以在构造型上使用`@IfBuildProfile`和`@UnlessBuildProfile`。

It is also possible to use @IfBuildProfile and @UnlessBuildProfile on stereotypes.

Enabling Beans for Quarkus Build Properties

Quarkus新增了CDI当前不支持的功能,即通过`@io.quarkus.arc.properties.IfBuildProperty`和`@io.quarkus.arc.properties.UnlessBuildProperty`注解,在Quarkus构建时间属性具有/不具有特定值时有条件地启用Bean。当与`@io.quarkus.arc.DefaultBean`结合使用时,此注解允许为不同的构建属性创建不同的Bean配置。

Quarkus adds a capability that CDI currently does not support which is to conditionally enable a bean when a Quarkus build time property has/has not a specific value, via the @io.quarkus.arc.properties.IfBuildProperty and @io.quarkus.arc.properties.UnlessBuildProperty annotations. When used in conjunction with @io.quarkus.arc.DefaultBean, this annotation allow for the creation of different bean configurations for different build properties.

我们上面使用`Tracer`提到的场景也可以通过以下方式实现:

The scenario we mentioned above with Tracer could also be implemented in the following way:

@Dependent
public class TracerConfiguration {

    @Produces
    @IfBuildProperty(name = "some.tracer.enabled", stringValue = "true")
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}

@IfBuildProperty`和@UnlessBuildProperty`是可以重复的注解,即仅当这些注解定义的条件得到满足时,才启用Bean。

@IfBuildProperty and @UnlessBuildProperty are repeatable annotations, i.e. a bean will only be enabled if all the conditions defined by these annotations are satisfied.

如果相反,只需要在`some.tracer.enabled`属性不是`false`时才使用`RealTracer`Bean,那么`@UnlessBuildProperty`将是理想选择。代码如下所示:

If instead, it is required that the RealTracer bean is only used if the some.tracer.enabled property is not false, then @UnlessBuildProperty would be ideal. The code would look like:

@Dependent
public class TracerConfiguration {

    @Produces
    @UnlessBuildProperty(name = "some.tracer.enabled", stringValue = "false")
    public Tracer realTracer(Reporter reporter, Configuration configuration) {
        return new RealTracer(reporter, configuration);
    }

    @Produces
    @DefaultBean
    public Tracer noopTracer() {
        return new NoopTracer();
    }
}

在运行时设置的属性对使用`@IfBuildProperty`进行Bean解析绝对没有影响。

Properties set at runtime have absolutely no effect on the bean resolution using @IfBuildProperty.

还可以在构造型上使用`@IfBuildProperty`和`@UnlessBuildProperty`。

It is also possible to use @IfBuildProperty and @UnlessBuildProperty on stereotypes.

Declaring Selected Alternatives

在CDI中,可以使用`@Priority`为应用程序全局选择一个备用Bean,或者使用`beans.xml`描述符为Bean归档选择一个备用Bean。Quarkus简化了Bean发现,忽略了`beans.xml`的内容。

In CDI, an alternative bean may be selected either globally for an application by means of @Priority, or for a bean archive using a beans.xml descriptor. Quarkus has a simplified bean discovery and the content of beans.xml is ignored.

但是,还可以使用统一配置为应用程序选择替代方案。quarkus.arc.selected-alternatives`属性接受一个字符串值列表,这些字符串值用于匹配备用Bean。如果任何值匹配,则`Integer#MAX_VALUE`的优先级用于相关Bean。通过@Priority`声明或从构造型继承的优先级将被覆盖。

However, it is also possible to select alternatives for an application using the unified configuration. The quarkus.arc.selected-alternatives property accepts a list of string values that are used to match alternative beans. If any value matches then the priority of Integer#MAX_VALUE is used for the relevant bean. The priority declared via @Priority or inherited from a stereotype is overridden.

Table 3. Value Examples

Value

Description

org.acme.Foo

Match the fully qualified name of the bean class or the bean class of the bean that declares the producer

org.acme.*

Match beans where the package of the bean class is org.acme

org.acme.**

Match beans where the package of the bean class starts with org.acme

Bar

Match the simple name of the bean class or the bean class of the bean that declares the producer

Example application.properties
quarkus.arc.selected-alternatives=org.acme.Foo,org.acme.*,Bar

Simplified Producer Method Declaration

在CDI中,生产者方法必须始终使用`@Produces`进行注解。

In CDI, a producer method must be always annotated with @Produces.

class Producers {

  @Inject
  @ConfigProperty(name = "cool")
  String coolProperty;

  @Produces
  @ApplicationScoped
  MyService produceService() {
    return new MyService(coolProperty);
  }
}

在Quarkus中,如果使用作用域注解、构造型或限定符对生产者方法进行注解,则可以完全跳过`@Produces`注解。

In Quarkus, you can skip the @Produces annotation completely if the producer method is annotated with a scope annotation, a stereotype or a qualifier.

class Producers {

  @ConfigProperty(name = "cool")
  String coolProperty;

  @ApplicationScoped
  MyService produceService() {
    return new MyService(coolProperty);
  }
}

Interception of Static Methods

拦截器规范明确指出,_around-invoke_方法不能被声明为静态方法。但是,此限制主要是由技术限制引起的。而且,由于Quarkus是一个面向构建时间的堆栈,它允许额外的类转换,因此这些限制不再适用。可以用拦截器绑定对非私有静态方法进行注解:

The Interceptors specification is clear that around-invoke methods must not be declared static. However, this restriction was driven mostly by technical limitations. And since Quarkus is a build-time oriented stack that allows for additional class transformations, those limitations don’t apply anymore. It’s possible to annotate a non-private static method with an interceptor binding:

class Services {

  @Logged 1
  static BigDecimal computePrice(long amount) { 2
    BigDecimal price;
    // Perform computations...
    return price;
  }
}
1 Logged is an interceptor binding.
2 Each method invocation is intercepted if there is an interceptor associated with Logged.

Limitations

  • Only method-level bindings are considered for backward compatibility reasons (otherwise static methods of bean classes that declare class-level bindings would be suddenly intercepted)

  • Private static methods are never intercepted

  • InvocationContext#getTarget() returns null for obvious reasons; therefore not all existing interceptors may behave correctly when intercepting static methods

拦截器可以使用 `InvocationContext.getMethod()`来检测静态方法并相应地调整行为。

Interceptors can use InvocationContext.getMethod() to detect static methods and adjust the behavior accordingly.

Ability to handle 'final' classes and methods

在普通 CDI 中,标记为 final`和/或具有 `final`方法的类不适合用于创建代理,这意味着拦截器和普通的作用域 Bean 不能正常工作。尝试将 CDI 与 Kotlin 等备选 JVM 语言搭配使用时,这种情况非常常见,其中类和方法默认情况下为 `final

In normal CDI, classes that are marked as final and / or have final methods are not eligible for proxy creation, which in turn means that interceptors and normal scoped beans don’t work properly. This situation is very common when trying to use CDI with alternative JVM languages like Kotlin where classes and methods are final by default.

然而,当 quarkus.arc.transform-unproxyable-classes`设置为 `true(这是默认值)时,Quarkus 可以克服这些限制。

Quarkus however, can overcome these limitations when quarkus.arc.transform-unproxyable-classes is set to true (which is the default value).

Container-managed Concurrency

没有适用于 CDI Bean 的标准并发控制机制。尽管如此,Bean 实例仍可以从多个线程并发共享和访问。在这种情况下,它应该是线程安全的。你可以使用标准的 Java 结构(volatilesynchronizedReadWriteLock`等)或让容器控制并发访问。Quarkus 为此拦截器绑定提供了 `@io.quarkus.arc.Lock`和内置拦截器。与拦截 Bean 的上下文实例关联的每个拦截器实例都包含一个带有非公平排序策略的单独 `ReadWriteLock

There is no standard concurrency control mechanism for CDI beans. Nevertheless, a bean instance can be shared and accessed concurrently from multiple threads. In that case it should be thread-safe. You can use standard Java constructs (volatile, synchronized, ReadWriteLock, etc.) or let the container control the concurrent access. Quarkus provides @io.quarkus.arc.Lock and a built-in interceptor for this interceptor binding. Each interceptor instance associated with a contextual instance of an intercepted bean holds a separate ReadWriteLock with non-fair ordering policy.

io.quarkus.arc.Lock`是常规拦截器绑定,因此可以将其用于具有任何作用域的任何 Bean。但是,对于“共享”作用域(例如,@Singleton`和 @ApplicationScoped)尤其有用。

io.quarkus.arc.Lock is a regular interceptor binding and as such can be used for any bean with any scope. However, it is especially useful for "shared" scopes, e.g. @Singleton and @ApplicationScoped.

Container-managed Concurrency Example
import io.quarkus.arc.Lock;

@Lock 1
@ApplicationScoped
class SharedService {

  void addAmount(BigDecimal amount) {
    // ...changes some internal state of the bean
  }

  @Lock(value = Lock.Type.READ, time = 1, unit = TimeUnit.SECONDS) 2 3
  BigDecimal getAmount() {
    // ...it is safe to read the value concurrently
  }
}
1 @Lock (which maps to @Lock(Lock.Type.WRITE)) declared on the class instructs the container to lock the bean instance for any invocation of any business method, i.e. the client has "exclusive access" and no concurrent invocations will be allowed.
2 @Lock(Lock.Type.READ) overrides the value specified at class level. It means that any number of clients can invoke the method concurrently, unless the bean instance is locked by @Lock(Lock.Type.WRITE).
3 You can also specify the "wait time". If it’s not possible to acquire the lock in the given time a LockException is thrown.

Repeatable interceptor bindings

Quarkus 仅支持 `@Repeatable`拦截器绑定注释。

Quarkus has limited support for @Repeatable interceptor binding annotations.

将拦截器绑定到组件时,可以在方法上声明多个 `@Repeatable`注释。在类和类型上声明的可重复拦截器绑定不受支持,因为在与拦截器规范的相互作用周围存在一些悬而未决的问题。这可能会在将来添加。

When binding an interceptor to a component, you can declare multiple @Repeatable annotations on methods. Repeatable interceptor bindings declared on classes and stereotypes are not supported, because there are some open questions around interactions with the Interceptors specification. This might be added in the future.

例如,假设我们有一个清除缓存的拦截器。相应的拦截器绑定将称为 @CacheInvalidateAll`并声明为 `@Repeatable。如果我们想同时清除两个缓存,我们将添加 `@CacheInvalidateAll`两次:

As an example, suppose we have an interceptor that clears a cache. The corresponding interceptor binding would be called @CacheInvalidateAll and would be declared as @Repeatable. If we wanted to clear two caches at the same time, we would add @CacheInvalidateAll twice:

@ApplicationScoped
class CachingService {
  @CacheInvalidateAll(cacheName = "foo")
  @CacheInvalidateAll(cacheName = "bar")
  void heavyComputation() {
    // ...
    // some computation that updates a lot of data
    // and requires 2 caches to be invalidated
    // ...
  }
}

这是拦截器的使用方法。那么如何创建拦截器呢?

This is how interceptors are used. What about creating an interceptor?

在声明拦截器的拦截器绑定时,可以像往常一样向拦截器类添加多个 `@Repeatable`注释。当注释成员为 `@Nonbinding`时这是无用的,例如 `@Cached`注释的情况,但在其他情况下则很重要。

When declaring interceptor bindings of an interceptor, you can add multiple @Repeatable annotations to the interceptor class as usual. This is useless when the annotation members are @Nonbinding, as would be the case for the @Cached annotation, but is important otherwise.

例如,假设我们有一个拦截器,可以将方法调用自动记录到特定目标。拦截器绑定的注释 `@Logged`将有一个称为 `target`的成员,它指定要存储日志的位置。我们的实现可以限制为控制台日志记录和文件日志记录:

For example, suppose we have an interceptor that can automatically log method invocations to certain targets. The interceptor binding annotation @Logged would have a member called target, which specifies where to store the log. Our implementation could be restricted to console logging and file logging:

@Interceptor
@Logged(target = "console")
@Logged(target = "file")
class NaiveLoggingInterceptor {
  // ...
}

其他拦截器可用于将方法调用记录到不同的目标。

Other interceptors could be provided to log method invocations to different targets.

Caching the Result of Programmatic Lookup

在某些情况下,通过注入的 `jakarta.enterprise.inject.Instance`和 `Instance.get()`以编程方式获取一个 Bean 实例是实用可行的。然而,根据规范, `get()`方法必须标识匹配的 Bean 并获得一个上下文引用。因此,在每次调用 `get()`时,都会返回一个 `@Dependent`Bean 的新实例。此外,此实例是注入的 `Instance`的一个从属对象。这种行为是明确规定的,但它可能会导致意外错误和内存泄漏。因此,Quarkus 提供了 `io.quarkus.arc.WithCaching`注解。使用此注解进行注解的已注入 `Instance`会缓存 `Instance#get()`操作的结果。结果在第一次调用时计算好,并且在所有后续调用(即使是 `@Dependent`Bean)中,都会返回相同的值。

In certain situations, it is practical to obtain a bean instance programmatically via an injected jakarta.enterprise.inject.Instance and Instance.get(). However, according to the specification the get() method must identify the matching bean and obtain a contextual reference. As a consequence, a new instance of a @Dependent bean is returned from each invocation of get(). Moreover, this instance is a dependent object of the injected Instance. This behavior is well-defined, but it may lead to unexpected errors and memory leaks. Therefore, Quarkus comes with the io.quarkus.arc.WithCaching annotation. An injected Instance annotated with this annotation will cache the result of the Instance#get() operation. The result is computed on the first call and the same value is returned for all subsequent calls, even for @Dependent beans.

class Producer {

  AtomicLong nextLong = new AtomicLong();
  AtomicInteger nextInt = new AtomicInteger();

   @Dependent
   @Produces
   Integer produceInt() {
     return nextInt.incrementAndGet();
   }

   @Dependent
   @Produces
   Long produceLong() {
     return nextLong.incrementAndGet();
   }
}

class Consumer {

  @Inject
  Instance<Long> longInstance;

  @Inject
  @WithCaching
  Instance<Integer> intInstance;

  // this method should always return true
  // Producer#produceInt() is only called once
  boolean pingInt() {
    return intInstance.get().equals(intInstance.get());
  }

  // this method should always return false
  // Producer#produceLong() is called twice per each pingLong() invocation
  boolean pingLong() {
    return longInstance.get().equals(longInstance.get());
  }
}

还可以通过 io.quarkus.arc.InjectableInstance.clearCache()`清除缓存值。在这种情况下,您需要注入 Quarkus 特有的 `io.quarkus.arc.InjectableInstance`而不是 `jakarta.enterprise.inject.Instance

It is also possible to clear the cached value via io.quarkus.arc.InjectableInstance.clearCache(). In this case, you’ll need to inject the Quarkus-specific io.quarkus.arc.InjectableInstance instead of jakarta.enterprise.inject.Instance.

Declaratively Choose Beans That Can Be Obtained by Programmatic Lookup

有时缩小可以通过 `jakarta.enterprise.inject.Instance`编程查找获取的 Bean 集合非常有用。通常,用户需要基于运行时配置属性选择接口的适当实现。

It is sometimes useful to narrow down the set of beans that can be obtained by programmatic lookup via jakarta.enterprise.inject.Instance. Typically, a user needs to choose the appropriate implementation of an interface based on a runtime configuration property.

想象一下,我们有两个 Bean,它们实现了接口 org.acme.Service。除非您的实现声明一个 CDI 限定符,否则不能直接注入 org.acme.Service。但是,您可以转而注入 Instance<Service>,然后遍历所有实现并手动选择正确的实现。或者,您也可以使用 @LookupIfProperty`和 `@LookupUnlessProperty`注解。@LookupIfProperty`表示仅当运行时配置属性与提供的值相匹配时,才获取 Bean。另一方面,`@LookupUnlessProperty`表示仅当运行时配置属性与提供的值不相匹配时,才获取 Bean。

Imagine that we have two beans implementing the interface org.acme.Service. You can’t inject the org.acme.Service directly unless your implementations declare a CDI qualifier. However, you can inject the Instance<Service> instead, then iterate over all implementations and choose the correct one manually. Alternatively, you can use the @LookupIfProperty and @LookupUnlessProperty annotations. @LookupIfProperty indicates that a bean should only be obtained if a runtime configuration property matches the provided value. @LookupUnlessProperty, on the other hand, indicates that a bean should only be obtained if a runtime configuration property does not match the provided value.

@LookupIfProperty Example
 interface Service {
    String name();
 }

 @LookupIfProperty(name = "service.foo.enabled", stringValue = "true")
 @ApplicationScoped
 class ServiceFoo implements Service {

    public String name() {
       return "foo";
    }
 }

 @ApplicationScoped
 class ServiceBar implements Service {

    public String name() {
       return "bar";
    }
 }

 @ApplicationScoped
 class Client {

    @Inject
    Instance<Service> service;

    void printServiceName() {
       // This will print "bar" if the property "service.foo.enabled" is NOT set to "true"
       // If "service.foo.enabled" is set to "true" then service.get() would result in an AmbiguousResolutionException
       System.out.println(service.get().name());
    }
 }

Injecting Multiple Bean Instances Intuitively

在 CDI 中,可通过实现 `java.lang.Iterable`的 `jakarta.enterprise.inject.Instance`注入多个 Bean 实例(也称为上下文引用)。然而,这并非直观。因此,Quarkus 中引入了一种新方式 - 您可使用 `io.quarkus.arc.All`限定符对 `java.util.List`进行注解。执行查找时,列表中元素的类型用作必需类型。

In CDI, it’s possible to inject multiple bean instances (aka contextual references) via the jakarta.enterprise.inject.Instance which implements java.lang.Iterable. However, it’s not exactly intuitive. Therefore, a new way was introduced in Quarkus - you can inject a java.util.List annotated with the io.quarkus.arc.All qualifier. The type of elements in the list is used as the required type when performing the lookup.

@ApplicationScoped
public class Processor {

     @Inject
     @All
     List<Service> services; 1 2
}
1 The injected instance is an immutable list of the contextual references of the disambiguated beans.
2 For this injection point the required type is Service and no additional qualifiers are declared.

列表按照 io.quarkus.arc.InjectableBean#getPriority()`定义的优先级排序。优先级越高,排列越靠前。通常情况下,@jakarta.annotation.Priority`注解可用于为类 Bean、生产者方法或生产者字段分配优先级。

The list is sorted by priority as defined by io.quarkus.arc.InjectableBean#getPriority(). Higher priority goes first. In general, the @jakarta.annotation.Priority annotation can be used to assign the priority to a class bean, producer method or producer field.

如果一个注入点未声明 @All`以外的其他限定符,则使用 `@Any,即行为等同于 @Inject @Any Instance<Service>

If an injection point declares no other qualifier than @All then @Any is used, i.e. the behavior is equivalent to @Inject @Any Instance<Service>.

您还可以注入一个包装在 `io.quarkus.arc.InstanceHandle`中的 Bean 实例列表。如果您需要检查相关的 Bean 元数据,这可能非常有用。

You can also inject a list of bean instances wrapped in io.quarkus.arc.InstanceHandle. This can be useful if you need to inspect the related bean metadata.

@ApplicationScoped
public class Processor {

     @Inject
     @All
     List<InstanceHandle<Service>> services;

     public void doSomething() {
       for (InstanceHandle<Service> handle : services) {
         if (handle.getBean().getScope().equals(Dependent.class)) {
           handle.get().process();
           break;
         }
       }
     }
}

类型变量和通配符都不是 @All List<>`注入点的合法类型参数,即不支持 `@Inject @All List<?> all,而且会导致发生部署错误。

Neither a type variable nor a wildcard is a legal type parameter for an @All List<> injection point, i.e. @Inject @All List<?> all is not supported and results in a deployment error.

还可以通过 `Arc.container().listAll()`方法以编程方式获取所有 Bean 实例句柄的列表。

It is also possible to obtain the list of all bean instance handles programmatically via the Arc.container().listAll() methods.

Ignoring Class-Level Interceptor Bindings for Methods and Constructors

如果管理 Bean 在类级别声明拦截器绑定注解,则相应的 `@AroundInvoke`拦截器将适用于所有业务方法。类似地,相应的 `@AroundConstruct`拦截器将适用于 Bean 构造器。

If a managed bean declares interceptor binding annotations on the class level, the corresponding @AroundInvoke interceptors will apply to all business methods. Similarly, the corresponding @AroundConstruct interceptors will apply to the bean constructor.

例如,假设我们有一个带有 `@Logged`绑定注解的日志拦截器,以及一个带有 `@Traced`绑定注解的追踪拦截器:

For example, suppose we have a logging interceptor with the @Logged binding annotation and a tracing interceptor with the @Traced binding annotation:

@ApplicationScoped
@Logged
public class MyService {
    public void doSomething() {
        ...
    }

    @Traced
    public void doSomethingElse() {
        ...
    }
}

在此示例中,`doSomething`和 `doSomethingElse`都将被假想的日志拦截器拦截。另外,`doSomethingElse`方法将被假想的追踪拦截器拦截。

In this example, both doSomething and doSomethingElse will be intercepted by the hypothetical logging interceptor. Additionally, the doSomethingElse method will be intercepted by the hypothetical tracing interceptor.

现在,如果该 `@Traced`拦截器还执行了所有必要的日志记录,我们希望跳过此方法的 `@Logged`拦截器,但将其保留在所有其他方法中。要做到这一点,您可以使用 `@NoClassInterceptors`对此方法进行注解:

Now, if that @Traced interceptor also performed all the necessary logging, we’d like to skip the @Logged interceptor for this method, but keep it for all other methods. To achieve that, you can annotate the method with @NoClassInterceptors:

@Traced
@NoClassInterceptors
public void doSomethingElse() {
    ...
}

`@NoClassInterceptors`注解可用于方法和构造器,表示此方法和构造器忽略所有类级别拦截器。换句话说,如果一个方法/构造器用 `@NoClassInterceptors`进行注解,则适用于此方法/构造器的唯一拦截器是在该方法/构造器上直接声明的拦截器。

The @NoClassInterceptors annotation may be put on methods and constructors and means that all class-level interceptors are ignored for these methods and constructors. In other words, if a method/constructor is annotated @NoClassInterceptors, then the only interceptors that will apply to this method/constructor are interceptors declared directly on the method/constructor.

此注解只影响业务方法拦截器 (@AroundInvoke) 和构造器生命周期回调拦截器 (@AroundConstruct)。

This annotation affects only business method interceptors (@AroundInvoke) and constructor lifecycle callback interceptors (@AroundConstruct).

Exceptions Thrown By An Asynchronous Observer Method

如果异步观察器抛出了异常,那么 CompletionStage 方法返回的 fireAsync() 异常完成,以便事件生产者可以做出适当的反应。但是,如果事件生产者不关心,那么异常将被静默忽略。因此,Quarkus 默认会记录一条错误消息。还可以实现一个自定义的 AsyncObserverExceptionHandler。实现此接口的 Bean 应当是 @jakarta.inject.Singleton@jakarta.enterprise.context.ApplicationScoped

If an exception is thrown by an asynchronous observer then the CompletionStage returned by the fireAsync() method completes exceptionally so that the event producer may react appropriately. However, if the event producer does not care then the exception is ignored silently. Therefore, Quarkus logs an error message by default. It is also possible to implement a custom AsyncObserverExceptionHandler. A bean that implements this interface should be @jakarta.inject.Singleton or @jakarta.enterprise.context.ApplicationScoped.

NoopAsyncObserverExceptionHandler
@Singleton
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {

  void handle(Throwable throwable, ObserverMethod<?> observerMethod, EventContext<?> eventContext) {
    // do nothing
  }

}

Intercepted self-invocation

Quarkus 支持所谓的拦截自身调用,或者仅支持自拦截——一种场景,在其中 CDI Bean 从其他方法中调用其自己的拦截方法,同时触发任何相关拦截器。这是一项非标准功能,因为 CDI 规范并没有定义自拦截是否应该运行。

Quarkus supports what is known as intercepted self-invocation or just self-interception - a scenario where CDI bean invokes its own intercepted method from within another method while triggering any associated interceptors. This is a non-standard feature as CDI specification doesn’t define whether self-interception should work or not.

假设我们有一个有两个方法的 CDI Bean,其中一个方法带有 @Transactional 拦截器绑定:

Suppose we have a CDI bean with two methods, one of which has the @Transactional interceptor binding associated with it:

@ApplicationScoped
public class MyService {

  @Transactional 1
  void doSomething() {
    // some application logic
  }

  void doSomethingElse() {
    doSomething();2
  }

}
1 One or more interceptor bindings; @Transactional is just an example.
2 Non-intercepted method invoking another method from the same bean that has associated binding(s); this will trigger interception.

在上面的示例中,调用 doSomething() 方法的任何代码都将触发拦截,在这种情况下,方法变为事务性。无论调用是否直接来自 MyService Bean(例如 MyService#doSomethingElse),还是来自其他 Bean。

In the above example, any code calling the doSomething() method triggers interception - in this case, the method becomes transactional. This is regardless of whether the invocation originated directly from the MyService bean (such as MyService#doSomethingElse) or from some other bean.

Intercepting Producer Methods and Synthetic Beans

默认情况下,仅对托管 Bean(也称为基于类的 Bean)支持拦截。为了支持生产者方法和合成 Bean 的拦截,CDI 规范包括 InterceptionFactory,这是一个面向运行时的概念,因此无法在 Quarkus 中得到支持。

By default, interception is only supported for managed beans (also known as class-based beans). To support interception of producer methods and synthetic beans, the CDI specification includes an InterceptionFactory, which is a runtime oriented concept and therefore cannot be supported in Quarkus.

相反,Quarkus 有自己的 API: InterceptionProxy@BindingsSourceInterceptionProxyInterceptionFactory 非常相似:它创建一个在将方法调用转发到目标实例之前应用 @AroundInvoke 拦截器的代理。@BindingsSource 注释允许在拦截的类处于外部且无法更改的情况下设置拦截器绑定。

Instead, Quarkus has its own API: InterceptionProxy and @BindingsSource. The InterceptionProxy is very similar to InterceptionFactory: it creates a proxy that applies @AroundInvoke interceptors before forwarding the method call to the target instance. The @BindingsSource annotation allows setting interceptor bindings in case the intercepted class is external and cannot be changed.

import io.quarkus.arc.InterceptionProxy;

@ApplicationScoped
class MyProducer {
    @Produces
    MyClass produce(InterceptionProxy<MyClass> proxy) { (1)
        return proxy.create(new MyClass()); (2)
    }
}
1 Declares an injection point of type InterceptionProxy<MyClass>. This means that at build time, a subclass of MyClass is generated that does the interception and forwarding. Note that the type argument must be identical to the return type of the producer method.
2 Creates an instance of the interception proxy for the given instance of MyClass. The method calls will be forwarded to this target instance after all interceptors run.

在此示例中,拦截器绑定从 MyClass 类中读取。

In this example, interceptor bindings are read from the MyClass class.

请注意,InterceptionProxy 仅支持在拦截器类中声明的 @AroundInvoke 拦截器。其他类型的拦截,以及在目标类及其超类中声明的 @AroundInvoke 拦截器不受支持。

Note that InterceptionProxy only supports @AroundInvoke interceptors declared on interceptor classes. Other kinds of interception, as well as @AroundInvoke interceptors declared on the target class and its superclasses, are not supported.

拦截的类应该是 proxyable,因此不应该是 final,不应该有非私有的 final 方法,并且应该有一个非私有的零参数构造函数。如果不满足要求,那么如果 enabled,字节码转换将尝试修复它,但请注意,添加零参数构造函数并不总是可能的。

The intercepted class should be proxyable and therefore should not be final, should not have non-private final methods, and should have a non-private zero-parameter constructor. If it isn’t, a bytecode transformation will attempt to fix it if unproxyable_classes_transformation, but note that adding a zero-parameter constructor is not always possible.

通常,产生的类来自外部库,根本不包含拦截器绑定注释。为了支持这种情况,可以在 InterceptionProxy 参数中声明 @BindingsSource 注释:

It is often the case that the produced classes come from external libraries and don’t contain interceptor binding annotations at all. To support such cases, the @BindingsSource annotation may be declared on the InterceptionProxy parameter:

import io.quarkus.arc.BindingsSource;
import io.quarkus.arc.InterceptionProxy;

abstract class MyClassBindings { (1)
    @MyInterceptorBinding
    abstract String doSomething();
}

@ApplicationScoped
class MyProducer {
    @Produces
    MyClass produce(@BindingsSource(MyClassBindings.class) InterceptionProxy<MyClass> proxy) { (2)
        return proxy.create(new MyClass());
    }
}
1 A class that mirrors the MyClass structure and contains interceptor bindings.
2 The @BindingsSource annotation says that interceptor bindings for MyClass should be read from MyClassBindings.

bindings source 的概念是 InterceptionFactory.configure() 的构建时友好等价物。

The concept of bindings source is a build-time friendly equivalent of InterceptionFactory.configure().

生产者方法拦截和合成 Bean 拦截仅适用于实例方法。Interception of Static Methods 不支持生产者方法和合成 Bean。

Producer method interception and synthetic bean interception only works for instance methods. Interception of Static Methods is not supported for producer methods and synthetic beans.

Declaring @BindingsSource

@BindingsSource 注释指定了一个反映拦截类结构的类。然后从该类中读取拦截器绑定,并将其视为在拦截类中声明。

The @BindingsSource annotation specifies a class that mirrors the structure of the intercepted class. Interceptor bindings are then read from that class and treated as if they were declared on the intercepted class.

具体来说:绑定源类上声明的类级拦截器绑定被视为拦截类中的类级绑定。在绑定源类上声明的方法级拦截器绑定被视为具有与被拦截类的同名、返回值类型、参数类型和 static 标记的方法的方法级绑定。

Specifically: class-level interceptor bindings declared on the bindings source class are treated as class-level bindings of the intercepted class. Method-level interceptor bindings declared on the bindings source class are treated as method-level bindings of a method with the same name, return type, parameter types and static flag of the intercepted class.

通常让绑定源类和方法 abstract 以便您可以不必编写方法正文:

It is common to make the bindings source class and methods abstract so that you don’t have to write method bodies:

abstract class MyClassBindings {
    @MyInterceptorBinding
    abstract String doSomething();
}

因为此类从未被实例化,并且其方法从未被调用,所以这没问题,但是也可以创建一个非 abstract 类:

Since this class is never instantiated and its method are never invoked, this is okay, but it’s also possible to create a non-abstract class:

class MyClassBindings {
    @MyInterceptorBinding
    String doSomething() {
        return null; (1)
    }
}
1 The method body does not matter.

请注意,对于泛型类,类型变量名还必须是相同的。例如,对于以下类:

Note that for generic classes, the type variable names must also be identical. For example, for the following class:

class MyClass<T> {
    T doSomething() {
        ...
    }

    void doSomethingElse(T param) {
        ...
    }
}

绑定源类还必须将 T 用作类型变量的名称:

the bindings source class must also use T as the name of the type variable:

abstract class MyClassBindings<T> {
    @MyInterceptorBinding
    abstract T doSomething();
}

您无需仅仅因为它们存在于被拦截类中而声明未注释的方法。如果您想向部分方法添加方法级绑定,您只需声明应该具有拦截器绑定的方法。如果您只想添加类级绑定,您根本无需声明任何方法。

You don’t need to declare methods that are not annotated simply because they exist on the intercepted class. If you want to add method-level bindings to a subset of methods, you only have to declare the methods that are supposed to have an interceptor binding. If you only want to add class-level bindings, you don’t have to declare any methods at all.

这些注释可能存在于绑定源类上:

These annotations can be present on a bindings source class:

  • interceptor bindings: on the class and on the methods

  • stereotypes: on the class

  • @NoClassInterceptors: on the methods

绑定源类上存在的任何其他注释都会被忽略。

Any other annotation present on a bindings source class is ignored.

Synthetic Beans

在合成 bean 中使用 InterceptionProxy 是相似的。

Using InterceptionProxy in synthetic beans is similar.

首先,您必须声明您的合成 bean 注入 InterceptionProxy

First, you have to declare that your synthetic bean injects the InterceptionProxy:

public void register(RegistrationContext context) {
    context.configure(MyClass.class)
            .types(MyClass.class)
            .injectInterceptionProxy() (1)
            .creator(MyClassCreator.class)
            .done();
}
1 Once again, this means that at build time, a subclass of MyClass is generated that does the interception and forwarding.

其次,您必须从 BeanCreator 中的 SyntheticCreationalContext 中获取 InterceptionProxy 并使用它:

Second, you have to obtain the InterceptionProxy from the SyntheticCreationalContext in the BeanCreator and use it:

public MyClass create(SyntheticCreationalContext<MyClass> context) {
    InterceptionProxy<MyClass> proxy = context.getInterceptionProxy(); (1)
    return proxy.create(new MyClass());
}
1 Obtains the InterceptionProxy for MyClass, as declared above. It would also be possible to use the getInjectedReference() method, passing a TypeLiteral, but getInterceptionProxy() is easier.

@BindingsSource 还有一个等效项。injectInterceptionProxy() 方法具有带参数的重载:

There’s also an equivalent of @BindingsSource. The injectInterceptionProxy() method has an overload with a parameter:

public void register(RegistrationContext context) {
    context.configure(MyClass.class)
            .types(MyClass.class)
            .injectInterceptionProxy(MyClassBindings.class) (1)
            .creator(MyClassCreator.class)
            .done();
}
1 The argument is the bindings source class.

Pitfalls with Reactive Programming

CDI 是一个纯粹的同步框架。其异步概念非常有限,完全基于线程池和线程卸载。因此,在将 CDI 与反应式编程一起使用时有许多陷阱。

CDI is a purely synchronous framework. Its notion of asynchrony is very limited and based solely on thread pools and thread offloading. Therefore, there is a number of pitfalls when using CDI together with reactive programming.

Detecting When Blocking Is Allowed

io.quarkus.runtime.BlockingOperationControl#isBlockingAllowed() 方法可用于检测当前线程是否允许阻塞。当它不允许而您需要执行阻塞操作时,您必须将其卸载到其他线程。最简单的方法是使用 Vertx.executeBlocking() 方法:

The io.quarkus.runtime.BlockingOperationControl#isBlockingAllowed() method can be used to detect whether blocking is allowed on the current thread. When it is not, and you need to perform a blocking operation, you have to offload it to another thread. The easiest way is to use the Vertx.executeBlocking() method:

import io.quarkus.runtime.BlockingOperationControl;

@ApplicationScoped
public class MyBean {
    @Inject
    Vertx vertx;

    @PostConstruct
    void init() {
        if (BlockingOperationControl.isBlockingAllowed()) {
            somethingThatBlocks();
        } else {
            vertx.executeBlocking(() -> {
                somethingThatBlocks();
                return null;
            });
        }
    }

    void somethingThatBlocks() {
        // use the file system or JDBC, call a REST service, etc.
        Thread.sleep(5000);
    }
}

Build Time Extensions

Quarkus 集成了构建时优化,以便提供即时启动和低内存占用。此方法的缺点是无法支持 CDI 便携式扩展。不过,大部分功能可以使用 Quarkus extensions实现。有关详情,请参阅 integration guide

Quarkus incorporates build-time optimizations in order to provide instant startup and low memory footprint. The downside of this approach is that CDI Portable Extensions cannot be supported. Nevertheless, most of the functionality can be achieved using Quarkus extensions. See the integration guide for more information.

[id="development-mode"] Dev mode

在开发模式中,自动注册两个特殊端点,以 JSON 格式提供一些基本的调试信息:

In dev mode, two special endpoints are registered automatically to provide some basic debug info in the JSON format:

这些端点仅在开发模式下可用,即通过 mvn quarkus:dev(或 ./gradlew quarkusDev)运行应用程序时。

These endpoints are only available in dev mode, i.e. when you run your application via mvn quarkus:dev (or ./gradlew quarkusDev).

Monitoring Business Method Invocations and Events

在开发模式中,还可以启用对业务方法调用和触发事件的监控。只需将 quarkus.arc.dev-mode.monitoring-enabled`配置属性设置为 `true,并浏览相关的 Dev UI 页面。

In dev mode, it is also possible to enable monitoring of business method invocations and fired events. Simply set the quarkus.arc.dev-mode.monitoring-enabled configuration property to true and explore the relevant Dev UI pages.

Strict Mode

默认情况下,ArC 不会执行 CDI 规范要求的所有验证。它还在很多方面改进了 CDI 的可用性,其中一些方面直接违反规范。

By default, ArC does not perform all validations required by the CDI specification. It also improves CDI usability in many ways, some of them being directly against the specification.

为了通过 CDI Lite TCK,ArC 还有一个 _strict_模式。此模式会启用其他验证,并禁用与规范冲突的特定改进。

To pass the CDI Lite TCK, ArC also has a strict mode. This mode enables additional validations and disables certain improvements that conflict with the specification.

要启用严格模式,请使用以下配置:

To enable the strict mode, use the following configuration:

quarkus.arc.strict-compatibility=true

其他一些功能还会影响规范兼容性:

Some other features affect specification compatibility as well:

要获得更接近规范的行为,应禁用这些功能。

To get a behavior closer to the specification, these features should be disabled.

建议应用程序使用默认的非严格模式,这使得 CDI 更易于使用。严格模式的“严格性”(在 CDI 规范之上执行其他验证和禁用改进的集合)可能会随时间而变化。

Applications are recommended to use the default, non-strict mode, which makes CDI more convenient to use. The "strictness" of the strict mode (the set of additional validations and the set of disabled improvements on top of the CDI specification) may change over time.

ArC Configuration Reference

Unresolved directive in cdi-reference.adoc - include::{generated-dir}/config/quarkus-arc.adoc[]