内容


使用 JSP 集成 Amazon Web Service

Comments

在本系列文章的 第 1 部分 中,我们介绍了如何使用 Notes/Domino 6 来集成 Amazon Web Service。我们确信很多读者很想了解如何使用 J2EE 来创建类似的应用程序,因为 IBM 已经从技术方面做出了很多努力。因此,在本文中,我们将介绍如何使用 J2EE 来重新创建在第一篇文章中构建的 Notes 应用程序,同时还将介绍这种技术与 Domino 的不同。本文的目的是为 Domino 开发人员简要介绍如何可以使用 J2EE 采用一种不同的方法来开发一个类似的 Amazon Web Service 应用程序。本文假设您是一名非常有经验的 Java 开发人员。

首先,我们先来了解一下如何使用 Dreamweaver 和 JSP(JavaServer Page)来创建应用程序;这种技术被用于最简单的 Web 应用程序,至于原因,我们会慢慢介绍。然后,我们将介绍如何使用 EJB(Entity JavaBeans)、JavaBeans 和 Struts 来创建应用程序,这种技术通常用于更加健壮、更复杂的 Web 应用程序。

J2EE 技术和框架

在 J2EE 世界中有很多不同的技术和框架,例如 Cocoon(XML/XSLT)、Structs、ColdFusion MX、Java Server Faces,等等,它们都是在 J2EE 平台上运行的。当然,J2EE 平台允许您在运行 J2EE 的平台上使用任何一种框架,这就为您提供了一个可以使用多种平台环境的承诺。

Domino 服务器也可以采用混合型的 J2EE/Domino 设计,使用 Domino 表单来显示用户界面,使用 Java servlet 来实现对表单的处理。这为表单处理提供了更多的灵活性,并且比纯粹的 Domino 应用程序具有更好的性能,但是很多纯粹的 J2EE 开发人员可能会认为这种方式非常奇怪。

另外一种可以选择的 Domino/J2EE 混合架构将使用一台 J2EE 服务器(通常是 JSP,例如 Tomcat)来显示用户界面,但使用 Domino 数据库来存储数据。这可以使用 Domino JSP 定制标签实现,在 LDD Today 概述“Jeff Calow on new Web technologies in Domino 6” 中对此进行了讨论。另外,这种架构提供了比纯粹的 Domino 应用程序更好的性能,但是大部分开发人员使用它是为了改善控制 Web 站点的图形化层次结构的能力,因为 Domino 在自己的 Web 视图中并没有提供每个元素的位置。

与使用 Domino 创建的站点相比,这两种混合技术在性能方面的改善都不是很明显,但是它们都提供了一种替代品,替代品需要的工作量比纯 J2EE 应用程序更少;而且对于 Domino 开发人员来说,通过这种可选技术来体验 J2EE 技术也非常有帮助。我们将在本系列的第三篇文章中介绍使用 Lotus Domino Toolkit for WebSphere Studio 来组合使用 Domino/J2EE 技术的方法,以及如何使用此工具为 Notes 数据库中保存的 Amazon 书籍数据开发一个 Web UI。

什么是 J2EE?

J2EE 是一组标准的技术和 API 集,Sun Microsystems 将其定义为一台 J2EE 服务器成为兼容服务器之前必须具备的一些需求。与 Web 开发前景有关的一些技术是 servlet、JSP、JavaBeans 和 EJB。

对于简单的 Web 应用程序来说,您可以使用 servlet 和 JSP。servlet 和 JSP 都受到 Apache Jakarta 项目的 Tomcat 引擎的支持。servlet 和 JSP 的效率比 Notes/Domino 代理高,因为它们只有在首次使用时才需要加载;相比较而言,Java 代理在每次调用时都会重新进行加载,因此它们占用的 CPU 处理时间更多。从 JSP 版本 1.1 开始,增加了 JSP 定制标签,您并不需要在其中编写 JSP 中的代码逻辑,因此您可以纯粹将 JSP 作为一个表示层。

EJB 通常都是与 J2EE 应用程序相关的。它们提供了一种封装数据库访问和业务逻辑的方法,这样就可以在多台服务器上实现分发了。在正确完成处理之后,数据库访问被隐藏到了业务逻辑对象之后,因此,那些使用 JSP 的 Web 站点的开发人员并不知道如何组织数据库。

J2EE 强大功能的来源之一是它可以将一个层次划分为多个单独的服务器。虽然 Domino 可以运行数据库、业务和 Web 服务器上的所有表示/UI 层,但 J2EE 可以将这三部分划分到不同的服务器上。另外,每个部分都可以使用集群,这样就可以在层次级别上具有一些冗余。虽然您可以对 Domino 服务器采用集群,但是您并不能非常精准或深入地进行这种聚集,因此一个精确定义的 J2EE 应用程序天生就比一个类似的 Domino 应用程序范围更加广泛,因为您可以将其扩充到多台机器上。缺点是这种技术的开发成本更高并且更复杂。

使用 RDBMS 数据库

正如大部分 Domino 开发人员所知道的那样,将很多个字段放到一个表单中可以定义一个 Domino 数据库。这种设计非常简单(也就是说,表单和视图的设计并不需要一个正式的设计过程),也可以使用一个正式的过程进行设计;您可以任意添加字段,尤其是将某些字段从单值更改为多值。字段的大小并不需要提前指定,不过您必须要知道字段大小的限定值。在创建表单之后,就可以用视图来简单地显示这些字段,其中包括那些多值字段。

在 RDBMS(关系数据库管理系统)中,在最初的数据库设计上您需要更多的努力,因为修改表的结构是非常痛苦的一件事情,主要是因为所有的外键链接和字段大小都必须提前声明。在某些情况中,您还需要删除表以及表中的所有数据,修改数据库的设计,然后重新加载数据。

需要对多值字段进行更深入的讨论。您通常会将一个 Domino 文档映射为 RDBMS 表中的一行数据。Domino 文档中的每个字段可以映射成为该表中的一列。然而,一个表中的每一列(也就是每个字段)都只能存储一个值。在一个 RDBMS 中,您需要单独创建一个具有两列的表。一列使用一个外键链接到表单上(表中的一行)。另外一列链接到另外一个表上,其中列出了这个多值字段所有可能的值。

安全性

Domino 基础设施的另外一个主要特性为您提供的是用户管理和文档级别的安全性。用户角色和访问控制都是 Domino 数据库结构中与生具来的特性。虽然 RDBMS 具有类似的用户管理(添加/删除/分组),但是大部分 RDBMS 都没有定制角色的支持;它们的角色都是特定于 RDBMS 任务的,例如,插入行、删除行、修改模式,等等。更糟糕的是,在一个典型的 Web 应用程序中,您可能不会使用每个用户来登录数据库,因为所有的数据库访问都是通过一个用户进行的,这样就可以采用数据库连接池,提高系统的性能。

在 RDBMS 中,文档级别的安全性是使用具有特定选择和视图的存储过程来限制数据库的访问权限实现的。在 RDBMS 中,没有行级别的访问控制。如果您希望模拟这种功能,就必须自己添加表来列出可以访问表中某行的用户或角色。

正如您可以看到的一样,大部分 RDBMS Web 应用程序并没有与 Notes 应用程序的安全级别类似的安全级别,因为 Notes 天生就有一个非常紧密的安全性模型。然而,Web 应用程序可以依赖于一个单一访问点对 Web 页面进行访问。通过依赖于这个单一访问点,在访问数据库之前,可以为 RDBMS Web 应用程序添加安全性。不幸的是,如果开发人员试图将其放到一个断开连接的笔记本上,那么对于这个单一访问点的依赖会导致 Web 应用程序更不安全,因为用户可以使用支持 ODBC 的任何应用程序直接访问本地数据库。这样做就不能通过复制安全地使用标准的 Web 应用程序,而这在 Notes 用户中都是理所当然的一种功能。

Macromedia Dreamweaver 在用户界面层提供了一种非常简单的安全性。Dreamweaver 让 Web 开发人员可以向 Web 页面(ASP、JSP、Cold Fusion)添加一些操作来提供登录信息,对页面的访问权限进行控制,或者根据用户具有的访问级别决定隐藏部分 Web 页面的内容,等等。这需要使用一个用户表,表中包括用户名、密码和访问级别等字段。每个用户只可以有一个访问级别,因此 Dreamweaver 并不是基于角色的。所有的安全性都是在 Web 页面级别上进行控制的。

EJB 提供了一种方法级的安全模型。用户可以分配 J2EE 服务器中的角色,应用程序开发人员可以指定哪个角色可以访问 EJB 中某个特定的方法。由于 EJB 是由 Web 页面的设计者或 JavaBean 的编写者使用的(用来封装业务逻辑),因此它可以在 Web 页面被攻击时提供另外一层的安全性。另外,J2EE 服务器也可以对特定的 URL 进行保护(例如,/admin 目录中的页面),因此它们就只能由那些被分配了特定角色的用户进行访问,例如 Admin 角色。另外,这些都发生在访问 RDBMS 之前,因此如果用户可以直接访问 RDBMS,那么他可以绕过您在 EJB 中设置的任何安全性。

数据库模式

各种 Web 应用程序的主要设计工作中的一个重要部分就是用来存储应用程序数据的关系数据库的设计。这种设计需要支持 Web 应用程序的所有特性,这在首次设计时非常重要,因为对此进行修改要比在 Domino 中修改设计困难得多,对于后者,我们可以任意在表单和视图中添加多个字段。我们将根据下图来设置表:

图 1. 数据库模式图
数据库模式图
数据库模式图

下面是用来生成该表的 SQL 语句。在第一部分中,SQL 语句用来在数据库设计发生变化时首先删除数据库中已有的表:

DROP TABLE "Books_Categories"
DROP TABLE "Books_Versions"
DROP TABLE "Versions"
DROP TABLE "Categories"
DROP TABLE "Books"
DROP TABLE "Users"

下面的代码将为应用程序创建所有的表:

CREATE TABLE "Books"
(
    "ASIN" varchar(30) NOT NULL PRIMARY KEY,    
    "Title" varchar(255) NOT NULL,    
    "Authors" varchar(255),    
    "Publisher" varchar(255),    
    "ISBN" varchar(30),    
    "State" varchar(30),    
    "LastFound" datetime
)
CREATE TABLE "Versions"
(
    "VersionID" int NOT NULL PRIMARY KEY,    
    "Description" varchar(50) NOT NULL
)
CREATE TABLE "Categories"
(
    "CategoryID" int NOT NULL PRIMARY KEY,    
    "Description" varchar(50) NOT NULL
)
CREATE TABLE "Books_Versions"
(
    "ASIN" varchar(30) NOT NULL,    
    "VersionID" int NOT NULL
)
CREATE TABLE "Books_Categories"
(
    "ASIN" varchar(30) NOT NULL,    
    "CategoryID" int NOT NULL
)

下面的代码将添加上图中的所有外键链接:

ALTER TABLE "Books_Versions" ADD CONSTRAINT "FK_book_version_ASIN_key" FOREIGN KEY (
    "ASIN"
)
REFERENCES "Books" (
    "ASIN"
)
ALTER TABLE "Books_Versions" ADD CONSTRAINT "FK_book_version_key" 
FOREIGN KEY (
    "VersionID"
)
REFERENCES "Versions" (
    "VersionID"
)
ALTER TABLE "Books_Categories" ADD CONSTRAINT "FK_book_categories_ASIN_key" 
FOREIGN KEY (
    "ASIN"
)
REFERENCES "Books" (
    "ASIN"
)
ALTER TABLE "Books_Categories" ADD CONSTRAINT "FK_book_category_key" 
FOREIGN KEY (
    "CategoryID"
)
REFERENCES "Categories" (
    "CategoryID"
)

下面的代码将为 Dreamweaver 的访问控制管理创建一个表。在本文后面部分,我们将讨论 Dreamweaver 的访问控制管理。

CREATE TABLE "Users"
(
    "Username" varchar(100) NOT NULL PRIMARY KEY,    
    "Password" varchar(100) NOT NULL,    
    "AccessLevel" varchar(100),    
    "Email" varchar(100)
)

下面的代码将对这个数据库进行初始化;这等同于在 Notes 中为一个为话框列表字段添加一些可以选择的值:

INSERT INTO "Versions" ("VersionID", "Description")
    VALUES (1, 'R3')
INSERT INTO "Versions" ("VersionID", "Description")
    VALUES (2, 'R4')
INSERT INTO "Versions" ("VersionID", "Description")
    VALUES (3, 'R4.5')
INSERT INTO "Versions" ("VersionID", "Description")
    VALUES (4, 'R4.6')
INSERT INTO "Versions" ("VersionID", "Description")
    VALUES (5, 'R5')
INSERT INTO "Versions" ("VersionID", "Description")
    VALUES (6, 'R6')
INSERT INTO "Categories" ("CategoryID", "Description")
    VALUES (1, 'Administration')
INSERT INTO "Categories" ("CategoryID", "Description")
    VALUES (2, 'Education')
INSERT INTO "Categories" ("CategoryID", "Description")
    VALUES (3, 'Infrastructure')
INSERT INTO "Categories" ("CategoryID", "Description")
    VALUES (4, 'Programming')
INSERT INTO "Categories" ("CategoryID", "Description")
    VALUES (5, 'Platforms')
INSERT INTO "Categories" ("CategoryID", "Description")
    VALUES (6, 'User')
INSERT INTO "Users" ("Username", "Password", "AccessLevel")
    VALUES ('admin', 'password', 'Administrator')

现在我们已经准备好将 Amazon Web Service 的数据加载到这个数据库中了。如果您还没有安装 Amazon Web 服务,可以可以从 Amazon.com 下载 开发人员工具包

导入 Amazon Web Services

要将 Amazon Web Services 书籍的信息导入这个数据库中,需要使用在本系列文章第 1 部分中定义的 BookTracker 接口来创建一个适当的类。我们使用 JDBC 与数据库进行通信,并将 Amzon 信息加载到表中。

import java.sql.*;
	// implements the book update object using a JDBC database as a data store
	public class JdbcBookTracker implements BookTracker {
    	Connection connBooks;    
	    PreparedStatement findBook;    
	    PreparedStatement updateBook;    
    	PreparedStatement addBook;

构造应用程序将建立 RDBMS 连接:

// constructor    
	public JdbcBookTracker(String driver, String datasource, 
	String username, String password) throws Exception {    
	    // initialize JDBC connection    
    Driver driverBooks = (Driver)Class.forName(driver).newInstance();    
    connBooks = DriverManager.getConnection(datasource,username,password);    
    }
    // Java pseudo-destructor
    public void finalize() throws Exception {
	    // clean up JDBC connections 
		    connBooks.close();
	    }

以下是数据库更新代码的主程序。它与将 Amazon Web Services 数据加载到 Domino 应用程序中所使用的应用程序类似:

    // update book from amazon query    
    public void updateBook(    
    	String title,    
    	String ASIN,    
   	 	String ISBN,    
   	 	String authors,    
	    String publisher,    
	    String rating,    
	    int numreviews)    
	    throws Exception {    
	// check for blank ISBN    
	    if (ISBN == null) {    
	    	System.out.println(title + " has a null ISBN!");    
    	}    
	// check for blank ASIN    
	    if (ASIN == null) {    
        	System.out.println(title + " has a null ASIN!");
	    }
    // see if we can find the book
    	findBook = connBooks.prepareStatement
    	("SELECT Title FROM Books WHERE ASIN = ?");
    findBook.setString(1, ASIN);
    ResultSet booksFound = findBook.executeQuery();
    boolean isInDatabase = !booksFound.next();
    booksFound.close();
    findBook.close();
    if (isInDatabase) { 
       System.out.println("Adding " + title + " to database");
       // new book, so we have to add it to the database
       addBook = connBooks.prepareStatement("INSERT INTO Books 
       (ASIN,Title,Authors,Publisher,ISBN,State,LastFound) VALUES 
       (?,?,?,?,?,?,CURRENT_TIMESTAMP)"); 
          addBook.setString(1, ASIN);
          addBook.setString(2, title);
          addBook.setString(3, authors);
          addBook.setString(4, publisher);
          addBook.setString(5, ISBN);
          addBook.setString(6, "A"); // A for added
    //addBook.setTimestamp(7, new Timestamp
    (java.util.Calendar.getInstance().
    getTimeInMillis())); 
    addBook.execute(); 
          addBook.close();
    } else { 
          System.out.println("Updating timestamp for " + title);
          // update book timestamp
          updateBook = connBooks.prepareStatement("UPDATE Books 
          SET LastFound = CURRENT_TIMESTAMP WHERE ASIN = ?");
          updateBook.setString(1, ASIN);
          updateBook.execute();
          updateBook.close();
          }
    }
}

调用代码非常简单:

BookTracker tracker = new JdbcBookTracker
("sun.jdbc.odbc.JdbcOdbcDriver", 
"jdbc:odbc:Amazon", "sa", "");    
getAmazonBooks("lotus domino", tracker);

假设您有一个为上面定义的模式的数据库使用的 ODBC 数据源,它的名字是 Amazon,很多数据库的默认用户名/密码是“sa”/(即密码为空,管理员的用户名是“sa”)。在加载数据之后,我们就可以为用户提供一个 J2EE 版本的 Amazon Web Services 界面。

表单和视图

表单和视图是我们在 Donimo 环境中理所当然要使用的元素。对于其他 Web 技术来说,您通常需要创建三个项目:一个页面用读模式来显示文档的内容,一个页面用编辑模式来显示文档的内容,还有一个页面用来显示一个视图。您还需要创建单独的管理 Web 页面,用该页面来处理用户、角色和组的需求。大多数 Web 环境都有一个类似 Hide-Whens(何时隐藏)的概念。这些 Web 环境并没有 Domino 视图中的分页、分类或搜索功能。然而,对于最终的输出结果来说,您的确需要比对 Domino 元素进行更多的控制。

使用 Macromedia Dreamweaver

Macromedia Dreamweaver 为 HTML 提供了丰富的可视化编辑环境,这些功能是所有的 Web 开发环境中都需要的。与其他工具相比,Dreamweaver 提供了更好的代码编辑功能,包括为 Web 站点使用的宏,它们可以使用某种页面语言,例如 ASP、ASP.Net、JSP、ColdFusion 和 PHP。扩展宏让您可以自动进行登录,实现类似何时隐藏的功能,自动显示结果集,创建客户结果集等等。您可以编辑并添加自己的代码宏。在不同的页面之间可能存在一些可以共享的代码(HTML 或脚本)。对于 JSP 的支持来说,Dreamweaver 为 JavaBeans 和 JSP 定制标签库提供了完整的方法/标签。

Dreamweaver 中的视图

让我们开始创建一个视图来显示所有的书籍。首先在 Server Behaviors 中为 JSP 添加一个 RecordSet,并选择希望在该表中显示哪个表以及该表中的哪些列:

图 2. Recordset 对话框
Recordset 对话框
Recordset 对话框

您可以看到我们已经选择从数据库的 Books 表中显示 Title、Authors 和 Publisher 字段;我们还会根据 Title 字段,对结果进行排序。在完成这个步骤之后,就可以将这些字段从 Bindings 窗口拖动到 Web 页面上:

图 3. Bindings 窗口
Bindings 窗口

由于我们正在创建一个视图,应该创建一个表来显示文档中的每一行的内容:

图 4. Categories 视图
Categories 视图
Categories 视图

注意,该视图中有几个“Repeat”指示器,并且高亮显示了表中的某一行,指出这是一个 Repeating Region。这就是 Dreamweaver 的显示 RecordSet 的方式,它通过循环遍历记录并重复 HTML 部分的内容来显示 RecordSet。您也可以指定在 Repeat Region 对话框中显示多少条记录:

图 5. Repeat Region 对话框
Repeat Region 对话框
Repeat Region 对话框

最后,您可以在该页面中为结果添加一个导航条。虽然我们可以使用一个文本或图形形式的导航条,但是为了简便起见,此处我们将使用一个文本导航条:

图 6. 导航条
导航条
导航条

现在我们可以看到在 Web 浏览器中显示这个页面时的样子:

图 7. Web 页面
Web 页面
Web 页面

注意,如果您现在在第一个页面中,那么导航条隐藏 Prev/First 部分。这个导航条还知道如何根据结果进行分页。由于您的重复字段被设置为一次只显示 10 个结果,因此当您单击 Next 时,这个导航条就会显示下 10 条结果。当您在最后一个页面中时,它还会隐藏导航条中的 Next/Last 部分。

Dreamweaver 中的用户身份验证

要支持 Notes 中那种对书籍进行编辑或分类的功能,还需要提供一个单独的 Web UI。然而,首先需要创建一个登录页面,因为我们并不希望每个用户都可以修改书籍的类别。可以在页面中放上几个登录字段来实现这种功能,如果登录失效,就显示一条错误消息:

图 8. 无效的登录界面
无效的登录界面
无效的登录界面

您可以通过创建一个表单区域来实现这种功能,然后添加一个包含字段和字段标签的表。矩形的红色区域是表单的边界。表单操作被设置为跳回这个登录页面,因此必须添加一些代码来处理实际的登录操作。我们已经添加了一条消息( JSP 图标边上的文本)来显示登录失败的用户信息。

接下来我们要向这个页面中添加一个 Login Server Behavior:

图 9. Log In User 对话框
Log In User 对话框
Log In User 对话框

正如您可以看到的,允许您指定使用数据库中的哪个表来进行身份验证,并允许您指定登录用户的访问级别(AccessLevel 字段)。您还可以指定用户登录成功或失败之后将转向哪个页面。在登录失败时,我们添加了一个查询字符串参数 “lf”。我们还添加了一些定制的代码(这就是 JSP 图标指示的内容)来处理这种功能:

<% if (request.getParameter("lf") != null) { %>    
<p align="center">Invalid Login!</p>    
<% } /* end request.getParameter(lf) != null */ %>

不幸的是,并不存在这种内嵌的 Dreamweaver Server Behavior,这与视图的导航条的隐藏功能不同。由于这个原因,您需要使用一个很好的 Java 工具来实现这种等效的自动隐藏功能,除非您使用的是一个具有这种自动隐藏功能支持的 JSP 定制标签库。

Dreamweaver 中的访问控制

Dreamweaver 有一种显示对特定页面的访问的方便方法,除非用户具有特定的访问权限,否则将不能访问特定页面。Dreamweaver 对特定页面的访问是通过向页面中添加一个 Restrict Access Server Behavior 实现的:

图 10. Restrict Access to Page 对话框
Restrict Access to Page 对话框
Restrict Access to Page 对话框

在这种情况中,只有哪些在自己的登录中具有管理员权限的用户可以访问这个页面。真正实现这种功能的代码(在该页面的可视化表示中,您看不到这些代码)如下所示:

    <%
    // *** Restrict Access To Page: Grant or deny access to this page
    String MM_authorizedUsers="Administrator";
    String MM_authFailedURL="accessdenied.jsp";
    boolean MM_grantAccess=false;
    if (session.getValue("MM_Username") != null && !session.getValue
    ("MM_Username").equals("")) {    
    if (false || (session.getValue("MM_UserAuthorization")=="") || 
    (MM_authorizedUsers.indexOf((String)session.getValue("MM_UserAuthorization")) 
    >=0)) { 
    MM_grantAccess = true;
    }
    }
    if (!MM_grantAccess) {
    String MM_qsChar = "?";
    if (MM_authFailedURL.indexOf("?") >= 0) MM_qsChar = "&";
    String MM_referrer = request.getRequestURI();
    if (request.getQueryString() != null) MM_referrer 
    = MM_referrer + "?" + request.getQueryString();
    MM_authFailedURL = MM_authFailedURL + MM_qsChar 
    + "accessdenied=" + java.net.URLEncoder.encode(MM_referrer);
    response.sendRedirect(response.encodeRedirectURL(MM_authFailedURL));
    return;
    }
    %>

正如您可以看到的,它是与 Login Server Behavior 紧密地绑定在一起的。

Dreamweaver 的缺点

Dreamweaver 为编辑页面语言 Web 页面提供了很好的环境(尽管它不能生成部署描述符)。不幸的是,异常行为会违反良好的 Web 架构的一条规则:保持 UI 和逻辑分离。对于简单的站点,可以这样做,但是对于复杂而健壮的站点来说,必须保持二者是分离的。因为要将数据库访问操作和代码直接加入 JSP 中,这使得对 Web 站点的管理变得更加困难,除非您严格使用 Dreamweaver 开发环境,或编写自己的操作。即使您继续使用 Dreamweaver 环境,也会有很多问题,因为您可能会更新 Dreamweaver 的版本, Server Behaviors 可能会发生变化,因此可能无法与以前的代码匹配,这样您就需要直接修改 JSP 代码。

JSP 标签

JSP 定制标签和 JavaBeans 使得将用户界面和业务逻辑分离成为可能。JSP 定制标签对于 Web 开发人员来说就像是定制的 HTML 标签。JavaServer Page Standard Tag Library(JSTL)是 JSP 1.3 中必不可少的一部分。JSTL 包含可以用来循环遍历结果集并实现 Hide-When 功能使用的标签。它还提供了 XML 处理和转换标签的功能,并且可以引用与 JavaScript 类似的对象。最后,它还提供了对 JDBC 源的 SQL 访问功能,不过如果您试图将 UI 和业务逻辑分隔开,就应该避免使用这些标签。

举例来说,如果登录失败,那么登录页面将显示一条“invalid login”消息。然而,这需要知道如何编写一个 Java if 表达式,以及要从什么对象中获取查询字符串参数:

    <% if (request.getParameter("lf") != null) { %>    
    <p align="center">Invalid Login!</p>    
    <% } /* end request.getParameter(lf) != null */ %>

使用 JSP 定制标签重新编写这段应用程序,代码如下:

    <c:if test="${!empty param.lf}">    
    <p align="center">Invalid Login!</p>    
    </c:if>

这就简单多了,也不必要求 Web 页面的设计者必须了解 Java 的语法了。

所有的数据库访问都应该隐藏在 JavaBeans 中,以防止将 Web 站点的 UI 限制在数据库中的数据上。降低数据的耦合度可以防止数据库模式的变化,这会导致业务逻辑和用户界面发生巨大的变化。如果处理适当,那么 JSP Web 页面的设计者需要知道的惟一一件事情就是如何读取 JavaBean 的值(这样就可以在页面中的适当部分显示)和要将页面中的 POST 部分发往哪个 URL(通常是一个 servlet)。

EJB 和封装数据库访问

Entity JavaBeans 用来对数据库访问进行封装。对于简单的数据库来说,一个 EJB 对象就表示一个表中的一行。不幸的是,EJB 不太容易编写,因为您需要为每个 EJB 创建三个类(和三个单独的文件):home 类、local 接口和 remote 接口。home 类让用户可以创建新的 bean 实例,或者查找现有的实例(例如数据库中的行)。local 和 remote 接口与 Microsoft COM 代理类似。它们允许您访问本地的 Java 虚拟机或另外一台远程服务器上的 EJB。

有几个工具可以用来减轻这种问题。XDoclet 类似于 Microsoft 的 MIDL 编译器,使用这种编译器,可以从想用作 EJB 的类中生成代理类;XDoclet 还提供了一种与数据库无关的指定查询的方法,并为这些访问类创建了部署描述符。MiddleGen 提供了一种从数据库本身来生成数据库访问类的方法。AndroMDA(以前称为 UML2EJB)提供了一种从高级的 UML 模型生成数据库访问类的方法。MiddleGen 和 AndroMDA 都可以生成一些可以通过 XDoclet 进行编译的代码。

XDoclet

XDoclet 是一个非常有用的工具,使用这个工具您只需要为访问数据库的 EJB 编写一个类即可,而不用编写 4 个类。它是通过允许您对主要的类和方法进行标记来实现这种功能的,这样就可以自动生成其他必需的 EJB 类。下面是您需要对 Book 表编写的一个 EJB 类:

/**
    * @ejb.bean    
    *    type="CMP"    
    *    cmp-version="2.x"    
    *    name="Book"    
    *    local-jndi-name="amazon/BookLocalHome"    
    *    view-type="both"    
    *    primkey-field="asin"    
    *    
    * @ejb.finder    
    *    signature="java.util.Collection findAll()"    
    *    result-type-mapping="Local"    
    *    method-intf="LocalHome"    
    *    query="SELECT OBJECT(o) FROM Book o"    
    *    
    * @ejb.finder    
    *    signature="java.util.Collection findByIsbn(java.lang.String isbn)"    
    *    result-type-mapping="Local"    
    *    method-intf="LocalHome"    
    *    query="SELECT DISTINCT OBJECT(o) FROM Book o WHERE o.isbn = ?1"    
    *    description="ISBN is not indexed."    
    * @ejb.persistence table-name="Books"    
    * @ejb.transaction type="Required"     
    * @ejb:security-role-ref admin Administrator *
*/
public abstract class BookBean implements javax.ejb.EntityBean {
/**
    * Sets the title    
    *    
    * @param title the new title value    
    * @ejb.interface-method view-type="both"    
    * @ejb:permission Administrator */    
    public abstract void setTitle(java.lang.String title);

从这段代码中可以看出,它定义了如何将一个方法映射到一个特定的表和列上,并指定哪些列是主键、查找方法,等等。您还可以添加一些不同的标签为 JBoss、WebLogic、Orion 和 WebSphere 生成一些部署描述符。黑体加亮显示的那几行是手动添加的,用来向一个方法(在本例中是 setTitle)中添加基于角色的权限;这样,即使 JSP 调用了 EJB,也可以防止那些不是管理员的用户修改书籍的标题。

MiddleGen

如果您早已建立了数据库,那么 MiddleGen 是一个可以自动生成 EJB 的工具。您需要给出一个 JDBC 的数据源,从中可以提取出数据库模式的信息。它可以生成数据库访问的 EJB、一个使用 Jakarta Struts 的 Web 用户界面,以及部署 EAR 文件所需要的其他 XML 文件,您可以将这些文件放到 J2EE 服务器上的部署目录中。MiddleGen 是通过创建 XDoclet 用来创建 local/remote 接口的文件来实现这种功能的。上面的代码就是由 MiddleGen 生成的。

AndroMDA

AndroMDA 采用了一种不同的方法。它允许从一个 UML 项目文件中生成 EJB;UML 工具可以导出一些 XML 文件,它们类似于 word 处理的 RTF 格式。这就是从头开始创建一个项目并且不想从数据库设计开始创建时希望做的事情。AndroMDA 创建一个适当的 XDoclet 文件,该文件的功能与 MiddleGen 的功能类似;并自动生成适当的 EJB 描述符来处理一对多和多对多的关系。

使用 WebSphere Studio

当您使用开源工具并花费一些时间来正确安装各种工具之后,就会感激 IBM 在最新的 WebSphere Studio 5.0 中集成了这么多优秀的工具。它不仅提供了一种创建 EJB 的自下而上的方法(MiddleGen 技术),而且还提供了一种自上而下的方法,从现有的 EJB 生成数据库;它还提供了一种适中的方法,可以用这种方法将现有的 EJB 映射为特定的表/列。在 WebSphere Studio 中,还没有什么高级的工具可以让您使用 UML 图来创建项目,但是很可能 IBM 会购买 Rational 来实现这种功能。

IBM 已经花费了很多时间来创建向导(提示用户完成某项特定任务所需的所有信息)和部署描述符编辑器,不过它也可以帮助用户理解底层的 XML 配置文件是如何工作的。例如,下面是一个自下而上的 EJB 的连接对话框:

图 11. EJB to RDB Mapping 对话框
EJB to RDB Mapping 对话框
EJB to RDB Mapping 对话框

您可以得到很多预编译的 EJB,它们对数据库进行了映射(尽管这看起来并不像是已经正确检测了多对多的表,但它仍然为 WebSphere Studio Application Developer 5.0 生成 EJB):

图 12. WebSphere Studio Application Developer
WebSphere Studio Application Developer
WebSphere Studio Application Developer

从 EJB 中,您可以创建访问 bean,然后使用另外一个向导为这个访问 bean 生成访问页面;结果非常简单,因此,在有了 EJB 和 访问 bean 之后,就可以很好地编写自己的 JSP 或者 Struts 页面了。访问 bean 位于 EJB 上面的一层中,可以用这些访问 bean 对 EJB 的访问进行抽象,就像是抽象不同的 JavaBeans 一样(没有 remote/home 接口的问题),因此 Web 页面并不需要理解它们如何处理 EJB;有一些 JSP 定制标签来访问不同 JavaBeans 的值,这样 Web 页面的设计者就无需了解有关 Java 方面的知识。

结束语

正如您可以看到的一样,使用 J2EE 技术进行开发要比使用 Domino 进行开发更加复杂。与使用 Domino 的需要相比,使用 J2EE 技术需要更多时间和资源,因为您需要一些专门的知识来设计每个层次的内容(JavaBeans/EJBs、数据库和 Web)。这些工具正在迅速改进,以减轻部分开发工作的负担,但是不断发生的变化同时还意味着在您使用这些工具来实现 Domino 所提供的功能时,也会促现更多的不兼容性问题。在您拥有大型的数据库或进行大量的 Web 操作时,J2EE 技术应该是您要考虑的一种解决方案;UI/数据库/中间件的分离以及聚合这些层次的能力为 J2EE 提供了一片广阔的天空,对于创建 eBay 这种大型的 Web 站点来说,这是必需的。如果您正在将一个 Domino 的应用程序移植到 J2EE 平台上,那么要确保从时间和费用方面的投资考虑值得这样去做。

如果您冒险进入标准的 J2EE Web 开发世界,需要时刻牢记以下内容:

  1. 最好可以自由选择各个组件(后端数据库、Web UI、中间件,等等),但是这意味着您必须确保这些组件可以一起很好地工作。如果您要向现有的 Web 解决方案或开发环境中添加一个新软件,那么请花费一些时间来了解这样做需要付出的“特性”和时间代价。
  2. 准备手工编辑大量的文本文件。虽然在 Notes 数据库中,有很好的用户界面来配置 Notes 中的内容,但是大部分 J2EE 中的内容仍然依赖于 XML 文件的内容(例如部署描述符),您需要使用一个文本编辑器对其进行编辑。这包括学习 Jakarta Ant,它是 Java 世界中与 UNIX 的“make”等效的一个工具,用来帮助生成 EJB。
  3. 在原型设计时可以使用 Sun 的 JDBC/ODBC 驱动,但是在正式发布时不要使用它。因为它存在一些性能方面的问题和其他方面的一些缺点(例如,如果您使用 Microsoft SQL Server 作为数据库,那么要求必须按照与选择列相同的顺序来读取列的值)。
  4. J2EE 应用程序中的错误通常都非常隐蔽。通常您会看到调用堆栈中有一个 Java 异常,这并不能确切地解决产生问题的代码行。
  5. 查询是我们尚未涉及的一种功能,这是因为这个主题非常复杂,足以单独撰写一篇文章。在 Domino 的世界中,启用搜索非常简单,就像在 Database Properties 中选择一个对话框一样。在 J2EE 的世界中,您需要自己去找一个搜索引擎,然后使用一种方法为这个搜索引擎填充数据,然后获得结果,并对结果进行格式化,将它们显示给用户。
  6. 本文也没有涉及后台的代理。在一个 RDBMS 中,这是通过存储过程实现的,在不同的数据库之间,并不能很好地进行移植。新的 RDBMS 已经可以运行 Java 的存储过程,因此与 Amazon Web Services 代码的集成相当简单。

我们在有限的篇幅中介绍了很多技术。每种技术实际上都要更详细地进行介绍,如果您希望更详细地了解某项特定的技术,请与我们进行联系。

在本系列的下一篇文章中,我们将使用 Domino WebSphere Studio Toolkit 来创建一个完整的应用程序,因为这是 Domino 开发人员要集成 J2EE 时最喜欢使用的一种方法。


相关主题


评论

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Lotus
ArticleID=161709
ArticleTitle=使用 JSP 集成 Amazon Web Service
publish-date=07282005