
本文详解如何为任意第三方 web 服务(如自研或外部依赖服务)创建可集成到 quarkus 开发模式的自定义 dev service,实现容器自动拉起、健康就绪检测、配置动态注入及 dev ui 可视化展示,无需手动管理 docker 容器。
本文详解如何为任意第三方 web 服务(如自研或外部依赖服务)创建可集成到 quarkus 开发模式的自定义 dev service,实现容器自动拉起、健康就绪检测、配置动态注入及 dev ui 可视化展示,无需手动管理 docker 容器。
Quarkus 的 Dev Services 是其开发者体验的核心优势之一:它能在 quarkus:dev 模式下,根据项目依赖自动探测、启动并配置所需基础设施(如 PostgreSQL、Redis、Kafka),并将连接信息无缝注入应用配置。但当你依赖一个非官方支持的自定义 Web 服务(例如内部 Mock API、遗留系统网关或私有 SaaS 接口)时,Quarkus 默认不会识别它——此时,你需要编写一个轻量级扩展(Extension),将其“注册”为原生 Dev Service。
✅ 为什么必须写扩展?而非复用 QuarkusTestResourceLifecycleManager?
你在 JUnit 测试中成功使用的 QuarkusTestResourceLifecycleManager 仅作用于测试生命周期(@QuarkusTest),与开发模式(quarkus:dev)完全隔离。Dev Services 运行在构建时(Build Time)和运行时(Runtime)的交汇点,由 Quarkus 的构建框架(基于 Jandex 和 Build Items)统一调度。因此,自定义 Dev Service 的唯一标准路径是开发 Quarkus 扩展——这是官方设计范式,也是确保与 Dev UI、热重载、配置自动绑定等特性深度协同的前提。
?️ 四步实现自定义 Dev Service
1. 初始化扩展骨架
使用 Quarkus CLI 快速生成扩展结构(推荐 Quarkus 3.2+):
mvn io.quarkus.platform:quarkus-maven-plugin:3.12.1:create-extension -N \ -DgroupId=org.acme \ -DextensionId=mock-external-api \ -DclassNamePrefix=MockExternalApi
该命令将生成标准 Maven 模块,含 runtime/ 和 deployment/ 子模块。
2. 编写容器启动逻辑(Deployment 模块)
在 deployment/src/main/java/org/acme/mockexternalapi/deployment/MockExternalApiProcessor.java 中添加构建步骤:
@BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
public DevServicesResultBuildItem startMockApiContainer(
LaunchModeBuildItem launchMode,
Config config) {
// 从配置读取镜像与参数(支持 application.properties 动态覆盖)
String imageName = config.mockExternalApi.imageName.orElse("acme/mock-api:1.0");
int exposedPort = config.mockExternalApi.port.getAsInt();
MockApiContainer container = new MockApiContainer(DockerImageName.parse(imageName))
.withEnv("API_MODE", "dev")
.withEnv("LOG_LEVEL", "INFO");
container.start();
// 构建运行时配置映射(将自动注入到 @ConfigProperty 中)
Map<String, String> devProps = Map.of(
"acme.mock-api.url",
"http://" + container.getHost() + ":" + container.getMappedPort(exposedPort),
"acme.mock-api.ready-check-url",
"http://" + container.getHost() + ":" + container.getMappedPort(exposedPort) + "/health"
);
return new DevServicesResultBuildItem.RunningDevService(
"mock-external-api", // FEATURE 名称(显示在 Dev UI)
container.getContainerId(), // 容器 ID(用于日志/停止)
container::stop, // 停止回调(热重载时自动调用)
devProps // 注入的配置项
).toBuildItem();
}
// 自定义容器类(继承 GenericContainer,专注就绪检测)
static class MockApiContainer extends GenericContainer<MockApiContainer> {
private static final int DEFAULT_PORT = 8080;
public MockApiContainer(DockerImageName image) {
super(image);
}
@Override
protected void configure() {
withNetwork(Network.SHARED);
addExposedPorts(DEFAULT_PORT);
// 关键:精准等待服务真正可响应(避免配置注入过早)
waitingFor(Wait.forHttp("/health").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(2)));
}
public int getMappedPort() {
return getMappedPort(DEFAULT_PORT);
}
}⚠️ 注意事项:
onlyIfNot = IsNormal.class确保仅在dev或test模式生效;GlobalDevServicesConfig.Enabled.class尊重用户全局开关(quarkus.devservices.enabled=false);waitingFor(...)必须严格匹配容器日志或 HTTP 响应,否则 Dev Services 会超时失败并回退到“未启用”状态。
3. 定义配置属性(增强可维护性)
在 runtime/src/main/java/org/acme/mockexternalapi/runtime/MockExternalApiConfig.java 中声明类型安全配置:
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public class MockExternalApiConfig {
/**
* Docker 镜像名称,格式:registry/repo:tag
*/
@ConfigItem(defaultValue = "acme/mock-api:1.0")
public String imageName;
/**
* 容器内暴露端口(默认 8080)
*/
@ConfigItem(defaultValue = "8080")
public Integer port;
}对应在 application.properties 中即可覆盖:
# 开发环境专用配置 quarkus.mock-external-api.image-name=local/mock-api:dev quarkus.mock-external-api.port=9000
4. 在应用中消费服务(Runtime 模块)
在业务代码中直接注入配置,无需硬编码 URL:
@ApplicationScoped
public class ApiService {
@ConfigProperty(name = "acme.mock-api.url")
String apiUrl; // 自动绑定 Dev Service 提供的动态地址
@ConfigProperty(name = "acme.mock-api.ready-check-url")
String healthUrl;
public String callExternal() {
return WebClient.create().get(apiUrl + "/data").send().await().indefinitely().body();
}
}? 效果验证:Dev UI 实时可见
启动应用后访问 http://localhost:8080/q/dev,你将看到 “mock-external-api” 明确列在 “Running Dev Services” 区域,包含容器 ID、启动时间及一键停止按钮。所有日志也会自动聚合到 Quarkus 控制台,与应用日志同屏输出。
? 最佳实践总结
-
优先复用 Testcontainers:你的
GenericContainer逻辑可 100% 复用,但必须置于@BuildStep中由 Quarkus 调度; -
强制健康检查:永远不要依赖
container.isRunning(),务必用Wait.forHttp()或Wait.forLogMessage()确保服务已就绪; -
配置驱动而非硬编码:通过
Config类暴露参数,让团队成员可灵活覆盖; -
Dev UI 友好命名:
FEATURE字符串(如"mock-external-api")将直接显示在 UI,建议语义化; -
优雅关闭:
container::stop回调确保热重载或 Ctrl+C 时容器被清理,避免僵尸进程。
通过此方案,你不仅将自定义服务无缝融入 Quarkus 开发流,更获得了与官方数据库扩展完全一致的开发者体验——零手动 Docker 操作、配置自动同步、UI 可视化管控。这才是云原生 Java 开发应有的效率水位。


















