天天看點

java腳本_Java SE 6 新特性

Java SE 6 新特性

對腳本語言的支援

java腳本_Java SE 6 新特性

邱 小俠

2007 年 9 月 28 日釋出

系列内容:

此内容是該系列 # 部分中的第 # 部分: Java SE 6 新特性

http://www.ibm.com/developerworks/cn/java/j-lo-jse6/

敬請期待該系列的後續内容。

此内容是該系列的一部分:Java SE 6 新特性

敬請期待該系列的後續内容。

Java 腳本 API 概述

腳本引擎

腳本引擎就是指腳本的運作環境,它能能夠把運作其上的解釋性語言轉換為更底層的彙編語言,沒有腳本引擎,腳本就無法被運作。

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 庫就能被各種腳本語言所利用,節省了開發成本縮短了開發周期;其次,可以把一些複雜異變的業務邏輯交給腳本語言處理,這又大大提高了開發效率。

在 javax.script 包中定義的實作類并不多,主要是一些接口和對應的抽象類,圖 1 顯示了其中包含的各個接口和類。

圖 1. javax.script 包概況

java腳本_Java SE 6 新特性
java腳本_Java SE 6 新特性

這個包的具體實作類少的根本原因是這個包隻是定義了一個程式設計接口的架構規範,至于對如何解析運作具體的腳本語言,還需要由第三方提供實作。雖然這些腳本引擎的實作各不相同,但是對于 Java 腳本 API 的使用者來說,這些具體的實作被很好的隔離隐藏了。Java 腳本 API 為開發者提供了如下功能:擷取腳本程式輸入,通過腳本引擎運作腳本并傳回運作結果,這是最核心的接口。

發現腳本引擎,查詢腳本引擎資訊。

通過腳本引擎的運作上下文在腳本和 Java 平台間交換資料。

通過 Java 應用程式調用腳本函數。

在詳細介紹這四個功能之前,我們先通過一個簡單的例子來展示如何通過 Java 語言來運作腳本程式,這裡仍然以經典的“Hello World”開始。

清單 1. Hello Worldimport 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 Scriptpublic 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);

}

}

清單 2 代碼,從指令行分别擷取腳本名稱和腳本檔案名,程式通過腳本名稱建立對應的腳本引擎執行個體,通過腳本名稱指定的腳本檔案名讀入腳本程式運作。運作下面這個指令,就能在 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. 腳本引擎發現機制時序圖

java腳本_Java SE 6 新特性
java腳本_Java SE 6 新特性

ScriptEngineFactory 接口的實作類被用來描述和執行個體化 ScriptEngine 接口,每一個實作 ScriptEngine 接口的類會有一個對應的工廠類來描述其中繼資料(meta

data),ScriptEngineFactory 接口定義了許多函數供 ScriptEngineManager 查詢這些中繼資料,ScriptEngineManager 會根據這些中繼資料查找需要的腳本引擎,表 2列出了可供使用的函數:

表 2. ScriptEngineFactory 提供的查詢函數函數描述String getEngineName()傳回腳本引擎的全稱

String getEngineVersion()傳回腳本引擎的版本資訊

String getLanguageName()傳回腳本引擎所支援的腳本語言的名稱

String getLanguageVersion()傳回腳本引擎所支援的腳本語言的版本資訊

List getExtensions()傳回一個腳本檔案擴充名組成的 List,目前腳本引擎支援解析這些擴充名對應的腳本檔案

List getMimeTypes()傳回一個與目前引擎關聯的所有 mimetype 組成的 List

List 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 中的分布

java腳本_Java SE 6 新特性
java腳本_Java SE 6 新特性

從 圖 3 中可以看到,共有三個級别的地方可以存取屬性,分别是 ScriptEngineManager 中的 Bindings,ScriptEngine 執行個體對應的 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腳本_Java SE 6 新特性

除了能在 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();

compiledScript.eval();

}

}

}

與 InovcableTest 類似,也應該先通過 instanceof 操作判斷腳本引擎是否支援編譯操作,防止預料外的異常抛出。并且我們可以發現同一段編譯過的腳本,在第二次運作時 greeting 變量的内容被上一次的運作改變了,導緻輸出不一緻:

圖 5. 程式 CompilableTest 的輸出

java腳本_Java SE 6 新特性

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 平台上運作自己的腳本語言,這對于衆多開發者來說都是非常有誘惑力的。

相關主題閱讀 Java SE 6 新特性系列 文章的完整清單,了解 Java SE 6 其它重要的增強。

developerWorks 文章“動态調用動态語言,第 1 部分: 引入 Java 腳本 API”:介紹了 Java 腳本 API 的各種特性,并使用一個簡單的 Hello World 應用程式展示 Java 代碼如何執行腳本代碼以及腳本如何反過來執行 Java 代碼。

developerWorks 文章“動态調用動态語言,第 2 部分: 在運作時尋找、執行和修改腳本”:進一步講解了 Java 腳本 API 的功能,示範如何在無需停止并重新啟動應用程式的情況下,在運作時執行外部 Ruby、Groovy 和 JavaScript 腳本以修改業務邏輯。

developerWorks 文章“給 Java SE 注入腳本語言的活力”:這篇文章在 Java SE 6 正式釋出之前預覽了 Java 腳本 API 的功能。

Java SE 6 文檔:Java SE 6 的規範文檔,可以找到絕大部分新特性的官方說明。

JSR 223:較長的描述了腳本語言和 Java 平台互動的規範。

Java SE 6 的規範文檔上關于 Java 腳本程式設計的 教程。

這個網站 為許多著名腳本語言的腳本引擎提供了對 JSR 223 的支援。

Sun 的 Java SE 6 實作包含了 Rhino 版本 1.6R2 的腳本引擎,Rhino 是一個完全用 Java 開發的開源的 JavaScript 實作。