编辑此页面

上下文和依赖注入

Quarkus DI 解决方案(也称为 ArC)基于 Jakarta Contexts and Dependency Injection 4.1 规范。它实现了 CDI Lite 规范,并在其基础上进行了一些改进,并通过了 CDI Lite TCK。它没有实现 CDI Full。另请参阅支持的功能和限制列表。大多数现有的 CDI 代码应该可以正常工作,但由于 Quarkus 架构和目标的原因,存在一些小的差异。

如果您是 CDI 的新手,我们建议您首先阅读CDI 简介
CDI 集成指南包含有关常见 CDI 相关集成用例的更多详细信息,以及解决方案的示例代码。

1. Bean 发现

CDI 中的 Bean 发现是一个复杂的过程,涉及遗留的部署结构和底层模块架构的可访问性要求。但是,Quarkus 使用的是简化的 Bean 发现。只有一个 Bean 存档,其Bean 发现模式为 annotated,并且没有可见性边界。

Bean 存档由以下内容合成:

  • 应用程序类,

  • 包含 beans.xml 描述符的依赖项(内容被忽略),

  • 包含 Jandex 索引的依赖项 - META-INF/jandex.idx

  • application.properties 中由 quarkus.index-dependency 引用的依赖项,

  • 和 Quarkus 集成代码。

没有Bean 定义注解的 Bean 类不会被发现。此行为由 CDI 定义。但是,即使声明类没有使用 Bean 定义注解进行注释,也会发现生产者方法和字段以及观察者方法(此行为与 CDI 中定义的行为不同)。实际上,声明 Bean 类被视为使用 @Dependent 注释。

Quarkus 扩展可能会声明其他发现规则。例如,即使声明类没有使用 Bean 定义注解进行注释,也会注册 @Scheduled 业务方法。

1.1. 如何生成 Jandex 索引

具有 Jandex 索引的依赖项会自动扫描 Bean。要生成索引,只需将以下插件添加到您的构建文件中

Maven
<build>
  <plugins>
    <plugin>
      <groupId>io.smallrye</groupId>
      <artifactId>jandex-maven-plugin</artifactId>
      <version>3.3.2</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 '1.0.0'
}

您可以在Gradle 插件门户中找到最新的插件版本

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

您可以在Gradle 插件门户中找到最新的插件版本

如果您无法修改依赖项,您仍然可以通过将 quarkus.index-dependency 条目添加到您的 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 的所有依赖项。

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

示例 application.properties
quarkus.index-dependency.acme.group-id=org.acme (1)
quarkus.index-dependency.acme.artifact-id=acme-api (2)
1 该值是名称为 acme 的依赖项的组 ID。
2 该值是名称为 acme 的依赖项的构件 ID。

1.2. 如何从发现中排除类型和依赖项

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

表 1. 值示例

描述

org.acme.Foo

匹配类的完全限定名称

org.acme.*

匹配具有包 org.acme 的类

org.acme.**

匹配包以 org.acme 开头的类

Bar

匹配类的简单名称

示例 application.properties
quarkus.arc.exclude-types=org.acme.Foo,org.acme.*,Bar (1)(2)(3)
1 排除类型 org.acme.Foo
2 排除 org.acme 包中的所有类型。
3 排除所有简单名称为 Bar 的类型

也可以排除依赖项构件,否则会扫描该构件以查找 Bean。例如,因为它包含 beans.xml 描述符。

示例 application.properties
quarkus.arc.exclude-dependency.acme.group-id=org.acme (1)
quarkus.arc.exclude-dependency.acme.artifact-id=acme-services (2)
1 该值是名称为 acme 的依赖项的组 ID。
2 该值是名称为 acme 的依赖项的构件 ID。

2. 基于字符串的限定符

您可能熟悉的 @Named 限定符是一个基于字符串的限定符。也就是说,限定符注解的字符串值决定了限定符是否匹配。这是不类型安全的,不应该是 CDI 应用程序中的常态。应首选特定的限定符类型。

但是,有时基于字符串的限定符是必要的。在这种情况下,请避免使用 @Named 限定符,因为在 CDI 中,它的工作方式与所有其他限定符不同。

具体来说:如果 Bean 唯一的限定符是 @Named,那么它也会自动获得 @Default。这意味着如果存在相同类型的多个 Bean,其中一个没有限定符,而其他 Bean 具有 @Named,那么它们会获得 @Default 限定符,并且 Bean 解析会因歧义而报错。例如

@ApplicationScoped
public class Producers {
    @Produces
    MyBean produce() {
        ...
    }

    @Produces
    @Named("foo")
    MyBean produceFoo() {
        ...
    }
}

@ApplicationScoped
public class Consumer {
    @Inject
    MyBean bean;
}

在这种情况下,Consumer#bean 注入点将导致歧义错误,因为两个 MyBean 生产者都将具有 @Default 限定符。

可以使用 @io.smallrye.common.annotation.Identifier 代替 @Named。这是一个常规限定符,其工作方式与其他限定符类似。因此,如果我们将示例重写为使用 @Identifier

@ApplicationScoped
public class Producers {
    @Produces
    MyBean produce() {
        ...
    }

    @Produces
    @Identifier("foo")
    MyBean produceFoo() {
        ...
    }
}

@ApplicationScoped
public class Consumer {
    @Inject
    MyBean bean;
}

只有第一个生产者会获得 @Default 限定符,第二个不会。因此,不会有错误,一切都会按预期工作。

2.1. 何时使用 @Named

在一种情况下,使用 @Named 是正确的:为不支持直接依赖注入的其他语言指定外部标识符。

例如

@ApplicationScoped
@Named("myBean")
public class MyBean {
    public String getValue() {
        ...
    }
}

@ApplicationScoped
public class Consumer {
    @Inject
    MyBean bean;
}

如您所见,在应用程序代码中,Bean 是在没有限定符的情况下注入的。Bean 名称仅用于在其他语言中引用 Bean。

从历史上看,使用 Bean 名称最常见的外部语言是 JSF。在 Quarkus 中,我们有Qute。在 Qute 模板中,可以使用其名称引用 Bean

The current value is {inject:myBean.value}.

在此用例之外,只需使用 @Identifier

3. 本地可执行文件和私有成员

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

如果 Quarkus DI 需要访问私有成员,则必须使用反射。这就是为什么鼓励 Quarkus 用户不要在他们的 Bean 中使用私有成员的原因。这包括注入字段、构造函数和初始化程序、观察者方法、生产者方法和字段、处置者和拦截器方法。

如何避免使用私有成员?您可以使用包私有修饰符

@ApplicationScoped
public class CounterBean {

    @Inject
    CounterService counterService; (1)

    void onMessage(@Observes Event msg) { (2)
    }
}
1 包私有注入字段。
2 包私有观察者方法。

或者构造函数注入

@ApplicationScoped
public class CounterBean {

    private CounterService service;

    CounterBean(CounterService service) { (1)
      this.service = service;
    }
}
1 包私有构造函数注入。在这种特殊情况下,@Inject 是可选的。

4. 支持的功能和限制

完全支持 CDI Lite 规范。还支持 CDI Full 中的以下功能

  • 装饰器

    • 不支持内置 Bean(如 Event)的装饰

  • BeanManager

    • 除了 BeanContainer 方法外,还支持以下方法:getInjectableReference()resolveDecorators()

  • @SessionScoped

    • 仅适用于 Undertow 扩展;有关详细信息,请参阅此处

方法调用器实现支持异步方法。以下方法被认为是异步的,并且只有在异步操作完成后才会销毁 @Dependent 实例

  • 声明 CompletionStageUniMulti 返回类型的方法

这些附加功能不在 CDI Lite TCK 的范围内。

5. 非标准功能

5.1. Bean 的急切实例化

5.1.1. 默认情况下延迟

默认情况下,CDI Bean 是在需要时延迟创建的。 “需要”的确切含义取决于 Bean 的作用域。

  • 普通作用域 Bean@ApplicationScoped@RequestScoped 等)是在注入实例(规范中每个上下文引用的上下文引用)上调用方法时需要的。

    换句话说,注入普通作用域 Bean 是不够的,因为会注入客户端代理而不是 Bean 的上下文实例。

  • 具有伪作用域的 Bean@Dependent@Singleton)是在注入时创建的。

延迟实例化示例
@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 注入触发 AmazingService 的实例化。
2 注入本身不会导致 CoolService 的实例化。注入了一个客户端代理。
3 在注入的代理上第一次调用会触发 CoolService 的实例化。

5.1.2. 启动事件

但是,如果您确实需要急切地实例化 Bean,您可以

  • 声明 StartupEvent 的观察者 - 在这种情况下,Bean 的作用域无关紧要

    @ApplicationScoped
    class CoolService {
      void startup(@Observes StartupEvent event) { (1)
      }
    }
    1 CoolService 在启动期间创建,以服务观察者方法调用。
  • StartupEvent 的观察者中使用 Bean - 普通作用域 Bean 必须按照默认情况下延迟中的描述使用

    @Dependent
    class MyBeanStarter {
    
      void startup(@Observes StartupEvent event, AmazingService amazing, CoolService cool) { (1)
        cool.toString(); (2)
      }
    }
    1 AmazingService 在注入期间创建。
    2 CoolService 是一个普通作用域 Bean,因此我们必须在注入的代理上调用一个方法才能强制实例化。
  • 使用 @io.quarkus.runtime.Startup 注释 Bean,如启动注解中所述

    @Startup (1)
    @ApplicationScoped
    public class EagerAppBean {
    
       private final String name;
    
       EagerAppBean(NameGenerator generator) { (2)
         this.name = generator.createName();
       }
    }
    1 对于每个使用 @Startup 注释的 Bean,都会生成一个 StartupEvent 的合成观察者。使用默认优先级。
    2 在应用程序启动时调用 Bean 构造函数,并将生成的上下文实例存储在应用程序上下文中。
应用程序初始化和终止指南中所述,建议 Quarkus 用户始终优先选择 @Observes StartupEvent 而不是 @Initialized(ApplicationScoped.class)

5.2. 请求上下文生命周期

请求上下文也是活动的

  • 在同步观察者方法的通知期间。

请求上下文已销毁

  • 如果在通知开始时它尚未处于活动状态,则在观察者通知完成事件后。

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

5.2.1. 如何为请求上下文激活启用跟踪日志记录

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

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

5.3. 限定的注入字段

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

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

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

  @ConfigProperty(name = "cool")
  String coolProperty;
除了下面讨论的一个特殊情况外,构造函数和方法注入仍然需要 @Inject

5.4. 简化的构造函数注入

在 CDI 中,普通作用域 Bean 必须始终声明一个无参数构造函数(除非您声明任何其他构造函数,否则此构造函数通常由编译器生成)。但是,此要求使构造函数注入变得复杂 - 您需要提供一个虚拟的无参数构造函数才能使 CDI 中的工作正常进行。

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService() { // dummy constructor needed
  }

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

无需在 Quarkus 中为普通作用域 Bean 声明虚拟构造函数 - 它们会自动生成。此外,如果只有一个构造函数,则不需要 @Inject

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}
如果 Bean 类扩展了一个未声明无参数构造函数的类,则我们不会自动生成无参数构造函数。

5.5. 删除未使用的 Bean

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

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

5.5.1. 什么被删除了?

Quarkus 首先标识所谓的不可删除的 Bean,这些 Bean 构成了依赖树中的根。一个很好的例子是 Jakarta REST 资源类或声明 @Scheduled 方法的 Bean。

不可删除的 Bean

  • 从删除中排除,或者

  • 具有通过 @Named 指定的名称,或者

  • 声明了一个观察者方法。

未使用的 Bean

  • 不是不可删除的,并且

  • 不符合注入到不可删除的 Bean 的依赖树中的任何注入点的条件,并且

  • 不声明任何符合注入到依赖树中的任何注入点的生产者,并且

  • 不符合注入到任何 jakarta.enterprise.inject.Instancejakarta.inject.Provider 注入点的条件,并且

  • 不符合注入到任何@Inject @All List<>注入点的条件。

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

使用开发模式(运行 ./mvnw quarkus:dev)时,您可以查看有关哪些 Bean 被删除的更多信息

  1. 在控制台中 - 只需在您的 application.properties 中启用 DEBUG 级别,即 quarkus.log.category."io.quarkus.arc.processor".level=DEBUG

  2. 在相关的 Dev UI 页面中

5.5.2. 如何消除误报

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

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

表 2. 值示例

描述

org.acme.Foo

匹配 Bean 类的完全限定名称

org.acme.*

匹配 Bean 类的包为 org.acme 的 Bean

org.acme.**

匹配 Bean 类的包以 org.acme 开头的 Bean

Bar

匹配 Bean 类的简单名称

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

此外,扩展可以通过生成 UnremovableBeanBuildItem 来消除误报。

5.6. 默认 Bean

Quarkus 添加了一种 CDI 当前不支持的功能,即如果没有任何可用方法(Bean 类、生产者、合成 Bean 等)声明具有相同类型和限定符的其他 Bean,则有条件地声明一个 Bean。这是使用 @io.quarkus.arc.DefaultBean 注解完成的,最好用一个例子来解释。

假设有一个 Quarkus 扩展,它除了其他事情之外,还声明了一些 CDI Bean,如以下代码所示

@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。我们的应用程序中唯一需要的就是以下内容

@Dependent
public class CustomTracerConfiguration {

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

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

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

@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
    }
}

5.7. 为 Quarkus 构建配置文件启用 Bean

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

例如,假设一个应用程序包含一个名为 Tracer 的 Bean,该 Bean 在测试或开发模式下不需要执行任何操作,但在生产构件中以其正常能力工作。创建此类 Bean 的一种优雅方式如下

@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 将是理想的选择。代码将如下所示

@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 解析绝对没有影响。
也可以在构造型上使用 @IfBuildProfile@UnlessBuildProfile

5.8. 为 Quarkus 构建属性启用 Bean

Quarkus 添加了一种 CDI 当前不支持的功能,即通过 @io.quarkus.arc.properties.IfBuildProperty@io.quarkus.arc.properties.UnlessBuildProperty 注解,在 Quarkus 构建时属性具有或不具有特定值时有条件地启用 Bean。与 @io.quarkus.arc.DefaultBean 结合使用时,这些注解允许为不同的构建属性创建不同的 Bean 配置。

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

@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。

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

@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 解析绝对没有影响。
也可以在构造型上使用 @IfBuildProperty@UnlessBuildProperty

5.9. 声明选定的替代项

在 CDI 中,可以通过 @Priority 为应用程序全局选择替代 Bean,或者通过使用 beans.xml 描述符为 Bean 存档选择替代 Bean。 Quarkus 具有简化的 Bean 发现,并且 beans.xml 的内容将被忽略。

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

表 3. 值示例

描述

org.acme.Foo

匹配 Bean 类的完全限定名称或声明生产者的 Bean 的 Bean 类

org.acme.*

匹配 Bean 类的包为 org.acme 的 Bean

org.acme.**

匹配 Bean 类的包以 org.acme 开头的 Bean

Bar

匹配 Bean 类的简单名称或声明生产者的 Bean 的 Bean 类

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

5.10. 简化的生产者方法声明

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

class Producers {

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

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

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

class Producers {

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

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

5.11. 静态方法的拦截

拦截器规范明确指出,around-invoke 方法不能声明为静态的。但是,此限制主要是由技术限制驱动的。并且由于 Quarkus 是一个面向构建时的堆栈,允许进行额外的类转换,因此这些限制不再适用。可以使用拦截器绑定来注释非私有静态方法

class Services {

  @Logged (1)
  static BigDecimal computePrice(long amount) { (2)
    BigDecimal price;
    // Perform computations...
    return price;
  }
}
1 Logged 是一个拦截器绑定。
2 如果存在与 Logged 关联的拦截器,则会拦截每个方法调用。

5.11.1. 限制

  • 出于向后兼容的原因,仅考虑方法级别的绑定(否则声明类级别绑定的 Bean 类的静态方法将被突然拦截)

  • 永远不会拦截私有静态方法

  • 由于显而易见的原因,InvocationContext#getTarget() 为静态方法返回 null;因此,并非所有现有的拦截器在拦截静态方法时都能正确运行

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

5.12. 处理“final”类和方法的能力

在普通的 CDI 中,标记为 final 和/或具有 final 方法的类不符合代理创建的条件,这反过来意味着拦截器和普通作用域 Bean 无法正常工作。当尝试将 CDI 与 Kotlin 等替代 JVM 语言一起使用时,这种情况非常常见,在 Kotlin 中,类和方法默认都是 final

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

5.13. 容器管理的并发

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

io.quarkus.arc.Lock 是一个常规的拦截器绑定,因此可以用于任何作用域的任何 Bean。但是,它对于“共享”作用域(例如 @Singleton@ApplicationScoped)尤其有用。
容器管理的并发示例
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(映射到 @Lock(Lock.Type.WRITE))指示容器锁定 Bean 实例,以供任何业务方法的任何调用使用,即客户端具有“独占访问权限”,并且不允许并发调用。
2 @Lock(Lock.Type.READ) 覆盖在类级别指定的值。这意味着除非 Bean 实例被 @Lock(Lock.Type.WRITE) 锁定,否则任意数量的客户端可以并发调用该方法。
3 您还可以指定“等待时间”。如果在给定的时间内无法获取锁,则会抛出 LockException

5.14. 可重复的拦截器绑定

Quarkus 对 @Repeatable 拦截器绑定注解的支持有限。

将拦截器绑定到组件时,您可以在方法上声明多个 @Repeatable 注解。不支持在类和构造型上声明的可重复拦截器绑定,因为围绕与拦截器规范的交互存在一些未决问题。将来可能会添加此功能。

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

@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
    // ...
  }
}

这就是拦截器的使用方式。那么创建拦截器呢?

声明拦截器的拦截器绑定时,您可以像往常一样向拦截器类添加多个 @Repeatable 注解。当注解成员是 @Nonbinding 时,这将是无用的,对于 @Cached 注解来说就是这种情况,但在其他情况下很重要。

例如,假设我们有一个拦截器,可以自动将方法调用记录到某些目标。拦截器绑定注解 @Logged 将有一个名为 target 的成员,该成员指定在哪里存储日志。我们的实现可能仅限于控制台日志记录和文件日志记录

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

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

5.15. 缓存程序化查找的结果

在某些情况下,通过注入的 jakarta.enterprise.inject.InstanceInstance.get() 以编程方式获取 Bean 实例是实用的。但是,根据规范,get() 方法必须识别匹配的 Bean 并获取上下文引用。因此,从每次调用 get() 都会返回 @Dependent Bean 的新实例。此外,此实例是注入的 Instance 的依赖对象。此行为是明确定义的,但可能会导致意外错误和内存泄漏。因此,Quarkus 带有 io.quarkus.arc.WithCaching 注解。使用此注解注释的注入的 Instance 将缓存 Instance#get() 操作的结果。结果是在第一次调用时计算的,并且对于所有后续调用都返回相同的值,即使对于 @Dependent Bean 也是如此。

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

5.16. 以声明方式选择可以通过程序化查找获得的 Bean

有时,缩小可以通过 jakarta.enterprise.inject.Instance 程序化查找获得的 Bean 集是有用的。通常,用户需要根据运行时配置属性选择接口的适当实现。

假设我们有两个实现接口 org.acme.Service 的 Bean。您无法直接注入 org.acme.Service,除非您的实现声明了一个 CDI 限定符。但是,您可以注入 Instance<Service>,然后迭代所有实现并手动选择正确的实现。或者,您可以使用 @LookupIfProperty@LookupUnlessProperty 注解。 @LookupIfProperty 指示只有当运行时配置属性与提供的值匹配时才能获得 Bean。另一方面,@LookupUnlessProperty 指示只有当运行时配置属性与提供的值不匹配时才能获得 Bean。

@LookupIfProperty 示例
 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());
    }
 }

5.17. 对通过程序化查找获得的 Bean 进行排序

如果有多个 Bean 匹配所需的类型和限定符并符合注入条件,则可以迭代(或流式传输)可用的 Bean 实例。由流和迭代器方法返回的 Bean 都按 io.quarkus.arc.InjectableBean#getPriority() 定义的优先级排序。更高的优先级排在前面。如果没有显式声明优先级,则假定为 0。

interface Service {

}

@Priority(100)
@ApplicationScoped
class FirstService implements Service {

}

@Priority(10)
@ApplicationScoped
class SecondService implements Service {

}

@ApplicationScoped
class ThirdService implements Service {

}

@ApplicationScoped
class Client {

   @Inject
   Instance<Service> serviceInstance;

   void printServiceName() {
       if(service.isAmbiguous()){
           for (Service service : serviceInstance) {
                // FirstService, SecondService, ThirdService
           }
       }
   }
}

5.18. 直观地注入多个 Bean 实例

在 CDI 中,可以通过实现 java.lang.Iterablejakarta.enterprise.inject.Instance 注入多个 Bean 实例(又名上下文引用)。但是,这并不是完全直观的。因此,Quarkus 中引入了一种新方法 - 您可以注入一个使用 io.quarkus.arc.All 限定符注释的 java.util.List。执行查找时,列表中的元素类型用作必需的类型。

@ApplicationScoped
public class Processor {

     @Inject
     @All
     List<Service> services; (1) (2)
}
1 注入的实例是明确的 Bean 的上下文引用的不可变列表
2 对于此注入点,必需的类型是 Service,并且未声明其他限定符。
该列表按 io.quarkus.arc.InjectableBean#getPriority() 定义的优先级排序。更高的优先级排在前面。一般来说,可以使用 @jakarta.annotation.Priority 注解将优先级分配给类 Bean、生产者方法或生产者字段。

如果注入点声明了除了 @All 之外的其他限定符,则使用 @Any,即,该行为等同于 @Inject @Any Instance<Service>

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

@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,并且会导致部署错误。
也可以通过 Arc.container().listAll() 方法以编程方式获取所有 Bean 实例句柄的列表。

5.19. 忽略方法和构造函数的类级别拦截器绑定

如果托管 bean 在类级别声明了拦截器绑定注解,那么相应的 @AroundInvoke 拦截器将应用于所有业务方法。类似地,相应的 @AroundConstruct 拦截器将应用于 bean 构造函数。

例如,假设我们有一个带有 @Logged 绑定注解的日志拦截器和一个带有 @Traced 绑定注解的跟踪拦截器

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

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

在此示例中,doSomethingdoSomethingElse 都将被假设的日志拦截器拦截。此外,doSomethingElse 方法将被假设的跟踪拦截器拦截。

现在,如果 @Traced 拦截器也执行了所有必要的日志记录,我们希望跳过此方法的 @Logged 拦截器,但保留它用于所有其他方法。为了实现这一点,您可以为该方法添加 @NoClassInterceptors 注解

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

@NoClassInterceptors 注解可以放在方法和构造函数上,表示所有类级别的拦截器都将被这些方法和构造函数忽略。换句话说,如果一个方法/构造函数被注解为 @NoClassInterceptors,那么唯一会应用于此方法/构造函数的拦截器是直接在该方法/构造函数上声明的拦截器。

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

5.20. 异步观察者方法抛出的异常

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

NoopAsyncObserverExceptionHandler
@Singleton
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {

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

}

5.21. 被拦截的自调用

Quarkus 支持所谓的被拦截的自调用或简称自拦截 - CDI bean 从另一个方法中调用其自身的被拦截方法,同时触发任何关联的拦截器。这是一个非标准特性,因为 CDI 规范没有定义自拦截是否应该工作。

假设我们有一个 CDI bean,其中两个方法,其中一个方法具有与之关联的 @Transactional 拦截器绑定

@ApplicationScoped
public class MyService {

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

  void doSomethingElse() {
    doSomething();(2)
  }

}
1 一个或多个拦截器绑定;@Transactional 只是一个例子。
2 非拦截方法调用同一 bean 中的另一个具有关联绑定方法;这将触发拦截。

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

5.22. 拦截生产者方法和合成 Bean

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

相反,Quarkus 有自己的 API:InterceptionProxy@BindingsSourceInterceptionProxy 非常类似于 InterceptionFactory:它创建一个代理,该代理在将方法调用转发到目标实例之前应用 @AroundInvoke 拦截器。如果被拦截的类是外部的且无法更改,则 @BindingsSource 注解允许设置拦截器绑定。

import io.quarkus.arc.InterceptionProxy;

@ApplicationScoped
class MyProducer {
    @Produces
    MyClass produce(InterceptionProxy<MyClass> proxy) { (1)
        return proxy.create(new MyClass()); (2)
    }
}
1 声明 InterceptionProxy<MyClass> 类型的注入点。这意味着在构建时,会生成 MyClass 的子类,该子类执行拦截和转发。请注意,类型参数必须与生产者方法的返回类型相同。
2 为给定的 MyClass 实例创建拦截代理的实例。方法调用将在所有拦截器运行后转发到此目标实例。

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

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

被拦截的类应该是可代理的,因此不应是 final,不应具有非私有的 final 方法,并且应具有非私有的零参数构造函数。如果不是,如果启用,字节码转换将尝试修复它,但请注意,添加零参数构造函数并不总是可能的。

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

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 一个镜像 MyClass 结构并包含拦截器绑定的类。
2 @BindingsSource 注解表示 MyClass 的拦截器绑定应从 MyClassBindings 读取。

绑定源的概念是 InterceptionFactory.configure() 的构建时友好等效项。

生产者方法拦截和合成 bean 拦截仅适用于实例方法。静态方法的拦截不支持生产者方法和合成 bean。

5.22.1. 声明 @BindingsSource

@BindingsSource 注解指定一个类,该类镜像被拦截类的结构。然后从该类读取拦截器绑定,并将其视为好像是在被拦截的类上声明的一样。

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

通常使绑定源类和方法 abstract,这样您就不必编写方法体

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

由于此类永远不会被实例化,并且其方法永远不会被调用,所以这是可以的,但也可以创建一个非 abstract

class MyClassBindings {
    @MyInterceptorBinding
    String doSomething() {
        return null; (1)
    }
}
1 方法体无关紧要。

请注意,对于泛型类,类型变量名称也必须相同。例如,对于以下类

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

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

绑定源类也必须使用 T 作为类型变量的名称

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

您不需要声明未注解的方法,仅仅因为它们存在于被拦截的类上。如果您想向方法的子集添加方法级别的绑定,您只需要声明应该具有拦截器绑定的方法。如果您只想添加类级别的绑定,则根本不需要声明任何方法。

这些注解可以出现在绑定源类上

  • 拦截器绑定:在类和方法上

  • 定型:在类上

  • @NoClassInterceptors:在方法上

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

5.22.2. 合成 Bean

在合成 bean 中使用 InterceptionProxy 类似。

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

public void register(RegistrationContext context) {
    context.configure(MyClass.class)
            .types(MyClass.class)
            .injectInterceptionProxy() (1)
            .creator(MyClassCreator.class)
            .done();
}
1 再一次,这意味着在构建时,会生成 MyClass 的子类,该子类执行拦截和转发。

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

public MyClass create(SyntheticCreationalContext<MyClass> context) {
    InterceptionProxy<MyClass> proxy = context.getInterceptionProxy(); (1)
    return proxy.create(new MyClass());
}
1 获取 MyClassInterceptionProxy,如上所述。也可以使用 getInjectedReference() 方法,传递一个 TypeLiteral,但 getInterceptionProxy() 更容易。

还有一个与 @BindingsSource 等效的东西。injectInterceptionProxy() 方法有一个带有参数的重载

public void register(RegistrationContext context) {
    context.configure(MyClass.class)
            .types(MyClass.class)
            .injectInterceptionProxy(MyClassBindings.class) (1)
            .creator(MyClassCreator.class)
            .done();
}
1 参数是绑定源类。

5.23. Instance.Handle.close() 行为

根据 CDI 规范,Instance.Handle.close() 方法总是委托给 destroy()。在 ArC 中,这仅在严格模式中成立。

在默认模式下,只有当 bean 是 @Dependent 时(或者当实例句柄不代表 CDI 上下文对象时),close() 方法才会委托给 destroy()。当实例句柄代表任何其他作用域的 bean 时,close() 方法不执行任何操作;bean 保持原样,并在其上下文被销毁时销毁。

这是为了使以下代码的行为符合人们的期望

Instance<T> instance = ...;
try (Instance.Handle<T> handle : instance.getHandle()) {
   T value = handle.get();
   ... use value ...
}

@Dependent bean 会立即销毁,而其他 bean 则根本不会销毁。当 Instance 可能返回多个不同作用域的 bean 时,这一点很重要。

6. 反应式编程的陷阱

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

6.1. 检测何时允许阻塞

可以使用 io.quarkus.runtime.BlockingOperationControl#isBlockingAllowed() 方法来检测当前线程上是否允许阻塞。当不允许时,并且您需要执行阻塞操作,您必须将其卸载到另一个线程。最简单的方法是使用 Vertx.executeBlocking() 方法

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);
    }
}

6.2. 异步观察者

CDI 异步观察者 (@ObservesAsync) 不知道反应式编程,也不打算用作反应式管道的一部分。观察者方法应该是同步的,它们只是卸载到线程池。

Event.fireAsync() 方法返回一个 CompletionStage,该 CompletionStage 在所有观察者都被通知后完成。如果所有观察者都已成功通知,则 CompletionStage 以事件有效负载完成。如果某些观察者抛出了异常,则 CompletionStageCompletionException 异常完成。

观察者的返回类型无关紧要。观察者的返回值被忽略

您可以声明一个返回类型为 CompletionStage<>Uni<> 的观察者方法,但是返回类型和实际返回值都不会影响 Event.fireAsync() 的结果。此外,如果观察者声明了 Uni<> 的返回类型,则返回的 Uni 将不会被订阅,因此很可能观察者逻辑的某些部分甚至不会执行。

因此,建议观察者方法(同步和异步)始终声明为 void

7. 构建时扩展

Quarkus 结合了构建时优化,以提供即时启动和低内存占用。此方法的缺点是 CDI 可移植扩展无法支持。但是,大多数功能可以使用 Quarkus 扩展来实现。有关更多信息,请参见集成指南

8. 开发模式

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

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

8.1. 监控业务方法调用和事件

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

9. 严格模式

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

为了通过 CDI Lite TCK,ArC 还具有严格模式。此模式启用了额外的验证并禁用了与规范冲突的某些改进。

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

quarkus.arc.strict-compatibility=true

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

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

建议应用程序使用默认的非严格模式,这使得 CDI 使用起来更方便。严格模式的“严格性”(CDI 规范之上的额外验证集和禁用的改进集)可能会随着时间的推移而变化。

10. ArC 配置参考

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

  • 如果设置为 all (或 true),则容器将尝试删除所有未使用的 bean。

  • 如果设置为 none (或 false),则永远不会删除任何 bean,即使它们未被使用(根据下面列出的标准)

  • 如果设置为 fwk,则将删除所有未使用的 bean,但类在应用程序代码中声明的未使用的 bean 除外

一个未使用的 bean

  • 不是内置 bean 或拦截器,

  • 不符合注入到任何注入点的条件,

  • 不被任何扩展排除,

  • 没有名称,

  • 不声明观察者,

  • 不声明任何符合注入到任何注入点条件的生产者,

  • 不直接符合注入到任何 jakarta.enterprise.inject.Instance 注入点的条件

环境变量:QUARKUS_ARC_REMOVE_UNUSED_BEANS

显示更多

字符串

all

如果设置为 true,则会自动将 @Inject 添加到所有非静态非 final 字段,这些字段使用 AutoInjectAnnotationBuildItem 定义的注解之一进行注解。

环境变量:QUARKUS_ARC_AUTO_INJECT_FIELDS

显示更多

布尔值

true

如果设置为 true,则将转换不可代理 bean 的字节码。这确保可以正确创建代理/子类。如果值设置为 false,则会在构建时抛出一个异常,表明无法创建子类/代理。启用此设置后,Quarkus 会执行以下转换

  • 当需要代理时,从类和方法中删除“final”修饰符。

  • 如果需要,创建一个无参数构造函数。

  • 如果需要,将私有无参数构造函数设为包私有。

环境变量:QUARKUS_ARC_TRANSFORM_UNPROXYABLE_CLASSES

显示更多

布尔值

true

如果设置为 true,则注入点的私有字段的字节码将转换为包私有。这确保可以完全无反射地执行字段注入。如果该值设置为 false,则使用反射回退来执行注入。

环境变量:QUARKUS_ARC_TRANSFORM_PRIVATE_INJECTED_FIELDS

显示更多

布尔值

true

如果设置为 true(默认值),则如果既不是观察者也不是生产者,而是使用拦截器绑定来注解私有方法,则构建失败。一个例子是在 bean 的私有方法上使用 Transactional。如果设置为 false,Quarkus 只会记录一条警告,表明该注解将被忽略。

环境变量:QUARKUS_ARC_FAIL_ON_INTERCEPTED_PRIVATE_METHOD

显示更多

布尔值

true

应用程序的选定替代方案列表。

元素值可以是

  • 完全限定的类名,即 org.acme.Foo

  • Class#getSimpleName() 定义的简单类名,即 Foo

  • 带有后缀 .* 的包名称,即 org.acme.*,匹配一个包

  • 带有后缀 .** 的包名称,即 org.acme.**,匹配以该值开头的包

每个元素值都用于匹配替代 bean 类、替代定型注解类型或声明替代生产者的 bean 类。如果任何值匹配,则 Integer#MAX_VALUE 的优先级用于相关的 bean。通过 jakarta.annotation.Priority 声明的优先级将被覆盖。

环境变量:QUARKUS_ARC_SELECTED_ALTERNATIVES

显示更多

字符串列表

如果设置为 true,则自动将 jakarta.enterprise.inject.Produces 添加到所有非 void 方法,这些方法使用作用域注解、定型或限定符进行注解,并且不使用 InjectProduces 进行注解,并且没有参数使用 DisposesObservesObservesAsync 进行注解。

环境变量:QUARKUS_ARC_AUTO_PRODUCER_METHODS

显示更多

布尔值

true

应从发现中排除的类型列表。

元素值可以是

  • 完全限定的类名,即 org.acme.Foo

  • Class#getSimpleName() 定义的简单类名,即 Foo

  • 带有后缀 .* 的包名称,即 org.acme.*,匹配一个包

  • 带有后缀 .** 的包名称,即 org.acme.**,匹配以该值开头的包

如果任何元素值与发现的类型匹配,则该类型将从发现中排除,即不会从此类型创建 bean 和观察者方法。

环境变量:QUARKUS_ARC_EXCLUDE_TYPES

显示更多

字符串列表

无论是否直接使用,都应视为不可删除的类型列表。这是一个与使用 io.quarkus.arc.Unremovable 注解等效的配置选项。

元素值可以是

  • 完全限定的类名,即 org.acme.Foo

  • Class#getSimpleName() 定义的简单类名,即 Foo

  • 带有后缀 .* 的包名称,即 org.acme.*,匹配一个包

  • 带有后缀 .** 的包名称,即 org.acme.**,匹配以该值开头的包

如果任何元素值匹配已发现的 bean,则此类 bean 被视为不可删除。

环境变量:QUARKUS_ARC_UNREMOVABLE_TYPES

显示更多

字符串列表

应从发现中排除的工件

类型

默认

工件的 Maven groupId。

环境变量:QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__GROUP_ID

显示更多

字符串

必需

工件的 Maven artifactId(可选)。

环境变量:QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__ARTIFACT_ID

显示更多

字符串

工件的 Maven classifier(可选)。

环境变量:QUARKUS_ARC_EXCLUDE_DEPENDENCY__DEPENDENCY_NAME__CLASSIFIER

显示更多

字符串

如果设置为 true,则容器尝试检测运行时程序化查找期间的“未使用的已删除 bean”误报。您可以禁用此功能以在生产环境中运行应用程序时节省一些内存。

环境变量:QUARKUS_ARC_DETECT_UNUSED_FALSE_POSITIVES

显示更多

布尔值

true

如果设置为 true,则容器尝试检测注解的错误用法,并最终使构建失败,以防止 Quarkus 应用程序出现意外行为。

一个典型的例子是 @jakarta.ejb.Singleton,它经常与 @jakarta.inject.Singleton 混淆。结果,使用 @jakarta.ejb.Singleton 注解的组件将被完全忽略。另一个例子是用作用域注解来注解内部类 - 该组件将再次被完全忽略。

环境变量:QUARKUS_ARC_DETECT_WRONG_ANNOTATIONS

显示更多

布尔值

true

如果设置为 true,则容器将执行 CDI 规范要求的其他验证。CDI 规范之上的一些改进可能会被禁用。在严格模式下按预期工作的应用程序应该在默认的非严格模式下无需更改即可工作。

引入严格模式主要是为了允许通过 CDI Lite TCK。建议应用程序使用默认的非严格模式,这使得 CDI 使用起来更方便。严格模式的“严格性”(CDI 规范之上的额外验证集和禁用的改进集)可能会随着时间的推移而变化。

请注意,transform-unproxyable-classesremove-unused-beans 也对规范兼容性有影响。您可能需要禁用这些功能以获得更接近规范的行为。

环境变量:QUARKUS_ARC_STRICT_COMPATIBILITY

显示更多

布尔值

false

如果设置为 true,则容器在开发模式下监控业务方法调用和触发的事件。

不应在开发模式下更改此配置属性,因为它需要完全重建应用程序

环境变量:QUARKUS_ARC_DEV_MODE_MONITORING_ENABLED

显示更多

布尔值

false

如果设置为 true,则会生成依赖关系图,并在 Dev UI 中可用。如果设置为 auto,则如果应用程序中少于 1000 个 bean,则会生成依赖关系图。如果设置为 false,则不会生成依赖关系图。

环境变量:QUARKUS_ARC_DEV_MODE_GENERATE_DEPENDENCY_GRAPHS

显示更多

true, false, auto

auto

如果设置为 true,则在测试期间禁用在应用程序 bean 类上声明的 StartupEventShutdownEvent 观察者。

环境变量:QUARKUS_ARC_TEST_DISABLE_APPLICATION_LIFECYCLE_OBSERVERS

显示更多

布尔值

false

将不会检查拆分包问题的包列表。

包字符串表示可以是

  • 包的完整名称,即 org.acme.foo

  • 带有后缀 .* 的包名称,即 org.acme.*,它匹配以提供的价值开头的包

环境变量:QUARKUS_ARC_IGNORED_SPLIT_PACKAGES

显示更多

字符串列表

如果设置为 true 并且存在 SmallRye 上下文传播扩展,则 CDI 上下文将通过 MicroProfile 上下文传播 API 进行传播。具体来说,会注册一个 org.eclipse.microprofile.context.spi.ThreadContextProvider 实现。另一方面,如果设置为 false,则 MicroProfile 上下文传播 API 将永远不会用于传播 CDI 上下文。请注意,CDI 上下文可以通过不同的方式传播。例如,使用 Vertx 重复上下文。

环境变量:QUARKUS_ARC_CONTEXT_PROPAGATION_ENABLED

显示更多

布尔值

true

相关内容