熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

給 Java SE 注入腳本語言的活力

2013-11-15 11:39:53  來源: JSP教程 

  

     在即將發布的 Java SE(Mustang)中增加了對腳本語言的支持通過對腳本語言的調用使得一些通常用 Java 比較難於實現的功能變得簡單和輕便腳本語言與 Java 之間的互操作將變得優雅而直接

  腳本語言與 Java

  假設我們有一個簡單的需求察看一份文檔中 個字母組成的單詞的個數用 Java 一般實現如下

  import javaioBufferedReader; import javaioFileReader; import javaioIOException; public class FindWords { public static void main(String[] args) throws IOException { String result = ; String line = null; int num = ; FileReader fr = new FileReader(filename); BufferedReader br = new BufferedReader(fr); while ((line = brreadLine()) != null) { result += line; } brclose(); frclose(); String[] s = resultsplit( ); for (int i = ; i < slength; i++) { if (s[i]matches(^\\w{}$)) { num++; } } Systemoutprintln(num); } }


  再看看 Perl 語言實現同樣功能的代碼

  open FILE <filename ; while (<FILE>) { for (split) { $num++ if /^\w{}$/ } } print $num;


  那麼有沒有一種優雅的方式將 Java 與腳本語言結合呢在今年秋季即將發布的 Java SE(代號 Mustang)中這將成為現實

  Mustang 的腳本引擎

  JSR 為 Java 設計了一套腳本語言 API這一套 API 提供了在 Java 程序中調用各種腳本語言引擎的接口任何實現了這一接口的腳本語言引擎都可以在 Java 程序中被調用在 Mustang 的發行版本中包括了一個基於 Mozilla Rhino 的 JavaScript 腳本引擎

  Mozilla Rhino

  Rhino 是一個純 Java 的開源的 JavaScript 實現他的名字來源於 OReilly 關於 JavaScript 的書的封面

  Rhino

  Rhino 項目可以追朔到 當時 Netscape 計劃開發一個純 Java 實現的 Navigator為此需要一個 Java 實現的 JavaScript —— Javagator它也就是 Rhino 的前身起初 Rhino 將 JavaScript 編譯成 Java 的二進制代碼執行這樣它會有最好的性能後來由於編譯執行的方式存在垃圾收集的問題並且編譯和裝載過程的開銷過大不能滿足一些項目的需求Rhino 提供了解釋執行的方式隨著 Rhino 開放源代碼越來越多的用戶在自己的產品中使用了 Rhino同時也有越來越多的開發者參與了 Rhino 的開發並做出了很大的貢獻如今 RhinoR 版本將被包含在 Java SE 中發行更多的 Java 開發者將從中獲益

  Rhino 提供了如下功能


對 JavaScript 的完全支持
直接在 Java 中使用 JavaScript 的功能
一個 JavaScript shell 用於運行 JavaScript 腳本
一個 JavaScript 的編譯器用於將 JavaScript 編譯成 Java 二進制文件

   

  支持的腳本語言

  在可以找到官方的腳本引擎的實現項目這一項目基於BSD License 表示這些腳本引擎的使用將十分自由目前該項目已對包括 Groovy JavaScript Python Ruby PHP 在內的二十多種腳本語言提供了支持這一支持列表還將不斷擴大

  在 Mustang 中對腳本引擎的檢索使用了工廠模式首先需要實例化一個工廠 —— ScriptEngineManager

  

  // create a script engine manager ScriptEngineManager factory = new ScriptEngineManager();


  ScriptEngineManager 將在 Thread Context ClassLoader 的 Classpath 中根據 jar 文件的 METAINF 來查找可用的腳本引擎它提供了 種方法來檢索腳本引擎

  

  // create engine by name ScriptEngine engine = factorygetEngineByName (JavaScript); // create engine by name ScriptEngine engine = factorygetEngineByExtension (js); // create engine by name ScriptEngine engine = factorygetEngineByMimeType (application/javascript);


  下面的代碼將會打印出當前的 JDK 所支持的所有腳本引擎

  

  ScriptEngineManager factory = new ScriptEngineManager(); for (ScriptEngineFactory available : factorygetEngineFactories()) { Systemoutprintln(availablegetEngineName()); }


  以下各章節代碼將以 JavaScript 為例

  在 Java 中解釋腳本

  有了腳本引擎實例就可以很方便的執行腳本語言按照慣例我們還是從一個簡單的 Hello World 開始

  

  public class RunJavaScript { public static void main(String[] args){ ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName (JavaScript); engineeval(print(Hello World)); } }


  這段 Java 代碼將會執行 JavaScript 並打印出 Hello World如果 JavaScript 有語法錯誤將會如何?

  

  engineeval(if(true){println (hello));


故意沒有加上}執行這段代碼 Java 將會拋出一個 javaxscriptScriptException 並准確的打印出錯信息

  Exception in thread main javaxscriptScriptException: mozillajavascriptinternalEvaluatorException: missing } in compound statement (<Unknown source>#) in <Unknown source> at line number at


  如果我們要解釋一些更復雜的腳本語言或者想在運行時改變該腳本該如何做呢?腳本引擎支持一個重載的 eval 方法它可以從一個 Reader 讀入所需的腳本

  

  ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName (JavaScript); engineeval(new Reader(HelloWorldjs));


     如此這段 Java 代碼將在運行時動態的尋找 HelloWorldjs 並執行用戶可以隨時通過改變這一腳本文件來改變 Java 代碼的行為做一個簡單的實驗Java 代碼如下

  public class RunJavaScript { public static void main(String[] args) throws FileNotFoundException ScriptException InterruptedException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName (JavaScript); while (true) { engineeval(new FileReader(HelloWorldjs)); Threadsleep(); } } }


  HelloWorldjs 內容為簡單的打印一個 Hello World print(Hello World);

  運行 RunJavaScript 將會每一秒鐘打印一個 Hello World這時候修改 HelloWorldjs 內容為 print(Hello Tony);

  打印的內容將變為 Hello Tony由此可見 Java 程序將動態的去讀取腳本文件並解釋執行對於這一簡單的 Hello World 腳本來說IO 操作將比直接執行腳本損失 % 左右的性能(在我的 Think Pad 上)但他帶來的靈活性——在運行時動態改變代碼的能力在某些場合是十分激動人心的

   

  腳本語言與 Java 的通信

  ScriptEngine 的 put 方法用於將一個 Java 對象映射成一個腳本語言的變量現在有一個 Java Class它只有一個方法功能就是打印一個字符串 Hello World

  

  package tony; public class HelloWorld { String s = Hello World; public void sayHello(){ Systemoutprintln(s); } }


  那麼如何在腳本語言中使用這個類呢?put 方法可以做到

  

  import javaxscriptScriptEngine; import javaxscriptScriptEngineManager; import javaxscriptScriptException; public class TestPut { public static void main(String[] args) throws ScriptException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName(JavaScript); HelloWorld hello = new HelloWorld(); engineput(script_hello hello); engineeval(script_hellosayHello()); } }


  首先我們實例化一個 HelloWorld然後用 put 方法將這個實例映射為腳本語言的變量 script_hello那麼我們就可以在 eval() 函數中像 Java 程序中同樣的方式來調用這個實例的方法同樣的假設我們有一個腳本函數它進行一定的計算並返回值我們在 Java 代碼中也可以方便的調用這一腳本

  

  package tony; import javaxscriptInvocable; import javaxscriptScriptEngine; import javaxscriptScriptEngineManager; import javaxscriptScriptException; public class TestInv { public static void main(String[] args) throws ScriptException NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName(JavaScript); String script = function say(firstsecond) { print(first + + second); }; engineeval(script); Invocable inv = (Invocable) engine; invinvokeFunction(say Hello Tony); } }


  在這個例子中我們首先定義了一個腳本函數 say它的作用是接受兩個字符串參數將他們拼接並返回這裡我們第一次遇到了 ScriptEngine 的兩個可選接口之一 —— InvocableInvocable 表示當前的 engine 可以作為函數被調用這裡我們將 engine 強制轉換為 Invocable 類型使用 invokeFunction 方法將參數傳遞給腳本引擎invokeFunction這個方法使用了可變參數的定義方式可以一次傳遞多個參數並且將腳本語言的返回值作為它的返回值下面這個例子用JavaScript實現了一個簡單的max函數接受兩個參數返回較大的那個為了便於斷言結果正確性這裡繼承了JUnit Testcase關於JUnit請參考

  

  package tony; import javaxscriptInvocable; import javaxscriptScriptEngine; import javaxscriptScriptEngineManager; import javaxscriptScriptException; import junitframeworkTestCase; public class TestScripting extends TestCase { public void testInv() throws ScriptException NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName(JavaScript); String script = function max(firstsecond) + { return (first > second) ?first:second;}; engineeval(script); Invocable inv = (Invocable) engine; Object obj = invinvokeFunction(max ); assertEquals( objtoString()); } }


  Invocable 接口還有一個方法用於從一個 engine 中得到一個 Java Interface 的實例它的定義如下

  

  <T> T getInterface(Class<T> clasz)


  它接受一個 Java 的 Interface 類型作為參數返回這個 Interface 的一個實例也就是說你可以完全用腳本語言來寫一個 Java Interface 的所有實現以下是一個例子首先定一了個 Java Interface它有兩個簡單的函數分別為求最大值和最小值

  

  package tony; public interface MaxMin { public int max(int a int b); public int min(int a int b); }


  這個 Testcase 用 JavaScript 實現了 MaxMin 接口然後用 getInterface 方法返回了一個實例並驗證了結果

  

  public void testInvInterface() throws ScriptException NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factorygetEngineByName(JavaScript); String script = function max(firstsecond) + { return (first > second) ?first:second;}; script += function min(firstsecond) { return (first < second) ?first:second;}; engineeval(script); Invocable inv = (Invocable) engine; MaxMin maxMin = invgetInterface(MaxMinclass); assertEquals( maxMinmax( )); assertEquals( maxMinmin( )); }


  腳本的編譯執行

  到目前為止我們的腳本全部都是解釋執行的相比較之下編譯執行將會獲得更好的性能這裡將介紹 ScriptEngine 的另外一個可選接口 —— Compilable實現了這一接口的腳本引擎支持腳本的編譯執行下面這個例子實現了一個判斷給定字符串是否是 email 地址或者 ip 地址的腳本

  

  public void testComplie() throws ScriptException { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = managergetEngineByName(JavaScript); String script = var email=/^[azAZ_]+@[azAZ_] + +(\\[azAZ_]+)+$/;; script += var ip = /^(\\d{}|\\d\\d|[]\\d|[]) +(\\(\\d{}|\\d\\d|[]\\d|[])){}$/;; script += if(emailtest(str)){println(it is an email)} + else if(iptest(str)){println(it is an ip address)} + else{println(I don\\t know)}; engineput(str email@addresstony); Compilable compilable = (Compilable) engine; CompiledScript compiled = pile(script); compiledeval(); }


  腳本編譯的過程如下首先將 engine 轉換為 Compilable 接口然後調用 Compilable 接口的 compile 方法得到一個 CompiledScript 的實例這個實例就代表一個編譯過的腳本如此用 CompiledScript 的 eval 方法即為調用編譯好的腳本了在我的 Think Pad 上這段代碼編譯後的調用大約比直接調用 engineeval 要快 隨著腳本復雜性的提升性能的提升會更加明顯

  腳本上下文與綁定

  真正將腳本語言與 Java 聯系起來的不是 ScriptEngine而是 ScriptContext它作為 Java 與 ScriptEngine 之間的橋梁而存在

  一個 ScriptEngine 會有一個相應的 ScriptContext它維護了一個 Map這個 Map 中的每個元素都是腳本語言對象與 Java 對象之間的映射同時這個 Map 在我們的 API 中又被稱為 Bindings一個 Bindings 就是一個限定了 key 必須為 String 類型的 Map —— Map<String Object>所以一個 ScriptContext 也會有對應的一個 Bindings它可以通過 getBindings 和 setBindings 方法來獲取和更改

  一個 Bindings 包括了它的 ScriptContext 中的所有腳本變量那麼如何獲取腳本變量的值呢?當然從 Bindings 中 get 是一個辦法同時 ScriptContext 也提供了 getAttribute 方法在只希望獲得某一特定腳本變量值的時候它顯然是十分有效的相應地 setAttribute 和 removeAttribute 可以增加修改或者刪除一個特定變量

  在 ScriptContext 中存儲的所有變量也有自己的作用域它們可以是 ENGINE_SCOPE 或者是 GLOBAL_SCOPE前者表示這個 ScriptEngine 獨有的變量後者則是所有 ScriptEngine 共有的變量例如我們執行 engineput(key value) 方法之後這時便會增加一個 ENGINE_SCOPE 的變量如果要定義一個 GLOBAL_SCOPE 變量可以通過 setAttribute(key value ScriptContextGLOBAL_SCOPE) 來完成

  此外 ScriptContext 還提供了標准輸入和輸出的重定向功能它可以用於指定腳本語言的輸入和輸出

  在 JavaScript 中使用 Java 高級特性

  這一部分不同於前述內容將介紹 JavaScript引擎 —— Rhino 獨有的特性

   

  使用 Java 對象

  前面的部分已經介紹過如何在 JavaScript 中使用一個已經實例化的 Java 對象那麼如何在 JavaScript 中去實例化一個 Java 對象呢?在 Java 中所有 Class 是按照包名分層次存放的而在 JavaScript 沒有這一結構Rhino 使用了一個巧妙的方法實現了對所有 Java 對象的引用Rhino 中定義了一個全局變量—— Packages並且它的所有元素也是全局變量這個全局變量維護了 Java 類的層次結構例如 PackagesjavaioFile 引用了 Java 的 io 包中 File 對象如此一來我們便可以在 JavaScript 中方便的使用 Java 對象了new 和 Packages 都是可以被省略的

  

  //The same as: var frame = new PackagesjavaioFile(filename); var frame = javaioFile(filename);


  我們也可以像 Java 代碼中一樣把這個對象引用進來

  

  importClass (javaioFile); var file = File(filename);


  如果要將整個包下的所有類都引用進來可以用 importPackage

  

  importPackage(javaio);


  如果只需要在特定代碼段中引用某些包可以使用 JavaImporter 搭配 JavaScript 的 with 關鍵字

  

  var MyImport = JavaImporter(javaioFile); with (MyImport) { var myFile = File(filename); }


  用戶自定義的包也可以被引用進來不過這時候 Packages 引用不能被省略

  

  importPackage(Packagestony); var hello = HelloWorld(); hellosayHello();


  注意這裡只有 public 的成員和方法才會在 JavaScript 中可見例如對 hellos 的引用將得到 undefined下面簡單介紹一些常用的特性

  使用 Java 數組

  需要用反射的方式構造

  

  var a = javalangreflectArraynewInstance(javalangString );


  對於大部分情況可以使用 JavaScript 的數組將一個 JavaScript 的數組作為參數傳遞給一個 Java 方法時 Rhino 會做自動轉換將其轉換為 Java 數組

  實現一個 Java 接口

  除了上面提到的 Invocable 接口的 getInterface 方法外我們也可以在腳本中用如下方式

  

  //Define a JavaScript Object which has corresponding method obj={max:function(ab){return (a > b) ?a:b;}}; //Pass this object to an Interface maxImpl=comtonyMaxMin(obj); //Invocation print (maxImplmax());


  如果接口只有一個方法需要實現那麼在 JavaScript 中你可以傳遞一個函數作為參數

  

  function func(){ println(Hello World); } t=javalangThread(func); tstart();


  對於 JavaBean 的支持

  Rhino 對於 JavaBean 的 get 和 is 方法將會自動匹配例如調用 hellostring如果不存在 string 這個變量Rhino 將會自動匹配這個實例的 isString 方法然後再去匹配 getString 方法若這兩個方法均不存在才會返回 undefined

  命令行工具 jrunscript

  在 Mustang 的發行版本中還將包含一個腳本語言的的命令行工具它能夠解釋所有當前 JDK 支持的腳本語言同時它也是一個用來學習腳本語言很好的工具你可以l找到這一工具的詳細介紹

  結束語

  腳本語言犧牲執行速度換來更高的生產率和靈活性隨著計算機性能的不斷提高硬件價格不斷下降可以預見的腳本語言將獲得更廣泛的應用在 JavaSE 的下一個版本中加入了對腳本語言的支持無疑將使 Java 程序變得更加靈活也會使 Java 程序員的工作更加有效率

  關於作者

  

  Author photo

  吳玥颢目前就職於 IBM 中國開發中心 Harmony 開發團隊 除了對 Java 和腳本語言的熱愛之外他的興趣還包括哲學神話歷史與籃球此外他還是個電腦游戲高手您可以通過聯系到他


From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19401.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.