脚本引擎实现

GraalJS 提供了一个符合 JSR-223 的 javax.script.ScriptEngine 实现,用于运行 JavaScript。请注意,此功能出于遗留原因提供,以便为当前基于 ScriptEngine 的实现提供更轻松的迁移。我们强烈建议用户使用 org.graalvm.polyglot.Context 接口直接控制许多设置,并从 GraalVM 中更细粒度的安全设置中获益。

注意:从 GraalVM for JDK 21 开始,GraalVM 默认情况下不再包含 ScriptEngine。如果您依赖它,则需要将您的设置迁移为显式依赖脚本引擎模块,并将它添加到模块路径中。

要启用 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 运行时映像。

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

使用建议 #

为了避免不必要地重新编译 JavaScript 源代码,建议使用 CompiledScript.eval 而不是 ScriptEngine.eval。这可以防止 JIT 编译的代码在相应的 CompiledScript 对象处于活动状态时被垃圾回收。

单线程示例

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 或 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(<option name>, true)。请注意,即使调用 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.Builder 实例将 Context 选项直接传递给 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 类。

支持的文件扩展名 #

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

与我们联系