为 WebSphere Application Server Community Edition 开发富 Internet 应用程序

通过 Dojo、Tomcat Comet 支持和 Direct Web Remoting (DWR) 允许应用程序使用 Ajax

本文简单列出了一些 Ajax 功能和 WebSphere® Community Edition 2.1 中包含的框架,并逐步说明了示例 Web 2.0 应用程序的开发工作,该示例应用程序使用了这些框架来简化开发工作。

Manu T. George, 软件工程师, EMC

作者照片Manu T. George 是位于印度班加罗尔的 IBM India Software Labs 的一位软件工程师。他是 Apache Geronimo 和 Apache OpenEJB 项目的参与者,也是 IBM WebSphere Application Server Community Edition Level 3 Support Team 的成员。他在 2001 年获得了 College of Engineering Trivandrum 的应用电子学士学位。


developerWorks 投稿作者

Vamsavardhana Reddy Chillakuru, 顾问软件工程师, EMC

作者照片Vamsavardhana Reddy Chillakuru 又名 Vamsi,是位于印度班加罗尔的 IBM India Software Labs 的一名顾问软件工程师。他是 Apache Geronimo 和 Apache Tuscany 的参与者,也是 IBM WebSphere Application Server Community Edition Level 3 Support Team 的成员。他分别于 1994 年和 1996 年获得了位于印度加尔各答的印度统计研究所 (Indian Statistical Institute) 的统计学学士学位(荣誉)和统计学硕士学位。



2009 年 1 月 08 日

引言

Ajax(异步 JavaScript 和 XML)术语用于表示一组支持创建富 Internet 应用程序 (Rich Internet Application) 的技术。通过使用这些技术,可以创建响应能力强且具有与桌面应用程序类似的丰富用户界面的 Web 应用程序。这些技术允许在后台以异步方式检索数据,而不会影响所显示的页面,而且可以仅请求数据,而不用请求整个 HTML 页面。可以使用现在的浏览器提供的 XmlHttpRequest 或等效对象进行此异步后台通信。

IBM WebSphere Application Server Community Edition v2.1.x(以下称为 Community Edition)的安装包中提供了一些用于开发和承载支持 Ajax 的应用程序的流行框架。在本文中,我们将了解如何配置和使用其中的三个框架,即:

  • Dojo 工具包
  • Direct Web Remoting (DWR)
  • Tomcat Comet 支持

我们还将开发一个简单的 Web 应用程序,其中使用了这些技术来提供得到改善的用户体验。为了遵循本文所述进行操作,您需要 WebSphere Application Server Community Edition v2.1.x。


Dojo 工具包

Dojo 是使用 JavaScript 编写的开源 DHTML 工具集。Dojo 解决了使用 JavaScript 时的一些问题,包括处理浏览器特定的行为。这些行为已经通过 Dojo 工具从用户层面抽象出来了。

另外,还提供了一组可配置小部件,可将其用于快速开发独立于浏览器的动态网页。Dojo 工具集由一组 JavaScript API 组成,包含在 WebSphere Application Server Community Edition v 2.1 中。可以通过上下文根 /dojo 访问此工具集。Dojo 还提供了用于进行异步 XMLHttpRequest 调用的 API,以在不刷新页面的情况下从服务器获取数据。


Direct Web Remoting (DWR)

DWR 允许开发人员通过 JavaScript 代理向客户端公开服务器端的 Java™ 对象。它会为所有公开的 Java™ 对象创建代理,以便从客户端调用这些对象。相应地,DWR 将在服务器中调用对应的 Java 方法,并将响应以 JSON (JavaScript Object Notation) 格式返回到调用方脚本。

您可以配置 DWR 在后台以同步或异步方式呼叫服务器。因此,可以避免与普通 HTTP 请求-响应模式关联的页面刷新。

DWR 还提供了 Reverse Ajax,即用于将信息从服务器以异步方式发送到浏览器的机制。通过这样,服务器可以采用以下方式定期将响应发送到客户端:

  • 轮询:客户端定期轮询服务器。
  • Comet:当客户端发出请求时,服务器将保留响应句柄,写入内容保存在此句柄中。在这种情况下,客户端不需要进行轮询。
  • 合并: 多个响应组合在一起,随客户端作出的下一个请求的响应一起发送。

Tomcat Comet 支持

Comet 术语是 Dojo Foundation 的 Alex Russel 发明的,用于描述通过 HTTP 协议进行的事件驱动的服务器推送机制。在普通 HTTP 通信中,客户端始终通过打开到服务器的连接并发送请求来发起数据传输。服务器将处理此请求,在相同连接上发送响应,然后关闭连接。因此,连接的寿命相对较短。在 Comet 中,服务器将连接保持开放状态,会在出现相关事件时持续通过其写入数据。Tomcat p通过 NIO 和 APR 连接器提供了此支持。BIO 连接器并不提供 Comet 支持。


创建 NIO 连接器

缺省情况下,Community Edition 并不提供预安装的 NIO 连接器。在运行使用 Comet 协议的应用程序前,我们需要从管理控制台创建新 NIO 连接器并将其启动。我们将首先删除在 8080 端口上运行的 BIO 连接器,然后创建在此端口运行的新 NIO 连接器。

请按照以下步骤创建 NIO 连接器:

  1. 启动 Community Edition,并在浏览器中打开 https://localhost:8443/console/
  2. 输入用户名 system,并输入密码 manager。点击 Login,将随即在管理控制台中打开 Welcome 页。
  3. 在左侧导航窗格中,单击 Web Server 链接,以打开 Web Server Manager 页,如图 1 中所示:
    图 1. Web Server Manager
    Web Server Manager 的屏幕截图,显示网络侦听器列表
  4. 删除名为 TomcatWebConnector 的连接器。

  5. 在 Add New 部分,单击 Tomcat NIO Connector 链接,将随即打开图 2 中所示的屏幕。
    图 2. 添加新的 Tomcat NIO 连接器
    添加新 NIO 连接器的窗口的屏幕截图
  6. uniqueName 字段中填写 TomcatNIOConnector 值,并点击 Save,从而创建并启动 NIO 连接器。您的窗口显示应该与图 3 类似。
    图 3. 添加到网络侦听器的 TomcatNIOConnector
    显示网络侦听器列表中的新 NIO 连接器的屏幕截图

开发 Web 应用程序

我们现在已经完成了所有准备工作,可以开始开发 Web 应用程序了。这是一个股票报价器应用程序,每 5 秒钟将使用最新的报价进行一次更新。我们将在 Tomcat 提供的 Comet 实现上使用 DWR 的 Reverse Ajax 功能。为了显示报价,我们将使用 Dojo 工具中提供的一个现成 Dojo 小部件,即 dojox.Grid 小部件。我们还将编写一个 Servlet 来使用 Tomcat 中的 Comet 支持打印股票信息。

编写 Comet Servlet

Apache Tomcat 提供了 org.apache.catalina.CometProcessor 接口,以便编写的所有 Servlet 都能够使用 Comet 支持。此接口定义了在我们的 Servlet 中实现的名为 public void event(CometEvent event) 的单个方法。清单 1 显示了此 Servlet 的两个主要方法:

清单 1:Comet Servlet
	public void init() throws ServletException {
	    super.init();
	    Runnable r = new Runnable() {
	        public void run() {
	            while (!stop) {
                      synchronized (openConnections){
	                List<Stock> stocks = new StockService().getStocks();
		         for (HttpServletResponse response : openConnections) {
		             try {
		               PrintWriter pw = response.getWriter();
		               for (Stock stock : stocks) {
			            pw.println(stock.getSymbol() + ":"
			                + stock.getPrice());
		               }
		               pw.flush();
		             } catch (IOException e) {			
		                e.printStackTrace(System.out);
		             }
                          }
		         }
		         try {
		             Thread.sleep(5000);
		         } catch (InterruptedException e) {
		             e.printStackTrace(System.out);
		         }
		     }
	         }
             };
	     Thread stockThread = new Thread(r);
	     stockThread.setDaemon(true);
	     stockThread.start();
	}

	public void event(CometEvent event) throws IOException, ServletException {
		HttpServletRequest request = event.getHttpServletRequest();
		HttpServletResponse response = event.getHttpServletResponse();

		if (event.getEventType() == CometEvent.EventType.BEGIN) {
                   synchronized (openConnections){
			openConnections.add(response);
                   }
		} else if (event.getEventType() == CometEvent.EventType.ERROR) {
                   synchronized (openConnections){
			openConnections.remove(response);
                   }
		} else if (event.getEventType() == CometEvent.EventType.END) {
                   synchronized (openConnections){
			openConnections.remove(response);
                              }
		} else if (event.getEventType() == CometEvent.EventType.READ) {

		}
          }

init 方法中,我们将实例化获取股票报价的线程,此线程还要将这些报价写入所有正在侦听的客户端,即 openConnections 列表中的所有响应对象。出现四个事件(BEGINERRORENDREAD)中的任意事件时,都会触发 event 方法。我们从作为参数传递的 org.apache.catalina.CometEvent 对象中获得响应。对于 BEGIN 事件,要将响应添加到集合中。对于 ERROREND 事件,我们将从列表中删除响应。

BEGIN 事件代表着该连接处理开始,因此我们要将响应添加到响应列表中。ENDERROR 事件代表着连接结束或出现了错误。有关这些事件的含义的更多信息,请参见 Apache Tomcat 文档

配置 DWR

应用程序硬编码了一组要显示的股票。股票值由 com.dev.trade.service.StockQuoteGenerator 类随机更新。清单 2 显示了这个类的源代码的相关部分。

清单 2:生成股票价格的类
package com.dev.trade.service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Collections;
import com.dev.trade.bo.Stock;

public class StockQuoteGenerator {
	private StockService ss = new StockService();
	private List<Stock>  stocks = null;
	private static StockQuoteGenerator generator = new StockQuoteGenerator();
	private Set<QuoteListener>  
                listeners = Collections.synchronizedSet(new HashSet<QuoteListener> ());
	private volatile boolean contextDestroyed = false;

	private StockQuoteGenerator() {
                Runnable r = new Runnable() {
                    public void run() {
                        try {
                            while (!contextDestroyed) {
                                update(generateStockQuotes());
                                Thread.sleep(5000);
                            }
                        }catch (Exception e) {
                            e.printStackTrace(System.out);
                        }
                    }
                };
                Thread t = new Thread(r);
                t.setDaemon(true);
                t.start();
            }

	public static StockQuoteGenerator getInstance() {
		return generator;
	}

	public void setContextDestroyed(boolean cd) {
		this.contextDestroyed = cd;
	}

	public void addListener(QuoteListener listener) {	
	    listeners.add(listener);	
	}

	private void update(List<Stock>  stocks) {
	    for (QuoteListener listener : listeners) {
	        listener.updateStockQuotes(stocks);
	    }
	}

	private List<Stock>  generateStockQuotes() {
		stocks = ss.getStocks();		
		return stocks;
	}

}

在 StockQuoteGenerator 类(一个单态类)的构造函数中,我们启动了一个新线程,并向该线程分配了一个 Runnable 对象。因此,这个对象的 run 方法将继续执行,直到将 contextDestroyed 变量设置为 true 为止。此变量仅在应用程序上下文要销毁时才会设置为 true。为此,我们将在 web.xml 中注册一个上下文侦听器,即 com.dev.trade.listener.ServletContextListener

generateStockQuotes 方法调用 StockServicegetStocks 方法来获取生成的股票报价。使用 addListener 方法可添加侦听器。这些侦听器实现 com.dev.trade.service.QuoteListener 接口以及相应的 updateStockQuotes 方法。

这些侦听器都是 com.dev.trade.service.StockQuoteListenerImpl 的实例。此类在其构造函数中初始化 DWR 提供的 org.directwebremoting.WebContext 类的实例,然后向 StockQuoteGenerator's 侦听器列表注册自身。清单 3 显示了此类的 updateStockQuotes 方法。

清单 3:向不同客户端推送数据的代码
	public void updateStockQuotes(List<Stock> stocks) {

		ScriptBuffer script = new ScriptBuffer();
		script.appendScript("updateStocks(").appendData(stocks).appendScript(
				");");			
		Collection<ScriptSession> sessions = wctx.getAllScriptSessions();
                              for (ScriptSession session : sessions) {
			if(!session.isInvalidated()){
			    session.addScript(script);
			} else {
				wctx.getAllScriptSessions().remove(session);
			}
		}
	}

此方法将 JavaScript 函数调用传播到所有开放客户端。将会调用函数 updateStocks 并传递股票值。此 JavaScript 方法实际更新我们将显示的 Dojo 网格小部件的模型,然后调用其 update 方法,以让网格显示新数据。DWR 为每个开放股票报价器页创建脚本会话。

请注意,为了使其工作,应该由执行 DWR 代码的同一个线程调用 StockQuoteListenerImpl 类的构造函数。为此,我们将需要配置 DWR,以实例化 StockQuoteListenerImpl 的对象。对于此配置,我们将创建 dwr.xml 文件,并将其放置在应用程序的 WEB-INF 目录中。清单 4 显示了此文件。

清单 4:dwr.xml
<dwr>
  <allow>
    <create creator="new" javascript="Ticker" scope="application">
      <param name="class" value="com.dev.trade.service.StockQuoteListenerImpl"/>
    </create>
    <create creator="new" javascript="StockService" scope="application">
      <param name="class" value="com.dev.trade.service.StockService"/>
    </create>    
    <convert converter="bean" match="com.dev.trade.bo.Stock"/>    
  </allow>
</dwr>

请注意,StockQuoteListenerImpl 已经通过创建程序在 dwr.xml 中公开。我们将此对象的范围设置为 application,因为每个应用程序只需要一个此对象的实例。我们向 JavaScript 公开的另一个类是 com.dev.trade.service.StockService,此类为我们提供了用于获得股票列表的 getStocks 方法。我们还需要将 DWRServlet 添加到应用程序的 web.xml,以便初始化 DWR,并由其将所配置的上述对象的代理向 JavaScript 公开。清单 5 显示了 web.xml 文件。

清单 5:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>brokerage</display-name>
<listener>
    <listener-class>
     com.dev.trade.listener.DwrContextListener
    </listener-class>
</listener>
<servlet>
  <display-name>DWR Servlet</display-name>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
  <init-param>
     <param-name>debug</param-name>
     <param-value>true</param-value>
  </init-param>
   <init-param>
    <param-name>activeReverseAjaxEnabled</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>initApplicationScopeCreatorsAtStartup</param-name>
    <param-value>true</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>  
</servlet>

<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>


<welcome-file-list>
	<welcome-file>/stocks.html</welcome-file>
</welcome-file-list>

</web-app>

activeReverseAjaxEnabled 属性设置为 true,以便 DWR 使用 Comet 来启用 Reverse Ajax。如果设置为 false,则 DWR 将使用合并选项。

initApplicationScopeCreatorsAtStartup 用于在启动 DWR 时初始化所有应用程序范围内的对象。

现在,我们需要在客户端导入两个脚本文件:DWR 客户端引擎和 DWR 为公开的对象 com.dev.trade.service.StockService 创建的 JavaScript 代理。清单 6 显示了此导入对应的代码。

清单 6:在客户端上进行访问
<script type="text/javascript" src="dwr/engine.js"></script>
<script type='text/javascript'
	src='/brokerage/dwr/interface/StockService.js'></script>

我们还需要配置 DWR 客户端引擎,以使用活动 Reverse Ajax,因此我们需要调用 dwr.engine.setActiveReverseAjax(true)

我们可以调用 StockServicegetStocks 方法,以使用 StockService.getStocks({callback:getModel,async:false}) 对要显示的 Dojo 网格小部件进行初始填充。getModel 参数引用将调用的 JavaScript 方法,此调用的结果将作为方法参数传递。

清单 7 所示的方法将返回我们要显示的 Dojo 网格的模型。

清单 7:Web Remoting
	function getModel(stocks) {		
		var data = new Array();
		for ( var i = 0; i < stocks.length; i++) {
			var subData = new Array();
			subData[0] = stocks[i].symbol;
			subData[1] = stocks[i].companyName
			subData[2] = stocks[i].price
			subData[3] = stocks[i].eps
			data[i] = subData;
		}
		updatedModel = new dojox.grid.data.Table(null, data);
		return updatedModel;
	}

配置 Dojo

Community Edition 随附了 Dojo JavaScript 库。其中对一些管理控制台 Portlet 使用了 Dojo。Dojo 打包为在上下文根 /dojo 提供的 Web 应用程序。要在我们的 HTML 客户端页中使用 Dojo,我们需要使用清单 8 中所示的脚本导入 dojo.js 脚本。

清单 8:导入 dojo.js
<script type="text/javascript" src="/dojo/dojo/dojo.js"
	djConfig="isDebug:false, parseOnLoad: true"></script>

导入 Dojo 脚本时,我们可以通过 dojo.require 调用导入其余依赖项。我们要创建的 dojox.Grid 小部件需要模型和结构。其模型代表要显示的数据,而结构代表我们将要如何进行显示。此模型实际上是由数组组成的数组。清单 9 显示了结构定义:

清单 9:模型定义
var view1 = {
		noscroll :false,
		cells : [ [ {
			name :'Symbol',
			width :'auto'
		}, {
			name :'Company Name',
			width :'auto'
		}, {
			name :'Price',
			width :'auto'
		}, {
			name :'Earnings Per Share',
			width :'auto'
		} ] ]
	};

var gridLayout = [ {
		type :'dojox.GridRowView',
		width :'20px'
	}, view1 ];

<div class="tundra" id="stock_grid" dojoType="dojox.Grid" model="updatedModel"
	structure="gridLayout" elasticView="1" defaultHeight="37em"></div>

在此清单中,我们发现带 id="stock_grid"div 封装了实际的网格。此结构由 gridLayout 对象定义,此对象是包含 view1 的数组。

view1 变量也是数组,其中包含提供所有列应该在网格中如何显示的详细信息的 cells 属性。


构建并部署 Web 应用程序

要构建此应用程序,您将需要 Apache Maven2。安装 Maven2 并按照以下步骤构建应用程序:

  1. 提取 ticker.zip 存档,这将创建名为 ticker 的目录。
  2. 打开命令 Shell,并将当前目录更改为 ticker
  3. 运行 mvn install。这将构建应用程序,并将输出 WAR 文件放入 ticker 下的 target 目录中。WAR 文件的名称为 ticker.war
  4. 在浏览器中打开 Community Edition 控制台,并导航到 Deploy New Portlet。
  5. Archive 文本框中,输入所生成 ticker.war 的路径,并单击 Install

测试 Web 应用程序

  1. 在 Web 浏览器中打开 http://localhost:8080/ticker,以打开股票报价器,如图 4 中所示。
    图 4. 浏览器中运行的股票报价器
    显示正在运行的应用程序的屏幕截图
  2. 在 Web 浏览器中打开 http://localhost:8080/ticker/comet,以查看 Comet Servlet 的输出,如图5 中所示。
    图 5. Comet Servlet 输出
    显示浏览器中 Comet Servlet 的输出的屏幕截图

结束语

在本文中,我们通过管理控制台创建并配置了嵌入在 Community Edition 中的 Tomcat 实例的 NIO 连接器。我们还开发了一个 Java EE Web 应用程序,使用 DWR 的 Reverse Ajax 功能将来自服务器的数据通过 NIO 连接器中内置的 Comet 支持推送到浏览器。最后,我们了解了如何实现 Tomcat 的 CometProcessor 接口来编写公开其 Comet 功能的 Servlet。

致谢

感谢 Phani Madgula 和 Ashish Jain 对本文进行了审阅。


下载

描述名字大小
Sample source codeticker.zip8KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, Open source, Web development, Java technology
ArticleID=361340
ArticleTitle=为 WebSphere Application Server Community Edition 开发富 Internet 应用程序
publish-date=01082009