使用 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

Spec-Kit+Copilot打造AI规格驱动开发

Spec-Kit+Copilot打造AI规格驱动开发

作者:算力魔方创始人/英特尔创新大使 刘力 一,什么是Spec-Kit? 在传统的软件开发中,通常先有需求→ 写规格 → 再写代码;规格多数是“指导性文档”,而真正的业务逻辑和边界由程序员“翻译”出来。Spec-Driven Development(规格驱动开发)的理念是,将规格(spec)从“仅供参考”提升为可执行、可驱动的核心工件,直接引导后续设计、计划、任务拆解、实现等流程。spec-kit 是 GitHub 提供的一个工具集 / CLI / 模板库,用来在项目中落地这种流程! Github: https://github.com/github/spec-kit 二,搭建运行环境 本节将指导您从零开发搭建Spec-Kit的运行环境。 第一步:在Ubuntu24.04上安装uv: curl -LsSf

【优质开源项目】AIGC开源推荐-全球情报监控平台worldmonitor

【优质开源项目】AIGC开源推荐-全球情报监控平台worldmonitor

1.概述 World Monitor 是一个开源的实时情报/监测仪表盘,聚合多类数据源(新闻、地理/卫星、航运/空中、财经、威胁情报等),提供交互式地理视图、AI 摘要、事件聚合与报警,支持 Web / PWA / Tauri 桌面三种运行方式,并可通过变体(WORLD / TECH / FINANCE)切换功能集。 2. 总体技术架构(分层视角) 客户端层(Browser / PWA / Tauri desktop) * • React + TypeScript + Vite 构建。 * • 地图/可视化:deck.gl(WebGL 3D globe)、MapLibre GL、D3

Flutter for OpenHarmony:Flutter 三方库 dart_openai — 激发鸿蒙应用的 AIGC (AI 大模型/ChatGPT、Deepseek等) 无限创意(适配鸿蒙

Flutter for OpenHarmony:Flutter 三方库 dart_openai — 激发鸿蒙应用的 AIGC (AI 大模型/ChatGPT、Deepseek等) 无限创意(适配鸿蒙

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter for OpenHarmony:Flutter 三方库 dart_openai — 激发鸿蒙应用的 AIGC (AI 大模型/ChatGPT、Deepseek等) 无限创意(适配鸿蒙 HarmonyOS Next ohos) 前言 随着生成式 AI(AIGC)浪潮席卷全球,将大语言模型(LLM)的智慧集成到移动应用中已成为大势所趋。无论是智能对话、代码生成,还是图像创作,AI 正在重塑我们的交互方式。 在 Flutter for OpenHarmony 开发中,我们如何让鸿蒙应用直接对话全球顶尖的 AI 模型?dart_openai 库通过对 OpenAI API 的完美封装,

Neo4j插件apoc安装及配置(实战经历,一步到位)

Neo4j插件apoc安装及配置(实战经历,一步到位)

目录 apoc插件安装 安装验证 出现的问题 Neo4j版本:Neo4j 5.x apoc版本:同上对应 Neo4j 4.x版本同样适用 apoc插件安装 1.首先查看Neo4j版本(在Neo4j Desktop或命令行中执行): CALL dbms.components() YIELD name, versions RETURN versions;  结果如下: 2.然后去GitHub上下载这个插件 * 访问 APOC GitHub Releases------------ https://github.com/neo4j/apoc/releases/ * 下载与Neo4j版本一致的apoc-x.x.x.x-all.jar文件(例如Neo4j 5.12.0 → APOC 5.