使用 Angular 构建 Java 桌面应用

使用 Angular 构建 Java 桌面应用
在这里插入图片描述


本文介绍如何构建一个跨平台的 Java 桌面应用,在原生 Swing 窗口中集成现代化的 Angular Web 界面。

前置条件

要完成本教程,您需要:

  • Git
  • Java 17 或更高版本
  • Node.js 22.0+
  • npm 9+
  • 有效的 JxBrowser 许可证(评估版或商业版)。有关许可证的更多信息,请参阅许可指南。

项目设置

本教程示例应用程序的代码与其他示例一起,存储在一个基于 Gradle 的 GitHub 仓库中。

如果您想构建一个基于 Maven 的项目,请参考 Maven 配置指南。如果您希望从头开始构建一个基于 Gradle 的项目,请参考 Gradle 配置指南。

获取代码

要获取代码,请执行以下命令:

git clone https://github.com/TeamDev-IP/JxBrowser-Gallery.git cd JxBrowser-Gallery/desktop-angular-dashboard 

添加许可证

要运行本教程,您需要设置许可证密钥

您将构建什么

在本教程中,您将创建一个桌面应用程序,该应用具备以下特性:

  • 在原生 Java Swing 窗口中嵌入 Angular 仪表板
  • 在开发模式下,从本地开发服务器加载 Angular UI
  • 在生产模式下,将所有 UI 资源直接打包到 JAR 中,支持离线可用
  • 使用 JxBrowser 提供的 JavaScript-Java 桥接器,实现 Angular 和 Java 之间的通信

项目架构

本文创建的桌面应用由两个主要部分组成:

  • Java 后端:一个基于 Swing 的应用程序,用于托管浏览器窗口并提供数据服务
  • Angular 前端:一个提供用户界面并与 Java 后端通信的 Web 应用程序

项目结构

desktop-angular-dashboard/ ├── build.gradle.kts # Gradle 构建配置 ├── src/main/ │ ├── java/.../angular/ # Java 后端 │ │ ├── App.java # 入口点 │ │ ├── AppInitializer.java # 窗口设置和 JS-Java bridge │ │ └── production/ # URL 拦截器和 MIME 类型 │ └── resources/ │ └── web/ # 打包的 Angular 文件(生产环境) └── web-app/ # Angular 前端 ├── package.json # npm 依赖 ├── angular.json └── src/app/ ├── app.config.ts └── services/ └── backend.service.ts 

创建应用窗口

主窗口使用 Java Swing 的 JFrame 实现。
在窗口内嵌入 JxBrowser 的 BrowserView 来渲染 Angular UI。

var engine =Engine.newInstance(HARDWARE_ACCELERATED);var browser = engine.newBrowser();SwingUtilities.invokeLater(()->{var view =BrowserView.newInstance(browser);var frame =newJFrame("Angular Dashboard"); frame.addWindowListener(newWindowAdapter(){@OverridepublicvoidwindowClosing(WindowEvent e){ engine.close();}}); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.add(view,BorderLayout.CENTER); frame.setSize(1280,800); frame.setLocationRelativeTo(null); frame.setVisible(true);});

Engine 管理底层的 Chromium 进程,而 BrowserView 是在 Java 应用程序中显示 Web 内容的 Swing 组件。要深入了解这些组件如何交互,请参阅架构指南。

在桌面应用中加载 Angular UI

应用程序的 Angular 部分是位于 web-app/ 目录中的标准 Web 项目。
启动开发服务器时,您会看到熟悉的输出:

$ npm start Initial chunk files | Names | Raw size polyfills.js | polyfills | 90.20 kB | main.js | main | 18.18 kB | Application bundle generation complete. [1.204 seconds] ➜ Local: http://localhost:4200/ 

该应用支持两种模式(开发模式和生产模式)以满足不同的使用需求。

在开发模式下,我们希望修改组件或样式时能获得即时反馈。
在生产环境下,我们希望用户界面是安全且独立封装的,不依赖任何外部服务器。

开发模式

在开发模式下,我们希望对 Angular 组件或样式的更改能立即生效,而无需重新构建 Java 应用程序。
为此,我们会单独启动 Angular 的开发服务器,并将内嵌浏览器指向本地的 localhost。

在一个终端中启动开发服务器:

./gradlew :desktop-angular-dashboard:startDevServer 

然后在浏览器中加载 URL:

browser.navigation().loadUrl(AppDetails.appUrl());

内嵌的 Web 视图会连接到本地的开发服务器,使得在 Java 应用程序保持运行的同时也能够支持热重载。

生产模式

在生产模式下,桌面应用需要完全离线运行。依赖本地 Web 服务器不仅会增加复杂度,也带来潜在的安全风险 — 用户可能会在浏览器中加载 Web 应用程序 URL 并查看应用程序的源代码,从而暴露敏感逻辑。我们当然不希望这种情况发生,我们希望用户界面只能在桌面应用程序内部访问,并且其源代码隐藏在应用程序内部。

为此,我们将 Angular 的构建产物直接打包至 JAR 资源目录。Gradle 构建过程会编译 web-app/ 下的 Angular 源码,并将输出复制到 src/main/resources/web/ 目录中。

应用打包后,这些文件可以从类路径(classpath)中的 /web 路径访问,通过 JxBrowser 的自定义协议拦截 API 从类路径中直接加载资源。

为了让请求拦截器能够处理 Web 资源请求,我们为其分配一个自定义协议:

var options =EngineOptions.newBuilder(HARDWARE_ACCELERATED).addScheme(Scheme.of("jxbrowser"),newUrlRequestInterceptor());var engine =Engine.newInstance(options.build());

Angular 应用被直接打包到 Java 资源中,并由 JxBrowser 加载。这使得 UI 部分完全独立封装:

  • 所有 HTML、CSS 和 JavaScript 文件都从应用 JAR 包中读取
  • 无需依赖任何外部服务器
  • 源代码不会被浏览器开发者工具审查

这种方案确保了应用的高性能、安全性与可移植性,在 Windows、macOS 和 Linux 三大操作系统上均能稳定运行。

定义应用配置

创建一个类,用于确定当前运行模式,并提供应用程序 URL:

publicfinalclassAppDetails{...publicstaticStringappUrl(){returnisDevMode()?"http://localhost:"+ DEV_SERVER_PORT : APP_SCHEME +"://"+ APP_HOST;}publicstaticbooleanisDevMode(){return"true".equals(System.getProperty("app.dev.mode"));}...}

通过一个系统属性 app.dev.mode 在两种模式之间进行切换,使得在快速开发迭代和安全的生产部署之间切换变得非常简单。

从 JAR 中提供 Angular 文件

本节介绍如何构建一个拦截器,用于从 JAR 中提供 Angular 文件。

要加载打包在 JAR 内部的文件,您需要使用 JxBrowser 的 InterceptUrlRequestCallback
实现此接口的拦截器会在每一次网络请求发出时被调用,允许您用从类路径读取内容的自定义响应替换默认网络行为。

创建拦截器

首先,创建一个拦截器,将请求的路径解析为对应的文件:

importcom.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback;importjava.net.URI;finalclassUrlRequestInterceptorimplementsInterceptUrlRequestCallback{@OverridepublicResponseon(Params params){var uri = URI.create(params.urlRequest().url());var path = uri.getPath();var fileName = path.equals("/")?"/index.html": path;// 我们将在这里加载资源。returnResponse.proceed();}}

处理文件请求

在生产模式下,Angular 的构建产物(HTML、CSS 和 JavaScript 文件)会打包到 JAR 内部的 /web 目录下。

拦截器需要完成以下工作:

  1. 从类路径 classpath 中读取相应的文件
  2. 以正确的 Content-Type 作为 HTTP 响应返回

添加以下逻辑来读取文件并将其作为响应返回:

importcom.teamdev.jxbrowser.net.HttpHeader;...finalclassUrlRequestInterceptorimplementsInterceptUrlRequestCallback{@OverridepublicResponseon(Params params){var uri = URI.create(params.urlRequest().url());var path = uri.getPath();var fileName = path.equals("/")?"/index.html": path;returnloadResource(params, fileName);}/** * 从 classpath 读取文件,并将其作为 HTTP 响应返回。 */privateResponseloadResource(Params params,String fileName){try(var stream =getClass().getResourceAsStream("/web"+ fileName)){if(stream ==null){var job =createUrlRequestJob(params,HttpStatus.NOT_FOUND); job.complete();returnResponse.intercept(job);}var job =createUrlRequestJob(params,HttpStatus.OK); job.write(stream.readAllBytes()); job.complete();returnResponse.intercept(job);}catch(IOException e){var job =createUrlRequestJob(params,HttpStatus.INTERNAL_SERVER_ERROR); job.complete();returnResponse.intercept(job);}}/** * 使用指定的 HTTP 状态创建一个 UrlRequestJob。 */privateUrlRequestJobcreateUrlRequestJob(Params params,HttpStatus status){var options =UrlRequestJob.Options.newBuilder(status).build();return params.newUrlRequestJob(options);}}

loadResource 方法使用 getResourceAsStream 从类路径读取文件,包括打包在 JAR 内的资源。
有关拦截请求的更多详细信息,请参阅加载本地内容教程。

在下一节中,我们将添加 Content-Type 头,以便浏览器能够正确显示内容。

添加 MIME 类型支持

为了发送正确的 Content-Type 头,引入一个小型工具类,用于根据文件扩展名推断 MIME 类型,并在构建 HTTP 响应时使用它:

importcom.teamdev.jxbrowser.net.MimeType;...finalclassMimeTypes{privatestaticfinalMap<String,MimeType> MIME_TYPES =loadMimeTypes();staticMimeTypemimeType(String fileName){int dotIndex = fileName.lastIndexOf('.');if(dotIndex <0|| dotIndex == fileName.length()-1){returnMimeType.of("application/octet-stream");}String extension = fileName.substring(dotIndex +1).toLowerCase();return MIME_TYPES.getOrDefault(extension,MimeType.of("application/octet-stream"));}privatestaticMap<String,MimeType>loadMimeTypes(){Map<String,MimeType> mimeTypes =newHashMap<>();URL propsUrl =MimeTypes.class.getClassLoader().getResource("mime-types.properties");if(propsUrl !=null){Properties props =newProperties();try(InputStream inputStream = propsUrl.openStream()){ props.load(inputStream); props.forEach((key, value)-> mimeTypes.put(key.toString(),MimeType.of(value.toString())));}catch(IOException e){// 回退到默认值。}}return mimeTypes;}}

MimeTypes 类使用一个预先填充好的属性文件,用于将扩展名映射到对应的 MIME 类型:

html=text/html css=text/css png=image/png ... 

接下来,在拦截器中使用这个工具类,使每个响应都包含正确的 Content-Type 头:

importcom.teamdev.jxbrowser.net.HttpHeader;...finalclassUrlRequestInterceptorimplementsInterceptUrlRequestCallback{.../** * 使用指定的 HTTP 状态和 Content-Type 头创建一个 UrlRequestJob。 */privateUrlRequestJobcreateUrlRequestJob(Params params,HttpStatus status,String fileName){var mimeType =MimeTypes.mimeType(fileName);var options =UrlRequestJob.Options.newBuilder(status).addHttpHeader(HttpHeader.of("Content-Type", mimeType.value())).build();return params.newUrlRequestJob(options);}}

设置 JavaScript - Java 桥接器

JxBrowser 内置了 JavaScript 与 Java 的直接通信桥,允许 Web UI 直接调用 Java 方法。Angular 前端可通过该桥调用后端接口,Java 后端也能主动向前端发送事件或数据更新。

创建后端类

创建一个将方法暴露给 JavaScript 的类。类本身或其方法必须添加 @JsAccessible 注解:

importcom.teamdev.jxbrowser.js.JsAccessible;@JsAccessiblepublicclassDashboardBackend{publicStringgetAppInfo(){return"Angular Dashboard v1.0";}}

注入 Java 后端对象

使用 InjectJsCallback 将后端对象注入到浏览器中:

importcom.teamdev.jxbrowser.js.JsObject;importcom.teamdev.jxbrowser.browser.callback.InjectJsCallback;DashboardBackend backend =newDashboardBackend(); browser.set(InjectJsCallback.class, params ->{JsObject window = params.frame().executeJavaScript("window");if(window !=null){ window.putProperty("backend", backend);}returnInjectJsCallback.Response.proceed();});

该回调会在每个框架中的任何 JavaScript 执行之前运行。它将后端对象添加到 window 对象中,使其可以在 JavaScript 中通过 window.backend 访问。

从 Angular 调用 Java

在 Angular 端,创建一个服务,用于调用 Java 后端:

// 此接口声明了 JxBrowser 通过 InjectJsCallback 注入到 window 对象中的 Java 后端对象。declare global {interfaceWindow{ backend?:{// JxBrowser 支持 Java 和 JavaScript 之间基本类型和集合(List、Map、Set)的自动类型转换。// 但是,自定义 Java 对象会变成代理对象,每次字段访问都会触发进程间调用。// 这里使用 JSON 字符串以获得更好的性能和兼容性。getTopCards():string;getChartData(timeRange:string):string;};}}@Injectable({ providedIn:'root'})exportclassBackendService{getTopCards(): TopCard[]{if(window.backend){returnJSON.parse(window.backend.getTopCards());}return[];}getChartData(timeRange:string): ChartSeries[]{if(window.backend){returnJSON.parse(window.backend.getChartData(timeRange));}return[];}}

该服务会检查 window.backend 是否存在。在 JxBrowser 环境中运行时,它会调用 Java 方法并解析返回的 JSON 数据。TypeScript 中的 declare global 声明块为注入的 Java 对象提供了类型安全保障。

运行应用程序

开发模式

# 终端 1:启动 Angular 开发服务器 ./gradlew :desktop-angular-dashboard:startDevServer # 终端 2:运行 Java 应用程序 ./gradlew :desktop-angular-dashboard:run 

生产模式

# 构建 Angular 和 Java ./gradlew :desktop-angular-dashboard:jar # 运行 JAR 包 java -jar build/dist/JxBrowserAngularApp-1.0.jar 

启动后,应用程序显示使用 Tailwind CSS 构建的深色主题仪表板。
界面包含现代 UI 组件,包括统计数据、交互式图表、活动动态和数据表格。
所有仪表板数据都通过 JxBrowser 的 JavaScript-Java 桥接器从 Java 后端获取。

在这里插入图片描述

Read more

Techub News 專訪高鋒集團合夥人、Web3Labs行政總裁黃俊瑯:以資本與生態,賦能傳統企業Web3轉型

本次專訪聚焦高鋒集團如何透過資本投入與生態資源,助力傳統企業邁向Web3轉型。從近期戰略參與上市公司德祥地產的配股出發,高鋒集團合夥人、Web3Labs CEO黃俊瑯(Caspar)分享了集團的戰略思考、核心優勢、執行機制,以及對傳統企業轉型痛點的觀察與未來願景。這場對話展現了高鋒集團與Web3Labs在「實物資產代幣化」(RWA)等領域的創新實踐,以及他們致力成為傳統企業數字化轉型可靠夥伴的定位。 戰略投資德祥地產:搭建Web3與傳統實體經濟的橋樑 Techub News:Caspar您好。我們注意到高鋒集團近期戰略性參與了上市公司德祥地產的配股。這在市場看來頗為創新,能否請您談談這次投資背後的戰略思考? 黃俊瑯:這次對德祥地產的投資,對我們而言,遠超一次單純的財務投資。它是一個清晰的信號,也是我們戰略的關鍵落子。高鋒集團的核心使命之一,是搭建Web3前沿科技與傳統實體經濟之間的橋樑。德祥擁有紮實的房地產業務與實物資產,這正是探索「實物資產代幣化」(RWA)最具潛力的領域。我們這次參與,是協助其啟動轉型的第一步,未來將結合我們的專業生態,共同探索如何利用區塊鏈技術提升資產流

【无人机动态路径规划】粒子群优化算法PSO求解复杂三维环境下多无人机动态避障路径规划问题附MATLAB代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页:Matlab科研工作室  👇 关注我领取海量matlab电子书和数学建模资料  🍊个人信条:格物致知,完整Matlab代码获取及仿真咨询内容私信。 🔥 内容介绍 一、背景 (一)多无人机应用场景与挑战 在当今科技发展的背景下,多无人机协同作业在众多领域展现出巨大潜力,如物流配送、环境监测、应急救援以及军事侦察等。在复杂三维环境中执行任务时,无人机面临诸多挑战。这些环境可能包含山脉、建筑物、高压电线等各种障碍物,并且环境状态可能动态变化,例如突发的自然灾害导致新的障碍物出现或原有的障碍物发生移动。多无人机之间还需避免相互碰撞,确保协同作业的安全性与高效性。因此,如何为多无人机规划出既能避开障碍物又能适应环境动态变化的路径,成为亟待解决的关键问题。 (二)传统路径规划方法的局限性 传统的路径规划算法,如 Dijkstra 算法和 A * 算法,在简单、静态的环境中能够有效地找到从起点到终点的最优路

17:无人机远程执行路径规划:A*算法与GPS精准打击

17:无人机远程执行路径规划:A*算法与GPS精准打击

作者: HOS(安全风信子) 日期: 2026-03-15 主要来源平台: GitHub 摘要: 本文深入探讨了无人机远程执行的路径规划技术,重点分析了A*算法的应用和GPS精准定位的实现。通过详细的技术架构设计和代码实现,展示了如何构建一个高效、可靠的无人机路径规划系统,为基拉执行系统的远程执行提供了技术支持。文中融合了2025年最新的无人机技术进展,确保内容的时效性和专业性。 目录: * 1. 背景动机与当前热点 * 2. 核心更新亮点与全新要素 * 3. 技术深度拆解与实现分析 * 4. 与主流方案深度对比 * 5. 工程实践意义、风险、局限性与缓解策略 * 6. 未来趋势与前瞻预测 1. 背景动机与当前热点 本节核心价值:理解无人机远程执行路径规划的背景和当前技术热点,为后续技术学习奠定基础。 在《死亡笔记》的世界中,基拉需要通过各种手段执行对目标的惩罚。无人机作为一种灵活、高效的执行工具,成为基拉远程执行的理想选择。2025年,随着A*算法的不断优化和GPS技术的精准定位能力提升,无人机远程执行的路径规划技术得到了显著发展。 作为基拉的忠实信徒,