ScriptEngine 实现

GraalJS 提供了一个符合 JSR-223 规范的 javax.script.ScriptEngine 实现,用于运行 JavaScript。请注意,此功能是出于兼容性考虑而提供的,以便基于 ScriptEngine 的现有实现能够更轻松地进行迁移。我们强烈建议用户使用 org.graalvm.polyglot.Context 接口直接控制多项设置,并受益于 GraalVM 中更精细的安全设置。

注意:自 GraalVM for JDK 21 起,GraalVM 默认不再包含 ScriptEngine。如果您依赖此功能,则需要迁移您的设置,以显式依赖脚本引擎模块并将其添加到 module path(模块路径)中。

要启用 js-scriptengine 模块,请将其添加为 Maven 依赖项,如下所示:

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>${graaljs.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>${graaljs.version}</version>
    <type>pom</type>
</dependency>

如果您未使用 Maven,则需要手动将 js-scriptengine.jar 文件添加到模块路径,例如 --module-path=languages/js/graaljs-scriptengine.jar。在某些情况下,您可能还需要在命令行中添加 --add-modules org.graalvm.js.scriptengine,以确保能够找到 ScriptEngine。仅当您想直接使用 GraalJSScriptEngine 时(见下文),才需要显式依赖于 org.graalvm.js.scriptengine 模块。最后,也可以使用 jlink 来生成包含 GraalJS 的 ScriptEngine 的自定义 Java 运行时映像。

示例 pom.xml 文件可在 GitHub 上的 GraalJS 仓库中找到。

使用建议 #

为避免 JavaScript 源文件不必要的重新编译,建议使用 CompiledScript.eval 而非 ScriptEngine.eval。只要相应的 CompiledScript 对象存在,这就能防止 JIT 编译的代码被垃圾回收。

单线程示例

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
CompiledScript script = ((Compilable) engine).compile("console.log('hello world');");
script.eval();

多线程示例

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
CompiledScript script = ((Compilable) engine).compile("console.log('start');var start = Date.now(); while (Date.now()-start < 2000);console.log('end');");
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            // Create ScriptEngine for this thread (with a shared polyglot Engine)
            ScriptEngine engine = manager.getEngineByName("js");
            script.eval(engine.getContext());
        } catch (ScriptException scriptException) {
            scriptException.printStackTrace();
        }
    }
}).start();
script.eval();

通过 Bindings 设置选项 #

ScriptEngine 接口不提供设置选项的默认方式。作为一种变通方法,GraalJSScriptEngine 支持通过 Bindings 设置一些 Context 选项。这些选项包括:

  • polyglot.js.allowHostAccess <boolean>
  • polyglot.js.allowNativeAccess <boolean>
  • polyglot.js.allowCreateThread <boolean>
  • polyglot.js.allowIO <boolean>
  • polyglot.js.allowHostClassLookup <boolean or Predicate<String>>
  • polyglot.js.allowHostClassLoading <boolean>
  • polyglot.js.allowAllAccess <boolean>
  • polyglot.js.nashorn-compat <boolean>
  • polyglot.js.ecmascript-version <String>

这些选项控制应用于评估的 JavaScript 代码的沙盒规则,并且默认设置为 false,除非应用程序是在 Nashorn 兼容模式下启动的 (--js.nashorn-compat=true)。

请注意,使用 ScriptEngine 意味着允许实验性选项。这是通过 Bindings 传递的允许选项的详尽列表;如果您需要将其他选项传递给 GraalJS,则需要手动创建 Context,如下所示。

要通过 Bindings 设置选项,请在引擎的脚本上下文初始化**之前**使用 Bindings.put(。请注意,即使调用 Bindings#get(String) 也可能导致上下文初始化。以下代码展示了如何通过 Bindings 启用 polyglot.js.allowHostAccess

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
bindings.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // it will not work without allowHostAccess and allowHostClassLookup

如果用户在调用 bindings.put("polyglot.js.allowHostAccess", true); 之前调用了(例如)engine.eval("var x = 1;"),则此示例将不起作用,因为任何对 eval 的调用都会强制进行上下文初始化。

通过系统属性设置选项 #

在启动 JVM 之前,可以通过在系统属性前添加 polyglot. 来为 JavaScript 引擎设置选项。

java -Dpolyglot.js.ecmascript-version=2022 MyApplication

或者,可以在创建 ScriptEngine 之前,在 Java 应用程序中以编程方式设置 JavaScript 引擎的选项。然而,这仅适用于传递给 JavaScript 引擎的选项(例如 js.ecmascript-version),而不适用于示例中提及的可以通过 Bindings 设置的选项。另一个需要注意的是,这些系统属性会由所有并发执行的 ScriptEngine 共享。

手动创建 Context 以获得更大灵活性 #

Context 选项也可以通过 Context.Builder 实例直接传递给 GraalJSScriptEngine

ScriptEngine engine = GraalJSScriptEngine.create(null,
        Context.newBuilder("js")
        .allowHostAccess(HostAccess.ALL)
        .allowHostClassLookup(s -> true)
        .option("js.ecmascript-version", "2022"));
engine.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));");

这允许设置 GraalJS 中所有可用的选项。但其代价是需要硬性依赖 GraalJS,例如 GraalJSScriptEngineContext 类。

支持的文件扩展名 #

GraalJS 的 javax.script.ScriptEngine 实现支持 JavaScript 源文件的 *js* 文件扩展名,以及 ES 模块的 *mjs* 扩展名。

联系我们