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

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