ScriptEngine 實作

GraalJS 提供符合 JSR-223 標準的 javax.script.ScriptEngine 實作,用於執行 JavaScript。請注意,提供此功能是為了舊版相容性,以便讓目前基於 ScriptEngine 的實作更容易遷移。我們強烈建議使用者使用 org.graalvm.polyglot.Context 介面來直接控制許多設定,並從 GraalVM 中更精細的安全設定中受益。

注意:從適用於 JDK 21 的 GraalVM 開始,GraalVM 預設不再包含 ScriptEngine。如果您依賴該功能,則必須遷移您的設定,明確地依賴於 script engine 模組並將其新增至模組路徑

若要啟用 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(<選項名稱>, 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 實作支援 JavaScript 來源檔案的 js 檔案副檔名,以及 ES 模組的 mjs 副檔名。

與我們聯繫