Java SE 6 內建 JSR 223: Scripting for the Java Platform,標誌著 JVM 正式從單一語言運行時進化為多語言虛擬機。預設僅包含 Mozilla Rhino (JavaScript/ECMAScript),但通過標準 SPI 機制,任意符合 JSR 223 規範的腳本引擎(Groovy、JRuby、Jython、PHP、Scala 等)皆可即插即用。
import javax.script.*;
import java.util.List;
import static java.lang.System.*;
public class ListScriptEngines {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = manager.getEngineFactories();
for (ScriptEngineFactory factory : factories) {
out.printf("引擎名稱: %s%n版本: %s%n語言: %s %s%n副檔名: %s%nMIME: %s%n別名: %s%n%n",
factory.getEngineName(),
factory.getEngineVersion(),
factory.getLanguageName(),
factory.getLanguageVersion(),
factory.getExtensions(),
factory.getMimeTypes(),
factory.getNames());
}
}
}
引擎名稱: Mozilla Rhino
版本: 1.6 release 2
語言: ECMAScript 1.6
副檔名: [js]
MIME: [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
別名: [js, rhino, JavaScript, javascript, ECMAScript, ecmascript]
| 方法 | 參數範例 | 適用場景 |
|---|---|---|
getEngineByExtension("js") | 副檔名 | 根據腳本文件後綴自動匹配 |
getEngineByMimeType("text/javascript") | MIME 類型 | Web 環境基於 Content-Type 選擇 |
getEngineByName("javascript") | 別名列表任意項 | 代碼中顯式指定,最常用 |
eval() 執行ScriptEngine engine = manager.getEngineByName("javascript");
Double hour = (Double) engine.eval(
"var date = new Date();" +
"date.getHours();" // 最後表達式值自動返回
);
關鍵特性: 最後一條語句的值作為 eval() 返回值(類似 C 逗號運算符),無需顯式 return。
// Java 物件暴露給腳本
engine.put("user", new User("Alice", 25));
engine.put("config", System.getProperties());
// 腳本調用 Java 方法
Object result = engine.eval(
"user.getName().toUpperCase() + ' - ' + config.getProperty('os.name')"
);
// 腳本定義函數供 Java 調用
engine.eval("function greet(name) { return 'Hello, ' + name + '!'; }");
Invocable invocable = (Invocable) engine;
String greeting = (String) invocable.invokeFunction("greet", "World");
// 隔離命名空間:每個用戶獨立 ScriptContext
ScriptContext context = new SimpleScriptContext();
context.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
context.getBindings(ScriptContext.ENGINE_SCOPE).put("data", userData);
// 預編譯:重複執行同一腳本提升效能
Compilable compilable = (Compilable) engine;
CompiledScript compiled = compilable.compile(scriptText);
for (int i = 0; i < 1000; i++) {
compiled.eval(context); // 無需重複解析
}
groovy-all-x.y.z.jar$JAVA_HOME/jre/lib/ext/ 或應用類路徑META-INF/services/javax.script.ScriptEngineFactory 存在ListScriptEngines 驗證出現 Groovy Scripting Engine| 引擎 | 語言 | 適用場景 | 效能特徵 | Java 整合深度 |
|---|---|---|---|---|
| Rhino (內建) | JavaScript | 規則引擎、模板、輕量腳本化 | 解釋執行,啟動快 | 完整 API 訪問 |
| Groovy | Groovy | DSL、測試腳本、建構腳本 | 編譯為字節碼,熱加載強 | 無縫,語法高度兼容 Java |
| JRuby | Ruby | Rails 應用嵌入、元編程場景 | JIT 編譯,長期運行效能佳 | 雙向透明映射 |
| Jython | Python | 科學計算腳本、數據分析嵌入 | 解釋為主 | 良好,部分 C 擴展不支援 |
| Nashorn (Java 8+) | JavaScript | 替代 Rhino,`invokedynamic` 優化 | 極高,接近 V8 | 現代 JS 特性全支援 |
將定價、促銷、風控等高頻變更邏輯剝離為 .js/.groovy 文件,熱部署更新無需重啟 JVM。
SaaS 產品提供「安全沙箱」讓租戶編寫腳本自定義工作流、報表計算,配合 ScriptContext 隔離與白名單機制保障安全。
整合 FreeMarker/Velocity 之外的輕量選項:Rhino 執行 JavaScript 模板,共享前後端渲染邏輯(Isomorphic Rendering 前身)。
ClassLoader 或 SecurityManager 限制腳本訪問敏感 API (Runtime.exec、System.exit、文件 I/O)CompiledScript,避免重複解析開銷JSR 223 將 Java 生態推向「語言無關」新紀元。合理運用腳本引擎,可在保持 Java 型別安全、效能優勢的同時,引入動態語言的表達力與熱更新能力。關鍵在於:識別適合腳本化的業務邊界,建立安全隔離與治理機制,而非濫用導致系統不可維護。