Quarkus Desktop 0.1.0 已上线:用 CDI 和 dev mode 写 AWT / Swing,同一套代码可打成 Windows、Linux、macOS 的 native 可执行文件。对 Solo 做桌面工具、小产品的人来说,这条路把本机 JVM 调试和给用户一个接近单文件的分发连上了。它跟同日的 Quarkus 3.40 LTS 不是一回事:3.40 是平台 LTS,这篇讲的是桌面扩展。

主源:官博 2026-09-30、GitHub quarkiverse/quarkus-desktop、Quarkiverse 文档。Maven Central 已有 io.quarkiverse.desktop:quarkus-desktop-swing:0.1.0(及 quarkus-desktop-awt)。下文数字与限制以官博为准;平台细节必要时对照文档。本稿命令未在本机完整跑通。

独立开发者为什么会碰到它

做小工具时,分发成本经常比功能本身更烦。用户不愿装 JDK,你也不想维护安装器脚本、JRE 捆绑和各平台启动参数。Electron 能打包,但体积和内存对「改个配置、看个日志、托盘里挂个状态」这类需求往往过重。Java 桌面(AWT / Swing)仍能覆盖大量内部工具场景,缺的是一条从开发到 native 分发的现成轨道。

GraalVM native 对桌面很香:启动快、目标机不必装 JVM、交付形态接近一个可执行文件加旁边几份库。现实障碍是 AWT / Swing 依赖大量按名字查找的类、资源、JNI 回调,而且 Windows、Linux、macOS 各有一套。手工补 reachability metadata,项目一大就容易漏。

Quarkus Desktop 的定位,是把 GUI 侧需要的注册做进扩展:窗口、字体、图片、打印、剪贴板、拖放、输入法、声音、无障碍、Swing 外观等,并在构建时把 native 可执行文件要加载的 JDK 库拷到旁边。同一应用可以在 JVM 模式、dev mode 下开发,再在对应平台打出 native。官博也写了适用面:你在写或维护 Java 桌面应用,想用上 CDI、配置、dev mode 和 native;或者服务旁边需要设置窗、托盘图标这类小 UI。

加依赖:0.1.0

Swing 应用:

<dependency>
    <groupId>io.quarkiverse.desktop</groupId>
    <artifactId>quarkus-desktop-swing</artifactId>
    <version>0.1.0</version>
</dependency>

只用 AWT 时换成 quarkus-desktop-awt。quarkus-desktop-swing 已包含 AWT 扩展。写稿时 Maven Central 上 quarkus-desktop-swing 的 latest / release 均为 0.1.0(metadata lastUpdated 为 2026-09-29)。之后以 Central 最新版为准即可。

Gradle 写法在文档里也有,例如 implementation("io.quarkiverse.desktop:quarkus-desktop-swing:0.1.0")。工程仍是标准 Quarkus 应用,只是多挂了桌面扩展。

窗口就是 CDI Bean,没有 main

传统 Swing 从 main 里 SwingUtilities.invokeLater 拉起窗口。Quarkus Desktop 把模型换成:窗口是 CDI Bean,观察 DesktopStartupEvent。该事件在应用启动完成后,在事件派发线程(EDT)上发出。没有 main,也没有 @QuarkusMain。

import io.quarkiverse.desktop.awt.DesktopStartupEvent;

@Singleton
public class MainWindow extends JFrame {

    @Inject
    Library library;

    @ConfigProperty(name = "app.title", defaultValue = "Library")
    String title;

    void open(@Observes DesktopStartupEvent event) {
        setTitle(title);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        add(new JScrollPane(new JList<>(library.titles())));
        pack();
        setVisible(true);
    }
}

窗口上可以直接 @Inject 业务 Bean、读 @ConfigProperty。最后一个可见窗口关掉后,应用经 Quarkus 退出,ShutdownEvent 观察者和 @PreDestroy 会执行到。对 Solo 产品这很实用:关窗等于走完生命周期,不会留下半吊子进程。

窗口作用域只能是 @Singleton 或 @Dependent。@ApplicationScoped 这类带客户端代理的普通作用域不适合挂 Swing 组件:代理会变成第二个组件,而且往往在 EDT 外创建。构建阶段扩展会报告这类误用,别等到运行时才发现界面行为怪。

开发时继续用 quarkus dev。热替换、配置刷新这些 Quarkus 习惯,可以带到桌面 UI 上,先把交互做对,再考虑 native。

慢活离开 EDT,回来用 EdtExecutor

Swing 组件只能在 EDT 上碰。读文件、调远程 API、扫本地库这类活,必须丢到别的线程。Quarkus Desktop 提供 EdtExecutor:在 worker 上跑完,再回到 EDT 更新界面。

@Inject
ManagedExecutor workers;

@Inject
EdtExecutor edt;

void refresh() {
    status.setText("Loading...");
    CompletableFuture.supplyAsync(repository::titles, workers)
            .thenAcceptAsync(this::show, edt);
}

EdtExecutor 也能配 Mutiny(emitOn(edt))和异步 CDI 事件。如果方法是从别的线程进来的,例如 @Scheduled 定时任务或一个本地 REST 管理口,给方法加 @RunOnEdt:

@ApplicationScoped
public class StatusPresenter {

    @Inject
    Instance<StatusBar> statusBar;

    @RunOnEdt
    public void show(String text) {
        statusBar.get().setText(text);
    }
}

无论调用方在哪条线程,方法都会在 EDT 上执行。方法还可以返回 CompletionStage,带着结果或异常完成。写小工具时,把「业务线程」和「画界面」分清楚,卡顿和偶发崩溃会少很多。

macOS:About / Quit 变成 CDI 事件

在 macOS 上,应用菜单和 Dock 会把 About、Preferences、从 Finder 打开的文件、退出请求发给应用。Quarkus Desktop 把它们打成 CDI 事件,观察者写法和其他事件一样:

void about(@Observes AboutEvent event) {
    WindowBeans.get(aboutDialog).setVisible(true);
}

void quit(@Observes QuitRequest request) {
    if (documents.hasUnsavedChanges()) {
        request.cancel();
    }
}

aboutDialog 用注入的 Instance;WindowBeans 负责创建对话框,关闭时销毁 Bean。Cmd-Q 触发 QuitRequest,观察者可以 cancel(),否则经 Quarkus 正常退出。对要发 Mac 版的小产品,这比自己接 Desktop / Application 监听器省事,也和整棵 CDI 树一致。

打 native:命令与平台边界

构建命令与普通 Quarkus 相同:

./mvnw package -Dnative

在目标平台本机构建。官博给出的要点如下(数字与限制以该文为准):

  • Windows:可执行文件 DPI 感知,外观跟 java 启动器一致。配置 quarkus.desktop.awt.windows.subsystem=windows 可以去掉控制台窗口,接近 javaw 的体验。
  • Linux:使用系统 X11 库;Wayland 环境下走 XWayland。也可以在容器里构建。
  • macOS native:需要 Quarkus 4.0(其 quarkus-awt 支持 macOS)以及 GraalVM 25.1 或更新。
  • Windows arm64:GraalVM 没有该平台的 native image builder,应用在那里 只能 JVM 模式。

文档与 README 有一条实务补充:写稿时 macOS 侧对 quarkus-awt 的支持尚未随正式 4.0 放出,需要跟 main 构建的 Quarkus 999-SNAPSHOT,直到 4.0 带上这项能力。Windows / Linux 的 native 可按文档要求的 Quarkus / GraalVM 版本先推进;若你的产品必须先发 Mac native,排期要对齐 4.0 与 GraalVM 25.1。

AWT / Swing 在 native 里大体能按 JVM 那样用,但有文档化限制。例如 native 可执行文件不能加载构建时未知的类。发版前把动态加载、反射、字体与打印路径对一下 Limitations 列表,比盲目加参数省时间。

Solo 落地一条小工具的路径

可以按这条线试一版最小产品:

  1. 新建或沿用 Quarkus 工程,加上 quarkus-desktop-swing:0.1.0。
  2. 主窗口做成 @Singleton,观察 DesktopStartupEvent,先在 quarkus dev 里把界面和业务跑通。
  3. 慢路径统一走 ManagedExecutor + EdtExecutor / @RunOnEdt,避免 EDT 卡死。
  4. 需要 macOS 菜单行为时,接 AboutEvent、QuitRequest 等。
  5. 在目标 OS 上执行 ./mvnw package -Dnative;Windows arm64 用户继续发 JVM 包。
  6. 对照官方 Limitations 做一次抽查:反射、资源、打印、剪贴板、外观切换。

托盘状态、本地服务旁的管理窗、离线小编辑器,都可以按同一模型长出来。配置项多数在 native 构建时固定;JVM 模式仍走普通 java 启动器。具体属性名以文档配置表为准,例如 Windows 的 subsystem、DPI、是否拷贝 VC 运行库,macOS 的应用名与 Info.plist 相关开关等。

体积与外观上,Swing 的 look and feel 是否全部打进 native、JavaBeans 属性是否注册,文档里有对应配置和大致体积说明。Solo 产品建议先默认,确认功能后再按需裁剪。

showcase:75 页、约 3800 检查

官博写:Quarkus Desktop showcase 是约 75 页、约 3800 项检查 的应用,覆盖 AWT、Java2D、文本、图片、Swing、外观、数据传输、打印、无障碍和声音。CI 在 Linux、Windows、macOS 上分别用 JVM 与 native 跑每一页,再 按像素、按检查项 对照两次运行。文档还写到,除了展示 native 合法差异的那一页,其余页在两边结果一致。对扩展可信度来说,这比口号有用:你至少知道主流路径有人在三端对照过。

和站内「Quarkus 3.40 LTS」怎么分工

3.40 LTS 那篇谈平台 BOM、升级命令、维护窗口,服务端生产线怎么钉版本。本稿只谈 Quarkus Desktop 0.1.0:CDI 窗口模型、EDT、macOS 事件、跨平台 native 边界。做服务的人盯 LTS;做桌面小产品的人盯这篇、扩展文档和 showcase。两者同一天周边信息多,选题刻意错开,避免读者以为「升 LTS 就自动有了桌面 native」。

和「自己手搓 native AWT」差在哪

有人会问:GraalVM 文档里也有 AWT 相关 reachability,为什么还要扩展。差别主要在工程成本。手搓时你要自己收集 JNI、字体、图片编解码、打印、剪贴板、各平台 peer 类,还要处理可执行文件旁边的原生库怎么拷、Windows 清单和 DPI、Linux 上 X11 / GTK 依赖、macOS 主线程与 Cocoa 事件环。项目一复杂,漏一项就黑屏或启动即崩。

Quarkus Desktop 把这些注册和拷库步骤收进构建期扩展,并和 CDI、配置、dev mode、退出生命周期绑在同一棵树上。你仍然要懂 Swing 与 EDT,但不必从零维护一份「桌面 native 元数据大全」。showcase 用 75 页、约 3800 检查在三端做 JVM / native 像素级对照,等于给这条路径垫了一层回归网。Solo 团队没有专职构建工程师时,这种封装更值钱。

当然扩展解决不了产品问题:界面信息架构、权限模型、自动更新、代码签名与公证,仍要你自己做。它只是把「能跑、能打、能在三端对齐」这一截缩短。

分发时可以先想清楚的几件事

发桌面小产品,构建成功只是一半。Windows 上要不要关控制台、要不要打进 Access Bridge;Linux 用户环境有没有合适的 X11 / 字体;macOS 是否先发 JVM 包、等 4.0 再切 native。Windows arm64 目前只能 JVM,若你的用户里有这类机器,发布说明里写清楚,避免他们对着 -Dnative 报错空耗。

版本策略上,桌面扩展坐标是 io.quarkiverse.desktop:*:0.1.0,和平台 BOM 是两回事。应用仍要钉自己的 Quarkus 平台版本;Desktop 再单独引入。升 Quarkus 主版本时,对照 Desktop 文档里的「Supported platforms」表,尤其是 macOS native 对 Quarkus 4.0 与 GraalVM 25.1 的要求,不要假设「升了 LTS 就自动能打 Mac native」。

本地验证建议最小集:dev mode 开窗与关窗退出;一条 worker + EDT 回写路径;目标平台打一次 native 冒烟;若做 Mac,再测 About / Quit。有余力再碰打印、拖放、无障碍。showcase 是扩展作者的回归集,你的产品不必全抄,但可以从里面挑和业务相近的页当对照。

什么时候先别上

如果界面要大量动态加载第三方 Look and Feel、运行时插件式面板,或强依赖构建期不可见的类,先读 Limitations,再决定是否 native。若团队完全没有 Java 桌面经验,只是想快速做一个安装包好看的消费级 App,评估成本可能仍偏向其他技术栈。Quarkus Desktop 更适合:你已经会一点 Swing / AWT,或者愿意用它们做工具型 UI,同时希望复用 Quarkus 的 CDI、配置与构建习惯。

0.1.0 仍是早期版本。API 与配置以当前文档为准,跟 Issues 和后续小版本。对 Solo 来说,适合先做内部工具或小范围用户验证,再决定是否作为长期桌面底座。

发版说明里建议写清三件事:当前扩展版本(0.1.0)、各平台是 JVM 还是 native、macOS native 是否已具备 Quarkus 4.0 与 GraalVM 25.1。用户按说明安装,比事后在 Issue 里解释「为什么 arm64 Windows 打不出 native」省事。

若你已有纯 Swing 老项目,迁移时不必一次重写全部窗口。可以先把启动切到观察 DesktopStartupEvent,主窗改成 @Singleton,业务 Bean 逐步 @Inject 进来,确认 dev mode 稳定后再加 native 构建。

文档仍标注 work in progress,配置项与平台表可能随小版本调整,发版前再对一次文档站。

务必以官博与文档站为准。

参考

— 感谢阅读 —

一起交流

分享你的思考,让讨论更进一步。