1. 概述
Quarkus 是一个由核心模块和一系列扩展(extension)构成的框架。其核心基于上下文与依赖注入(CDI),而扩展的作用通常是将第三方框架集成进来,并将其核心组件以 CDI Bean 的形式暴露出来。
本文将介绍如何开发一个 Quarkus 扩展,假设你已经具备基本的 Quarkus 使用经验。
2. 什么是 Quarkus 扩展
Quarkus 扩展本质上是一个可以运行在 Quarkus 应用之上的模块。Quarkus 自身也是由核心模块 + 多个扩展组成的。
最常见的场景是:你想把某个第三方框架(比如 Liquibase)无缝集成进 Quarkus,这时候就需要写一个扩展。
3. 在普通 Java 应用中运行 Liquibase
我们以集成 Liquibase(数据库变更管理工具)为例来实现一个扩展。
在动手之前,先回顾一下如何在标准 Java 程序中执行 Liquibase 迁移。这有助于我们理解后续扩展的实现逻辑。
Liquibase 的入口是其 Java 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));
拿到 Liquibase
实例后,调用 update()
方法即可执行数据库变更:
liquibase.update(new Contexts());
我们的目标就是把这个流程封装成一个 Quarkus 扩展 ✅:
- 通过 Quarkus 配置机制传入数据库连接和 changelog 路径
- 将
Liquibase
实例作为 CDI Bean 暴露 - 支持在应用启动时自动执行迁移
4. 如何编写 Quarkus 扩展
一个标准的 Quarkus 扩展通常是一个 Maven 多模块项目,包含两个子模块:
- runtime 模块:定义运行时逻辑,比如配置类、Bean 生产者等
- deployment 模块:负责处理构建时逻辑,比如读取配置、生成字节码
我们创建一个名为 quarkus-liquibase-parent
的父项目,包含 runtime
和 deployment
两个子模块:
<modules>
<module>runtime</module>
<module>deployment</module>
</modules>
5. 实现 runtime 模块
runtime 模块主要做三件事:
✅ 提供配置类
✅ 提供 CDI 生产者(Producer)
✅ 提供 Recorder(记录器),用于延迟执行方法调用
5.1 Maven 依赖与插件
runtime 模块需要以下依赖:
<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>4.26.0</version>
</dependency>
⚠️ 注意:
quarkus-agroal
提供了 DataSource 支持- Liquibase 依赖必须显式引入
可选:添加 quarkus-bootstrap-maven-plugin
自动生成扩展描述文件:
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<executions>
<execution>
<goals>
<goal>extension-descriptor</goal>
</goals>
</execution>
</executions>
</plugin>
最终生成的描述文件位于 META-INF/quarkus-extension.properties
:
deployment-artifact=com.baeldung.quarkus.liquibase:deployment:1.0-SNAPSHOT
5.2 配置类定义
使用 @ConfigRoot
和 @ConfigItem
定义配置项:
@ConfigRoot(name = "liquibase", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
public final class LiquibaseConfig {
@ConfigItem
public String changeLog;
}
这样用户就可以在 application.properties
中配置:
quarkus.liquibase.change-log=db/liquibase-changelog-master.xml
📌 说明:
BUILD_AND_RUN_TIME_FIXED
表示该配置在构建期解析,运行期不可变- 字段名
changeLog
对应配置项change-log
(驼峰转连字符)
5.3 将 Liquibase 暴露为 CDI Bean
通过 CDI Producer 创建 Liquibase
实例:
@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;
}
⚠️ 注意:这里的 dataSource
和 liquibaseConfig
需要通过注入方式获取。
5.4 Recorder:记录运行时调用
Recorder 是 Quarkus 构建时的核心机制之一,它不直接执行方法,而是“记录”调用,留到运行时再执行。
@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());
}
}
关键点:
setLiquibaseConfig
:在 BeanContainer 初始化后设置配置migrate
:记录update()
调用,用于启动时自动迁移- ❌ 这些方法在构建期不会真正执行,只是生成字节码记录调用
6. 实现 deployment 模块
deployment 模块的核心是 Build Step Processor,即用 @BuildStep
注解的方法。它们在构建阶段执行,生成运行时代码。
6.1 Maven 依赖
<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>
⚠️ 注意:必须依赖 runtime 模块,且版本一致。
6.2 实现 Build Step 处理器
构建期初始化(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)));
}
功能说明:
FeatureBuildItem
:标记扩展名称,可用于条件化配置AdditionalBeanBuildItem
:确保LiquibaseProducer
被注册为 BeanBeanContainerListenerBuildItem
:在容器启动后注入配置
运行时初始化(RUNTIME_INIT)
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void processMigration(LiquibaseRecorder recorder,
BeanContainerBuildItem beanContainer) throws LiquibaseException {
recorder.migrate(beanContainer.getValue());
}
作用:
- 使用
RUNTIME_INIT
标记,表示该调用在应用启动时执行 - 调用
recorder.migrate()
,实际会触发liquibase.update()
📌 执行顺序:
- 构建期:
@BuildStep
方法被调用,生成字节码 - 运行时:生成的代码按
STATIC_INIT
→RUNTIME_INIT
顺序执行
7. 测试 Liquibase 扩展
7.1 创建 Quarkus 应用
mvn io.quarkus:quarkus-maven-plugin:3.15.0:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=quarkus-app \
-DclassName="com.example.GreetingResource" \
-Dpath="/hello"
7.2 添加依赖
<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>
</dependency>
7.3 配置 application.properties
# 数据库配置
quarkus.datasource.url=jdbc:h2:mem:testdb
quarkus.datasource.driver=org.h2.Driver
quarkus.datasource.username=user
quarkus.datasource.password=password
# Liquibase 配置
quarkus.liquibase.change-log=db/liquibase-changelog-master.xml
7.4 启动应用
开发模式:
mvn compile quarkus:dev
生产模式:
mvn clean package
java -jar target/quarkus-app-1.0-SNAPSHOT-runner.jar
✅ 启动时会自动执行 Liquibase 迁移。
8. 总结
本文通过实现 Liquibase 扩展,深入讲解了 Quarkus 扩展的开发流程:
- ✅ 多模块结构:runtime + deployment
- ✅ 配置系统:
@ConfigRoot
/@ConfigItem
- ✅ CDI 集成:Producer 暴露 Bean
- ✅ 构建时处理:
@BuildStep
+ Recorder 实现延迟执行 - ✅ BuildItem 机制:构建阶段的数据传递
掌握这套模式后,你可以为任何第三方库编写 Quarkus 扩展,真正实现“云原生优先”的开发体验。
完整代码见 GitHub。