Java SE 6 (Mustang) 引入 JSR 223: Scripting for the Java Platform,定義統一的 javax.script API,讓 Java 應用能以標準方式調用 JavaScript、Groovy、Ruby、Python 等 25+ 種腳本引擎。本文系統講解腳本引擎發現機制、執行上下文、資料交換、函數調用、腳本編譯緩存等核心功能,附完整代碼範例。
┌─────────────────────────────────────────────────────────────┐
│ Java Application │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ScriptEngineManager │ │ ← 統一入口:發現/建立引擎
│ │ • getEngineByName("JavaScript") │ │
│ │ • getEngineFactories() │ │
│ └─────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────▼──────────────────────────────────┐ │
│ │ ScriptEngine (介面) │ │ ← 抽象腳本引擎
│ │ • eval(String/Reader) │ │
│ │ • getContext() / setContext() │ │
│ │ • getBindings() / setBindings() │ │
│ └─────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────▼──────────────────────────────────┐ │
│ │ ScriptContext / Bindings │ │ ← 執行上下文與資料交換區
│ │ • ENGINE_SCOPE (引擎級) GLOBAL_SCOPE (全域) │ │
│ │ • put/get/remove 屬性 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
JavaScript Groovy JRuby / Jython / ...
(內建) (需 Jar) (需 Jar)
import javax.script.*;
public class HelloWorld {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.eval("print('Hello World')"); // 輸出: Hello World
}
}
public class RunScript {
public static void main(String[] args) throws Exception {
String engineName = args[0]; // "javascript", "groovy", ...
String scriptFile = args[1]; // "script.js", "script.groovy"
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName(engineName);
engine.eval(new FileReader(new File(scriptFile)));
}
}
// 執行: java RunScript javascript script.js
ScriptEngineManager 掃描 Classpath 所有 JARMETA-INF/services/javax.script.ScriptEngineFactory 檔案com.sun.script.javascript.RhinoScriptEngineFactory)factory.getScriptEngine() 產生引擎優勢:零代碼切換/升級腳本引擎,只需替換 JAR。

三層作用域 (優先級遞增):
| 層級 | 來源 | 可見範圍 | 典型用途 |
|---|---|---|---|
| GLOBAL_SCOPE | ScriptEngineManager | 同一 Manager 建立的所有引擎 | 全域配置、共用服務物件 |
| ENGINE_SCOPE | ScriptEngine 實例 | 單一引擎實例內的所有腳本 | 引擎級共享變數、函數庫 |
| 局部 (eval 參數) | eval(script, bindings/context) | 單次 eval 調用 | 臨時參數、覆蓋測試 |
public class ScopeTest {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
String script = "println(greeting)";
// 1. 全域層
manager.put("greeting", "Hello from ScriptEngineManager");
engine.eval(script); // 輸出: Hello from ScriptEngineManager
// 2. 引擎層 (覆蓋全域)
engine.put("greeting", "Hello from ScriptEngine");
engine.eval(script); // 輸出: Hello from ScriptEngine
// 3. 局部層 (最高優先級)
ScriptContext ctx = new SimpleScriptContext();
ctx.setAttribute("greeting", "Hello from eval method", ScriptContext.ENGINE_SCOPE);
engine.eval(script, ctx); // 輸出: Hello from eval method
}
}


engine.put("myList", new ArrayList()); // 放入 Java 集合
engine.eval("myList.add('from JS'); println(myList.size())"); // 腳本直接調用 Java 方法
PipedReader pr = new PipedReader();
PipedWriter pw = new PipedWriter(pr);
engine.getContext().setWriter(new PrintWriter(pw));
engine.eval("println('captured')");
new BufferedReader(pr).readLine(); // 讀取腳本輸出
String script = "function greeting(msg) { println(msg); }";
engine.eval(script);
if (engine instanceof Invocable) {
Invocable inv = (Invocable) engine;
inv.invokeFunction("greeting", "hi from Java"); // 直接調用腳本函數
// inv.invokeMethod(obj, "methodName", args); // 調用腳本物件方法
}
注意:調用不存在函數拋出 NoSuchMethodException,需捕獲。
String script = "println(greeting); greeting = 'Good Afternoon!'"; // 有副作用
engine.put("greeting", "Good Morning!");
if (engine instanceof Compilable) {
Compilable comp = (Compilable) engine;
CompiledScript cs = comp.compile(script); // 編譯為中間碼/位元碼
cs.eval(); // 第一次: Good Morning!
cs.eval(); // 第二次: Good Afternoon! (狀態被修改)
}

關鍵特性:CompiledScript 可重複執行避免重複解析,但**狀態變更會保留**,適用於無副作用或刻意保留狀態的場景。
jrunscript → 直接輸入腳本行,即時執行 (REPL)jrunscript script.js 或 jrunscript -l groovy script.groovy官方彙整頁面提供 25+ 語言 JSR 223 適配器:Groovy、JRuby、Jython、Scala、Clojure、BeanShell、Rhino (JS)、Lua、Scheme、Tcl 等。
使用方式:下載對應 JAR 放入 Classpath,getEngineByName("groovy") 即可使用,Java 代碼零修改。
SecurityManager 限制腳本存取檔案/網路/反射ScriptException (語法錯誤)、NoSuchMethodException (函數不存在)JSR 223 讓 Java 平台成為多語言執行環境。善用腳本語言處理「易變業務規則、模板渲染、動態配置、DSL 表達」,能大幅提升開發彈性與交付速度。