關於 Java Scripting API 您不知道的 5 件事

 

關於 Java Scripting API 您不知道的 5 件事

Java 平臺上更簡單的腳本編寫方法javascript

Ted Neward, 主管,ThoughtWorks, Neward & Associates

 

簡介: Java™ 語言足以知足您的一些項目的需求,可是腳本語言在性能方面一直表現不佳。Java Scripting API (javax.script) 支持在 Java 程序中調用腳本,反之亦然,經過本文,您將瞭解它在這兩方面是如何作到最好的。html

本文的標籤:  ajaxj2se_(java_2_standard_edition)jsjsp開放源碼java

 

 

 

 

發佈日期: 2010 年 9 月 10 日 
級別: 初級  其餘語言版本: 英文 
建議: 0 (添加評論)web

1 star 2 stars 3 stars 4 stars 5 stars  平均分 (共 3 個評分 )

 

關於本系列文章

您認爲您已經瞭解 Java 編程了嗎?事實上,大多數開發人員只瞭解 Java 平臺的皮毛,他們只學習了足以完成手頭工做所須要的知識而已。在本 系列文章 中,Ted Neward 經過介紹 Java 平臺的核心功能來講明一些不爲人所熟知的知識,這些知識甚至能夠幫助您解決最棘手的編程問題。ajax

如今,許多 Java 開發人員都喜歡在 Java 平臺中使用腳本語言,可是使用編譯到 Java 字節碼中的動態語言有時是不可行的。在某些狀況中,直接編寫一個 Java 應用程序的腳本 部分 或者在一個腳本中調用特定的 Java 對象是更快捷、更高效的方法。shell

這就是 javax.script 產生的緣由了。Java Scripting API 是從 Java 6 開始引入的,它填補了便捷的小腳本語言和健壯的 Java 生態系統之間的鴻溝。經過使用 Java Scripting API,您就能夠在您的 Java 代碼中快速整合幾乎全部的腳本語言,這使您可以在解決一些很小的問題時有更多可選擇的方法。數據庫

1. 使用 jrunscript 執行 JavaScript編程

每個新的 Java 平臺發佈都會帶來新的命令行工具集,它們位於 JDK 的 bin 目錄。Java 6 也同樣,其中 jrunscript 即是 Java 平臺工具集中的一個不小的補充。數組

設想一個編寫命令行腳本進行性能監控的簡單問題。這個工具將借用 jmap(見本系列文章 前一篇文章 中的介紹),每 5 秒鐘運行一個 Java 進程,從而瞭解進程的運行情況。通常狀況下,咱們會使用命令行 shell 腳原本完成這樣的工做,可是這裏的服務器應用程序部署在一些差異很大的平臺上,包括 Windows® 和 Linux®。系統管理員將會發現編寫可以同時運行在兩個平臺的 shell 腳本是很痛苦的。一般的作法是編寫一個 Windows 批處理文件和一個 UNIX® shell 腳本,同時保證這兩個文件同步更新。瀏覽器

可是,任何閱讀過 The Pragmatic Programmer 的人都知道,這嚴重違反了 DRY (Don't Repeat Yourself) 原則,並且會產生許多缺陷和問題。咱們真正但願的是編寫一種與操做系統無關的腳本,它可以在全部的平臺上運行。

固然,Java 語言是平臺無關的,可是這裏並非須要使用 「系統」 語言的狀況。咱們須要的是一種腳本語言 — 如,JavaScript。

清單 1 顯示的是咱們所須要的簡單 shell 腳本:


清單 1. periodic.js
				
while (true)
{
    echo("Hello, world!");
}

因爲常常與 Web 瀏覽器打交道,許多 Java 開發人員已經知道了 JavaScript(或 ECMAScript;JavaScript 是由 Netscape 開發的一種 ECMAScript 語言)。問題是,系統管理員要如何運行這個腳本?

固然,解決方法是 JDK 所帶的 jrunscript 實用程序,如清單 2 所示:


清單 2. jrunscript
				
C:\developerWorks\5things-scripting\code\j***c>jrunscript periodic.js
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
...

注意,您也可使用 for 循環按照指定的次數來循環執行這個腳本,而後才退出。基本上,jrunscript 可以讓您執行 JavaScript 的全部操做。唯一不一樣的是它的運行環境不是瀏覽器,因此運行中不會有 DOM。所以,最頂層的函數和對象稍微有些不一樣。

由於 Java 6 將 Rhino ECMAScript 引擎做爲 JDK 的一部分,jrunscript 能夠執行任何傳遞給它的 ECMAScript 代碼,不論是一個文件(如此處所示)或是在更加交互式的 REPL(「Read-Evaluate-Print-Loop」)shell 環境。運行 jrunscript 就能夠訪問 REPL shell。

 

2. 從腳本訪問 Java 對象

可以編寫 JavaScript/ECMAScript 代碼是很是好的,可是咱們不但願被迫從新編譯咱們在 Java 語言中使用的全部代碼 — 這是違背咱們初衷的。幸虧,全部使用 Java Scripting API 引擎的代碼都徹底可以訪問整個 Java 生態系統,由於本質上一切代碼都仍是 Java 字節碼。因此,回到咱們以前的問題,咱們能夠在 Java 平臺上使用傳統的 Runtime.exec() 調用來啓動進程,如清單 3 所示:


清單 3. Runtime.exec() 啓動 jmap
				
var p = java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ])
p.waitFor()

數組 arguments 是指向傳遞到這個函數參數的 ECMAScript 標準內置引用。在最頂層的腳本環境中,則是傳遞給腳本自己的的參數數組(命令行參數)。因此,在清單 3 中,這個腳本預期接收一個參數,該參數包含要映射的 Java 進程的 VMID。

除此以外,咱們能夠利用自己爲一個 Java 類的 jmap,而後直接調用它的 main() 方法,如清單 4 所示。有了這個方法,咱們不須要 「傳輸」 Process 對象的 in/out/err 流。


清單 4. JMap.main()
				
var args = [ "-histo", arguments[0] ]
Packages.sun.tools.jmap.JMap.main(args)

Packages 語法是一個 Rhino ECMAScript 標識,它指向已經 Rhino 內建立的位於核心 java.* 包以外的 Java 包。

 

3. 從 Java 代碼調用腳本

從腳本調用 Java 對象僅僅完成了一半的工做:Java 腳本環境也提供了從 Java 代碼調用腳本的功能。這隻須要實例化一個ScriptEngine 對象,而後加載和評估腳本,如清單 5 所示:


清單 5. Java 平臺的腳本調用
				
import java.io.*;
import javax.script.*;

public class App
{
    public static void main(String[] args)
    {
        try
        {
            ScriptEngine engine = 
                new ScriptEngineManager().getEngineByName("javascript");
            for (String arg : args)
            {
                FileReader fr = new FileReader(arg);
                engine.eval(fr);
            }
        }
        catch(IOException ioEx)
        {
            ioEx.printStackTrace();
        }
        catch(ScriptException scrEx)
        {
            scrEx.printStackTrace();
        }
    }
}

eval() 方法也能夠直接操做一個 String,因此這個腳本不必定必須是文件系統的一個文件 — 它能夠來自於數據庫、用戶輸入,或者甚至能夠基於環境和用戶操做在應用程序中生成。

 

4. 將 Java 對象綁定到腳本空間

僅僅調用一個腳本還不夠:腳本一般會與 Java 環境中建立的對象進行交互。這時,Java 主機環境必須建立一些對象並將它們綁定,這樣腳本就能夠很容易找到和使用這些對象。這個過程是 ScriptContext 對象的任務,如清單 6 所示:


清單 6. 爲腳本綁定對象
				
import java.io.*;
import javax.script.*;

public class App
{
    public static void main(String[] args)
    {
        try
        {
            ScriptEngine engine = 
                new ScriptEngineManager().getEngineByName("javascript");
                
            for (String arg : args)
            {
                Bindings bindings = new SimpleBindings();
                bindings.put("author", new Person("Ted", "Neward", 39));
                bindings.put("title", "5 Things You Didn't Know");
                
                FileReader fr = new FileReader(arg);
                engine.eval(fr, bindings);
            }
        }
        catch(IOException ioEx)
        {
            ioEx.printStackTrace();
        }
        catch(ScriptException scrEx)
        {
            scrEx.printStackTrace();
        }
    }
}

訪問所綁定的對象很簡單 — 所綁定對象的名稱是做爲全局命名空間引入到腳本的,因此在 Rhino 中使用 Person 很簡單,如清單 7 所示:


清單 7. 是誰撰寫了本文?
				
println("Hello from inside scripting!")

println("author.firstName = " + author.firstName)

您能夠看到,JavaBeans 樣式的屬性被簡化爲使用名稱直接訪問,這就好像它們是字段同樣。

 

5. 編譯頻繁使用的腳本

腳本語言的缺點一直存在於性能方面。其中的緣由是,大多數狀況下腳本語言是 「即時」 解譯的,於是它在執行時會損失一些解析和驗證文本的時間和 CPU 週期。運行在 JVM 的許多腳本語言最終會將接收的代碼轉換爲 Java 字節碼,至少在腳本被第一次解析和驗證時進行轉換;在 Java 程序關閉時,這些即時編譯的代碼會消失。將頻繁使用的腳本保持爲字節碼形式能夠幫助提高可觀的性能。

咱們能夠以一種很天然和有意義的方法使用 Java Scripting API。若是返回的 ScriptEngine 實現了 Compilable 接口,那麼這個接口所編譯的方法可用於將腳本(以一個 String 或一個 Reader 傳遞過來的)編譯爲一個 CompiledScript 實例,而後它可用於在 eval() 方法中使用不一樣的綁定重複地處理編譯後的代碼,如清單 8 所示:


清單 8. 編譯解譯後的代碼
				
import java.io.*;
import javax.script.*;

public class App
{
    public static void main(String[] args)
    {
        try
        {
            ScriptEngine engine = 
                new ScriptEngineManager().getEngineByName("javascript");
                
            for (String arg : args)
            {
                Bindings bindings = new SimpleBindings();
                bindings.put("author", new Person("Ted", "Neward", 39));
                bindings.put("title", "5 Things You Didn't Know");
                
                FileReader fr = new FileReader(arg);
                if (engine instanceof Compilable)
                {
                    System.out.println("Compiling....");
                    Compilable compEngine = (Compilable)engine;
                    CompiledScript cs = compEngine.compile(fr);
                    cs.eval(bindings);
                }
                else
                    engine.eval(fr, bindings);
            }
        }
        catch(IOException ioEx)
        {
            ioEx.printStackTrace();
        }
        catch(ScriptException scrEx)
        {
            scrEx.printStackTrace();
        }
    }
}

在大多數狀況中,CompiledScript 實例須要存儲在一個長時間存儲中(例如,servlet-context),這樣才能避免一次次地重複編譯相同的腳本。然而,若是腳本發生變化,您就須要建立一個新的 CompiledScript 來反映這個變化;一旦編譯完成,CompiledScript就再也不執行原始的腳本文件內容。

 

結束語

Java Scripting API 在擴展 Java 程序的範圍和功能方面前進了很大一步,而且它將腳本語言的編碼效率的優點帶到 Java 環境。jrunscript — 它顯然不是很難編寫的程序 — 以及 javax.script 給 Java 開發人員帶來了諸如 Ruby (JRuby) 和 ECMAScript (Rhino) 等腳本語言的優點,同時還不會破壞 Java 環境的生態系統和可擴展性。

請繼續閱讀下一篇文章 5 件事 系列文章:JDBC。


參考資料

學習

討論

  • 加入 My developerWorks 中文社區。查看開發人員推進的博客、論壇、組和 wikis,並與其餘 developerWorks 用戶交流。 
     

關於做者

Ted Neward 的照片

Ted Neward 是 ThoughtWorks 的一名顧問,ThoughtWorks 是一家在全球提供諮詢服務的公司,他仍是 Neward & Associates 的主管,負責有關 Java、.NET 和 XML 服務和其餘平臺的諮詢、指導、培訓和推介。他如今居住在華盛頓西雅圖附近。

相關文章
相關標籤/搜索