使用 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 隐藏。