玩酷网

为何推荐 java-bridge 来让 Node.js 调用 Java 更丝滑?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

1. 什么是 java-bridge

java-bridge 是使用 napi-rs 构建的 Node.js 程序与 Java API 之间的桥梁,而后者是通过 Node-API 在 Rust 中构建已编译 Node.js 插件的框架。java-bridge 接口也使用 Rust 编写,旨在提供两种语言之间快速且内存安全的接口。

A bridge between Node.js programs and Java APIs written in Rust using napi-rs to provide a fast and memory-safe interface between the two languages.

java-bridge 将提供预编译的二进制文件,用户只需在计算机上安装 Java 运行时环境 (JRE) 即可使用此软件包。与其他 node.js 到 Java 的接口不同, 该二进制并非硬链接到编译时使用的 JDK,而是在程序首次启动时动态加载 JVM 原生库。

目前 napi-rs 在 Github 通过 MIT 协议开源,有超过 6.6k 的 star、0.3k 的 fork,是一个值得关注的前端开源项目。

2. 如何使用 java-bridge2.1 创建 JVM

开发者可以使用 EnsureJvm 方法创建一个新的 Java VM。在 JVM 创建后调用此方法不会执行任何操作,而且目前尚不支持手动销毁 JVM。

当开发者不使用任何参数创建 JVM 时,其会首先在系统上搜索合适的 JVM 原生库,然后启动 JVM。比如下面的示例:

npm i java-bridgeimport {ensureJvm} from "java-bridge";ensureJvm();

当然,开发者也可以在创建 jvm 时传递配置,例如:特定的 jvm 版本、指定 jvm 本机库的位置或向 jvm 传递额外的参数。

import {ensureJvm, JavaVersion} from "java-bridge";ensureJvm({ libPath: "path/to/jvm.dll", // 本地库地址 version: JavaVersion.VER_9, // jvm 版本 opts: ["-Xms512m", "-Xmx512m"],});

所有线程都将作为守护线程连接,允许 JVM 在主线程退出时退出,而且此行为无法更改。

同时值得一提的是,如果在 Electron 应用中使用,开发者可以将此包以及适用于平台的二进制文件解压到 app.asar.unpacked 文件夹中。使用 Electron-builder 时,可以通过在 package.json 中添加以下内容:

{ "build": { // 添加构建配置 "asarUnpack": [ "node_modules/java-bridge/**", "node_modules/java-bridge-*/**" ] }}

当然,还应该在创建 jvm 时将 isPackagedElectron 设置为 true:

ensureJvm({ isPackagedElectron: true,});2.2 将 JAR 注入到类路径

为了将自己的类导入到 Node 环境,开发者需要将 JAR 文件添加到类路径,可以使用 appendClasspath 或path.append 方法来实现。

加载 JAR 后,可以像从 JVM 导入任何其他类一样,使用 importClass 或 importClassAsync 从中导入类。

import {appendClasspath} from "java-bridge";// 添加单个 jar 到类路径appendClasspath("/path/to/jar.jar");// 添加多个 jar 到类路径appendClasspath(["/path/to/jar1.jar", "/path/to/jar2.jar"]);///////// 或者使用path.append 方法import {classpath} from "java-bridge";// Append a single jar to the pathclasspath.append("/path/to/jar.jar");2.3 同步调用

如果想以同步方式使用 Java API,可以使用此模块的同步 API 版本。

在该版本中,任何对 Java API 的调用都将在与 Node 进程相同的线程中执行,因此可能会导致程序挂起,直到执行完成。但是,与异步 API 相比,该调用速度更快,因为无需创建额外的线程附加到 JVM。

import {importClass} from "java-bridge";// 导入const JString = importClass("java.lang.String");// 创建类实例const str = new JString("Hello World");// 调用实例方法str.lengthSync();// 11// Supported native types will be automatically converted// to the corresponding type in the other languagestr.toStringSync();// 输出'Hello World'

下面的示例还导入了 java.util.ArrayList 的类并创建实例:

import {importClass, config} from 'java-bridge';// 使用自定义配置导入 java.util.ArrayListconst ArrayList = importClass('java.util.ArrayList', { syncSuffix: '', asyncSuffix: 'Async',});// 创建 ArrayList 的新实例const list = new ArrayList();// 调用异步方法await list.addAsync('Hello World!');// 调用同步方法list.add('Hello World!');2.4 异步调用

如果想以异步方式使用 Java API,可以使用此模块的异步 API。任何对 Java API 的调用都将在单独的线程中执行,并且执行不会阻塞当前程序,其通常比同步 API 慢得多,但可以使程序运行更流畅。

要异步导入类,开发者可以使用 importClassAsync 函数。

import {importClassAsync} from "java-bridge";// 异步导入 java 的原生方法const JString = await importClassAsync("java.lang.String");// 使用 newInstanceAsync 异步创建新实例const str = await JString.newInstanceAsync("Hello World");// 异步调用方法await str.length(); // 11await str.toString(); // 'Hello World'2.5 抛出错误

Java 进程中抛出的错误会以 JavaError 对象的形式返回,该对象包含错误消息、完整的堆栈跟踪(包括: Java、Node 和 Rust 进程)以及导致错误的 Java 可抛出对象。

只有当错误是在 Java 进程中抛出而非在 Node 进程中抛出,并且调用是同步调用时,可抛出对象才可用。可以使用 JavaError 对象的 cause 属性访问可抛出对象。

import type {JavaError} from 'java-bridge';try { // 调用一个抛出错误的方法 someInstance.someMethodSync();} catch (e: unknown) { const throwable = (e as JavaError).cause; throwable.printStackTraceSync();}

如果要从异步调用中获取 Java 可抛出异常,则需要在导入类之前或导入时启用 asyncJavaExceptionObjects 配置,比如下面的示例:

import {importClass} from 'java-bridge';const SomeClass = importClass('path.to.SomeClass', { asyncJavaExceptionObjects: true, // 一定要启动 asyncJavaExceptionObjects 配置});try { await SomeClass.someMethod();} catch (e: unknown) { const throwable = (e as JavaError).cause; throwable.printStackTraceSync();}参考资料

https://github.com/MarkusJx/node-java-bridge

https://markusjx.github.io/node-java-bridge/functions/ensureJvm.html

https://github.com/napi-rs/napi-rs

https://markusjx.github.io/node-java-bridge/functions/importClassAsync.html

https://blog.scottlogic.com/2019/04/05/jumping-from-java-to-javascript.html