Building my first extension

Quarkus 扩展对应用程序的增强就像项目依赖关系一样。扩展的作用是利用 Quarkus 范例将库无缝集成到 Quarkus 架构中 - 例如,在构建时执行更多操作。这正是您可以使用经过实战检验的生态系统并利用 Quarkus 性能和本机编译的方式。访问 code.quarkus.io 以获取受支持扩展的列表。 在本指南中,我们将开发 Sample Greeting Extension。该扩展将公开一个可自定义的 HTTP 终结点,该终结点仅向访问者问好。

Disclaimer

需要明确的是,您不需要一个扩展来向您的应用程序添加 Servlet。本指南是一个简化的示例,旨在解释扩展开发的概念,如果您需要更多信息,请参阅 full documentation。请记住,它不能代表将内容移至生成时或简化本机映像生成能力的作用。

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

使用除 Java 和 Maven 以外的其他语言编写扩展的方式,*not*已由 Quarkus 团队进行过测试,因此如果你偏离此路径,体验可能会不同

Basic Concepts

首先,我们需要了解一些基本概念。

  • JVM 模式与原生模式

    • Quarkus 首先是一个 Java 框架,这意味着你可以开发、打包和运行经典 JAR 应用程序,这就是我们所说的 JVM mode.

    • 感谢 GraalVM,你可以将 Java 应用程序编译成特定于机器的代码(就像你在 Go 或 C++ 中所做的那样),这就是我们所说的 Native mode.

将 Java 字节码编译成特定于系统的原生机器码的操作被称为 Ahead of Time Compilation(又称 AoT)。

  • 经典 Java 框架中的构建时间与运行时

    • 构建时间对应于你对 Java 源文件应用的所有操作,以将它们转换为可运行的内容(类文件、jar/war、原生映像)。通常,此阶段由编译、注释处理、字节码生成等组成。此时,所有内容都在开发人员的范围和控制之下。

    • 运行时是指执行应用程序时发生的所有操作。它显然专注于启动你的面向业务的操作,但它依赖于许多技术操作,例如加载库和配置文件、扫描应用程序的类路径、配置依赖项注入、设置你的对象关系映射、实例化你的 REST 控制器等。

通常,Java 框架会在运行时在实际启动应用程序“面向业务的层”之前进行引导。在引导期间,框架通过扫描类路径动态收集元数据以查找配置、实体定义、依赖项注入绑定等,以便通过反射实例化适当的对象。主要后果有:

  • 延迟了应用程序的就绪性:你需要等待几秒钟才能实际处理业务请求。

  • 在引导期间达到资源消耗峰值:在受限环境中,你需要根据你的技术引导需求而不是实际业务需求来调整所需资源的规模。

Quarkus 的理念是尽可能防止缓慢且内存密集型动态代码执行,方法是左移这些操作并最终在构建期间执行它们。Quarkus 扩展是一个 Java 代码块,充当你的首选库或技术的适配器层。

Description of a Quarkus extension

Quarkus 扩展由两部分组成:

  • runtime module,代表扩展开发人员向应用程序开发人员公开的功能(身份验证过滤器、增强的数据层 API 等)。运行时依赖项是用户将作为其应用程序依赖项添加的依赖项(在 Maven POM 或 Gradle 构建脚本中)。

  • deployment module,在构建的增强阶段使用,它描述了如何按照 Quarkus 理念“部署”库。换句话说,它在构建期间对你的应用程序应用所有 Quarkus 优化。部署模块也是我们为 GraalVM 的原生编译准备内容的地方。

用户不应将扩展的部署模块添加为应用程序依赖项。部署依赖项由 Quarkus 在增强阶段从应用程序的运行时依赖项中解析。

此时,你应该已经理解了由于部署模块,大部分神奇操作将在增强构建时间发生。

Quarkus Application Bootstrap

Quarkus 应用程序有三个不同的引导阶段。

  • Augmentation.在构建时,Quarkus 扩展程序将加载和扫描应用程序的字节码(包括依赖项)和配置。在此阶段,扩展程序可以读取配置文件,针对特定注解扫描类等。在收集所有元数据后,扩展程序可以预处理诸如 ORM、DI 或 REST 控制器配置等库引导操作。引导结果将直接记录到字节码中,并将成为最终应用程序包的一部分。

  • Static Init.在运行时,Quarkus 首先将执行包含某些扩展操作/配置的静态 init 方法。执行原生打包时,此静态方法将在构建时经过预处理,其生成的的对象将序列化到最终原生可执行文件中,因此初始化代码不会在原生模式下执行(想象一下你在此阶段执行斐波那契函数,计算结果将直接记录在原生可执行文件中)。在 JVM 模式下运行应用程序时,此静态 init 阶段将在应用程序启动时执行。

  • Runtime Init.此处没什么新奇的,我们执行经典运行时代码。因此,在上述两个阶段中运行的代码越多,应用程序启动得就越快。

现在一切都已说明,我们可以开始编码了!

Project setup

可以使用 Maven 或 Gradle 构建扩展程序。根据构建工具,可以按照如下进行设置:

Gradle 扩展插件仍处于试验阶段,可能会缺少 Maven 插件中提供的功能。

Maven setup

Quarkus 提供 create-extension Maven Mojo 来初始化扩展项目。

它将尝试自动检测其选项:

  • 来自 quarkus (Quarkus 核心)或 quarkus/extensions 目录,它将使用“Quarkus 核心”扩展布局和默认值。

  • 使用 -DgroupId=io.quarkiverse.[extensionId],它将使用“Quarkiverse”扩展布局和默认值。

  • 在其他情况下,它将使用“独立”扩展布局和默认值。

  • 我们未来可能会引入其他布局类型。

可以在不输入任何参数的情况下调用它以使用交互模式: mvn io.quarkus.platform:quarkus-maven-plugin:${project.version}:create-extension -N

$ mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create-extension -N \
    -DgroupId=org.acme \ (1)
    -DextensionId=greeting-extension \  (2)
    -DwithoutTests (3)

[INFO] --- quarkus-maven-plugin:{quarkus-version}:create-extension (default-cli) @ standalone-pom ---

Detected layout type is 'standalone' (4)
Generated runtime artifactId is 'greeting-extension' (5)


applying codestarts...
🔠 java
🧰 maven
🗃 quarkus-extension
🐒 extension-base

-----------
👍 extension has been successfully generated in:
--> /Users/ia3andy/workspace/redhat/quarkus/demo/greeting-extension
-----------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.659 s
[INFO] Finished at: 2021-01-25T16:17:16+01:00
[INFO] ------------------------------------------------------------------------
1 The extension groupId
2 扩展 ID(无命名空间)。
3 表示我们不想生成任何测试
4 对于没有 pom.xml 且没有任何其他选项的目录,生成器将自动选择“独立”扩展布局
5 对于“独立”布局, namespaceId 默认情况下为空,因此计算出的运行时模块 artifactId 是 extensionId

Maven 生成了包含扩展项目的 greeting-extension 目录,该目录包括父 pom.xmlruntimedeployment 模块。

The parent pom.xml

你的扩展程序是一个多模块项目。所以,让我们从查看 ./greeting-extension/pom.xml 中的父 POM 开始。

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.acme</groupId>
  <artifactId>greeting-extension-parent</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Greeting Extension - Parent</name>
  <modules>(1)
    <module>deployment</module>
    <module>runtime</module>
  </modules>
  <properties>
    <compiler-plugin.version>3.13.0</compiler-plugin.version>(2)
    <failsafe-plugin.version>${surefire-plugin.version}</failsafe-plugin.version>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <quarkus.version>{quarkus-version}</quarkus.version>
    <surefire-plugin.version>3.0.0</surefire-plugin.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>(3)
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <build>
    <pluginManagement>
      <plugins>
        <plugin>(4)
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${surefire-plugin.version}</version>
          <configuration>
            <systemPropertyVariables>
              <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
              <maven.home>${maven.home}</maven.home>
              <maven.repo>${settings.localRepository}</maven.repo>
            </systemPropertyVariables>
          </configuration>
        </plugin>
        <plugin>(4)
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>${failsafe-plugin.version}</version>
          <configuration>
            <systemPropertyVariables>
              <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
              <maven.home>${maven.home}</maven.home>
              <maven.repo>${settings.localRepository}</maven.repo>
            </systemPropertyVariables>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${compiler-plugin.version}</version>
          <configuration>
            <parameters>true</parameters>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
1 您的扩展声明了 2 个子模块 deployment`和 `runtime
2 Quarkus 需要支持 annotationProcessorPaths 配置的最新版本 Maven 编译器插件。
3 `quarkus-bom`将您的依赖关系与 Quarkus 在增强阶段使用的依赖关系保持一致。
4 Quarkus 需要这些配置才能正确运行测试。

The Deployment module

我们来看看部署的 ./greeting-extension/deployment/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.acme</groupId>
        <artifactId>greeting-extension-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>greeting-extension-deployment</artifactId> (1)
    <name>Greeting Extension - Deployment</name>

    <dependencies>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-arc-deployment</artifactId> (2)
        </dependency>
        <dependency>
            <groupId>org.acme</groupId>
            <artifactId>greeting-extension</artifactId> (3)
            <version>${project.version}</version>
        </dependency>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-junit5-internal</artifactId>
          <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.quarkus</groupId>
                            <artifactId>quarkus-extension-processor</artifactId>  (4)
                            <version>${quarkus.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

要点如下:

1 根据惯例,部署模块具有 -deployment`后缀 (`greeting-extension-deployment)。
2 部署模块依赖于 `quarkus-arc-deployment`构件。我们稍后会看到哪些依赖关系方便添加。
3 部署模块还 *must*依赖于运行时模块。
4 我们向编译器注释处理器添加了 quarkus-extension-processor

除了 `pom.xml`之外,`create-extension`还生成了 `org.acme.greeting.extension.deployment.GreetingExtensionProcessor`类。

package org.acme.greeting.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

class GreetingExtensionProcessor {

    private static final String FEATURE = "greeting-extension";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

}

`FeatureBuildItem`表示扩展提供的一项功能。功能的名称会在应用程序引导期间显示在日志中。扩展应最多提供一项功能。

请耐心等待,我们稍后会解释 `Build Step Processor`概念和所有扩展部署 API。此时,您只需要了解此类向 Quarkus 解释如何部署名为 `greeting`的功能,即您的扩展。换句话说,您正在增强您的应用程序以使用 `greeting`扩展以及所有 Quarkus 优势(构建时间优化、原生支持等)。

The Runtime module

最后是 ./greeting-extension/runtime/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/pom/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/xmlschema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.acme</groupId>
        <artifactId>greeting-extension-parent</artifactId>
        <version>0.0.1-snapshot</version>
    </parent>

    <artifactId>greeting-extension</artifactId>  (1)
    <name>Greeting Extension - Runtime</name>

    <dependencies>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-arc</artifactId> (2)
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-extension-maven-plugin</artifactId>  (3)
                <version>${quarkus.version}</version>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>extension-descriptor</goal>
                        </goals>
                        <configuration>
                            <deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}
                            </deployment>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.quarkus</groupId>
                            <artifactId>quarkus-extension-processor</artifactId> (4)
                            <version>${quarkus.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

要点如下:

1 根据惯例,运行时模块没有后缀 (greeting-extension),因为它向最终用户公开该构件。
2 运行时模块依赖于 `quarkus-arc`构件。
3 我们添加了 `quarkus-extension-maven-plugin`以生成包含在运行时构件中的 Quarkus 扩展描述符,将其与相应部署构件相关联。
4 我们向编译器注释处理器添加了 quarkus-extension-processor

Gradle setup

Quarkus 尚未提供任何用于为扩展初始化 Gradle 项目的方法。

如前所述,扩展由两个模块组成:

  • runtime

  • deployment

我们使用这两个模块来创建一个 Gradle 多模块项目。下面是一个简单的 `settings.gradle`示例文件:

pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    plugins {
        id 'io.quarkus.extension' version "${quarkus.version}" 1
    }
}

include 'runtime', 'deployment' 2

rootProject.name = 'greeting-extension'
1 配置 quarkus 扩展插件版本
2 包含 `runtime`和 `deployment`模块

下面是根 `build.gradle`文件的示例:

subprojects {
    apply plugin: 'java-library' 1
    apply plugin: 'maven-publish' 2

    group 'org.acme' 3
    version '1.0-SNAPSHOT'
}
1 为所有子模块应用 `java-library`插件
2 应用用于发布我们工件的 `maven-publish`插件
3 全局设置用于发布的分组 ID

使用 `io.quarkus.extension`插件来帮助我们构建扩展。该插件将 *only*应用于 `runtime`模块。

The deployment module

部署模块不需要任何特定插件。下面是 `deployment`模块的最小 `build.gradle`示例文件:

name = 'greeting-extension-deployment' 1

dependencies {
    implementation project(':runtime') 2

    implementation platform("io.quarkus:quarkus-bom:${quarkus.version}")

    testImplementation 'io.quarkus:quarkus-junit5-internal'
}
1 根据惯例,部署模块具有 -deployment`后缀 (`greeting-extension-deployment)。
2 部署模块 *must*依赖于 `runtime`模块。

The runtime module

运行时模块应用 `io.quarkus.extension`插件。这将:

  • 将 `quarkus-extension-process`添加为两个模块的注释处理器。

  • Generate extension description files.

下面是 `runtime`模块的 `build.gradle`示例文件:

plugins {
    id 'io.quarkus.extension' 1
}

name = 'greeting-extension' 2
description = 'Greeting extension'

dependencies {
    implementation platform("io.quarkus:quarkus-bom:${quarkus.version}")
}
1 Apply the io.quarkus.extension plugin.
2 按照惯例,运行时模块没有后缀(因此命名为 greeting-extension),因为它是向最终用户公开的工件。

Basic version of the Sample Greeting extension

Implementing the Greeting feature

我们的扩展提出的(杀手锏)功能是向用户打招呼。为此,我们的扩展将在用户应用程序中部署一个展示 HTTP 端点 `/greeting`的 Servlet,它使用普通文本 `Hello`响应 GET 动词。

`runtime`模块是开发你要向你的用户提出的功能的地方,所以现在该创建我们的 Web Servlet。

要在你的应用程序中使用 Servlet,你需要一个 Servlet 容器,如 Undertow。幸运的是,我们的父 `pom.xml`导入的 `quarkus-bom`已经包含 Undertow Quarkus 扩展。

我们只需要将 `quarkus-undertow`添加为 `./greeting-extension/runtime/pom.xml`的依赖项即可:

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-undertow</artifactId>
    </dependency>

对于 Gradle,在 `./greeting-extension/runtime/build.gradle`文件中添加依赖项:

    implementation 'io.quarkus:quarkus-undertow'

由于`quarkus-undertow`已经依赖了`create-extension`mojo生成的`quarkus-arc`依赖,因此现在可以将其移除。

我们现在可以在`runtime`模块中创建Servlet org.acme.greeting.extension.GreetingExtensionServlet

mkdir -p ./greeting-extension/runtime/src/main/java/org/acme/greeting/extension
package org.acme.greeting.extension;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet
public class GreetingExtensionServlet extends HttpServlet { (1)

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { (2)
        resp.getWriter().write("Hello");
    }
}
1 和往常一样,定义Servlet时需要扩展`jakarta.servlet.http.HttpServlet`。
2 由于我们希望响应HTTP GET 动词,所以重写`doGet`方法,并在Servlet响应的输出流中写入`Hello`。

Deploying the Greeting feature

Quarkus 的优势在于它依赖于生成字节码,而不是等到运行时代码评估完成,这就是扩展的`deployment`模块中的角色。冷静,我们知道字节码很难,你不想手动编写。Quarkus 提出了一个高级别 API,让你的工作变得更轻松。借助基本概念,你可以描述要生成/使用的项,以及相应的步骤来生成部署过程中要生成的字节码。

`io.quarkus.builder.item.BuildItem`概念表示你将要生成或使用的对象实例(并且在某个时间点转换为字节码),这要归功于使用 `@io.quarkus.deployment.annotations.BuildStep`进行注释的方法,该方法描述了扩展的部署任务。

返回到生成的`org.acme.greeting.extension.deployment.GreetingExtensionProcessor`类。

package org.acme.greeting.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

class GreetingExtensionProcessor {

    private static final String FEATURE = "greeting-extension";

    @BuildStep (1)
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE); (2)
    }

}
1 `feature()`方法使用 `@BuildStep`进行注释,这意味着在部署过程中Quarkus 必须执行一个部署任务。在增强时间,以并发方式运行`BuildStep`方法以增强应用程序。这些方法采用生产者/消费者模型,其中确保只有在生成了要使用的所有项之后,才知道运行该步骤。
2 `io.quarkus.deployment.builditem.FeatureBuildItem`是 `BuildItem`的实现,其中表示了扩展的描述。当应用程序启动时,Quarkus 将使用此`BuildItem`向用户显示信息。

有许多`BuildItem`实现,每个实现都表示部署过程的某个方面。以下是一些示例:

  • ServletBuildItem:描述了我们希望在部署期间生成的Servlet(名称、路径等)。

  • BeanContainerBuildItem:描述了用于在部署期间存储和检索对象实例的容器。

如果你没有找到符合你要求的`BuildItem`,你可以创建自己的实现。请记住,BuildItem`应尽可能细化,以表示部署的特定部分。要创建`BuildItem,可以扩展:

  • io.quarkus.builder.item.SimpleBuildItem,如果你只需要在部署期间使用该项的单个实例,(例如`BeanContainerBuildItem`,你只需要一个容器)。

  • io.quarkus.builder.item.MultiBuildItem,如果你想要多个实例(例如`ServletBuildItem`,你可以在部署期间生成多个Servlet)。

现在是时候声明我们的HTTP 端点了。为此,我们需要生成`ServletBuildItem`。在这一点上,我们确信你已经了解到,如果`quarkus-undertow`依赖项为我们的`runtime`模块提出Servlet 支持,那么我们需要`quarkus-undertow-deployment`依赖项在我们的`deployment`模块中访问`io.quarkus.undertow.deployment.ServletBuildItem`。

让我们将`quarkus-undertow-deployment`添加为`./greeting-extension/deployment/pom.xml`的依赖项:

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-undertow-deployment</artifactId>
    </dependency>

由于`quarkus-undertow-deployment`已经依赖了`create-extension`mojo生成的`quarkus-arc`依赖,因此现在可以将其移除。

对于Gradle,在`./greeting-extension/deployment/build.gradle`文件中添加依赖项:

    implementation 'io.quarkus:quarkus-undertow-deployment'

我们现在可以更新 org.acme.greeting.extension.deployment.GreetingExtensionProcessor 了:

package org.acme.greeting.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import org.acme.greeting.extension.GreetingExtensionServlet;
import io.quarkus.undertow.deployment.ServletBuildItem;

class GreetingExtensionProcessor {

    private static final String FEATURE = "greeting-extension";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

    @BuildStep
    ServletBuildItem createServlet() { (1)
      ServletBuildItem servletBuildItem = ServletBuildItem.builder("greeting-extension", GreetingExtensionServlet.class.getName())
        .addMapping("/greeting")
        .build(); (2)
      return servletBuildItem;
    }

}
1 我们添加了一个返回 ServletBuildItemcreateServlet 方法,并用 @BuildStep 做批注。现在,Quarkus 将处理这个新任务,它会在构建时生成 Servlet 注册的字节码。
2 ServletBuildItem 提出了一个用于实例化一个名为 greeting-extension 的 Servlet 的流畅 API,该 Servlet 是类型 GreetingExtensionServlet 的(这是我们由扩展 runtime 模块提供的类),并将其映射到 /greeting 路径。

Testing the Greeting Extension feature

在开发 Quarkus 扩展时,你的主要目标是测试你的特性在应用程序中得到正确部署并按预期的那样运行。这就是为什么测试会托管在 deployment 模块中的原因。

Quarkus 提出了通过 quarkus-junit5-internal 制品来对扩展进行测试的工具(这个制品应该已经在部署 pom.xml 中了),尤其是 io.quarkus.test.QuarkusUnitTest 运行器,它会使用你的扩展启动一个应用程序。

我们将使用 RestAssured (在 Quarkus 中得到广泛使用)来测试我们的 HTTP 终结点。现在让我们将 rest-assured 依赖项添加到 ./greeting-extension/deployment/pom.xml 中。

    ...
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>

对于Gradle,在`./greeting-extension/deployment/build.gradle`文件中添加依赖项:

    ...
    testImplementation 'io.rest-assured:rest-assured'

create-extension Maven Mojo 可以创建测试和集成测试结构(放下 -DwithoutTests)。这里,我们将自己创建它:

mkdir -p ./greeting-extension/deployment/src/test/java/org/acme/greeting/extension/deployment

为了开始测试你的扩展,创建以下 org.acme.greeting.extension.deployment.GreetingExtensionTest 测试类:

package org.acme.greeting.extension.deployment;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.hamcrest.Matchers.containsString;

public class GreetingExtensionTest {

  @RegisterExtension
  static final QuarkusUnitTest config = new QuarkusUnitTest()
    .withEmptyApplication(); (1)

  @Test
  public void testGreeting() {
    RestAssured.when().get("/greeting").then().statusCode(200).body(containsString("Hello")); (2)
  }

}
1 我们注册一个 Junit 扩展,它将启动一个带有 Greeting 扩展的 Quarkus 应用程序。
2 我们验证此应用程序有一个 greeting 终结点,它通过一个带有 OK 状态(200)和包含 Hello 的纯文本内容的 HTTP GET 请求进行响应。

现在就可以在我们的本地 Maven 存储库中进行测试并进行安装了!

$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Greeting Extension - Parent                                        [pom]
[INFO] Greeting Extension - Runtime                                       [jar]
[INFO] Greeting Extension - Deployment                                    [jar]
[INFO]
[INFO] -----------------< org.acme:greeting-extension-parent >-----------------
[INFO] Building Greeting Extension - Parent 1.0.0-SNAPSHOT                [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
...
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.greeting.extension.deployment.GreetingExtensionTest
2021-01-27 10:24:42,506 INFO  [io.quarkus] (main) Quarkus {quarkus-version} on JVM started in 0.470s. Listening on: http://localhost:8081
2021-01-27 10:24:42,508 INFO  [io.quarkus] (main) Profile test activated.
2021-01-27 10:24:42,508 INFO  [io.quarkus] (main) Installed features: [cdi, greeting-extension, servlet]
2021-01-27 10:24:43,764 INFO  [io.quarkus] (main) Quarkus stopped in 0.018s
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.799 s - in org.acme.greeting.extension.deployment.GreetingExtensionTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ greeting-extension-deployment ---
[INFO] Building jar: /Users/ia3andy/workspace/redhat/quarkus/demo/greeting-extension/deployment/target/greeting-extension-deployment-1.0.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ greeting-extension-deployment ---
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Greeting Extension - Parent 1.0.0-SNAPSHOT:
[INFO]
[INFO] Greeting Extension - Parent ........................ SUCCESS [  0.303 s]
[INFO] Greeting Extension - Runtime ....................... SUCCESS [  3.345 s]
[INFO] Greeting Extension - Deployment .................... SUCCESS [  7.365 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.246 s
[INFO] Finished at: 2021-01-27T10:24:44+01:00
[INFO] ------------------------------------------------------------------------

看起来不错!恭喜你,你刚刚完成了你的第一个扩展。

Debugging your extension

If debugging is the process of removing bugs, then programming must be the process of putting them in. Edsger W. Dijkstra

Debugging your application build

由于你的扩展部署是在应用程序构建期间进行的,此过程会由你的构建工具触发。这意味着如果你想调试此阶段,则需要以启用远程调试模式的方式启动你的构建工具。

Maven

你可以通过使用 mvnDebug 来激活 Maven 远程调试。你可以通过以下命令行启动你的应用程序:

mvnDebug clean compile quarkus:dev

在默认情况下,Maven 将等待对 localhost:8000 的连接。现在,你可以运行你的 IDE Remote 配置以将它附加到 localhost:8000

Gradle

你可以通过在守护进程模式下使用标志 org.gradle.debug=trueorg.gradle.daemon.debug=true 来激活 Gradle 远程调试。你可以通过以下命令行启动你的应用程序:

./gradlew quarkusDev -Dorg.gradle.daemon.debug=true

在默认情况下,Gradle 将等待对 localhost:5005 的连接。现在,你可以运行你的 IDE Remote 配置以将它附加到 localhost:5005

Debugging your extension tests

我们已经共同了解了如何测试你的扩展,有时情况并不会那么好,你会希望调试你的测试。原理与此相同,诀窍在于启用 Maven Surefire 远程调试以便附加一个 IDE Remote 配置。

cd ./greeting-extension
mvn clean test -Dmaven.surefire.debug

默认情况下,Maven 将等待 localhost:5005 上的连接。

Time to use your new extension

既然你刚刚完成了构建你的第一个扩展,你应该迫不及待地想在 Quarkus 应用程序中使用它了!

Classic Maven publication

如果上一步尚未安装,则应该在本地存储库中安装 greeting-extension

cd ./greeting-extension
mvn clean install

然后,从另一个目录中,使用我们的工具用你的新扩展创建一个新的 greeting-app Quarkus 应用程序:

mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
     -DprojectGroupId=org.acme \
     -DprojectArtifactId=greeting-app \
     -Dextensions="org.acme:greeting-extension:1.0.0-SNAPSHOT" \
     -DnoCode

cdgreeting-app 中。

必须在本地 Maven 存储库中安装 greeting-extension 扩展才能在应用程序中使用它。

运行应用程序,并注意 Installed Features 列表中包含 greeting-extension 扩展。

$ mvn clean compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< org.acme:greeting-app >------------------------
[INFO] Building greeting-app 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ greeting-app ---
[INFO]
[INFO] --- quarkus-maven-plugin:{quarkus-version}:generate-code (default) @ greeting-app ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ greeting-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.13.0:compile (default-compile) @ greeting-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:{quarkus-version}:dev (default-cli) @ greeting-app ---
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-01-27 10:28:07,240 INFO  [io.quarkus] (Quarkus Main Thread) greeting-app 1.0.0-SNAPSHOT on JVM (powered by Quarkus {quarkus-version}) started in 0.531s. Listening on: http://localhost:8080
2021-01-27 10:28:07,242 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-01-27 10:28:07,243 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, greeting-extension, servlet]

从扩展开发者的角度来看,Maven 发布策略非常方便和快速,但 Quarkus 希望进一步确保 ecosystem 的可靠性,以供使用这些扩展的人使用。思考一下,我们都对一个未维护的库、依赖关系之间的不兼容性(我们甚至不讨论法律问题)有着糟糕的开发者体验。这就是 Quarkus 平台存在的理由。

Quarkus Platform

Quarkus 平台是一组针对 Quarkus 的主要用例作为开发框架的扩展,在同一应用程序中可以安全地组合使用,而不会创建依赖冲突。从应用程序开发者的角度来看,Quarkus 平台表示为一个或多个 Maven BOM,例如 io.quarkus.platform:quarkus-bom:${project.version}io.quarkus.platform:quarkus-camel-bom:${project.version} 等,它们的依赖版本约束已全局对齐,以便可以在同一应用程序中按任何顺序导入这些 BOM 而不会引入依赖冲突。

Quarkiverse Hub

Quarkiverse Hub 是 GitHub 组织,它为社区贡献的 Quarkus 扩展项目提供存储库托管(包括构建、CI 和发布设置)。

如果您想知道创建新 Quarkus 扩展并将它添加到 Quarkus ecosystem 中,以便 Quarkus 社区可以使用 Quarkus dev 工具(包括 Quarkus CLIcode.quarkus.io)发现它,则 Quarkiverse Hub GitHub 组织将是它的理想选择。

你可以通过创建 Extension Request issue(首先先检查是否已提交 here)并要求领导它来开始。

我们将负责设置新存储库并:

  • 得到我们工具的支持;

  • 将你为你的扩展制作的文档发布到 Quarkiverse 网站;

  • 配置你的扩展以使用 Quarkus Ecosystem CI根据最新的 Quarkus Core 更改进行构建;

  • 让您自由管理项目并按您的喜好发布到 Maven Central。

在 Quarkiverse 中托管的扩展可能最终不会出现在 Quarkus 平台上。

如需更多信息,请查看 the Quarkiverse Wikithis blog post

Conclusion

创建新的扩展可能乍一看似乎是一项复杂的任务,但一旦您理解了 Quarkus 的改变游戏规则范例(构建时间与运行时),扩展的结构就会完全有意义。

与往常一样,沿途 Quarkus 简化底层事务(Maven Mojo、字节码生成或测试)以使其易于开发新特性。

Further reading