UML软件工程组织

结合使用 Ajax 和 WebSphere Portal
Karl Bishop (kfbishop@us.ibm.com), 高级软件工程师, IBM
Doug Phillips (dougep@us.ibm.com), 顾问软件工程师, IBM
 您肯定听说了 Ajax 的一些用法,您可能想知道是否可以在自己的门户应用程序中使用它。当然可以,本文将向您介绍一些入门知识。门户中开销最大的操作之一是刷新页面。您可以使用 Ajax 处理许多用户交互事件,然后将更新应用于页面的各个部分,而无需刷新整个页面。您可以改进门户性能,创建更清晰的总体门户应用程序体系结构,而最重要的是,您的用户会非常高兴具有此类响应的门户。

 引言

本文向您介绍将 Ajax 集成到门户应用程序的方法。因为已经有了几篇对 Ajax 进行一般性介绍的文章(请参见参考资料),所以我们假设您了解 Ajax 的基础知识;也就是说,您已经知道了什么是 Ajax、它的名称的由来、它并不是一项新的技术这一事实,以及 Google 如何将此技术带入全球每个经理和技术专家的思维模式。我们的目的是向您提供有用的信息,这些信息与在门户应用程序中使用 Ajax 相关,所以当 CTO 的办公室里的人员询问您的门户应用程序是否启用了 Ajax 时,您可以理直气壮地说:“当然!”。

所以,如果您已决定将 Ajax 引入门户,则本文所讨论的内容非常值得您关注。尽管本文侧重介绍的是门户应用程序,但是这些技巧通常适用于大多数复杂应用程序。本文还为您准备了后续教程,在此教程中,我们将详细介绍 Ajax Portlet 应用程序的创建。

在我们回到即将开始讨论的主题之前,先简要说明以下内容:您看到或读到的有关 Ajax 的内容所介绍的大部分不是真正的 Ajax;而是 Dynamic HTML 或 DHTML。正确意义的 Ajax 由称为 XMLHttpRequest 的单个 JavaScript 对象组成。该类为服务器和产生的响应提供后台通信通道。承担所有其他任务(包括拖放、DOM 更新、创建样式和各人喜欢做的所有其他事情)的是 DHTML。

为什么 Ajax 和 WebSphere Portal 可以很好地配合?

门户环境中开销最大的操作之一是刷新页面。当用户在页面上单击链接或进行一些其他操作时,门户会处理页面上目标 Portlet 的 actionPerformed() 方法和每个 Portlet 的 doView() 方法。然后,聚合结果,并将整个 HTML 文档向下发送到浏览器。

尽管缓存可以减少大量的开销,但是仍有许多其他问题。您可以使用 Ajax 在后台处理许多用户交互事件,然后更新页面的各个部分,而无需完整的门户刷新周期。此技术通过增加对单个操作的响应大大改进了最终用户体验,同时也显著提高了应用程序的总体性能。在某些环境中,使用 Ajax 可以使应用程序的总体体系结构更清晰。使用二级 Ajax 控制器(如 Servlet 或 Web 服务)可以进行更强大的模型代码分离。

将整个 Ajax 控制器设计应用于应用程序时,您应该使用 Ajax 控制器处理所有基本用户输入操作和分段显示更新。而仅将门户 actionPerformed() 方法用于页面级转换或处理主要状态更改。

为什么 Ajax 和 WebSphere Portal 不能很好地配合?

那么,为什么您不希望在您的富 Internet 应用程序中使用这一新颖的范例呢?所有的技术周刊都认为这是一个好方法,此外,您的上司也通知您使用它,因为它是“业务目标之一”。当然,我们不会叫您不要使用它,但是,我的确想让您知道一些潜在缺陷:

  • 使用多个控制器(例如 Portlet、Servlet 和 Web 服务)会增加应用程序的复杂性。
  • 使用 Ajax 强制客户端处理许多逻辑。
  • JavaScript 很难调试,特别是在跨浏览器环境中更是如此。
  • 可访问性问题和移动设备会产生冗余代码。因为许多屏幕阅读器和其他辅助设备不支持 JavaScript/Ajax,所以您需要提供备选功能。
  • 您的应用程序可能不需要在页面切换之间进行额外的数据更新。

综上所述,您可能确定 Ajax 不适合您,需要改读其他文章。稍等,这只是一个玩笑。朋友,请继续阅读!Ajax 太酷了,如果您不将其添加到自己的应用程序中,您肯定会感到遗憾。

至少您应该慢慢接受它。找到一个可以使用小技巧的应用程序,并将 Ajax 的妙处添加到用户表单或向导中。一旦您入门并了解到做一点努力就可以获得一些有效的用户增强功能,您肯定会准备将一些奇妙的方法实际添加到自己的门户应用程序中。

设计注意事项

将 Ajax 添加到门户应用程序后,您就可以有效地将多个控制器添加到经典 MVC 模式。此决定对强制执行模型逻辑的清晰分离有潜在的好处。不足之处是增加了复杂性,并且不可避免地将控制器分成以下三个部分:

  1. Portlet
  2. Servlet 或 Web 服务
  3. 基于 JavaScript 的客户端
 在门户应用程序中使用 Ajax 的基本前提是需要一个独立控制器。通常情况下,您可以使用 Servlet 执行与 Ajax 客户端的通信。您可以将 Servlet 与 Portlet WAR 文件捆绑在一起,或将其作为独立 Web 应用程序的一部分包括在内。

图 1 显示了潜在的 Ajax 服务器目标。

如果您将 Servlet 与 Portlet WAR 文件捆绑在一起,那么可以在 Servlet 和 Portlet 之间共享会话数据。Servlet、Portlet 和模型代码是紧密耦合的。

 如果您不需要该级别的耦合,并且 Ajax 处理的数据和逻辑不依赖于 Portlet,那么您可以为远程重用创建独立的 Servlet 或 Web 服务。

图 1. Ajax 服务器目标的可能性

 
 Ajax 工具包

实现 Ajax 的缺陷之一是难以编写良好的跨浏览器的 JavaScript。有许多 JavaScript 和 DHTML 工具包可以提供 Ajax 抽象。事实上,由于要测试的内容太多,所以无法确定哪一个工具包最适合您。对于所有开放源代码项目,在接下来的两年里可能将推出新的工具包。

我们使用的最有前途、设计合理的一些工具包是:Dojo、Rico 和 DWR(请参见参考资料)。DoJo 是首选工具包,因为它具有类似于方面的高级体系结构。DWR 或 Direct Web Rendering 提供了从客户端 Javascript 引用基于主机的 JavaBeans 的方便机制。由于提供了许多其他好的工具包,所以您需要确定哪些适合您。

回页首

 将 Ajax 添加到 Portlet 应用程序

要在门户应用程序中实现 Ajax,您需要按照以下几个简单步骤进行操作。下面的讨论假设您将 Ajax Servlet 与 Portlet WAR 文件捆绑在一起。

  • 创建和定义 Ajax Servlet。
  • 定义指向 Servlet 的 JavaScript 引用变量。
  • 加载任何外部 JavaScript 文件。
  • 实现 Ajax 框架。
  • 创建和定义 Ajax Servlet

将 Servlet 与 Portlet WAR 文件捆绑在一起的过程非常简单;不过,即使经验丰富的 Portlet 开发人员也并非总是能够确保所有的细节都正确无误。所以,下面是全部易忽略的细节。

在 web.xml 文件中定义 Servlet,如清单 1 所示 包括 Servlet JAR 文件或类。

清单 1. web.xml 中的 Servlet 映射

<servlet>
<servlet-name>MyAjaxServlet</servlet-name>
<display-name>MyAjaxServlet</display-name>
<description></description>
<servlet-class>
com.ibm.ajax.MyAjaxServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyAjaxServlet</servlet-name>
<url-pattern>/Ajax</url-pattern>
</servlet-mapping>

定义指向 Servlet 的 JavaScript 引用

您需要在 JSP 文件中定义全局引用(参见清单 2),以便可以对 Portlet 请求库进行访问。定义全局变量后,包括的任何 JavaScript 都可以安全地使用它,以指向 Servlet。

 清单 2. 指向 Servlet 的全局引用。

<script type="text/javaScript">
var PATH = "<%= request.getContextPath() %>";
var Ajax_SERVLET = PATH + "/Ajax";
</script>

加载任何外部 JavaScript 文件

对于添加到 Portlet 页面的任何外部资源,您必须对 URL 进行编码,并设置基本上下文,如清单 3 所示。

清单 3. 对 URL 进行编码并设置基本上下文的脚本。

<script type="text/javascript"
src="<%=renderResponse.encodeURL(
renderRequest.getContextPath() + "/js/myajax.js?v1.1.2")%>" >
</script>

提示: 通过在 JavaScript 参数上使用字符串参数,可以使浏览器在每次加载时强制执行缓存刷新。如果您的 JavaScript 可能频繁更改,则此刷新将使浏览器不能使用旧的缓存代码。此示例使用的是版本 ID(?v1.1.2),但是也可以使用任何字符串。

实现 Ajax 框架

使 Ajax 执行其奇妙方法的过程涉及到几个样板文件操作。我们在此处向您简要介绍一下。以后您会看到相关代码说明描述,并且可以按教程说明进行操作。

  • 创建全局 XMLHttpRequest 对象变量。因为所有通信都是异步的,所以您必须为每个 Ajax 事件定义唯一的变量。
  • 定义触发该进程的事件。通常,您可以在输入标记中使用 JavaScript 事件。例如:<input onChange='eventHandlerFunction()' ... >
    定义一个函数以处理事件;特别是,要实现以下任务:
  • 实例化 XMLHttpRequest (xhr) 对象变量。此变量的细节是特定于浏览器的,我们将在后续教程中对此进行介绍。
  • 设置 xhr 回调函数。 xhr.onreadystatechange()
  • 设置 Servlet、类型和参数。 xhr.open(), xhr.setRequestHandler(), and xhr.send()
  • 定义回调函数,以处理通信状态和响应数据。
  • 此函数会处理各种通信状态更改(如调用启动时、建立连接时和接收到响应时)。
  • 响应处理通常涉及到解析返回的 XML(或其他内容)和使用此数据来更新 DOM 树。
 图 2 显示了如何汇总各个部分。

图 2. AJAX 通信事件模型
 

门户特定的注意事项

在门户应用程序中实现 Ajax 时,您应注意几个问题。

全局 JavaScript 变量

通常,在门户应用程序中,避免使用 JavaScript 中的全局变量,因为门户会将几个 Portlet 聚合到单个页面中。全局 JavaScript 变量的命名空间(如清单 4 所示)是一个好的做法,因为您可以保证变量名是唯一的,即使在同一页面上部署相同的 Portlet 两次。

清单 4. 命名空间 JavaScript 变量。

// Global XMLHttpRequest variable
var <portlet:namespace />xhrFieldsRequest;

如果您使用 Ajax 工具包,则抽象层将解决任何命名冲突。

使用 ID 属性

通常在 Ajax 中使用 ID 属性,以快速更新页面的某个部分。因为任何 HTML 标记中的 ID 属性对于 DOM 都是全局的,所以您需要确保它们是唯一的。如果您有重复的 ID 属性,那么结果是不可预知的,但是,通常这不是您希望的结果,并且问题非常难以确定。

为了安全起见,请对所有 ID 属性设置名称空间,即使此操作会使您的代码像在清单 5 中看到的一样难于阅读。

 清单 5. 安全地对 ID 属性设置命名空间。

<h1 id="<portlet:namespace />header">Hello</h1>
<script type="text/javascript">
var x = document.getElementByID
("<portlet:namespace/>header");
x.innerHtml = "GOODBYE!";
</script>

状态维护

在门户中使用 Ajax 调用时,您容易遇到的缺陷是缺乏内在的状态管理。没有什么可以让用户停止在 Portlet 中执行可能导致页面刷新的操作。您需要确保能够重启任何 Ajax 活动,而不依赖于以前的任何状态。尽管能够对 Servlet 使用 Cookies 或 Ajax 调用来检查和存储状态消息,但是要避免依赖于页面的状态。使所有的 Ajax 调用变成原子的。

容易让您失败的另一个状态问题是后退按钮和书签标记的 URL。通常,避免基于 Ajax 的主要状态更改。将它保留到真正的门户 actionPerformed() 调用。

共享会话数据

当您将 Servlet 与门户应用程序捆绑在一起时,您可以在 Servlet 和 Portlet 之间共享会话数据。通常,在共享会话数据时,您希望使用应用程序范围。对于 Servlet,这是正常的会话范围。从 Servlet 访问 Portlet 范围变量需要一个已设置命名空间的特殊名称值,该值基于最初将其部署到门户时设置的 Portlet 的 ID。在部署过程中提取此值非常困难。尽管大多数理论认为,Portlet 范围变量的语法是:

javax.portlet.p.<ID>?<NAME>

其中:

<ID> 是 Portlet 的唯一标识

<NAME> 用于在 Portlet 会话中设置对象的名称。

Action URL

在使用 Ajax 时,处理 Action URL 会非常麻烦。通常您不应在共享的会话中尝试存储 Action URL,因为它们仅对当前的 doView() 有效。尝试使用在会话(来自先前的 doView() 周期)中存储的 ActionURL 将导致不可预期的结果。

您需要将 Action URL 存储到会话的例子是 Ajax 驱动的分页数据表,它包含 Action URL 链接作为数据集的一部分。当用户单击 Next,浏览器会生成一个对 Servlet 的 Ajax 调用。然后,Servlet 从会话中提取下一页数据,并且它必须具有预定义的 Action URL。只需确保 doView() 调用在任何时间都能够处理,存放任何 Action URL 的任何会话数据都能够重新生成。

活动通知

由于聚合的许多信息都被填充到单个页面,所以门户页面通常非常繁忙。因为 Ajax 调用是在后台执行的,并且它们不能触发浏览器上的活动图标,所以您需要提供一致的可视机制来通知用户继续完成某事。否则,他们会感到困惑,不知道应用程序正忙着处理什么操作。(我们的确不希望用户困惑。)

您可以在活动过程中使用浮点 DIV 部分显示或使用浏览器状态栏上的简单消息实现此通知(不过,有些人认为这不是好的形式)。您也可以集成自定义主题扩展,它会在页面上显示任何启用 Ajax 的 Portlet 的一般 Please Wait 消息。

回页首

 结束语

在本文中,我们描述了在门户应用程序中使用 Ajax 的方式和原因。在未来的相关教程中,我们将向您展示如何将各部分组合在一起,以获得支持 Ajax 的数据库管理工具。请继续关注。

 

版权所有:UML软件工程组织