使用 Quarkus 扩展解决问题 (2/n)
一切顺利:两篇文章构成一个系列!
如果您还没有阅读本系列的上一篇文章,我邀请您阅读它!
今日问题: 一个库在使用 @Inject
注释来处理其内部注入,当它被用于 bean 时,这会与 Quarkus 中的 CDI 注入发生冲突。导致 CDI 层无法注入这些对象,因为它们不是 CDI bean。
一些背景信息
与本系列的上一篇文章一样,这篇文章基于我为Quarkus GitHub App 扩展所做的工作,该扩展使您能够以极快的速度和很少的样板代码开发基于 Quarkus 的 GitHub 应用。
此扩展的最新功能是能够轻松地在 GitHub 应用中开发基于注释的命令。例如,当用户在拉取请求的注释中发布 @bot do-something
时执行某些操作。
虽然使用 Quarkus GitHub App 的标准功能可以完全自己实现,但我们开发了一个额外的扩展来使事情变得更加容易。
使用此扩展实现基于注释的命令就像
@Cli(name = "@bot", commands = { DoSomething.class })
public class MyFirstCli {
@Command(name = "do-something")
static class DoSomething implements Runnable {
@Override
public void run() {
// do something
}
}
}
当用户在 issue 或 pull request 的注释中发布 @bot do-something
时,DoSomething
类的 run()
方法将被调用。
这些是基础知识,但该扩展还有许多其他功能,例如基于反应的反馈、作用域、权限……
此扩展基于Airline 库。该库旨在轻松解析和执行命令行。虽然最初设计用于开发 CLI 应用程序,但它非常适合我们的用途。
我们使用此库遇到的一个问题是,它使用 @Inject
注释将某些对象注入命令,例如 GlobalMetadata
@Command(name = "do-something")
static class DoSomething implements Runnable {
@Inject
GlobalMetadata metadata;
@Override
public void run() {
// do something
}
}
这对我们来说是个问题,因为此 @Inject
注释被 CDI 注入使用,并且在我们的扩展的上下文中,@Command
类是 CDI bean。因此,这个特定的 @Inject
注释也将被我们的 CDI 实现 ArC 解释,ArC 将尝试将 GlobalMetadata
注入为 CDI bean……但由于它不是 CDI bean 而失败。
可以说,这不会很好地工作,我们需要修复它。
让 |
我们如何解决这个问题?
理想情况下,Airline 库不会为其内部目的使用 @Inject
注释,好消息是,在最新版本中,可以指定用于注入的注释。
但为了练习的目的,我们仍然使用旧版本的 Airline。
那么现在呢?
Airline 库可能注入的类集是有限的:它用于注入有限数量的类并处理组合(即在多个命令之间共享组件)。
对于这些用例,我们需要 ArC 以某种方式忽略注入点。
AnnotationTransformers 来帮忙
如果您熟悉 Quarkus,您可能熟悉 Jandex 索引的概念。在 Quarkus 中,我们构建项目注释的索引,我们的核心和扩展使用这些索引来查找注释(以及更多)。
ArC,我们的 CDI 实现,就是消耗 Jandex 索引的组件之一。
有趣的是,ArC 并不是按原样消耗 Jandex 索引的。
Annotations transformers 可以在 ArC 消耗之前添加、删除、更新现有注释。Quarkus 中的许多功能都使用了它们,例如 Hibernate Validator 拦截器支持。
Annotations transformers 不会修改原始类,也不会修改 Jandex 索引。 使用 annotations transformers 只会影响 ArC,我们的 CDI 实现。 |
这个行为对我们很有价值:我们可以使用 annotations transformer 将注释隐藏起来,不让 ArC 看到,同时仍然让 Airline 通过反射消费它们。
让我们创建我们的 annotations transformer
public class HideAirlineInjectAnnotationsTransformer implements AnnotationsTransformer { (1)
private final IndexView index;
HideAirlineInjectAnnotationsTransformer(IndexView index) { (2)
this.index = index;
}
@Override
public boolean appliesTo(Kind kind) {
return Kind.FIELD == kind; (3)
}
@Override
public void transform(TransformationContext transformationContext) {
FieldInfo fieldInfo = transformationContext.getTarget().asField();
if (!fieldInfo.hasAnnotation(DotNames.INJECT)) { (4)
return;
}
if (fieldInfo.hasAnnotation(ARGUMENTS) ||
fieldInfo.hasAnnotation(OPTION) ||
GLOBAL_METADATA.equals(fieldInfo.type().name()) || (5)
COMMAND_GROUP_METADATA.equals(fieldInfo.type().name()) ||
COMMAND_METADATA.equals(fieldInfo.type().name()) ||
isComposition(fieldInfo)) { (6)
transformationContext.transform().remove(ai -> DotNames.INJECT.equals(ai.name())).done(); (7)
}
}
private boolean isComposition(FieldInfo fieldInfo) { (8)
Type fieldType = fieldInfo.type();
if (fieldType.kind() != Type.Kind.CLASS) {
return false;
}
ClassInfo fieldClass = index.getClassByName(fieldType.asClassType().name());
if (fieldClass == null) {
return false;
}
Set<DotName> fieldClassAnnotations = fieldClass.annotationsMap().keySet();
return fieldClassAnnotations.contains(ARGUMENTS) || fieldClassAnnotations.contains(OPTION);
}
}
1 | 我们的类实现了 AnnotationsTransformer 。 |
2 | 我们将 Jandex 索引注入到我们的 transformer 中,因为我们需要它来检测组合。 |
3 | 我们只对字段感兴趣,所以我们只将我们的 transformer 应用于字段。 |
4 | 如果字段没有用 @Inject 注释,我们就不关心。 |
5 | 如果字段类型是 GlobalMetadata 、GroupMetadata 或 CommandMetadata ,我们就知道这是 Airline 负责注入的。 |
6 | 我们也在检测组合。 |
7 | 我们从 ArC 可见的转换视图中删除了 @Inject 注释。请确保您不要忘记使用 .done() 完成转换。 |
8 | 对于组合,我们检测字段的类型是否包含 @Arguments 或 @Option 注释。 |
现在我们已经创建了我们的 annotations transformer,我们需要确保 Quarkus 知道它。
像往常一样,对于 Quarkus 构建过程,您只需要生成一个 BuildItem
来注册 annotations transformer
@BuildStep
public void beanConfig(CombinedIndexBuildItem index,
BuildProducer<AnnotationsTransformerBuildItem> annotationsTransformer) {
annotationsTransformer
.produce(new AnnotationsTransformerBuildItem(new HideAirlineInjectAnnotationsTransformer(index.getIndex())));
}
就是这样,从现在开始,Airline 库使用的 @Inject
注释将对 ArC 隐藏,同时仍然对使用反射的 Airline 库可见。
常规 CDI 注入仍然受支持,因为只有 Airline 处理的 @Inject
注释才对 ArC 隐藏。