2006年01月06日

Brett McLaughlin , 作家,编辑, O’Reilly and Associates

2006 年 1 月 04 日

五年前,如果不知道 XML,您就是一只无人重视的丑小鸭。十八个月前,Ruby 成了关注的中心,不知道 Ruby 的程序员只能坐冷板凳了。今天,如果想跟上最新的技术时尚,那您的目标就是 Ajax。

但是,Ajax 不仅仅 是一种时尚,它是一种构建网站的强大方法,而且不像学习一种全新的语言那样困难。

但在详细探讨 Ajax 是什么之前,先让我们花几分钟了解 Ajax 什么。目前,编写应用程序时有两种基本的选择:

  • 桌面应用程序
  • Web 应用程序

两者是类似的,桌面应用程序通常以 CD 为介质(有时候可从网站下载)并完全安装到您的计算机上。桌面应用程序可能使用互联网下载更新,但运行这些应用程序的代码在桌面计算机上。Web 应用程序运行在某处的 Web 服务器上 —— 毫不奇怪,要通过 Web 浏览器访问这种应用程序。

不过,比这些应用程序的运行代码放在何处更重要的是,应用程序如何运转以及如何与其进行交互。桌面应用程序一般很快(就在您的计算机上运行,不用等待互联网连接),具有漂亮的用户界面(通常和操作系统有关)和非凡的动态性。可以单击、选择、输入、打开菜单和子菜单、到处巡游,基本上不需要等待。

另一方面,Web 应用程序是最新的潮流,它们提供了在桌面上不能实现的服务(比如 Amazon.com 和 eBay)。但是,伴随着 Web 的强大而出现的是等待,等待服务器响应,等待屏幕刷新,等待请求返回和生成新的页面。

显然这样说过于简略了,但基本的概念就是如此。您可能已经猜到,Ajax 尝试建立桌面应用程序的功能和交互性,与不断更新的 Web 应用程序之间的桥梁。可以使用像桌面应用程序中常见的动态用户界面和漂亮的控件,不过是在 Web 应用程序中。

还等什么呢?我们来看看 Ajax 如何将笨拙的 Web 界面转化成能迅速响应的 Ajax 应用程序吧。

老技术,新技巧

在谈到 Ajax 时,实际上涉及到多种技术,要灵活地运用它必须深入了解这些不同的技术(本系列的头几篇文章将分别讨论这些技术)。好消息是您可能已经非常熟悉其中的大部分技术,更好的是这些技术都很容易学习,并不像完整的编程语言(如 Java 或 Ruby)那样困难。

Ajax 的定义

顺便说一下,Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的缩写。这个短语是 Adaptive Path 的 Jesse James Garrett 发明的(请参阅 参考资料),按照 Jesse 的解释,这不是 个首字母缩写词。

下面是 Ajax 应用程序所用到的基本技术:

  • HTML 用于建立 Web 表单并确定应用程序其他部分使用的字段。
  • JavaScript 代码是运行 Ajax 应用程序的核心代码,帮助改进与服务器应用程序的通信。
  • DHTML 或 Dynamic HTML,用于动态更新表单。我们将使用 divspan 和其他动态 HTML 元素来标记 HTML。
  • 文档对象模型 DOM 用于(通过 JavaScript 代码)处理 HTML 结构和(某些情况下)服务器返回的 XML。

我们来进一步分析这些技术的职责。以后的文章中我将深入讨论这些技术,目前只要熟悉这些组件和技术就可以了。对这些代码越熟悉,就越容易从对这些技术的零散了解转变到真正把握这些技术(同时也真正打开了 Web 应用程序开发的大门)。

XMLHttpRequest 对象

要了解的一个对象可能对您来说也是最陌生的,即 XMLHttpRequest。这是一个 JavaScript 对象,创建该对象很简单,如清单 1 所示。


清单 1. 创建新的 XMLHttpRequest 对象


<script language="javascript" type="text/javascript">
var xmlHttp = new XMLHttpRequest();
</script>


下一期文章中将进一步讨论这个对象,现在要知道这是处理所有服务器通信的对象。继续阅读之前,先停下来想一想:通过 XMLHttpRequest 对象与服务器进行对话的是 JavaScript 技术。这不是一般的应用程序流,这恰恰是 Ajax 的强大功能的来源。

在一般的 Web 应用程序中,用户填写表单字段并单击 Submit 按钮。然后整个表单发送到服务器,服务器将它转发给处理表单的脚本(通常是 PHP 或 Java,也可能是 CGI 进程或者类似的东西),脚本执行完成后再发送回全新的页面。该页面可能是带有已经填充某些数据的新表单的 HTML,也可能是确认页面,或者是具有根据原来表单中输入数据选择的某些选项的页面。当然,在服务器上的脚本或程序处理和返回新表单时用户必须等待。屏幕变成一片空白,等到服务器返回数据后再重新绘制。这就是交互性差的原因,用户得不到立即反馈,因此感觉不同于桌面应用程序。

Ajax 基本上就是把 JavaScript 技术和 XMLHttpRequest 对象放在 Web 表单和服务器之间。当用户填写表单时,数据发送给一些 JavaScript 代码而不是 直接发送给服务器。相反,JavaScript 代码捕获表单数据并向服务器发送请求。同时用户屏幕上的表单也不会闪烁、消失或延迟。换句话说,JavaScript 代码在幕后发送请求,用户甚至不知道请求的发出。更好的是,请求是异步发送的,就是说 JavaScript 代码(和用户)不用等待服务器的响应。因此用户可以继续输入数据、滚动屏幕和使用应用程序。

然后,服务器将数据返回 JavaScript 代码(仍然在 Web 表单中),后者决定如何处理这些数据。它可以迅速更新表单数据,让人感觉应用程序是立即完成的,表单没有提交或刷新而用户得到了新数据。JavaScript 代码甚至可以对收到的数据执行某种计算,再发送另一个请求,完全不需要用户干预!这就是 XMLHttpRequest 的强大之处。它可以根据需要自行与服务器进行交互,用户甚至可以完全不知道幕后发生的一切。结果就是类似于桌面应用程序的动态、快速响应、高交互性的体验,但是背后又拥有互联网的全部强大力量。

加入一些 JavaScript

得到 XMLHttpRequest 的句柄后,其他的 JavaScript 代码就非常简单了。事实上,我们将使用 JavaScript 代码完成非常基本的任务:

  • 获取表单数据:JavaScript 代码很容易从 HTML 表单中抽取数据并发送到服务器。
  • 修改表单上的数据:更新表单也很简单,从设置字段值到迅速替换图像。
  • 解析 HTML 和 XML:使用 JavaScript 代码操纵 DOM(请参阅 下一节),处理 HTML 表单服务器返回的 XML 数据的结构。

对于前两点,需要非常熟悉 getElementById() 方法,如 清单 2 所示。


清单 2. 用 JavaScript 代码捕获和设置字段值


// Get the value of the "phone" field and stuff it in a variable called phone
var phone = document.getElementById("phone").value;

// Set some values on a form using an array called response
document.getElementById("order").value = response[0];
document.getElementById("address").value = response[1];


这里没有特别需要注意的地方,真是好极了!您应该认识到这里并没有非常复杂的东西。只要掌握了 XMLHttpRequest,Ajax 应用程序的其他部分就是如 清单 2 所示的简单 JavaScript 代码了,混合有少量的 HTML。同时,还要用一点儿 DOM,我们就来看看吧。

以 DOM 结束

最后还有 DOM,即文档对象模型。可能对有些读者来说 DOM 有点儿令人生畏,HTML 设计者很少使用它,即使 JavaScript 程序员也不大用到它,除非要完成某项高端编程任务。大量使用 DOM 的 复杂的 Java 和 C/C++ 程序,这可能就是 DOM 被认为难以学习的原因。

幸运的是,在 JavaScript 技术中使用 DOM 很容易,也非常直观。现在,按照常规也许应该说明如何使用 DOM,或者至少要给出一些示例代码,但这样做也可能误导您。即使不理会 DOM,仍然能深入地探讨 Ajax,这也是我准备采用的方法。以后的文章将再次讨论 DOM,现在只要知道可能需要 DOM 就可以了。当需要在 JavaScript 代码和服务器之间传递 XML 和改变 HTML 表单的时候,我们再深入研究 DOM。没有它也能做一些有趣的工作,因此现在就把 DOM 放到一边吧。


获取 Request 对象

有了上面的基础知识后,我们来看看一些具体的例子。XMLHttpRequest 是 Ajax 应用程序的核心,而且对很多读者来说可能还比较陌生,我们就从这里开始吧。从 清单 1 可以看出,创建和使用这个对象非常简单,不是吗?等一等。

还记得几年前的那些讨厌的浏览器战争吗?没有一样东西在不同的浏览器上得到同样的结果。不管您是否相信,这些战争仍然在继续,虽然规模较小。但令人奇怪的是,XMLHttpRequest 成了这场战争的牺牲品之一。因此获得 XMLHttpRequest 对象可能需要采用不同的方法。下面我将详细地进行解释。

使用 Microsoft 浏览器

Microsoft 浏览器 Internet Explorer 使用 MSXML 解析器处理 XML(可以通过 参考资料 进一步了解 MSXML)。因此如果编写的 Ajax 应用程序要和 Internet Explorer 打交道,那么必须用一种特殊的方式创建对象。

但并不是这么简单。根据 Internet Explorer 中安装的 JavaScript 技术版本不同,MSXML 实际上有两种不同的版本,因此必须对这两种情况分别编写代码。请参阅 清单 3,其中的代码在 Microsoft 浏览器上创建了一个 XMLHttpRequest


清单 3. 在 Microsoft 浏览器上创建 XMLHttpRequest 对象


var xmlHttp = false;
try {
  xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
  try {
    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (e2) {
    xmlHttp = false;
  }
}


您对这些代码可能还不完全理解,但没有关系。当本系列文章结束的时候,您将对 JavaScript 编程、错误处理、条件编译等有更深的了解。现在只要牢牢记住其中的两行代码:

xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");

xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");

这两行代码基本上就是尝试使用一个版本的 MSXML 创建对象,如果失败则使用另一个版本创建该对象。不错吧?如果都不成功,则将 xmlHttp 变量设为 false,告诉您的代码出现了问题。如果出现这种情况,可能是因为安装了非 Microsoft 浏览器,需要使用不同的代码。

处理 Mozilla 和非 Microsoft 浏览器

如果选择的浏览器不是 Internet Explorer,或者为非 Microsoft 浏览器编写代码,就需要使用不同的代码。事实上就是 清单 1 所示的一行简单代码:

var xmlHttp = new XMLHttpRequest object;

这行简单得多的代码在 Mozilla、Firefox、Safari、Opera 以及基本上所有以任何形式或方式支持 Ajax 的非 Microsoft 浏览器中,创建了 XMLHttpRequest 对象。

结合起来

关键是要支持所有 浏览器。谁愿意编写一个只能用于 Internet Explorer 或者非 Microsoft 浏览器的应用程序呢?或者更糟,要编写一个应用程序两次?当然不!因此代码要同时支持 Internet Explorer 和非 Microsoft 浏览器。清单 4 显示了这样的代码。


清单 4. 以支持多种浏览器的方式创建 XMLHttpRequest 对象


/* Create a new XMLHttpRequest object to talk to the Web server */
var xmlHttp = false;
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
try {
  xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
  try {
    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (e2) {
    xmlHttp = false;
  }
}
@end @*/

if (!xmlHttp && typeof XMLHttpRequest != 'undefined') {
  xmlHttp = new XMLHttpRequest();
}


现在先不管那些注释掉的奇怪符号,如 @cc_on,这是特殊的 JavaScript 编译器命令,将在下一期针对 XMLHttpRequest 的文章中详细讨论。这段代码的核心分为三步:

  1. 建立一个变量 xmlHttp 来引用即将创建的 XMLHttpRequest 对象。
  2. 尝试在 Microsoft 浏览器中创建该对象:
    • 尝试使用 Msxml2.XMLHTTP 对象创建它。
    • 如果失败,再尝试 Microsoft.XMLHTTP 对象。
  3. 如果仍然没有建立 xmlHttp,则以非 Microsoft 的方式创建该对象。

最后,xmlHttp 应该引用一个有效的 XMLHttpRequest 对象,无论运行什么样的浏览器。

关于安全性的一点说明

安全性如何呢?现在浏览器允许用户提高他们的安全等级,关闭 JavaScript 技术,禁用浏览器中的任何选项。在这种情况下,代码无论如何都不会工作。此时必须适当地处理问题,这需要单独的一篇文章来讨论,要放到以后了(这个系列够长了吧?不用担心,读完之前也许您就掌握了)。现在要编写一段健壮但不够完美的代码,对于掌握 Ajax 来说就很好了。以后我们还将讨论更多的细节。


Ajax 世界中的请求/响应

现在我们介绍了 Ajax,对 XMLHttpRequest 对象以及如何创建它也有了基本的了解。如果阅读得很仔细,您可能已经知道与服务器上的 Web 应用程序打交道的是 JavaScript 技术,而不是直接提交给那个应用程序的 HTML 表单。

还缺少什么呢?到底如何使用 XMLHttpRequest。因为这段代码非常重要,您编写的每个 Ajax 应用程序都要以某种形式使用它,先看看 Ajax 的基本请求/响应模型是什么样吧。

发出请求

您已经有了一个崭新的 XMLHttpRequest 对象,现在让它干点活儿吧。首先需要一个 Web 页面能够调用的 JavaScript 方法(比如当用户输入文本或者从菜单中选择一项时)。接下来就是在所有 Ajax 应用程序中基本都雷同的流程:

  1. 从 Web 表单中获取需要的数据。
  2. 建立要连接的 URL。
  3. 打开到服务器的连接。
  4. 设置服务器在完成后要运行的函数。
  5. 发送请求。

清单 5 中的示例 Ajax 方法就是按照这个顺序组织的:


清单 5. 发出 Ajax 请求


function callServer() {
  // Get the city and state from the web form
  var city = document.getElementById("city").value;
  var state = document.getElementById("state").value;
  // Only go on if there are values for both fields
  if ((city == null) || (city == "")) return;
  if ((state == null) || (state == "")) return;

  // Build the URL to connect to
  var url = "/scripts/getZipCode.php?city=" + escape(city) + "&state=" + escape(state);

  // Open a connection to the server
  xmlHttp.open("GET", url, true);

  // Setup a function for the server to run when it's done
  xmlHttp.onreadystatechange = updatePage;

  // Send the request
  xmlHttp.send(null);
}


其中大部分代码意义都很明确。开始的代码使用基本 JavaScript 代码获取几个表单字段的值。然后设置一个 PHP 脚本作为链接的目标。要注意脚本 URL 的指定方式,city 和 state(来自表单)使用简单的 GET 参数附加在 URL 之后。

然后打开一个连接,这是您第一次看到使用 XMLHttpRequest。其中指定了连接方法(GET)和要连接的 URL。最后一个参数如果设为 true,那么将请求一个异步连接(这就是 Ajax 的由来)。如果使用 false,那么代码发出请求后将等待服务器返回的响应。如果设为 true,当服务器在后台处理请求的时候用户仍然可以使用表单(甚至调用其他 JavaScript 方法)。

xmlHttp(要记住,这是 XMLHttpRequest 对象实例)的 onreadystatechange 属性可以告诉服务器在运行完成 后(可能要用五分钟或者五个小时)做什么。因为代码没有等待服务器,必须让服务器知道怎么做以便您能作出响应。在这个示例中,如果服务器处理完了请求,一个特殊的名为 updatePage() 的方法将被触发。

最后,使用值 null 调用 send()。因为已经在请求 URL 中添加了要发送给服务器的数据(city 和 state),所以请求中不需要发送任何数据。这样就发出了请求,服务器按照您的要求工作。

如果没有发现任何新鲜的东西,您应该体会到这是多么简单明了!除了牢牢记住 Ajax 的异步特性外,这些内容都相当简单。应该感激 Ajax 使您能够专心编写漂亮的应用程序和界面,而不用担心复杂的 HTTP 请求/响应代码。

清单 5 中的代码说明了 Ajax 的易用性。数据是简单的文本,可以作为请求 URL 的一部分。用 GET 而不是更复杂的 POST 发送请求。没有 XML 和要添加的内容头部,请求体中没有要发送的数据;换句话说,这就是 Ajax 的乌托邦。

不用担心,随着本系列文章的展开,事情会变得越来越复杂。您将看到如何发送 POST 请求、如何设置请求头部和内容类型、如何在消息中编码 XML、如何增加请求的安全性,可以做的工作还有很多!暂时先不用管那些难点,掌握好基本的东西就行了,很快我们就会建立一整套的 Ajax 工具库。

处理响应

现在要面对服务器的响应了。现在只要知道两点:

  • 什么也不要做,直到 xmlHttp.readyState 属性的值等于 4。
  • 服务器将把响应填充到 xmlHttp.responseText 属性中。

其中的第一点,即就绪状态,将在下一篇文章中详细讨论,您将进一步了解 HTTP 请求的阶段,可能比您设想的还多。现在只要检查一个特定的值(4)就可以了(下一期文章中还有更多的值要介绍)。第二点,使用 xmlHttp.responseText 属性获得服务器的响应,这很简单。清单 6 中的示例方法可供服务器根据 清单 5 中发送的数据调用。


清单 6. 处理服务器响应


function updatePage() {
  if (xmlHttp.readyState == 4) {
    var response = xmlHttp.responseText;
    document.getElementById("zipCode").value = response;
  }
}


这些代码同样既不难也不复杂。它等待服务器调用,如果是就绪状态,则使用服务器返回的值(这里是用户输入的城市和州的 ZIP 编码)设置另一个表单字段的值。于是包含 ZIP 编码的 zipCode 字段突然出现了,而用户没有按任何按钮!这就是前面所说的桌面应用程序的感觉。快速响应、动态感受等等,这些都只因为有了小小的一段 Ajax 代码。

细心的读者可能注意到 zipCode 是一个普通的文本字段。一旦服务器返回 ZIP 编码,updatePage() 方法就用城市/州的 ZIP 编码设置那个字段的值,用户就可以改写该值。这样做有两个原因:保持例子简单,说明有时候可能希望 用户能够修改服务器返回的数据。要记住这两点,它们对于好的用户界面设计来说很重要。


连接 Web 表单

还有什么呢?实际上没有多少了。一个 JavaScript 方法捕捉用户输入表单的信息并将其发送到服务器,另一个 JavaScript 方法监听和处理响应,并在响应返回时设置字段的值。所有这些实际上都依赖于调用 第一个 JavaScript 方法,它启动了整个过程。最明显的办法是在 HTML 表单中增加一个按钮,但这是 2001 年的办法,您不这样认为吗?还是像 清单 7 这样利用 JavaScript 技术吧。


清单 7. 启动一个 Ajax 过程


<form>
 <p>City: <input type="text" name="city" id="city" size="25"
       onChange="callServer();" /></p>
 <p>State: <input type="text" name="state" id="state" size="25"
       onChange="callServer();" /></p>
 <p>Zip Code: <input type="text" name="zipCode" id="city" size="5" /></p>
</form>


如果感觉这像是一段相当普通的代码,那就对了,正是如此!当用户在 city 或 state 字段中输入新的值时,callServer() 方法就被触发,于是 Ajax 开始运行了。有点儿明白怎么回事了吧?好,就是如此!


结束语

现在您可能已经准备开始编写第一个 Ajax 应用程序了,至少也希望认真读一下 参考资料 中的那些文章了吧?但可以首先从这些应用程序如何工作的基本概念开始,对 XMLHttpRequest 对象有基本的了解。在下一期文章中,您将掌握这个对象,学会如何处理 JavaScript 和服务器的通信、如何使用 HTML 表单以及如何获得 DOM 句柄。

现在先花点儿时间考虑考虑 Ajax 应用程序有多么强大。设想一下,当单击按钮、输入一个字段、从组合框中选择一个选项或者用鼠标在屏幕上拖动时,Web 表单能够立刻作出响应会是什么情形。想一想异步 究竟意味着什么,想一想 JavaScript 代码运行而且不等待 服务器对它的请求作出响应。会遇到什么样的问题?会进入什么样的领域?考虑到这种新的方法,编程的时候应如何改变表单的设计?

如果在这些问题上花一点儿时间,与简单地剪切/粘贴某些代码到您根本不理解的应用程序中相比,收益会更多。在下一期文章中,我们将把这些概念付诸实践,详细介绍使应用程序按照这种方式工作所需要的代码。因此,现在先享受一下 Ajax 所带来的可能性吧。


参考资料

学习


讨论

  • 加入本文的论坛 。(您也可以通过点击文章顶部或者底部的论坛链接参加讨论。)

  • Ajax.NET Professional 是关于 Ajax 各个方面的很好的 blog。

  • 通过参与 developerWorks blog 加入 developerWorks 社区。

创建内嵌浮动框架。

成员表

下面的表格列出了 iframe 对象引出的成员。请单击左侧的标签来选择你想要查看的成员类型。

标签属性/属性   

显示:
标签属性/属性
行为
集合
事件
滤镜
方法
对象
样式
标签属性 属性 描述
ALIGN align 设置或获取表格排列。
ALLOWTRANSPARENCY allowTransparency 设置或获取对象是否可为透明。
APPLICATION APPLICATION 表明对象的内容是否为 HTML 应用程序(HTA),以便免除浏览器安全模式。
ATOMICSELECTION 指定元素及其内容是否可以一不可见单位统一选择。
BEGIN begin 设置或获取时间线在该元素上播放前的延迟时间。
BORDER border 设置或获取框架间的空间,包括 3D 边框。
canHaveChildren 获取表明对象是否可以包含子对象的值。
canHaveHTML 获取表明对象是否可以包含丰富的 HTML 标签的值。
CLASS className 设置或获取对象的类。
contentWindow 获取指定的 frame 或 iframe 的 window 对象。
DATAFLD dataFld 设置或获取由 dataSrc 属性指定的绑定到指定对象的给定数据源的字段。
DATASRC dataSrc 设置或获取用于数据绑定的数据源。
disabled 获取表明用户是否可与该对象交互的值。
END end 设置或获取表明元素结束时间的值,或者元素设置为重复的简单持续终止时间。
firstChild 获取对象的 childNodes 集合的第一个子对象的引用。
FRAMEBORDER frameBorder 设置或获取是否显示框架的边框。
hasMedia 获取一个表明元素是否为 HTML+TIME 媒体元素的 Boolean 值。
HEIGHT height 设置或获取对象的高度。
HIDEFOCUS hideFocus 设置或获取表明对象是否显式标明焦点的值。
HSPACE hspace 设置或获取对象的水平边距。
ID id 获取标识对象的字符串。
innerText 设置或获取位于对象起始和结束标签内的文本。
isContentEditable 获取表明用户是否可编辑对象内容的值。
isDisabled 获取表明用户是否可与该对象交互的值。
isMultiLine 获取表明对象的内容是包含一行还是多行的值。
isTextEdit 获取是否可使用该对象创建一个 TextRange 对象。
LANG lang 设置或获取要使用的语言。
LANGUAGE language 设置或获取当前脚本编写用的语言。
lastChild 获取该对象 childNodes 集合中最后一个子对象的引用。
longDesc 设置或获取对象长描述的统一资源标识符(URI)。
MARGINHEIGHT marginHeight 设置或获取显示框架中文本之前的上下边距高度。
MARGINWIDTH marginWidth 设置或获取显示框架中文本之前的左右边距宽度。
NAME name 设置或获取框架的名称。
nextSibling 获取对此对象的下一个兄弟对象的引用。
nodeName 获取特定结点类型的名称。
nodeType 获取所需结点的类型。
nodeValue 设置或获取结点的值。
offsetHeight 获取对象相对于版面或由父坐标 offsetParent 属性指定的父坐标的高度。
offsetLeft 获取对象相对于版面或由 offsetParent 属性指定的父坐标的计算左侧位置。
offsetParent 获取定义对象 offsetTop 和 offsetLeft 属性的容器对象的引用。
offsetTop 获取对象相对于版面或由 offsetTop 属性指定的父坐标的计算顶端位置。
offsetWidth 获取对象相对于版面或由父坐标 offsetParent 属性指定的父坐标的宽度。
onOffBehavior 获取表明指定的 Microsoft&reg; DirectAnimation&reg; 行为是否正在运行的对象。
outerHTML 设置或获取对象及其内容的 HTML 形式。
outerText 设置或获取对象的文本。
ownerDocument 设置或获取结点关联的 document 对象。
parentElement 获取对象层次中的父对象。
parentNode 获取文档层次中的父对象。
parentTextEdit 获取文档层次中可用于创建包含原始对象的 TextRange 的容器对象。
previousSibling 获取对此对象的上一个兄弟对象的引用。
readyState 获取对象的当前状态。
readyState 获取表明对象当前状态的值。
recordNumber 获取数据集中生成对象的原始记录。
scopeName 获取为该元素定义的命名空间。
SCROLLING scrolling 设置或获取框架是否可被滚动。
SECURITY SECURITY 获取表明 frame 或 iframe 的源文件是否应用了特定的安全限制的值。
sourceIndex 获取对象在源序中的依次位置,即对象出现在 document 的 all 集合中的顺序。
SRC src 设置或获取要由对象装入的 URL。
STYLE 为该设置元素设置内嵌样式。
SYNCMASTER syncMaster 设置或获取时间容器是否必须在此元素上同步回放。
SYSTEMBITRATE 获取系统中大约可用带宽的 bps。
SYSTEMCAPTION 表明是否要显示文本来代替演示的的音频部分。
SYSTEMLANGUAGE 表明是否在用户计算机上的选项设置中选中了给定语言。
SYSTEMOVERDUBORSUBTITLE 指定针对那些正在观看演示但对被播放的音频所使用的语言并不熟悉的用户来说是否要渲染配音或字幕。
TABINDEX tabIndex 设置或获取定义对象的 Tab 顺序的索引。
tagName 获取对象的标签名称。
tagUrn 设置或获取在命名空间声明中指定的统一资源名称(URN)。
TIMECONTAINER timeContainer 设置或获取与元素关联的时间线类型。
TITLE title 设置或获取对象的咨询信息(工具提示)。
uniqueID 获取为对象自动生成的唯一标识符。
UNSELECTABLE 指定该元素不可被选中。
VSPACE vspace 设置或获取对象的垂直边距。
WIDTH width 设置或获取对象的宽度。




标签属性/属性

行为

行为 描述
clientCaps 提供关于 Internet Explorer 支持的特性的信息,以及提供即用即装的方法。
download 下载文件并在下载完成后通知一个指定的回调函数。
homePage 包含关于用户主页的信息。
time 为 HTML 元素提供一个活动的时间线。
time2 为 HTML 元素或一组元素提供一个活动的时间线。

集合

集合 描述
all 返回对象所包含的元素集合的引用。
attributes 获取对象标签属性的集合。
behaviorUrns 返回标识附加到该元素行为的统一资源名称(URN)字符串的集合。
childNodes 获取作为指定对象直接后代的 HTML 元素和 TextNode 对象的集合。
children 获取作为对象直接后代的 DHTML 对象的集合。

事件

事件 描述
onactivate 当对象设置为活动元素时触发。
onafterupdate 当成功更新数据源对象中的关联对象后在数据绑定对象上触发。
onbeforedeactivate 在 activeElement 从当前对象变为父文档其它对象之前立即触发。
onbeforeupdate 当成功更新数据源对象中的关联对象前在数据绑定对象上触发。
onblur 在对象失去输入焦点时触发。
oncontrolselect 当用户将要对该对象制作一个控件选中区时触发。
ondeactivate 当 activeElement 从当前对象变为父文档其它对象时触发。
onerrorupdate 更新数据源对象中的关联数据出错时在数据绑定对象上触发。
onfocus 当对象获得焦点时触发。
onload 在浏览器完成对象的装载后立即触发。
onmove 当对象移动时触发。
onmoveend 当对象停止移动时触发。
onmovestart 当对象开始移动时触发。
onreadystatechange 当对象状态变更时触发。
onresizeend 当用户更改完控件选中区中对象的尺寸时触发。
onresizestart 当用户开始更改控件选中区中对象的尺寸时触发。
ontimeerror 当特定时间错误发生时无条件触发,通常由将属性设置为无效值导致。

滤镜

滤镜属性 描述
Alpha 调整对象内容的不透明度。
AlphaImageLoader 在对象的边界和对象背景到内容之间显示图像,可选剪裁或缩放图像大小。当装入便携网络图像(PNG)时,从 0 到 100% 的 透明度都是支持的。
Barn 以开门或关门的运动方式显示对象的新内容。
BasicImage 调整对象内容的颜色处理、图像旋转或不透明度。
BlendTrans 以渐隐原始内容的形式显示对象的新内容。
Blinds 以打开或关闭盲点的运动方式显示对象的新内容。
Blur 模糊对象的内容以便使其看起来失去焦点。
CheckerBoard 以揭开覆盖在原始内容上的棋盘的形式显示对象的新内容。
Chroma 将对象内容的指定颜色显示为透明。
Compositor 以新旧内容逻辑颜色组合的形式显示对象的新内容。每个版本的颜色和 alpha 值都会被计算用来决定输出图像的最终颜色。
DropShadow 创建对象内容的实体阴影,偏移量位于指定方向。这将使得内容看起来是浮动的因此会产生阴影。
Emboss 使用灰度值对对象以浮雕纹理显示。
Engrave 使用灰度值对对象以雕刻纹理显示。
Fade 以渐隐原始内容的形式显示对象的新内容。
FlipH 以沿水平方向翻转的形式显示对象内容。
FlipV 以沿垂直方向翻转的形式显示对象内容。
Glow 在对象边缘外侧添加光晕以便使其看起来像发光的样子。
Gradient 在对象的背景和内容之间显示一个渐变色彩的表面。
GradientWipe 以在原有内容上覆盖渐变带的形式显示对象的新内容。
Gray 以灰度显示对象内容。
ICMFilter 根据图像颜色管理(ICM)配置文件转换对象的彩色内容。这将允许指定内容的显示效果得以改善,或者在打印机或监视器等硬件设备上模拟显示。
Inset 以对角线方向显示对象的新内容。
Invert 反转对象内容的色调、饱和度和亮度。
Iris 以彩虹效果显示对象的新内容,这与照相机光圈打开相似。
Light 在对象的内容上创建灯光效果。
MaskFilter 将对象内容的透明像素显示为彩色遮罩,将非透明像素显示为透明。
Matrix 使用矩阵变换缩放、旋转或显现对象的内容。
MotionBlur 以运动模糊的效果显示对象内容。
Pixelate 将对象的内容显示为彩色方块,其颜色取决于该方块所替代区域的平均颜色值。此滤镜显示可用于切换。
RadialWipe 以辐射状擦除的形式显现对象的新内容。
RandomBars 以随机像素线爆炸的形式显现对象的新内容。
RandomDissolve 以随机像素爆炸的形式显现对象的新内容。
Redirect 目前还不支持。
RevealTrans 使用 24 种预先定义的切换效果显现对象的新内容。
Shadow 创建对象内容的实体阴影,偏移量位于指定方向。这将创建阴影效果。
Slide 以图像滑行的形式显现对象的新内容。
Spiral 以螺旋运动的形式显现对象的新内容。
Stretch 以拉伸覆盖原始内容的形式显现对象的新内容。有一个选项类似立方体从一个表面转到另外一个表面。
Strips 以条形覆盖的形式显现对象的原始内容,好像有一把锯将原始内容锯开。
Wave 在对象的内容上执行垂直方向的正弦波扭曲。
Wheel 以旋转运动的形式显现对象的新内容,好像轮子滚过原始内容。
Xray 更改对象内容的颜色深度将其以黑白显示。
Zigzag 在对象上将对象的新内容进行来回移动以便覆盖原始内容。

方法

方法 描述
addBehavior 给元素附加一个行为。
appendChild 给对象追加一个子元素。
applyElement 使得元素成为其它元素的子元素或父元素。
attachEvent 将指定函数绑定到事件,以便每当该事件在对象上触发时都调用该函数。
blur 使元素失去焦点并触发 onblur 事件。
clearAttributes 从对象中删除全部标签属性和值。
cloneNode 从文档层次中复制对对象的引用。
componentFromPoint 通过特定事件返回对象在指定坐标下的位置。
contains 检查对象中是否包含给定元素。
detachEvent 从事件中取消指定函数的绑定,这样当事件触发时函数就不会收到通知了。
dragDrop 初始化拖曳事件。
fireEvent 触发对象的指定事件。
focus 使得元素得到焦点并执行由 onfocus 事件指定的代码。
getAdjacentText 返回邻接文本字符串。
getAttribute 获取指定标签属性的值。
getAttributeNode 获取由 attribute.name 属性引用的 attribute 对象。
getElementsByTagName 获取基于指定元素名称的对象集合。
getExpression 获取给定属性的表达式。
hasChildNodes 返回表明对象是否有子对象的值。
insertAdjacentElement 在指定位置插入元素。
insertAdjacentHTML 在指定位置的元素中插入给定的 HTML 文本。
insertAdjacentText 在指定位置插入给定的文本。
insertBefore 在文档层次中插入元素作为父对象的子结点。
mergeAttributes 复制所有读/写标签属性到指定元素。
normalize 合并邻接 TextNode 对象以便生成一个常规的文档对象模型。
removeAttribute 删除对象的给定标签属性。
removeAttributeNode 从对象中删除删除 attribute 对象。
removeBehavior 分离元素的行为。
removeChild 从元素上删除子结点。
removeExpression 从指定属性中删除表达式。
removeNode 从文档层次中删除对象。
replaceAdjacentText 替换元素的邻接文本。
replaceChild 用新的子元素替换已有的子元素。
replaceNode 用其它元素替换对象。
scrollIntoView 将对象滚动到可见范围内,将其排列到窗口顶部或底部。
setActive 设置对象为当前对象而不将对象置为焦点。
setAttribute 设置指定标签属性的值。
setAttributeNode 设置 attribute 对象为对象的一部分。
setExpression 设置指定对象的表达式。
swapNode 交换文档层次中两个对象的位置。

对象

对象 描述
runtimeStyle 代表了居于全局样式表、内嵌样式和 HTML 标签属性指定的格式和样式之上的对象的格式和样式。
style 代表了给定元素所有可能的内嵌样式的当前设置。

样式

样式标签属性 样式属性 描述
ACCELERATOR accelerator 设置或获取表明对象是否包含快捷键的字符串。
background-attachment backgroundAttachment 设置或获取背景图像如何附加到文档内的对象中。
background-color backgroundColor 设置或获取对象内容后的颜色。
background-position-x backgroundPositionX 设置或获取 backgroundPosition 属性的 x 坐标。
background-position-y backgroundPositionY 设置或获取 backgroundPosition 属性的 y 坐标。
behavior behavior 设置或获取 DHTML 行为的位置。
border-bottom borderBottom 设置或获取对象下边框的属性。
border-bottom-color borderBottomColor 设置或获取对象下边框的颜色。
border-bottom-style borderBottomStyle 设置或获取对象下边框的样式。
border-bottom-width borderBottomWidth 设置或获取对象下边框的宽度。
border-color borderColor 设置或获取对象的边框颜色。
border-left borderLeft 设置或获取对象左边框的属性。
border-left-color borderLeftColor 设置或获取对象左边框的颜色。
border-left-style borderLeftStyle 设置或获取对象左边框的样式。
border-left-width borderLeftWidth 设置或获取对象左边框的宽度。
border-right borderRight 设置或获取对象右边框的属性。
border-right-color borderRightColor 设置或获取对象右边框的颜色。
border-right-style borderRightStyle 设置或获取对象右边框的样式。
border-right-width borderRightWidth 设置或获取对象右边框的宽度。
border-style borderStyle 设置或获取对象上下左右边框的样式。
border-top borderTop 设置或获取对象上边框的属性。
border-top-color borderTopColor 设置或获取对象上边框的颜色。
border-top-style borderTopStyle 设置或获取对象上边框的样式。
border-top-width borderTopWidth 设置或获取对象上边框的宽度。
border-width borderWidth 设置或获取对象上下左右边框的宽度。
bottom bottom 设置或获取对象相对于文档层次中下个定位对象的底部的位置。
clear clear 设置或获取对象是否允许在其左侧、右侧或两边放置浮动对象,以防下段文本显示在浮动对象上。
clip clip 设置或获取定位对象的哪个部分可见。
cursor cursor 设置或获取当鼠标指针指向对象时所使用的鼠标指针。
display display 设置或获取对象是否要渲染。
filter filter 设置或获取应用于对象的滤镜或滤镜集合。
height height 设置或获取对象的高度。
layout-grid layoutGrid 设置或获取指定文本字符版面的组合文档格线属性。
layout-grid-mode layoutGridMode 设置或获取文本布局网格是否使用二维。
left left 设置或获取对象相对于文档层次中下个定位对象的左边界的位置。
margin margin 设置或获取对象的上下左右边距。
margin-bottom marginBottom 设置或获取对象的下边距宽度。
margin-left marginLeft 设置或获取对象的左边距宽度。
margin-right marginRight 设置或获取对象的右边距宽度。
margin-top marginTop 设置或获取对象的上边距宽度。
overflow-x overflowX 设置或获取当内容超出对象宽度时如何管理对象内容。
overflow-y overflowY 设置或获取当内容超出对象高度时如何管理对象内容。
pixelBottom 设置或获取对象的下方位置。
pixelHeight 设置或获取对象的高度。
pixelLeft 设置或获取对象的左侧位置。
pixelRight 设置或获取对象的右侧位置。
pixelTop 设置或获取对象的上方位置。
pixelWidth 设置或获取对象的宽度。
posBottom 设置或获取以 bottom 标签属性指定的单位的对象下方位置。
posHeight 设置或获取以 height 标签属性指定的单位的对象高度。
position position 设置或获取对象所使用的定位方式。
posLeft 设置或获取以 left 标签属性指定的单位的对象左侧位置。
posRight 设置或获取以 right 标签属性指定的单位的对象右侧位置。
posTop 设置或获取以 top 标签属性指定的单位的对象上方位置。
posWidth 设置或获取以 width 标签属性指定的单位的对象宽度。
right right 设置或获取对象相对于文档层次中下个已定位的对象的右边界的位置。
float styleFloat 设置或获取文本要绕排到对象的哪一侧。
text-autospace textAutospace 设置或获取自动留空和文本的窄空间宽度调整。
top top 设置或获取对象相对于文档层次中下个定位对象的上边界的位置。
visibility visibility 设置或获取对象的内容是否显示。
z-index zIndex 设置或获取定位对象的堆叠次序。
zoom zoom 设置或获取对象的放大比例。

注释

IFRAME 元素也就是文档中的文档,或者好像浮动的框架(FRAME)。frames 集合提供了对 IFRAME 内容的访问。请使用 frames 集合读写 IFRAME 内包含的元素。例如,如果要访问 iframe 内 body 对象的 backgroundColor 样式,语法应为:

sColor = document.frames("sFrameName").document.body.style.backgroundColor;

通过 iframe 对象所在页面的对象模型,你可以访问 iframe 对象的属性,但不能访问其内容。例如,访问 iframe 对象的 border 样式的语法应为:

sBorderValue = document.all.oFrame.style.border;

注意 iframe 的属性必须使用前缀 document.all 访问,例如 document.all.iframeId.marginWidth。
Internet Explorer 5.5 支持浮动框架的内容透明。如果想要为浮动框架定义透明内容,则必须满足下列条件。

与 IFRAME 元素一起使用的 ALLOWTRANSPARENCY 标签属性必须设置为 true。
在 IFRAME 内容源文档,background-color 或 BODY 元素的 BGCOLOR 标签属性必须设置为 transparent。
参看使用透明的浮动框架可以获得关于透明浮动框架更多的信息。

此元素在 Microsoft&reg; Internet Explorer 4.0 的 HTML 和脚本中可用。

此元素是一个块元素。

此元素需要关闭标签。

示例

下面的例子使用了 IFRAME 元素创建了包含页面 sample.htm 的框架。

<IFRAME ID=IFrame1 FRAMEBORDER=0 SCROLLING=NO SRC="sample.htm"></IFRAME>

2006年01月05日

作者:jGuru

课程大纲

JavaMail API简介

JavaMail API是一种可选的、能用于读取、编写和发送电子消息的包(标准扩展)。您可使用这种包创建邮件用户代理(Mail User Agent MUA 类型的程序,它类似于EudoraPineMicrosoft Outlook这些邮件程序。其主要目的不是像发送邮件或其他邮件传输代理(Mail Transfer AgentMTA)类型的程序那样用于传输、发送和转发消息。换句话说,用户可以与MUA类型的程序交互,以阅读和撰写电子邮件。MUA依靠MTA处理实际的发送任务。

JavaMail API的设计是,为收发信息提供与协议无关的访问。方式是把该API划分成两个部分:

  • API的第一个部分是本课程的重点。基本上是如何发送和接收独立于提供程序/协议的消息。

  • 第二个部分则使用特定的协议语言,如:SMTPPOPIMAPNNTP。如果要让JavaMail API与服务器通信,就需要为之提供协议。由于Sun公司对特定协议提供程序有充分的介绍,用户可以免费获取,所以本课程没有介绍创建特定协议提供程序的内容。

复习相关协议

在学习JavaMail API的深层知识之前,让我们回过头来看一看在该API中使用的协议,本质上有4种人们常用的协议:

  • SMTP

  • POP

  • IMAP

  • MIME

您还需要了解NNTP及其他一些协议。理解这些协议的基本原理有助于您理解如何使用JavaMail API。而该API的设计要与协议无关,所以不能克服这些基础协议的限制。如果选用的协议不支持某种功能,那么JavaMail API也无法在其上添加这种功能。(正如您一会儿就会看到的,在操作POP协议时,常常会碰到这种问题)。

SMTP

简单邮件传输协议(SMTP)是用于传送电子邮件的机制。在JavaMail API环境中,您的基于JavaMail的程序将与您公司或Internet服务提供商(ISP)的SMTP服务器通信。该SMTP服务器将会把消息转发给用作接收消息的SMTP服务器,最后用户可通过POPIMAP协议获取该消息。由于支持身份验证,所以不需要SMTP服务器是一种开放的转发器,但需要确保SMTP服务器配置正确。JavaMail API中没有集成用于处理诸如配置服务器以转发消息或添加/删除电子邮件帐户这一类任务的功能。

POP

POP的含义是邮局协议,当前的版本为3,也称作POP3,该协议是在RFC 1939中定义的。POPInternet上的大多数人用来接收邮件的机制。它为每个用户的每个邮箱定义支持,这是它所做的全部工作,也是大多数问题的根源。在使用POP协议时,人们熟悉的很多功能,如查看收到了多少新邮件消息的功能,POP根本不支持。这些功能都内置到诸如EudoraMicrosoft Outlook之类的邮件程序中,能为您记住接收的上一封邮件,以及计算有多少新邮件这类信息。因此,使用JavaMail API时,如果想获取这类信息,将需要由自己进行计算。

IMAP

IMAP是用于接收消息的更加高级的协议,它是在RFC 2060中定义的。IMAP的含义是“Internet消息访问协议”,当前版本是第4版,也称作IMAP4。使用IMAP时,您的邮件服务器必须支持该协议。您不能只是简单地把程序转变为支持IMAP,而不是支持POP,就指望能支持IMAP中的一切。假定您的邮件服务器支持IMAP,那么基于JavaMail的程序就可利用在服务器上拥有多个文件夹的用户,并且这些文件夹可以被多个用户共享的功能。

由于IMAP协议具有更高级的功能,您也许会想IMAP应该被每一个人使用,但事实不是这样。因为IMAP会加重邮件服务器的负荷,它需要服务器接收新消息,发送消息给请求的用户,并在多个文件夹中为每个用户维护这些消息。而这要集中备份,因而长期下去用户的文件夹会变得越来越大,当磁盘空间用光了时,每个人都会遭受损失。而使用POP协议时,已保存消息可以解除服务器的重负。

MIME

MIME的含义是“多用途的网际邮件扩充协议”。它不是一种邮件传输协议,相反,它定义传输的内容:消息的格式、附件等。许多文档都定义了MIME协议,包含:RFC 822RFC 2045RFC 2046RFC 2047。作为JavaMail API的用户,一般不需要担心这些格式。但是,这些格式确实存在,并为您的程序所用。

NNP和其他协议

由于JavaMail API分开了提供程序和其他部分,所以您可以轻松地为附加协议添加支持。Sun公司提供第3方提供程序清单,这些提供程序要利用 Sun公司不支持的少见的协议。在这份清单中,您将会看到对NNTP(网络新闻传输协议)[新闻组]S/MIME(安全多用途的网际邮件扩充协议)及其他协议的提供支持的第3方提供程序。

安装

目前有两种版本的JavaMail API最常用:1.21.1.3。本课程中的所有例子都适用于这两种版本。其中JavaMail API 1.2是最新的,而JavaMail API 1.1.3中包含了Java 2企业版(J2EE)平台1.2.1版,所以它仍然很常用。使用JavaMail API的版本会对您的下载和安装产生一些影响。这两种版本的JavaMail API都能与JDK 1.1.6Java 2标准版(J2SE)平台1.2.x1.3.x协同工作。

注意:在安装了Sun公司的JavaMail工具后,会在演示目录下看到许多示例程序。

安装JavaMail 1.2

要使用JavaMail 1.2 API,可以下载JavaMail 1.2工具,然后解压缩javamail-1_2.zip文件,并把mail.jar文件添加到典型安装路径下。JavaMail 1.2工具带有SMTPIMAP4POP3提供程序以及核心类。

安装完JavaMail 1.2后,再安装JavaBeans Activation Framework

安装JavaMail 1.1.3

要使用JavaMail 1.1.3 API,可以下载JavaMail 1.1.3工具,然后解压缩javamail1_1_3.zip文件,并把mail.jar文件添加到典型安装路径下。JavaMail 1.1.3工具带有SMTPIMAP4提供程序以及核心类。

如果您想用JavaMail 1.1.3访问POP服务器,需要下载并安装POP3提供程序。Sun公司拥有一个独立于 JavaMail 工具的提供程序。在下载并解压缩pop31_1_1.zip文件后,也还需要把pop3.jar添加到典型安装路径下。

安装完JavaMail 1.1.3后,再安装JavaBeans Activation Framework

安装JavaBeans Activation Framework

JavaMail API的所有版本都需要JavaBeans Activation FrameworkJavaBeans激活框架),这种框架提供了对输入任意数据块的支持,并能相应地对其进行处理。看上去效果好像不太好,但该框架是在当今的许多浏览器和邮件工具中可以找到的基本MIME类型支持。下载该框架后,解压缩jaf1_0_1.zip文件,并将activation.jar文件添加到典型安装路径下。

对于JavaMail 1.2用户,现在应该把mail.jaractivation.jar文件添加到典型安装路径下。

对于JavaMail 1.1.3用户,现在应该把mail.jarpop3.jaractivation.jar添加到典型安装路径下。如果您不打算使用POP3,就不需要把pop3.jar文件添加到典型安装路径下。

如果您不想更改安装路径环境变量,可以把JAR文件复制到Java运行时环境(JRE)目录下的lib/ext目录下。例如,对于J2SE 1.3版本,Windows平台上的默认目录应该是C:\jdk1.3\jre\lib\ext

使用Java 2企业版

如果您使用的是J2EE,则在使用基本JavaMail API时,不需要做什么特殊的工作;JavaMail API带有J2EE类。只要确保j2ee.jar文件位于典型安装路径下,并完成了所有的设置工作。

对于J2EE 1.2.1POP3提供程序是单独提供的,因此需要下载该提供程序,并按安装JavaMail 1.1.3的步骤,在J2EE 1.2.1中包含POP3提供程序。J2EE 1.3的用户会获得J2EEPOP3提供程序,因而不需要对POP3提供程序执行独立安装。使用这两种版本的J2EE用户,都不需要安装JavaBeans Activation Framework

练习

设置您的 JavaMail 环境

复习核心类

在开始深入研究JavaMail类之前,首先让用户浏览一下构成API的核心类:会话、消息、地址、验证程序、传输,存储和文件夹。所有这些类都可以在JavaMail APIjavax.mail的顶层包中找到,尽管您将频繁地发现您自己使用的子类是在javax.mail.internet包中找到的。

Session

Session类定义了一个基本的邮件会话。通过该会话可让别的工作顺利执行。Session对象利用java.util.Properties对象获取诸如邮件服务器、用户名、密码等信息,以及其他可在整个应用程序中共享的信息。

Session类的构造器是私有的。您可以获得一个可被getDefaultInstance()方法共享的单一的默认会话:

Properties props = new Properties();

// fill props with any information

Session session = Session.getDefaultInstance(props, null);

或者,您可以用getInstance()方法创建一个独特的会话:

Properties props = new Properties();

// fill props with any information

Session session = Session.getInstance(props, null);

这两种情形下的null参数都是一种Authenticator对象,它不是在此时使用的。详细信息请参阅其后的“Autherticator”一节。

在大多数情况下,使用共享会话就足够了,即使为多个用户邮箱处理邮件会话也是如此。您可以在通信过程的后面一步添加上用户名和密码的组合,并保持所有的一切是独立的。

Message

一旦创建了自己的Session对象,就是该去创建要发送的消息的时候了。这时就要用到消息类型。作为一个抽象类,您必须操作一个子类,在大多数情况下,该子类是javax.mail.internet.MimeMessage。一个MimeMessage是一种理解MIME类型和报头(在不同的RFC文档中均有定义)的消息。消息的报头被严格限制成只能使用US-ASCII字符,尽管非ASCII字符可以被编码到某些报头字段中。

可以通过将Session对象传递给MimeMessage构造器的方法来创建消息:

MimeMessage message = new MimeMessage(session);

注意:还有其他的构造器,像用于创建消息的源于RFC822格式化的输入流的构造器。

一旦创建了消息,就可以设置其各个部分,如Message(消息)实现Part(部分)接口(以MimeMessage实现MimePart)。设置内容的基本机制是setContent()方法,它带有表示内容和MIME类型的参数:

message.setContent("Hello", "text/plain");

但是,如果正在使用 MimeMessage,并且您的消息是纯文本,那么您就可以使用setText()方法。该方法只需要一个表示实际内容的参数,默认的MIME类型为纯文本:

message.setText("Hello");

对于纯文本消息,setText()方法更常常被用来设置内容。要发送其他类型的消息,如HTML消息,就要使用setContent方法()。现在用的更多的是HTML消息。

要设置主题,可以使用setSubject()方法:

message.setSubject("First");

Address

一旦创建了会话和消息,并为消息填充了内容,就需要用Address类为您的信件标上地址了。同Message类一样,Address类也是一种抽象类。您可以使用javax.mail.internet.InternetAddress类。

要创建只带有电子邮件地址的地址,可以把电子邮件地址传递给Address类的构造器:

Address address = new InternetAddress("president@whitehouse.gov");

如果想让一个名字出现在电子邮件地址后,也可以将其传递给构造器:

Address address = new InternetAddress("president@whitehouse.gov", "George Bush");

您要为消息的from(发送者)字段和to(接收者)字段创建地址对象。除非您的邮件服务器阻止这样做,否则要在发送的消息中注明该消息的发送者。

一旦创建好了地址,有两种方法可让您将地址与消息连接起来。为了鉴别发送者,您可以使用setFrom()setReplyTo()方法。

message.setFrom(address)

如果您的消息需要显示多个地址来源,则可以使用addFrom()方法:

Address address[] = …;

message.addFrom(address);

为了鉴别消息接收者,您可以使用addRecipient()方法。该方法除了需要一个地址参数外,还需要一个Message.RecipientType属性(消息的接收类型)。

message.addRecipient(type, address)

地址的3种预定义类型如下:

  • Message.RecipientType.TO

  • Message.RecipientType.CC

  • Message.RecipientType.BCC

因此,如果一条消息将发送给副总统,同时还将发送该消息的副本给第一夫人,则采用下面的代码:

Address toAddress = new InternetAddress("vice.president@whitehouse.gov");

Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");

message.addRecipient(Message.RecipientType.TO, toAddress);

message.addRecipient(Message.RecipientType.CC, ccAddress);

JavaMail API没有提供检查电子邮件地址有效性的机制。您可以自己编写支持扫描有效字符(在RFC 822文档中所定义的)的程序或检验MX(邮件交换)记录,这些都超越了JavaMail API的范围。

Authenticator

java.net类一样,JavaMail API可以利用Authenticator(验证程序)类通过用户名和密码来访问受保护的资源。对于JavaMail API来说,这种受保护的资源是指邮件服务器。JavaMailAuthenticator类可以在javax.mail包中找到,并有别于同名的java.net类。当JavaMail APIJava 1.1下工作时,JavaMailjava.net不会共享同一个Authenticator类名称,这是因为Java 1.1中不含有java.net

要使用Authenticator类,您可以使用该抽象类的子类,并通过getPasswordAuthentication()方法返回一个PasswordAuthentication实例。在创建时,您必须用会话记录Authentication类。其后,当需要进行身份验证时,会通知您的Authenticator。会弹出一个窗口,或从一个配置文件(尽管不加密就不安全)中读取用户名和密码,并把它们作为一个PasswordAuthentication对象返回给调用程序。

Properties props = new Properties();

// fill props with any information

Authenticator auth = new MyAuthenticator();

Session session = Session.getDefaultInstance(props, auth);

Transport

发送消息的最后一步操作是使用Transport类。该类使用特定于协议(通常是SMTP)的语言来发送消息。它是一个抽象类,其操作与Session类有些相似。您可以通过只调用静态的send()方法来使用该类的默认版本:

Transport.send(message);

或者,您可以从用于您的协议的会话中获取一个特定的实例,然后传递用户名和密码(不必要时可以为空)并发送消息,最后关闭连接:

message.saveChanges(); // implicit with send()

Transport transport = session.getTransport("smtp");

transport.connect(host, username, password);

transport.sendMessage(message, message.getAllRecipients());

transport.close();

当您需要发送多个消息时,建议采用后一种方法,因为它将保持消息间活动服务器的连接。而基本的send()机制会为每一个方法调用都建立一条独立的连接。

注意:要查看经过邮件服务器邮件命令,可以用session.setDebug(true)方法设置调试标志。

StoreFolder

使用Session类来获取消息,开始时与发送消息很相似。但是,在获取会话后,很有可能使用用户名和密码或Authenticator类来连接Store类。与Transport类一样,您要告诉Store类将使用什么协议:

// Store store = session.getStore("imap");

Store store = session.getStore("pop3");

store.connect(host, username, password);

在连接Store类后,就可以获取一个Folder类,在读取其中的消息前必须先打开该类。

Folder folder = store.getFolder("INBOX");

folder.open(Folder.READ_ONLY);

Message message[] = folder.getMessages();

对于POP3协议,惟一可用的文件夹是INBOX。如果使用的是IMAP协议,则可以使用其他的文件夹。

注意:Sun公司的提供程序本来想提供方便。而Message message[]=folder.getMessages();这条语句却是一种从服务器逐条读取消息的缓慢操作,所以仅当您确实需要获取消息部分(该内容是所检索消息的内容)时可以使用这条语句。

一旦读取消息,就可以使用getContent()方法获取其内容,或使用writeTo()方法将其内容写到一个流中。getContent()方法只获取消息内容,而writeTo()方法则还会输出报头。

System.out.println(((MimeMessage)message).getContent());

一旦您阅读完邮件,就可以关闭对文件夹和存储的连接。

folder.close(aBoolean);

store.close();

传递给文件夹的close()方法的布尔变量指定了是否通过清除已删除的消息来更新文件夹。

继续前进

实际上,理解使用这7个类的方式,是使用JavaMail API处理几乎所有事情所需要的全部内容。用这7个类以外的方式构建的JavaMail API,其大多数功能都是以几乎完全相同或特定的方式来执行任务的,就好像内容是附件。特定的任务,如:搜索、隔离等将在后面进行介绍。

使用JavaMail API

您已经看到了如何操作JavaMail API的核心部分。在下面几节中,您将学习如何连接几个部分以执行特定的任务。

发送消息

发送电子邮件消息涉及到获取会话、创建和填充消息并发送消息这些操作。您可以在获取Session时,通过为要传递的Properties对象设置mail.smtp.host属性来指定您的SMTP服务器。

String host = …;

String from = …;

String to = …;

// Get system properties

Properties props = System.getProperties();

// Setup mail server

props.put("mail.smtp.host", host);

// Get session

Session session = Session.getDefaultInstance(props, null);

// Define message

MimeMessage message = new MimeMessage(session);

message.setFrom(new InternetAddress(from));

message.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

message.setSubject("Hello JavaMail");

message.setText("Welcome to JavaMail");

// Send message

Transport.send(message);

您应该在try-catch块中编写代码,以在创建消息并发送它时可以抛出一个异常。

练习

发送您的第一个消息

获取消息

对于阅读邮件来说,首先您要获取一个会话,然后获取并连接到一个相应的用于您的收件箱的存储上,接着打开相应的文件夹,再获取消息。同时,不要忘记了操作完成后关闭连接。

String host = …;

String username = …;

String password = …;

// Create empty properties

Properties props = new Properties();

// Get session

Session session = Session.getDefaultInstance(props, null);

// Get the store

Store store = session.getStore("pop3");

store.connect(host, username, password);

// Get folder

Folder folder = store.getFolder("INBOX");

folder.open(Folder.READ_ONLY);

// Get directory

Message message[] = folder.getMessages();

for (int i=0, n=message.length; i<n; i++) {

System.out.println(i + ": " + message[i].getFrom()[0]

+ "\t" + message[i].getSubject());

}

// Close connection

folder.close(false);

store.close();

每一条消息执行何种操作取决于自己决定。上面的代码块只是显示了消息的发送者和主题。从技术上讲,发送者地址列表可以为空,此时getFrom()[0]调用会抛出一个异常。

为了显示整条消息,您可以提示用户在看完消息的发送者和主题字段后,如果想看到消息的内容,可以再调用消息的writeTo()方法。

BufferedReader reader = new BufferedReader (

new InputStreamReader(System.in));

// Get directory

Message message[] = folder.getMessages();

for (int i=0, n=message.length; i<n; i++) {

System.out.println(i + ": " + message[i].getFrom()[0]

+ "\t" + message[i].getSubject());

System.out.println("Do you want to read message? " +

"[YES to read/QUIT to end]");

String line = reader.readLine();

if ("YES".equals(line)) {

message[i].writeTo(System.out);

} else if ("QUIT".equals(line)) {

break;

}

}

练习

检查邮件

删除消息和标志

删除消息涉及到操作与消息关联的标志。对不同的状态有不同的标志,有些标志是系统定义的,有些则是由用户定义的。预定义的标志都是在内部类Flags.Flag中定义的,如下所示:

  • Flags.Flag.ANSWERED

  • Flags.Flag.DELETED

  • Flags.Flag.DRAFT

  • Flags.Flag.FLAGGED

  • Flags.Flag.RECENT

  • Flags.Flag.SEEN

  • Flags.Flag.USER

仅仅因为标志存在,并不表示标志为所有的邮件服务器/提供程序所支持。例如,除了删除消息外,POP协议对它们都不支持。检查新邮件不是POP的任务,但它已内置到邮件客户程序中。要搞清楚什么标志受到支持,可以使用getPermanentFlags()方法来询问文件夹。

要删除消息,需要为消息设置DELETE标志:

message.setFlag(Flags.Flag.DELETED, true);

第一次以READ_WRITE(读-写)模式打开文件夹:

folder.open(Folder.READ_WRITE);

然后,处理完了所有的消息,请关闭文件夹,并传递true值以擦去删除的消息。

folder.close(true);

用户可使用Folder类的expunge()方法来删除消息。但是,该方法对Sun公司的POP3提供程序不起作用。其他提供程序或许能也或许不能实现其功能。它更有可能适用于IMAP提供程序。由于POP只支持对收件箱的简单访问,使用Sun公司的提供程序时,您将不得不关闭文件夹以删除消息。

要移去标志,只需传递一个false值给setFlag()方法。要看看是否设置了某个标志,可以使用isSet()进行检查。

自我验证

先前学到的是使用Authenticator类,以在需要时提示输入用户名和密码,而不是以字符串的形式传入它们。这里,您将真正看到如何更加充分地使用验证。

不需使用主机、用户名和密码连接到Store,您可以配置Properties带有主机,并告诉Session关于您自定义的Authenticator实例,如下所示:

// Setup properties

Properties props = System.getProperties();

props.put("mail.pop3.host", host);

// Setup authentication, get session

Authenticator auth = new PopupAuthenticator();

Session session = Session.getDefaultInstance(props, auth);

// Get the store

Store store = session.getStore("pop3");

store.connect();

然后您可以使用Authenticator类的子类,并通过getPasswordAuthentication()方法返回一个PasswordAuthentication对象。下面是这种实现的一个例子,其中一个字段同时适用于两部分内容。它不是一个Project Swing指南,只是在一个字段中输入了两部分内容,它们是用逗号隔开的。

import javax.mail.*;

import javax.swing.*;

import java.util.*;

public class PopupAuthenticator extends Authenticator {

public PasswordAuthentication getPasswordAuthentication() {

String username, password;

String result = JOptionPane.showInputDialog(

"Enter ‘username,password’");

StringTokenizer st = new StringTokenizer(result, ",");

username = st.nextToken();

password = st.nextToken();

return new PasswordAuthentication(username, password);

}

}

由于PopupAuthenticator依赖于Swing,因而将会启动用于AWT的事件处理线程。这在本质上要求您在代码中添加一个对System.exit()的调用,以终止程序的执行。

回复消息

Message类包含一个reply()方法,以用正确的接收者和主题(添加“Re::”,如果没有的话)配置一条新消息。该方法不会为消息添加任何内容,只是为新的接收者复制发送者或回复到的报头。该方法使用一个布尔型参数,提示是否只回复给发送者(false)或回复给所有人(true)

MimeMessage reply = (MimeMessage)message.reply(false);

reply.setFrom(new InternetAddress("president@whitehouse.gov"));

reply.setText("Thanks");

Transport.send(reply);

在发送消息时要配置回复到地址,可使用setReplyTo()方法。

练习

回复邮件

转发消息

转发消息涉及的内容要稍微多一点,没有一个专门用于转发消息的方法,您可以通过处理组成消息的各个部分来创建要转发的消息。

一条邮件消息可由多个部分组成,每一部分是一个BodyPart(报文部分),或更特殊一点,在操作MIME消息时则是MimeBodyPart。不同的报文部分组合到一个称为Multipart的容器中,或者又更特殊一点,是一个MimeMultipart容器。要转发消息,您要创建一个用于消息文本的部分,和用于要转发的消息的第二个部分,并将这两个部分组合成一个multipart(多个部分)。然后您可以把这个multipart添加到一个合适的注明地址的消息中并发送它。

这就是转发消息的本质。要把一条消息的内容复制给另一条消息,只需通过它的DataHandler类复制即可,它是出自于JavaBeans Activation Framework的一个类。

// Create the message to forward

Message forward = new MimeMessage(session);

// Fill in header

forward.setSubject("Fwd: " + message.getSubject());

forward.setFrom(new InternetAddress(from));

forward.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

// Create your new message part

BodyPart messageBodyPart = new MimeBodyPart();

messageBodyPart.setText(

"Here you go with the original message:\n\n");

// Create a multi-part to combine the parts

Multipart multipart = new MimeMultipart();

multipart.addBodyPart(messageBodyPart);

// Create and fill part for the forwarded content

messageBodyPart = new MimeBodyPart();

messageBodyPart.setDataHandler(message.getDataHandler());

// Add part to multi part

multipart.addBodyPart(messageBodyPart);

// Associate multi-part with message

forward.setContent(multipart);

// Send message

Transport.send(forward);

操作附件

附件是与邮件消息关联的资源,通常保存在消息之外,如:一个文本文件,电子表格或图片。对于像EudoraPine之类的常用邮件程序,您可以通过JavaMail API把资源附加到邮件消息上,并在您接收消息时获取附件。

发送附件

发送附件与转发消息非常相似,您要创建组成完整消息的各个部分。在创建好第一个部分即消息文本之后,您添加的用DataHandler类处理的其他部分就是您的附件,而不是转发消息中的共享处理程序。当您从一个文件读取附件时,附件的数据资源是FileDataSource;从URL读取时,则是URLDataSource。一旦您有了自己的DataSource,在将其通过setDataHandler()方法最终附加到BodyPart上之前,只需将其传递给DataHandler类的构造器即可。假定您想保留附件的原始文件名,要做的最后一件事就是用BodyPart类的setFileName()方法设置与附件关联的文件名。所有这些操作如下所示:

// Define message

Message message = new MimeMessage(session);

message.setFrom(new InternetAddress(from));

message.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

message.setSubject("Hello JavaMail Attachment");

// Create the message part

BodyPart messageBodyPart = new MimeBodyPart();

// Fill the message

messageBodyPart.setText("Pardon Ideas");

Multipart multipart = new MimeMultipart();

multipart.addBodyPart(messageBodyPart);

// Part two is attachment

messageBodyPart = new MimeBodyPart();

DataSource source = new FileDataSource(filename);

messageBodyPart.setDataHandler(new DataHandler(source));

messageBodyPart.setFileName(filename);

multipart.addBodyPart(messageBodyPart);

// Put parts in message

message.setContent(multipart);

// Send the message

Transport.send(message);

在消息中包含附件时,如果您的程序是一个servlet,您的用户就必须上传附件,并告诉您要把消息发送到什么位置。上传的每一个文件都可以用一个表单来处理,该表单是以multipart/表单数据(form-data)来编码的。

<FORM ENCTYPE="multipart/form-data"

method=post action="/myservlet">

<INPUT TYPE="file" NAME="thefile">

<INPUT TYPE="submit" VALUE="Upload">

</FORM>

注意:消息的大小要受到您的SMTP服务器的限制,而不是由JavaMail API限制的。如果出现了问题,可以通过设置msmx参数来考虑增加Java堆区的空间尺寸。

练习

发送附件

获取附件

从消息中取出附件比发送附件涉及的操作要稍微多一点,而MIME没有简单的附件概念。当消息带有附件时,消息的内容就是一个Multipart对象。然后需要处理各个部分,以获取主要内容和附件。通过part.getDisposition()方法标记上Part.ATTACHMENT配置的部分显然就是附件。同时,附件也可以不带有配置(和非文本MIME类型)或Part.INLINE配置。当配置是Part.ATTACHMENTPart.INLINE时,您可以脱离该消息部分的内容将其保存起来。只需通过getFileName()方法获取原始文件名,并通过getInputStream()方法获取输入流即可。

Multipart mp = (Multipart)message.getContent();

for (int i=0, n=multipart.getCount(); i<n; i++) {

Part part = multipart.getBodyPart(i));

String disposition = part.getDisposition();

if ((disposition != null) &&

((disposition.equals(Part.ATTACHMENT) ||

(disposition.equals(Part.INLINE))) {

saveFile(part.getFileName(), part.getInputStream());

}

}

saveFile()方法只用于根据文件名创建一个文件,从输入流中读取字节,并将它们写入一个文件中去。如果文件已存在,将在文件名后添加一个编号,直到找到一个不存在的文件为止。

// from saveFile()

File file = new File(filename);

for (int i=0; file.exists(); i++) {

file = new File(filename+i);

}

上面的代码介绍了消息的各个部分被标上相应的标志的一个最简单的例子。要想包含所有的情况,还要对disposition值为null及消息部分为MIME类型的情况作相应处理。

if (disposition == null) {

// Check if plain

MimeBodyPart mbp = (MimeBodyPart)part;

if (mbp.isMimeType("text/plain")) {

// Handle plain

} else {

// Special non-attachment cases here of image/gif, text/html, …

}

}



处理HTML消息

发送基于HTML的消息比发送纯文本消息要稍微复杂一点,尽管它不需要做大量的工作。它全部取决于您特定的需求。

发送HTML消息

如果您所要做的全部工作是发送一个等价的HTML文件作为消息,并让邮件阅读者忧心于取出任何嵌入的图片或相关片段,那么就可以使用消息的setContent()方法,以字符串形式传递消息内容,并把内容类型设置为text/html

String htmlText = "<H1>Hello</H1>" +

"<img src=\"http://www.jguru.com/images/logo.gif\">";

message.setContent(htmlText, "text/html"));

在接收端,如果您用JavaMail API获取消息,在该API中没有内置任何用于以HTML格式显示消息的功能。JavaMail API只以字节流的形式来查看消息。要以HTML格式显示消息,您必须使用Swing JeditorPane或某些第3HTML阅读器组件。

if (message.getContentType().equals("text/html")) {

String content = (String)message.getContent();

JFrame frame = new JFrame();

JEditorPane text = new JEditorPane("text/html", content);

text.setEditable(false);

JScrollPane pane = new JScrollPane(text);

frame.getContentPane().add(pane);

frame.setSize(300, 300);

frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

frame.show();

}

在消息中包含图片

另一方面,如果您的HTML消息中嵌入了作为消息一部分的图片,并且您想保持消息内容的完整,就必须把图片看作附件,并用特殊的通信标识符URL引用该图片,该通信标识符引用的是图片附件的内容ID报文。

嵌入图片的处理与附加一个文件到消息上非常相似,惟一的不同之处在于:您必须区分MimeMultipart中,哪些部分是在构造器(或通过setSubType()方法)通过设置其子类型而使之相关的,以及将图片的内容ID报头设置成任意字符串,它将在img标记中用作图片的源路径。下面显示了一个完整的示例:

String file = …;

// Create the message

Message message = new MimeMessage(session);

// Fill its headers

message.setSubject("Embedded Image");

message.setFrom(new InternetAddress(from));

message.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

// Create your new message part

BodyPart messageBodyPart = new MimeBodyPart();

String htmlText = "<H1>Hello</H1>" +

"<img src=\"cid:memememe\">";

messageBodyPart.setContent(htmlText, "text/html");

// Create a related multi-part to combine the parts

MimeMultipart multipart = new MimeMultipart("related");

multipart.addBodyPart(messageBodyPart);

// Create part for the image

messageBodyPart = new MimeBodyPart();

// Fetch the image and associate to part

DataSource fds = new FileDataSource(file);

messageBodyPart.setDataHandler(new DataHandler(fds));

messageBodyPart.setHeader("Content-ID","memememe");

// Add part to multi-part

multipart.addBodyPart(messageBodyPart);

// Associate multi-part with message

message.setContent(multipart);

练习

发送带有图片的 HTML 消息

SearchTerm搜索

JavaMail API包含一种可用于创建SearchTerm(搜索条件)的筛选机制,它可以在javax.mail.search包中找到。一旦创建了SearchTerm,您就可以询问某个文件夹匹配的消息,并检索出消息对象数组:

SearchTerm st = …;

Message[] msgs = folder.search(st);

22种不同的类可用于帮助创建搜索条件。

  • AND条件(AndTerm)

  • OR条件(OrTerm)

  • NOT条件(NotTerm)

  • SENT DATE条件(SentDateTerm)

  • CONTENT条件(BodyTerm)

  • HEADER条件(FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)

本质上,您可以为匹配的消息创建一个逻辑表达式,然后进行搜索。例如,下面显示了一条消息的条件搜索示例,该消息带有(部分带有)一个ADV主题字符串,其发送者字段为friend@public.com。您可能考虑定期运行该查询,并自动删除任何返回的消息。

SearchTerm st =

new OrTerm(

new SubjectTerm("ADV:"),

new FromStringTerm("friend@public.com"));

Message[] msgs = folder.search(st);

资源

您可以使用JavaMail API执行比这里介绍的多得多的任务。这里看到的课程和练习可以由下面列出的资源提供:

  • JavaMail API之家

  • JavaBeans Activation Framework之家

  • 令人感兴趣的Java邮件列表

  • Sun公司的JavaMail常见问题解答

  • jGuruJavaMail常见问题解答

  • 3方产品列表

版权1996-2001 jGuru.com. 版权所有。

用于调用Javaean组件中的操作和执行请求分派的标准JSP标签简化了JSP页面的开发和维护。JSP技术还提供了在自定义标签中封装其他动态功能的机制,这种自定标签是JSP语言的扩展。自定义标签通常是以标签库的形式出现的,它定义了一组相关的自定义标签,并包含实现这些标签的对象。

可以由自定义标签执行的任务包括对隐式对象的操作、处理表单、访问数据库和其他企业级服务,如电子邮件和目录、以及执行流程控制。JSP标签库是由精通Java编程语言和对访问数据库和其他服务非常熟悉的开发人员创建的,使用这些标签,Web应用程序开发人员就可以把注意力放到内容的呈现上,而不用费心考虑如何访问企业级服务。就像鼓励将库开发人员和库使用人员的工作分开一样,自定义标签通过封装反复执行的任务使它们可以在多个应用程序中重复使用,从而提高了生产率。

JSP技术社区给予标签库非常高的重视。有关标签库的信息和一些免费的库的地址,参见

http://java.sun.com/products/jsp/taglibraries.html

什么是自定义标签?

自定义标签是用户定义的JSP语言元素。当包含自定义标签的JSP页面转换为servlet时,这个标签就转换为一个名为tag handler的对象上的操作。之后当JSP页面的servlet执行时,Web容器就调用这些操作。

自定义标签有丰富的功能。它们可以

·  通过从调用页面传递的属性进行定制。

·  访问JSP页面可以使用的所有对象。

·  修改由调用页面生成的响应。

·  彼此通信。可以创建并初始化JavaBean组件、在一个标签中创建引用该bean的变量、再在另一个标签中使用这个bean。

·  彼此嵌套,可以在JSP页面中实现复杂的交互交互。

JSP页面示例

本章描述使用和定义标签所涉及的任务。本章用改写了的、在JSP页面示例中讨论的JSP版本的Duke’s Bookstore应用程序的部分演示这些任务,所做的改写利用了两个标签库的优点:Struts和tutorial-template。本章的第三节示例详细描述了两个标签:Strutst中的iterate和tutorial-template标签库中的一组标签。

Struts标签库提供了构建实现模型-视图-控制设计模式的国际化Web应用程序的框架。Struts包括完整的一组自定义工具标签,用于处理:

·  HTML 表单

·  模板

·  JavaBeans组件

·  逻辑处理

Duke’s Bookstore应用程序使用Struts bean和logic子库中的标签。

Tutorial-template标签库定义了一组用于创建应用程序模板的标签。模板是带有占位符的JSP页面,这些占位符需要在每一屏幕中改变。每一个占位符称为模板的参数。例如,一个简单的模板可能包括在生成的屏幕上方的title参数,和一个JSP页面作为屏幕的定制内容的body参数。模板是用一组嵌入的标签创建的——definition、screen和parameter——它们用于构建Duke’s Bookstore的屏幕定义表,并用insert标签将参数从表中插入屏幕。

图16-1显示了通过Duke’s Bookstore Web组件的请求流程:

·  template.jsp, template.jsp确定每一屏幕的结构。它使用insert标签用子组件组成屏幕。

·  screendefinitions.jsp,它定义了每一屏幕使用的子组件。所有屏幕都有相同的横幅,但是标题和正文不同(由表15-1中的JSP页面列所指定)。

·  Dispatcher,这是一个servlet,它处理请求并转发给template.jsp。

Request Flow Through Duke's Bookstore Components

图16-1 通过Duke’s Bookstore组件的请求流程

Duke’s Bookstore应用程序的源代码位于在解压缩教程包(见运行示例)时生成的docs/tutorial/examples/web/bookstore3目录中。要编译、部署并运行这个例子,你需要:

1.     从以下地址下载Struts version 1.0.2

http://jakarta.apache.org/builds/jakarta-struts/release/v1.0.2/

2.     解压缩Struts并将struts-bean.tld、struts-logic.tld和struts.jar从jakarta-struts-1.0/lib拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore3。

3.     在终端窗口,进入<JWSDP_HOME>/docs/tutorial/examples/bookstore3.

4.     运行ant build。Build目标会进行所有必要的编译并将文件拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore3/build目标。

5.     确保已经启动了Tomcat。

6.     运行ant install。Install目标通知Tomcat 已经有了内容。

7.     如果还没有做的话,就启动PointBas数据库服务器并加入数据(见从Web应用程序中访问数据库)。

8.     打开书店URL http://localhost:8080/bookstore3/enter。

有关诊断常见问题的帮助见常见问题及其解决方案故障排除

使用标签

本节描述JSP页面如何使用标签,并介绍不同类型的标签。

要使用标签,页面编写者必须做以下两件事:

·  声明包含标签的标签库

·  让标签库实现对于Web应用程序可用

声明标签库

通过在使用任何自定义标签之前,将taglib指令加入页面中声明JSP页面将使用在标签库中定义的标签:

<%@ taglib uri="/WEB-INF/tutorial-template.tld" prefix="tt" %>

uri属性表示唯一标识标签库描述符(TLD)的URI,在标签库描述符中描述了uri。这个URI可以是直接或者非直接的。prefix属性定义了区分指定标签库所定义的标签与其他标签库提供的标签的前缀。

标签库描述符文件名必须有扩展名.tld。TLD文件储存在WAR的WEB-INF目录中,或者在WEB-INF的子目录中。可以直接或者间接引用TLD。

下面taglib指令直接引用一个TLD文件名:

<%@ taglib uri="/WEB-INF/tutorial-template.tld" prefix="tt" %>

这个taglib指令使用一个短的逻辑名间接引用TLD:

<%@ taglib uri="/tutorial-template" prefix="tt" %>

在Web应用程序部署描述符中将逻辑名映射到一个绝对位置。要将逻辑名/tutorial-template映射为绝对位置/WEB-INF/tutorial-template.tld,在web.xml中添加元素taglib:

<taglib>
  <taglib-uri>/tutorial-template</taglib-uri>
  <taglib-location>   
  /WEB-INF/tutorial-template.tld
  </taglib-location>
</taglib>

让标签库实现可用

可以以两种方式让标签库实现对Web应用程序可用。实现了标签handler的类可以以非打包的形式储存在Web应用程序的WEB-INF/classes子目录中。另一种方法是,如果以JAR的形式发布库,就将它储存在Web应用程序的WEB-INF/lib目录中。在多个应用程序中共享的标签库储存在Java WSDP的<JWSDP_HOME>/common/lib目录中。

标签类型

JSP自定义标签是用XML语法编写的。它们有一个开始标签和结束标签,可能还有正文:

<tt:tag>
  body
</tt:tag>

不带正文的自定义标签如下表示:

<tt:tag />

简单标签

一个简单标签没有正文,也没有属性:

<tt:simple />

带属性的标签

自定义标签可以带有属性。属性列在开始标签中,语法为attr="value"。像用参数定制方法的行为一样,属性值用于定制自定义标签的行为。在标签库描述符中指定标签属性的类型(见带属性的标签)。

可以用一个常量或者运行时表达式设置属性值。常量和运行时表达式与属性类型之间的转换过程遵循在设置JavaBean组件属性中描述的JavaBean组件属性规则。

Struts logic:present标签的属性决定是否对标签的正文进行判断。在下面的例子中,一个属性指定需要一个名为的参数Clear:

<logic:present parameter="Clear">

Duke’s Bookstore应用程序页面catalog.jsp使用了运行时表达式设置属性的值,它决定Struts logic:iterate标签要枚举哪几本书。

<logic:iterate collection="<%=bookDB.getBooks()%>"
  id="book" type="database.BookDetails">

带正文的标签

自定义标签可以包含自定义和核心标签、脚本元素、HTML文本和开始与结束标签之间的、依赖于标签的正文内容。

在下面的例子中,Duke’s Bookstore应用程序页面showcart.jsp使用Struts logic:present标签清除购物车,并且如果请求包含一个名为Clear的参数就打印一个消息。

<logic:present parameter="Clear">
  <% cart.clear(); %>
  <font color="#ff0000" size="+2"><strong>
  You just cleared your shopping cart!
  </strong><br>&nbsp;<br></font>
</logic:present>

选择用属性或者正文传递信息

正如最后两节中所展示的,可以将给定的数据作为标签的属性或者标签的正文传递。一般来说,任何简单字符串或者可以由对简单表达式判断而生成的数据最好作为属性传递。

定义脚本变量的标签

自定义标签可以定义可在页面中的脚本中使用的变量。下面的例子展示了如何定义并使用包含一个从JNDI查询中返回的对象的脚本变量。这种对象的例子包括企业bean、事务、数据库、环境项等等:

<tt:lookupegin(); %> id="tx" type="UserTransaction"
  name="java:comp/UserTransaction" />
<% tx.b

在Duke’s Bookstore应用程序中,有几个页面使用了Struts的面向bean的标签以定义脚本变量。例如,bookdetails.jsp使用了bean:parameter标签以创建脚本变量bookId并设置它并将它设置为请求参数bookId的值。jsp:setProperty语句还设置bookDB对象的bookId属性为请求参数bookId的值。bean:define标签提取书店数据库bookDetails属性bookDetails的值并将结果定义为脚本变量book:

<bean:parameter id="bookId" name="bookId" />
<jsp:setProperty name="bookDB" property="bookId"/>
<bean:define id="book" name="bookDB" property="bookDetails"
  type="database.BookDetails"/>
<h2><jsp:getProperty name="book" property="title"></h2>

操作标签

自定义标签可以通过共享对象彼此合作。

在下面的例子中,tag1创建了一个名为obj1的对象,再由tag2返回这个对象。

<tt:tag1 attr1="obj1" value1="value" />
<tt:tag2 attr1="obj1" />

在下面的例子中,由一组嵌套标签中的外围标签创建的对象对于所有内部标签都是可用的。因为没有为对象命名,所以可以减少潜在的命名冲突。这个例子展示在JSP页面中一组协作的嵌入标签会是什么样子的。

<tt:outerTag>
  <tt:innerTag />
</tt:outerTag>

Duke’s Bookstore页面template.jsp使用了一组协作标签定义应用程序的屏幕。在模板标签库中描述了这些标签。

定义标签

要定义标签,需要:

·  为该标签开发一个tag handler和helper类

·  在标签库描述符中声明这个标签

本节描述标签handler和TLD的属性,并解释如何为在前面几节中介绍的标签开发tag handler和库描述符元素。

标签handler

标签handler是由Web容器调用的一个对象,用于执行带有自定义标签的JSP页面时对这个标签进行判断。标签handler必须实现Tag或者BodyTag接口。接口可以用于接受现有Java对象并使它成为标签handler。对于新创建的处理器,可以用TagSupportBodyTagSupport类作为基类。这些类和接口包含在javax.servlet.jsp.tagext包中。

JSP页面的servlet在对标签处理的不同阶段调用由Tag和BodyTag接口定义的标签handler。遇到自定义标签的开始标签时,JSP页面的servlet调用方法以初始化相应的handler,然后调用handler的doStartTag方法。遇到自定义标签的结束标签时,调用处理器的doEndTag方法。在标签handler需要与标签的正文交互时调用其他方法,见带正文的标签。为了提供标签handler的实现,必须实现在处理标签的不同阶段调用的方法,在表16-1中汇总了这些方法。

16-1标签handler方法

标签handler类型

方法

简单

doStartTag, doEndTag, release

属性

doStartTag, doEndTag, set/getAttribute1…N, release

正文、判断且无交互

doStartTag, doEndTag, release

正文、迭代判断

doStartTag, doAfterBody, doEndTag, release

正文、交互

doStartTag, doEndTag, release, doInitBody, doAfterBody, release

标签handler可以使用一个能让它得以与JSP页面通信的API。到API的入口点是页面上下文对象(javax.servlet.jsp.PageContext),通过它标签handler可以获取JSP页面能够访问的所有其他隐式对象(请求、会话和应用程序)。

隐式对象可以有与其相关联的命名属性。可以用[set|get]Attribute方法访问这种属性。

如果标签是嵌入的,标签handler还可以访问与外围标签关联的handler称为parent)。

一组相关的标签handler类(标签库)一般是打包的且作为JAR文档部署。

标签库描述符

标签库描述符(TLD)是一个描述标签库的XML文档。TLD包含有关整个库以及库中包含的每一个标签的信息。Web容器用TLD验证标签,JSP页面开发工具也使用TLD。

TLD文件名必须有扩展名.tld。TLD文件也储存在WAR文件的WEB-INF目录中或者在WEB-INF的子目录中。

TLD必须以指定XML的版本和文档类型定义(DTD)的XML文档序言(prolog)开始。

<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">

Tomcat支持版本 1.1和1.2的DTD。不过,本章所讨论的是1.2版本,因为在开发的所有标签库中都应该使用最新的版本。模板库TLDtutorial-template.tld符合版本1.2。Struts库TLD符合版本1.1的DTD,它的元素要少,且其中一些元素使用了稍微不同的名字。

TLD的根是taglib元素。表16-2中列出了taglib的子元素:

16-2 taglib子子元素 

元素

说明

tlib-version

标签库的版本

jsp-version

这个标签库要求的JSP规范版本

short-name

JSP页面编写工具可以用来创建助记名的可选名字

uri

唯一标识该标签库的的URI

display-name

将由工具显示的可选名

small-icon

将由工具使用的可选小图标

large-icon

可被工具使用的可选大图标

description

可选的标签特定信息

listener

listener元素

tag

tag元素

 

listener元素

标签库可以指定一些事件监听器类(见处理Servlet生命周期事件)。这些监听器在TLD中作为listener元素列出,Web容器将初始化监听器类并以类似在WAR级定义的监听器的方式注册它们。与WAR级监听器不同,这里没有指定标签库监听器注册的顺序。listener元素的唯一子元素是listener-class元素,它必须包含监听类的完全限定名。

tag元素

库中的每一个标签都由给出其名字和其标签handler的类、在由标签创建的脚本变量上的信息以及标签属性上的信息描述。脚本变量信息可以在TLD中直接给出,也可以通过tag extra info类给出(见定义脚本变量的标签)。每一个属性声明包含指明属性是否是必需的、其值是否可以由请求时表达式确定以及属性类型的内容(见属性元素)。

在tag元素中的TLD中指定标签。在表16-3中出了tag的子元素:

16-3 标签子元素

元素

说明

name

唯一标签名

tag-class

标签handler类的完全限定名

tei-class

javax.servlet.jsp.tagext.TagExtraInfo的可选子类。见提供有关脚本变量的信息

body-content

正文内容类型。见body-conten元素body-content元素

display-name

由工具显示的可选名

small-icon

可以由工具使用的小图标

large-icon

可以由工具使用的大图标

description

可选的标签特定的信息

variable

可选的脚本变量信息。见提供有关脚本变量的信息

attribute

标签属性信息。见Attribute 元素

下面几节描述开发在标签类型中介绍的每一种类型的标签所需要的方法和TLD。

简单标签

标签handler

简单标签的handler必须实现Tag接口的doStartTag和doEndTag方法。在遇到开始标签时调用doStartTag方法。因为简单标签没有正文,所以这个方法返回SKIP_BODY。在遇到结束标签时调用doEndTag方法。如果要对页面的其他部分进行判断,则doEndTag方法需要返回EVAL_PAGE,否则,它就返回SKIP_PAGE。

在第一节讨论的简单标签

<tt:simple />

由下列标签handler实现:

public SimpleTag extends TagSupport {
  public int doStartTag() throws JspException {
    try {       pageContext.getOut().print("Hello.");
    } catch (Exception ex) {
      throw new JspTagException("SimpleTag: " +
        ex.getMessage());     }
    return SKIP_BODY;   }
  public int doEndTag() {
    return EVAL_PAGE;
  }
}

body-content元素

没有正文的标签必须用body-content元素声明它们的正文内容是空的:

<body-content>empty</body-content>

JavaServer Pages标准标签库(JSTL)封装了许多JSP应用程序共有的核心功能。例如,不需要用脚本或者众多不同厂商的不同迭代器标签对列表进行迭代,JSTL定义了在所有地方都可以使用的标准标签。这种标准化使您可以学习一种标签后,在多个JSP容器中使用它。而且,当标签标准化以后,容器可以优化对它们的实现。

JSTL支持通用的、结构化的任务,如迭代和条件、操作XML文档的标签、国际化标签以及以及用SQL访问数据库的标签。它还引入了表达式语言的概念以简化页面的开发。JSTL还提供了集成现有标签库与JSTL的框架。

本章通过在前面几章讨论的Duke’s Bookstore应用程序的JSP版本的内容展示JSTL。假定您已经熟悉了第16章中的“使用标签”中的内容。

JSP页面示例

本章通过如下重新编写JSP版本的、在第16章讨论过的Duke’s Bookstore应用程序的内容来展示JSTL:

·  用JSTL核心标签替换Struts逻辑标签。

·  用消息格式标签替换访问消息储存的scriptlet。

·  用通过JSTL SQL标签对数据库的直接调用替换JavaBean组件数据库helper。对于大多数应用程序来说,最好将对数据库的调用封装到bean中。JSTL包含SQL标签,在创建已有原型的应用程序并有可能减少创建bean的开销的情况下可以使用这个标签。

Duke’s Bookstore应用程序的源代码在解开教程压缩包时创建的<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4目录中(见运行示例)。

要编译、安装和运行这个例子:

1.     在终端窗口,进入 <JWSDP_HOME>/docs/tutorial/examples/web/bookstore4.

2.     运行ant build。Build目标会进行所有需要的编译并将文件拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4/build 目录中。

3.     确保已经启动Tomcat。

4.     运行ant install。install目标通知Tomcat新的内容已经可用。

5.     如果还没有做的话,启动PointBase数据库服务器并填入数据 (见Web应用程序访问数据库)。

6.     打开书店URL http://localhost:8080/bookstore4/enter.

有关诊断常问题的帮助见常见问题及其解决方法故障排除

使用JSTL

JSTL包括很多种不同的标签,可应用到不同功能的领域中。因此,通过多个标签库描述符(TLD)明确表明它所覆盖的功能领域,并给每一领域它自己的命名空间。17-1总结了这些功能领域,以及在本章和Duke’s Bookstore应用程序中使用的逻辑TLD名和前缀。

17-1 JSTL 标签 

领域

功能

标签

TLD

前缀

核心

支持表达式语言

catch

out

remove

set

/jstl-c

c

流程控制

choose

  when

  otherwise

forEach

forTokens

if

URL管理

import

  param
redirect

  param

url

  param

XML

核心

out

parse

set

/jstl-x

x

流程控制

choose
  when
  otherwise

forEach

if

转换

transform

  param

I18n

区域

setLocale

/jstl-fmt

fmt

编排消息格式

bundle

message

  param

setBundle

编排数字和日期格式

formatNumber

formatDate

parseDate

parseNumber

setTimeZone

timeZone

数据库

 

setDataSource

/jstl-sql

sql

SQL

query

  dateParam

  param

transaction

update
  dateParam

  param

例如,要在JSP页面中使用JSTL核心标签,用引用TLD的taglib指令声明库:

<%@ taglib uri="/jstl-core" prefix="c" %>

JSTL标签库有两种版本(见孪生库)。JSTL-EL库的TLD命名为prefix.tld。JSTL-RT库的TLD命名为prefix-rt.tld。由于在本章讨论的例子使用逻辑TLD名,所以我们用Web应用部署描述符中的taglib元素将逻辑名映射为实际TLD位置。下面是将核心库逻辑TLD名/jstl-c映射到其位置/WEB-INF/c.tld的项:

<taglib>
 <taglib-uri>/jstl-c</taglib-uri>
 <taglib-location>/WEB-INF/c.tld</taglib-location>
</taglib>

在Java WSDP中,JSTL TLD储存在<JWSDP_HOME>/jstl-1.0.3/tld中。在构建Duke’s Bookstore应用程序时,这些TLD自动拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4/build/WEB-INF中。

还可以在taglib指令中用绝对URI引用TLD:

·  核心: http://java.sun.com/jstl/core

·  XML: http://java.sun.com/jstl/xml

·  国际化: http://java.sun.com/jstl/fmt

·  SQL: http://java.sun.com/jstl/sql

在使用绝对URI时,不必在web.xml中添加taglib元素,JSP容器会自动定位在JSTL库实现中的TLD。

除了声明标签库,还需要使JSTL API及其实现对于Web应用程序是可用的。它们是以<JWSDP_HOME>/jstl-1.0.3中的jstl.jar和<JWSDP_HOME>/jstl-1.0.3/standard中的standard.jar文档形式发布的。在构建Duke’s Bookstore应用程序时,这些库会自动拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4/build/WEB-INF/lib中。

表达式语言支持

JSTL的一个重要功能就是对表达式语言(EL)的支持。表达式语言结合JSTL标签,使得访问应用程序数据和以简单方式操纵它变得容易了,不需要使用scriptlet或者请求时表达式。当前,页面编写者必须使用表达式<%= aName %>访问系统的值或者用户定义的JavaBean组件。如:

<x:aTag att="<%= pageContext.getAttribute("aName") %>">  

对于嵌入式bean属性就更复杂了:

<%= aName.getFoo().getBar() %>

这使得页面的编写比它需要的更复杂。

表达式语言使页面编写者可以用简单的语法访问对象,如对于简单的变量

<x:atag att="${aName}">  

或者对于嵌入的属性

<x:aTag att="${aName.foo.bar}">  

JSTL表达式语言将JSP scoped 属性提升为业务逻辑与JSP页面交换信息的标准方式。例如,为这个条件标签的test属性提供一个比较名为cart的会话作用域属性中的项目数是否为0的表达式:

<c:if test="${sessionScope.cart.numberOfItems > 0}">   … </c:if>

JSP规范的下一个版本将会对所有自定义标签库的表达式语言进行标准化。这一版本的JSTL包括这一表达式语言的一个快照。

孪生库

JSTL标签库有两个版本,它们只是在对属性值使用运行时表达式的方式上有所不同。

在JSTL-RT标签库,表达式是在页面的脚本语言中指定的。这正是当前标签库的工作方式。

在JSTL-EL标签库中,表达式是在JSTL表达式语言中指定的。表达式是以EL的语法所写的一个String文字。

在使用EL标签库时,不能为一个属性的值传递脚本语言表达式。这个规则使它可以在转换时验证表达式的语法。

JSTL 表达式语言

JSTL表达式语言负责处理表达式和文字。表达式由${ }字符所包围。例如:

<c:if test="${bean1.a < 3}" />

所有不以${开始的值都认为是文字,用所预期的类型的PropertyEditor将文字解析为预期类型:

<c:if test="true" />

包含字符的文字值必须进行如下转义:

<mytags:example attr1="an expression is ${‘${‘}true}" />

属性

可以通过名称访问属性,也可以加上作用域。属性的属性是用.操作符访问的,并可任意嵌套。

EL统一了对.和[ ]操作符的处理。因此,expr-a.expr-b等于expr-a[expr-b]。要判断expr-a[expr-b],需将expr-a按value-a判断,将expr-b按value-b判断。

·  如果value-a是一个Map,返回value-a.get(value-b)。

·  如果value-a是一个List或者数组,强制value-b为int并相应并返回value-a.get(value-b)或者Array.get(value-a, value-b)。

·  如果value-a是JavaBean对象,强制value-b为String。如果value-b是value-a的可读属性,则返回getter调用的结果。

EL通过将标识作为属性查询、根据PageContext.findAttribute(String)的行为对标识进行判断。例如,${product}会查询名为product的属性,搜索页面、请求、会话和应用程序作用域,并返回其值。如果没有找到属性,则返回null。注意与一个在下面一节中描述的隐式对象相匹配的标识会返回该隐式对象而不是属性值。

隐式对象

JSTL 表达式语言定义了一组隐式对象:

·  pageContext: PageContext对象

·  pageScope :一个Map,它将页面作用域属性名映射为它们的值

·  requestScope:一个Map,它将请求作用域的属性名映射为它们的值

·  sessionScope :一个Map,它将会话作用域的属性名映射为它们的值

·  applicationScope:一个Map,它将应用程序作用域的属性值映射为它们的值

·  param:一个Map,它将参数名映射为单个String参数值(通过调用ServletRequest.getParameter(String))

·  paramValues: 一个Map,它将参数名映射为该参数的所有值的String[ ] (通过调用获得ServletRequest.getParameterValues(String) 获得)

·  header :一个Map,它将头名映射到单个String头值(通过调用ServletRequest.getheader(String)获得)

·  headerValues: 一个Map,它将头名映射到该参数的所有值的String[ ] (通过调用ServletRequest.getHeaders(String) 获得)

·  cookie: 一个Map,它将cookie名映射为单个Cookie (通过调用HttpServletRequest.getCookie(String) 获得)

·  initParam : 一个Map,它将参数名映射到单个String值 (通过调用ServletRequest.getInitParameter(String) 获得)

当表达式通过名字引用其中一个对象时,返回的是相应的对象而不是相应的属性。例如:${pageContext}返回PageContext对象,即使有一个现有的包含一些其他值的pageContext属性。表17-2显示了使用这种隐式对象的一些例子。

17-2 JSTL表达式例子

表达式

结果

${pageContext.request.contextPath}

上下文路径(从HttpServletRequest获得)

${sessionScope.cart.numberOfItems}

名为cart的会话作用域的numberOfItems属性

${param["mycom.productId"]}

mycom.productId参数的String 值

Literals

·  Boolean: true和false

·  Long: 与在Java中的一样

·  Floating point: 与在Java中一样

·  String: 带单引号和双引号。"转义为\",’ 转义为\’,而\ 转义为\\。

·  Null: null

操作符

EL提供了下列操作符:

·  算术:+、-、*、/和div、%和mod、-

·  逻辑: and、&&、or、||、not、!

·  关系: ==、eq、!=、ne、<、lt、>、gt、<=、ge、>=、le。可以是针对其他值,也可能针对布尔、字符串、整数或者浮点文字进行比较。

·  空: empty 操作符是一个前缀操作符,可以用来决定一个值是否为null或者空。

有关这些操作符的优先级和效果参见JSTL 1.0规范

标签协作

标签通常与它们的环境以隐式或者显式的方式协作。隐式协作是通过让上级标签公开定义好的接口、使嵌套的标签无缝地与上级标签协作来实现的。JSTL迭代器标签支持这种模式的协作。

显示协作在标签向环境公开信息时出现。传统上,这是通过公开一个脚本变量实现的(由JSP作用域的属性提供实际对象)。因为JSTL有一个表达式语言,因此不太需要脚本变量。所以JSTL标签(包括EL和RT版本)只是以JSP作用域属性公开信息,不使用脚本变量。下面的JSTL规范对于所有导出有关标签信息的标签属性使用名字var。例如,forEach标签以下面的方式公开购物车中正在迭代的当前项目

<c:forEach var="item" items="${sessionScope.cart.items}">
  …
</c:forEach>

选用名字var以突出公开的作用域变量不是脚本变量(一般情况下对于属性应命名为id)。

对于标签公开多种情息的情况,名字var用于公开的主要信息,对于公开的其他二级信息选用其他合适的名字。例如,forEach标签通过status属性公开迭代状态信息。

核心标签

核心标签包括那些与表达式、流程控制和一种通用的访问那些基于URL的、其内容可以包含在JSP页面或者在JSP页面中被处理的资源的方式相关的标签。

17-3 核心标签 

领域

功能

标签

TLD

前缀

核心

表达式语言支持

catch

out

remove

set

/jstl-c

c

流程控制

choose

  when

  otherwise

forEach

forTokens

if

URL管理

import

  param

redirect

  param

url

  param

表达式标签

out标签判断一个表达式并将判断结果输出为当前JspWriter对象。它对应于JSP语法<%= expression %>。例如,showcart.jsp像下面这样显示在购物车上的项目:

<c:out value="${sessionScope.cart.numberOfItems}"/>

set标签设置在任何JSP作用域(页面、请求、会话、应用程序)中的属性的值。如果该属性不存在,则创建它。

可以用属性值设置JSP作用域属性:

<c:set var="foo" scope="session" value="…"/>  

也可以用标签正文设置JSP作用域属性:

<c:set var="foo">
  …
</c:set>

例如,下面用名为Remove的请求参数设置名为bookID的页面作用域属性:

<c:set var="bookId" value="${param.Remove}"/>

如果使用RT版本的库,那么语句将为:

<c_rt:set var="bookId"
  value="<%= request.getParameter("Remove") %>" />

要删除一个作用域属性,使用remove标签。调用书店JSP页面receipt.jsp时,购货会话就结束了,所以像下面这样删除会话属性cart:

<c:remove var="cart" scope="session"/>

JSTL表达式语言减少了对脚本的需要。不过,页面编写者仍然会遇到在页面的脚本语言中一些非JSTL标签属性必须用表达式指定的情况。用标准JSP元素jsp:useBean声明可以在脚本表达式或者scriptlet中使用的脚本变量。例如,showcart.jsp使用scriptlet从购物车上删除一本书。这本要删除的书的ID作为请求参数传递。请求参数的值首先设置为页面属性(以便在后被标签JSTL sql:query使用),然后声明为脚本变量并传递给cart.remove方法:

<c:set var="bookId" value="${param.Remove}"/>
<jsp:useBean id="bookId" type="java.lang.String" />
<% cart.remove(bookId); %>
<sql:query var="books"
  dataSource="${applicationScope.bookDS}">
  select * from PUBLIC.books where id = ?
  <sql:param value="${bookId}" />
</sql:query>

catch标签为JSP错误页面机制提供了一种补充。它让页面编写者能够从可以控制的错误条件中正常恢复。页面上最重要的行动应当封装在catch中,这样它们的异常就会传播到错误页面。页面中次要的行动应当包装到catch中,这样它们就不会调用错误页面机制。

抛出的异常储存在由var标识的作用域变量中,它总是有页面作用域。如果没有发生异常,那么如果有由var标识的作用域变量的话,就将它删除。如果没有var,那么异常就仅仅被捕捉而不保存。

作者:杜玄

1.引言


在以因特网为基础的企业应用中,穿越防火墙是企业应用系统必须面对的问题。而Http协议是唯一一个可以在因特网中自由来往的协议,防火墙只有对Http访问是透明的,其它协议都会被挡在防火墙之外。Web Service正是基于这个原因,采用Http完成异构企业应用在因特网上的整合。本文提出一个方案,可以使基于Java平台的企业应用不必等待Web Service的渐渐成熟,而利用Java隧道技术实现在因特网上的企业应用部署。

Java隧道技术不是一个新的技术,它是Java对象序列化和Web技术的结合。2001年笔者就曾经做过基于Java隧道的试验。关于Java Servlet的书籍也有对Java隧道技术的相关论述(参考文献[2])。本文首先回顾一下Java隧道技术的基本原理,再通过一个实例来实现它。以此为后面的Java隧道回调技术和基于Java隧道技术的因特网通讯打下基础。

本文的内容可以用于Java Web Start客户端在因特网上的部署,可以用于利用因特网互连的Java应用服务。

2.Java隧道通讯


Web Service技术中的SOAP协议(Simple Object Application Protocol),简单的说是把对象用XML来序列化和反序列化的协议。XML表述的好处就是平台的独立性,不管对象在不同的平台中表现的形式是什么样的,最终都可以用XML来实现对象的互换。并且,基于Http协议的Web Service可以穿越防火墙。 Web Service技术整合因特网上的应用是它的强项。Web Service技术是一个很好的创意,包括IBM,Microsoft,Sun等公司都极力推出自己的相关产品。我们可以说,在未来的异构平台的集成,B2B应用中,Web Service将发挥重要的作用。

然而,对于Java平台的企业应用,你可以用Web Service相同的原理,利用Java平台的对象序列化,再借助Web技术,完成因特网上的应用部署。这样的技术就是Java的隧道技术。因为这个技术都是利用现有的基础技术来实现的,所以可以非常容易的完成系统的构架。当然Java隧道与Web Service的定位是完全不同的,他们应该用在不同的场合。Web Service更加强大,更加通用化,是一套新兴的互相访问的标准。也正是由于Web Service的强大,所以它也更复杂。回答下面的问题,来看看你需要哪种技术来构架你的应用:

  • 1.互连的系统是否都是Java平台?
  • 2.是否要在Internet上部署?
  • 3.是否要穿越防火墙?

如果第一个问题的答案是"否",其它问题回答"是",那么你别无选择,必须用Web Server技术;如果以上的问题的回答都是"是"的话,你可以用Web Service来构架系统,当然,你也可以用更便捷,简单,高效的Java隧道技术。

Java隧道技术的基本构架如下图。Java隧道系统的通讯构架,由Web Server提供Http服务,Servlet容器处理Java序列化对象。对于这样的一个框架,我们可以用常见的Tomcat,Jetty等软件作为Java隧道技术的运行基础环境。

Java隧道技术的基本工作原理是这样的:首先,Java客户端把对象序列化,用Http协议包装序列化对象的字节流在Internet上传播,透过防火墙,提交到Web Server上。Servlet容器把Http协议剥离开,取出序列化的对象,再反序列化这个java对象,传递给后台的应用。

整个工作过程,就是把Java序列化对象,通过Http协议的包装完成在Internet上的通讯。由于Java序列化技术是Java 1.1版本后内置的功能,只要实现java.io.Serializable接口就可以实现。而集成Web Server的Servlet容器更是有好多实现,这里也不乏开源的优秀产品,如Tomcat,Jetty等。这些都体现了Java隧道技术构架的简便性。

下面我们通过一根例子来看看Java隧道技术是如何具体实现的。

3.实例


这里我们选择Jetty(Jetty 4.2.21)作为Web Server和Java Servlet的运行容器。编写普通的Java应用程序TunnelClient作为java隧道的客户端。编写Servlet程序TunnelServer作为java隧道的服务端。如下图,运行java隧道客户端程序,在程序内部调用隧道服务http://127.0.0.1:8080/servlets/servlet/TunnelServer。从服务器返回对象,对象内容打印在控制台上。

如下图,是Jetty服务器中TunnelServer的运行情况,我们可以看到TunnelServer从客户端获得的对象内容的屏幕打印。

从下面隧道服务器程序TunnelServer的代码中我们可以看到,隧道服务首先是一个Servlet程序,TunnelServer继承于java.servlet.HttpServlet。隧道中的对象传递是通过java.io.ObjectInputStream, java.io.ObjectOutStream来实现的。被传递的对象,首先必须是可以序列化的对象,就是说必须实现java.io.Serializable接口。例子代码中的Msg类就是一个实现了java.io.Serializable接口的类。Msg对象通过java.io.ObjectInputStream, java.io.ObjectOutStream实现了对象的远程传递。由于TunnelServer继承于java.servlet.HttpServlet,所以对象的传递是基于Http协议的。对象的远程传递可以透过防火墙正是因为通过Http协议传输的结果。

从下面隧道客户端程序TunnelClient的代码中我们可以看到,其工作原理与Java隧道服务端的完全相同。客户端的java.io.ObjectInputStream,,java.io.ObjectOutStream分别对应服务端的java.io.ObjectOutStream ,java.io.ObjectInputStream。客户端与服务端形成了一个对象传输的管道。

在Java隧道中我们可以增加加密技术来保证传递信息在Internet上的安全传递。在对象进入通道之前,可以用加密密钥对对象流先加密(我们假设用非对称加密算法,当然可以根据加密强度的要求用不同的加密算法和密钥长度),再送入通道中;在接收到加密对象流之后,先用解密密钥对对象流解密,再还原为对象实例。

这里要注意,无论是在服务端还是在客户端,都必须包含Msg类的Class文件,而且Msg在两端必须要一致。因为隧道的一端要实例化Msg对象,另一端要从IO中获得对象流,再用Msg类来造型(Cast)。

4.结束语


Java隧道技术如此简单,无论是代码的编写,还是运行环境的组建,都非常容易。正是因为Java隧道技术的简单性,才使我们可以方便的把这个技术集成到应用系统中。然而,简洁不代表功能的简单,基于这个技术,可以实现基于Http的对象消息传递和远程方法调用,并且穿越防火墙。Java隧道技术为Java平台的应用在因特网上的部署提供了一个简单,高效的方案。

参考文献


1.
  http://www.sourceforge.com


2.
  http://www.codeproject.com


3.
  http://www.c-sharpcorner.com/


4.
  http://martinfowler.com/


5.
  http://channel9.msdn.com/default.aspx


6.
  http://www.ddj.com/


7.
  http://blogs.msdn.com/danielfe/


8.
  http://www.topcoder.com


9.
  http://www.spdevelop.com/default.aspx  (.NET开源项目)


10.
  http://java.sun.com/


11.
  http://asp.net  


12.
  http://www.jjhou.com/


13.
  http://lcspace.nease.net/dvp-mda/mda20.xml


14.
  http://www.dhxy.com/school/uml/List1.htm 


15.
  http://www.msuniversity.edu.cn


16.
  http://www.microsoft.com/china/technet/default.mspx


17.
  http://www.microsoft.com/china/msdn/


18.
  http://www.eastasp.com


19.
  http://www.chinaaspx.com/


20.
  http://www.developerfusion.com/utilities/convertcsharptovb.aspx


21.
  http://www.developerfusion.co.uk/utilities/convertvbtocsharp.aspx


22.
  http://bbs.mscommunity.com/forums/  (微软中文技术社区:)


23.
  http://blog.joycode.com


24.
  http://www.gotdotnet.com/


25.
  http://www.theserverside.net/


26.
  http://www.zhangyongji.name


27.
  http://www.csdn.net/


28.
  http://www.cnblogs.com


29.
  http://blog.joycode.com


30.
  http://dotnet.mblogger.cn/


31.
  www.ibook8.net


32.
  http://www.microsoft.com/china/msdn/default.mspx


33.
  http://www.asp.net/


34. 
  http://www.kbalertz.com/


35.
  http://www.15seconds.com/


36.
  http://www.microsoft.com/china/msdn/default.mspx


37.
  www.51js.com


38.
  http://dotnet.aspx.cc/  (孟子E章)


39.
  http://www.uml.org.cn/mxdx/mxdx.asp  (UML OO系列文章:)


40.
  http://singlepine.cnblogs.com/articles/263473.html   (小山网址集锦)


41.
  ASP.Net BBS www.52forum.com/bbs


42.
  http://communityserver.org/forums/85/showforum.aspx


43.
  http://sourceforge.net/projects/weblucene/


44.
  http://www.dotnetcharting.com/


45.
  http://www.sawin.cn/  (uml和系分的网站!)


46.
  http://www.chinaitpower.com/Dev/Softproject/UML/


47.
  http://www.madeinwuxi.com


48.
  http://www.host01.com/Get/Net/00020001/0561614361031675.htm  (教程在线)


49.
  www.experts-exchange.com


50.
  www.cnnie.net


51.
  http://discuss.develop.com


52.
  www.vckbase.com


53.
  http://bbs.firstdev.net


54.
  www.vckbase.com (VC网站)


55.
  www.codeguru.com


56.
  http://www.vckbase.com/


57.
  http://www.czvc.com


58.
  www.cnblogs.com


59.
  www.donews.com


60.
  www.chinaitpower.com 


61.
  http://www.w3schools.com/  (学习xml及相关技术可以去)


62.
  http://dret.net/glossary/


63.
  http://www.uml.org.cn/rational/list.asp?boardid=7  (Rose)


64.
  http://lcspace.nease.net  (松藕和空间)


65.
  http://support.apress.com  (网上书籍中代码下载)


66.
  http://www.codechina.net   (代码中国网,各类开发资源下载)


67.
  www.graphics.net.cn


68.
  www.21tx.com


69.
  www.codepub.com


70.
  http://www.dynamicdrive.com/


71.
  http://script.aculo.us/ (javascript)


72.
  http://singlepine.cnblogs.com/category/37633.html


73.
  http://www.ajaxgoals.com/index.html  (ajax)


74.
  http://www.regexplib.com/DisplayPatterns.aspx  (正则表达式)


75.
  http://weblogs.asp.net/


76.
  http://www.download.com/  (下载)


77.
  http://community.csdn.net/ChainStory/


78.
  http://search.csdn.net/Expert/topic/2439/2439969.xml?temp=.6801111


79.
  http://www.useit.com


80.
  http://www.webpagesthatsuck.com


81.
  www.iarchitect.com/mahame.htm   (接口错误集锦)


82.
  http://www.aspxcn.org/


83.
  http://www.matrix.org.cn/


84.
  http://www.seeitco.com


85.
  http://forums.microsoft.com/MSDN/


86.
  www.eclipse.org


87.
  http://www.uml.org.cn/oobject/OObject.asp


88.
  www.sf.net

2006年01月03日

有时候想为我们的网页提供多语言支持,如果一种语言用一张网页来做实在太麻烦了,幸好Google提供了语言工具功能,下面介绍如何利用它来实现网页多种语言之间的转换。   lan.htm <form><select name="lan"><option value="en|de">英语 翻译成 德语</option><option value="en|es">英语 翻译成 西班牙语</option><option value="en|fr">英语 翻译成 法语</option><option value="en|it">英语 翻译成 意大利语</option><option value="en|pt">英语 翻译成 葡萄牙语</option><option value="en|ja">英语 翻译成 日语 BETA</option><option value="en|ko">英语 翻译成 朝鲜语 BETA</option><option value="en|zh-CN" >英语 翻译成 中文(简体) BETA</option><option value="de|en">德语 翻译成 英语</option><option value="de|fr">德语 翻译成 法语</option><option value="es|en">西班牙语 翻译成 英语</option><option value="fr|en">法语 翻译成 英语</option><option value="fr|de">法语 翻译成 德语</option><option value="it|en">意大利语 翻译成 英语</option><option value="pt|en">葡萄牙语 翻译成 英语</option><option value="ja|en">日语 翻译成 英语 BETA</option><option value="ko|en">朝鲜语 翻译成 英语 BETA</option><option value="zh-CN|en">中文(简体) 翻译成 英语 BETA</option><input style="FONT-SIZE: 12px" type="button" value="Go->" name="Button1" onClick="javascript:window.open(’translate.asp?urls=’+document.location+’&lan=’+lan.value,’_self’,’’)"></select></form>   lan.htm中的内容用来选择要翻译的语言包括原来的语言和要翻译成的语言。我们只需要将lan.htm中的内容拷到提供多语言翻译的页面中就可以了。   translate.asp <html><head><title>在线翻译</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head> <body><% ’on error resume next ’ 如果网速很慢的话,可以调整以下时间。单位秒 Server.ScriptTimeout = 999999 ’======================================================== ’字符编码函数 ’======================================================== Function BytesToBstr(body,code) dim objstream set objstream = Server.CreateObject("adodb.stream") objstream.Type = 1 objstream.Mode =3 objstream.Open objstream.Write body objstream.Position = 0 objstream.Type = 2 objstream.Charset =code BytesToBstr = objstream.ReadText objstream.Close set objstream = nothing End Function ’取行字符串在另一字符串中的出现位置 Function Newstring(wstr,strng)  Newstring=Instr(lcase(wstr),lcase(strng))  if Newstring<=0 then Newstring=Len(wstr) End Function ’替换字符串函数 function ReplaceStr(ori,str1,str2)  ReplaceStr=replace(ori,str1,str2) end function ’===================================================== function ReadXml(url,code,start,ends)  set oSend=createobject("Microsoft.XMLHTTP")  SourceCode = oSend.open ("GET",url,false)  oSend.send()  ReadXml=BytesToBstr(oSend.responseBody,code )  if(start="" or ends="") then  else   start=Newstring(ReadXml,start)   ReadXml=mid(ReadXml,start)   ends=Newstring(ReadXml,ends)   ReadXml=left(ReadXml,ends-1)  end if end function dim urlpage,lan urlpage=request("urls") lan=request("lan") %><form method="post" action="translate.asp"><input type="text" name="urls" size="150" value="<%=urlpage%>"><input type="hidden" name="lan" value="<%=lan%>"><input type="submit" value="submit"></form><%  dim transURL  transURL="http://216.239.39.104/translate_c?hl=zh-CN&ie=UTF-8&oe=UTF-8&langpair="&server.URLEncode(lan)&"&u="&urlpage&"&prev=/language_tools"   if(len(urlpage)>3) then    getcont=ReadXml(transURL,"gb2312","","")    response.Write(getcont)   end if %></body></html>   translate.asp实现翻译功能,这是利用google的语言工具实现的。   注意,因为提供多语言支持,所以translate.asp文件所用的编码是支持所有字符的"utf-8"编码

一.大家试过没有,使用google,如果加用了超级代理,会有多十倍的东东可搜…

来个AD 高校交友联盟 http://7920.cn
二.下面是google的一些秘密入口

http://www.google.com/microsoft
  微软风格的入口
  
  http://www.google.com/mac
  MAC风格的入口
  
  http://www.google.com/linux
  Linux风格的入口
  
  http://www.google.com/bsd
  FreeBSD风格的入口
  
  Google有各种语言的版本,下面这些语言可能实在是稀罕了点儿
  
  http://www.google.com/intl/xx-klingon/
  克林语入口(没看过星际旅行吗?)
  
  http://www.google.com/intl/xx-bork/
  zz入口
  
  http://www.google.com/intl/xx-elmer/
  宗教入口
  
  http://www.google.com/intl/xx-piglatin/
  小猪入口
  
  http://www.google.com/intl/xx-hacker/
  黑客专用入口
  
  Google里还有一个小小的彩蛋游戏,大家自己去看吧!
  http://www.google.com/Easter/feature_easter.html
  
  
  下面是一此更有用的彩蛋
  
  http://froogle.google.com/
  作用:
通过特殊的搜索引擎,你可以在网上找到你想购物的网站位置。以及你可以很方便的搜索出同类产品的价格。
  
  看看世界各地的商品价格,自己再买的时候心底有数多了把
  
  http://labs.google.com/gviewer.html
  作用:一个小玩具,适合那些连鼠标都懒得动的懒人。当你在google上找到了查询结果后,你可以使用google
viewer让结果以自己定义的间隔时间来一条一条自动滚屏。
  
  http://labs.google.com/cgi-bin/webquotes
  作用:可以搜索出你要查询的内容在internet上被多少其他的网站引用过,可以让你知道internet上其他人对你要查询内容的观点,适合写论文和评论以及特殊用途人使用。
  
  http://labs.google.com/glossary
  作用:顾名思义了,就是一个查英语缩写语意的工具,google的词汇表
  
  http://labs1.google.com/gvs.html
  作用:给google打一个电话,说出你要查找的内容,然后google会把你要查询的结果显示出来。
给不识字的人用的

2005年12月31日

Derek Fong , IT 架构师, IBM Canada

2005 年 12 月 29 日

Web 应用程序安全设计的目的是消除漏洞。仅仅基于用户凭证的已知安全设计元素(如验证和授权)可以很好地满足基本安全要求。不过,需要对收到的客户数据作进一步的审查,以便将安全边界从常用的设计元素扩展到应用程序代码。为了满足这一要求,我提供了一个新的安全设计框架,它保护了两类常见的漏洞:操作篡改和参数操纵(也称为 数据篡改)。

我将在表示层引入这个框架,负责对由浏览器发送的静态数据进行安全检查。这个框架还检测来自浏览器的操作事件,并为 Web 应用程序提供简单的浏览控制。

更多安全性的要求

考虑恶意用户用代理工具劫持由浏览器发送的数据这一情况。后果可能是严重的,因为服务器会处理规定之外的数据。下面的场景更详细地展示了 Web 应用程序的漏洞。

Web 页浏览

Web 页浏览控制属于应用程序的访问策略。访问策略必须指定特定用户角色可能的浏览路径,以防止恶意用户浏览他或她无权浏览的某一页(例如,通过书签)。

HTML 标记注入

与页浏览控制类似,当恶意用户试图在服务器将数据发回浏览器之前修改 HTML 内容时就会发生HTML 标记注入。HTML 标记篡改可能包含超链接、提交按钮或者其他表单标记。例如,如果一个 Web 页包含两个提交按钮,并根据特定条件只显示一个按钮,恶意用户可以使用代理工具在 Web 页显示在浏览器中之前在其中加上第二个按钮。这样用户就可以向他或她无权访问的内容提交操作。

参数操纵

参数操纵(即数据篡改)使恶意用户可以改变浏览器与 Web 应用服务器之间发送的数据。用户通常可以修改包含 URL 查询字符串和隐藏字段的参数。例如,如果 Web 页包含一个表示客户 ID 的隐藏字段,那么恶意用户就可以在浏览器将数据发送给服务器之前改变隐藏字段的值。

我在这里介绍的解决方案会处理 Web 应用程序漏洞,将对应用程序代码的影响降至最低,与应用程序代码松散耦合,而且可以根据将来的需求而扩展。


使用这个框架

可以在以下情况下使用这个保护框架:

  • Web 应用程序/Struts 框架要求简单的浏览控制。
  • Web 应用程序需要确认用户操作事件(如返回和刷新按钮)。
  • Web 应用程序要求保护页面静态数据中的漏洞,如操作、链接、按钮和隐藏字段。


模型结构

为了防止恶意用户劫持 Web 页中的 HTML 静态内容,这个框架使用了服务器端验证技术。这个框架的程序模型如 图 1 所示,它描述了技术设计。


图 1. 保护框架的类图
保护框架的类图

Processor

Processor 定义了保护建模为 ActionParams 的用户操作的操作。它也是处理客户请求的单元素对象。ProcessorParamsRepository 注册用户操作,并生成一个引用代码。它通过引用代码验证通过 http 请求发送的用户操作(Post、Get 和 Put)。

ParamsRepository

ParamsRepository 维护对所有 ActionParams 对象的引用。它提供了对在 http 会话中获取和存储 ActionParams 的基本访问操作,并提供了一个生成引用代码的机制。

ActionParams

ActionParams 定义了对所有所支持的查询操作和静态数据通用的接口。它实现了一个 validate 操作以验证查询操作和静态数据。可以扩展 ActionParams 以支持应用程序特有的需求。

FormActionParams

FormActionParams 扩展了 ActionParams 以支持 HTML 表单操作和静态(隐藏)字段。它还覆盖了 validate 方法以支持表单操作验证。


框架对象的合作

本节描述客户机与框架交互以注册和验证用户操作的过程。

客户机注册要受到数据保护的静态内容

首先,客户机创建并向 Processor 传递 ActionParams 对象的一个实例。然后 ProcessorParamRepository 交互以存储 ActionParams 实例,并向客户机返回一个引用代码,如 图 2 所示。


图 2. 客户机注册要保护的数据
客户机注册要保护的数据

服务器验证来自客户机的数据

然后 Processor 将特定 ActionParams 的引用代码从客户机转发给 ParamRepositoryProcessorActionParams 实例交互以验证用户请求(请参阅 图 3)。


图 3. 服务器验证用户操作
服务器验证用户操作

实现思路

现在,我将讨论在实现这个框架时需要知道的设计细节。

代码惟一性

为每个 ActionParams 生成的引用代码必须在 Web 页中是惟一的,并且应当可以在不同的页面间重复。不过,如果要扩展这个框架以支持双击检测(请参阅 其他扩展),那么引用代码必须在应用程序中是惟一的。

代码生命周期

图 4 描绘了引用代码的生命周期。


图 4. 引用代码生命周期
引用代码生命周期

来自浏览器的每一个 JSP 请求都会生成一组新的引用代码并将它们存储到 http 会话中。当用户向服务器提交请求时,一个引用代码会映射到会话中的 ActionParams。当 Processor 完成了验证后,它会删除 http 会话中的所有代码。

异常处理

这个框架不会向调用者抛出任何异常。可以根据应用程序的需要处理返回的错误代码。一般来说,应用程序应当在发现任何攻击时,中止用户会话并返回到欢迎/登录页。

加密方式

ParamsRepository 用 http 会话存储受保护的信息。还可以加密受保护的数据并将它直接到存储到 Web 页中。向服务器提交数据时,也发送加密的数据。Processor 将解密数据并验证它是否是真正的参数。这种方式会减小会话的大小,不过,它使性能降低很多。


场景分析

下面,我将描述需要保护 JSP 中静态内容的不同场景。然后展示这个保护框架如何在运行时防止漏洞。这些场景可以使您更好地理解这个框架。

场景 1:JavaServer Page scriptlet

为了定义所有超链接和表单隐藏字段的 JSP scriptlet,必须:

  1. 定义每一个 Processor JSP scriptlet 的操作和参数为 ActionParams
  2. 为每一个超链接定义一个以 ActionParams 为参数的 Processor JSP scriptlet,如 图 5 中的代码所示。
    图 5. 保护超链接的 scriptlet
    保护超链接的 scriptlet
  3. 为隐藏字段定义一个以 ActionParams 为参数的 Processor JSP scriptlet,如 图 6 中的代码所示。
    图 6. 保护隐藏字段的 scriptlet
    保护隐藏字段的 scriptlet

服务器装载 JSP 时,它执行这些 scriptlet 并调用 ProcessorProcessor 将受保护的数据存储到 http 会话中的 ParamsRepository 中。然后 Processor 向 Web 页返回引用代码并显示为超链接参数或者隐藏参数。引用代码见 图 7 (红色部分)。


图 7. 得到的 HTML
得到的 HTML

场景 2:在正常情况下验证用户操作

在这个场景中,用户单击 Web 页面中的 Submit,然后:

  1. 服务器上的处理程序(如 Struts 中的 RequestProcessor 或者 ActionServlet)调用 Processor
  2. 然后 Processor 从 http 请求中提取引用代码并发送给 ParamsRepository。由于用户单击了 Submit 按钮,所以浏览器向服务器发送引用代码 x2w3e (请参阅 图 7)。
  3. ParamsRepository 查找匹配的 ActionParam 实例并将它返回给 Processor,如 图 8 所示。
    图 8. 存储在 ParamsRepository 中的 ActionParam
    存储在 ParamsRepository 中的 ActionParam
  4. 然后 Processor 用收到的 ActionParams 验证用户提交的操作。这个场景的 ActionParams 实例是一个 FormActionParams,并调用了 FormActionParamsvalidate 方法。由于发送给服务器的数据没有改变,因此 Processor 将返回成功的代码,如 图 9 所示。
    图 9. 用 ParamsRepository 中的 ActionParams 验证 FormActionParams
    用 ParamsRepository 中的 ActionParams 验证 FormActionParams

场景 3:验证用户操作是否篡改数据

在这里,当用户单击 Web 页中的 Submit 时:

  1. 用户在浏览器将请求发送给服务器之前,将隐藏值从 12345 改为 XYZ
  2. 重复 场景 2 中的第 2 步到第 4 步。
  3. Processor 查明用户请求中的参数与 ParamsRepositoryFormActionParams 实例中的不一样。Processor 返回错误代码。

图 10 所示,ActionParams 包含一个无效的参数 ID XYZProcessor 将其与 ParamsRespository 中的有效参数 ID 12345 进行比较。


图 10. Processor 验证收到的 ActionParams
Processor 验证收到的 ActionParams

这种验证防止用操作/参数操纵和 HTML 标记注入进行篡改,因为对操作/参数的任何修改都不会通过验证。

场景 4:验证用户操作是否修改引用代码

在这种场景中,当用户单击 Web 页上的 Submit 时:

  1. 用户在浏览器将请求发送给服务器之前,将隐藏字段标记中的引用代码值从 x2w3e 修改为 hackValue
  2. 服务器端的处理程序(例如 Struts 中的 RequestProcessor 或者 ActionServlet)会调用 Processor
  3. Processor 从 http 请求中提取引用代码并发送给 ParamsRepository。由于用户修改了值,所以浏览器向服务器发送的引用代码是 hackValue
  4. ParamsRepository 无法找到匹配的 ActionParams 实例。
  5. 图 11 所示,Processor 无法在 ParamsRepository 中找到收到的代码。
    图 11. Processor 无法找到收到的引用代码
    Processor 无法找到收到的引用代码
  6. Processor 返回一个错误代码。
  7. 由于 ParamsRepository 规定了特定 Web 页上可以进行的操作,所以这个保护框架会拒绝发送给它的所有不能识别的操作。这防止了 Web 浏览劫持(如书签)。

场景 5:验证用户操作是否没有代码

最后一种情况,当用户单击 Web 页中的 Submit 时:

  1. 用户在浏览器向服务器发送请求之前,删除了引用代码的隐藏字段。
  2. 服务器端的处理程序(例如 Struts 中的 RequestProcessor 或者 ActionServlet)调用 Processor
  3. Processor 确定从请求中不能提取代码并返回一个错误代码。


示例代码

下面的代码展示了如何在 Java 中实现这个框架。

ParamsRepository

storeParams 方法将 ActionParams 存储到 http 会话里的一个映射中。代码参数标识特定的 ActionParams 实例。因为实现是与调用者隔离的,所以可以用其他类型的实现存储 ActionParams,如 图 12 所示。


图 12. storeParams 方法实现
storeParams 方法实现

retrieveParams 方法根据引用代码返回一个 ActionParams 实例,如 图 13 所示。


图 13. retrieveParams 方法实现
retrieveParams 方法实现

ActionParams

validate 方法可以让 ActionParams 用另一个实例验证自身。它将操作和参数验证委派给不同的方法,如 图 14 所示。


图 14. validate 方法实现
validate 方法实现

FormActionParams

FormActionParams 覆盖了 doValidateParams 方法以验证表单中的静态隐藏字段(请参阅 图 15)。


图 15. doValidateParams 方法
doValidateParams 方法

Processor

Processor 调用 verifyAction 方法以验证用户操作,如果验证失败,它就返回错误代码。表 1 描述了会让 Processor 发出错误代码的可能场景。


表 1. Processor 对于以下场景生成错误代码

场景 错误代码 原因
检查 http 请求中是否缺少引用代码。 CODEMISSING_ERR 用户向服务器发送未知请求。
verifyWithRepository 方法检查 ParamsRepository 中是否有代码。 INVALIDCODE_ERR 用户向服务器发送以前做过书签的请求。
检查用户是否篡改过 http 请求中的静态数据。 DATATAMPERING_ERR 用户修改了发送给服务器的数据。


verifyAction 方法可以用存储在信息库中的 ActionParams 验证用户操作(请参阅 图 16)。


图 16. 验证操作
验证操作

Struts

可以容易地用 Struts 标记库集成这个保护框架,使这个框架的实现变为透明的。要在 Struts 中集成这个框架,必须继承以下 Struts 标记库。

表单和隐藏标记库

为了保护表单和隐藏字段,要修改 Struts 的 FormTagHiddenTag 以调用 Processor。创建一个子类,它:

  • 扩展 Struts HiddenTag 类,收集所有隐藏字段参数并将它们存储到 pageContext 属性中。
  • 扩展 Struts FormTag 类,覆盖 doAfterBody 方法以向 Processor 注册 pageContext 属性和表单操作。返回的引用代码输出作为隐藏字段(请参阅 图 17)。


图 17. FormTag 的 doAfterBody 方法
FormTag 的 doAfterBody 方法

链接标记库

为了保护链接,可以修改 Struts LinkTag 以调用 Processor。 创建一个子类,它:

扩展 Struts LinkTag 类,覆盖 calculateURL 方法以填充 ActionParams 对象并向 Processor 注册。返回的引用代码附加到 URL 的最后,如 图 18 所示。


图 18. calculateURL 方法
calculateURL 方法

将保护框架集成到 Struts 后,只要在 JSP 中使用 Struts 标记库,就可以自动应用这个框架。


其他扩展

只要扩展 ActionParams,这个框架就可以支持其他类型的保护。类似于 FormActionParams,可以对 ActionParams 扩展下拉框保护,用一组有效的下拉值验证所选的值。通过维护以前处理的代码值,这个框架可以用同步的用户请求探测双击事件。如果收到具有相同代码值的请求,那么可以将以前的响应指定给这个请求。


结束语

本文描述了常见的 Web 应用程序攻击类型,并提供了一个解决这些问题的框架。这个框架覆盖了 Web 应用程序的逻辑安全方面,并保护了 Web 页中的静态数据。虽然这个框架可以减少受攻击的危险,但是设计者应当反复分析应用程序体系结构并找出所有可能的漏洞。这可让您增强或者修改这个框架以防止新的攻击形式。


参考资料

学习


获得产品和技术


讨论

  • Cert.org :跟踪关于安全漏洞和策略的最新消息。

  • BugTraq :在这个公共邮件列表中讨论漏洞和安全隐患。

  • developerWorks blogs :参与 developerWorks 社区。