How to Implement a Quarkus Extension – 如何实现Quarkus扩展

最后修改: 2019年 12月 2日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Quarkus is a framework composed of a core and a set of extensions. The core is based on Context and Dependency Injection (CDI) and extensions are usually meant to integrate a third-party framework by exposing their primary components as CDI beans.

Quarkus是一个由核心和一组扩展组成的框架。核心是基于上下文和依赖注入(CDI),而扩展通常是为了通过将其主要组件暴露为CDIBean来整合第三方框架。

In this tutorial, we’ll focus on how to write a Quarkus extension assuming a basic understanding of Quarkus.

在本教程中,我们将重点介绍如何在对Quarkus有基本了解的前提下编写Quarkus扩展。

2. What’s a Quakus Extension

2.什么是Quakus扩展

A Quarkus extension is simply a module that can run on top of a Quarkus application. The Quarkus application itself is a core module with a set of other extensions.

Quarkus扩展只是一个可以运行在Quarkus应用程序之上的模块。Quarkus应用程序本身是一个核心模块,有一系列的其他扩展。

The most common use case for such an extension is to get a third-party framework running on top of a Quarkus application.

这种扩展的最常见的使用情况是让第三方框架在Quarkus应用程序之上运行。

3. Running Liquibase in a Plain Java Application

3.在一个普通的Java应用程序中运行Liquibase

Let’s try and implement an extension for integrating Liquibase, a tool for database change management.

让我们尝试实现一个扩展,以整合Liquibase,一个数据库变更管理的工具。

But before we dive in, we first need to show how to run a Liquibase migration from a Java main method. This will hugely facilitate implementing the extension.

但在我们深入研究之前,我们首先需要展示如何从Java主方法中运行Liquibase迁移。这将极大地促进扩展的实现。

The entry point for the Liquibase framework is the Liquibase API. To use this, we need a changelog file, a ClassLoader for accessing this file, and a Connection to the underlying database:

Liquibase框架的入口是Liquibase API。为了使用它,我们需要一个 changelog 文件,一个用于访问该文件的ClassLoader,以及一个与底层数据库的Connection

Connection c = DriverManager.getConnection("jdbc:h2:mem:testdb", "user", "password");
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor();
String changLogFile = "db/liquibase-changelog-master.xml";
Liquibase liquibase = new Liquibase(changLogFile, resourceAccessor, new JdbcConnection(c));

Having this instance, we simply call the update() method which updates the database to match the changelog file.

有了这个实例,我们只需调用update()方法,更新数据库以匹配更新日志文件。

liquibase.update(new Contexts());

The goal is to expose Liquibase as a Quarkus extension. That is, providing a database configuration and changelog file through Quarkus Configuration and then producing the Liquibase API as a CDI bean. This provides a means for recording migration invocation for later execution.

我们的目标是将Liquibase作为Quarkus的一个扩展来公开。也就是说,通过Quarkus配置提供一个数据库配置和变更日志文件,然后将Liquibase API作为CDI bean产生。这提供了一种记录迁移调用的方法,以便以后执行。

4. How to Write a Quarkus Extension

4.如何编写Quarkus扩展

Technically speaking, a Quarkus extension is a Maven multi-module project composed of two modules. The first is a runtime module where we implement requirements. The second is a deployment module for processing configuration and generating the runtime code.

从技术上讲,Quarkus扩展是一个由两个模块组成的Maven多模块项目。第一个是运行时模块,我们在这里实现需求。第二个是部署模块,用于处理配置和生成运行时代码。

So, let’s start by creating a Maven multi-module project called quarkus-liquibase-parent that contains two submodules, runtime and deployment:

因此,我们先创建一个名为quarkus-liquibase-parent的Maven多模块项目,其中包含两个子模块:runtimedeployment

<modules>
    <module>runtime</module>
    <module>deployment</module>
</modules>

5. Implementing the Runtime Module

5.实现运行时模块

In the runtime module, we’ll implement:

在运行时模块中,我们将实现。

  • a configuration class for capturing the Liquibase changelog file
  • a CDI producer for exposing the Liquibase API
  • and a recorder that acts as a proxy for recording invocation calls

5.1. Maven Dependencies and Plugins

5.1.Maven的依赖性和插件

The runtime module will depend on the quarkus-core module and eventually the runtime modules of the needed extensions. Here, we need the quarkus-agroal dependency as our extension needs a Datasource. We’ll include the Liquibase library here, too:

运行模块将依赖于quarkus-core模块以及最终所需扩展的运行模块。在这里,我们需要quarkus-agroal依赖项,因为我们的扩展需要一个数据源。我们将在这里也包括Liquibase库

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-core</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-agroal</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>3.8.1</version>
</dependency>

Also, we may need to add the quarkus-bootstrap-maven-plugin. This plugin automatically generates the Quarkus extension descriptor by calling the extension-descriptor goal.

另外,我们可能需要添加quarkus-bootstrap-maven-plugin。该插件通过调用extension-descriptor目标,自动生成Quarkus扩展描述符。

Or, we can omit this plugin and generate the descriptor manually.

或者,我们可以省略这个插件,手动生成描述符。

Either way, we can find the extension descriptor is located under META-INF/quarkus-extension.properties:

无论是哪种方式,我们都可以发现扩展描述符位于META-INF/quarkus-extension.properties下。

deployment-artifact=com.baeldung.quarkus.liquibase\:deployment\:1.0-SNAPSHOT

5.2. Exposing the Configuration

5.2.暴露配置

To provide the changelog file, we need to implement a configuration class:

为了提供更新日志文件,我们需要实现一个配置类。

@ConfigRoot(name = "liquibase", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
public final class LiquibaseConfig {
    @ConfigItem
    public String changeLog;
}

We annotate the class by @ConfigRoot and the properties by @ConfigItem. So, the changeLog field, which is the camel case form of the change-log, will be provided through the quarkus.liquibase.change-log key in the application.properties file, located in a Quarkus application classpath:

我们用@ConfigRoot注释类,用@ConfigItem.注释属性。因此,changeLog字段,即变化日志的驼峰形式,将通过位于Quarkus应用程序classpath的application.properties文件中的quarkus.liquidibase.change-log键提供。

quarkus.liquibase.change-log=db/liquibase-changelog-master.xml

We can also note the ConfigRoot.phase value which instructs when to resolve the change-log key. In this case, BUILD_AND_RUN_TIME_FIXED, the key is read at deployment time and available to the application at runtime.

我们还可以注意到ConfigRoot.phase值,它指示何时解决change-log键。在这种情况下,BUILD_AND_RUN_TIME_FIXED,该键在部署时被读取,并在运行时提供给应用程序。

5.3. Exposing the Liquibase API as a CDI Bean

5.3.将Liquibase的API作为CDI Bean公开

We’ve seen above how to run a Liquibase migration from the main method.

我们在上面看到了如何从主方法运行Liquibase迁移。

Now, we’ll reproduce the same code but as a CDI bean, and we’ll use a CDI producer for that purpose:

现在,我们将重现同样的代码,但作为一个CDI Bean,我们将为此使用一个CDI生产者。

@Produces
public Liquibase produceLiquibase() throws Exception {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classLoader);
    DatabaseConnection jdbcConnection = new JdbcConnection(dataSource.getConnection());
    Liquibase liquibase = new Liquibase(liquibaseConfig.changeLog, resourceAccessor, jdbcConnection);
    return liquibase;
}

5.4. Recording Bytecode

5.4.记录字节码

In this step, we’ll write a recorder class that acts as a proxy for recording bytecode and setting up the runtime logic:

在这一步,我们将编写一个记录器类,作为记录字节码和设置运行时逻辑的代理。

@Recorder
public class LiquibaseRecorder {

    public BeanContainerListener setLiquibaseConfig(LiquibaseConfig liquibaseConfig) {
        return beanContainer -> {
            LiquibaseProducer producer = beanContainer.instance(LiquibaseProducer.class);
            producer.setLiquibaseConfig(liquibaseConfig);
        };
    }

    public void migrate(BeanContainer container) throws LiquibaseException {
        Liquibase liquibase = container.instance(Liquibase.class);
        liquibase.update(new Contexts());
    }

}

Here, we have to record two invocations. setLiquibaseConfig for setting configuration and migrate for executing the migration. Next, we’ll look at how these recorder methods are called by the deployment build step processors which we’ll implement in the deployment module.

这里,我们必须记录两个调用。setLiquibaseConfig用于设置配置,migrate用于执行迁移。接下来,我们将看看这些记录器方法如何被部署构建步骤处理器调用,我们将在部署模块中实现。

Note that when we invoke these recorder methods at build time, instructions are not executed but recorded for later execution at startup time.

注意,当我们在构建时调用这些记录器方法时,指令不会被执行,而是被记录下来,以便以后在启动时执行。

6. Implementing the Deployment Module

6.实施部署模块

The central components in a Quarkus extension are the Build Step Processors. They are methods annotated as @BuildStep that generate bytecode through recorders, and they are executed during the build time through the build goal of the quarkus-maven-plugin configured in a Quarkus application.

Quarkus扩展中的核心组件是Build Step Processors。它们是被注解为@BuildStep的方法,通过记录器生成字节码,并在构建期间通过配置在Quarkus应用程序中的quarkus-maven-plugin的构建目标执行。

@BuildSteps are ordered thanks to BuildItems. They consume build items generated by early build steps and produce build items for later build steps.

@BuildSteps由于BuildItems而被排序。它们消耗由早期构建步骤产生的构建项,并为后来的构建步骤产生构建项。

The generated code by all the ordered build steps found in the application deployment modules is actually the runtime code.

应用程序部署模块中发现的所有有序构建步骤所生成的代码实际上是运行时代码。

6.1. Maven Dependencies

6.1.Maven的依赖性

The deployment module should depend on the corresponding runtime module and eventually on the deployment modules of the needed extensions:

部署模块应该依赖于相应的运行时模块,最终依赖于所需扩展的部署模块。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-core-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-arc-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-agroal-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>

<dependency>
    <groupId>com.baeldung.quarkus.liquibase</groupId>
    <artifactId>runtime</artifactId>
    <version>${project.version}</version>
</dependency>

The latest stable version of Quarkus extensions is the same for the runtime module.

Quarkus扩展的最新稳定版本对运行时模块也是如此。

6.2. Implementing Build Step Processors

6.2.实现构建步骤处理器

Now, let’s implement two build step processors for recording bytecode. The first build step processor is the build() method which will record bytecode for execution in the static init method. We configure this through the STATIC_INIT value:

现在,让我们实现两个用于记录字节码的构建步骤处理器。第一个构建步骤处理器是build()方法,它将记录字节码以便在静态 init 方法中执行。我们通过STATIC_INIT值对其进行配置。

@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void build(BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer,
  BuildProducer<FeatureBuildItem> featureProducer,
  LiquibaseRecorder recorder,
  BuildProducer<BeanContainerListenerBuildItem> containerListenerProducer,
  DataSourceInitializedBuildItem dataSourceInitializedBuildItem) {

    featureProducer.produce(new FeatureBuildItem("liquibase"));

    AdditionalBeanBuildItem beanBuilItem = AdditionalBeanBuildItem.unremovableOf(LiquibaseProducer.class);
    additionalBeanProducer.produce(beanBuilItem);

    containerListenerProducer.produce(
      new BeanContainerListenerBuildItem(recorder.setLiquibaseConfig(liquibaseConfig)));
}

First, we create a FeatureBuildItem to mark the type or the name of the extension. Then, we create an AdditionalBeanBuildItem so that the LiquibaseProducer bean will be available for the Quarkus container.

首先,我们创建一个FeatureBuildItem来标记扩展的类型或名称。然后,我们创建一个AdditionalBeanBuildItem,这样LiquibaseProducerbean就可以用于Quarkus容器

Finally, we create a BeanContainerListenerBuildItem in order to fire the BeanContainerListener after the Quarkus BeanContainer startup. Here, in the listener, we pass the configuration to the Liquibase bean.

最后,我们创建一个BeanContainerListenerBuildItem,以便在QuarkusBeanContainer启动后触发BeanContainerListener。在这里,在监听器中,我们将配置传递给Liquibase Bean。

The processMigration(), in turn, will record the invocation for execution in the main method as it’s configured using the RUNTIME_INIT parameter for recording.

processMigration(),将记录调用,以便在main方法中执行,因为它是使用RUNTIME_INIT参数配置的记录。

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void processMigration(LiquibaseRecorder recorder, 
  BeanContainerBuildItem beanContainer) throws LiquibaseException {
    recorder.migrate(beanContainer.getValue());
}

Here, in this processor, we just called the migrate() recorder method, which in turn records the update() Liquibase method for later execution.

在这里,在这个处理器中,我们只是调用了migrate()记录器方法,它又记录了update()Liquibase方法,以便以后执行。

7. Testing the Liquibase Extension

7.测试Liquibase扩展

To test our extension, we’ll first start by creating a Quarkus application using the quarkus-maven-plugin:

为了测试我们的扩展,我们首先要使用quarkus-maven-plugin创建一个Quarkus应用程序。

mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR1:create\
-DprojectGroupId=com.baeldung.quarkus.app\
-DprojectArtifactId=quarkus-app

Next, we’ll add our extension as a dependency in addition to the Quarkus JDBC extension corresponding to our underlying database:

接下来,我们将在Quarkus JDBC扩展之外,将我们的扩展作为一个依赖项添加到我们的底层数据库。

<dependency>
    <groupId>com.baeldung.quarkus.liquibase</groupId>
    <artifactId>runtime</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-h2</artifactId>
    <version>1.0.0.CR1</version>
</dependency>

Next, we’ll need to have the quarkus-maven-plugin in our pom file:

接下来,我们需要在pom文件中加入quarkus-maven-plugin

<plugin>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-maven-plugin</artifactId>
    <version>${quarkus.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>

This is especially useful for running the application using the dev goal or building an executable using the build goal.

这对于使用dev目标运行应用程序或使用build目标构建可执行文件特别有用。

And next, we’ll provide data source configuration through the application.properties file located in src/main/resources:

而接下来,我们将通过位于src/main/resourcesapplication.properties文件提供数据源配置。

quarkus.datasource.url=jdbc:h2:mem:testdb
quarkus.datasource.driver=org.h2.Driver
quarkus.datasource.username=user
quarkus.datasource.password=password

Next, we’ll provide the changelog configuration for our changelog file:

接下来,我们将为我们的更新日志文件提供更新日志配置。

quarkus.liquibase.change-log=db/liquibase-changelog-master.xml

Finally, we can start the application either in dev mode:

最后,我们可以在开发模式下启动应用程序。

mvn compile quarkus:dev

Or in production mode:

或者在生产模式下。

mvn clean package
java -jar target/quarkus-app-1.0-SNAPSHOT-runner.jar

8. Conclusion

8.结语

In this article, we implemented a Quarkus extension. As an example, we have showcased how to get Liquibase running on top of a Quarkus application.

在这篇文章中,我们实现了一个Quarkus扩展。作为一个例子,我们展示了如何让Liquibase在Quarkus应用程序之上运行。

The full code source is available over on GitHub.

完整的代码源可在GitHub上获得