反向Ajax,第5部分:事件驱动的Web开发
译者 Elaine.Ye
前言这一文章系列展示了如何使用反向Ajax(Reverse Ajax)技术开发事件驱动的web应用,第1部分内容介绍了反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling);第2部分内容说明了如何使用WebSocket来实现反向Ajax,并讨论了使用Comet和WebSocket的web服务器的局限性;第3部分内容说明了如果需要支持多种服务器,或是给用户提供部署在他们自己的服务器上的独立的web应用的话,那么实现自己的Comet或是WebSocket通信系统会有哪些难点,该部分内容还讨论了Socket.IO;第四部分内容谈到了Atmosphere和CometD——最知名的用于Java技术服务器的开源反向Ajax库。
到目前为止你已经了解了创建通过事件来通信的组件,在本系列的最后一部分内容中,我们把事件驱动开发的原则应用到实践中,构建一个示例性的事件驱动web应用。
你可以下载本文中使用的源代码。
前提条件
理想情况下,要充分体会本文的话,你应该对JavaScrpit和Java有一定的了解,并且要有一些web开发经验。若要运行本文中的例子,你还需要最新版本的Maven和JDK(参见参考资料)。
术语
你可能对事件驱动架构(event-driven architecture,EDA)、EventBus系统、消息系统、复杂事件处理(complex event processing,CEP)和信道(channel)这些说辞并不陌生,这些术语和概念已出现多年。随着技术的成熟,你可能会更频繁地听到这类说法。本节内容为这些概念提供一些简短的解释。
事件(event)
在系统中会发生的一些事情的出现,事件通常具有属性,比如说出现的时间(时间戳)、来源或位置(我们点击的组件),以及一些描述事件的数据。根据系统的不同,事件还可以有其他的一些属性选择。
事件驱动架构(Event-driven architecture,EDA)
也称作基于事件的编程,这是一种架构设计,在这种设计中,应用由通过发送和接收事件来通信和执行的组件构成。Swing的图形化用户界面(GUI)就是一个EDA例子,每个Swing组件都可以监听事件、对事件作出反应、发送其他事件等。EDA由几个部分组成:事件生产者、事件消费者、事件和处理软件。
1. 事件生产者(event producer)——该组件发出事件。在本文的例子中,表单的提交按钮就是一个事件生产者。
2. 事件消费者(event consumer)——监听特定事件的组件。例如,例子中的表单提交这种情况,浏览器监听表单的提交按钮上的点击操作,把表单数据发送给服务器。
3. 事件处理软件(event-processing software)——这是系统的核心,事件生产者发布事件,事件消费者注册自身以接收事件。根据软件的不同,处理过程可以很简单(只是把接收到的事件转发给消费者),或很复杂(CEP)。有了CEP,软件就可以支持各种各样的处理方式,比如说事件的汇集、过滤和转换等。
Esper就是这样的一个软件例子。事件处理软件不仅可以表现成一个独立的运行应用,其还可以是整合到你的应用中的库。
消息系统(messaging system)
一种事件驱动应用类型,在这种应用中,事件生产者把消息发布到信道中,事件消费者则通过信道进行订阅。事件生产者和消费者彼此之间没有链接,是完全独立开来的。在这种类型的事件驱动应用中,通常用到的术语是消息(message)而不是事件(event)。
信道(channel)
消息系统中分类事件的一种方式。其代表了事件生产者希望事件发送到的目的地。例如,在一个聊天室应用中,某个信道可能会是 /chatapplication/chatrooms/asdrt678,该信道标识了一个事件生产者可以发送消息的特定的聊天室,图形化的组件应该要订阅该信道,目的是显示最新到达的消息。
某些消息系统提供了两种类型的信道:队列(queue)和主题(topic)。
1. 队列(queue)——当某条消息被发送到队列中时,只有一个事件消费者拿到并处理该条消息,其他消费者不会看到它。队列可被持久化,以保证交付。最好的队列例子是邮递请求,某个web应用在用户注册时发布一条消息到队列 /myapp/mail/user-registration中,可能有多个邮件应用订阅了这一队列,如果没有的话,消息也不会丢失。
2. 主题(topic)——当某条消息发送到某个主题上时,每个订阅者都可以接收到它,主题通常是没有持久化的。一个例子是监控软件的一个主题/event/system/cpu/usage,生产者定期往其中发送CPU的使用情况;另一方面,这一主题可能会没有或是有多个订阅者,这取决于他们的兴趣所在。
发布/订阅(publish/subscribe)
事件驱动的解决方案实现了发布/订阅模式。事件生产者在处理软件中发布事件,事件消费者通过订阅来接收它们。事件消费者订阅的方式依赖于软件。在消息应用中,它们订阅信道(比如说,还可以有选择地把过滤规则应用在事件类型上)。使用诸如Esper一类的CEP,可通过类SQL的请求来定义你所感兴趣的事件,完成订阅操作。
为什么使用事件驱动的解决方案
在一个传统的通信方案中,如果系统A需要来自系统B的信息,就会发送一个请求给B。系统B会处理该请求,系统A则会停在那里等待响应。在处理完成时,响应会送回给系统A。在这一同步的通信模式中,资源的消耗是低效的,因为在等待响应时浪费掉了处理时间。
在异步模式中,系统A会从系统B中订阅它响应的信息。然后系统A可以选择性地给系统B发送通知,并立刻返回,系统A可以继续处理其他事情,这一步骤是可选的。通常情况下,在事件驱动的应用中,你不需要请求其他系统发送事件,因为你不知道它们是谁。当系统B发布响应时,系统A会立刻接收到。
事件驱动架构的一个优点是其允许更好的伸缩性。可伸缩性是系统在满足其目标的同时适应需求、容量或是强度变化的能力。通过消除暂停时间,事件驱动的架构通常有着更好的表现,以及有更高的处理效率。
另一个优点表现在应用的开发和维护方面。使用事件驱动的解决方案,应用的每个组件都可以是完全独立和解耦的。
事件驱动的解决方案允许有更好的反应时间,因为通信有着更低的延迟。
把事件驱动的解决方案应用在web上
web框架过去依赖于传统的请求-响应模式,这导致了页面的刷新。随着Ajax、反向Ajax以及诸如CometD和Atmosphere一类的功能强大的框架的出现,现在把事件驱动架构的概念应用到web上来获取解耦、可伸缩性和反应性的好处已经不是什么难事了。
客户端
事件驱动架构可应用在GUI开发的客户端。与创建一个传统的web页面不同,你可以把一个单独的web页面当作容器使用。每个组件(页面的每个组成部分)都可以是独立的,你可以在web上放一个Java Swing GUI,就像包含了小工具(gadget)的Google页面那样(参见参考资料中的链接)。
你需要一个事件总线(event bus),例如,你需要开发一个JavaScript事件总线,其允许每个页面组件从信道订阅或是在信道中发布。事件也可以是异步的,在两个或是多个事件到达后才触发行为。事件总线可以用于页面中的局部事件,但你也可以通过使用CometD或是Socket.IO来以插件的方式支持远端事件。
服务器端
在服务器端,你需要设置一个反向Ajax框架来支持事件驱动的框架。在本系列前几部分的考察中,发现只有CometD有着事件驱动的方法。对于其他框架来说,你需要增加自定义的支持,这不是什么大问题。你还可以加入第三方的消息系统,比如说JMS(例如Apache ActiveMQ)或是像Esper那样的CEP。一个更简单的解决方案是Redis,其支持基本的发布/订阅。
这一文章系列谈论的是事件驱动的web和反向Ajax,因此我们重点关注客户端,不会去设置一个复杂的消息系统。
事件驱动web的例子
本文将要创建的例子是一个聊天室web应用,该应用使用一个用户面板来列出连接的用户。你的用户名是加粗显示的,活动用户(20秒钟后还处活跃状态的那些)是绿色显示的,20秒钟后处于非活动状态的那些是橙色显示的。如果有用户连接或是断开连接,列表就会刷新。
出于安全目的,web.xml文件中配置了两分钟的会话超时,非活动状态两分钟后,就会弹出一个窗口,你会被重定向到登录页面。
只要你不再处于会话中或是还未连接,就会被重定向到登录页面。登录页面要求输入用户名并会查看是否可让你登录到聊天室中。
一旦登录成功,你就可以在聊天室中给所有用户发送消息。一个控制台也会显示出来,记录所有收到的事件。
该web应用是基于事件的,有了上述的信息,你可以很容易地定义几个事件:
1. 用户连接
2. 用户断开连接
3. 会话过期
4. 接收到聊天消息
5. 如果没有登录的话,安全过滤器阻拦请求
6. 用户变成非活动的
7. 用户变成活动的
8. 所有其他与UI协调相关的事件
某些事件只局部于web应用,由局部总线来识别,如清单1所示:
清单1. 总线设置
bus = {
local: new EventBus({
name: 'EventBus Local'
}),
remote: EventBus.cometd({
name: 'EventBus Remote',
logLevel: 'warn',
url: document.location.href.substring(0,
document.location.href.length -
document.location.pathname.length) + '/async',
onConnect: function() {
bus.local.topic('/event/bus/remote/connected').publish();
},
onDisconnect: function() {
bus.local.topic('/event/bus/remote/disconnected').publish();
}
})
};
其他事件是远端的,这意味着它们需要一个反向Ajax系统,比如说CometD来在所有客户端中发布它们。图1展示了该示例应用。
图1. 示例应用
你可以下载这一示例应用,许多类都是安全通道类,或是会话和用户管理通道类。本文给出了代码最重要的部分,不过建议你下载并运行该应用例子来更加深入地了解它的运作方式。
该web应用有不同的组件构成:聊天室、用户列表和控制台。每个都很独立,可以拿掉而不会影响到其他部分。
为了以局部的和远端的方式来设置这一事件驱动的系统,该例子使用了Ovea的EvenBus系统。其提供了一个局部的事件总线,一个活动远端事件的ComeD桥接,以及一种协调事件的方式(在几个事件完成之后触发行为)。当然,你可以选择使用另一个不同的系统。该例子使用了JavaScript来进行设置,如清单1所示。
一旦总线就位了之后,应用和组件就是基于事件的了。在本例子中,设置的是IDLE检测系统,如清单2所示。
清单2. IDLE检测系统
bus.local.topic('/event/dom/loaded').subscribe(function() {
$.idleTimer(20000);
$(document).bind('idle.idleTimer', function() {
bus.local.topic('/event/idle').publish('inactive');
});
$(document).bind('active.idleTimer', function() {
bus.local.topic('/event/idle').publish('active');
});
})
有了清单2中的代码,IDLE系统就会在检测到活动时发送事件。这一代码可用在任何需要IDLE系统的应用中。在该例子中,你需要在用户活动的远端事件中转化一下该代码。其也可用JavaScript来实现,如清单3所示。
清单3. 用户活动管理
bus.local.topic('/event/idle').subscribe(function(status) {
bus.remote.topic('/event/user/status/changed').publish({
status: status == 'active' ? 'online' : 'away'
});
});
bus.remote.topic('/event/user/status/changed').subscribe(function(evt) {
if(evt.user != me.name) {
$('#users li').filter(function() {
return evt.user == $(this).data('user').name;
}).removeClass('online')
.removeClass('away')
.addClass(evt.status);
}
});
首个订阅接收来自IDLE系统的事件,然后把用户状况发送给服务器端。其他的订阅接收来自服务器端的用户状况事件。因此,只要用户的状况发生改变,用户列表中的用户的颜色就会变成绿色或是橙色。
当用户连接或是断开连接时,就会发送一个事件,如清单4所示:
清单4. 用户列表管理
bus.remote.topic('/event/user/connected').subscribe(function(user) {
$('#users ul').append(row(user));
});
bus.remote.topic('/event/user/disconnected').subscribe(function(evt) {
$('#users li').filter(function() {
return evt.user == $(this).data('user').name;
}).remove();
});
应用的代码简单、解耦且是独立的,通过重用Ovea的许多技术,你可以快速地创建事件驱动的web应用。不过,因为可以使用其他的系统来代替它,因此这并不是必需的。该例子只花了一天的开发时间,其中一半的代码都是管道代码,包括:
1. Maven:构建工程
2. 安全功能(登录、退出、会话超时)
3. 使用了Jersey的REST服务
结束语
该文章系列展示了如何构建响应式的以及是可伸缩的应用,这些应用能够提供很好的用户体验。事件驱动的web应用还是一个相当新的概念,某些WEB框架在内部用到了这些概念。不过本文展示的是不需要web框架来构建的这样的应用,对于要分离Java开发者和web设计者之间的职责来说,使用好的、专业的库就已是绰绰有余的了。关于如何把事件驱动的开发变成你的日常工作的一部分,我希望你已经有了一个较为深入的理解。它很容易让人沉迷于其中,一旦经过尝试,你就不想再回退到传统的框架上了。
下载
描述 名称 大小 下载方法
文章的源代码 reverse_ajaxpt5_source.zip 925KB HTTP
参考资料
学习资料
1. 阅读这一系列之前的部分:
1.1. 第1部分:Comet介绍
1.2. 第2部分:Websocket
1.3. web服务器和Socket.IO
1.4. Atmosphere和CometD
2. iGoogle:web上的一个Java Swing GUI例子。
3. CometD:了解关于这一事件驱动的反向Ajax解决方案的各方面的内容。
4. 在维基百科上了解这些内容:
4.1. AJAX
4.2. 反向Ajax
5. Event Processing in Action,由Dr. Opher Etzion和Peter Nibblet合著,展示如何使用、设计和构建事件处理应用。
6. “Google AppEngine uses Jetty!”介绍了Google的Jetty定制。
7. 了解更多关于Jetty Continuation的功能。
8. “Exploring Reverse AJAX”:提供了一些关于反向Ajax技术的介绍说明。
9. “Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashups”(developerWorks, February 2009):了解如何把不起眼的跨域调用技术(JSONP)和一个灵活的JavaScript库(JQuery)结合在一起,以令人惊讶的速度构建出一些功能强大的聚合应用。
10. “Build Ajax applications with Ext JS”(developerWorks, July 2008):对Ext Js背后的面向对象JavaScript 设计概念做了一个概述,并说明了如何把Ext JS框架用作富互联网应用的UI元素。
11. “Compare JavaScript frameworks”(developerWorks, February 2010):对极大地增强了JavaScript开发的那些框架有一个整体的了解。
12. “Mastering Ajax, Part 2: Make asynchronous requests with JavaScript and Ajax”(developerWorks, January 2006):学习如何使用Ajax和XMLHttpRequest对象来创建一种永不会让用户等待服务器响应的请求/响应模型。
13. “Create Ajax applications for the mobile Web”(developerWorks, March 2010):了解如何使用Ajax构建跨浏览器的智能手机Web应用。
14. “Where and when to use Ajax in your applications”(developerWorks, February 2008):了解如何使用Ajax来改进网站,同时避免糟糕的用户体验。
15. “Improve the performance of Web 2.0 applications“(developerWorks, December 2009):探讨不同的浏览器端缓存机制。
16. “Introducing JSON”(JSON.org):获得对JSON语法的一个入门介绍。
17. developerWorks Web development zone:获得各种谈论基于Web的解决方案的文章。
18. developerWorks podcasts:收听各种与软件开发者进行的有趣的访谈和讨论。
19. developerWorks technical events and webcasts:随时关注developerWorks的技术事件和webcast的进展。
获取产品和技术
1. 从Ovea的GitHub上获取Ovea捐献给CometD项目的代码。
2. JavaScript Event-Bus(从Ovea的GitHub上获得):获得以JavaScript实现的局部方式的和远端方式的事件驱动解决方案。
3. JavaScript event synchronization(从Ovea的GitHub上获得):同步异步事件。
4. 获取Esper:复杂事件处理软件。
5. 从Apache获得ActiveMQ消息系统。
6. Redis:获取有着异步功能的内存缓存系统。
7. 事件驱动的web例子:获得本文中的例子源代码。
8. CometD源代码:获取该项目,这是一个web消息的可伸缩comet(服务器推)实现。
9. CometD扩展:提供了Jetty 8 WebSocket支持和来自Ovea的Google Guice支持。
10. Jetty:获取Jetty,一个web服务器和javax.servlet容器,外带对WebSocket的支持。
11. Apache Tomcat Advanced I/O文档:获得有用的链接、用户指南、参考手册和Apache Tomcat开发说明。
12. Grizzly NIO Framework:帮助你使用Java NIO API。
13. Grizzly Comet例子:在从头开始构建一个新的应用之前,了解需要对现有应用做哪些修改。
14. Glassfish Application Server:获得主要的Glassfish Server的开源版本。
15. Socket.IO Java:获得原始的项目和最后更新的Ovea的派生版本。
16. Apache Maven:获取Maven,一个软件项目管理和包容工具。
17. Java Development Kit, Version 6:获得Java平台标准版(Java Platform, Standard Edition,Java SE),该平台允许你在台式机和服务器上,以及在当今要求苛刻的嵌入式环境上开发和部署Java应用。
18. 免费试用IBM软件,下载使用版,登录在线试用,在沙箱环境中使用产品,或是通过云来访问,有超过100种IBM产品试用版选择。讨论
1. 参加论坛的讨论。
2. 现在就创建你的developerWorks个人资料,并设置一个关于Reverse Ajax的观看列表。与developerWorks社区建立联系并保持联系。
3. 找到其他在web开发方面感兴趣的developerWorks成员。
4. 分享你的知识:加入一个关注web专题的developerWorks组。
5. Roland Barcia在他的博客中谈论Web 2.0和中间件。
6. 关注developerWork成员的shared bookmarks on web topics。
7. 快速获得答案:访问Web 2.0 Apps论坛。
8. 快速获得答案:访问Ajax论坛。
关于作者
Mathieu Carbou是Ovea的一位提供服务和开发解决方案的Java web架构师和顾问。他是几个开源项目的提交者和领导者,也是Montreal的Java User Group的一位演讲者和领导者。Mathieu有着很强的代码设计和最佳实践背景,他是一个从客户端到后端的事件驱动的web开发方面的专家。他的工作重点是在高度可伸缩的web应用中提供事件驱动的和消息式的解决方案。你可以看一看他的博客。
(译者注:标题配图来自http://www.ibm.com/developerworks/网站)