未使用的 Bean 及其移除原因

Quarkus 是一个面向构建时的堆栈,也就是说,它试图在构建时尽可能多地完成工作,以提高应用程序的启动时间和内存使用量。然而,这并不总是像许多现有框架和库那样简单明了,因为它们没有考虑到这种设计选择。CDI 就是其中之一。

为什么要关心

传统的 CDI 容器会尝试在应用程序引导期间查找所有 Bean。为每个 Bean 创建一个元数据对象,并保留到应用程序的整个生命周期。这在运行时具有一定的动态性,但在内存消耗和应用程序启动时间方面是次优的。另一方面,Quarkus 默认会在构建期间尝试检测和删除所有*未使用*的 Bean、拦截器和装饰器。原因很简单。此优化有助于最小化生成的类数量和运行时使用的元数据对象数量,从而节省内存。

说到生成的类。Bean 的发现、验证和所有组件的连接 - 所有这些都是在构建时执行的。Quarkus 然后将收集到的元数据存储在字节码中,也就是说,为每个 Bean 生成一个或多个类。为了满足一些基本的 CDI API 要求,一个 Bean 至少需要一个对应的 javax.enterprise.inject.spi.Bean 实现。如果它是一个正常作用域的 Bean,那么还必须生成一个客户端代理类。最后,被拦截和装饰的 Bean 需要一些更内部的构造。

想象一下,您的应用程序包含 50 个实际上未在任何地方使用的 Bean。如果它们具有正常作用域(例如 @ApplicationScoped)并且被拦截(例如,声明一个带有 @Transactional 注释的方法),您可以期望生成超过 150 个类。而这些类完全没有用。尽管如此,容器仍然必须实例化并持有对这 50 多个 Bean 元数据类的引用。不用说,当生成原生映像时,Bean 类和任何引用的类都不能被消除死代码。因此,Quarkus 实现了一种算法来摆脱所有这些类。

实际移除了什么?

Quarkus 首先识别所谓的*不可移除* Bean,它们构成了依赖关系树中的根。不可移除的 Bean

  • 声明一个观察方法,或

  • 通过 @Named 指定名称,或

  • @io.quarkus.arc.Unremovable 注释,或

  • 通过 quarkus.arc.unremovable-types 配置属性排除,或

  • 由 Quarkus 扩展其他方式识别。

最后一点可能最重要,因为这是应用程序入口点被标记为不可移除的方式。一个很好的例子是 JAX-RS 资源类,由 RESTEasy 扩展识别。另一个例子是声明了 @Scheduled 方法的 Bean,由 Scheduler 扩展识别。

一个*未使用*的 Bean

  • 不是不可移除的,并且

  • 在依赖关系树中不符合注入任何注入点的条件,并且

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

  • 不符合注入任何 javax.enterprise.inject.Instancejavax.inject.Provider 注入点的条件。

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

在使用开发模式时(例如运行 ./mvnw quarkus:dev),您可以在 Dev UI 中看到有关哪些 Bean 被移除的更多信息。

主要缺点

但有一个问题。Quarkus 无法检测通过 CDI.current() 静态方法进行的程序化查找。因此,某个 Bean 的移除可能会导致误报错误,也就是说,Bean 被移除了,尽管它实际上正在使用。在这种情况下,您会在日志中注意到一个大的警告。当然,用户和扩展作者有几种方法可以消除这些错误。对于用户来说,最简单的方法是添加一个特殊的注释:@io.quarkus.arc.Unremovable 或使用 quarkus.arc.unremovable-types 配置属性。最后,还可以通过 quarkus.arc.remove-unused-beans=false 配置属性禁用此优化。

结论

Quarkus 检测并移除未使用的 Bean,以帮助您构建一个极简的应用程序。如果出现问题,也有相应的选项可以配置此优化的行为。