最近好几个团队从 Spring Boot 往 Quarkus 迁移,迁移过程中很少有人会在 Spring Boot 这边为 Maven 插件发愁,但换到 Quarkus 后问题全冒出来了:为什么 mvn package 打出来的东西在 target/quarkus-app,而不是一个干净的 jar?为什么 mvn quarkus:add-extension 非得让插件去改 pom?为什么 JVM 构建好好的,加个 -Pnative 就各种"反射找不到"?这些问题的答案其实都指向同一个东西——quarkus-maven-plugin 不是 Spring Boot 那种"顺便帮你打个可执行 jar"的辅助工具,它从项目创建、开发模式、代码生成、测试、打包到容器镜像构建,贯穿了整个生命周期。这篇文章就以 RESTful 服务项目和微服务项目为例,把插件的完整用法、目标命令和参数配置从头到尾捋一遍。
1. 先弄清插件的定位:它不是打包工具,是 Quarkus 的装配产线
1.1 为什么 Quarkus 敢把 "supersonic subatomic Java" 当口号
Quarkus 的核心思想是在构建期完成大部分本该在运行时做的工作。Spring Boot 项目启动时,组件扫描、反射元数据收集、配置绑定都由 Spring 容器在 JVM 里现做,启动慢、内存占用高;Quarkus 则把这些动作尽量前移到 Maven 构建阶段,编译时扫描类路径、生成字节码、把依赖注入关系静态化,甚至能在构建期直接把可选的 JVM 特征裁剪掉。
这套机制的执行载体就是 quarkus-maven-plugin。你以为你是用 Maven 打了个包,实际上你是在构建期触发了一次"装配",Quarkus 应用启动时要做的事早在打包那一刻就被安排好了。说得直白一点:Spring Boot 的插件更像"打包助手",而 Quarkus 的插件是应用的一部分。
理解了这一点,下面所有怪现象就都顺了:为什么原生镜像需要额外元数据?因为 GraalVM 在构建期需要知道哪些类会被反射调用,这些 reflect-config.json、resource-config.json 由插件的代码生成环节配合扩展生成;为什么不能随便往 pom 里塞一个没注册的依赖?因为插件如果不认识这个扩展,构建期就没人帮你做对应的元数据加工,服务起来行为自然不对。
1.2 插件提供的核心 goal 清单
quarkus-maven-plugin 暴露了一系列 Maven goal,下面这张表基本覆盖了日常使用频率最高的场景:
| 命令示例 | 对应 goal | 作用 |
|---|---|---|
mvn quarkus:create |
create |
创建新的 Quarkus 项目骨架 |
mvn quarkus:dev |
dev |
启动开发模式,支持热重载 |
mvn quarkus:add-extension |
add-extension |
向当前项目添加扩展 |
mvn quarkus:list-extensions |
list-extensions |
列出当前项目已装或可装的扩展 |
mvn quarkus:build |
build |
生产构建,默认绑定在 package 阶段 |
mvn quarkus:update |
update |
升级 Quarkus 平台版本 |
mvn quarkus:generate-code |
generate-code |
根据 proto/avro 等 schema 生成代码 |
quarkus:build 默认绑定到 Maven 的 package 阶段,所以你在生成的项目里直接执行 mvn clean package 就会触发它。generate-code 默认绑定在 generate-sources 阶段,像 gRPC 的 .proto 文件翻译成 Java 类,就是这一步干的。
1.3 项目 pom 里为什么要有那三行 executions
用 quarkus:create 生成的项目,pom 里会有这么一段:
xml复制<plugin>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<extensions>true</extensions> 很容易被忽略,但非常关键。它让插件有机会向 Maven 注册自己的构建扩展,否则部分代码生成和资源处理逻辑就不会生效。如果你把这个配置删了,很多项目表面上还能 mvn package,但生成的产物、健康检查、原生镜像元数据都可能缺东西。
有人问过:既然 quarkus:build 已经绑到了 package 阶段,为什么还要在 executions 里再声明一次?其实这是为了确保显式执行 mvn quarkus:build 时行为一致,同时让 generate-code 和 generate-code-tests 在生命周期里稳定触发。实践里我建议保留生成器默认的这一段,不要自己精简。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用 quarkus:create 创建项目:项目类型其实是扩展组合
2.1 一条命令创建 RESTful 服务项目
Quarkus 不像有些框架按"项目模板"区分类型,它只提供一个基础 Maven 骨架,剩下全靠扩展组合。所谓"RESTful 服务项目",本质就是在创建时加了 REST 相关的扩展。
bash复制mvn io.quarkus.platform:quarkus-maven-plugin:3.17.1:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=order-api \
-DclassName="com.example.order.OrderResource" \
-Dpath="/orders" \
-Dextensions="rest-jackson"
各参数含义:
projectGroupId/projectArtifactId:Maven 坐标,对应 pom 里的groupId和artifactId。className:要生成的资源类全限定名。插件会生成一个带 JAX-RS 注解的示例类。path:该类上的@Path值,比如/orders。extensions:逗号分隔的扩展列表。rest-jackson是 Quarkus REST(RESTEasy Reactive)框架配 Jackson 序列化的组合,3.x 之前叫resteasy-reactive-jackson,现在在新旧版本里都能识别,是 RESTful 服务的首选扩展。
生成完成后项目里会有一个 OrderResource 类,自带一个 GET 方法的示例代码,可以直接跑起来测接口返回 JSON。
命令里指定的 quarkus-maven-plugin 版本建议固定到和你要用的 Quarkus 平台版本一致,避免创建完项目再升级踩不必要的坑。
2.2 微服务项目不是一种独立类型,而是一组扩展组合
微服务项目在 Quarkus 里同样不是 "create 微服务模板" 这种概念,而是"基础项目 + 微服务相关扩展"的集合。一个面向生产环境的微服务,至少要覆盖这些能力:对外暴露 REST 接口、健康检查、API 文档、容错、消息通信、容器化部署。
我建微服务项目时常用这条命令:
bash复制mvn io.quarkus.platform:quarkus-maven-plugin:3.17.1:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=user-service \
-DclassName="com.example.user.UserResource" \
-Dpath="/users" \
-Dextensions="rest-jackson,smallrye-openapi,smallrye-health,smallrye-fault-tolerance,smallrye-reactive-messaging-kafka,container-image-jib,kubernetes"
逐个解释一下为什么选这些扩展:
smallrye-openapi:生成 OpenAPI 文档和 Swagger UI,按项目规范联调时,接口契约一目了然。smallrye-health:暴露/q/health健康检查接口,Kubernetes 的存活探针和就绪探针都靠它。smallrye-fault-tolerance:提供@Retry、@Timeout、@CircuitBreaker这类容错注解,微服务之间互相调用时必须有。smallrye-reactive-messaging-kafka:通过反应式消息收发 Kafka 事件,服务间解耦常用的通道。container-image-jib:构建期直接产出容器镜像,不需要额外装 Dockerfile 工具链。kubernetes:根据应用配置生成 Kubernetes YAML 清单,部署时直接 apply 就行。
你还会经常听到 quarkus-spring-web 扩展,它提供了部分 Spring Web 注解兼容。但实际项目中我建议优先用原生 JAX-RS 风格,不要让团队背着两套注解心智负担。
2.3 常用 create 参数速查
| 参数 | 示例 | 说明 |
|---|---|---|
-DprojectGroupId |
com.example |
Maven 的 groupId |
-DprojectArtifactId |
user-service |
Maven 的 artifactId |
-DprojectVersion |
1.0.0-SNAPSHOT |
项目版本 |
-DclassName |
com.example.user.UserResource |
生成的资源类 |
-Dpath |
/users |
资源路径 |
-Dextensions |
rest-jackson,smallrye-openapi |
逗号分隔的扩展列表 |
-DnoCode |
直接加这个参数 | 不生成示例代码,适合已有代码库想重建骨架 |
-DjavaVersion |
21 |
指定 Java 版本 |
-DnoCode 这个参数可能被不少新手忽略。如果团队已经有一份自己的基础模板,或者创建项目只是为了验证依赖组合,加上它可以把项目骨架清清爽爽地拉到本地,再往里面填自己的业务代码。
3. quarkus:dev 开发模式:热重载背后的机制与边界
3.1 热重载到底省了多少重启时间
mvn quarkus:dev 启动后,应用运行在一个专门为开发优化的 classloader 环境里。你修改 Java 类、资源文件、application.properties,插件会做增量编译并触发热重载,整个过程通常在几秒内完成,这比 Spring Boot DevTools 的部分场景更彻底,因为它连 CDI 依赖注入关系都可以重新解析,而不只是替换一个方法体。
这里有个很容易踩的误区:热重载不等于"改完 pom 也能自动生效"。凡是新增 Maven 依赖、调整插件版本这类构建级变化,dev 模式是不会自动感知的。正确做法是先退出 dev 模式,重新执行一次 mvn quarkus:dev,或者用 mvn quarkus:add-extension 添加扩展后再启动。你在 dev 模式里手动往 pom 里塞依赖,哪怕保存了文件,运行中的 JVM 也不一定加载到新类。
3.2 dev 模式常用参数与调试口
默认情况下 dev 模式监听 8080 端口,调试端口是 5005。实际开发中经常遇到端口被占或者要连调试器的情况,可以直接通过 -D 参数覆盖配置:
bash复制mvn quarkus:dev -Dquarkus.http.port=9090 -Dquarkus.http.host=0.0.0.0
quarkus.http.port:HTTP 服务端口。quarkus.http.host:监听地址,0.0.0.0用于容器或远程调试场景。quarkus.log.level:日志级别,比如DEBUG。debug=false:关闭调试端口;debug=5006指定自定义调试端口。
调试口这一点很容易被忘:如果你在本机同时开了多个 Quarkus dev 实例,5005 端口一定会冲突。此时给每个实例分配不同调试端口比反复关进程舒服得多。
3.3 Dev Services:微服务开发的白嫖神器
Quarkus dev 模式里还有个很容易提升幸福感的功能叫 Dev Services。比如项目里加了 Kafka 或 PostgreSQL 扩展,只要本机有 Docker,启动 dev 模式时插件会自动拉起对应的开发用中间件容器,配置自动注入,你不用手工搭一套本地环境。服务关掉后,这些临时容器也会被清理。
不过要注意:这要求 Docker 可用,且镜像拉取可能需要一些时间。CI 环境里如果不需要 Dev Services,可以通过 quarkus.devservices.enabled=false 关闭,免得每个任务都莫名其妙去拉容器。
3.4 远程开发模式什么时候用
Quarkus 还提供了 mvn quarkus:remote-dev 模式。它的适用场景是:应用跑在 Kubernetes 集群里,你想在本地改代码,让改动实时同步到远端实例。这个功能对本地资源紧张、目标环境内存很大的场景很有帮助。但它要求集群和本地网络可达,而且调试复杂度比本地 dev 模式高,建议先跑通本地环境再上远程模式。
4. quarkus:build 构建打包:fast-jar、uber-jar 与原生镜像怎么选
4.1 默认的 fast-jar 产物在 target/quarkus-app
从 Quarkus 2.x 开始,默认构建产物不是单 jar,而是 target/quarkus-app 目录结构:
text复制target/quarkus-app/
├── quarkus-run.jar
├── lib/
├── app/
├── quarkus/
启动方式是:
bash复制java -jar target/quarkus-app/quarkus-run.jar
这种"目录式布局"是官方推荐的,因为 Quarkus 构建期先生成了索引和元数据,运行时按固定目录结构加载比从一个大 jar 里动态扫描快得多。它的启动速度和原生镜像的差距会小一些,同时保留了 JVM 的成熟生态。
如果你习惯了 Spring Boot 那种"一个 fat jar 走天下",第一次见这个目录结构会不习惯,但千万不要顺手把 target/quarkus-app 里面的文件拿出去手动拼,直接整目录分发就对了。
4.2 uber-jar 和 legacy-jar 什么时候用
有些内部系统、老的部署脚本确实期望一个单 jar 文件,那就用:
bash复制mvn clean package -Dquarkus.package.jar.type=uber-jar
uber-jar 会把所有依赖打进一个 jar,缺点是体积大、启动时加载略慢,好处是分发方便。legacy-jar 是更早版本的默认格式,现在不建议新项目用。
三种格式的选择逻辑,我一般是这样判断的:
| 打包形态 | 产物 | 适合场景 |
|---|---|---|
| fast-jar(默认) | target/quarkus-app 目录 |
容器镜像、标准生产部署 |
| uber-jar | 单个 jar | 脚本部署、遗留平台、手动复制 |
| legacy-jar | 单个 jar + 依赖目录 | 老项目兼容,新项目不推荐 |
需要注意,改了打包类型就相当于改了应用启动方式,部署脚本里别写死一条 java -jar xxx.jar 不区分场景。
4.3 原生镜像构建参数
原生镜像是 Quarkus 的招牌能力,构建命令是:
bash复制./mvnw package -Dquarkus.package.type=native
执行前需要 GraalVM JDK(或 Mandrel),并且安装了 native-image 组件。如果你想免去本机安装,可以强制在容器里构建:
bash复制./mvnw package -Dquarkus.package.type=native -Dquarkus.native.container-build=true
这样插件会拉取官方构建镜像来完成原生编译,本地只需要有 Docker。这个参数在团队协作里尤其有用,能保证不同开发者本机环境一致。
原生编译是个吃内存的活,CI 机器内存不够时经常出现编译进程被 kill。可以显式设置构建内存:
bash复制./mvnw package -Dquarkus.package.type=native -Dquarkus.native.memory-max=8g
如果项目里用到反射、SPI、动态代理,原生构建时容易报"找不到类"或"NoSuchMethod"之类的问题。常见解法是给相关类加 @RegisterForReflection,或者在命令里追加 GraalVM 参数:
bash复制./mvnw package -Dquarkus.package.type=native \
-Dquarkus.native.additional-build-args=-H:ReflectionConfigurationFiles=reflection.json
这类问题排查起来很耗时,所以我的建议是:从第一天就把扩展选对,尽量少在原生镜像里做反射黑魔法。
4.4 构建时顺手生成容器镜像和 Kubernetes 清单
Quarkus 的构建可以用 Maven 参数直接完成"代码 -> 镜像 -> K8s 清单"的流水线:
bash复制mvn clean package \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.group=registry.example.com/demo \
-Dquarkus.container-image.name=user-service \
-Dquarkus.kubernetes.deployment-target=kubernetes
第一行让构建期直接出镜像;第二行指定镜像仓库的前缀;第三行指定镜像名;第四行让插件生成 Kubernetes YAML。对于纯 RESTful 服务可能不需要这么重,但微服务项目强烈建议把这一步早点接进 CI。
5. 按项目需求配置插件参数:RESTful 与微服务的实战配置
5.1 RESTful 服务项目:接口网关场景的典型配置
先看一个典型的对外 RESTful 服务配置。假设它负责订单查询,需要暴露给前端控制台,并且被网关注到。核心 application.properties 往往是这样的:
properties复制quarkus.http.port=8080
quarkus.http.host=0.0.0.0
quarkus.http.cors=true
quarkus.http.cors.origins=https://console.example.com
quarkus.swagger-ui.always-include=true
quarkus.smallrye-openapi.path=/api-docs
这里面有三个点值得展开:
quarkus.http.host=0.0.0.0:容器内运行时必须配,否则在外层网络访问不到。quarkus.http.cors.origins:RESTful API 被浏览器直接调用时,CORS 配置常年是漏网之鱼。开发环境经常配*,上线之前一定要收紧到具体域名。quarkus.swagger-ui.always-include=true:默认 Swagger UI 只在 dev 模式暴露。如果你希望非生产环境也能看接口文档,这个参数是必须的;但如果这是生产 API,建议不要开,避免信息泄露。
插件本身在 RESTful 项目上的另一个关键点是 quarkus:build 时是否生成 openapi 规范的元数据。加了 smallrye-openapi 扩展后,构建产物里会有对应的 OpenAPI 文件位置,可以给 API 网关做导入或契约测试用。
5.2 微服务项目:消息、容错和可观测性的配置
微服务项目比纯 RESTful 项目复杂在基础设施对齐上。下面是我在用户服务里常用的配置片段:
properties复制quarkus.http.port=8080
quarkus.http.host=0.0.0.0
# 健康检查
quarkus.smallrye-health.enabled=true
quarkus.smallrye-health.root-path=/health
# Kafka
quarkus.kafka.bootstrap.servers=kafka-cluster:9092
mp.messaging.outgoing.user-events.bootstrap.servers=kafka-cluster:9092
mp.messaging.outgoing.user-events.topic=user-events
# 容器与 Kubernetes
quarkus.container-image.group=registry.example.com/demo
quarkus.container-image.name=user-service
quarkus.kubernetes.deployment-target=kubernetes
quarkus.kubernetes.ingress.expose=true
quarkus.kubernetes.ingress.host=api.example.com
配合容错注解的代码通常是这样的:
java复制@Path("/users")
public class UserResource {
@Inject
@RestClient
OrderServiceClient orderClient;
@GET
@Path("/{id}/orders")
@Retry(maxRetries = 3, delay = 500)
@Timeout(2000)
public List<Order> getUserOrders(@PathParam("id") Long userId) {
return orderClient.listOrders(userId);
}
}
这里 @Retry 和 @Timeout 来自 smallrye-fault-tolerance,实际生产时建议把重试、超时这些参数做成配置项,而不是硬编码在注解里。Quarkus 的配置覆盖优先级是:系统属性 > 环境变量 > application.properties,所以部署时用环境变量覆盖临时调优非常方便。
对于微服务调用链追踪,推荐加 quarkus-opentelemetry 扩展。它不是插件的直接参数,但构建期会自动配置对应的追踪上下文,和日志、指标一起构成了线上排查的三件套。
5.3 最容易翻车的三个细节与排查思路
第一,别手动在 pom 里塞 Quarkus 扩展依赖。Quarkus 扩展通常会在构建期做字节码处理、附加元数据,光加一个 quarkus-rest 依赖而不走扩展机制,后续打包很可能缺这缺那。我试过手工加依赖,表面没问题,原生构建一跑全露馅。建议统一用:
bash复制mvn quarkus:add-extension -Dextensions="rest-jackson,smallrye-health"
这样插件会正确更新 BOM 和扩展注册信息,构建链路上所有参与者都认它。
第二,dev 模式的系统参数不好使时,检查一下是不是用了 mvn 而不是 ./mvnw。项目里的 Maven Wrapper 会绑定特定版本和本地仓库,换成本机 Maven 后版本不一致会导致插件解析异常。
第三,JVM 包和原生包行为不一致。很多团队在本地 JVM 模式开发,上生产却直接用原生镜像。开发阶段为了省事多反射一些字段,到了原生就报错。排查时先看构建日志里"unresolvable"类型告警,再决定加 @RegisterForReflection 还是补 quarkus.native.additional-build-args。
端口冲突也是高频问题。本地同时跑多个 Quarkus 服务时,dev 模式默认 8080 必顶。我的习惯是每个服务在 application.properties 里就指定一个固定端口,而不是启动时靠记忆传参。user-service 用 8081,order-api 用 8082,一眼能分清。
最后分享一点个人感受:刚开始用 Quarkus 时,我总拿 Spring Boot 的思路去套 Maven 插件,结果在打包路径、扩展管理和原生镜像这几处反复翻车。后来把"插件是一条装配产线"这个认知建立起来,绝大多数问题都能顺着构建流程自己找到答案。对于刚接触的团队,我建议先拿一个 RESTful 项目把 create、dev、package 三个环节完整跑通,再谈微服务扩展组合。一上来就 Kafka + Kubernetes + Knative 全上,排错难度会直接拉满,等前面的基础链路都熟了,这些高级参数对你来说就只是配置项,而不是黑盒子。
