- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
与 Truffle 语言的互操作性
Espresso 使您能够与其他“Truffle”语言(其解释器通过 Truffle 框架 实现的语言)交互,以创建多语言程序——即使用多种语言编写的程序。
本指南介绍了如何加载用其他语言编写的代码,如何在语言之间导出和导入对象,如何从外部语言使用 Espresso 对象,如何从 Espresso 使用外部对象,以及如何将其嵌入到 Java 应用程序中。
为避免混淆,使用术语 host(主机)和 guest(访客)来区分 Java 执行的不同层。Espresso 指的是访客层。
您将多语言选项传递给 java -truffle
启动器。如果您使用的是原生配置,则需要使用 --polyglot
标志来访问其他语言。
外部对象在流入 Espresso 时必须“寄居”在访客 Java 类型中。此类型如何附加到外部对象是实现细节。
多语言 #
Espresso 提供了访客 Java 多语言 API,在 polyglot.jar
中有所描述。此 JAR 文件会自动注入到访客 Java 上下文中,但可以使用 --java.Polyglot=false
排除。
您可以导入 Polyglot
类以与其他访客语言交互
// guest java
import com.oracle.truffle.espresso.polyglot.Polyglot;
int two = Polyglot.eval(int.class, "js", "1+1");
您可以确定一个对象是否为外部对象
// guest java
Object foreign = Polyglot.eval("js", "[2, 0, 2, 1]");
Object local = new int[]{2, 0, 2, 1};
System.out.println(Polyglot.isForeignObject(foreign)); // prints true
System.out.println(Polyglot.isForeignObject(local)); // prints false
您可以将外部对象转换为访客 Java 类型
// guest java
Object foreignArray = Polyglot.eval("js", "['a string', 42, 3.14159, null]");
Object[] objects = Polyglot.cast(Object[].class, foreignArray);
assert objects.length == 4;
String elem0 = Polyglot.cast(String.class, objects[0]); // eager conversion
Integer elem1 = Polyglot.cast(Integer.class, objects[1]); // preserves identity
int elem1_ = Polyglot.cast(int.class, objects[1]); // eager conversion
double elem2 = Polyglot.cast(double.class, objects[2]); // eager conversion
Object elem3 = objects[3];
assert elem3 == null;
Polyglot.cast(targetClass, obj)
方法是一个增强的 Java 强制类型转换,例如 targetClass.cast(obj)
- Java 强制类型转换成功 ⇒
Polyglot.cast
成功。 - 如果 Java 强制类型转换不成功,
Polyglot.cast
可以“重新类型化”外部对象,例如,要转换为Integer
,外部对象必须fitsInInt
。 - 如果
Polyglot.cast
失败,它将抛出ClassCastException
,类似于Class#cast
。
Polyglot.cast
支持从常见互操作“种类”到 Java 类型的自然映射,概述如下
互操作“种类” | 允许的类型 | 保留标识 |
---|---|---|
isBoolean | Boolean/boolean | 是*(封装类型) |
fitsInByte | Byte/byte | 是*(封装类型) |
fitsInShort | Short/short | 是*(封装类型) |
fitsInInt | Integer/int | 是*(封装类型) |
fitsInLong | Long/long | 是*(封装类型) |
fitsInFloat | Float/float | 是*(封装类型) |
fitsInDouble | Double/double | 是*(封装类型) |
isString & 1-character | Character/char | 是*(封装类型) |
isString | String | 否(急切转换) |
isException & Polyglot.isForeignObject | ForeignException | 是 |
hasArrayElements | Object[] | 是 |
isNull | * | 是 |
* | Object | 是 |
您可以访问多语言绑定
// guest java
Object foreignObject = Polyglot.importObject("foreign_object");
// typed imports
String userName = Polyglot.importObject("user_name", String.class);
int year = Polyglot.importObject("year", int.class);
// exports
Polyglot.exportObject("data", new double[]{56.77, 59.23, 55.67, 57.50, 64.44, 61.37);
Polyglot.exportObject("message", "Hello, Espresso!");
互操作协议 #
Espresso 提供了一个显式的访客 API 来访问 互操作协议。它包含模拟互操作协议消息的方法。此 API 也可以用于访客 Java 对象。
// guest java
import com.oracle.truffle.espresso.polyglot.Interop;
Object foreignArray = Polyglot.eval("js", "[2, 0, 2, 1]");
System.out.println(Interop.hasArrayElements(foreignArray)); // prints true
System.out.println(Interop.getArraySize(foreignArray)); // prints 4
Object elem0 = Interop.readArrayElement(foreignArray, 0);
System.out.println(Interop.fitsInInt(elem0)); // prints true
System.out.println(Interop.asInt(elem0)); // prints 2
嵌入到主机 Java 中 #
Espresso 通过 Polyglot API 嵌入,该 API 是 GraalVM 的一部分。
// host java
import org.graalvm.polyglot.*;
class Embedding {
public static void main(String[] args) {
Context polyglot = Context.newBuilder().allowAllAccess(true).build();
// Class loading is exposed through language bindings, with class
// names using the same format as Class#forName(String).
Value intArray = polyglot.getBindings("java").getMember("[I");
Value objectArray = polyglot.getBindings("java").getMember("[Ljava.lang.Object;")
Value java_lang_Math = polyglot.getBindings("java").getMember("java.lang.Math");
double sqrt2 = java_lang_Math.invokeMember("sqrt", 2).asDouble();
double pi = java_lang_Math.getMember("PI").asDouble();
System.out.println(sqrt2);
System.out.println(pi);
}
}
可以使用 contextBuilder.option(key, value)
设置许多有用的上下文选项
- 可以通过将
java.Properties.property.name
设置为所需值来添加 Java 属性(在此情况下,这将设置property.name
)。 java.Properties.java.class.path
可用于设置 Truffle 上下文中的 Java 类路径。java.Properties.java.library.path
可用于设置 Truffle 上下文中的 Java 原生库路径。java.EnableAssertions
可以设置为true
以启用断言。java.EnableSystemAssertions
可以设置为true
以在 Java 标准库中启用断言。java.Verify
可以设置为none
、remove
或all
,以控制是否不进行字节码验证、仅对用户代码进行验证,或对所有类进行验证。java.JDWPOptions
可以用于设置和启用通过 JDWP 进行调试。例如,它可以设置为transport=dt_socket,server=y,address=localhost:8000,suspend=y
。java.Polyglot
可以设置为true
或false
,以允许或拒绝访问com.oracle.truffle.espresso.polyglot
包中的多语言特性。java.PolyglotTypeConverters
可以用于声明一个类型转换函数,将元限定名称映射到类型转换器类。请参阅下面专门章节中的更多详细信息。java.PolyglotInterfaceMappings
可以设置为以分号分隔的 1:1 接口类型映射列表,以便为实现列表中声明接口的主机对象自动构建访客代理。请参阅下面专门章节中的更多详细信息。
*Espresso 不支持评估(.eval
)Java 源代码。
在 Java 中,方法可以重载,例如,多个方法可以共享相同的名称,但具有不同的签名。为了消除歧义,Espresso 允许以 methodName/methodDescriptor
的形式指定方法描述符。
// host java
Value java_lang_String = polyglot.getBindings("java").getMember("java.lang.String");
// String#valueOf(int)
String valueOf = String.format("%s/%s", "valueOf", "(I)Ljava/lang/String;");
Value fortyTwo = java_lang_String.invokeMember(valueOf, 42);
assert "42".equals(fortyTwo.asString());
Class<?> 实例 vs. 静态类访问器 (Klass)
静态类访问器允许访问(公共)静态字段和调用(公共)静态方法。
// Class loading through language bindings return the static class accessor.
Value java_lang_Number = polyglot.getBindings("java").getMember("java.lang.Number");
Value java_lang_Class = polyglot.getBindings("java").getMember("java.lang.Class");
// Class#forName(String) returns the Class<Integer> instance.
Value integer_class = java_lang_Class.invokeMember("forName", "java.lang.Integer");
// Static class accessor to Class<?> instance and vice versa.
assert integer_class.equals(java_lang_Integer.getMember("class"));
assert java_lang_Integer.equals(integer_class.getMember("static"));
// Get Integer super class.
assert java_lang_Number.equals(java_lang_Integer.getMember("super"));
使用类型转换器将主机对象转换为访客类型 #
Espresso 内置支持声明主机对象到适当访客类型对象的类型转换。这是通过上面描述的上下文构建器选项完成的。主要思想是允许对象从主机到访客的透明流动,而无需在主机对象进入嵌入式 Espresso 上下文时执行访客类型检查。具体来说,可以设置以下选项来控制嵌入式上下文的类型转换
java.PolyglotTypeConverters
此选项优先于 java.PolyglotInterfaceMappings
,因此,如果定义了专门的类型转换器函数,则不会生成其他自动接口映射代理 Espresso。
注意:声明的类型转换器必须实现位于 polyglor.jar
中 com.oracle.truffle.espresso.polyglot
包的 GuestTypeConversion
接口。
package com.oracle.truffle.espresso.polyglot;
public interface GuestTypeConversion<T> {
T toGuest(Object polyglotInstance);
}
对于每个声明的类型转换器,请使用一个选项调用,如下所示
// host java
Context polyglot = Context.newBuilder().allowAllAccess(true).
option("java.PolyglotTypeConverters.java.math.BigDecimal", "guest.context.path.BigDecimalConverter").
build();
...
// guest java
package guest.context.path;
import com.oracle.truffle.espresso.polyglot.GuestTypeConversion;
import com.oracle.truffle.espresso.polyglot.Interop;
import com.oracle.truffle.espresso.polyglot.InteropException;
import java.math.BigDecimal;
public class BigDecimalConverter implements GuestTypeConversion<BigDecimal> {
@Override
@SuppressWarnings("unchecked")
public BigDecimal toGuest(Object polyglotInstance) {
try {
return new BigDecimal(Interop.asString(Interop.invokeMember(polyglotInstance, "toString")));
} catch (InteropException e) {
throw new ClassCastException("polyglot instance cannot be cast to java.math.BigDecimal");
}
}
}
选项的 java.math.Bigdecimal
部分声明了进入 Espresso 的主机对象的完全限定元名称。
java.PolyglotInterfaceMappings
如果没有用于流入嵌入式 Espresso 上下文的主机对象的专用 java.PolyglotTypeConverters
,则会自动进行接口类型映射。java.PolyglotInterfaceMappings
实现了主机与嵌入式上下文之间接口类型的无缝共享。
以下示例展示了如何使用此选项来允许通过接口将常见的 JDK 集合类型传递到嵌入式 Espresso 上下文
// host java
builder.option("java.PolyglotInterfaceMappings", getInterfaceMappings());
private static String getInterfaceMappings(){
return "java.lang.Iterable;"+
"java.util.Collection;"+
"java.util.List;"+
"java.util.Set;"+
"java.util.Map;"+
"java.util.Iterator;"+
"java.util.Spliterator;";
}
多线程 #
Espresso 被设计为一种多线程语言,并且大部分生态系统都期望线程可用。这可能与其他不支持线程的 Truffle 语言不兼容,因此您可以使用选项 --java.MultiThreaded=false
禁用创建多个线程。
启用此选项后,终结器和 ReferenceQueue
通知机制都不会运行。这两个功能都需要启动新线程。请注意,弱可达对象的垃圾回收不受影响。
相反,引用处理可以通过一个特殊命令手动触发,该命令仅在单线程环境中可用。
// Host Java
// Will trigger Reference processing and run finalizers
polyglot.eval("java", "<ProcessReferences>");
请注意,此命令可能会触发任意的清理器和终结器代码。因此,理想情况下,应在堆栈上尽可能少的访客 Java 帧的情况下运行此命令。