- 适用于 JDK 23 的 GraalVM(最新)
- 适用于 JDK 24 的 GraalVM(抢先体验)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发版本
Java 互操作性
本文档展示了如何启用与 Java 的互操作性以及可能的 JavaScript 到 Java 嵌入场景。
启用 Java 互操作性 #
从适用于 JDK 21 的 GraalVM 开始,所有必要的工件都可以直接从 Maven Central 下载。所有与嵌入器相关的工件都可以在 Maven 依赖项组 org.graalvm.polyglot
中找到。在 入门指南 中详细了解依赖项设置。
多语言上下文 #
在 Java 中嵌入 JavaScript 的首选方法是通过 Context
。为此,使用 hostAccess
选项允许访问和 hostClassLookup
谓词定义允许访问的 Java 类来构建新的 org.graalvm.polyglot.Context
。
Context context = Context.newBuilder("js")
.allowHostAccess(HostAccess.ALL)
//allows access to all Java classes
.allowHostClassLookup(className -> true)
.build();
context.eval("js", jsSourceCode);
请参阅 嵌入语言指南,了解如何从 Java 主机应用程序与 JavaScript 等客语言交互。
ScriptEngine(JSR 223) #
在 GraalVM JDK 上运行的 JavaScript 与 JSR 223 完全兼容,并支持 ScriptEngine API
。在内部,GraalVM 的 JavaScript ScriptEngine 会包装一个多语言 Context
实例。
ScriptEngine eng = new ScriptEngineManager()
.getEngineByName("graal.js");
Object fn = eng.eval("(function() { return this; })");
Invocable inv = (Invocable) eng;
Object result = inv.invokeMethod(fn, "call", fn);
有关如何从 GraalJS 使用它的更多详细信息,请参阅 ScriptEngine 指南。
从 JavaScript 访问 Java #
GraalVM 提供了一组功能,允许从 JavaScript
到 Java
的互操作性。虽然 Rhino、Nashorn 和 GraalJS 拥有大部分可比的整体功能集,但它们在语法和部分语义上有所不同。
类访问 #
要访问 Java 类,GraalJS 支持 Java.type(typeName)
函数。
var FileClass = Java.type('java.io.File');
如果允许主机类查找(allowHostClassLookup
),则默认情况下 java
全局属性可用。访问例如 java.io.File
的现有代码应改写为使用 Java.type(name)
函数。
// GraalJS (and Nashorn) compliant syntax
var FileClass = Java.type("java.io.File");
// Backwards-compatible syntax
var FileClass = java.io.File;
GraalJS 为兼容性提供了 Packages
、java
和类似的全局属性。但是,出于两个原因,尽可能显式地使用 Java.type
访问所需的类是首选。
- 它在一个步骤中解析类,而不是尝试将每个属性解析为类。
- 如果找不到类或无法访问类,
Java.type
会立即抛出TypeError
,而不是静默地将未解析的名称视为包。
js.java-package-globals
标志可用于停用 Java 包的全局字段(设置为 false
以避免创建字段;默认值为 true
)。
构造 Java 对象 #
可以使用 JavaScript 的 new
关键字构造 Java 对象。
var FileClass = Java.type('java.io.File');
var file = new FileClass("myFile.md");
字段和方法访问 #
可以像 JavaScript 属性一样访问 Java 类的静态字段或 Java 对象的字段。
var JavaPI = Java.type('java.lang.Math').PI;
可以像 JavaScript 函数一样调用 Java 方法。
var file = new (Java.type('java.io.File'))("test.md");
var fileName = file.getName();
方法参数的转换 #
JavaScript 被定义为在 double
数字类型上运行。出于性能原因,GraalJS 在内部可能使用其他 Java 数据类型(例如,int
类型)。
调用 Java 方法时,可能需要进行值转换。当 Java 方法期望一个 long
参数而 GraalJS 提供一个 int
时会发生这种情况(类型扩展
)。如果此转换导致有损转换,则会抛出 TypeError
。
//Java
void longArg (long arg1);
void doubleArg (double arg2);
void intArg (int arg3);
//JavaScript
javaObject.longArg(1); //widening, OK
javaObject.doubleArg(1); //widening, OK
javaObject.intArg(1); //match, OK
javaObject.longArg(1.1); //lossy conversion, TypeError!
javaObject.doubleArg(1.1); //match, OK
javaObject.intArg(1.1); //lossy conversion, TypeError!
注意参数值必须适合参数类型。您可以使用自定义 目标类型映射 覆盖此行为。
方法选择 #
Java 允许通过参数类型重载方法。从 JavaScript 调用 Java 时,会选择最窄的可用类型(实际参数可以在不造成损失的情况下转换为该类型)的方法。
//Java
void foo(int arg);
void foo(short arg);
void foo(double arg);
void foo(long arg);
//JavaScript
javaObject.foo(1); // will call foo(short);
javaObject.foo(Math.pow(2,16)); // will call foo(int);
javaObject.foo(1.1); // will call foo(double);
javaObject.foo(Math.pow(2,32)); // will call foo(long);
要覆盖此行为,可以使用 javaObject['methodName(paramTypes)']
语法选择显式方法重载。参数类型需要用逗号分隔,没有空格,对象类型需要完全限定(例如,'get(java.lang.String,java.lang.String[])'
)。请注意,这与允许额外的空格和简单名称的 Nashorn 不同。在上面的示例中,人们可能总是想要调用例如 foo(long)
,即使可以使用无损转换(foo(1)
)来访问 foo(short)
。
javaObject['foo(int)'](1);
javaObject['foo(long)'](1);
javaObject['foo(double)'](1);
请注意,参数值仍然必须适合参数类型。您可以使用自定义 目标类型映射 覆盖此行为。
当方法重载不明确并且无法自动解析以及当您想要覆盖默认选择时,显式方法选择也很有用。
//Java
void sort(List<Object> array, Comparator<Object> callback);
void sort(List<Integer> array, IntBinaryOperator callback);
void consumeArray(List<Object> array);
void consumeArray(Object[] array);
//JavaScript
var array = [3, 13, 3, 7];
var compare = (x, y) => (x < y) ? -1 : ((x == y) ? 0 : 1);
// throws TypeError: Multiple applicable overloads found
javaObject.sort(array, compare);
// explicitly select sort(List, Comparator)
javaObject['sort(java.util.List,java.util.Comparator)'](array, compare);
// will call consumeArray(List)
javaObject.consumeArray(array);
// explicitly select consumeArray(Object[])
javaObject['consumeArray(java.lang.Object[])'](array);
请注意,目前没有办法显式选择构造函数重载。GraalJS 的未来版本可能会取消该限制。
包访问 #
GraalJS 提供了一个 Packages
全局属性。
> Packages.java.io.File
JavaClass[java.io.File]
数组访问 #
GraalJS 支持从 JavaScript 代码创建 Java 数组。Rhino 和 Nashorn 建议的两种模式都受支持。
//Rhino pattern
var JArray = Java.type('java.lang.reflect.Array');
var JString = Java.type('java.lang.String');
var sarr = JArray.newInstance(JString, 5);
// Nashorn pattern
var IntArray = Java.type("int[]");
var iarr = new IntArray(5);
创建的数组是 Java 类型,但可以在 JavaScript 代码中使用。
iarr[0] = iarr[iarr.length] * 2;
地图访问 #
在 GraalJS 中,您可以创建和访问 Java 地图,例如 java.util.HashMap
。
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put(1, "a");
map.get(1);
GraalJS 支持像 Nashorn 一样迭代此类地图。
for (var key in map) {
print(key);
print(map.get(key));
}
列表访问 #
在 GraalJS 中,您可以创建和访问 Java 列表,例如 java.util.ArrayList
。
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
list.add(42);
list.add("23");
list.add({});
for (var idx in list) {
print(idx);
print(list.get(idx));
}
字符串访问 #
GraalJS 可以使用 Java 互操作性创建 Java 字符串。可以使用 length
属性查询字符串的长度(请注意,length
是一个值属性,不能作为函数调用)。
var javaString = new (Java.type('java.lang.String'))("Java");
javaString.length === 4;
请注意,GraalJS 在内部使用 Java 字符串来表示 JavaScript 字符串,因此上面的代码和 JavaScript 字符串文字 "Java"
实际上是不可区分的。
迭代属性 #
可以使用 JavaScript for..in
循环迭代 Java 类和 Java 对象的属性(字段和方法)。
var m = Java.type('java.lang.Math')
for (var i in m) { print(i); }
> E
> PI
> abs
> sin
> ...
从 Java 访问 JavaScript 对象 #
JavaScript 对象对 Java 代码公开为 com.oracle.truffle.api.interop.java.TruffleMap
的实例。此类实现了 Java 的 Map
接口。
JavaImporter #
JavaImporter
功能仅在 Nashorn 兼容模式(使用 js.nashorn-compat
选项)下可用。
Java 类和 Java 对象的控制台输出 #
GraalJS 提供了 print
和 console.log
。
GraalJS 提供了一个与 Nashorn 兼容的 print
内置函数。
console.log
由 Node.js 直接提供。它不提供对互操作对象的特殊处理。请注意,GraalJS 上 console.log
的默认实现只是 print
的别名,而 Node 的实现仅在 Node.js 上运行时可用。
异常 #
在 Java 代码中抛出的异常可以在 JavaScript 代码中捕获。它们表示为 Java 对象。
try {
Java.type('java.lang.Class')
.forName("nonexistent");
} catch (e) {
print(e.getMessage());
}
承诺 #
GraalJS 支持 JavaScript Promise
对象和 Java 之间的互操作性。Java 对象可以作为可 then 对象公开给 JavaScript 代码,允许 JavaScript 代码 await
Java 对象。此外,JavaScript Promise
对象是常规的 JavaScript 对象,可以使用本文档中描述的机制从 Java 访问它们。这允许在 JavaScript 承诺解决或拒绝时从 JavaScript 回调 Java 代码。
创建可以从 Java 解决的 JavaScript Promise
对象 #
JavaScript 应用程序可以创建 Promise
对象,并将 JavaScript Promise
实例的解决委托给 Java。这可以通过从 JavaScript 使用 Java 对象作为 JavaScript Promise
的 “执行器” 函数来实现。例如,实现以下函数接口的 Java 对象可以用于创建新的 Promise
对象。
@FunctionalInterface
public interface PromiseExecutor {
void onPromiseCreation(Value onResolve, Value onReject);
}
任何实现 PromiseExecutor
的 Java 对象都可以用来创建一个 JavaScript Promise
。
// `javaExecutable` is a Java object implementing the `PromiseExecutor` interface
var myPromise = new Promise(javaExecutable).then(...);
JavaScript Promise
对象不仅可以使用函数接口创建,还可以使用任何其他可以由 GraalJS 执行的 Java 对象创建(例如,任何实现 Polyglot ProxyExecutable 接口的 Java 对象)。GraalJS 单元测试 中提供了更详细的示例用法。
将 await
与 Java 对象一起使用 #
JavaScript 应用程序可以使用 await
表达式与 Java 对象一起使用。这在 Java 和 JavaScript 必须与异步事件交互时很有用。要将 Java 对象作为可 then 对象公开给 GraalJS,Java 对象应实现一个名为 then()
的方法,该方法具有以下签名。
void then(Value onResolve, Value onReject);
当 await
与实现 then()
的 Java 对象一起使用时,GraalJS 会将该对象视为 JavaScript Promise
。onResolve
和 onReject
参数是可执行的 Value
对象,Java 代码应使用它们来恢复或中止与相应 Java 对象关联的 JavaScript await
表达式。GraalJS 单元测试 中提供了更详细的示例用法。
从 Java 使用 JavaScript 承诺 #
在 JavaScript 中创建的 Promise
对象可以像任何其他 JavaScript 对象一样公开给 Java 代码。Java 代码可以像普通 Value
对象一样访问此类对象,并可以使用 Promise
的默认 then()
和 catch()
函数注册新的承诺解决函数。例如,以下 Java 代码注册一个 Java 回调,在 JavaScript 承诺解决时执行。
Value jsPromise = context.eval(ID, "Promise.resolve(42);");
Consumer<Object> javaThen = (value)
-> System.out.println("Resolved from JavaScript: " + value);
jsPromise.invokeMember("then", javaThen);
GraalJS 单元测试 中提供了更详细的示例用法。
多线程 #
当与 Java 结合使用时,GraalJS 支持多线程。有关 GraalJS 多线程模型的更多详细信息,请参阅多线程 文档。
扩展 Java 类 #
GraalJS 提供使用 Java.extend
函数扩展 Java 类和接口的支持。请注意,此功能需要在 多语言环境 中启用主机访问才能使用。
Java.extend #
Java.extend(types...)
返回一个生成的适配器 Java 类对象,该对象扩展了指定的 Java 类和/或接口。例如
var Ext = Java.extend(Java.type("some.AbstractClass"),
Java.type("some.Interface1"),
Java.type("some.Interface2"));
var impl = new Ext({
superclassMethod: function() {/*...*/},
interface1Method: function() {/*...*/},
interface2Method: function() {/*...*/},
toString() {return "MyClass";}
});
impl.superclassMethod();
可以通过 Java.super(adapterInstance)
调用超级方法。请参阅一个组合示例
var sw = new (Java.type("java.io.StringWriter"));
var FilterWriterAdapter = Java.extend(Java.type("java.io.FilterWriter"));
var fw = new FilterWriterAdapter(sw, {
write: function(s, off, len) {
s = s.toUpperCase();
if (off === undefined) {
fw_super.write(s, 0, s.length)
} else {
fw_super.write(s, off, len)
}
}
});
var fw_super = Java.super(fw);
fw.write("abcdefg");
fw.write("h".charAt(0));
fw.write("**ijk**", 2, 3);
fw.write("***lmno**", 3, 4);
print(sw); // ABCDEFGHIJKLMNO
请注意,在 nashorn-compat
模式下,您还可以使用接口或抽象类的类型对象上的新运算符扩展接口和抽象类
// --experimental-options --js.nashorn-compat
var JFunction = Java.type('java.util.function.Function');
var sqFn = new JFunction({
apply: function(x) { return x * x; }
});
sqFn.apply(6); // 36