Java SE 6 新特性: 對腳本語言的支持

2006 年末,Sun 公司發佈了 Java Standard Edition 6(Java SE 6)的最終正式版,代號 Mustang(野馬)。跟 Tiger(Java SE 5)相比,Mustang 在性能方面有了不錯的提高。與 Tiger 在 API 庫方面的大幅度增強相比,雖然 Mustang 在 API 庫方面的新特性顯得不太多,可是也提供了許多實用和方便的功能:在腳本,WebService,XML,編譯器 API數據庫JMX網絡 和 Instrumentation 方面都有不錯的新特性和功能增強。javascript

Java 腳本 API 概述

腳本引擎

腳本引擎就是指腳本的運行環境,它能可以把運行其上的解釋性語言轉換爲更底層的彙編語言,沒有腳本引擎,腳本就沒法被運行。html

Java SE 6 引入了對 Java Specification Request(JSR)223 的支持,JSR 223 旨在定義一個統一的規範,使得 Java 應用程序能夠經過一套固定的接口與各類腳本引擎交互,從而達到在 Java 平臺上調用各類腳本語言的目的。javax.script 包定義了這些接口,即 Java 腳本編程 API。Java 腳本 API 的目標與 Apache 項目 Bean Script Framework(BSF)相似,經過它 Java 應用程序就能經過虛擬機調用各類腳本,同時,腳本語言也能訪問應用程序中的 Java 對象和方法。Java 腳本 API 是連通 Java 平臺和腳本語言的橋樑。首先,經過它爲數衆多的現有 Java 庫就能被各類腳本語言所利用,節省了開發成本縮短了開發週期;其次,能夠把一些複雜異變的業務邏輯交給腳本語言處理,這又大大提升了開發效率。java

在 javax.script 包中定義的實現類並很少,主要是一些接口和對應的抽象類,圖 1 顯示了其中包含的各個接口和類。shell

圖 1. javax.script 包概況

這個包的具體實現類少的根本緣由是這個包只是定義了一個編程接口的框架規範,至於對如何解析運行具體的腳本語言,還須要由第三方提供實現。雖然這些腳本引擎的實現各不相同,可是對於 Java 腳本 API 的使用者來講,這些具體的實現被很好的隔離隱藏了。Java 腳本 API 爲開發者提供了以下功能:數據庫

  1. 獲取腳本程序輸入,經過腳本引擎運行腳本並返回運行結果,這是最核心的接口。
  2. 發現腳本引擎,查詢腳本引擎信息。
  3. 經過腳本引擎的運行上下文在腳本和 Java 平臺間交換數據。
  4. 經過 Java 應用程序調用腳本函數。

在詳細介紹這四個功能以前,咱們先經過一個簡單的例子來展現如何經過 Java 語言來運行腳本程序,這裏仍然以經典的「Hello World」開始。編程

清單 1. Hello World
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')");
    }
}

這個例子很是直觀,只要經過 ScriptEngineManager 和 ScriptEngine 這兩個類就能夠完成最簡單的調用。首先,ScriptEngineManager 實例建立一個 ScriptEngine 實例,而後返回的 ScriptEngine 實例解析 JavaScript 腳本,輸出運行結果。運行這段程序,終端上會輸出「Hello World「。在執行 eval 函數的過程當中可能會有 ScriptEngine 異常拋出,引起這個異常被拋出的緣由通常是由腳本輸入語法有誤形成的。在對整個 API 有了大體的概念以後,咱們就能夠開始介紹各個具體的功能了。緩存

 

使用腳本引擎運行腳本

Java 腳本 API 經過腳本引擎來運行腳本,整個包的目的就在於統一 Java 平臺與各類腳本引擎的交互方式,制定一個標準,Java 應用程序依照這種標準就能自由的調用各類腳本引擎,而腳本引擎按照這種標準實現,就能被 Java 平臺支持。每個腳本引擎就是一個腳本解釋器,負責運行腳本,獲取運行結果。ScriptEngine 接口是腳本引擎在 Java 平臺上的抽象,Java 應用程序經過這個接口調用腳本引擎運行腳本程序,並將運行結果返回給虛擬機。網絡

ScriptEngine 接口提供了許多 eval 函數的變體用來運行腳本,這個函數的功能就是獲取腳本輸入,運行腳本,最後返回輸出。清單 1 的例子中直接經過字符串做爲 eval 函數的參數讀入腳本程序。除此以外,ScriptEngine 還提供了以一個 java.io.Reader 做爲輸入參數的 eval 函數。腳本程序實質上是一些能夠用腳本引擎執行的字節流,經過一個 Reader 對象,eval 函數就能從不一樣的數據源中讀取字節流來運行,這個數據源能夠來自內存、文件,甚至直接來自網絡。這樣 Java 應用程序就能直接利用項目原有的腳本資源,無需以 Java 語言對其進行重寫,達到腳本程序與 Java 平臺無縫集成的目的。清單 2 即展現瞭如何從一個文件中讀取腳本程序並運行,其中如何經過 ScriptEngineManager 獲取ScriptEngine 實例的細節會在後面詳細介紹。框架

清單 2. Run Script
public class RunScript {

    public static void main(String[] args) throws Exception {
        String script = args[0];
        String file = args[1];
       
        FileReader scriptReader = new FileReader(new File(file));
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName(script);
        engine.eval(scriptReader);
    }
}

 代碼,從命令行分別獲取腳本名稱和腳本文件名,程序經過腳本名稱建立對應的腳本引擎實例,經過腳本名稱指定的腳本文件名讀入腳本程序運行。運行下面這個命令,就能在 Java 平臺上運行全部的 JavaScript 腳本。編程語言

java RunScript javascript run.js

經過這種方式,Java 應用程序能夠把一些複雜易變的邏輯過程,用更加靈活的弱類型的腳本語言來實現,而後經過 javax.Script 包提供的 API 獲取運行結果,當腳本改變時,只需替換對應的腳本文件,而無需從新編譯構建項目,好處是顯而易見的,即節省了開發時間又提升了開發效率。

EngineScript 接口分別針對 String 輸入和 Reader 輸入提供了三個不一樣形態的 eval 函數,用於運行腳本:

表 1. ScriptEngine 的 eval 函數
函數 描述
Object eval(Reader reader) 從一個 Reader 讀取腳本程序並運行
Object eval(Reader reader, Bindings n) 以 n 做爲腳本級別的綁定,從一個 Reader 讀取腳本程序並運行
Object eval(Reader reader, ScriptContext context) 在 context 指定的上下文環境下,從一個 Reader 讀取腳本程序並運行
Object eval(String script) 運行字符串表示的腳本
Object eval(String script, Bindings n) 以 n 做爲腳本級別的綁定,運行字符串表示的腳本
Object eval(String script, ScriptContext context) 在 context 指定的上下文環境下,運行字符串表示的腳本

Java 腳本 API 還爲 ScriptEngine 接口提供了一個抽象類 —— AbstractScriptEngine,這個類提供了其中四個 eval 函數的默認實現,它們分別經過調用 eval(Reader,ScriptContext) 或 eval(String, ScriptContext) 來實現。這樣腳本引擎提供者,只需繼承這個抽象類並提供這兩個函數實現便可。AbstractScriptEngine 有一個保護域 context 用於保存默認上下文的引用,SimpleScriptContext 類被做爲AbstractScriptEngine 的默認上下文。關於上下文環境,將在後面進行詳細介紹。

 

發現和建立腳本引擎

在前面的兩個例子中,ScriptEngine 實例都是經過調用 ScriptEngineManager 實例的方法返回的,而不是常見的直接經過 new 操做新建一個實例。JSR 223 中引入 ScriptEngineManager 類的意義就在於,將 ScriptEngine 的尋找和建立任務委託給 ScriptEngineManager 實例處理,達到對 API 使用者隱藏這個過程的目的,使 Java 應用程序在無需從新編譯的狀況下,支持腳本引擎的動態替換。經過ScriptEngineManager 類和 ScriptEngineFactory 接口便可完成腳本引擎的發現和建立:

  • ScriptEngineManager 類:自動尋找 ScriptEngineFactory 接口的實現類
  • ScriptEngineFactory 接口:建立合適的腳本引擎實例

Service Provider

服務(service)是指那些成爲事實上標準的接口,服務提供者(service provider)則提供了這個接口的具體實現。不一樣的提供者會遵循一樣的接口提供實現,客戶能夠自由選擇不一樣的實現。能夠從 Sun 提供的文檔 Jar 文件規約 中獲取有關 Service Provider 更詳細的信息。

ScriptEngineManager 類自己並不知道如何建立一個具體的腳本引擎實例,它會依照 Jar 規約中定義的服務發現機制,查找並建立一個合適的 ScriptEngineFactory 實例,並經過這個工廠類來建立返回實際的腳本引擎。首先,ScriptEngineManager 實例會在當前 classpath 中搜索全部可見的 Jar 包;而後,它會查看每一個 Jar 包中的 META -INF/services/ 目錄下的是否包含 javax.script.ScriptEngineFactory 文件,腳本引擎的開發者會提供在 Jar 包中包含一個 ScriptEngineFactory 接口的實現類,這個文件內容便是這個實現類的完整名字;ScriptEngineManager 會根據這個類名,建立一個 ScriptEngineFactory 接口的實例;最後,經過這個工廠類來實例化須要的腳本引擎,返回給用戶。舉例來講,第三方的引擎提供者可能升級更新了新版的腳本引擎實現,經過 ScriptEngineManager 來管理腳本引擎,無需修改一行 Java 代碼就能替換更新腳本引擎。用戶只需在 classpath 中加入新的腳本引擎實現(Jar 包的形式),ScriptEngineManager 就能經過 Service Provider 機制來自動查找到新版本實現,建立並返回對應的腳本引擎實例供調用。圖 2 所示時序圖描述了其中的步驟:

圖 2. 腳本引擎發現機制時序圖

ScriptEngineFactory 接口的實現類被用來描述和實例化 ScriptEngine 接口,每個實現 ScriptEngine 接口的類會有一個對應的工廠類來描述其元數據(meta data),ScriptEngineFactory 接口定義了許多函數供 ScriptEngineManager 查詢這些元數據,ScriptEngineManager會根據這些元數據查找須要的腳本引擎,表 2列出了可供使用的函數:

表 2. ScriptEngineFactory 提供的查詢函數
函數 描述
String getEngineName() 返回腳本引擎的全稱
String getEngineVersion() 返回腳本引擎的版本信息
String getLanguageName() 返回腳本引擎所支持的腳本語言的名稱
String getLanguageVersion() 返回腳本引擎所支持的腳本語言的版本信息
List<String> getExtensions() 返回一個腳本文件擴展名組成的 List,當前腳本引擎支持解析這些擴展名對應的腳本文件
List<String> getMimeTypes() 返回一個與當前引擎關聯的全部 mimetype 組成的 List
List<String> getNames() 返回一個當前引擎全部名稱的 List,ScriptEngineManager 能夠根據這些名字肯定對應的腳本引擎

經過 getEngineFactories() 函數,ScriptEngineManager 會返回一個包含當前環境中被發現的全部實現 ScriptEngineFactory 接口的具體類,經過這些工廠類中保存的腳本引擎信息檢索須要的腳本引擎。第三方提供的腳本引擎實現的 Jar 包中除了包含 ScriptEngine 接口的實現類以外,還須要提供 ScriptEngineFactory 接口的實現類,以及一個 javax.script.ScriptEngineFactory 文件用於指明這個工廠類。這樣,Java 平臺就能經過 ScriptEngineManager 尋找到這個工廠類,並經過這個工廠類爲用戶提供一個腳本引擎實例。Java SE 6 默認提供了 JavaScirpt 腳本引擎的實現,若是須要支持其餘腳本引擎,須要將它們對應的 Jar 包包含在 classpath 中,好比對於前面 清單 2 中的代碼,只需在運行程序前將 Groovy 的腳本引擎添加到 classpath 中,而後運行:

java RunScript groovy run.groovy

無需修改一行 Java 代碼就能以 Groovy 腳本引擎來運行 Groovy 腳本。在 這裏 爲 Java SE 6 提供了許多著名腳本語言的腳本引擎對 JSR 223 的支持,這些 Jar 必須和腳本引擎配合使用,使得這些腳本語言能被 Java 平臺支持。到目前爲止,它提供了至少 25 種腳本語言的支持,其中包括了 Groovy、Ruby、Python 等當前很是流行的腳本語言。這裏須要再次強調的是,負責建立 ScriptEngine 實例的 ScriptEngineFactory 實現類對於用戶來講是不可見的,ScriptEngingeManager 實現負責與其交互,經過它建立腳本引擎。

 

腳本引擎的運行上下文

若是僅僅是經過腳本引擎運行腳本的話,還沒法體現出 Java 腳本 API 的優勢,在 JSR 223 中,還爲全部的腳本引擎定義了一個簡潔的執行環境。咱們都知道,在 Linux 操做系統中能夠維護許多環境變量好比 classpath、path 等,不一樣的 shell 在運行時能夠直接使用這些環境變量,它們構成了 shell 腳本的執行環境。在 javax.script 支持的每一個腳本引擎也有各自對應的執行的環境,腳本引擎能夠共享一樣的環境,也能夠有各自不一樣的上下文。經過腳本運行時的上下文,腳本程序就能自由的和 Java 平臺交互,並充分利用已有的衆多 Java API,真正的站在「巨人」的肩膀上。javax.script.ScriptContext 接口和 javax.script.Bindings 接口定義了腳本引擎的上下文。

  • Bindings 接口:

    繼承自 Map,定義了對這些「鍵-值」對的查詢、添加、刪除等 Map 典型操做。Bingdings 接口其實是一個存放數據的容器,它的實現類會維護許多「鍵-值」對,它們都經過字符串表示。Java 應用程序和腳本程序經過這些「鍵-值」對交換數據。只要腳本引擎支持,用戶還能直接在Bindings 中放置 Java 對象,腳本引擎經過 Bindings 不只能夠存取對象的屬性,還能調用 Java 對象的方法,這種雙向自由的溝通使得兩者真正的結合在了一塊兒。

  • ScriptContext 接口:

    將 Bindings 和 ScriptEngine 聯繫在了一塊兒,每個 ScriptEngine 都有一個對應的 ScriptContext,前面提到過經過ScriptEnginFactory 建立腳本引擎除了達到隱藏實現的目的外,還負責爲腳本引擎設置合適的上下文。ScriptEngine 經過 ScriptContext實例就能從其內部的 Bindings 中得到須要的屬性值。ScriptContext 接口默認包含了兩個級別的 Bindings 實例的引用,分別是全局級別和引擎級別,能夠經過 GLOBAL_SCOPE 和 ENGINE_SCOPE 這兩個類常量來界定區分這兩個 Bindings 實例,其中 GLOBAL_SCOPE 從建立它的ScriptEngineManager 得到。顧名思義,全局級別指的是 Bindings 裏的屬性都是「全局變量」,只要是同一個 ScriptEngineMananger 返回的腳本引擎均可以共享這些屬性;對應的,引擎級別的 Bindings 裏的屬性則是「局部變量」,它們只對同一個引擎實例可見,從而能爲不一樣的引擎設置獨特的環境,經過同一個腳本引擎運行的腳本運行時能共享這些屬性。

ScriptContext 接口定義了下面這些函數來存取數據:

表 3. ScriptContext 存取屬性函數
函數 描述
Object removeAttribute(String name, int scope) 從指定的範圍裏刪除一個屬性
void setAttribute(String name, Object value, int scope) 在指定的範圍裏設置一個屬性的值
Object getAttribute(String name) 從上下文的全部範圍內獲取優先級最高的屬性的值
Object getAttribute(String name, int scope) 從指定的範圍裏獲取屬性值

ScriptEngineManager 擁有一個全局性的 Bindings 實例,在經過 ScriptEngineFactory 實例建立 ScriptEngine 後,它把本身的這個Bindings 傳遞給全部它建立的 ScriptEngine 實例,做爲 GLOBAL_SCOPE。同時,每個 ScriptEngine 實例都對應一個 ScriptContext 實例,這個 ScriptContext 除了從 ScriptEngineManager 那得到的 GLOBAL_SCOPE,本身也維護一個 ENGINE_SCOPE 的 Bindings 實例,全部經過這個腳本引擎運行的腳本,都能存取其中的屬性。除了 ScriptContext 能夠設置屬性,改變內部的 Bindings,Java 腳本 API 爲ScriptEngineManager 和 ScriptEngine 也提供了相似的設置屬性和 Bindings 的 API。

圖 3. Bindings 在 Java 腳本 API 中的分佈

從 圖 3 中能夠看到,共有三個級別的地方能夠存取屬性,分別是 ScriptEngineManager 中的 BindingsScriptEngine 實例對應的ScriptContext 中含有的 Bindings,以及調用 eval 函數時傳入的 Bingdings。離函數調用越近,其做用域越小,優先級越高,至關於編程語言中的變量的可見域,即 Object getAttribute(String name) 中提到的優先級。從 清單 3 這個例子中能夠看出各個屬性的存取優先級:

清單 3. 上下文屬性的做用域
import javax.script.*;

public class ScopeTest {
    public static void main(String[] args) throws Exception {
        String script=" println(greeting) ";
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        
        //Attribute from ScriptEngineManager
        manager.put("greeting", "Hello from ScriptEngineManager");
        engine.eval(script);

        //Attribute from ScriptEngine
        engine.put("greeting", "Hello from ScriptEngine");
        engine.eval(script);

        //Attribute from eval method
        ScriptContext context = new SimpleScriptContext();
        context.setAttribute("greeting", "Hello from eval method", 
            ScriptContext.ENGINE_SCOPE);
        engine.eval(script,context);
        
    }
}

JavaScript 腳本 println(greeting) 在這個程序中被重複調用了三次,因爲三次調用的環境不同,致使輸出也不同,greeting 變量每一次都被優先級更高的也就是距離函數調用越近的值覆蓋。從這個例子同時也演示瞭如何使用 ScriptContext 和 Bindings 這兩個接口,在例子腳本中並無定義 greeting 這個變量,可是腳本經過 Java 腳本 API 能方便的存取 Java 應用程序中的對象,輸出 greeting 相應的值。運行這個程序後,能看到輸出爲:

圖 4. 程序 ScopeTest 的輸出

除了能在 Java 平臺與腳本程序之間的提供共享屬性以外,ScriptContext 還容許用戶重定向引擎執行時的輸入輸出流:

表 4. ScriptContext 輸入輸出重定向
函數 描述
void setErrorWriter(Writer writer) 重定向錯誤輸出,默認是標準錯誤輸出
void setReader(Reader reader) 重定向輸入,默認是標準輸入
void setWriter(Writer writer) 重定向輸出,默認是標準輸出
Writer getErrorWriter() 獲取當前錯誤輸出字節流
Reader getReader() 獲取當前輸入流
Writer getWriter() 獲取當前輸出流

清單 4 展現瞭如何經過 ScriptContext 將其對應的 ScriptEngine 標準輸出重定向到一個 PrintWriter 中,用戶能夠經過與這個PrintWriter 連通的 PrintReader 讀取實際的輸出,使 Java 應用程序能獲取腳本運行輸出,知足更加多樣的應用需求。

清單 4. 重定向腳本輸出
import java.io.*;
import javax.script.*;

public class Redirectory {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");

        PipedReader pr = new PipedReader();
        PipedWriter pw = new PipedWriter(pr);
        PrintWriter writer = new PrintWriter(pw);
        engine.getContext().setWriter(writer);

        String script = "println('Hello from JavaScript')";
        engine.eval(script);
        
        BufferedReader br =new BufferedReader(pr);
        System.out.println(br.readLine());
    }
}

Java 腳本 API 分別爲這兩個接口提供了一個簡單的實現供用戶使用。SimpleBindings 經過組合模式實現 Map 接口,它提供了兩個構造函數。無參構造函數在內部構造一個 HashMap 實例來實現 Map 接口要求的功能;同時,SimpleBindings 也提供了一個以 Map 接口做爲參數的構造函數,容許任何實現 Map 接口的類做爲其組合的實例,以知足不一樣的要求。SimpleScriptContext 提供了 ScriptContext 簡單實現。默認狀況下,它使用了標準輸入、標準輸出和標準錯誤輸出,同時維護一個 SimpleBindings 做爲其引擎級別的 Bindings,它的默認全局級別 Bindings 爲空。

 

腳本引擎可選的接口

在 Java 腳本 API 中還有兩個腳本引擎能夠選擇是否實現的接口,這個兩個接口不是強制要求實現的,即並不是全部的腳本引擎都能支持這兩個函數,不過 Java SE 6 自帶的 JavaScript 引擎支持這兩個接口。不管如何,這兩個接口提供了很是實用的功能,它們分別是:

  • Invocable 接口:容許 Java 平臺調用腳本程序中的函數或方法。
  • Compilable 接口:容許 Java 平臺編譯腳本程序,供屢次調用。

Invocable 接口

有時候,用戶可能並不須要運行已有的整個腳本程序,而僅僅須要調用其中的一個過程,或者其中某個對象的方法,這個時候 Invocable 接口就能發揮做用。它提供了兩個函數 invokeFunction 和 invokeMethod,分別容許 Java 應用程序直接調用腳本中的一個全局性的過程以及對象中的方法,調用後者時,除了指定函數名字和參數外,還須要傳入要調用的對象引用,固然這須要腳本引擎的支持。不只如此,Invocable 接口還容許 Java 應用程序從這些函數中直接返回一個接口,經過這個接口實例來調用腳本中的函數或方法,從而咱們能夠從腳本中動態的生成 Java 應用中須要的接口對象。清單 5 演示瞭如何使用一個 Invocable 接口:

清單 5. 調用腳本中的函數
import javax.script.*;

public class CompilableTest {
    public static void main(String[] args) throws ScriptException,
            NoSuchMethodException {
        String script = " function greeting(message){println (message);}";
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        engine.eval(script);

        if (engine instanceof Invocable) {
            Invocable invocable = (Invocable) engine;
            invocable.invokeFunction("greeting", "hi");

            // It may through NoSuchMethodException 
            try {
                invocable.invokeFunction("nogreeing");
            } catch (NoSuchMethodException e) {
                // expected
            }
        }
    }
}

在調用函數前,能夠先經過 instanceof 操做判斷腳本引擎是否支持編譯操做,防止類型轉換時拋出運行時異常,須要特別注意的時,若是調用了腳本程序中不存在的函數時,運行時會拋出一個 NoSuchMethodException 的異常,實際開發中應該注意處理這種特殊狀況。

Compilable 接口

通常來講,腳本語言都是解釋型的,這也是腳本語言區別與編譯語言的一個特色,解釋性意味着腳本隨時能夠被運行,開發者能夠邊開發邊查看接口,從而省去了編譯這個環節,提供了開發效率。可是這也是一把雙刃劍,當腳本規模變大,重複解釋一段穩定的代碼又會帶來運行時的開銷。有些腳本引擎支持將腳本運行編譯成某種中間形式,這取決與腳本語言的性質以及腳本引擎的實現,能夠是一些操做碼,甚至是 Java 字節碼文件。實現了這個接口的腳本引擎能把輸入的腳本預編譯並緩存,從而提升屢次運行相同腳本的效率。

Java 腳本 API 還爲這個中間形式提供了一個專門的類,每次調用 Compilable 接口的編譯函數都會返回一個 CompiledScript 實例。CompiledScript 類被用來保存編譯的結果,從而能重複調用腳本而沒有重複解釋的開銷,實際效率提升的多少取決於中間形式的完全程度,其中間形式越接近低級語言,提升的效率就越高。每個 CompiledScript 實例對應於一個腳本引擎實例,一個腳本引擎實例能夠含有多個 CompiledScript(這很容易理解),調用 CompiledScript 的 eval 函數會傳遞給這個關聯的 ScriptEngine 的 eval 函數。關於CompiledScript 類須要注意的是,它運行時對與之對應的 ScriptEngine 狀態的改變可能會傳遞給下一次調用,形成運行結果的不一致。清單 6演示瞭如何使用 Compiable 接口來調用腳本:

清單 6. 編譯腳本
import javax.script.*;

public class CompilableTest {
    public static void main(String[] args) throws ScriptException {
        String script = " println (greeting); greeting= 'Good Afternoon!' ";
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        engine.put("greeting", "Good Morning!");
        
        if (engine instanceof Compilable) {
            Compilable compilable = (Compilable) engine;
            CompiledScript compiledScript = compilable.compile(script);
            compiledScript.eval();
        }
    }
}

與 InovcableTest 相似,也應該先經過 instanceof 操做判斷腳本引擎是否支持編譯操做,防止預料外的異常拋出。而且咱們能夠發現同一段編譯過的腳本,在第二次運行時 greeting 變量的內容被上一次的運行改變了,致使輸出不一致:

圖 5. 程序 CompilableTest 的輸出
圖 5. 程序 CompilableTest 的輸出
 

jrunscript 工具

Java SE 6 還爲運行腳本添加了一個專門的工具 —— jrunscript。jrunscript 支持兩種運行方式:一種是交互式,即邊讀取邊解析運行,這種方式使得用戶能夠方便調試腳本程序,立刻獲取預期結果;還有一種就是批處理式,即讀取並運行整個腳本文件。用戶能夠把它想象成一個萬能腳本解釋器,即它能夠運行任意腳本程序,並且它仍是跨平臺的,固然全部這一切都有一個前提,那就是必須告訴它相應的腳本引擎的位置。默認即支持的腳本是 JavaScript,這意味着用戶能夠無需任何設置,經過 jrunscript 在任何支持 Java 的平臺上運行任何 JavaScript 腳本;若是想運行其餘腳本,能夠經過 -l 指定以何種腳本引擎運行腳本。不過這個工具還是實驗性質的,不必定會包含在 Java 的後續版本中,不管如何,它還是一個很是有用的工具。

 

結束語

在 Java 平臺上使用腳本語言編程很是方便,由於 Java 腳本 API 相對其餘包要小不少。經過 javax.script 包提供的接口和類咱們能夠很方便爲咱們的 Java 應用程序添加對腳本語言的支持。開發者只要遵守 Java 腳本 API 開發應用程序,開發中就無需關注具體的腳本語言細節,應用程序就能夠動態支持任何符合 JSR 223 標準的腳本語言,不只如此,只要按照 JSR 223 標準開發,用戶甚至還能爲 Java 平臺提供一個自定義腳本語言的解釋器。在 Java 平臺上運行本身的腳本語言,這對於衆多開發者來講都是很是有誘惑力的。

相關文章
相關標籤/搜索