反向Ajax的基本概念是客户端不必从服务器获取信息,服务器会把相关信息直接推送到客户端。这样做的目的是解决Ajax传统Web模型所带来的一个限制,即实时信息很难从技术上解决。原因是,客户端必须联系服务器,主动询问是否存在变更,如果有变更就会更新页面(或者页面的一部分)。虽然可以非常快速地完成这个操作,感觉好像是实时的,但是实际上不是的。我们需要的是,服务器联系查看其页面的所有浏览器,并通告所发生的变更。
反向Ajax是克服这个限制的一种方式。在基于Ajax的应用程序的事件序列中,某个用户动作会导致对某个客户端Ajax引擎的调用,不论它是JavaScript代码,还是其他库。这个引擎会向服务器发出一个请求,服务器按非Ajax模式进行处理,然后返回响应。响应内容首先被Ajax引擎处理,然后调用某些客户端代码以更新页面。直到用户离开当前页面。
现在共有三种DWR支持的技术可以辅助完成这种技术,其中两种技术属于主动的反向Ajax,另一种称为被动的反射Ajax。
轮询
在未使用Ajax的Web页面,使用meta刷新元素,每隔数秒就更新这个页面。这就是所说的轮询。客户端定时轮询服务器,看是否存在更新,并且显示服务器传回的当前信息。在网页上,使用一些简单的JavaScript代码也可以实现相同的功能。在Ajax中,事件流会更复杂,但本质是一样的,轮询技术是一种主动式反向Ajax技术,客户端在每个时间周期内向服务器发送请求,查询服务器是否有更新内容,若有则向服务请求数据。
Comet
Comet是一种基于HTTP长连接的服务器推动方式。客户端向服务器发送请求后,服务器将数据通过response发送给客户端,但并不会将此response关闭,而是一直通过response将最新的数据发送给客户端浏览器,直到客户端浏览器关闭。使用Ajax实现“服务器推”与传统的Ajax应用的不同之处在于:
- 服务器端会阻塞请求直到有数据传递或超时才返回;
- 客户端JavaScript响应处理函数会在处理完服务器返回信息后,再次发出请求,重新建立连接;
- 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达,这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端的所有信息取回。
PiggyBack
PiggyBack(回传)方法是DWR提供的一种“被动式”方法。服务器端将最新的数据排成队列,然后等待客户端下一次请求,接收到请求后不将等待更新的数据发送给客户端。DWR会使用默认的设置PiggyBack,在这种情况下,启用反向Ajax时,不会导致服务器超载。
各反向Ajax的配置
要让DWR程序支持反向Ajax,只须在web.xml中的DWRServlet里添加一个初始化参数,添加内容为:
<servlet> <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>classes</param-name> <param-value>org.lucifer.dwr.SendMessage</param-value> </init-param> <init-param> <param-name>activeReverseAjaxEnabled</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping>
如果使用的是轮询技术 ,则需要添加如下参数:
<init-param> <param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name> <param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value> </init-param>
除了上述配置外,为启用反向Ajax,在页面中还需要一些JavaScript代码,即:
dwr.engine.setActiveReverseAjax(true);
处理轮询请求的过程通常是在服务器端编写一些代码,以更新附加到服务器端的每个客户端的会话。DWR会记录与之联系的每个客户端,分别存储每个客户端的会话。这与通常的HTTP会话不同。借助于此,可以调用JavaScript代码,下一个轮询请求会通知这些调用。例如:
package org.lucifer.dwr;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
import org.directwebremoting.Browser;
import org.directwebremoting.ScriptSessions;
import org.directwebremoting.annotations.Param;
import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;
import org.directwebremoting.annotations.ScriptScope;
import org.directwebremoting.create.NewCreator;
@RemoteProxy(creator=NewCreator.class, name="sendMsg", scope=ScriptScope.APPLICATION,
creatorParams=@Param(name="class", value="org.lucifer.dwr.SendMessage"))
public class SendMessage {
private static final Logger logger = Logger.getLogger(SendMessage.class.getName());
private Queue<String> messages = new ConcurrentLinkedQueue<String>();
@RemoteMethod
public void addMessage(String message) {
StringBuilder sb = new StringBuilder("有客户请求,消息为:");
sb.append(message);
logger.info(sb.toString());
messages.add(message);
Browser.withCurrentPage(new Runnable() {
/* 启用监听客户端当前页线程 */
@Override
public void run() {
/* 把数据添加到客户端调用的方法中 */
ScriptSessions.addFunctionCall("receiveMessages", messages);
}
});
}
}
这里主要通过WebContext类获得DWR应用的Web上下文、利用ServletContext获得DWRServlet的上下文,以及通过Web上下文获取访问本应用的客户端浏览器的ScriptSession。一旦获得当前页面的名称,就可以获取当前连接到这个页面的所有会话列表,然后启用监听客户端当前线程,把数据添加到客户端调用的方法中。
下面是在客户端执行的JSP页面文件,代码如下:
<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <c:set var="basePath" value="${pageContext.servletContext.contextPath}"/> <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"/> <title>DWR 示例</title> <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script> <script type="text/javascript">jQuery.noConflict();</script> <script type="text/javascript" src="${basePath}/dwr/engine.js"></script> <script type="text/javascript" src="${basePath}/dwr/interface/sendMsg.js"></script> <script type="text/javascript" src="js/dwr_simple.js"></script> <script type="text/javascript" src="${basePath}/dwr/util.js"></script> </head> <body> <form id="myForm" name="myForm"> <label for="msg">请输入消息:</label> <input type="text" id="msg" name="msg" autocomplete="on" placeholder="请输入消息"/> <button type="button" name="sendMsg" id="sendMsg">反向Ajax测试</button> </form> <footer> <p><div id="console"></div></p> </footer> </body> </html>
JavaScript代码在JS文件中,代码如下:
var receiveMessages = function(messages) { var tmp = jQuery('<p>'); for(var item in messages) { jQuery("<span>").append(messages[item]).after("<br/>").prependTo(tmp[0]); } tmp.prependTo("#console"); }; jQuery(function() { dwr.engine.setActiveReverseAjax(true); jQuery("#sendMsg").click(function(event) { var msg = jQuery("#msg").val(); sendMsg.addMessage(msg); }); });