用 Spring Boot + Vue + Fuzio 构建现代 Java 桌面应用

用 Spring Boot + Vue + Fuzio 构建现代 Java 桌面应用 很多人以为 Spring 只属于服务器。但当桌面项目复杂到需要清晰的分层、依赖注入、配置管理和数据访问时把 Spring 引入客户端已经是一种成熟做法——尤其在企业级桌面软件中很常见。更进一步Spring Boot 自带的内嵌 Web 服务器让桌面客户端可以内置一个完整的本地后端从而用 Web 技术来构建界面。本文就把这套思路落地用 Spring Boot 承载业务、数据并内置本地 HTTP 服务用 Vue 构建界面再用Fuzio把 Web 界面嵌进桌面窗口、并把它和 Spring 后端连起来。我们会一步一步从零把它搭起来。为什么在桌面应用里引入 Spring在桌面端引入 Spring 的真正动因是项目一旦复杂起来原生工具包在应用结构管理上的短板就会暴露。Swing 和 JavaFX 擅长界面呈现却缺乏对依赖、配置和对象生命周期的统一管理你得手动 new 服务、自己维护单例、把配置逻辑散落在各个控制器里。规模较小时尚可应付应用一旦增长代码很快变得纠缠难维护。这也是为什么在稍具规模的项目、尤其是企业级桌面软件中用 Spring 来组织桌面应用已经是一种成熟模式。把 Spring 引入桌面你能直接复用整套后端能力依赖注入与 Bean 生命周期配置与 Profile数据访问生态组件内嵌 Web 服务器一旦客户端内置了 HTTP 服务器界面层就有了新的选择不必再用原生控件而可以用 Web 技术来构建。这恰好补齐了 Swing/JavaFX 在现代视觉和跨平台一致性上的短板。这就引出了本文要搭建的架构用 Spring Boot 做内嵌后端用 Web 技术做界面再用 Fuzio 把两者缝合进一个 Java 桌面窗口。下面我们一步一步把它实现出来。我们要构建什么一个单一可执行的 Java 桌面应用内部由三部分组成Spring Boot随桌面应用一起启动负责业务逻辑、配置管理与数据访问。Vue 前端标准的 Vue 3 Vite 工程负责界面布局、交互和图表展示。Fuzio在 Java 桌面窗口中嵌入 Chromium把 Vue 界面渲染成原生桌面应用的一部分。最终效果是一个深色风格的后台管理界面运行在原生桌面窗口中项目结构如下spring-fuzio-desktop-demo/ ├── pom.xml # Maven 构建配置 ├── src/main/ │ ├── java/tech/fuzio/demo/ │ │ ├── DesktopApplication.java # 入口启动 Spring Boot Swing 窗口 Fuzio │ │ ├── dashboard/ # 方案 AREST 接口Controller Service │ │ └── user/ # 方案 B用户业务 │ └── resources/ │ ├── application.yml # 内嵌服务器配置端口 / 监听地址 │ └── static/ # 前端构建产物由 Spring Boot 托管 └── web-app/ # Vue 前端 ├── package.json ├── vite.config.ts └── src/ ├── pages/ # Dashboard / 用户管理页 ├── components/ # 图表等复用组件 └── api/ # 两套通信封装HTTP 与 JS 桥对于 Vue 页面如何获取 Java 后端的数据这里有两种选择方案 A内嵌服务器 REST 接口Spring Boot 内嵌 Tomcat既托管前端又以 REST 接口供数据前端走标准 HTTP。方案 B自定义协议 Java↔JS 桥不启动本地 HTTP 服务器Vue 资源由 Fuzio 自定义协议从应用内部加载前后端通过 Java↔JS 桥直接对话。两套方案怎么选我们后面会讨论。下面先从两者共同的基础——让 Spring Boot 与 Swing 共处一个进程开始。第一步为桌面应用启动 Spring Boot 后端先解决最基础的问题一个 Spring Boot 应用怎么同时弹出一个 Swing 窗口。和任何 Spring Boot 应用一样入口类用SpringBootApplication注解修饰 注解启用 Spring Boot 服务。应用本身通过SpringApplicationBuilder创建这样我们可以在启动前调整启动参数。这里要调整的关键一项是关闭无头模式默认情况下 Spring Boot 以无头模式headless启动这种模式适用于服务器环境不提供图形界面支持。而在这种模式下创建JFrameJDialog等需要图形环境的组件时候 会抛HeadlessException。因此启动时显式headless(false)。importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.builder.SpringApplicationBuilder;importorg.springframework.context.ConfigurableApplicationContext;// SpringBootApplication 启用 Spring Boot 的自动配置与组件扫描。SpringBootApplicationpublicclassDesktopApplication{publicstaticvoidmain(String[]args){// 用 SpringApplicationBuilder 创建应用并在启动前关闭无头模式。// headless(false) 是桌面应用的必备项否则创建 Swing 窗口会抛 HeadlessException。ConfigurableApplicationContextcontextnewSpringApplicationBuilder(DesktopApplication.class)// .web(WebApplicationType.NONE).headless(false).run(args);// ... 后面创建窗口}}这里要不要保留内嵌 Web 服务器取决于你走哪套方案走方案 A第三步用内嵌服务器托管前端 REST就保持默认——不要加web(WebApplicationType.NONE)让 Spring Boot 把内嵌 Tomcat 跑起来。走方案 B第四步自定义协议 JS 桥用不到 HTTP可以显式web(WebApplicationType.NONE)把 Spring 当作纯 DI 容器。本文先按方案 A 往下走。为此配置application.yml让内嵌 Tomcat 用随机端口、只监听本地回环server:# 0 随机端口启动后由代码读取实际端口避免端口冲突。port:0# 只监听本地回环地址避免内嵌服务被局域网访问。address:127.0.0.1用port: 0让系统分配一个空闲端口再在启动后回读真实端口——这样多个实例或被占用端口都不会冲突。读取端口的方式importorg.springframework.boot.web.context.WebServerApplicationContext;intport((WebServerApplicationContext)context).getWebServer().getPort();StringappUrlhttp://127.0.0.1:port/;到这里Spring Boot 已经能在桌面环境下启动。但还没有窗口——接下来交给 Fuzio。第二步用 Fuzio 在桌面窗口中渲染 Vue 界面Fuzio 是一个 Java 嵌入式 Chromium 浏览器组件可以把基于 Chromium 的 Web 视图集成进 Swing、JavaFX、SWT、Compose Desktop 应用。这里我们使用 Swing 的 JFrame 作为主窗口并在窗口内部嵌入 Fuzio 的 BrowserView。先加入 Fuzio 依赖。下面分别给出 Maven 和 Gradle 的写法。Mavenpom.xmlrepositoriesrepositoryidtech.fuzio/idurlhttps://jiku.mycloudrepo.io/public/repositories/releases/url/repository/repositoriesdependencies!-- Fuzio获取所有平台的 Chromium 二进制文件 --dependencygroupIdtech.fuzio/groupIdartifactIdfuzio-cross-platform/artifactIdversion2026.3.0/versiontypepom/type/dependency!-- FuzioSwing UI 工具包集成 --dependencygroupIdtech.fuzio/groupIdartifactIdfuzio-swing/artifactIdversion2026.3.0/version/dependency/dependenciesGradlebuild.gradle使用官方 Fuzio Gradle 插件plugins{idtech.fuzio.gradleversion1.0.0}fuzio{// Fuzio 版本必需version2026.3.0}dependencies{// 检测当前平台并添加对应的 Chromium 二进制文件implementation fuzio.currentPlatform// FuzioSwing UI 工具包集成implementation fuzio.swing}应用主窗口基于 Java Swing 的JFrame实现——作为 Java 内置组件它轻量且易用。窗口内部嵌入 Fuzio 提供的 Chromium 内核 Web 视图BrowserView直接在桌面窗口中渲染 Vue.js 界面并支持所有现代浏览器特性varengineEngine.newInstance(HARDWARE_ACCELERATED);varbrowserengine.newBrowser();SwingUtilities.invokeLater(()-{varviewBrowserView.newInstance(browser);varframenewJFrame(Spring Fuzio 桌面应用);frame.addWindowListener(newWindowAdapter(){OverridepublicvoidwindowClosing(WindowEvente){engine.close();}});frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);frame.add(view,BorderLayout.CENTER);frame.setSize(1280,800);frame.setLocationRelativeTo(null);frame.setVisible(true);// 加载由内嵌 Spring Boot 托管的本地前端browser.navigation().loadUrl(appUrl);});这里appUrl指向内嵌 Spring Boot 的本地地址http://127.0.0.1:{port}/。这套架构分工明确Spring 后端负责核心业务逻辑数据读写、文件操作等Vue.js 前端专注界面布局与用户交互各司其职、高效协同。另外Fuzio 采用多进程架构。Chromium 在独立进程中运行并通过 IPC 与 Java 进程通信。这样即使某个网页崩溃承载 Spring 后端的 Java 进程依然存活对要保护用户数据的桌面应用尤为关键。第三步方案 A——内嵌服务器托管前端 REST 接口第一套方案最直接让 Spring Boot 启动内嵌 Tomcat既托管 Vue 构建产物又提供 REST 接口。这一步我们写一个普通的RestController前端通过标准 HTTP 调用它RestControllerRequestMapping(/api/dashboard)publicclassDashboardController{privatefinalDashboardServicedashboardService;// 普通 Spring Bean构造器注入publicDashboardController(DashboardServicedashboardService){this.dashboardServicedashboardService;}GetMapping(/summary)publicMapString,Objectsummary(){returnMap.of(statCards,dashboardService.statCards(),revenueChart,dashboardService.revenueChart());}}DashboardService 是一个普通 Spring Bean项目中则可以接入 Spring Data、MyBatis、本地数据库或企业内部系统。这里的重点是桌面应用仍然可以沿用 Spring Boot 熟悉的分层方式组织业务代码。前端Vue这边就是普通的fetch// 前端通过标准 fetch 调用内嵌 Spring Boot 的 REST 接口。exportasyncfunctionfetchSummary():PromiseDashboardSummary{constresawaitfetch(/api/dashboard/summary)if(!res.ok)thrownewError(加载仪表盘数据失败res.status)returnres.json()}这里暂时省略了鉴权逻辑。实际企业项目或生产环境中即使服务只监听 127.0.0.1也建议在请求中携带应用启动时生成的本地访问 token例如通过 Authorization 或自定义请求头传递避免同一台机器上的其他进程直接调用本地接口。前端的构建产物也由这个内嵌 Spring Boot 托管。Vue 工程用 Vite把构建输出目录指向 Spring Boot 的静态资源目录// vite.config.tsexportdefaultdefineConfig({plugins:[vue()],base:./,build:{// 构建产物直接输出到 Spring Boot 的 static 目录由内嵌 Tomcat 托管。outDir:fileURLToPath(newURL(../src/main/resources/static,import.meta.url)),emptyOutDir:true,},})这样一来Spring Boot 启动后Fuzio 加载http://127.0.0.1:port/就能显示完整的 Vue 页面而前端的fetch(/api/dashboard/summary)也落在同一个内嵌服务上。第四步方案 B——自定义协议 Java↔JS 桥第三步那套内嵌服务器 HTTP已经能跑通完整应用。但它隐含了一个前提你愿意在本地起一个 HTTP 服务器。有些场景并不需要 - 比如你只想用 Spring 的依赖注入、配置、数据访问并不打算对外暴露任何 HTTP 接口或者出于安全考虑不希望前端代码和接口能被本地浏览器直接访问。这种情况下可以走一条完全不依赖本地服务器的路线前端资源用 Fuzio 的自定义协议从应用内部加载前后端通信用 Java↔JS 桥。这一步我们换用这套方案做用户管理页。用自定义协议托管前端第三步是把 Vue 构建产物交给内嵌 Tomcat、通过http://127.0.0.1:port/提供。这里换一种思路把 Vue 应用文件直接打包进 Java 资源目录通过 Fuzio 的自定义协议拦截 API 从类路径中直接加载资源。为了允许请求拦截器处理 Web 资源请求我们将其分配给一个自定义协议varoptionsEngineOptions.newBuilder(HARDWARE_ACCELERATED).addScheme(Scheme.of(app),newUrlRequestInterceptor());varengineEngine.newInstance(options.build());每当浏览器尝试加载 app:// URL 时拦截器就会处理该请求并从应用的资源目录中返回对应的 HTML、CSS 或 JavaScript 文件。通过这种方式所有 Web 资源的加载都发生在应用内部外部无法拦截也无法访问这些资源。同时使用自定义协议可确保常规 HTTPS/XHR 请求例如身份验证、API 调用能够顺利通过。之后让浏览器加载app:/my-app.com即可不再需要本地端口browser.navigation().loadUrl(app://my-app.com);这样一来所有 HTML、CSS、JavaScript 都从应用 JAR 内读取不经过本地 HTTP 服务器源码也不会暴露在某个本地地址上。用 Java↔JS 桥进行通信前端不再用fetch打 HTTP而是直接调用注入到页面里的 Java 对象。出于安全考虑Fuzio 只允许标注了JsAccessible的公共方法被 JavaScript 访问。我们定义一个桥对象注入需要的 Spring Bean把方法暴露给前端ComponentJsAccessiblepublicclassSpringBridge{privatefinalUserServiceuserService;publicSpringBridge(UserServiceuserService){this.userServiceuserService;}publicStringlistUsers(){returnnewGson().toJson(userService.findAll());}}然后用 Fuzio 的InjectJsCallback把从 Spring 容器取出的桥对象挂到window上SpringBridgebridgecontext.getBean(SpringBridge.class);browser.set(InjectJsCallback.class,params-{params.frame().JsObjectexecuteJavaScript(window).putProperty(springBridge,bridge);returnInjectJsCallback.Response.proceed();});前端只要声明一下类型就能像调用本地方法一样调用后端declareconstspringBridge:{listUsers():string}constusersJSON.parse(springBridge.listUsers())关于 Java↔JS 桥的更多用法可参考 Fuzio JavaScript 指南。用户管理页跑起来是这样延伸阅读Fuzio 架构指南https://fuzio.tech/docs/guides/introduction/architecture/获取 Fuzio 免费试用密钥https://fuzio.tech/#try-for-free