Rhino是基於Java的Javascript引擎,大概也是Java用戶使用Javascript的不二之選。在部份的Java環境有整合,不必倚賴外部的Rhino就可以使用。
http://www.mozilla.org/rhino/
一些常見應用
* YUI Compressor
http://developer.yahoo.com/yui/compressor/
如果想要把Javascript檔案縮到最小,YUI Compressor應該是最好的解決方案之一。YUI Compressor可以去掉Javascript檔案中所有不需要的換行與註解,然後把函數的參數及變數改成簡短的名字,藉由這樣,可以大幅縮小Javascript程式的體積,並且使得程式不容易閱讀而達到保護的目的。
YUI Compressor內部使用Rhino來做Javascript的parser,把Javascript的字串轉成token來進一步處理。除了縮小體積,YUI Compressor也可以檢查Javascript是否有錯誤,只要加上額外的參數,他就會對程式碼的問題做回報。
* Flow Script
http://cocoon.apache.org/
Cocoon是Apache基金會底下的一個計畫,他是一個基於XML的網站應用軟體,不過他使用了Javascript做為流程控制語言,這個功能是使用Rhino實作的。
這部份的重點是continuation。關於什麼是continuation,wikipedia有一些解釋http://en.wikipedia.org/wiki/Continuation。利用continuation可以保存執行時的整個context環境的狀態,所以把他用serialization保存起來之後,就可以在之後接著執行下去。利用這個方法,Spring WebFlow以及Cocoon使用Rhino的Continuation物件,在需要輸出/輸入(也就是request/response)的時候進行Continuation來處理,處理完就回復狀態繼續執行。詳細的說明請參考:
* http://cocoon.apache.org/2.2/core-modules/core/2.2/1372_1_1.html
* http://cocoon.apache.org/2.2/core-modules/core/2.2/1373_1_1.html
* http://cocoon.apache.org/2.2/core-modules/core/2.2/1380_1_1.html
使用Rhino
一樣來試試看怎樣在Java程式中使用Rhino來內嵌Javascript。下面的程式也是之前的練習,部份來自Rhino的tutorial。主要的操作是建立一個Javascript執行環境,然後在global物件加入Count物件及print、printFile兩個函數。
package tw.idv.fillano;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
public class Counter extends ScriptableObject {
private static final long serialVersionUID = -8999275232076586375L;
private int count;
public Counter() {
}
public Counter(Scriptable scope, Scriptable prototype) {
super(scope, prototype);
}
@Override
public String getClassName() {
return "Counter";
}
public void jsConstructor(int a) {
count = a;
}
public int jsGet_count() {
return ++count;
}
public void jsFunction_resetCount() {
count = 0;
}
}
(Counter.java)
然後是主程式:
package tw.idv.fillano;
import org.mozilla.javascript.*;
import java.io.*;
public class Main extends ScriptableObject {
private static final long serialVersionUID = 6156186852109434265L;
/**
* @param args
*/
public static void main(String[] args) {
if(args.length<1) {
System.err.println("you must provide js file name as the first argument");
return;
}
Main main = new Main();
String[] names = {"print","printFile"};
main.defineFunctionProperties(names, Main.class, ScriptableObject.DONTENUM);
ContextFactory cf = ContextFactory.getGlobal();
Context cx = cf.enterContext();
try {
File fh = new File(args[0]);
FileReader fr = new FileReader(fh);
Scriptable scope = cx.initStandardObjects(main);
ScriptableObject.defineClass(scope, Counter.class);
cx.evaluateReader(scope, fr, fh.getName(), 1, null);
}
catch (Exception ex) {
ex.printStackTrace();
}
finally {
Context.exit();
}
}
public static void print(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
for (int i=0; i < args.length; i++) {
if (i > 0)
System.out.print(" ");
String s = Context.toString(args[i]);
System.out.print(s);
}
System.out.println();
}
public static void printFile(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
try {
if (args.length!=1) return;
String s = Context.toString(args[0]);
BufferedReader br = new BufferedReader(new FileReader(s));
String line;
while((line=br.readLine()) != null) {
System.out.println(line);
}
}catch(Exception e) {
e.printStackTrace();
}
}
@Override
public String getClassName() {
return "global";
}
}
(Main.java)
從程式可以看出,先用ContextFactory產生GlobalContext,然後進入這個context,把Main設為這個context的scope,同時把Main的幾個static method使用defineFunctionProperties方法定義為global的函數,另外使用ScriptableObject.defineClass()給這個scope加入Counter物件。最後利用這個context的evaluateReader方法透過FileReader讀取Javascript檔案來執行。
接下來用一個Javascript檔案做測試:
function add(x,y){return x+y;}
print(add(3,4));
var a=new Counter(7);
print(a.count);
printFile('test01.js');
(test01.js)
跑出的結果:
7
8
function add(x,y){return x+y;}
print(add(3,4));
var a=new Counter(7);
print(a.count);
printFile('test01.js');
(要怎麼下載Rhino,怎麼設定編譯環境,怎麼執行...這些就不提了)