Building my first extension

Quarkus 扩展对应用程序的增强就像项目依赖关系一样。扩展的作用是利用 Quarkus 范例将库无缝集成到 Quarkus 架构中 - 例如,在构建时执行更多操作。这正是您可以使用经过实战检验的生态系统并利用 Quarkus 性能和本机编译的方式。访问 code.quarkus.io 以获取受支持扩展的列表。

Quarkus extensions enhance your application just as projects dependencies do. The role of the extensions is to leverage Quarkus paradigms to integrate seamlessly a library into Quarkus architecture - e.g. do more things at build time. This is how you can use your battle-tested ecosystem and take advantage of Quarkus performance and native compilation. Go to code.quarkus.io to get the list of the supported extensions.

在本指南中,我们将开发 Sample Greeting Extension。该扩展将公开一个可自定义的 HTTP 终结点,该终结点仅向访问者问好。

In this guide we are going to develop the Sample Greeting Extension. The extension will expose a customizable HTTP endpoint which simply greets the visitor.

Disclaimer

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

Disclaimer

To be sure it’s extra clear you don’t need an extension to add a Servlet to your application. This guide is a simplified example to explain the concepts of extensions development, see the full documentation if you need more information. Keep in mind it’s not representative of the power of moving things to build time or simplifying the build of native images.

Prerequisites

Unresolved directive in building-my-first-extension.adoc - include::{includes}/prerequisites.adoc[]

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

Writing extension with any other than Java and Maven has not been tested by the Quarkus team so your mileage may vary if you stray off this path

Basic Concepts

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

First things first, we will need to start with some basic concepts.

  • JVM mode vs Native mode

    • Quarkus is first and foremost a Java framework, that means you can develop, package and run classic JAR applications, that’s what we call JVM mode.

    • Thanks to GraalVM you can compile your Java application into machine specific code (like you do in Go or C++) and that’s what we call Native mode.

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

The operation of compiling Java bytecode into a native system-specific machine code is named Ahead of Time Compilation (aka AoT).

  • build time vs runtime in classic Java frameworks

    • The build time corresponds to all the actions you apply to your Java source files to convert them into something runnable (class files, jar/war, native images). Usually this stage is composed by the compilation, annotation processing, bytecode generation, etc. At this point, everything is under the developer’s scope and control.

    • The runtime is all the actions that happen when you execute your application. It’s obviously focused on starting your business-oriented actions, but it relies on a lot of technical actions like loading libraries and configuration files, scanning the application’s classpath, configuring the dependency injection, setting up your Object-Relational Mapping, instantiating your REST controllers, etc.

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

Usually, Java frameworks do their bootstrapping during the runtime before actually starting the application "Business oriented layer". During bootstrap, frameworks dynamically collect metadata by scanning the classpath to find configurations, entity definitions, dependency injection binding, etc. in order to instantiate proper objects through reflection. The main consequences are:

  • Delaying the readiness of your application: you need to wait a couple of seconds before actually serving a business request.

  • Having a peak of resource consumption at bootstrap: in a constrained environment, you will need to size the needed resources based on your technical bootstrap needs rather than your actual business needs.

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

Quarkus' philosophy is to prevent as much as possible slow and memory intensive dynamic code execution by shifting left these actions and eventually do them during the build time. A Quarkus extension is a Java piece of code acting as an adapter layer for your favorite library or technology.

Description of a Quarkus extension

Quarkus 扩展由两部分组成:

A Quarkus extension consists of two parts:

  • The runtime module which represents the capabilities the extension developer exposes to the application’s developer (an authentication filter, an enhanced data layer API, etc). Runtime dependencies are the ones the users will add as their application dependencies (in Maven POMs or Gradle build scripts).

  • The deployment module which is used during the augmentation phase of the build, it describes how to "deploy" a library following the Quarkus philosophy. In other words, it applies all the Quarkus optimizations to your application during the build. The deployment module is also where we prepare things for GraalVM’s native compilation.

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

Users should not be adding the deployment modules of extension as application dependencies. The deployment dependencies are resolved by Quarkus during the augmentation phase from the runtime dependencies of the application.

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

At this point, you should have understood that most of the magic will happen at the Augmentation build time thanks to the deployment module.

Quarkus Application Bootstrap

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

There are three distinct bootstrap phases of a Quarkus application.

  • Augmentation. During the build time, the Quarkus extensions will load and scan your application’s bytecode (including the dependencies) and configuration. At this stage, the extension can read configuration files, scan classes for specific annotations, etc. Once all the metadata has been collected, the extensions can pre-process the libraries bootstrap actions like your ORM, DI or REST controllers configurations. The result of the bootstrap is directly recorded into bytecode and will be part of your final application package.

  • Static Init. During the run time, Quarkus will execute first a static init method which contains some extensions actions/configurations. When you will do your native packaging, this static method will be pre-processed during the build time and the objects it has generated will be serialized into the final native executable, so the initialization code will not be executed in the native mode (imagine you execute a Fibonacci function during this phase, the result of the computation will be directly recorded in the native executable). When running the application in JVM mode, this static init phase is executed at the start of the application.

  • Runtime Init. Well nothing fancy here, we do classic run time code execution. So, the more code you run during the two phases above, the faster your application will start.

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

Now that everything is explained, we can start coding!

Project setup

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

Extensions can be built with either Maven or Gradle. Depending on your build tool, setup can be done as following:

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

The Gradle extension plugin is still experimental and may be missing features available in the Maven plugin.

Maven setup

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

Quarkus provides create-extension Maven Mojo to initialize your extension project.

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

It will try to auto-detect its options:

  • from quarkus (Quarkus Core) or quarkus/extensions directory, it will use the 'Quarkus Core' extension layout and defaults.

  • with -DgroupId=io.quarkiverse.[extensionId], it will use the 'Quarkiverse' extension layout and defaults.

  • in other cases it will use the 'Standalone' extension layout and defaults.

  • we may introduce other layout types in the future.

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

You may call it without any parameter to use the interactive mode: mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-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 The extension id (not namespaced).
3 Indicate that we don’t want to generate any test
4 From a directory with no pom.xml and without any further options, the generator will automatically pick the 'standalone' extension layout
5 With the 'standalone' layout, the namespaceId is empty by default, so the computed runtime module artifactId is the extensionId

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

Maven has generated a greeting-extension directory containing the extension project which consists of the parent pom.xml, the runtime and the deployment modules.

The parent pom.xml

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

Your extension is a multi-module project. So let’s start by checking out the parent POM at ./greeting-extension/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>
  <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 Your extension declares 2 sub-modules deployment and runtime.
2 Quarkus requires a recent version of the Maven compiler plugin supporting the annotationProcessorPaths configuration.
3 The quarkus-bom aligns your dependencies with those used by Quarkus during the augmentation phase.
4 Quarkus requires these configs to run tests properly.

The Deployment module

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

Let’s have a look at the deployment’s ./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>

要点如下:

The key points are:

1 By convention, the deployment module has the -deployment suffix (greeting-extension-deployment).
2 The deployment module depends on the quarkus-arc-deployment artifact. We will see later which dependencies are convenient to add.
3 The deployment module also must depend on the runtime module.
4 We add the quarkus-extension-processor to the compiler annotation processors.

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

In addition to the pom.xml create-extension also generated the org.acme.greeting.extension.deployment.GreetingExtensionProcessor class.

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`表示扩展提供的一项功能。功能的名称会在应用程序引导期间显示在日志中。扩展应最多提供一项功能。

FeatureBuildItem represents a functionality provided by an extension. The name of the feature gets displayed in the log during application bootstrap. An extension should provide at most one feature.

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

Be patient, we will explain the Build Step Processor concept and all the extension deployment API later on. At this point, you just need to understand that this class explains to Quarkus how to deploy a feature named greeting which is your extension. In other words, you are augmenting your application to use the greeting extension with all the Quarkus benefits (build time optimization, native support, etc.).

The Runtime module

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

Finally ./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>

要点如下:

The key points are:

1 By convention, the runtime module has no suffix (greeting-extension) as it is the artifact exposed to the end user.
2 The runtime module depends on the quarkus-arc artifact.
3 We add the quarkus-extension-maven-plugin to generate the Quarkus extension descriptor included into the runtime artifact which links it with the corresponding deployment artifact.
4 We add the quarkus-extension-processor to the compiler annotation processors.

Gradle setup

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

Quarkus does not provide any way to initialize a Gradle project for extensions yet.

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

As mentioned before, an extension is composed of two modules:

  • runtime

  • deployment

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

We are going to create a Gradle multi-module project with those two modules. Here is a simple settings.gradle example file:

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

include 'runtime', 'deployment' 2

rootProject.name = 'greeting-extension'
1 Configure the quarkus extension plugin version
2 Include both runtime and deployment modules

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

Here is a sample of a root build.gradle file:

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

    group 'org.acme' 3
    version '1.0-SNAPSHOT'
}
1 Apply the java-library plugin for all sub-modules
2 Apply the maven-publish plugin used to publish our artifacts
3 Globally set the group id used for publication

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

The io.quarkus.extension plugin will be used in order to help us building the extension. The plugin will only be applied to the runtime module.

The deployment module

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

The deployment module does not require any specific plugin. Here is an example of a minimal build.gradle file for the deployment module:

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 By convention, the deployment module has the -deployment suffix (greeting-extension-deployment).
2 The deployment module must depend on the runtime module.

The runtime module

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

The runtime module applies the io.quarkus.extension plugin. This will:

  • Add quarkus-extension-process as annotation processor to both modules.

  • Generate extension description files.

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

Here is an example of build.gradle file for the runtime module:

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 By convention, the runtime module doesn’t have a suffix (and thus is named greeting-extension) as it is the artifact exposed to the end user.

Basic version of the Sample Greeting extension

Implementing the Greeting feature

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

The (killer) feature proposed by our extension is to greet the user. To do so, our extension will deploy, in the user application, a Servlet exposing the HTTP endpoint /greeting which responds to the GET verb with a plain text Hello.

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

The runtime module is where you develop the feature you want to propose to your users, so it’s time to create our Web Servlet.

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

To use Servlets in your applications you need to have a Servlet Container such as Undertow. Luckily, quarkus-bom imported by our parent pom.xml already includes the Undertow Quarkus extension.

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

All we need to do is add quarkus-undertow as dependency to our ./greeting-extension/runtime/pom.xml:

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

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

For Gradle, add the dependency in ./greeting-extension/runtime/build.gradle file:

    implementation 'io.quarkus:quarkus-undertow'

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

The dependency on quarkus-arc generated by the create-extension mojo can now be removed since quarkus-undertow already depends on it.

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

Now we can create our Servlet org.acme.greeting.extension.GreetingExtensionServlet in the runtime module.

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 As usual, defining a servlet requires to extend jakarta.servlet.http.HttpServlet.
2 Since we want to respond to the HTTP GET verb, we override the doGet method and write Hello in the Servlet response’s output stream.

Deploying the Greeting feature

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

Quarkus magic relies on bytecode generation at build time rather than waiting for the runtime code evaluation, that’s the role of your extension’s deployment module. Calm down, we know, bytecode is hard and you don’t want to do it manually. Quarkus proposes a high level API to make your life easier. Thanks to basic concepts, you will describe the items to produce/consume and the corresponding steps in order to generate the bytecode to produce during the deployment time.

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

The io.quarkus.builder.item.BuildItem concept represents object instances you will produce or consume (and at some point convert into bytecode) thanks to methods annotated with @io.quarkus.deployment.annotations.BuildStep which describe your extension’s deployment tasks.

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

Go back to the generated org.acme.greeting.extension.deployment.GreetingExtensionProcessor class.

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() method is annotated with @BuildStep which means it is identified as a deployment task Quarkus will have to execute during the deployment. BuildStep methods are run concurrently at augmentation time to augment the application. They use a producer/consumer model, where a step is guaranteed not to be run until all the items that it is consuming have been produced.
2 io.quarkus.deployment.builditem.FeatureBuildItem is an implementation of BuildItem which represents the description of an extension. This BuildItem will be used by Quarkus to display information to the users when the application is starting.

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

There are many BuildItem implementations, each one represents an aspect of the deployment process. Here are some examples:

  • ServletBuildItem: describes a Servlet (name, path, etc.) we want to generate during the deployment.

  • BeanContainerBuildItem: describes a container used to store and retrieve object instances during the deployment.

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

If you don’t find a BuildItem for what you want to achieve, you can create your own implementation. Keep in mind that a BuildItem should be as fine-grained as possible, representing a specific part of the deployment. To create your BuildItem you can extend:

  • io.quarkus.builder.item.SimpleBuildItem if you need only a single instance of the item during the deployment (e.g. BeanContainerBuildItem, you only want one container).

  • io.quarkus.builder.item.MultiBuildItem if you want to have multiple instances (e.g. ServletBuildItem, you can produce many Servlets during the deployment).

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

It’s now time to declare our HTTP endpoint. To do so, we need to produce a ServletBuildItem. At this point, we are sure you understood that if the quarkus-undertow dependency proposes Servlet support for our runtime module, we will need the quarkus-undertow-deployment dependency in our deployment module to have access to the io.quarkus.undertow.deployment.ServletBuildItem.

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

Let’s add quarkus-undertow-deployment as dependency to our ./greeting-extension/deployment/pom.xml:

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

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

The dependency on quarkus-arc-deployment generated by the create-extension mojo can now be removed since quarkus-undertow-deployment already depends on it.

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

For Gradle, add the dependency in ./greeting-extension/deployment/build.gradle file:

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

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

We can now update 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 We add a createServlet method which returns a ServletBuildItem and annotate it with @BuildStep. Now, Quarkus will process this new task which will result in the bytecode generation of the Servlet registration at build time.
2 ServletBuildItem proposes a fluent API to instantiate a Servlet named greeting-extension of type GreetingExtensionServlet (it’s our class provided by our extension runtime module), and map it the /greeting path.

Testing the Greeting Extension feature

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

When developing a Quarkus extension, you mainly want to test your feature is properly deployed in an application and works as expected. That’s why the tests will be hosted in the deployment module.

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

Quarkus proposes facilities to test extensions via the quarkus-junit5-internal artifact (which should already be in the deployment pom.xml), in particular the io.quarkus.test.QuarkusUnitTest runner which starts an application with your extension.

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

We will use RestAssured (massively used in Quarkus) to test our HTTP endpoint. Let’s add the rest-assured dependency into the ./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`文件中添加依赖项:

For Gradle, add the dependency in ./greeting-extension/deployment/build.gradle file:

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

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

The create-extension Maven Mojo can create the test and integration-test structure (drop the -DwithoutTests). Here, we’ll create it ourselves:

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

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

To start testing your extension, create the following org.acme.greeting.extension.deployment.GreetingExtensionTest test class:

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 We register a Junit Extension which will start a Quarkus application with the Greeting extension.
2 We verify the application has a greeting endpoint responding to an HTTP GET request with an OK status (200) and a plain text body containing Hello

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

Time to test and install to our local maven repository!

$ 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] ------------------------------------------------------------------------

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

Looks good! Congratulations you just finished your first extension.

Debugging your extension

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

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

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

Since your extension deployment is made during the application build, this process is triggered by your build tool. That means if you want to debug this phase you need to launch your build tool with the remote debug mode switched on.

Maven

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

You can activate Maven remote debugging by using mvnDebug. You can launch your application with the following command line:

mvnDebug clean compile quarkus:dev

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

By default, Maven will wait for a connection on localhost:8000. Now, you can run your IDE Remote configuration to attach it to localhost:8000.

Gradle

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

You can activate Gradle remote debugging by using the flags org.gradle.debug=true or org.gradle.daemon.debug=true in daemon mode. You can launch your application with the following command line:

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

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

By default, Gradle will wait for a connection on localhost:5005. Now, you can run your IDE Remote configuration to attach it to localhost:5005.

Debugging your extension tests

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

We have seen together how to test your extension and sometimes things don’t go so well and you want to debug your tests. Same principle here, the trick is to enable the Maven Surefire remote debugging in order to attach an IDE Remote configuration.

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

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

By default, Maven will wait for a connection on localhost:5005.

Time to use your new extension

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

Now that you just finished building your first extension you should be eager to use it in a Quarkus application!

Classic Maven publication

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

If not already done in the previous step, you should install the greeting-extension in your local repository:

cd ./greeting-extension
mvn clean install

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

Then from another directory, use our tooling to create a new greeting-app Quarkus application with your new extension:

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 中。

cd into greeting-app.

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

The greeting-extension extension has to be installed in the local Maven repository to be usable in the application.

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

Run the application and notice the Installed Features list contains the greeting-extension 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 平台存在的理由。

From an extension developer standpoint the Maven publication strategy is very handy and fast but Quarkus wants to go one step further by also ensuring a reliability of the ecosystem for the people who will use the extensions. Think about it, we all had a poor Developer Experience with an unmaintained library, an incompatibility between dependencies (and we don’t even talk about legal issues). That’s why there is the Quarkus Platform.

Quarkus Platform

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

Quarkus platform is a set of extensions that target the primary use-cases of Quarkus as a development framework and can safely be used in any combination in the same application without creating a dependency conflict. From an application developer perspective, a Quarkus platform is represented as one or more Maven BOMs, for example {quarkus-platform-groupid}:quarkus-bom:{quarkus-version}, {quarkus-platform-groupid}:quarkus-camel-bom:{quarkus-version}, etc, whose dependency version constraints were globally aligned so that these BOMs can be imported in the same application in any order without introducing a dependency conflict.

Quarkiverse Hub

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

Quarkiverse Hub is the GitHub organization that provides repository hosting (including build, CI and release publishing setup) for Quarkus extension projects contributed by the community.

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

In case you are wondering about creating a new Quarkus extension and adding it to the Quarkus ecosystem so that the Quarkus community can discover it using the Quarkus dev tools (including the Quarkus CLI and code.quarkus.io), the Quarkiverse Hub GitHub organization will be a good home for it.

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

You can get started by creating an Extension Request issue (check first if one wasn’t already submitted here) and asking to lead it.

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

We’ll take care of provisioning a new repository and set it up to:

  • Be supported by our tooling;

  • Publish the documentation you produce for your extension to the Quarkiverse website;

  • Configure your extension to use the Quarkus Ecosystem CI to build against the latest Quarkus Core changes;

  • Give you the freedom to manage the project and release to Maven Central as you like.

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

Extensions hosted in the Quarkiverse Hub may or may not end up in the Quarkus platform.

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

For more information, check the Quarkiverse Wiki and this blog post.

Conclusion

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

Creating new extensions may appear to be an intricate task at first but once you understood the Quarkus game-changer paradigm (build time vs runtime) the structure of an extension makes perfectly sense.

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

As usual, along the path Quarkus simplifies things under the hood (Maven Mojo, bytecode generation or testing) to make it pleasant to develop new features.

Further reading