在 Java 生態系統中,靜態類型與編譯時檢查雖然帶來了健壯性,但有時我們需要更靈活、動態的方式來解決小型問題——例如快速原型驗證、配置邏輯外部化、或是讓非 Java 開發者參與業務規則編寫。這時,javax.script(Java Scripting API)應運而生。自 Java 6 起納入標準庫,它橋接了 Java 平台與各類腳本語言(JavaScript、Groovy、JRuby、Python 等),讓開發者能在 Java 代碼中無縫嵌入腳本,兼得「腳本的敏捷」與「Java 的生態」。
JDK bin 目錄下的 jrunscript 是一個輕量級工具,能直接運行 ECMAScript(JavaScript)檔案或進入 REPL 互動模式。它解決了傳統 Shell 腳本跨平台(Windows/Linux)維護困難的痛點:
.bat 與 .sh,一份 .js 檔案即可在所有安裝 JRE 的機器上運行。// periodic.js
while (true) {
var args = ["-histo", arguments[0]]; // arguments[0] 為傳入的 VMID
Packages.sun.tools.jmap.JMap.main(args); // 直接調用 jmap 的 main()
java.lang.Thread.sleep(5000);
}
執行方式:jrunscript periodic.js 12345(12345 為目標 Java 進程 PID)。
腳本引擎運行於 JVM 之上,因此腳本可直接使用 Java 類庫:
Java.type("java.lang.Runtime") 或 Packages.java.lang.Runtime(Rhino 風格)。obj.prop 等同 obj.getProp())。JavaObject[] 或 List。var proc = Java.type("java.lang.ProcessBuilder").start(["jmap", "-histo", pid]);
var reader = new Java.type("java.io.BufferedReader")(
new Java.type("java.io.InputStreamReader")(proc.getInputStream())
);
var line;
while ((line = reader.readLine()) != null) {
print(line);
}
proc.waitFor();
核心類:ScriptEngineManager、ScriptEngine、ScriptContext、Bindings。
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); // 或 "graal.js"
engine.eval(new FileReader("rules.js")); // 執行檔案
engine.eval("print('Hello from script')"); // 執行字串
Object result = engine.eval("function add(a,b){return a+b} add(1,2)"); // 取得返回值
進階用法:
Invocable 介面:調用腳本中定義的特定函數,並傳遞 Java 物件作為參數。讓腳本能「看見」Java 端準備好的資料模型:
Bindings bindings = engine.createBindings();
bindings.put("user", new User("Alice", 30));
bindings.put("config", loadConfigMap());
ScriptContext ctx = new SimpleScriptContext();
ctx.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
engine.eval(scriptReader, ctx);
腳本中直接使用:print(user.name); config.get("timeout")。
頻繁執行的腳本(如模板渲染、規則引擎)每次 eval 都會解析 AST,開銷可觀。Compilable 允許預編譯為 CompiledScript:
if (engine instanceof Compilable) {
CompiledScript compiled = ((Compilable) engine).compile(scriptReader);
// 重複使用,傳入不同 Bindings
for (Bindings b : bindingList) {
compiled.eval(b);
}
}
注意:腳本內容變更需重新編譯;CompiledScript 非線程安全,多線程環境下需同步或使用線程本地儲存。
| 場景 | 建議 |
|---|---|
| 高併發 Web 應用 | 使用 GraalVM Polyglot API 替代 Nashorn,啟用腳本緩存,限制腳本執行時間(Context.Builder().maxCPUTime())。 |
| 用戶提交腳本 | 嚴格沙箱:禁止 java.lang.Runtime、反射、文件 IO;使用 ScriptContext 隔離作用域。 |
| 長期運行的批次任務 | 預編譯 CompiledScript,定期重新載入以拾取更新。 |
| 多語言混合專案 | 統一用 ScriptEngineManager 管理,避免重複初始化引擎實例。 |
javax.script 讓 Java 擁抱了動態語言的表達力,無論是運維自動化、業務規則外部化、還是為最終用戶提供腳本擴展點,它都是輕量、標準且強大的選擇。隨著 GraalVM 的普及,Java 上的多語言互操作將更加高效、安全,值得每位 Java 開發者深入掌握。

Ted Neward | Java Champion, 作家與架構師