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

FPGA模块如何助力现代工厂实现高速数据采集和实时处理

1. 工业 4.0 背景下的数据挑战 在智能制造的浪潮下,现代工厂正加速从“自动化”向“智能化”迈进。随着传感器部署密度的迅速上升,工厂内部产生的数据量呈几何级增长,涵盖结构化数据(如温度、湿度、压力)与非结构化数据(如图像、视频、音频)等多种类型,对数据采集与处理能力提出了前所未有的挑战: * 实时性要求高:在高速生产线、精密制造与运动控制等场景中,关键数据必须被及时采集与处理,以确保生产过程的高效运行与安全性。这不仅要求系统具备高速采集能力,更要求具备每秒处理百万乃至千万数据点的能力。 * 传输与处理带宽受限:庞大的原始数据若未经处理直接上传至数据中心或云端,将对网络带宽造成巨大负担,且传输延迟难以控制,极易影响系统响应速度和可靠性。 * 多协议兼容的复杂性:现代工厂常用的工业以太网、CAN、Profibus 等通信协议并存,系统需兼容上百种协议并实现无缝对接,大大增加了系统集成的复杂性。 2. FPGA 技术的核心优势 传统处理器架构逐渐难以胜任智能制造的核心需求。FPGA(现场可编程门阵列)凭借其强大的并行处理能力、毫秒级低延迟响应以及灵活可重构的架构,

Clawdbot(Moltbot) 飞书机器人配置,体验老板和助手沟通的感觉

Clawdbot(Moltbot) 飞书机器人配置,体验老板和助手沟通的感觉

一、背景说明 Clawdbot可以24小时待命(参考配置方式:Clawdbot(Moltbot) windows安装配置教程(含各种问题处理)),但是网页端使用起来比毕竟没那么方便,然而clawdbot支持多种渠道交互,这也正是这个AI助理的魅力所在,想想飞书发送一个消息,一个任务就完成了,这不就是老板指挥我做事的方式吗,来赶紧体验一波老板的感觉~ 二、飞书机器人创建 飞书开放平台构建机器人:https://open.feishu.cn/ 记录App ID 和 App Secret,一会要用: 三、自动安装插件 项目地址:https://github.com/m1heng/Clawdbot-feishu 这时候,就可以发挥clawdbot的能力了,直接让clawdbot给我安装: 我要安装飞书机器人,帮我按照这个命令安装:Clawdbot plugins install @m1heng-clawd/feishu 到这个过程有点慢,安装了好一会没反应,我开始问了: 又过了好一会没反应,

比 OpenClaw 轻 99%!我用 nanobot 搭了个 QQ AI 机器人,还顺手贡献了代码

❝ 4000 行代码,打造你的私人 AI 助手❞ 前言 最近 AI Agent 领域有个项目特别火——「OpenClaw」,它是一个功能强大的 AI 助手框架,能让你拥有一个 7×24 小时在线的智能助理。 但当我 clone 下来准备研究时,发现它有 「43 万行代码」!对于想快速上手或做二次开发的个人开发者来说,这个体量实在太重了。 直到我发现了它的"轻量版"——「nanobot」。 nanobot:99% 的瘦身,核心功能全保留 nanobot 来自香港大学数据科学实验室(HKUDS),它的设计理念很简单: ❝ 用最少的代码,实现 AI Agent 的核心能力❞ 来看一组对比数据: 项目 代码行数 核心功能 OpenClaw 430,

FPGA入门指南:从点亮第一颗LED开始(手把手教程)

FPGA入门指南:从点亮第一颗LED开始(手把手教程)

文章目录 * 一、到底啥是FPGA?(电子工程师的乐高) * 二、开发环境搭建(Vivado安装避坑指南) * 1. 安装包获取 * 2. 硬件准备(别急着买开发板!) * 3. 第一个工程创建 * 三、Verilog速成秘籍(记住这10个关键词) * 四、实战:LED流水灯(代码+仿真+烧录) * 1. 代码实现(带注释版) * 2. 仿真测试(Modelsim技巧) * 3. 上板验证(真实硬件操作) * 五、学习路线图(避免走弯路!) * 阶段一:数字电路基础 * 阶段二:Verilog进阶 * 阶段三:实战项目 * 推荐学习资源: * 六、新手常见坑点(血泪经验) 一、到底啥是FPGA?(电子工程师的乐高) 刚接触硬件的同学可能会懵:这货和单片机有啥区别?