<!----><!----><!---->2006 年底,Sun 公司發(fā)布了 Java Standard Edition 6(Java SE 6)的最終正式版,代號 Mustang(野馬)。跟 Tiger(Java SE 5)相比,Mustang 在性能方面有了不錯的提升。與 Tiger 在 API 庫方面的大幅度加強(qiáng)相比,雖然 Mustang 在 API 庫方面的新特性顯得不太多,但是也提供了許多實(shí)用和方便的功能:在腳本,WebService,XML, 編譯器 API , 數(shù)據(jù)庫 , JMX , 網(wǎng)絡(luò) 和 Instrumentation 方面都有不錯的新特性和功能加強(qiáng)。
本系列 文章主要介紹 Java SE 6 在 API 庫方面的部分新特性,通過一些例子和講解,幫助開發(fā)者在編程實(shí)踐當(dāng)中更好的運(yùn)用 Java SE 6,提高開發(fā)效率。本文是系列文章的第 6 篇,介紹了 Java SE 6 在腳本編程方面的新特性。
![]() |
|
Java SE 6 引入了對 Java Specification Request(JSR)223 的支持,
JSR 223
旨在定義一個統(tǒng)一的規(guī)范,使得 Java 應(yīng)用程序可以通過一套固定的接口與各種腳本引擎交互,從而達(dá)到在 Java 平臺上調(diào)用各種腳本語言的目的。
javax.script
包定義了這些接口,即 Java 腳本編程 API。Java 腳本 API 的目標(biāo)與 Apache 項(xiàng)目 Bean Script Framework(BSF)類似,通過它 Java 應(yīng)用程序就能通過虛擬機(jī)調(diào)用各種腳本,同時,腳本語言也能訪問應(yīng)用程序中的 Java 對象和方法。Java 腳本 API 是連通 Java 平臺和腳本語言的橋梁。首先,通過它為數(shù)眾多的現(xiàn)有 Java 庫就能被各種腳本語言所利用,節(jié)省了開發(fā)成本縮短了開發(fā)周期;其次,可以把一些復(fù)雜異變的業(yè)務(wù)邏輯交給腳本語言處理,這又大大提高了開發(fā)效率。
在
javax.script
包中定義的實(shí)現(xiàn)類并不多,主要是一些接口和對應(yīng)的抽象類,
圖 1
顯示了其中包含的各個接口和類。
圖 1. javax.script 包概況
這個包的具體實(shí)現(xiàn)類少的根本原因是這個包只是定義了一個編程接口的框架規(guī)范,至于對如何解析運(yùn)行具體的腳本語言,還需要由第三方提供實(shí)現(xiàn)。雖然這些腳本引擎的實(shí)現(xiàn)各不相同,但是對于 Java 腳本 API 的使用者來說,這些具體的實(shí)現(xiàn)被很好的隔離隱藏了。Java 腳本 API 為開發(fā)者提供了如下功能:
- 獲取腳本程序輸入,通過腳本引擎運(yùn)行腳本并返回運(yùn)行結(jié)果,這是最核心的接口。
- 發(fā)現(xiàn)腳本引擎,查詢腳本引擎信息。
- 通過腳本引擎的運(yùn)行上下文在腳本和 Java 平臺間交換數(shù)據(jù)。
- 通過 Java 應(yīng)用程序調(diào)用腳本函數(shù)。
在詳細(xì)介紹這四個功能之前,我們先通過一個簡單的例子來展示如何通過 Java 語言來運(yùn)行腳本程序,這里仍然以經(jīng)典的“Hello World”開始。
清單 1. Hello World
|
這個例子非常直觀,只要通過
ScriptEngineManager
和
ScriptEngine
這兩個類就可以完成最簡單的調(diào)用。首先,
ScriptEngineManager
實(shí)例創(chuàng)建一個
ScriptEngine
實(shí)例,然后返回的
ScriptEngine
實(shí)例解析 JavaScript 腳本,輸出運(yùn)行結(jié)果。運(yùn)行這段程序,終端上會輸出“Hello World“。在執(zhí)行
eval
函數(shù)的過程中可能會有
ScriptEngine
異常拋出,引發(fā)這個異常被拋出的原因一般是由腳本輸入語法有誤造成的。在對整個 API 有了大致的概念之后,我們就可以開始介紹各個具體的功能了。
![]() ![]() |
![]()
|
Java 腳本 API 通過腳本引擎來運(yùn)行腳本,整個包的目的就在于統(tǒng)一 Java 平臺與各種腳本引擎的交互方式,制定一個標(biāo)準(zhǔn),Java 應(yīng)用程序依照這種標(biāo)準(zhǔn)就能自由的調(diào)用各種腳本引擎,而腳本引擎按照這種標(biāo)準(zhǔn)實(shí)現(xiàn),就能被 Java 平臺支持。每一個腳本引擎就是一個腳本解釋器,負(fù)責(zé)運(yùn)行腳本,獲取運(yùn)行結(jié)果。
ScriptEngine
接口是腳本引擎在 Java 平臺上的抽象,Java 應(yīng)用程序通過這個接口調(diào)用腳本引擎運(yùn)行腳本程序,并將運(yùn)行結(jié)果返回給虛擬機(jī)。
ScriptEngine
接口提供了許多
eval
函數(shù)的變體用來運(yùn)行腳本,這個函數(shù)的功能就是獲取腳本輸入,運(yùn)行腳本,最后返回輸出。
清單 1
的例子中直接通過字符串作為
eval
函數(shù)的參數(shù)讀入腳本程序。除此之外,
ScriptEngine
還提供了以一個
java.io.Reader
作為輸入?yún)?shù)的
eval
函數(shù)。腳本程序?qū)嵸|(zhì)上是一些可以用腳本引擎執(zhí)行的字節(jié)流,通過一個
Reader
對象,
eval
函數(shù)就能從不同的數(shù)據(jù)源中讀取字節(jié)流來運(yùn)行,這個數(shù)據(jù)源可以來自內(nèi)存、文件,甚至直接來自網(wǎng)絡(luò)。這樣 Java 應(yīng)用程序就能直接利用項(xiàng)目原有的腳本資源,無需以 Java 語言對其進(jìn)行重寫,達(dá)到腳本程序與 Java 平臺無縫集成的目的。
清單 2
即展示了如何從一個文件中讀取腳本程序并運(yùn)行,其中如何通過
ScriptEngineManager
獲取
ScriptEngine
實(shí)例的細(xì)節(jié)會在后面詳細(xì)介紹。
清單 2. Run Script
|
清單 2 代碼,從命令行分別獲取腳本名稱和腳本文件名,程序通過腳本名稱創(chuàng)建對應(yīng)的腳本引擎實(shí)例,通過腳本名稱指定的腳本文件名讀入腳本程序運(yùn)行。運(yùn)行下面這個命令,就能在 Java 平臺上運(yùn)行所有的 JavaScript 腳本。
java RunScript javascript run.js |
通過這種方式,Java 應(yīng)用程序可以把一些復(fù)雜易變的邏輯過程,用更加靈活的弱類型的腳本語言來實(shí)現(xiàn),然后通過
javax.Script
包提供的 API 獲取運(yùn)行結(jié)果,當(dāng)腳本改變時,只需替換對應(yīng)的腳本文件,而無需重新編譯構(gòu)建項(xiàng)目,好處是顯而易見的,即節(jié)省了開發(fā)時間又提高了開發(fā)效率。
EngineScript
接口分別針對
String
輸入和
Reader
輸入提供了三個不同形態(tài)的
eval
函數(shù),用于運(yùn)行腳本:
表 1. ScriptEngine 的 eval 函數(shù)
函數(shù) 描述
Object eval(Reader reader)
|
從一個
Reader
讀取腳本程序并運(yùn)行
|
Object eval(Reader reader, Bindings n)
|
以
n
作為腳本級別的綁定,從一個
Reader
讀取腳本程序并運(yùn)行
|
Object eval(Reader reader, ScriptContext context)
|
在
context
指定的上下文環(huán)境下,從一個
Reader
讀取腳本程序并運(yùn)行
|
Object eval(String script)
|
運(yùn)行字符串表示的腳本 |
Object eval(String script, Bindings n)
|
以
n
作為腳本級別的綁定,運(yùn)行字符串表示的腳本
|
Object eval(String script, ScriptContext context)
|
在
context
指定的上下文環(huán)境下,運(yùn)行字符串表示的腳本
|
Java 腳本 API 還為
ScriptEngine
接口提供了一個抽象類 ——
AbstractScriptEngine
,這個類提供了其中四個
eval
函數(shù)的默認(rèn)實(shí)現(xiàn),它們分別通過調(diào)用
eval(Reader,ScriptContext)
或
eval(String, ScriptContext)
來實(shí)現(xiàn)。這樣腳本引擎提供者,只需繼承這個抽象類并提供這兩個函數(shù)實(shí)現(xiàn)即可。
AbstractScriptEngine
有一個保護(hù)域
context
用于保存默認(rèn)上下文的引用,
SimpleScriptContext
類被作為
AbstractScriptEngine
的默認(rèn)上下文。關(guān)于上下文環(huán)境,將在后面進(jìn)行詳細(xì)介紹。
![]() ![]() |
![]()
|
在前面的兩個例子中,
ScriptEngine
實(shí)例都是通過調(diào)用
ScriptEngineManager
實(shí)例的方法返回的,而不是常見的直接通過
new
操作新建一個實(shí)例。JSR 223 中引入
ScriptEngineManager
類的意義就在于,將
ScriptEngine
的尋找和創(chuàng)建任務(wù)委托給
ScriptEngineManager
實(shí)例處理,達(dá)到對 API 使用者隱藏這個過程的目的,使 Java 應(yīng)用程序在無需重新編譯的情況下,支持腳本引擎的動態(tài)替換。通過
ScriptEngineManager
類和
ScriptEngineFactory
接口即可完成腳本引擎的發(fā)現(xiàn)和創(chuàng)建:
-
ScriptEngineManager
類: 自動尋找ScriptEngineFactory
接口的實(shí)現(xiàn)類 -
ScriptEngineFactory
接口: 創(chuàng)建合適的腳本引擎實(shí)例
![]() |
|
ScriptEngineManager
類本身并不知道如何創(chuàng)建一個具體的腳本引擎實(shí)例,它會依照 Jar 規(guī)約中定義的服務(wù)發(fā)現(xiàn)機(jī)制,查找并創(chuàng)建一個合適的
ScriptEngineFactory
實(shí)例,并通過這個工廠類來創(chuàng)建返回實(shí)際的腳本引擎。首先,
ScriptEngineManager
實(shí)例會在當(dāng)前 classpath 中搜索所有可見的 Jar 包;然后,它會查看每個 Jar 包中的 META -INF/services/ 目錄下的是否包含
javax.script.ScriptEngineFactory
文件,腳本引擎的開發(fā)者會提供在 Jar 包中包含一個
ScriptEngineFactory
接口的實(shí)現(xiàn)類,這個文件內(nèi)容即是這個實(shí)現(xiàn)類的完整名字;
ScriptEngineManager
會根據(jù)這個類名,創(chuàng)建一個
ScriptEngineFactory
接口的實(shí)例;最后,通過這個工廠類來實(shí)例化需要的腳本引擎,返回給用戶。舉例來說,第三方的引擎提供者可能升級更新了新版的腳本引擎實(shí)現(xiàn),通過
ScriptEngineManager
來管理腳本引擎,無需修改一行 Java 代碼就能替換更新腳本引擎。用戶只需在 classpath 中加入新的腳本引擎實(shí)現(xiàn)(Jar 包的形式),
ScriptEngineManager
就能通過 Service Provider 機(jī)制來自動查找到新版本實(shí)現(xiàn),創(chuàng)建并返回對應(yīng)的腳本引擎實(shí)例供調(diào)用。
圖 2
所示時序圖描述了其中的步驟:
圖 2. 腳本引擎發(fā)現(xiàn)機(jī)制時序圖
ScriptEngineFactory
接口的實(shí)現(xiàn)類被用來描述和實(shí)例化
ScriptEngine
接口,每一個實(shí)現(xiàn)
ScriptEngine
接口的類會有一個對應(yīng)的工廠類來描述其元數(shù)據(jù)(meta data),
ScriptEngineFactory
接口定義了許多函數(shù)供
ScriptEngineManager
查詢這些元數(shù)據(jù),
ScriptEngineManager
會根據(jù)這些元數(shù)據(jù)查找需要的腳本引擎,
表 2
列出了可供使用的函數(shù):
表 2. ScriptEngineFactory 提供的查詢函數(shù)
函數(shù) 描述
String getEngineName()
|
返回腳本引擎的全稱 |
String getEngineVersion()
|
返回腳本引擎的版本信息 |
String getLanguageName()
|
返回腳本引擎所支持的腳本語言的名稱 |
String getLanguageVersion()
|
返回腳本引擎所支持的腳本語言的版本信息 |
List<String> getExtensions()
|
返回一個腳本文件擴(kuò)展名組成的 List,當(dāng)前腳本引擎支持解析這些擴(kuò)展名對應(yīng)的腳本文件 |
List<String> getMimeTypes()
|
返回一個與當(dāng)前引擎關(guān)聯(lián)的所有 mimetype 組成的 List |
List<String> getNames()
|
返回一個當(dāng)前引擎所有名稱的 List,
ScriptEngineManager
可以根據(jù)這些名字確定對應(yīng)的腳本引擎
|
通過
getEngineFactories()
函數(shù),
ScriptEngineManager
會返回一個包含當(dāng)前環(huán)境中被發(fā)現(xiàn)的所有實(shí)現(xiàn)
ScriptEngineFactory
接口的具體類,通過這些工廠類中保存的腳本引擎信息檢索需要的腳本引擎。第三方提供的腳本引擎實(shí)現(xiàn)的 Jar 包中除了包含
ScriptEngine
接口的實(shí)現(xiàn)類之外,還需要提供
ScriptEngineFactory
接口的實(shí)現(xiàn)類,以及一個
javax.script.ScriptEngineFactory
文件用于指明這個工廠類。這樣,Java 平臺就能通過
ScriptEngineManager
尋找到這個工廠類,并通過這個工廠類為用戶提供一個腳本引擎實(shí)例。Java SE 6 默認(rèn)提供了 JavaScirpt 腳本引擎的實(shí)現(xiàn),如果需要支持其他腳本引擎,需要將它們對應(yīng)的 Jar 包包含在 classpath 中,比如對于前面
清單 2
中的代碼,只需在運(yùn)行程序前將 Groovy 的腳本引擎添加到 classpath 中,然后運(yùn)行:
java RunScript groovy run.groovy |
無需修改一行 Java 代碼就能以 Groovy 腳本引擎來運(yùn)行 Groovy 腳本。在
這里
為 Java SE 6 提供了許多著名腳本語言的腳本引擎對 JSR 223 的支持,這些 Jar 必須和腳本引擎配合使用,使得這些腳本語言能被 Java 平臺支持。到目前為止,它提供了至少 25 種腳本語言的支持,其中包括了 Groovy、Ruby、Python 等當(dāng)前非常流行的腳本語言。這里需要再次強(qiáng)調(diào)的是,負(fù)責(zé)創(chuàng)建
ScriptEngine
實(shí)例的
ScriptEngineFactory
實(shí)現(xiàn)類對于用戶來說是不可見的,
ScriptEngingeManager
實(shí)現(xiàn)負(fù)責(zé)與其交互,通過它創(chuàng)建腳本引擎。
![]() ![]() |
![]()
|
如果僅僅是通過腳本引擎運(yùn)行腳本的話,還無法體現(xiàn)出 Java 腳本 API 的優(yōu)點(diǎn),在 JSR 223 中,還為所有的腳本引擎定義了一個簡潔的執(zhí)行環(huán)境。我們都知道,在 Linux 操作系統(tǒng)中可以維護(hù)許多環(huán)境變量比如 classpath、path 等,不同的 shell 在運(yùn)行時可以直接使用這些環(huán)境變量,它們構(gòu)成了 shell 腳本的執(zhí)行環(huán)境。在
javax.script
支持的每個腳本引擎也有各自對應(yīng)的執(zhí)行的環(huán)境,腳本引擎可以共享同樣的環(huán)境,也可以有各自不同的上下文。通過腳本運(yùn)行時的上下文,腳本程序就能自由的和 Java 平臺交互,并充分利用已有的眾多 Java API,真正的站在“巨人”的肩膀上。
javax.script.ScriptContext
接口和
javax.script.Bindings
接口定義了腳本引擎的上下文。
-
Bindings 接口:
繼承自 Map,定義了對這些“鍵-值”對的查詢、添加、刪除等 Map 典型操作。
Bingdings
接口實(shí)際上是一個存放數(shù)據(jù)的容器,它的實(shí)現(xiàn)類會維護(hù)許多“鍵-值”對,它們都通過字符串表示。Java 應(yīng)用程序和腳本程序通過這些“鍵-值”對交換數(shù)據(jù)。只要腳本引擎支持,用戶還能直接在Bindings
中放置 Java 對象,腳本引擎通過Bindings
不僅可以存取對象的屬性,還能調(diào)用 Java 對象的方法,這種雙向自由的溝通使得二者真正的結(jié)合在了一起。 -
ScriptContext 接口:
將
Bindings
和ScriptEngine
聯(lián)系在了一起,每一個ScriptEngine
都有一個對應(yīng)的ScriptContext
,前面提到過通過ScriptEnginFactory
創(chuàng)建腳本引擎除了達(dá)到隱藏實(shí)現(xiàn)的目的外,還負(fù)責(zé)為腳本引擎設(shè)置合適的上下文。ScriptEngine
通過ScriptContext
實(shí)例就能從其內(nèi)部的Bindings
中獲得需要的屬性值。ScriptContext
接口默認(rèn)包含了兩個級別的Bindings
實(shí)例的引用,分別是全局級別和引擎級別,可以通過GLOBAL_SCOPE
和ENGINE_SCOPE
這兩個類常量來界定區(qū)分這兩個Bindings
實(shí)例,其中GLOBAL_SCOPE
從創(chuàng)建它的ScriptEngineManager
獲得。顧名思義,全局級別指的是Bindings
里的屬性都是“全局變量”,只要是同一個ScriptEngineMananger
返回的腳本引擎都可以共享這些屬性;對應(yīng)的,引擎級別的Bindings
里的屬性則是“局部變量”,它們只對同一個引擎實(shí)例可見,從而能為不同的引擎設(shè)置獨(dú)特的環(huán)境,通過同一個腳本引擎運(yùn)行的腳本運(yùn)行時能共享這些屬性。
ScriptContext
接口定義了下面這些函數(shù)來存取數(shù)據(jù):
表 3. ScriptContext 存取屬性函數(shù)
函數(shù) 描述
Object removeAttribute(String name, int scope)
|
從指定的范圍里刪除一個屬性 |
void setAttribute(String name, Object value, int scope)
|
在指定的范圍里設(shè)置一個屬性的值 |
Object getAttribute(String name)
|
從上下文的所有范圍內(nèi)獲取優(yōu)先級最高的屬性的值 |
Object getAttribute(String name, int scope)
|
從指定的范圍里獲取屬性值 |
ScriptEngineManager
擁有一個全局性的
Bindings
實(shí)例,在通過
ScriptEngineFactory
實(shí)例創(chuàng)建
ScriptEngine
后,它把自己的這個
Bindings
傳遞給所有它創(chuàng)建的
ScriptEngine
實(shí)例,作為
GLOBAL_SCOPE
。同時,每一個
ScriptEngine
實(shí)例都對應(yīng)一個
ScriptContext
實(shí)例,這個
ScriptContext
除了從
ScriptEngineManager
那獲得的
GLOBAL_SCOPE
,自己也維護(hù)一個
ENGINE_SCOPE
的
Bindings
實(shí)例,所有通過這個腳本引擎運(yùn)行的腳本,都能存取其中的屬性。除了
ScriptContext
可以設(shè)置屬性,改變內(nèi)部的
Bindings
,Java 腳本 API 為
ScriptEngineManager
和
ScriptEngine
也提供了類似的設(shè)置屬性和
Bindings
的 API。
圖 3. Bindings 在 Java 腳本 API 中的分布
從
圖 3
中可以看到,共有三個級別的地方可以存取屬性,分別是
ScriptEngineManager
中的
Bindings
,
ScriptEngine
實(shí)例對應(yīng)的
ScriptContext
中含有的
Bindings
,以及調(diào)用
eval
函數(shù)時傳入的
Bingdings
。離函數(shù)調(diào)用越近,其作用域越小,優(yōu)先級越高,相當(dāng)于編程語言中的變量的可見域,即
Object getAttribute(String name)
中提到的優(yōu)先級。從
清單 3
這個例子中可以看出各個屬性的存取優(yōu)先級:
清單 3. 上下文屬性的作用域
|
JavaScript 腳本
println(greeting)
在這個程序中被重復(fù)調(diào)用了三次,由于三次調(diào)用的環(huán)境不一樣,導(dǎo)致輸出也不一樣,
greeting
變量每一次都被優(yōu)先級更高的也就是距離函數(shù)調(diào)用越近的值覆蓋。從這個例子同時也演示了如何使用
ScriptContext
和
Bindings
這兩個接口,在例子腳本中并沒有定義
greeting
這個變量,但是腳本通過 Java 腳本 API 能方便的存取 Java 應(yīng)用程序中的對象,輸出
greeting
相應(yīng)的值。運(yùn)行這個程序后,能看到輸出為:
圖 4. 程序 ScopeTest 的輸出
除了能在 Java 平臺與腳本程序之間的提供共享屬性之外,
ScriptContext
還允許用戶重定向引擎執(zhí)行時的輸入輸出流:
表 4. ScriptContext 輸入輸出重定向
函數(shù) 描述
void setErrorWriter(Writer writer)
|
重定向錯誤輸出,默認(rèn)是標(biāo)準(zhǔn)錯誤輸出 |
void setReader(Reader reader)
|
重定向輸入,默認(rèn)是標(biāo)準(zhǔn)輸入 |
void setWriter(Writer writer)
|
重定向輸出,默認(rèn)是標(biāo)準(zhǔn)輸出 |
Writer getErrorWriter()
|
獲取當(dāng)前錯誤輸出字節(jié)流 |
Reader getReader()
|
獲取當(dāng)前輸入流 |
Writer getWriter()
|
獲取當(dāng)前輸出流 |
清單 4
展示了如何通過
ScriptContext
將其對應(yīng)的
ScriptEngine
標(biāo)準(zhǔn)輸出重定向到一個
PrintWriter
中,用戶可以通過與這個
PrintWriter
連通的
PrintReader
讀取實(shí)際的輸出,使 Java 應(yīng)用程序能獲取腳本運(yùn)行輸出,滿足更加多樣的應(yīng)用需求。
清單 4. 重定向腳本輸出
|
Java 腳本 API 分別為這兩個接口提供了一個簡單的實(shí)現(xiàn)供用戶使用。
SimpleBindings
通過組合模式實(shí)現(xiàn)
Map
接口,它提供了兩個構(gòu)造函數(shù)。無參構(gòu)造函數(shù)在內(nèi)部構(gòu)造一個
HashMap
實(shí)例來實(shí)現(xiàn)
Map
接口要求的功能;同時,
SimpleBindings
也提供了一個以
Map
接口作為參數(shù)的構(gòu)造函數(shù),允許任何實(shí)現(xiàn)
Map
接口的類作為其組合的實(shí)例,以滿足不同的要求。
SimpleScriptContext
提供了
ScriptContext
簡單實(shí)現(xiàn)。默認(rèn)情況下,它使用了標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出,同時維護(hù)一個
SimpleBindings
作為其引擎級別的 Bindings,它的默認(rèn)全局級別 Bindings 為空。
![]() ![]() |
![]()
|
在 Java 腳本 API 中還有兩個腳本引擎可以選擇是否實(shí)現(xiàn)的接口,這個兩個接口不是強(qiáng)制要求實(shí)現(xiàn)的,即并非所有的腳本引擎都能支持這兩個函數(shù),不過 Java SE 6 自帶的 JavaScript 引擎支持這兩個接口。無論如何,這兩個接口提供了非常實(shí)用的功能,它們分別是:
- Invocable 接口: 允許 Java 平臺調(diào)用腳本程序中的函數(shù)或方法。
- Compilable 接口: 允許 Java 平臺編譯腳本程序,供多次調(diào)用。
有時候,用戶可能并不需要運(yùn)行已有的整個腳本程序,而僅僅需要調(diào)用其中的一個過程,或者其中某個對象的方法,這個時候
Invocable
接口就能發(fā)揮作用。它提供了兩個函數(shù)
invokeFunction
和
invokeMethod
,分別允許 Java 應(yīng)用程序直接調(diào)用腳本中的一個全局性的過程以及對象中的方法,調(diào)用后者時,除了指定函數(shù)名字和參數(shù)外,還需要傳入要調(diào)用的對象引用,當(dāng)然這需要腳本引擎的支持。不僅如此,
Invocable
接口還允許 Java 應(yīng)用程序從這些函數(shù)中直接返回一個接口,通過這個接口實(shí)例來調(diào)用腳本中的函數(shù)或方法,從而我們可以從腳本中動態(tài)的生成 Java 應(yīng)用中需要的接口對象。
清單 5
演示了如何使用一個
Invocable
接口:
清單 5. 調(diào)用腳本中的函數(shù)
|
在調(diào)用函數(shù)前,可以先通過
instanceof
操作判斷腳本引擎是否支持編譯操作,防止類型轉(zhuǎn)換時拋出運(yùn)行時異常,需要特別注意的時,如果調(diào)用了腳本程序中不存在的函數(shù)時,運(yùn)行時會拋出一個
NoSuchMethodException
的異常,實(shí)際開發(fā)中應(yīng)該注意處理這種特殊情況。
一般來說,腳本語言都是解釋型的,這也是腳本語言區(qū)別與編譯語言的一個特點(diǎn),解釋性意味著腳本隨時可以被運(yùn)行,開發(fā)者可以邊開發(fā)邊查看接口,從而省 去了編譯這個環(huán)節(jié),提供了開發(fā)效率。但是這也是一把雙刃劍,當(dāng)腳本規(guī)模變大,重復(fù)解釋一段穩(wěn)定的代碼又會帶來運(yùn)行時的開銷。有些腳本引擎支持將腳本運(yùn)行編 譯成某種中間形式,這取決與腳本語言的性質(zhì)以及腳本引擎的實(shí)現(xiàn),可以是一些操作碼,甚至是 Java 字節(jié)碼文件。實(shí)現(xiàn)了這個接口的腳本引擎能把輸入的腳本預(yù)編譯并緩存,從而提高多次運(yùn)行相同腳本的效率。
Java 腳本 API 還為這個中間形式提供了一個專門的類,每次調(diào)用
Compilable
接口的編譯函數(shù)都會返回一個
CompiledScript
實(shí)例。
CompiledScript
類被用來保存編譯的結(jié)果,從而能重復(fù)調(diào)用腳本而沒有重復(fù)解釋的開銷,實(shí)際效率提高的多少取決于中間形式的徹底程度,其中間形式越接近低級語言,提高的效率就越高。每一個
CompiledScript
實(shí)例對應(yīng)于一個腳本引擎實(shí)例,一個腳本引擎實(shí)例可以含有多個
CompiledScript
(這很容易理解),調(diào)用
CompiledScript
的
eval
函數(shù)會傳遞給這個關(guān)聯(lián)的 ScriptEngine 的
eval
函數(shù)。關(guān)于
CompiledScript
類需要注意的是,它運(yùn)行時對與之對應(yīng)的 ScriptEngine 狀態(tài)的改變可能會傳遞給下一次調(diào)用,造成運(yùn)行結(jié)果的不一致。
清單 6
演示了如何使用
Compiable
接口來調(diào)用腳本:
清單 6. 編譯腳本
|
與
InovcableTest
類似,也應(yīng)該先通過
instanceof
操作判斷腳本引擎是否支持編譯操作,防止預(yù)料外的異常拋出。并且我們可以發(fā)現(xiàn)同一段編譯過的腳本,在第二次運(yùn)行時
greeting
變量的內(nèi)容被上一次的運(yùn)行改變了,導(dǎo)致輸出不一致:
圖 5. 程序 CompilableTest 的輸出
![]() ![]() |
![]()
|
Java SE 6 還為運(yùn)行腳本添加了一個專門的工具 —— jrunscript。jrunscript 支持兩種運(yùn)行方式:一種是交互式,即邊讀取邊解析運(yùn)行,這種方式使得用戶可以方便調(diào)試腳本程序,馬上獲取預(yù)期結(jié)果;還有一種就是批處理式,即讀取并運(yùn)行整 個腳本文件。用戶可以把它想象成一個萬能腳本解釋器,即它可以運(yùn)行任意腳本程序,而且它還是跨平臺的,當(dāng)然所有這一切都有一個前提,那就是必須告訴它相應(yīng) 的腳本引擎的位置。默認(rèn)即支持的腳本是 JavaScript,這意味著用戶可以無需任何設(shè)置,通過 jrunscript 在任何支持 Java 的平臺上運(yùn)行任何 JavaScript 腳本;如果想運(yùn)行其他腳本,可以通過
-l
指定以何種腳本引擎運(yùn)行腳本。不過這個工具仍是實(shí)驗(yàn)性質(zhì)的,不一定會包含在 Java 的后續(xù)版本中,無論如何,它仍是一個非常有用的工具。
![]() ![]() |
![]()
|
在 Java 平臺上使用腳本語言編程非常方便,因?yàn)?Java 腳本 API 相對其他包要小很多。通過
javax.script
包提供的接口和類我們可以很方便為我們的 Java 應(yīng)用程序添加對腳本語言的支持。開發(fā)者只要遵照 Java 腳本 API 開發(fā)應(yīng)用程序,開發(fā)中就無需關(guān)注具體的腳本語言細(xì)節(jié),應(yīng)用程序就可以動態(tài)支持任何符合 JSR 223 標(biāo)準(zhǔn)的腳本語言,不僅如此,只要按照 JSR 223 標(biāo)準(zhǔn)開發(fā),用戶甚至還能為 Java 平臺提供一個自定義腳本語言的解釋器。在 Java 平臺上運(yùn)行自己的腳本語言,這對于眾多開發(fā)者來說都是非常有誘惑力的。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
