门户应用程序非常适用于从多个源提取信息以及为包含门户Web应用程序的portlet提供应用服务。对于用户,portlet应用程序是独立的实体,类似于桌面上的窗口应用程序。如果在一个窗口应用程序中执行一项操作会导致其他所有应用程序中的内容被刷新,那又会怎么样呢?这就是当前大多数门户的情况。在一个portlet中通过页面流进行转移会导致整个Web页面被刷新,包括该页面上的其他所有portlet。
为了避免出现这种有时不希望有的行为,Web开发人员采用了所谓Ajax-风格的编程方法。Ajax即异步Java和XML(Asynchronous Java and XML),它是一个技术的集合,包括用于创建交互式Web应用程序的XHTML、CSS、JavaScript、DOM和XmlHttpRequest对象。本文将说明在BEA WebLogic Portal环境中使用Ajax编程方法的基本原理,并提供了一些最佳实践和建议,以避免新手Ajax程序员经常会犯的许多错误。
Ajax简介
考虑一个基于一些用户标准(比如街道地址、城市和州,)来绘制街道地图的Web应用程序。这类应用程序在用户界面中已经存在很多年了,并且很少有所改变。用户输入一个地址,然后单击一个按钮,页面中心就会显示周边区域的地图。用户通常需要缩小和放大以更清楚地显示周围区域,或者需要把地图向左或向右稍作移动,以找到一些能够帮助他们进行定位的主要街道或地界标。访问maps.yahoo.com或mapquest.com,便可以获得这种体验。来吧,试试这个地址:100 East Wacker Drive Chicago, IL
最终,您将看到一幅芝加哥市区的地图,中心是 Wacker和Michigan大街���在右方,您将看到一系列缩放级别,您可以从中选择一个。当选定一个缩放级别之后,会出现什么情况呢?整个页面将会刷新,这会花费相当长一段时间。现在,当您把地图向左或向右移动时,又会出现什么情况呢?您不可避免地会刷新整个页面,而整个地图需要再次下载。页面大小通常是75到100k,而平均的返回时间大约是3到10秒钟,这取决于您的网络连接速度。
现在使用Google Maps进行同样的尝试,这是一个完全使用Ajax技术的站点。输入地址,然后单击Search。页面将完全显示出来。现在进行缩放。注意,地图之外的页面内容不会刷新。下面列出了这背后所发生的事情:
用户单击缩放控件。
缩放控件调用一个JavaScript方法。
JavaScript方法调用服务器来请求新的地图信息。
服务器创建新的图像,然后将其发回给浏览器。
浏览器使用新的图像数据替换现有的图像数据。
用户在旧图像的位置看到新图像。
注意,地图上没有指示东西南北的箭头。用户如何与地图进行交互呢?只需像在一个滚动窗口中那样进行拖拉即可。试着单击地图的中心,然后把地图向左方拖动。注意拖动地图时地图是如何响应的!下面列出这背后所发生的事情:
用户把地图向左方拖动。
Web页面使用它所下载的图像信息,而用户等待着页面根据客户端已经存在的数据重新绘制地图。
如果预先没有检索到图像数据,浏览器将从服务器获得它所需要的附加信息(新出现的地图区域)。
新的数据被添加至现有数据,并在浏览器中重新绘制出来。
用户看见地图向右方滚动,就像是一幅真正的大图那样。
下面是使用Ajax技术所带来的好处:
客户端与服务器之间传输的数据量大大减少。
可感知的系统响应时间大幅度缩短。
由于反馈及时,用户感觉对应用程序的控制更加得心应手。
用户喜欢这个站点,因为使用它可以提高他们的工作效率。
用户将会再次访问站点,这样其竞争对手的客户就会减少。
上面对于Ajax风格的用户界面的演示令人印象深刻。(如果在使用Google Maps数天之后,您还不相信其竞争对手已经恼羞成 怒,那么您就不必阅读本文下面的内容了。)
有关Ajax的完整介绍,请阅读An Introduction to Ajax(中文版,dev2dev,2005年11月)。
Ajax所解决的门户问题
考虑那些大量使用Java applet而且希望利用其现有资产创建门户的潜在客户。把现有applet和其他页面流包装到portlet容器中是一件很简单的事情,但是也要考虑到进行门户测试时会出现哪些问题。例如,某个porlet中的一个动作会导致刷新以及随后的重新加载,并重新初始化页面上其他所有基于applet的 portlet。如果所讨论的applet具有后端连接,那么portlet的重新初始化将导致服务器丢弃现有连接,然后强制applet重新进行连接,这不仅加重了服务器的负担,而且会使门户用户看到几秒钟的“静止时间”,在这段时间内,基于applet的portlet必须保持灰��,一直到它们完成初始化为止。
这显然是一个潜在的瑕疵。告诉客户们重写所有的applet,因为基于JSP的应用程序并非有用的响应,尤其是对于已经在现有资产中投入大量资源的客户来说。此外,门户必须帮助客户包装现有的应用程序,而不是强迫他们重写整个系统。
我们需要一种让单个portlet在不引起页面刷新的情况下进行操作或获得新数据的方法。虽然这显然存在一些不利之处(我将在本文后面讨论这些),但是在这种情况下,把避免页面刷新作为使用门户的进入屏障是完全有必要的。
注:对于这个问题,一个可行的解决方案是iframes,也就是inline frames,一种基于浏览器的机制,它可以使屏幕的一块区域变为独立的实体,当页面重新加载时它不会刷新。使用类似于Ajax编程中所使用的技术,我们可以使用XML-RPC进行服务器调用,从而获取数据并将其加载到DOM文档中。Apple的开发者Web站点上有一篇文章非常好地总结了这种方法的优点和缺点。我确信,关于为什么iframes更好或Ajax更好,双方的支持者已经进行过精彩的辩论,但是Ajax已经流行开来,而iframes则没有。因此,本文只介绍了Ajax,而没有就iframes进行讨论。
用例
在下列情形下,Ajax技术很有用处:
门户中使用了一个或多个基于applet的portlet。例子:参见前面内容中描述的场景。
portlet��要定期刷新其数据或重绘其内容。例子:一个带有股票价值表的可执行面板,这些值每分钟都要更新。
使用Portlet间通信(Inter- Communication,IPC)而不能刷新整个Web页面。例子:一个列出股票名称的portlet需要更新另一个用于简要描述这些股票的当前状态(比如股票的当前价格、最高价格和最低价格)的portlet。
页面包含大量通常为静态的数据,这样页面操作只需要置换少量数据。例子:Google地图。
一个portlet需要基于在此portlet中的其他地方所做的选择来检索一个有限的数据集。例子:一个表单有3个组合框:State、City和ZIP。当用户选择一个州时,该州所有的城市名称都将出现在City组合框中。然后,用户从City组合框中选择一个城市,接着ZIP组合框中就会显示该城市所有有效的邮政编码。
示例架构
Ajax技术的核心是Web浏览器对一些负责提供信息的Web服务的调用。如何为该解决方案设计架构呢?有3种值得考虑的架构。第一种架构使用Web浏览器作为集成点,出于随之而来的安全考虑和浏览器方面的问题,该架构存在问题。第二种解决方案使用代理来获取分布的资源,这消除了安全问题,但是添加了一个单点故障。第三种架构使用企业服务总线(enterprise service bus,ESB)来消除单点故障,并为Web服务和Ajax技术的蓬勃发展提供了最适宜的环境。
以浏览器为中心
在以浏览器为中心的架构中,Web浏览器成为联系远程系统的中心点,如图1所示。数据从各个远程系统中获得,然后在浏览器处的JavaScript中进行整理和排序。
图 1.以浏览器为中心的架构
这种架构是最自然的设计,它具有多处设计缺陷:
JavaScript是一种糟糕的集成语言/环境。Java在各个方面都要比它好得多。
浏览器必须通过每个系统的身份验证,而且可能使用不同的方法。
缓存静态信息的能力很差,这会导致整个系统出现很长的延迟,这首先就失去了使用Ajax方法的意义!
代理服务
如图2所示,代理服务消除了第一种架构的所有缺陷,但是它也有自己的不足之处。它导致出现了一个系统单点故障。当然,Web服务器可以作为带有硬件负载平衡器的集群的一部分。这无疑可以解决这个问题。
图 2. 代理服务架构
尽管这种架构比起以浏览器为中心的架构已经有了巨大的改进,还可以做得更好。我们已经为Web开发人员提供了一个非常好的环境,但是这很可能为后端系统开发人员带来不便。对远程系统进行身份验证以及数据整理仍然是必要的,但是至少后端程序员应该有更多可使用的工具来迎接挑战。
企业服务总线
图3与前面的两幅图又有所不同,因为企业服务总线(ESB)架构是一种逻辑架构。服务可以位于网络上的任意位置,数据源可以被抽象到(可插入到ESB中的)服务中。ESB为后端系统开发人员处理了他们通常必须做的工作,比如服务身份验证、数据转换、协议转换和可靠性特征。ESB可以扩展到带有硬件负载平衡��的计算机集群上,这提供了代理服务架构所具有的优点。
图 3.企业服务总线
为了说明在门户中对Ajax应用程序使用ESB的优点,考虑对于每次Ajax调用都要引用一个Web服务的情况。对于小型门户来说,Web服务的数量可能相对较少,譬如说几十个。但是随着门户的增长,将会引入更多的Web服务。大量Web服务的添加与Ajax没有关系,但是与SOA的实现有着密切的联系。这是一个ESB的经典用例。尽管从严格意义上来说,它对于Ajax实现的操作不是必要的,但是它还是带来了几个好处:
ESB消除了对ProxyServlet类(稍后将会介绍)的需求,因为代理服务更好地完成了同样的工作。
ESB支持监控和管理单个Web服务,同时无需在每个Web服务中增加额外的代码。这包括监控服务水平协议(SLA)和报告违反情况。
ESB可以保护Ajax代码不会被外部提供者修改。如果外部提供者以一种无需修改门户用户界面但需要修改Ajax调用代码的方式修改了 WSDL,那么就有可能在ESB的代理服务中修改配置,而不用修改Ajax代码。为什么这样做比修改Ajax代码更好呢?我认为,在ESB中代理服务的配置中进行小的改动比起在JavaScript中对每个到相关服务的引用进行修改更不容易出错。此外,对于同样的信息(比如��票报价查询)还可以使用多个外部提供者,但是每个提供者具有不同的调用接口并返回XML。如果某个提供者出现故障或者只是为了进行负载平衡,使用多个提供者就很有利了。在ESB中可以轻松实现这一点,但是在JavaScript中这几乎是不可能的。
在这里的例子中,我之所以使用了以浏览器为中心的架构和代理服务架构,是因为作为例子来说,它们比较简单,但是我希望能够使您相信,Ajax是体现ESB优点的非常不错的方式。
对门户使用的影响
当在门户内采用Ajax编程方法时,出现了几个问题。这些问题中的大多数与门户的生命周期以及如何/何时挑选用户信息有关。具体来说,门户要使用诸如用户在哪里单击之类的信息来决定向其显示何种类型的相关信息。WebLogic Portal具有一种叫做campaign的特性,它允许门户设计人员基于用户个人信息指定对用户的有目的广告宣传。用户个人信息中包括用户的页面历史,即,用户过去点击过的页面。门户在页面刷新时收集这类信息,所以如果用户从未刷新页面,门户就无法(容易地)自动收集用户信息。
考虑Ajax编程可能对门户产生的以下副作用:
不可跟踪性使campaign得不到有效使用(参见上面的内容)。
页面刷新重置了DOM树,所以当用户最终刷新页面时,所有对使用Ajax修改过的portlet的更新都会丢失。对portlet内容的修改将会丢失,还原为初始的页面状态。
JSP中的所有JavaScript都是整个页面共用的。考虑一个包含在每个portlet中的JavaScript片断。当呈现最终的HTML页面时,JavaScript片断将在包含它的每个portlet中重复使用。类似地,如果已经将两个portlet嵌入了 JavaScript,而且每个portlet的脚本都有一个名为getData()的方法,那么最终的HTML页面将会有两个不同的getData() 函数定义。调用该函数时很可能导致调用不正确的方法。如果有两个名为isOK()的变量,也会出现同样的情况。
使用适当的编码风格可以防止这些问题的发生,比如给所有变量和函数取独有的名称,或者使用cookies来保存portlet中使用的当前数据。
一种最佳实践是在所有脚本中使用独有的变量和函数名称,具体做法是在每个变量和函数的名称前加上包含它们的portlet的名称。
浏览器战争尚未结束,专有浏览器和基于标准的浏览器之间的战争仍在延续。Ajax相当有趣的一点是,其中有一半是标准(XHTML、XSLT、 JavaScript/ECMAScript、DOM和Web services)驱动的。但是其核心技术——XmlHttpRequest对象——来自微软。
下面列出了在进行跨浏览器的Ajax编程时要注意的一些重要的常见错误,以及如何避免这些陷阱。
安全性
XmlHttpRequest对象直接把浏览器连接到一台远程主机,要么是加载页面的Web服务器,要么是从另一个完全不同的服务器。正如您所想像的,这里为恶意软件提供了大量的机会。例如,一段恶意的JavaScript可能等着用户输入口令字段,然后把口令传递给一个远程浏览器,而用户却不知道,甚至还没有单击页面上的提交按钮。如果把口令换为信用卡号码,事情就变得更加有趣了。
为了避免这种风险,Mozilla拒绝到为Web页面提供服务的主机之外的任意主机的连接。用户不会看到错误消息,因为它根本就不会运行!
Internet Explorer (IE)采用另一种方法。当要求连接到远程主机时,将使用一个对话框通知用户,而用户可以决定执行什么操作。但是要注意,该对话框不会告诉用户要连接到哪个站点,所以用户缺乏可以用于做出决策的信息。
图 4. 没有有用的信息!
这个问题的解决方案是使用一个Java servlet作为到Web服务的代理。该servlet获得所有的参数,并把它们传递给远程服务,接着将响应返回给Web站点。通过让servlet运行在��建Web页面的Web服务器上,Mozilla就会认为服务是本地的。注意,这是企业服务总线(比如AquaLogic Service Bus)的一个绝好用例。
使用XmlHttpRequest对象
包含 XmlHttpRequest对象的XmlHttp库最初是随Internet Explorer 4一起发行的ActiveX控件。Mozilla包含一个兼容的函数库,所以没有什么好担心的。我仍然推荐使用一个开源库,比如Sarissa或DWR。然而,它们在将XML数据传递到对象的方式上存在着细微的区别。
更新DOM节点
Mozilla和IE之间最令人恼火的区别就是Web页面中对DOM(Document Object Model,文档对象模型)的处理。大多数函数的工作方式是一样的(至少在DOM Level 2上),但是仍然有很多值得注意的地方。下面给出两个例子。
innerHtml与innerText的使用
当使用新的动态内容更新<div>标签时,IE用户有两个选择:可以更新节点的innerText或innerHTML。二者的区别很细微,但是却能在信息的显示方面造成很大的差别,尤其是当要显示的文本是XML格式或者包含HTML实体(比如尖括号或&符号)时。使用 innerHTML时假定内容与标签是一起放入的(不管内容是什么)。假设我们试着把以下文本放入一个节点中:
var txt = <b>This is a test</b>
document.getElementById('mydivtext').innerText = txt;
document.getElementById('mydivhtml').innerHTML = txt;
<div id="mydivhtml">部分在浏览器中看起来如下:
This is a test
<div id="mydivtext">部分看起来则是下面这样:
<b>This is a test</b>
基本上,innerText节点对输入字符串进行转义,这样显示在用户面前的就是内容���来的样子。但是Mozilla在DOM节点上不支持 innerText属性,所以更好的方法是用户亲自对文本进行转义,并始终使用innerHTML。Sarissa有一个帮助器函数用于实现这一项功能:
document.getElementById('mydivtext').innerHTML =
Sarissa.escape("<b>This is a test</b>")
我们将得到同样的结果,如下:
document.getElementById('mydivtext').innerText =
"<b>This is a test</b>")
在门户中使用独有名称
当某个门户页面由WebLogic Portal(或任何门户)进行解析时,每个portlet均作为完整的Web页面放在HTML文档中,包括<body>标签(有时甚至包括<html>标签)。因此,如果在每个portlet中始终以相同的ID来命名<div>标签(理论上来说,这似乎是使编程标准化的一种良好方法),那么将获得不正确的结果。考虑如果有两个ID为“result_data”的元素,那么解析后的门户页面将会是什么样子。
// Outer portal shell
<html>
// First portlet
...
<div id="result_data"></div>
...
// Second portlet
...
<div id="result_data"></div>
...
</html>
现在,进行一次如下的调用:
document.getElementById('result_data').innerText = "stuff";
哪个元素将被更新呢?答案基本上会随浏览器的不同而不同,但是一般的答案就是“第一个”。
因此,这里的最佳实践是使用portlet名称作为HTML标签中所有ID的前缀。
结束语
传统的Web应用程序已经无法满足客户的要求。由于Google之类的公司提供了更��、更快和交互性更强的Web站点,客户的期望值变得越来越高。构建使客户可以提高工作速度和效率的用户界面始终是一项战略性挑战。
Ajax编程是Web应用程序交互方面的新的事实标准,它为紧密耦合的数据和应用程序筒仓提供了部分解决方案。特别是在与BEA WebLogic平台结合使用时,Ajax代表了Web编程的未来方向,并预示着构建具有高度的交互性和响应灵敏度的Web站点的新潮流。
在本系列的第二部分中,我将使用具体的例子说明如何在WebLogic Portal环境中使用Ajax。具体来说,我将演示大量portlet,以说明如何从JavaScript调用Web服务以及使用结果更新页面、如何实现代理servlet来处理对外部Web服务的调用、如何使用Ajax把Web页面嵌入到另一个Web页面中,以及如何更新您自己的可更新数据库表小构件。