楼主: Sky-Tiger

Ajax for Java developers:

[复制链接]
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
11#
 楼主| 发表于 2009-6-2 22:18 | 只看该作者
Making Continuations useful

Now that you've seen that Continuations allow servlet requests to be suspended without thread consumption, I need to explain a little bit more of the Continuations API to show you how to use Continuations for practical purposes.

A resume() method forms a pair with suspend(). You can think of them of as the Continuations equivalent of the standard Object wait()/notify() mechanism. That is, suspend() puts a Continuation (and therefore the execution of the current method) on hold until either its timeout expires or another thread calls resume(). The suspend()/resume() pair is key to implementing a real Comet-style service using Continuations. The basic pattern is to obtain the Continuation from the current request, call suspend(), and wait until your asynchronous event arrives. Then call resume() and generate a response.

However, unlike the true language-level continuations in languages such as Scheme, or indeed the Java language's wait()/notify() paradigm, calling resume() on a Jetty Continuation doesn't mean that code execution picks up exactly where it left off. As you've seen, what actually happens is that the request associated with the Continuation is replayed. This results in two problems: undesirable reexecution of code as in ContinuationServlet in Listing 4, and loss of state: anything in scope when the call is made to suspend() is lost.

The solution to the first of these issues is the isPending() method. If the return value of isPending() is true, this means that suspend() has been called previously, and execution of the retried request has not yet reached suspend() for the second time. In other words, making code prior to your suspend() call conditional on isPending() ensures that it executes only once per request. It's best to design your application code before the suspend() call to be idempotent, so that calling it twice won't matter anyway, but where that isn't possible you can use isPending(). Continuation also offers a simple mechanism for preserving state: the putObject(Object) and getObject() methods. Use these to hold a context object with any state you need to preserve when the Continuation is suspended. You can also use this mechanism as a way of passing event data between threads, as you'll see later on.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
12#
 楼主| 发表于 2009-6-2 22:18 | 只看该作者
Writing a Continuations-based application

As a vaguely real-world example scenario, I'm going to develop a basic GPS coordinate-tracking Web application. It will generate randomised latitude-longitude pairs at irregular intervals. With some imagination, the coordinates generated could be the positions of nearby public transport, marathon runners carrying GPS devices, cars in a rally, or the location of a package in transit. The interesting part is how I tell the browser about the coordinates. Figure 1 shows a class diagram for this simple GPS-tracker application:

Figure 1. Class diagram showing major components of the GPS tracker application
UML class diagram of GPS tracker components

First, the application needs something that generates coordinates. This is what RandomWalkGenerator does. Starting from an initial coordinate pair, each call to its private generateNextCoord() method takes a random constrained step away from that location and returns the new position as a GpsCoord object. When initialized, RandomWalkGenerator creates a thread that calls the generateNextCoord() method at randomized intervals and then sends the generated coordinate to any CoordListener instances that have registered themselves with addListener(). Listing 6 shows the logic of RandomWalkGenerator's loop:

IMG01.gif (11.13 KB, 下载次数: 9)

IMG01.gif

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
13#
 楼主| 发表于 2009-6-2 22:18 | 只看该作者
Listing 6. RandomWalkGenerator's run() method

               
public void run() {

  try {
    while (true) {
      int sleepMillis = 5000 + (int)(Math.random()*8000d);
      Thread.sleep(sleepMillis);
      dispatchUpdate(generateNextCoord());
    }
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}


CoordListener is a callback interface that just defines the onCoord(GpsCoord coord) method. In this example, the ContinuationBasedTracker class implements CoordListener. The other public method on ContinuationBasedTracker is getNextPosition(Continuation, int). Listing 7 shows the implementation of these methods:

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
14#
 楼主| 发表于 2009-6-2 22:18 | 只看该作者
Listing 7. The innards of ContinuationBasedTracker

               
public GpsCoord getNextPosition(Continuation continuation, int timeoutSecs) {

  synchronized(this) {
    if (!continuation.isPending()) {
      pendingContinuations.add(continuation);
    }

    // Wait for next update
    continuation.suspend(timeoutSecs*1000);
  }

  return (GpsCoord)continuation.getObject();
}


public void onCoord(GpsCoord gpsCoord) {

  synchronized(this) {
    for (Continuation continuation : pendingContinuations) {

      continuation.setObject(gpsCoord);
      continuation.resume();
    }

    pendingContinuations.clear();
  }
}


When a client calls getNextPosition() with a Continuation, the isPending method checks that the request is not being retried at this point, then adds it to a collection of Continuations that are waiting for a coordinate. Then the Continuation is suspended. Meanwhile, onCoord -- invoked when a new coordinate is generated -- simply loops over any pending Continuations, sets the GPS coordinate on them, and resumes them. Each retried request then completes execution of getNextPosition(), retrieving the GpsCoord from the Continuation and returning it to the caller. Note the need for synchronization here, both to guard against inconsistent state in the pendingContinuations collection and to ensure that a newly added Continuation isn't resumed before it has been suspended.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
15#
 楼主| 发表于 2009-6-2 22:19 | 只看该作者
Listing 8. GPSTrackerServlet implementation

               
public class GpsTrackerServlet extends HttpServlet {

    private static final int TIMEOUT_SECS = 60;
    private ContinuationBasedTracker tracker = new ContinuationBasedTracker();
  
    public void service(HttpServletRequest req, HttpServletResponse res)
                                                throws java.io.IOException {

      Continuation c = ContinuationSupport.getContinuation(req,null);
      GpsCoord position = tracker.getNextPosition(c, TIMEOUT_SECS);

      String json = new Jsonifier().toJson(position);
      res.getWriter().print(json);
    }
}


As you can see, this servlet does very little. It simply obtains the request's Continuation, calls getNextPosition(), converts the GPSCoord into JavaScript Object Notation (JSON), and writes it out. Nothing here needs protection from reexecution, so I don't need to check isPending(). Listing 9 shows the output of a call to the GpsTrackerServlet, again with five simultaneous requests but only a single available thread on the server:

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
16#
 楼主| 发表于 2009-6-2 22:19 | 只看该作者
Listing 9. Output of GPSTrackerServlet

               
$  for i in 'seq 1 5'  ; do lynx -dump localhost:8080/tracker &  done
   { coord : { lat : 51.51122, lng : -0.08103112 } }
   { coord : { lat : 51.51122, lng : -0.08103112 } }
   { coord : { lat : 51.51122, lng : -0.08103112 } }
   { coord : { lat : 51.51122, lng : -0.08103112 } }
   { coord : { lat : 51.51122, lng : -0.08103112 } }


This example is unspectacular but serves as a proof-of-concept. After the requests are dispatched, they are held open for several seconds until the coordinate is generated, at which point the responses are quickly generated. This is the basis of the Comet pattern, with Jetty handling five concurrent requests on one thread, thanks to Continuations.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
17#
 楼主| 发表于 2009-6-2 22:19 | 只看该作者
Creating a Comet client

Now that you've seen how Continuations can be used in principle to create nonblocking Web services, you might wonder how to create client-side code to exploit this ability. A Comet client needs to:

   1. Hold open an XMLHttpRequest connection until a response is received.
   2. Dispatch that response to the appropriate JavaScript handler.
   3. Immediately establish a new connection.

A more advanced Comet setup could use one connection to push data from several different services to the browser, with appropriate routing mechanisms on the client and server. One possibility would be to write client-side code against a JavaScript library such as Dojo, which provides Comet-based request mechanisms in the shape of dojo.io.cometd.

However, if you're working with the Java language on the server, a great way to get advanced Comet support on both client and server is to use the DWR 2 (see Resources). If you're not familiar with DWR, you can read Part 3 of this series, "Ajax with Direct Web Remoting." DWR transparently provides an HTTP-RPC transport layer, exposing your Java objects to calls across the Web from JavaScript code. DWR generates client-side proxies, automatically marshalls and unmarshalls data, handles security concerns, provides a convenient client-side utility library, and works in all major browsers.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
18#
 楼主| 发表于 2009-6-2 22:19 | 只看该作者
DWR 2: Reverse Ajax

Newly introduced with DWR 2 is the concept of Reverse Ajax. This is a mechanism by which server-side events are "pushed" to the client. Client-side DWR code transparently deals with establishing connections and parsing responses, so from a developer's point of view, events can simply be published to the client from server-side Java code.

DWR can be configured to use three different mechanisms for Reverse Ajax. One is the familiar polling approach. The second, known as piggyback, doesn't create any connections to the server. Instead, it waits until another DWR service call occurs and piggybacks pending events onto this request's response. This makes it highly efficient but means that client notification of events is delayed until the client makes an unrelated call. The final mechanism uses long-lived, Comet-style connections. And best of all, DWR can auto-detect when it's running under Jetty and switch to using Continuations for nonblocking Comet.

I'll adapt my GPS example to use Reverse Ajax with DWR 2. On the way, you'll see in more detail how Reverse Ajax works.

I no longer need my servlet. DWR provides a controller servlet that mediates client requests directly onto Java objects. I also no longer need to deal explicitly with Continuations because DWR takes care of this under the hood. So I simply need a new CoordListener implementation that publishes coordinate updates to any client browsers.

An interface called ServerContext provides DWR's Reverse Ajax magic. ServerContext is aware of all Web clients currently viewing a given page and can provide a ScriptSession to talk to each. This ScriptSession is used to push JavaScript fragments to the client from Java code. Listing 10 shows how the ReverseAjaxTracker responds to coordinate notifications, using them to generate calls to the client-side updateCoordinate() function. Note that the appendData() call on the DWR ScriptBuffer object automatically marshalls a Java object to JSON, if a suitable converter is available.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
19#
 楼主| 发表于 2009-6-2 22:19 | 只看该作者
Listing 10. The notification callback method in ReverseAjaxTracker

               
public void onCoord(GpsCoord gpsCoord) {

  // Generate JavaScript code to call client-side
  // function with coord data
  ScriptBuffer script = new ScriptBuffer();
  script.appendScript("updateCoordinate(")
    .appendData(gpsCoord)
    .appendScript(");");

  // Push script out to clients viewing the page
  Collection<ScriptSession> sessions =
            sctx.getScriptSessionsByPage(pageUrl);
            
  for (ScriptSession session : sessions) {
    session.addScript(script);
  }   
}


Next, DWR must be configured to know about ReverseAjaxTracker. In a larger application, DWR's Spring integration could be leveraged to provide DWR with Spring-created beans. Here, however, I'll just have DWR create a new instance of ReverseAjaxTracker and place it in the application scope. All subsequent DWR requests will then access this single instance.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
20#
 楼主| 发表于 2009-6-2 22:19 | 只看该作者
I also need to tell DWR how to marshall data from GpsCoord beans into JSON. Because GpsCoord is a simple object, DWR's reflection-based BeanConverter is sufficient. Listing 11 shows the configuration for ReverseAjaxTracker:

Listing 11. DWR configuration for ReverseAjaxTracker

               
<dwr>
   <allow>
      <create creator="new" javascript="Tracker" scope="application">
         <param name="class" value="developerworks.jetty6.gpstracker.ReverseAjaxTracker"/>
      </create>

      <convert converter="bean" match="developerworks.jetty6.gpstracker.GpsCoord"/>
   </allow>
</dwr>


The create element's javascript attribute specifies a name that DWR uses to expose the tracker as a JavaScript object. However, in this case, my client-side code won't use it, instead having data pushed to it from the tracker. Also, some extra configuration in web.xml is needed to configure DWR for Reverse Ajax, as shown in Listing 12:

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表