如何解决自定义Quarkus扩展的问题

617 阅读5分钟

我不时地看到一些推文或文章,声称他们看不到Quarkus的意义,因为 "谁需要快速启动?"、"我有足够的内存 "或 "实时重载的意义是什么?"

我可以写一篇文章来驳斥这些论点,并解释后者如何使你的开发工作流程更有效率,以及前者如何使后者成为可能,即使快速启动不是你的事。 但为了这篇博文,让我们承认这些人是完全正确的,这些都不是使用Quarkus的好理由。

那么现在怎么办?回到<insert your favorite framework here> ?没有那么快...

Quarkus并不是通过使用黑暗魔法或懒惰加载技巧来实现快速启动和低内存占用,而是通过完全重新思考Java应用程序的启动方式来实现的。 Quarkus的全部意义在于将尽可能多的工作转移到构建时间,这个过程使我们创建了一个框架来推动构建时间的工作,可以在Quarkus扩展中利用。

一个Quarkus扩展?这听起来像一个很大的工作?

不,真的,不是的。 你可以非常容易地开发你自己的扩展,它们可以以非常简单的方式解决一些普通的问题。

上周,我们的一个用户(嘿,胡安!)在Zulip上问了这个问题。

嗨!我想了解如何找到具有某些标准的类,并将它们添加到依赖注入上下文中,例如。我想找到所有名字以 "MessageTransformer "结尾的类并将它们添加到上下文中,我想在一个外部库中找到这些类,所以我不能给它们添加注解。

让我们看看如何通过开发一个自定义扩展来解决这个问题。

创建扩展

创建扩展是非常简单的:

mvn io.quarkus:quarkus-maven-plugin:create-extension -DwithoutTests

它将要求提供一个groupId - 让我们保持默认的org.acme - 和一个扩展的ID - 我选择了message-transformers-as-beans

然后你就可以把你的新扩展导入你最喜欢的IDE。

扩展的结构

关于扩展,我们有很多话要说,但就本篇博文而言,我们将长话短说。 该扩展由三个Maven模块组成:

  • 母模块--这里没什么可看的

  • 部署模块--这是我们的博文所关注的模块

  • 运行模块--在这篇博文中,我们不会修改它

让我们保持简单:部署模块是在构建时使用的,运行时模块是在运行时使用的。

在我们的例子中,我们想声明新的Bean,这是我们在构建时要做的事情,所以部署模块,我们来了!

处理器和构建步骤

如果你看一下你的deployment 模块,你会看到一个MessageTransformersAsBeansProcessor ,你可以看到其中有一个注解为@BuildStep 的方法。

Quarkus的构建是由这些构建步骤填充的,它们遵循一个消费者/生产者模型,并带有依赖性注入。被消费和生产的项目被称为BuildItems。

自动生成的构建步骤很容易理解。 它产生了一个FeatureBuildItem ,它将被Quarkus启动时消费,你将在Quarkus启动时显示的列表中看到扩展名。

INFO  [io.quarkus] my-app 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.2.Final) started in 0.221s.
INFO  [io.quarkus] Profile prod activated.
INFO  [io.quarkus] Installed features: [cdi, message-transformers-as-beans]

Jandex索引

现在我们已经完成了脚手架,让我们来思考一下我们想要实现的目标:我们需要在一个给定的包中找到所有名字以MessageTransformer 结尾的类。

Quarkus的一个重要假设是,应用程序生活在一个封闭的世界中。 你不能在运行时动态地添加一个jar到你的Quarkus应用程序中,并期望它能够工作。

虽然这可以被看作是一种限制,但它开启了各种可能性,其中之一就是对类和它们的注解进行索引的能力,以方便查找它们。

这个基于Jandex的索引是Quarkus bootstrap的一个非常重要的部分。

Jandex索引并不包含周围所有的类,但默认情况下,只限于应用类和包含预建索引或空META-INF/beans.xml 的依赖关系。

在我们的例子中,我们想列出一个外部依赖的类,所以我们需要将它们添加到索引中。 我们可以通过在MessageTransformersAsBeansProcessor 中添加一个构建步骤来轻松完成。

@BuildStep
IndexDependencyBuildItem indexExternalDependency() {
    return new IndexDependencyBuildItem("my.group.id", "my-artifact-id");
}

这将把my.group.id:my-artifact-id jar的内容添加到索引中。

声明额外的豆类

现在我们已经有了我们的类的索引,我们想让它们成为CDI豆。

这可以通过添加另一个构建步骤来实现。

@BuildStep
void declareMessageTransformersAsBean(CombinedIndexBuildItem index, (1)
        BuildProducer<AdditionalBeanBuildItem> additionalBeans) { (2)
    List<String> messageTransformers = index.getIndex().getKnownClasses().stream() (3)
            .filter(ci -> !Modifier.isAbstract(ci.flags())) (4)
            .map(ci -> ci.name().toString()) (5)
            .filter(c -> c.startsWith("my.package.")) (6)
            .filter(c -> c.endsWith("MessageTransformer")) (7)
            .collect(Collectors.toList());

    additionalBeans.produce(new AdditionalBeanBuildItem.Builder() (8)
            .addBeanClasses(messageTransformers)
            .setUnremovable() (9)
            .setDefaultScope(DotNames.APPLICATION_SCOPED) (10)
            .build());
}
1读取Jandex索引
2注入额外的豆类生产者
3从索引中获取所有已知的类
4过滤掉抽象类
5获取该类的FQCN
6只保留我们的目标根包中的类
7只保留MessageTransformers
8产生一个AdditionalBeanBuildItem
9使豆子不能被移除,以防止ArC在豆子只被程序性消耗的情况下将其移除
10将默认范围设置为@ApplicationScoped - 可以是你喜欢的任何CDI范围

通过这个构建步骤,我们的根包my.package 中任何名称以MessageTransformer结尾的非抽象类都将成为一个@ApplicationScoped CDI Bean。

最重要的是,所有这些工作都在构建时完成,你不需要在运行时扫描整个classpath。

通常,我们在索引中用一个接口、一个超类或一个注解来查找类。 这比抓取整个索引和按名称过滤要少得多,也更快。

但这里的重点是与用户的约束条件有关,而且适应外部依赖性也不是一个选项。

这就是全部,伙计们!

显然,这只是一个非常简单的例子,你可以用Quarkus扩展做更复杂的事情。

但这篇博文的重点是要证明你可以很容易地利用我们的扩展框架来解决现实生活中的问题。在大约10分钟的编码中,我们的问题就解决了