- 适用于 JDK 23 的 GraalVM(最新)
- 适用于 JDK 24 的 GraalVM(抢先体验)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 归档
- 开发版本
嵌入式语言
- 依赖关系设置
- 编译和运行多语言应用程序
- 将客语言函数定义为 Java 值
- 直接从 Java 访问客语言
- 从客语言访问 Java
- 从客语言查找 Java 类型
- 使用多语言代理的计算数组
- 主机访问
- 运行时优化支持
- 从多语言应用程序构建本机可执行文件
- 跨多个上下文的代码缓存
- 多语言隔离
- 在 Java 中嵌入客语言
- 为多种语言构建外壳
- 使用执行监听器逐步执行
- 设置堆大小
- 与 JSR-223 ScriptEngine 的兼容性
The GraalVM Polyglot API lets you embed and run code from guest languages in Java host applications.
Throughout this section, you will learn how to create a host application in Java that runs on GraalVM and directly calls a guest language. You can use the tabs beneath each code example to choose between JavaScript, R, Ruby, and Python.
Note: The usage description for polyglot embeddings was revised with GraalVM for JDK 21 and Polyglot API version 23.1.0. If you are still using Polyglot API version older than 23.1.0, ensure the correct version of the documentation is displayed. More information on the change can be found in the release notes.
依赖关系设置 #
Since Polyglot API version 23.1.0, all necessary artifacts can be downloaded directly from Maven Central. Artifacts relevant to embedders can be found in the Maven dependency group org.graalvm.polyglot
. See the polyglot embedding demonstration on GitHub for a complete runnable example.
Here is an example Maven dependency setup that you can put into your project
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>polyglot</artifactId>
<version>${graalvm.polyglot.version}</version>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<!-- Select a language: js, ruby, python, java, llvm, wasm, languages-->
<artifactId>js</artifactId>
<version>${graalvm.polyglot.version}</version>
<type>pom</type>
</dependency>
<!-- Add additional languages if needed -->
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<!-- Select a tool: profiler, inspect, coverage, dap, tools -->
<artifactId>profiler</artifactId>
<version>${graalvm.polyglot.version}</version>
<type>pom</type>
</dependency>
The
pom
type is a requirement for language or tool dependencies.
Language and tool dependencies use the GraalVM Free Terms and Conditions (GFTC) license. To use community-licensed versions instead, add the -community
suffix to each artifact (for example, js-community
). To access polyglot isolate artifacts, use the -isolate
suffix instead (for example, js-isolate
).
The artifacts languages
and tools
include all available languages and tools as dependencies. This artifact might grow or shrink between major releases. We recommend selecting only the needed language(s) for a production deployment.
Additionally, your module-info.java file should require org.graalvm.polyglot
when using Java modules
module com.mycompany.app {
requires org.graalvm.polyglot;
}
Whether your configuration can run with a Truffle runtime optimization depends on the GraalVM JDK you use. For further details, refer to the Runtime Compilation section.
We recommend configuring polyglot embeddings using modules and the module path whenever possible. Be aware that using org.graalvm.polyglot
from the class path instead will enable access to unsafe APIs for all libraries on the class path. If the application is not yet modularized, hybrid use of the class path and module path is possible. For example
$java -classpath=lib --module-path=lib/polyglot --add-modules=org.graalvm.polyglot ...
In this example, lib/polyglot
directory should contain all polyglot and language JAR files. To access polyglot classes from the class path, you must also specify the --add-modules=org.graalvm.polyglot
JVM option. If you are using GraalVM Native Image, polyglot modules on the class path will be automatically upgraded to the module path.
While we do support creating single uber JAR files from polyglot libraries, for example, using the Maven Assembly plugin, but we do not recommend it. Also note that uber JAR files are not supported when creating native binaries with GraalVM Native Image.
编译和运行多语言应用程序 #
GraalVM 可以运行用任何用 Truffle 语言实现框架 实现的语言编写的多语言应用程序。这些语言从此被称为 **客语言**。
完成本节中的步骤以创建一个在 GraalVM 上运行的示例多语言应用程序,并演示编程语言互操作性。
-
使用 Maven 创建一个新的 Java 项目。
- 克隆 polyglot-embedding-demo 存储库
git clone https://github.com/graalvm/polyglot-embedding-demo.git
-
将示例代码插入 Main 类 中。
-
下载并安装 GraalVM,方法是将
JAVA_HOME
环境变量的值设置为 GraalVM JDK 的位置。 - 运行
mvn package exec:exec
来构建和执行示例代码。
现在,您有一个多语言应用程序,该应用程序由一个 Java 主机应用程序和客语言代码组成,在 GraalVM 上运行。您可以将此应用程序与其他代码示例一起使用,以演示 GraalVM Polyglot API 的更高级功能。
将客语言函数定义为 Java 值 #
多语言应用程序可以让您从一种编程语言中获取值并将其与其他语言一起使用。
将本节中的代码示例与您的多语言应用程序一起使用,以展示 Polyglot API 如何将 JavaScript、R、Ruby 或 Python 函数作为 Java 值返回。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_js {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value function = context.eval("js", "x => x+1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_R {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
Value function = context.eval("R", "function(x) x + 1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_ruby {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value function = context.eval("ruby", "proc { |x| x + 1 }");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_python {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value function = context.eval("python", "lambda x: x + 1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
在此代码中
Value function
是一个引用函数的 Java 值。eval
调用解析脚本并返回客语言函数。- 第一个断言检查代码片段返回的值是否可以执行。
execute
调用使用参数41
执行函数。asInt
调用将结果转换为 Javaint
。- 第二个断言验证结果按预期加了一。
直接从 Java 访问客语言 #
多语言应用程序可以轻松访问大多数语言类型,而不限于函数。主机语言(例如 Java)可以直接访问嵌入在多语言应用程序中的客语言值。
将本节中的代码示例与您的多语言应用程序一起使用,以展示 Polyglot API 如何访问对象、数字、字符串和数组。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_js_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value result = context.eval("js",
"({ " +
"id : 42, " +
"text : '42', " +
"arr : [1,42,3] " +
"})");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_R_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
Value result = context.eval("R",
"list(" +
"id = 42, " +
"text = '42', " +
"arr = c(1,42,3)" +
")");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_ruby_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value result = context.eval("ruby",
"o = Struct.new(:id, :text, :arr).new(" +
"42, " +
"'42', " +
"[1,42,3] " +
")");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_python_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value result = context.eval("python",
"type('obj', (object,), {" +
"'id' : 42, " +
"'text': '42', " +
"'arr' : [1,42,3]" +
"})()");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
在此代码中
Value result
是一个包含三个成员的对象:名为id
的数字、名为text
的字符串和名为arr
的数组。- 第一个断言验证返回值可以包含成员,这表明该值是类似对象的结构。
id
变量通过从结果对象中读取名为id
的成员来初始化。然后使用asInt()
将结果转换为 Javaint
。- 下一个断言验证结果的值为
42
。 text
变量使用text
成员的值来初始化,该值也使用asString()
转换为 JavaString
。- 以下断言验证结果值等于 Java
String
"42"
。 - 接下来读取包含数组的
arr
成员。 - 数组对于
hasArrayElements
返回true
。R 数组实例可以同时具有成员和数组元素。 - 下一个断言验证数组的大小等于 3。Polyglot API 支持大数组,因此数组长度为
long
类型。 - 最后,我们验证索引为
1
的数组元素等于42
。使用多语言值进行数组索引始终从零开始,即使对于 R 等语言也是如此,其中索引从 1 开始。
从客语言访问 Java #
多语言应用程序提供了客语言和主机语言之间的双向访问。因此,您可以将 Java 对象传递给客语言。
由于 Polyglot API 默认情况下是安全的,因此在默认配置中访问是有限制的。要允许客语言访问 Java 对象的任何公共方法或字段,您必须在构建上下文时显式指定 allowAllAccess(true)
。在此模式下,客语言代码可以访问主机 Java 代码可以访问的任何资源。
将本节中的代码示例与您的多语言应用程序一起使用,以展示客语言如何访问基本 Java 值、对象、数组和函数式接口。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_js {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getBindings("js").putMember("javaObj", new MyClass());
boolean valid = context.eval("js",
" javaObj.id == 42" +
" && javaObj.text == '42'" +
" && javaObj.arr[1] == 42" +
" && javaObj.ret42() == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_R {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getBindings("R").putMember("javaObj", new MyClass());
boolean valid = context.eval("R",
" javaObj$id == 42" +
" && javaObj$text == '42'" +
" && javaObj$arr[[2]] == 42" +
" && javaObj$ret42() == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_ruby {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getPolyglotBindings().putMember("javaObj", new MyClass());
boolean valid = context.eval("ruby",
"javaObj = Polyglot.import('javaObj')\n" +
" javaObj[:id] == 42" +
" && javaObj[:text] == '42'" +
" && javaObj[:arr][1] == 42" +
" && javaObj[:ret42].call == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_python {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getPolyglotBindings().putMember("javaObj", new MyClass());
boolean valid = context.eval("python",
"import polyglot \n" +
"javaObj = polyglot.import_value('javaObj')\n" +
"javaObj.id == 42" +
" and javaObj.text == '42'" +
" and javaObj.arr[1] == 42" +
" and javaObj.ret42() == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
在此代码中
- Java 类
MyClass
有四个公共字段id
、text
、arr
和ret42
。这些字段使用42
、"42"
、new int[]{1, 42, 3}
和始终返回int
值为42
的 lambda() -> 42
初始化。 - Java 类
MyClass
被实例化并使用名称javaObj
导出到多语言范围内,这允许主机语言和客语言交换符号。 - 客语言脚本被评估,该脚本导入
javaObj
符号并将其分配给本地变量,该变量也命名为javaObj
。为了避免与变量发生冲突,必须在语言的最顶层范围内显式导入和导出多语言范围内的每个值。 - 接下来的两行通过将其与数字
42
和字符串'42'
进行比较来验证 Java 对象的内容。 - 第三个验证从第二个数组位置读取,并将其与数字
42
进行比较。数组是否使用基于 0 或基于 1 的索引取决于客语言。与语言无关,存储在arr
字段中的 Java 数组始终使用转换后的基于 0 的索引访问。例如,在 R 语言中,数组是基于 1 的,因此第二个数组元素可以使用索引2
访问。在 JavaScript 和 Ruby 语言中,第二个数组元素位于索引1
处。在所有语言示例中,Java 数组都是使用相同的索引1
读取的。 - 最后一行调用包含在
ret42
字段中的 Java lambda,并将结果与数字值42
进行比较。 - 客语言脚本执行后,将进行验证以确保脚本返回
boolean
值为true
的结果。
从客语言查找 Java 类型 #
除了将 Java 对象传递给客语言之外,还可以允许在客语言中查找 Java 类型。
使用本节中的代码示例与您的多语言应用程序一起展示客语言如何查找 Java 类型并实例化它们。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_js {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("js",
"var BigDecimal = Java.type('java.math.BigDecimal');" +
"BigDecimal.valueOf(10).pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_R {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("R",
"BigDecimal = java.type('java.math.BigDecimal');\n" +
"BigDecimal$valueOf(10)$pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_ruby {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("ruby",
"BigDecimal = Java.type('java.math.BigDecimal')\n" +
"BigDecimal.valueOf(10).pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_python {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("python",
"import java\n" +
"BigDecimal = java.type('java.math.BigDecimal')\n" +
"BigDecimal.valueOf(10).pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
在此代码中
- 使用所有访问权限(
allowAllAccess(true)
)创建一个新上下文。 - 评估客语言脚本。
- 该脚本查找 Java 类型
java.math.BigDecimal
并将其存储在名为BigDecimal
的变量中。 - 调用静态方法
BigDecimal.valueOf(long)
来创建值为10
的新BigDecimal
。除了查找静态 Java 方法外,还可以直接实例化返回的 Java 类型,例如,在 JavaScript 中使用new
关键字。 - 新的十进制数用于调用
pow
实例方法,其值为20
,计算10^20
。 - 通过调用
asHostObject()
将脚本的结果转换为主机对象。返回值自动转换为BigDecimal
类型。 - 断言结果十进制字符串等于
"100000000000000000000"
。
使用多语言代理计算数组 #
多语言 API 包含多语言代理接口,使您可以通过模仿客语言类型(如对象、数组、本机对象或基本类型)来自定义 Java 交互性。
使用本节中的代码示例与您的多语言应用程序一起查看如何实现懒惰地计算其值的数组。
注意:多语言 API 在 JVM 或 Native Image 上都支持多语言代理。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
public class proxy_js {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.create()) {
ComputedArray arr = new ComputedArray();
context.getBindings("js").putMember("arr", arr);
long result = context.eval("js",
"arr[1] + arr[1000000000]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;
public class proxy_R {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
ComputedArray arr = new ComputedArray();
context.getPolyglotBindings().putMember("arr", arr);
long result = context.eval("R",
"arr <- import('arr');" +
"arr[2] + arr[1000000001]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;
public class proxy_ruby {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
ComputedArray arr = new ComputedArray();
context.getPolyglotBindings().putMember("arr", arr);
long result = context.eval("ruby",
"arr = Polyglot.import('arr') \n" +
"arr[1] + arr[1000000000]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;
public class proxy_python {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
ComputedArray arr = new ComputedArray();
context.getPolyglotBindings().putMember("arr", arr);
long result = context.eval("python",
"import polyglot\n" +
"arr = polyglot.import_value('arr') \n" +
"arr[1] + arr[1000000000]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
在此代码中
- Java 类
ComputedArray
实现代理接口ProxyArray
,以便客语言将 Java 类的实例视为数组。 ComputedArray
数组覆盖方法get
,并使用算术表达式计算值。- 数组代理不支持写入访问。因此,它在
set
的实现中抛出UnsupportedOperationException
。 getSize
的实现为其长度返回Long.MAX_VALUE
。- 主方法创建一个新的多语言执行上下文。
- 然后使用名称
arr
导出ComputedArray
类的新的实例。 - 客语言脚本导入
arr
符号,该符号返回导出的代理。 - 访问第二个元素和第
1000000000
个元素,将它们加起来,然后返回。请注意,来自基于 1 的语言(如 R)的数组索引将转换为代理数组的基于 0 的索引。 - 语言脚本的结果作为长值返回并进行验证。
有关多语言代理接口的更多信息,请参阅多语言 API JavaDoc。
主机访问 #
默认情况下,多语言 API 限制对某些关键功能(如文件 I/O)的访问。可以通过将allowAllAccess
设置为true
来完全取消这些限制。
注意:访问限制目前仅在 JavaScript 中受支持。
控制对主机函数的访问 #
可能需要限制客应用程序对主机的访问。例如,如果公开了一个调用System.exit
的 Java 方法,那么客应用程序将能够退出主机进程。为了避免意外公开方法,默认情况下不允许主机访问,并且每个公共方法或字段都需要显式地使用@HostAccess.Export
进行注释。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotException;
public class explicit_access_java_from_js {
static
// BEGIN-SNIPPET
public class Employee {
private final String name;
Employee(String name) {this.name = name;}
@HostAccess.Export
public String getName() {
return name;
}
}
//END-SNIPPET
static
//BEGIN-SNIPPET
public class Services {
@HostAccess.Export
public Employee createEmployee(String name) {
return new Employee(name);
}
public void exitVM() {
System.exit(1);
}
}
public static void main(String[] args) {
try (Context context = Context.create()) {
Services services = new Services();
context.getBindings("js").putMember("services", services);
String name = context.eval("js",
"let emp = services.createEmployee('John Doe');" +
"emp.getName()").asString();
assert name.equals("John Doe");
try {
context.eval("js", "services.exitVM()");
assert false;
} catch (PolyglotException e) {
assert e.getMessage().endsWith(
"Unknown identifier: exitVM");
}
}
}
// END-SNIPPET
}
在此代码中
- 类
Employee
声明了一个类型为String
的字段name
。通过使用@HostAccess.Export
注释方法来显式地允许访问getName
方法。 Services
类公开了两个方法,createEmployee
和exitVM
。createEmployee
方法采用员工的名称作为参数,并创建一个新的Employee
实例。createEmployee
方法使用@HostAccess.Export
进行注释,因此客应用程序可以访问它。exitVM
方法没有显式导出,因此无法访问。main
方法首先在默认配置中创建一个新的多语言上下文,禁止主机访问,除了使用@HostAccess.Export
注释的方法外。- 创建一个新的
Services
实例,并将其放入上下文作为全局变量services
。 - 第一个评估的脚本使用 services 对象创建一个新的员工,并返回其名称。
- 断言返回的名称等于预期名称
John Doe
。 - 评估第二个脚本,该脚本在 services 对象上调用
exitVM
方法。这将导致PolyglotException
,因为exitVM
方法没有公开给客应用程序。
可以通过创建自定义HostAccess
策略来完全自定义主机访问。
控制主机回调参数范围 #
默认情况下,Value
的生存时间与其相应的Context
相同。但是,可能需要更改此默认行为并将值绑定到范围,以便在执行离开范围时,值将失效。这种范围的示例是客机到主机的回调,其中Value
可以作为回调参数传递。我们已经在上面看到过如何使用默认的HostAccess.EXPLICIT
传递回调参数。
public class Services {
Value lastResult;
@HostAccess.Export
public void callback(Value result) {
this.lastResult = result;
}
String getResult() {
return this.lastResult.asString();
}
}
public static void main(String[] args) {
Services s = new Services()
try (Context context = Context.newBuilder().allowHostAccess(HostAccess.EXPLICIT).build()) {
context.getBindings("js").putMember("services", s);
context.eval("js", "services.callback('Hello from JS');");
System.out.println(s.getResult());
}
}
在此示例中,lastResult
维护对主机上存储的客机值的引用,并且在callback()
范围结束之后仍然可以访问。
但是,这并不总是可取的,因为让值保持活动状态可能会不必要地阻塞资源或不能正确反映短暂值的特性。对于这些情况,可以使用HostAccess.SCOPED
,它会更改所有回调的默认行为,以便作为回调参数传递的值仅在回调持续时间内有效。
为了使上面的代码能够与HostAccess.SCOPED
一起使用,可以将作为回调参数传递的单个值固定以延长其有效性,直到回调返回之后。
public class Services {
Value lastResult;
@HostAccess.Export
void callback(Value result, Value notneeded) {
this.lastResult = result;
this.lastResult.pin();
}
String getResult() {
return this.lastResult.asString();
}
}
public static void main(String[] args) {
Services s = new Services()
try (Context context = Context.newBuilder().allowHostAccess(HostAccess.SCOPED).build()) {
context.getBindings("js").putMember("services", s);
context.eval("js", "services.callback('Hello from JS', 'foobar');");
System.out.println(services.getResult());
}
}
或者,如果使用@HostAccess.DisableMethodScope
对整个回调方法进行注释,则可以从范围中排除该方法,从而为回调的所有参数维护常规语义。
public class Services {
Value lastResult;
Value metaInfo;
@HostAccess.Export
@HostAccess.DisableMethodScope
void callback(Value result, Value metaInfo) {
this.lastResult = result;
this.metaInfo = metaInfo;
}
String getResult() {
return this.lastResult.asString() + this.metaInfo.asString();
}
}
public static void main(String[] args) {
Services s = new Services()
try (Context context = Context.newBuilder().allowHostAccess(HostAccess.SCOPED).build()) {
context.getBindings("js").putMember("services", s);
context.eval("js", "services.callback('Hello from JS', 'foobar');");
System.out.println(services.getResult());
}
}
访问权限配置 #
可以为客应用程序配置细粒度的访问权限。可以在构建新上下文时使用Context.Builder
类提供配置。可以配置以下访问参数
- 使用
allowPolyglotAccess
允许访问其他语言。 - 使用
allowHostAccess
允许和自定义对主机对象的访问。 - 使用
allowHostClassLookup
允许和自定义主机查找主机类型。允许客应用程序查找由查找谓词允许的主机应用程序类。例如,JavaScript 上下文可以创建一个 Java ArrayList,前提是 ArrayList 被classFilter
列入白名单,并且主机访问策略允许访问:context.eval("js", "var array = Java.type('java.util.ArrayList')")
- 使用
allowHostClassLoading
允许主机类加载。只有当主机访问策略授予对这些类的访问权限时,这些类才可访问。 - 使用
allowCreateThread
允许创建线程。 - 使用
allowNativeAccess
允许访问本机 API。 - 使用
allowIO
允许访问 IO,并使用fileSystem
代理文件访问。
注意:授予对类加载、本机 API 或主机 I/O 的访问权限实际上等同于授予所有访问权限,因为这些权限可用于绕过其他访问限制。
运行时优化支持 #
多语言 Truffle 运行时可以在多个主机虚拟机上使用,这些虚拟机对运行时优化的支持程度各不相同。客应用程序代码的运行时优化对于嵌入式客应用程序的有效执行至关重要。下表显示了 Java 运行时目前提供的优化级别
Java 运行时 | 运行时优化级别 |
---|---|
Oracle GraalVM | 通过额外的编译器阶段进行优化 |
GraalVM 社区版 | 优化 |
Oracle JDK | 如果通过实验性 VM 选项启用,则优化 |
OpenJDK | 如果通过实验性 VM 选项启用,则优化 |
没有 JVMCI 功能的 JDK | 没有运行时优化(仅解释器) |
解释 #
- 优化:执行的客应用程序代码可以在运行时被编译并作为高效的机器代码执行。
- 通过额外的编译器阶段进行优化:Oracle GraalVM 实施了在运行时编译期间执行的额外优化。例如,它使用更高级别的内联启发式方法。这通常会导致更好的运行时性能和内存消耗。
- 如果通过实验性 VM 选项启用,则优化:默认情况下不启用优化,必须使用
-XX:+EnableJVMCI
虚拟机选项启用它。此外,为了支持编译,必须将 Graal 编译器下载为 JAR 文件,并将其放在--upgrade-module-path
上。在这种模式下,编译器作为 Java 应用程序运行,可能会对主机应用程序的执行性能产生负面影响。 - 没有运行时优化:如果没有运行时优化,或者如果未启用 JVMCI,客应用程序代码将在仅解释器模式下执行。
- JVMCI:指的是大多数 Java 运行时支持的Java 级 JVM 编译器接口。
已创建了一个项目,旨在为 Oracle JDK 和 OpenJDK 默认启用运行时优化。有关更多详细信息,请参阅项目 Galahad。
在 OpenJDK 和 Oracle JDK 上启用优化 #
在默认情况下启用运行时优化的 JDK 运行时(如 OpenJDK)上运行时,您可能会看到以下警告
[engine] WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to machine code.
Execution without runtime compilation will negatively impact the guest application performance.
这表示访客应用程序在没有启用运行时优化的情况下执行。可以通过以下两种方式抑制警告:使用 `--engine.WarnInterpreterOnly=false` 选项或 `-Dpolyglot.engine.WarnInterpreterOnly=false` 系统属性进行抑制。此外,必须从 Maven Central 下载 `compiler.jar` 文件及其依赖项,并使用 `--upgrade-module-path` 选项引用。请注意,`compiler.jar` *不能* 放在模块或类路径上。有关使用 Maven 或 Gradle 的示例配置,请参阅 polyglot 嵌入演示。
切换到备用引擎 #
如果需要,例如,仅运行简单的脚本或在资源受限的系统上运行,您可能希望切换到没有运行时优化的备用引擎。从 Polyglot 23.1 版开始,可以通过从类路径或模块路径中删除 `truffle-runtime` 和 `truffle-enterprise` 模块来激活备用引擎。
这可以通过 Maven 以下方式实现
<dependencies>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js</artifactId>
<version>$graalvm-version</version>
<exclusions>
<exclusion>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-runtime</artifactId>
</exclusion>
<exclusion>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-enterprise</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
如果您只使用 `-community` 依赖项,则 `truffle-enterprise` 的排除规则是多余的。由于排除了 `truffle-enterprise`,备用引擎不支持高级扩展,如沙箱限制或 polyglot 隔离。最好使用 `mvn dependency:tree` 仔细检查这两个依赖项是否未在其他地方包含。
如果成功排除了运行时,您应该会看到以下日志消息
[engine] WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to native code.
Execution without runtime compilation will negatively impact the guest application performance.
The following cause was found: No optimizing Truffle runtime found on the module or class path.
For more information see: https://graalvm.java.net.cn/latest/reference-manual/embed-languages/.
To disable this warning use the '--engine.WarnInterpreterOnly=false' option or the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.
您可以使用指示的选项作为附加步骤来禁用此消息。
删除这些依赖项也会在 Native Image 构建中自动切换到备用引擎。
从 Polyglot 应用程序构建本机可执行文件 #
使用 GraalVM for JDK 21 及更高版本的 Polyglot 23.1 版,不需要进行特殊配置即可使用 Native Image 构建包含嵌入式 polyglot 语言运行时的映像。与任何其他 Java 依赖项一样,在构建本机可执行文件时,polyglot 语言 JAR 文件必须位于类路径或模块路径上。我们建议您使用 Maven 或 Gradle Native Image 插件来配置您的 `native-image` 构建。可以在 polyglot 嵌入演示存储库 中找到用于 Native Image 的示例 Maven 和 Gradle 配置。
这是一个 Maven 配置文件配置示例
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.1</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<imageName>${project.artifactId}</imageName>
<mainClass>org.example.embedding.Main</mainClass>
<buildArgs>
<buildArg>--no-fallback</buildArg>
<buildArg>-J-Xmx20g</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
要使用上述配置构建本机可执行文件,请运行
mvn -Pnative package
要从 polyglot 应用程序构建本机可执行文件(例如,嵌入 Python 的 Java 宿主应用程序),默认情况下会创建一个名为 `./resources` 的目录,其中包含所有必需的文件。默认情况下,语言运行时将在构建的本机可执行文件或库映像的相对路径中查找资源目录。在运行时,可以使用 `-Dpolyglot.engine.resourcePath=path/to/resources` 选项自定义查找位置。要禁用资源创建,可以使用 `-H:-CopyLanguageResources` 构建时选项。请注意,某些语言可能不支持在没有资源目录的情况下运行。
在 Polyglot 23.1 版中,`-Dorg.graalvm.home` 等语言主目录选项不再使用,已被资源目录选项取代。语言主目录选项出于兼容性原因仍然有效,但在将来的版本中可能会被删除。
配置本机宿主反射 #
要从访客应用程序访问宿主 Java 代码,需要 Java 反射才能正常工作。当在本地可执行文件中使用反射时,需要 反射配置文件。
对于此示例,我们将使用 JavaScript 来演示具有本机可执行文件的宿主访问。将以下代码复制到名为 `AccessJavaFromJS.java` 的新文件中。
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
import java.util.concurrent.*;
public class AccessJavaFromJS {
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getBindings("js").putMember("javaObj", new MyClass());
boolean valid = context.eval("js",
" javaObj.id == 42" +
" && javaObj.text == '42'" +
" && javaObj.arr[1] == 42" +
" && javaObj.ret42() == 42")
.asBoolean();
System.out.println("Valid " + valid);
}
}
}
将以下代码复制到 `reachability-metadata.json` 中
{
"reflection": [
{ "type": "AccessJavaFromJS$MyClass", "allPublicFields": true },
{ "type": "java.util.concurrent.Callable", "allPublicMethods": true }
]
}
现在,您可以将 `reachability-metadata.json` 添加到项目的 `META-INF/native-image/<group-id>/` 中。
跨多个上下文进行代码缓存 #
GraalVM Polyglot API 允许跨多个上下文进行代码缓存。代码缓存允许重用已编译的代码,并允许只解析一次源代码。代码缓存通常可以减少应用程序的内存消耗和预热时间。
默认情况下,代码仅在单个上下文实例内进行缓存。需要指定一个显式引擎才能在多个上下文之间启用代码缓存。在使用 上下文构建器 创建上下文时指定引擎。引擎实例确定代码共享的范围。代码仅在与一个引擎实例关联的上下文之间共享。
默认情况下,所有源代码都会被缓存。可以通过将 cached(boolean cached) 设置为 `false` 来显式禁用缓存。如果已知源代码只会被评估一次,禁用缓存可能很有用。
以下代码段是一个示例
public class Main {
public static void main(String[] args) {
try (Engine engine = Engine.create()) {
Source source = Source.create("js", "21 + 21");
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
}
}
}
在此代码中
import org.graalvm.polyglot.*
导入 Polyglot API 的基础 API。Engine.create()
使用默认配置创建一个新的引擎实例。Source.create()
为表达式“21 + 21”创建一个源对象。我们使用显式的 `Source` 对象来确保代码缓存不会在上下文之间被垃圾回收。使用“js”语言,它是 JavaScript 的语言标识符。Context.newBuilder().engine(engine).build()
使用分配给它的显式引擎构建一个新的上下文。与引擎关联的所有上下文都共享代码。context.eval(source).asInt()
评估源代码并以 `Value` 实例形式返回结果。
重要提示: 要使已缓存源代码的代码缓存保持在执行上下文之间处于活动状态,应用程序必须确保持续引用 `Source` 对象。polyglot 运行时可能会在下一个 GC 周期内收集不再被引用的源代码的已缓存代码。
管理代码缓存 #
代码缓存的数据存储为 `Engine` 实例的一部分。两个独立的引擎实例之间永远不会发生代码共享。因此,如果需要全局代码缓存,我们建议使用单例 `Engine` 实例。与上下文相反,引擎始终可以在多个线程之间共享。上下文是否可以在多个线程之间共享取决于使用的语言。
没有显式方法来清除代码缓存。我们依靠垃圾收集器在下次收集时自动执行此操作。只要引擎仍被强引用且未关闭,引擎的代码缓存就不会被收集。此外,必须使 `Source` 实例保持活动状态才能确保关联的代码不会被收集。如果源实例不再被引用,但引擎仍被引用,与源对象关联的代码缓存可能会被 GC 收集。因此,我们建议在 `Source` 应该保持缓存状态时,保持对 `Source` 对象的强引用。
总而言之,可以通过保持和维护对 `Engine` 和 `Source` 对象的强引用来控制代码缓存。
Polyglot 隔离 #
在 Oracle GraalVM 上,可以将 Polyglot 引擎配置为在专用的 Native Image 隔离中运行。在这种模式下的 polyglot 引擎在具有专用垃圾收集器和 JIT 编译器的 VM 级故障域中执行。Polyglot 隔离对于 沙箱化 很有用。在隔离中运行语言适用于 HotSpot 和 Native Image 宿主虚拟机。
用作 polyglot 隔离的语言可以从 Maven Central 使用 `-isolate` 后缀下载。例如,可以通过添加以下 Maven 依赖项来配置对隔离 JavaScript 的依赖项
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>polyglot</artifactId>
<version>24.0.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js-isolate</artifactId>
<version>24.0.0</version>
<type>pom</type>
</dependency>
下载的依赖项与平台无关,其中包含每个平台的本机映像。我们计划在将来的版本中支持下载针对各个平台的 polyglot 隔离本机映像。
要使用 Polyglot API 启用隔离,在构建时必须将 `--engine.SpawnIsolate=true` 选项传递给 `Engine` 或 `Context`。如果在除 Oracle GraalVM 之外的任何其他 JDK 上使用 `engine.SpawnIsolate` 选项,则该选项可能不可用。
import org.graalvm.polyglot.*;
public class PolyglotIsolate {
public static void main(String[] args) {
try (Context context = Context.newBuilder("js")
.allowHostAccess(HostAccess.SCOPED)
.option("engine.SpawnIsolate", "true").build()) {
Value function = context.eval("js", "x => x+1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
}
}
目前,以下语言可作为 polyglot 隔离使用
语言 | 可用 |
---|---|
JavaScript (js-isolate ) |
23.1 |
我们计划在将来的版本中添加对更多语言的支持。
在前面的示例中,我们使用 `HostAccess.SCOPED` 启用范围引用。这是必要的,因为宿主 GC 和访客 GC 相互不了解,因此无法自动解决对象之间的循环引用。因此,我们强烈建议使用 宿主回调的范围参数,以完全避免循环引用。
可以通过 共享引擎 在同一个隔离引擎中生成多个上下文。
public class PolyglotIsolateMultipleContexts {
public static void main(String[] args) {
try (Engine engine = Engine.newBuilder("js")
.option("engine.SpawnIsolate", "true").build()) {
Source source = Source.create("js", "21 + 21");
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
}
}
}
传递 Native Image 运行时选项 #
在隔离中运行的引擎可以通过将 `--engine.IsolateOption.<option>` 传递给引擎构建器来使用 Native Image 运行时选项。例如,这可以用来通过设置隔离的最大堆大小(通过 `--engine.IsolateOption.MaxHeapSize=128m`)来限制引擎使用的最大堆内存。
import org.graalvm.polyglot.*;
public class PolyglotIsolateMaxHeap {
public static void main(String[] args) {
try {
Context context = Context.newBuilder("js")
.allowHostAccess(HostAccess.SCOPED)
.option("engine.SpawnIsolate", "true")
.option("engine.IsolateOption.MaxHeapSize", "64m").build()
context.eval("js", "var a = [];while (true) {a.push('foobar');}");
} catch (PolyglotException ex) {
if (ex.isResourceExhausted()) {
System.out.println("Resource exhausted");
}
}
}
}
超过最大堆大小将自动关闭上下文并引发 `PolyglotException`。
确保宿主回调堆栈空间 #
使用 Polyglot 隔离时,`--engine.HostCallStackHeadRoom` 可确保在执行宿主回调时可用的最小堆栈空间。如果可用堆栈大小降至指定阈值以下,则宿主回调将失败。
内存保护 #
在支持内存保护密钥的 Linux 环境中,可以使用 `--engine.MemoryProtection=true` 选项在硬件级别隔离 Polyglot 隔离的堆。如果使用此选项创建引擎,则将为隔离引擎的堆分配一个专用的保护密钥。GraalVM 仅在执行 Polyglot 隔离的代码时才启用对引擎堆的访问。
在 Java 中嵌入访客语言 #
GraalVM Polyglot API 可以从访客语言内部使用 Java 交互性。如果脚本需要与父上下文隔离运行,这将非常有用。在 Java 作为宿主语言中,对 `Context.eval(Source)` 的调用将返回 `Value` 的实例,但由于我们在访客语言中执行此代码,因此可以使用特定于语言的交互性 API。因此,可以使用在语言内部创建的上下文返回的值,就像语言的常规值一样。在下面的示例中,我们可以方便地编写 `value.data` 而不是 `value.getMember("data")`。有关如何与外部值进行交互的详细信息,请参阅各个语言文档。有关在多个上下文之间共享值的更多信息,请参阅 此处。
以下代码段是一个示例
import org.graalvm.polyglot.*;
public class Main {
public static void main(String[] args) {
try (Context outer = Context.newBuilder()
.allowAllAccess(true)
.build()) {
outer.eval("js", "inner = Java.type('org.graalvm.polyglot.Context').create()");
outer.eval("js", "value = inner.eval('js', '({data:42})')");
int result = outer.eval("js", "value.data").asInt();
outer.eval("js", "inner.close()");
System.out.println("Valid " + (result == 42));
}
}
}
在此代码中
Context.newBuilder().allowAllAccess(true).build()
使用所有权限构建一个新的外部上下文。outer.eval
在外部上下文中评估 JavaScript 片段。inner = Java.type('org.graalvm.polyglot.Context').create()
第一行 JS 脚本代码查找 Java 宿主类型 Context 并创建一个新的内部上下文实例,没有权限(默认)。inner.eval('js', '({data:42})');
在内部上下文中评估 JavaScript 代码({data:42})
并返回结果。"value.data"
这行代码从内部上下文的结果中读取成员data
。 请注意,只有在内部上下文尚未关闭的情况下才能读取此结果。context.eval("js", "c.close()")
此代码段关闭内部上下文。 内部上下文需要手动关闭,不会随父上下文自动关闭。- 最后,示例预计会在控制台中打印
Valid true
。
为多种语言构建 Shell #
只需几行代码,GraalVM Polyglot API 就可以让您构建与 GraalVM 支持的任何访客语言集成的应用程序。
此 Shell 实现与任何特定访客语言无关。
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
PrintStream output = System.out;
Context context = Context.newBuilder().allowAllAccess(true).build();
Set<String> languages = context.getEngine().getLanguages().keySet();
output.println("Shell for " + languages + ":");
String language = languages.iterator().next();
for (;;) {
try {
output.print(language + "> ");
String line = input.readLine();
if (line == null) {
break;
} else if (languages.contains(line)) {
language = line;
continue;
}
Source source = Source.newBuilder(language, line, "<shell>")
.interactive(true).buildLiteral();
context.eval(source);
} catch (PolyglotException t) {
if(t.isExit()) {
break;
}
t.printStackTrace();
}
}
使用执行监听器逐步执行 #
GraalVM Polyglot API 允许用户通过 ExecutionListener 类 来检测访客语言的执行。 例如,它允许您附加一个执行监听器,该监听器将在访客语言程序的每个语句调用时调用。 执行监听器被设计为用于多语言嵌入器的简单 API,在单步执行程序时可能会派上用场。
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.management.*;
public class ExecutionListenerTest {
public static void main(String[] args) {
try (Context context = Context.create("js")) {
ExecutionListener listener = ExecutionListener.newBuilder()
.onEnter((e) -> System.out.println(
e.getLocation().getCharacters()))
.statements(true)
.attach(context.getEngine());
context.eval("js", "for (var i = 0; i < 2; i++);");
listener.close();
}
}
}
在此代码中
Context.create()
调用为访客语言创建一个新的上下文。- 通过调用
ExecutionListeners.newBuilder()
创建执行监听器构建器。 - 将
onEnter
事件设置为在元素的执行被进入并使用时通知。 至少需要启用一个事件使用者和一个筛选后的源元素。 - 要完成监听器附加,需要调用
attach()
。 statements(true)
将执行监听器筛选为仅语句。context.eval()
调用评估指定的访客语言代码片段。listener.close()
提前关闭监听器,但是执行监听器会随引擎自动关闭。
与 JSR-223 ScriptEngine 的兼容性 #
Truffle 语言实现框架没有提供 JSR-223 ScriptEngine 实现。 Polyglot API 提供了对 Truffle 功能的更细粒度的控制,我们强烈建议用户使用 org.graalvm.polyglot.Context
接口来直接控制许多设置,并从 GraalVM 中更细粒度的安全设置中获益。
但是,为了轻松地评估 Truffle 语言作为使用 ScriptEngine API 集成的其他脚本语言的替代品,我们提供了一个单文件脚本引擎。 此文件可以放置在源代码树中并直接使用来通过 ScriptEngine API 评估 Truffle 语言。 只有两行代码需要适应您的项目
public final class CHANGE_NAME_EngineFactory implements ScriptEngineFactory {
private static final String LANGUAGE_ID = "<<INSERT LANGUAGE ID HERE>>";
根据需要重命名类,并将 LANGUAGE_ID
更改为所需的 Truffle 语言(例如,对于 GraalPy 为“python”,对于 TruffleRuby 为“ruby”)。 要使用它,请在您的资源中包含一个 META-INF/services/javax.script.ScriptEngineFactory
文件,其中包含所选类的名称。 这将允许默认的 javax.script.ScriptEngineManager
自动发现该语言。 或者,可以将工厂注册到 javax.script.ScriptEngineManager#registerEngineName
或直接实例化并使用。
最佳实践是在不再使用时关闭 ScriptEngine
,而不是依赖终结器。 要关闭它,请使用 ((AutoCloseable) scriptEngine).close();
,因为 ScriptEngine
没有 close()
方法。
请注意,GraalJS 为从 JDK 11 中弃用的 Nashorn JavaScript 引擎迁移的用户提供了 ScriptEngine 实现,因此此处的方法不需要。
展开以查看单文件中 Truffle 语言的 ScriptEngineFactory
实现。
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import org.graalvm.home.Version;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
public final class CHANGE_NAME_EngineFactory implements ScriptEngineFactory {
private static final String LANGUAGE_ID = "<>";
/***********************************************************/
/* Everything below is generic and does not need to change */
/***********************************************************/
private final Engine polyglotEngine = Engine.newBuilder().build();
private final Language language = polyglotEngine.getLanguages().get(LANGUAGE_ID);
@Override
public String getEngineName() {
return language.getImplementationName();
}
@Override
public String getEngineVersion() {
return Version.getCurrent().toString();
}
@Override
public List getExtensions() {
return List.of(LANGUAGE_ID);
}
@Override
public List getMimeTypes() {
return List.copyOf(language.getMimeTypes());
}
@Override
public List getNames() {
return List.of(language.getName(), LANGUAGE_ID, language.getImplementationName());
}
@Override
public String getLanguageName() {
return language.getName();
}
@Override
public String getLanguageVersion() {
return language.getVersion();
}
@Override
public Object getParameter(final String key) {
switch (key) {
case ScriptEngine.ENGINE:
return getEngineName();
case ScriptEngine.ENGINE_VERSION:
return getEngineVersion();
case ScriptEngine.LANGUAGE:
return getLanguageName();
case ScriptEngine.LANGUAGE_VERSION:
return getLanguageVersion();
case ScriptEngine.NAME:
return LANGUAGE_ID;
}
return null;
}
@Override
public String getMethodCallSyntax(final String obj, final String m, final String... args) {
throw new UnsupportedOperationException("Unimplemented method 'getMethodCallSyntax'");
}
@Override
public String getOutputStatement(final String toDisplay) {
throw new UnsupportedOperationException("Unimplemented method 'getOutputStatement'");
}
@Override
public String getProgram(final String... statements) {
throw new UnsupportedOperationException("Unimplemented method 'getProgram'");
}
@Override
public ScriptEngine getScriptEngine() {
return new PolyglotEngine(this);
}
private static final class PolyglotEngine implements ScriptEngine, Compilable, Invocable, AutoCloseable {
private final ScriptEngineFactory factory;
private PolyglotContext defaultContext;
PolyglotEngine(ScriptEngineFactory factory) {
this.factory = factory;
this.defaultContext = new PolyglotContext(factory);
}
@Override
public void close() {
defaultContext.getContext().close();
}
@Override
public CompiledScript compile(String script) throws ScriptException {
Source src = Source.create(LANGUAGE_ID, script);
try {
defaultContext.getContext().parse(src); // only for the side-effect of validating the source
} catch (PolyglotException e) {
throw new ScriptException(e);
}
return new PolyglotCompiledScript(src, this);
}
@Override
public CompiledScript compile(Reader script) throws ScriptException {
Source src;
try {
src = Source.newBuilder(LANGUAGE_ID, script, "sourcefromreader").build();
defaultContext.getContext().parse(src); // only for the side-effect of validating the source
} catch (PolyglotException | IOException e) {
throw new ScriptException(e);
}
return new PolyglotCompiledScript(src, this);
}
@Override
public Object eval(String script, ScriptContext context) throws ScriptException {
if (context instanceof PolyglotContext) {
PolyglotContext c = (PolyglotContext) context;
try {
return c.getContext().eval(LANGUAGE_ID, script).as(Object.class);
} catch (PolyglotException e) {
throw new ScriptException(e);
}
} else {
throw new ClassCastException("invalid context");
}
}
@Override
public Object eval(Reader reader, ScriptContext context) throws ScriptException {
Source src;
try {
src = Source.newBuilder(LANGUAGE_ID, reader, "sourcefromreader").build();
} catch (IOException e) {
throw new ScriptException(e);
}
if (context instanceof PolyglotContext) {
PolyglotContext c = (PolyglotContext) context;
try {
return c.getContext().eval(src).as(Object.class);
} catch (PolyglotException e) {
throw new ScriptException(e);
}
} else {
throw new ScriptException("invalid context");
}
}
@Override
public Object eval(String script) throws ScriptException {
return eval(script, defaultContext);
}
@Override
public Object eval(Reader reader) throws ScriptException {
return eval(reader, defaultContext);
}
@Override
public Object eval(String script, Bindings n) throws ScriptException {
throw new UnsupportedOperationException("Bindings for Polyglot language cannot be created explicitly");
}
@Override
public Object eval(Reader reader, Bindings n) throws ScriptException {
throw new UnsupportedOperationException("Bindings for Polyglot language cannot be created explicitly");
}
@Override
public void put(String key, Object value) {
defaultContext.getBindings(ScriptContext.ENGINE_SCOPE).put(key, value);
}
@Override
public Object get(String key) {
return defaultContext.getBindings(ScriptContext.ENGINE_SCOPE).get(key);
}
@Override
public Bindings getBindings(int scope) {
return defaultContext.getBindings(scope);
}
@Override
public void setBindings(Bindings bindings, int scope) {
defaultContext.setBindings(bindings, scope);
}
@Override
public Bindings createBindings() {
throw new UnsupportedOperationException("Bindings for Polyglot language cannot be created explicitly");
}
@Override
public ScriptContext getContext() {
return defaultContext;
}
@Override
public void setContext(ScriptContext context) {
throw new UnsupportedOperationException("The context of a Polyglot ScriptEngine cannot be modified.");
}
@Override
public ScriptEngineFactory getFactory() {
return factory;
}
@Override
public Object invokeMethod(Object thiz, String name, Object... args)
throws ScriptException, NoSuchMethodException {
try {
Value receiver = defaultContext.getContext().asValue(thiz);
if (receiver.canInvokeMember(name)) {
return receiver.invokeMember(name, args).as(Object.class);
} else {
throw new NoSuchMethodException(name);
}
} catch (PolyglotException e) {
throw new ScriptException(e);
}
}
@Override
public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
throw new UnsupportedOperationException();
}
@Override
public T getInterface(Class interfaceClass) {
throw new UnsupportedOperationException();
}
@Override
public T getInterface(Object thiz, Class interfaceClass) {
return defaultContext.getContext().asValue(thiz).as(interfaceClass);
}
}
private static final class PolyglotContext implements ScriptContext {
private Context context;
private final ScriptEngineFactory factory;
private final PolyglotReader in;
private final PolyglotWriter out;
private final PolyglotWriter err;
private Bindings globalBindings;
PolyglotContext(ScriptEngineFactory factory) {
this.factory = factory;
this.in = new PolyglotReader(new InputStreamReader(System.in));
this.out = new PolyglotWriter(new OutputStreamWriter(System.out));
this.err = new PolyglotWriter(new OutputStreamWriter(System.err));
}
Context getContext() {
if (context == null) {
Context.Builder builder = Context.newBuilder(LANGUAGE_ID)
.in(this.in)
.out(this.out)
.err(this.err)
.allowAllAccess(true);
Bindings globalBindings = getBindings(ScriptContext.GLOBAL_SCOPE);
if (globalBindings != null) {
for (Entry<String, Object> entry : globalBindings.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
builder.option(entry.getKey(), (String) value);
}
}
}
context = builder.build();
}
return context;
}
@Override
public void setBindings(Bindings bindings, int scope) {
if (scope == ScriptContext.GLOBAL_SCOPE) {
if (context == null) {
globalBindings = bindings;
} else {
throw new UnsupportedOperationException(
"Global bindings for Polyglot language can only be set before the context is initialized.");
}
} else {
throw new UnsupportedOperationException("Bindings objects for Polyglot language is final.");
}
}
@Override
public Bindings getBindings(int scope) {
if (scope == ScriptContext.ENGINE_SCOPE) {
return new PolyglotBindings(getContext().getBindings(LANGUAGE_ID));
} else if (scope == ScriptContext.GLOBAL_SCOPE) {
return globalBindings;
} else {
return null;
}
}
@Override
public void setAttribute(String name, Object value, int scope) {
if (scope == ScriptContext.ENGINE_SCOPE) {
getBindings(scope).put(name, value);
} else if (scope == ScriptContext.GLOBAL_SCOPE) {
if (context == null) {
globalBindings.put(name, value);
} else {
throw new IllegalStateException("Cannot modify global bindings after context creation.");
}
}
}
@Override
public Object getAttribute(String name, int scope) {
if (scope == ScriptContext.ENGINE_SCOPE) {
return getBindings(scope).get(name);
} else if (scope == ScriptContext.GLOBAL_SCOPE) {
return globalBindings.get(name);
}
return null;
}
@Override
public Object removeAttribute(String name, int scope) {
Object prev = getAttribute(name, scope);
if (prev != null) {
if (scope == ScriptContext.ENGINE_SCOPE) {
getBindings(scope).remove(name);
} else if (scope == ScriptContext.GLOBAL_SCOPE) {
if (context == null) {
globalBindings.remove(name);
} else {
throw new IllegalStateException("Cannot modify global bindings after context creation.");
}
}
}
return prev;
}
@Override
public Object getAttribute(String name) {
return getAttribute(name, ScriptContext.ENGINE_SCOPE);
}
@Override
public int getAttributesScope(String name) {
if (getAttribute(name, ScriptContext.ENGINE_SCOPE) != null) {
return ScriptContext.ENGINE_SCOPE;
} else if (getAttribute(name, ScriptContext.GLOBAL_SCOPE) != null) {
return ScriptContext.GLOBAL_SCOPE;
}
return -1;
}
@Override
public Writer getWriter() {
return this.out.writer;
}
@Override
public Writer getErrorWriter() {
return this.err.writer;
}
@Override
public void setWriter(Writer writer) {
this.out.writer = writer;
}
@Override
public void setErrorWriter(Writer writer) {
this.err.writer = writer;
}
@Override
public Reader getReader() {
return this.in.reader;
}
@Override
public void setReader(Reader reader) {
this.in.reader = reader;
}
@Override
public List getScopes() {
return List.of(ScriptContext.ENGINE_SCOPE, ScriptContext.GLOBAL_SCOPE);
}
private static final class PolyglotReader extends InputStream {
private volatile Reader reader;
public PolyglotReader(InputStreamReader inputStreamReader) {
this.reader = inputStreamReader;
}
@Override
public int read() throws IOException {
return reader.read();
}
}
private static final class PolyglotWriter extends OutputStream {
private volatile Writer writer;
public PolyglotWriter(OutputStreamWriter outputStreamWriter) {
this.writer = outputStreamWriter;
}
@Override
public void write(int b) throws IOException {
writer.write(b);
}
}
}
private static final class PolyglotCompiledScript extends CompiledScript {
private final Source source;
private final ScriptEngine engine;
public PolyglotCompiledScript(Source src, ScriptEngine engine) {
this.source = src;
this.engine = engine;
}
@Override
public Object eval(ScriptContext context) throws ScriptException {
if (context instanceof PolyglotContext) {
return ((PolyglotContext) context).getContext().eval(source).as(Object.class);
}
throw new UnsupportedOperationException(
"Polyglot CompiledScript instances can only be evaluated in Polyglot.");
}
@Override
public ScriptEngine getEngine() {
return engine;
}
}
private static final class PolyglotBindings implements Bindings {
private Value languageBindings;
PolyglotBindings(Value languageBindings) {
this.languageBindings = languageBindings;
}
@Override
public int size() {
return keySet().size();
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean containsValue(Object value) {
for (String s : keySet()) {
if (get(s) == value) {
return true;
}
}
return false;
}
@Override
public void clear() {
for (String s : keySet()) {
remove(s);
}
}
@Override
public Set keySet() {
return languageBindings.getMemberKeys();
}
@Override
public Collection