Quarkus 依赖注入
Quarkus ArC 是一个面向构建时的依赖注入,基于 CDI 2.0。在这篇博客中,我们将解释它与规范的关系,并描述构建时处理设计的优点和缺点。
兼容性
在依赖注入方面,没有必要重新发明轮子。有许多框架试图解决类似的问题。一年前,我们做出了一个设计决定,在 CDI 之上构建 Quarkus DI。我们有几个很好的理由选择 CDI:
-
CDI 是一个成熟且经过验证的组件模型
-
我们在 Red Hat 拥有近十年的开发 Weld - CDI 参考实现 的经验。
-
CDI API 构建在
javax.inject
之上,因此从任何与@Inject
兼容的 DI 框架迁移应该很容易。
构建时处理的优点和缺点
快速失败
Bean 和依赖项在构建过程中得到验证,因此您的应用程序永远不会因生产中常见的 `AmbiguousResolutionException` 或 `UnsatisfiedResolutionException` 等问题而失败。
即时启动
当应用程序启动时,ArC 仅加载所有元数据并初始化一些内部结构。无需再次分析应用程序类。这意味着启动开销可以忽略不计。
这适用于 GraalVM 和 OpenJDK HotSpot 运行时。 |
最小化的运行时
在 Quarkus 0.19 中,ArC 加上集成运行时包含 72 个类,在 jar 文件中占用约 140 KB。 Weld 3.1.1 (CDI 参考实现) 核心大约有 1200 个类,jar 文件大小约为 2 MB。换句话说,ArC 运行时在类数量和 jar 体积方面大约占 Weld 运行时的 7%。
扩展点
不幸的是,CDI 可移植扩展本质上是运行时构造,因此无法在 Quarkus 中完全支持。实际上,目前所有 CDI 扩展都被忽略了。尽管如此,大多数功能都可以通过 Quarkus 扩展 来实现。鼓励 CDI 扩展来泛化代码,并在可能的情况下提供 Quarkus 扩展以充分利用构建时元数据处理。
非标准功能
ArC 不仅限于标准,我们还在不断寻找超越和扩展可能性。以下是 Quarkus DI 提供的一些非标准功能的示例。
限定注入字段
通常,如果您声明一个注入字段,您总是需要使用 @Inject
和可选的必需限定符。
@Inject
@ConfigProperty(name = "cool")
String coolProperty;
在 Quarkus 中,如果注入字段至少声明了一个限定符,您可以完全省略 @Inject
注释。
@ConfigProperty(name = "cool")
String coolProperty;
构造函数和方法注入仍然需要 @Inject 。 |
简化的构造函数注入
在 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 类扩展了另一个未声明无参构造函数的类,我们不会自动生成无参构造函数。 |
默认 Bean
CDI 有一个称为 替代 的功能。简单来说,用 @Alternative
和 @Priority
注释的 Bean 在类型安全的解析过程中优先于非替代 Bean。
class MyBean {
}
@Alternative
@Priority(1)
class MyAlternativeBean extends MyBean {
}
@Inject // MyAlternativeBean wins and is injected!
MyBean bean;
那么,如果用户想覆盖由库/扩展定义的 Bean 怎么办?该 Bean 必须被标记为 CDI @Alternative
,并使用 @Priority
注释启用。有更简单的方法吗?是的,有。您可以使用一个称为“默认 Bean”的非标准功能。在这种情况下,可以覆盖的 Bean 应使用 @io.quarkus.arc.DefaultBean
进行注释。就是这样。
@DefaultBean
class MyBean {
}
class MyOwnBean extends MyBean {
}
@Inject // MyOwnBean wins and is injected!
MyBean bean;
移除未使用的 Bean
GraalVM 原生镜像在移除应用程序无法访问的所有类方面做得很好。然而,有时检查可达性是不够的。有时框架本身必须决定是否需要某个组件。在标准的 CDI 中,无论是否需要,所有 Bean 都会被容器保留。
假设我们有一个 Bean 类 org.acme.Foo
。这个 Bean 类导入并使用了许多不同的类。它被注释为 @ApplicationScoped
,因此 Quarkus 需要生成一个 Bean 元数据类和一个客户端代理,并在应用程序启动时注册此元数据。但是,如果没有人使用这个 Bean 呢?我们仍然会保留对生成的元数据的引用,以及 Bean 类本身及其依赖项。换句话说,所有这些类都是可达的。
Quarkus 默认尝试在构建过程中移除所有 **未使用的 Bean**。这有助于减少生成的类数量以及运行时所需的内存量。但是我们如何实际检测未使用的 Bean 呢?规则在 参考指南 中描述,但简单来说:如果一个 Bean 没有在任何地方注入,并且无法通过任何其他标准方式(例如观察者通知)到达,它就会被移除。此外,用户可以通过用 @io.quarkus.arc.Unremovable
注释 Bean 类来指示容器不要移除 Bean。最后,可以通过使用 quarkus.arc.remove-unused-beans
属性来禁用和微调此优化。
此功能也适用于 JVM 模式。 |