- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
Java 互操作性
本文档介绍了如何启用与 Java 的互操作性,以及 JavaScript 到 Java 的嵌入场景。
启用 Java 互操作性 #
从 GraalVM for JDK 21 开始,所有必要的构件都可以直接从 Maven Central 下载。所有与嵌入者相关的构件都可以在 Maven 依赖组 org.graalvm.polyglot
中找到。了解更多关于依赖设置的信息,请参阅入门指南。
多语言上下文 #
在 Java 中嵌入 JavaScript 的首选方法是通过 Context
。为此,可以使用 hostAccess
选项构建一个新的 org.graalvm.polyglot.Context
以允许访问,并使用 hostClassLookup
谓词定义允许访问的 Java 类。
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 对象 #
Java 对象可以使用 JavaScript 的 new
关键字构造。
var FileClass = Java.type('java.io.File');
var file = new FileClass("myFile.md");
字段和方法访问 #
Java 类的静态字段或 Java 对象的字段可以像 JavaScript 属性一样访问。
var JavaPI = Java.type('java.lang.Math').PI;
Java 方法可以像 JavaScript 函数一样调用。
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 不同,Nashorn 允许额外的空格和简单名称。在上面的例子中,即使 foo(short)
可以通过无损转换(foo(1)
)到达,您可能也希望始终调用例如 foo(long)
。
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;
Map 访问 #
在 GraalJS 中,您可以创建和访问 Java Map,例如 java.util.HashMap
。
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put(1, "a");
map.get(1);
GraalJS 支持类似于 Nashorn 迭代此类 Map。
for (var key in map) {
print(key);
print(map.get(key));
}
List 访问 #
在 GraalJS 中,您可以创建和访问 Java List,例如 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"
实际上是无法区分的。
迭代属性 #
Java 类和 Java 对象的属性(字段和方法)可以使用 JavaScript 的 for..in
循环进行迭代。
var m = Java.type('java.lang.Math')
for (var i in m) { print(i); }
> E
> PI
> abs
> sin
> ...
从 Java 访问 JavaScript 对象 #
JavaScript 对象作为 com.oracle.truffle.api.interop.java.TruffleMap
的实例暴露给 Java 代码。此类实现了 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());
}
Promises #
GraalJS 支持 JavaScript Promise
对象与 Java 之间的互操作性。Java 对象可以作为 thenable 对象暴露给 JavaScript 代码,从而允许 JavaScript 代码 await
Java 对象。此外,JavaScript Promise
对象是常规的 JavaScript 对象,可以使用本文档中描述的机制从 Java 访问。这使得当 JavaScript Promise 被解决或拒绝时,Java 代码可以从 JavaScript 获得回调。
创建可从 Java 解析的 JavaScript Promise 对象 #
JavaScript 应用程序可以创建 Promise
对象,将 Promise
实例的解析委托给 Java。这可以通过在 JavaScript 中使用 Java 对象作为 JavaScript Promise
的“executor”函数来实现。例如,实现以下函数式接口的 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 对象作为 thenable 对象暴露给 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 Promises #
在 JavaScript 中创建的 Promise
对象可以像任何其他 JavaScript 对象一样暴露给 Java 代码。Java 代码可以像普通的 Value
对象一样访问这些对象,并可以通过使用 Promise
的默认 then()
和 catch()
函数注册新的 Promise 解析函数。例如,以下 Java 代码注册了一个 Java 回调,当 JavaScript Promise 解析时执行:
Value jsPromise = context.eval(ID, "Promise.resolve(42);");
Consumer<Object> javaThen = (value)
-> System.out.println("Resolved from JavaScript: " + value);
jsPromise.invokeMember("then", javaThen);
更详细的用法示例可在 GraalJS 单元测试中找到。
多线程 #
GraalJS 与 Java 结合使用时支持多线程。有关 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
模式下,您还可以使用接口或抽象类的类型对象上的 new
运算符来扩展接口和抽象类。
// --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