内容


快速上手 Java 编程

FESI 为 Java 平台的强大功能提供了灵活的脚本

Comments

从概念到产品, 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 主环境
图 1.JavaScript 主环境
图 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 Stringsplit() 方法将内容拆分为独立的行。我们将每一行存储在其自己的数组元素内 -- 一个叫做 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
图 2. RDBMS 数据的 FESI GUIViewer
图 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 文件。

图 3.一个嵌入了 FESI 的 Java 程序
图 3.一个嵌入了 FESI 的 Java 程序
图 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 语言中任何可能超过一页长度的脚本。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=53225
ArticleTitle=快速上手 Java 编程
publish-date=07112001