首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2EE

Java 腳本編程秘技:javax.script API 深度解析與實戰

     編輯:J2EE

前言:為什麼 Java 開發者需要腳本語言?

在 Java 生態系統中,靜態類型與編譯時檢查雖然帶來了健壯性,但有時我們需要更靈活、動態的方式來解決小型問題——例如快速原型驗證、配置邏輯外部化、或是讓非 Java 開發者參與業務規則編寫。這時,javax.script(Java Scripting API)應運而生。自 Java 6 起納入標準庫,它橋接了 Java 平台與各類腳本語言(JavaScript、Groovy、JRuby、Python 等),讓開發者能在 Java 代碼中無縫嵌入腳本,兼得「腳本的敏捷」與「Java 的生態」。

1. jrunscript:跨平台的命令列腳本執行器

JDK bin 目錄下的 jrunscript 是一個輕量級工具,能直接運行 ECMAScript(JavaScript)檔案或進入 REPL 互動模式。它解決了傳統 Shell 腳本跨平台(Windows/Linux)維護困難的痛點:

  • 無需分別維護 .bat 與 .sh,一份 .js 檔案即可在所有安裝 JRE 的機器上運行。
  • 內建 Rhino 引擎,支援完整 ECMAScript 3 規範(Java 8 後升級為 Nashorn,Java 15 後移除,可改用 GraalVM)。
  • 適用於定時監控、部署自動化、日誌分析等運維腳本。

範例:定期執行 jmap 監控 Java 進程

// 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)。

2. 腳本中無縫訪問 Java 物件

腳本引擎運行於 JVM 之上,因此腳本可直接使用 Java 類庫:

  • Java 類引用:使用 Java.type("java.lang.Runtime") 或 Packages.java.lang.Runtime(Rhino 風格)。
  • 物件建構與方法調用:語法與 Java 幾乎一致,支援 JavaBeans 屬性簡寫(obj.prop 等同 obj.getProp())。
  • 集合與陣列互通:JS 陣列自動轉換為 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();

3. 從 Java 代碼調用腳本:ScriptEngine 核心流程

核心類: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 物件作為參數。
  • 多腳本引擎共存:同一 JVM 可同時註冊 Nashorn、GraalJS、Jython 等,按名稱切換。

4. 綁定 Java 物件到腳本作用域:Bindings 與 ScriptContext

讓腳本能「看見」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")。

5. 編譯腳本提升效能:Compilable 介面

頻繁執行的腳本(如模板渲染、規則引擎)每次 eval 都會解析 AST,開銷可觀。Compilable 允許預編譯為 CompiledScript:

if (engine instanceof Compilable) {
    CompiledScript compiled = ((Compilable) engine).compile(scriptReader);
    // 重複使用,傳入不同 Bindings
    for (Bindings b : bindingList) {
        compiled.eval(b);
    }
}

注意:腳本內容變更需重新編譯;CompiledScript 非線程安全,多線程環境下需同步或使用線程本地儲存。

6. 最佳實踐與避坑指南

場景建議
高併發 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, 作家與架構師

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved