Java語言是一種優秀的高級編程語言,在使用各種不同解決方案進行簡單試 驗的時候,允許我們夠接近我們想要解決的問題。然而在平時的計算(和開發) 中,很多情況下“迅速准備一個Java 程序”來執行任務不是不切實際就是太花 時間。本文將您帶入了 FESI(免費 EcmaScript解釋程序)的秘密世界。在那裡 ,用快速上手的方式展開 Java語言是一個標准,而非異議。
從概念到產品, Java 平台和 Java 編程語言都能提供其它開發環境中所沒 有的高級功能和全面的 API,從而推進軟件產品的性能。這些功能和 API 顯著 提高了生產力,並縮短了產品周期。
它像 Java 編程語言一樣功能繁多,且仍是一種正式的、編譯語言程序。 Java 開發周期需要仔細的前期規劃。在開發人員每天的生活中,很多情況下他 或她可能需要創建一個簡單的一次性程序,而使用 Java 語言的開發未必符合需 要(且可能造成過度)。這種情況在創建代碼測試利用、為多個程序/行為創建 可能只用一次的排序代碼,以及創建應用程序概念的快速原型等方面經常會發生 。許多開發人員此時放棄使用 Java 語言,而轉向諸如命令解釋程序腳本或 Win32 批處理文件等的快速上手腳本解決方案。不幸的是,這些解決方案是不可 移植的,且沒有像使用 Java 平台解決方案那樣的功能和支持。
FESI 登場了,它是寫在 Java 語言內部的腳本解釋程序,這也就使得它能移 植到所有支持 Java 平台的操作系統和硬件系統中去。FESI 支持叫做 EcmaScript 的 JavaScript 的支語言,並且能獲得主 JDK 的所有性能。這就是 說,您能利用作為基礎的 JDK 的全面 API 編寫 EcmaScript 程序。您獲得了 Java 平台的所有功能,並且能通過快速上手腳本編寫對其進行控制。
EcmaScript 和JavaScript
EcmaScript 是 JavaScript 編程語言的一個可移植的分支,也就是每個現代 Web 浏覽器內嵌的編程語言。事實上,EcmaScript 是試圖在 JavaScript 語言 的單個支語言上實現標准化的結果 -- 使 Web 頁上的代碼能跨越不同廠商生產 的浏覽器而工作(但主要是在 Netscape 和 Microsoft 的浏覽器之間,因為它 們占據了安裝群體的絕大部分)。ECMA 是歐洲計算機廠商協會 (European Computer Manufacturers Association),一個管理眾多標准的協會。 EcmaScript 是 ECMA 標准第 262 條。
在許多方面,對於流行的 Web 浏覽器中的腳本代碼兼容性問題來說, EcmaScript 是共同特點最少的解決方案。盡管 Microsoft Explorer 5.5 和 Netscape Navigator 6.0 都支持 EcmaScript,但這並不是說在前者中創建的 JavaScript 代碼毫不改變就可以在後者中運行。
想要了解情況為何會這樣,我們須仔細地研究一下 JavaScript 主環境的構 成。圖 1 中對此作出了說明。
圖 1. JavaScript 主環境
EcmaScript 組件只提供核心語言功能 -- 原始數據類型、表達式賦值和流量 控制等等。要做任何有益的工作,您就必須使用語言來操作主環境提供給腳本語 言的對象。需通過調用對象方法或改變它們的屬性值來完成這個操作。每個 JavaScript 主程序都須揭示其自身的主對象集以供使用(例如,一個浏覽器能 揭示一個對象模型以和 Web 頁進行交互)。EcmaScript 自身只要求為數不多的 一套本地對象支持(請參閱 參考資料)。
事實上,在浏覽器內部,可用的 JavaScript 主環境與由 FESI 提供的環境 的最主要區別就是所揭示的主對象集的不同。FESI,通過 JavaAccess 擴展名, 幾乎揭示了所有的 JDK 對象供 EcmaScript 直接操作。換句話說,我們能隨意 地使用 EcmaScript 程序來控制大多數 JDK API。除了 JavaAccess 擴展名以外 , FESI 還在以下領域提供了特殊的擴展名代碼:
通過 JDBC 對數據庫的訪問
文件 I/O
基本 I/O
這些擴展名幫助您使對象支持與編程的 EcmaScript 方式(而不是通常更難 編寫的 Java API 方式)相匹配,從而使特定范圍內的編程更加簡單易行。我們 將在本文討論的樣本程序中直接體驗到這一點。(請參閱 參考資料,下載本文 所用到的源代碼。)
在我們深入討論之前,請參閱 “下載並安裝 FESI”,讓 FESI 在您的機箱 中運行起來。
您會 FESI語言嗎?
請使用命令行中的 fesidev 命令開始 FESI 交互式解釋程序環境。現在您就 能方便地與 EcmaScript 解釋程序進行交互並立刻得到結果。
有一些內部命令,不是交互式解釋程序所能理解的 EcmaScript 或 FESI 的 一部分。您能通過在命令行輸入 @help 來訪問這些命令。
這裡有一個對最常用命令的描述:
@clear -- 清除交互式顯示區域
@describe -- 獲取對變量與對象的詳細描述
@exit -- 從交互式環境中退出
@listall -- 顯示一個對象的所有屬性
您能將一個 EcmaScript 程序交互式地輸入 FESI,或使用 File|Open 菜單 將它在一個包含著現有 EcmaScript 程序的文本文件中打開。基於文本的腳本文 件能以批處理方式運行。慣例是對 EcmaScript 源用 .es 擴展名以及對使用 GUI 支持的 EcmaScript 源用 .ew 擴展名。要以批處理方式處理 EcmaScript 文件,請使用安裝時生成的 fesi.bat 和 fesiw.bat 文件。
用 EcmaScript來試驗
對於經驗豐富的 Java 開發人員來說,EcmaScript 會顯得粗糙而缺乏系統化 。例如,您無需在使用變量前先予以聲明。對象定義非常特別。學習 EcmaScript 最好的方法就是使用交互式 FESI 解釋程序。在這部分中,我們將 看看 EcmaScript 句法和 Java 語言中存在的主要差異。我們將看一下它的晚期 綁定和類型松散(您直到真正執行代碼時才決定變量的數據類型) -- 及其缺少 強制的、正規的和面向對象的構造。
要聲明一個叫做 myObject 的對象,我們可以輸入:
> var myObj = new Object;
迄今為止一切順利。若要為對象添加屬性,就需要這樣做:
> myObj.name = "George";
這類似於為支持動態屬性表的 Java 對象添加屬性,然而 Java 語言自身不 帶有基本的相同功能。我們可以用同樣的方法為 myObj 添加一個叫做 age 的整 數類型的屬性。
> myObj.age = 33;
我們能用此前討論過的 @listall 命令來查看對象所有的屬性。
>@listall myObj
age: [PRIMITIVE]: 33
name: [PRIMITIVE]: George
最後,我們希望在對象中加入一些行為,就先定義一個 EcmaScript 函數, 然後把它指定為對象的一個屬性。
> function intAdd(inParam) { writeln(inParam + this.age); }
> myObj.add = intAdd;
> @listall myObj
age: [PRIMITIVE]: 33
name: [PRIMITIVE]: George
add: [OBJECT]: function intAdd(inParam) { writeln (inParam + this.age ); }
如想查看 EcmaScript 的最新綁定特征,請嘗試:
> myObj.add(5);
38
請注意,由於 add() 函數的輸入參數在運行時被綁定到 int 類自變量,因 此產生了數值的增加。現在來試試這個:
>myObj.add("5");
533
這次,相同方法內發生了字符串串聯,並且現在輸入參數在函數運行時受到 String 類自變量的限制。
創建一個對象定義,您能通過它重復創建新實例,但必須先定義其構造器。 我們先在下面創建了一個 myObjDef 構造器,並立即用新的操作符創建了一個新 的 myObjDef 實例。
> function myObjDef(inName, inAge) { this.name = inName; this.age = inAge; }
> myObj = new myObjDef("Joe", 7);
> @listall myObj
age: [PRIMITIVE]: 7 name: [PRIMITIVE]: Joe
EcmaScript 中的數組為本地對象。由於類型松散,每個元素都能包含任何基 本類型或對象。
> myArray = new Array(2);
> myArray[0] = "A String";
> myArray[1] = new myObjDef("Joe", 7);
> @listall myArray
0: [PRIMITIVE]: A String 1: [OBJECT]: [object Object] length: [PRIMITIVE]: 2
> @listall myArray[1]
age: [PRIMITIVE]: 7 name: [PRIMITIVE]: Joe
EcmaScript 裡大多數控制流的語句對於 Java 編程人員來說都應該是熟悉的 ,因為這些語句都是根據 Java 編程語言的模型設計的。EcmaScript 還支持很 有限的繼承體、容器和集合的窗體 -- 其中任何格式都不是通過編程語言自身執 行的(不同於 Java 語言)。有興趣的讀者可參考 參考資料以獲得更多信息。
我們現在開始使用一下用了一些 JDK 功能的 FESI 程序,用 JDBC 來訪問 RDBMS 數據。
使用運用了 FESI 的 RDBMS
FESI 帶有數據庫訪問擴展名。該擴展名讓 FESI 程序輕易地訪問 RDBMS 內 含有的數據。數據庫訪問是通過 JDBC 實現的。當可能直接通過 FESI 的 Java 對象訪問能力來使用 JDBC 時,數據庫訪問擴展名讓使用 FESI 裡的數據變得異 常的簡單。
讓我們來看一個簡單的程序,它能創建一個數據庫表並填入數據值。我們將 在示例中使用流行的 mySQL 服務器及 mm JDBC 4 類驅動程序(請參閱 參考資 料)。這一示例假設您在一個叫做 devworks 的數據庫中擁有創建、刪除和添加 的訪問特權。您應修改 JDBC 的連接 URL 以反映自己的安裝。如果您更願意使 用帶有本地 MS 訪問數據庫的 JDBC-ODBC 網橋,請參閱 “為 JDBC 創建一個數 據源”以獲得更多信息。清單 1 顯示了 FESI 代碼;您能在 DBScript.es 文件 連同其源代碼包中一起找到它。
清單 1. 數據庫訪問的 FESI代碼
var db= new Database("org.gjt.mm.mysql.Driver");
db.connect("jdbc:mysql://192.168.23.38:3306/devworks? user=dbuser");
var removeOLD = "drop table SimpContact;";
var createNew = "create table SimpContact ( name char(30), age int (4));"
var insertData1 = "insert into SimpContact values ('Joe', 33);";
var insertData2 = "insert into SimpContact values ('Mary', 48);";
result = db.executeCommand(removeOLD);
result = db.executeCommand(createNew);
result = db.executeCommand(insertData1);
result = db.executeCommand(insertData2);
db.disconnect();
在清單 1 中, FESI 數據庫訪問擴展名提供了通用的 Database 對象。可通 過指定您用來訪問 RDBMS 的 JDBC 驅動程序來創建一個實例。接下來,需要獲 得一個 JDBC 連接 -- 這是通過 Database 對象的 connect() 方法來實現的。 最後,我們用 Database 對象的 executeCommand() 方法來執行數據定義語言 (DDL) 語句。我們不久後將看到如何使用 SQL SELECT 查詢及運用 FESI 數據庫 訪問擴展名的結果集。
當這一簡單的程序可用時,我們能通過以下方法增強其實質的靈活性:
使用戶能通過外部配置文件來指定 JDBC 驅動器及連接 URL
從獨立的 SQL 命令文件中讀入命令以執行
我們將在下一部分中介紹如何在 FESI 中簡單地實現這一目的。
FESI JavaAccess 及文件 I/O 擴展名
讓我們來看一看增強的 EcmaScript 程序,DBScriptFlex.es 是怎樣讀入一 個叫做 dbinit.ini 的文件的。dbinit.ini 文件中的項目會決定您將使用哪個 JDBC 驅動程序和 URL,以及您將執行的 SQL 命令文件。dbinit.ini 文件包括 :
JDBCdriver=org.gjt.mm.mysql.Driver
DBConnURL=jdbc:mysql://192.168.23.38:3306/devworks?user=dbuser
SQLcmds=DBPop.sql
命令文件 DBPop.sql 正包含了待執行的 SQL 命令:
drop table SimpContact;
create table SimpContact ( name char(30), age int(4));
insert into SimpContact values ('Joe', 33);
insert into SimpContact values ('Mary', 48);
這裡是更為靈活的數據訪問程序:
清單 2.DBScriptFlex.es
var myProp = new java.util.Properties;
myProp.load(new java.io.FileInputStream("dbinit.ini"));
var DBDriver = myProp.getProperty("JDBCdriver");
var DBConn = myProp.getProperty("DBConnURL");
var SQLFile = myProp.getProperty("SQLcmds");
writeln("Using JDBC Driver " + DBDriver);
writeln("Using Connection URL " + DBConn);
var db = new Database(DBDriver);
db.connect(DBConn);
var cmdFile = File(SQLFile);
var allCmds = cmdFile.readAll();
var Cmds = allCmds.split("\n");
for (i=0; i< Cmds.length; i++)
result = db.executeCommand(Cmds[i]);
db.disconnect();
請注意在創建 java.util.Properties 對象時對 JavaAccess 擴展名的使用 。用同樣的方法,我們還創建了一個 java.io.FileInputStream 。我們用這個 流從 dbinit.ini 文件中載入屬性。換句話說,我們能用 JavaAccess 擴展名的 Packages 對象來訪問任何 Java 程序包。例如,要創建一個叫做 com.ibm.devworks.ScanProp 的 Java 類的示例,我們使用:
var myScanProp = new Packages.com.ibm.devworks.ScanProp;
清單 2 同樣顯示了針對 FESI 的文件 I/O 擴展名的使用。我們首先使用 SQLcmds 屬性值來創建一個文件對象。我們使用此文件對象的 readAll() 方法 將文件的內容讀入一個 EcmaScript String 。然後我們使用 EcmaScript String 的 split() 方法將內容拆分為獨立的行。我們將每一行存儲在其自己的 數組元素內 -- 一個叫做 Cmds 的數組內。最後,我們繞過 Cmds 數組,並調用 數組每個元素上的 executeCommand() 方法。DBScriptFlex.es FESI 程序可以 用來在任何支持 JDBC 的 RDBMS 上創建一個表,並填入數據。
使用 Swing GUI
您能通過 JavaAccess 擴展名對 AWT 或 Swing 庫的支持,使用 FESI 來創 建交互式 GUI 應用程序。這一 23 行的程序將是本文中研究的最復雜的 FESI 程序了 -- 但您會發覺它非常易懂。源代碼在 GUIViewer.es 中,並在下面重新 編寫一遍。
首先,我們用 JavaAccess 擴展名中的 Packages 對象來引用 javax.swing 程序包。現在我們能用 Swing.xxx 來引用該程序包中的任何類了。
var Swing = Packages.javax.swing;
接下來,我們像先前那樣連接到數據庫。這一次,我們通過 select * from SimpContact 語句來進行 SQL 查詢。我們使用 FESI 數據庫擴展名中數據庫對 象的 executeRetrieval() 方法來實現這一目的。這一調用將返回一個我們重復 循環的數據結果集。
var db= new Database("org.gjt.mm.mysql.Driver");
db.connect("jdbc:mysql://192.168.23.38:3306/devworks? user=dbuser");
var res = db.executeRetrieval("select * from SimpContact");
現在已經准備好建立基於 Swing 的 GUI 了。我們通過在 JScrollPane 中創 建一個 JTable 並將其加入 JScrollPane 來開始這一工作。我們將在程序的最 後創建這個封閉的框架。
var maxCols = res.getColumnCount();
var dbPanel = new Swing.JPanel;
var dbTable = new Swing.JTable(5, maxCols);
dbPanel.add(new Swing.JScrollPane(dbTable));
接下來,我們使用 JavaAccess 創建列的名稱向量,這些名稱是用 getColumnName() 方法從結果集中的元數據處獲取的。然後我們用這一向量來設 定 JTable 的列的標題。請注意,與 EcmaScript 數組基於 0 的索引不同,結 果集的操作方法在基於 1 的索引上工作。
var dbVec = new java.util.Vector;
for (col=0; col<maxCols; col++)
dbVec.add(res.getColumnName(col+1));
dbTable.getModel.setColumnIdentifiers(dbVec);
然後我們仔細研究結果集中每一行中每個列的值,並在 JTable 中設定相應 的值。
row = 0;
while (res.next()) {
for (col=0; col<maxCols; col++)
dbTable.setValueAt(res.getColumnItem(col+1), row, col);
row++;
}
GUI 事件處理
現在已准備好創建即將成為應用程序框架的 JFrame 了。然後我們將 JPanel 連同 JTable 加入到 JFrame 中,並為 WindowClosing 事件連接一個事件處理 程序。請注意簡單句法。一旦在 FESI 運行期接受了 WindowClosing 事件,分 配給 onWindowClosing 的 EcmaScript 代碼片段就會被執行。
var dbFrame = new Swing.JFrame("FESI DB Viewer App");
dbFrame.getContentPane().add(dbPanel);
dbFrame.pack();
dbFrame.setVisible(true);
dbFrame.onWindowClosing = "dbFrame.dispose(); exit();";
若現在運行 GUIViewer.es 腳本,您將看到如圖 2 顯示的數據庫內容。如在 運行中遇到問題,請確認 JDBC 驅動程序位於運行 FESI 的 VM 類路徑中。
圖 2. RDBMS 數據的 FESI GUIViewer
將腳本引擎集成到應用程序中
目前為止,我們的討論都是圍繞著使用 EcmaScript 來簡單、輕松地平衡 Java 平台上的 API 。我們未涉及如何將 FESI 自身嵌入到 Java 程序中去。這 一方法將允許我們:
創建使用 Java 和 EcmaScript 的結合來實現內部邏輯的 Java 應用程序。 我們能夠在配置期間輕松地定制這些應用程序的核心邏輯,或者甚至在運行期間 動態地修改它。
創建支持 EcmaScript 作為腳本語言的 Java 應用程序 -- 通常叫做腳本主 機
您能通過使用 fesi.jar 存檔中的 FESI.jslib 庫包來利用 FESI。我們將使 用以下的類:
FESI.jslib.JSGlobalObject -- 引用全程對象聲明一個 FESI 解釋程序實例
FESI.jslib.JSUtil -- 一個有效類,包括適合創建 FESI 求值程序(解釋程 序實例)的工廠方法。
這一叫做 EmbedScript.java 的樣本 Java 程序在內部整合了 DBScriptFlex.es 和 GUIViewer.es 的功能。它呈現出一個帶有兩個按鈕的用戶 界面,如圖 3 所示。按下任何一個按鈕,您就能執行相應的功能性。事實上, 該程序在內部創建了一個 FESI 解釋程序,並解釋了相關的 EcmaScript 文件。--www.bianceng.cn
圖 3.一個嵌入了 FESI 的 Java 程序
讓我們來回顧一下 EmbedScript.java 的源代碼。首先需要連接到 FESI 以 引入 FESI.jslib 程序包:
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import FESI.jslib.*;
JSGlobalObject 引用的, FESIInst ,將包括 FESI 解釋程序的實例。這兩 個我們先定義的常量與兩個按鈕的操作邏輯相對應。我們在 EcmaScript 中編寫 了邏輯的每個片段的代碼:
public class EmbedScript extends Frame implements ActionListener {
static JSGlobalObject FESIInst = null;
static final String POPULATE_RDBMS = "DBScriptFlex.es";
static final String GUIVIEW_RDBMS = "GUIViewer.es";
private Panel basePanel;
private Button action1Button;
private Button action2Button;
// extensions[] contains the FESI extension that we will load with our
// FESI evaluator.
String[] extensions = new String[] {"FESI.Extensions.JavaAccess",
"FESI.Extensions.BasicIO",
"FESI.Extensions.FileIO",
"FESI.Extensions.Database"};
接下來,構造程序建立 GUI,並為兩個按鈕添加 ActionListener :
public EmbedScript() {
super("Java Application with Embedded FESI");
action1Button = new Button("Create & Populate RDBMS");
action2Button = new Button("View RDBMS Data");
action1Button.addActionListener(this);
action2Button.addActionListener(this);
addWindowListener( new WindowAdapter() {
public void windowClosing(WindowEvent ev) { System.exit(0); }
});
basePanel = new Panel();
basePanel.setLayout(new FlowLayout());
basePanel.add(action1Button);
basePanel.add(action2Button);
add(basePanel);
}
下面顯示的按鈕操作事件處理程序使用了 runFESI 方法來運行 EcmaScript 程序。請注意,在 FESI 求值程序不存在,該程序會使用 JSUtil 類來創建一個 FESI 求值程序(解釋程序實例)。 JSUtil 類是創建 FESI 解釋程序的工廠類 。在這種方法裡,我們創建了一個 BufferedReader ,並將整個 EcmaScript 文 件讀入 String ,然後我們將 String 傳遞給 FESI 求值程序供解釋。這就是在 Java 代碼內處理 EcmaScript 的方法。
private void runFESI(String scriptFileName) {
try {
if (FESIInst == null_)
FESIInst = JSUtil.makeEvaluator(extensions);
BufferedReader inFile =
new BufferedReader(new FileReader(scriptFileName));
String scriptCode = "";
String line;
while ((line = inFile.readLine()) != null) {
scriptCode += line;
}
FESIInst.eval(scriptCode);
} catch (Exception ex) {
ex.printStackTrace();
}
}
接下來,按鈕操作事件處理程序只需用恰當的 EcmaScript 文件名就可以簡 單地調用 runFESI() 方法:
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == action1Button) {
runFESI(POPULATE_RDBMS);
}
if (evt.getSource() == action2Button) {
runFESI(GUIVIEW_RDBMS);
}
}
public static void main (String args[])
throws Exception
{
EmbedScript myFESI = new EmbedScript();
myFESI.pack();
myFESI.show();
} // of main
}
買方負責
FESI 利用 Java 平台的能力,提供了腳本語言的簡單性。我們能使用 FESI 為 Java 語言不盡適合的問題區域提供有效的跨平台解決方案。然而這種能力也 有可能被濫用。EcmaScript 不是為編寫大型程序而設計的語言。它所推崇的特 殊編程風格,雖然方便,但同時也能創建意義模糊的代碼,有副作用,且可能不 易於維護。這就是快速上手編程的本質。換句話說,在項目中使用 FESI 時要學 會慎重。更具體地說,我建議在內部開發一個可用的 EcmaScript 編程指導方針 ,並考慮重寫 Java 語言中任何可能超過一頁長度的腳本。