Agile Japan 2009
2009年4月22日、東京千代田区にある放送会館で「アジャイルジャパン2009」が開催されました。本イベントは「ソフトウェア開発の次世代リーダーをつくる」ことを合い言葉に、200人以上の参加者を集めてスタートしました。
作者 川尻 剛 投稿日 2008年6月13日 午前12時21分
JavaOne 2008から一ヶ月が過ぎ、様々な現地レポートが揃いました。JavaOneはもちろんJavaのお祭りですので、これらのレポートではJavaFXや Java EE/SEの動向が目玉となっています。しかし、近年のJavaOneではJavaだけに限らず様々な分野の話題を扱うようになっています。例えば先日公開されたセッション資料から、「Next Generation Web」や「Tools and Script Language」のトラックを見てみましょう。AjaxやJavaScript、Rubyといったキーワードが続いているのが解るでしょうか。実際の発表でも「Java にあまり関係ないんだけど・・」と始める発表者が多かったのが印象的でした。JavaVMが多様な言語のプラットフォームとなったように、JavaOne も先端技術の統合的なカンファレンスになりつつあると言えるでしょう。
このうち、持続的なHTTPコネクションを使用する、つまりCometと呼べる方法は後ろの2つです。まずは各手法の特徴を順に見ていきましょう。それぞれの手法の動作を次の図に示します。
まず最初に、ポーリングは定期的にリクエストを送信してイベントが発生したかどうか確かめる方法です。例えば、javascriptの setInterval関数を使用してXHRリクエストを何度も送信することなどが相当します。従来のリクエスト-レスポンス型に近い形でクライアントとサーバの両端を実装できるので実現が容易ですが、無駄なリクエストとレスポンスが発生してしまう欠点があります。また、リクエストを送信する間隔を短くするとサーバの負荷が高くなり、長くするとイベントの通知が遅くなるというジレンマを抱える事になります。
続いて、ロングポーリングはサーバーがイベントが発生するまでレスポンスを返すのを保留して、クライアント側はレスポンスが返ると同時に再度リクエストを送信する方法です。例えば、XHRのコールバック関数で再度通信を行うことなどが相当します。ポーリングと違い、無駄なレスポンスを排除する事ができますが、リクエストの方は依然として残る欠点があります。また、サーバー側はイベントが発生するまでの間にスレッドをブロックするので、すぐに接続キューの空きが不足してしまう問題を抱えます。
最後に、ストリーミングはコネクションを張りっぱなしの状態にして、イベントが発生するたびにレスポンスを返す方法です。本法ではクライアント側において、全てのレスポンスが届く前にデータを解析する必要があるので、少々トリッキーな実装を行う必要があります。例えばIEのXHR通信では、全ての読み込みが完了するまでコールバック関数は実行されません。未完了のレスポンスを読み込むにはいくつか方法がありますが、最も簡単なのはiframeを使う方法です。
本手法では空のiframeとレスポンスの処理を記述した関数を予めページに記述しておき、Comet処理が必要となった時点でiframeのsrc属性にサーバーへのパスを指定します。すると自動的にGETリクエストが送信されるので、サーバは応答を返さずに保留させ、イベントが発生するたびにレスポンスを返すようにします。このレスポンスはiframeの中に流れていくので、レスポンスに親ページで定義した関数を呼び出す内容を含めておくと、それが到着するごとに解析処理が実行されます。
以上のような方法で実現できるストリーミングですが、無駄なリクエストとレスポンスを完全に排除できる一方で、ロングポーリングと同様のスレッド問題が残ります。この問題を解決するために、現在は以下のようなアプリケーションサーバが提供されています。
package web;次にクライアント側の準備をします。先に述べたように、ストリーミング方式では予めレスポンスを解析する処理とiframeをページに記述しておきます。index.htmlというファイルを作成し、次のように内容を修正して下さい。
import java.io.IOException;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class StreamingServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger("servlet");
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info("connect");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">iframeのsrc属性にサーブレットのパスを指定しているので、ブラウザで開いた瞬間にdoGetメソッドが実行されます。ここで一度アプリケーションを起動して、さきほど追加したログが出力されるか確かめて下さい。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>HTTP Streamingのサンプル</title>
<script type="text/javascript">
function update(message) {
alert(message);
}
</script>
</head>
<body>
<iframe src="streaming" frameborder="0" width="0" height="0"></iframe>
</body>
</html>
private String cometContextPath;CometContextを作成したら、次にCometHandlerを定義します。CometHandlerは com.sun.grizzly.comet.CometHandlerインターフェイスを実装する事で表現する事ができます。インターフェイスのAPI のうち、onEventメソッドがイベントの発生時に呼び出されるメソッドです。FormHandlerというCometHandler実装クラスを作成し、次のようにイベント発生時にクライアントのupdate関数を実行するように修正して下さい。
@Override
public void init(ServletConfig config) throws ServletException {
cometContextPath = config.getServletContext().getContextPath() + "/streaming";
CometContext context = CometEngine.getEngine().register(cometContextPath);
context.setExpirationDelay(10 * 60 * 60 *1000);
}
Package web.handler;
import com.sun.grizzly.comet.CometEvent;
import com.sun.grizzly.comet.CometHandler;
import java.io.IOException;
import java.io.PrintWriter;
public class FormHandler implements CometHandler<PrintWriter> {
private final static String SCRIPT_START_TAG = "<script type='text/javascript'>";
private final static String SCRIPT_END_TAG = "</script>";
private PrintWriter writer = null;
public void attach(PrintWriter writer) {
this.writer = writer;
}
public void onEvent(CometEvent event) throws IOException {
if (event.getType() == CometEvent.NOTIFY) {
writer.write(SCRIPT_START_TAG);
writer.write("window.parent.update('Hello Comet!');");
writer.write(SCRIPT_END_TAG);
writer.flush();
}
}
public void onInitialize(CometEvent event) throws IOException {
}
public void onTerminate(CometEvent event) throws IOException {
onInterrupt(event);
}
public void onInterrupt(CometEvent event) throws IOException {
writer.close();
event.getCometContext().removeCometHandler(this);
}
}
@Override登録したCometHandlerはCometContextのnotifyメソッドを実行する事で呼び出す事ができます。次の図に示すように、 CometContextは登録された全てのCometHandlerを呼び出します。なお、特定のCometHandlerを実行したい場合は addCometHandlerの返り値にIDが返りますので、それを退避してnotifyメソッドに指定して下さい。
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info("connect");
FormHandler handler = new FormHandler();
handler.attach(response.getWriter());
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.addCometHandler(handler);
}
@Override後はXHRなどでPOSTリクエストを送信するようにページにボタンを付け加えると、ボタンが押されるたびに全てのブラウザに「Hello Comet!」と書かれたダイアログが表示されるようになります。なお、今回は固定値を表示しましたが、動的なデータをCometHandlerに渡したい場合は、notifyメソッドの引数にデータを指定します。するとonEvent側では次のようにデータを参照することができます。
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.notify(null);
}
MyClass output = (MyClass) event.attachment();一例として、Prototype.js(Script.aculo.us)とJson-libを使用してフォームの内容をCometHandlerに渡すサンプルを以下に示します。これらを参考に、自分だけのCometアプリケーションを作成してみて下さい。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cometフォーム</title>
<script src="lib/prototype.js" type="text/javascript"></script>
<script src="lib/scriptaculous.js?load=effects" type="text/javascript"></script>
<script type="text/javascript">
var CometForm = Class.create();
CometForm.prototype = {
initialize: function(url,root,button) {
this.url = url;
this.root = root;
Event.observe($(button), 'click',
this.kick.bindAsEventListener(this), false);
},
connect: function(iframeEl) {
$(iframeEl).src = this.url + '?date=' + new Date().getTime();
},
kick: function() {
var query = Form.serialize(this.root);
new Ajax.Request( this.url, {
"method": "post",
"parameters": query
});
}
}
function update(obj) {
new Effect.Highlight(this.root);
$('id').value = obj.id;
$('name').value = obj.name;
$('tel').value = obj.tel;
}
Event.observe(window, 'load', function() {
var myform = new CometForm('streaming','address','exec');
myform.connect("connecter");
});
</script>
</head>
<body>
<iframe id="connecter" frameborder="0" height="0" width="100%"></iframe>
<table id="address">
<tr>
<td>id</td>
<td><input type="text" name="id" id="id" /></td>
</tr>
<tr>
<td>氏名</td>
<td><input type="text" name="name" id="name" /></td>
</tr>
<tr>
<td>電話番号</td>
<td><input type="text" name="tel" id="tel" /></td>
</tr>
</table>
<button id="exec">サンプル実行</button>
</body>
</html>
package web;
import com.sun.grizzly.comet.CometContext;
import com.sun.grizzly.comet.CometEngine;
import java.io.IOException;
import java.util.logging.Logger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import web.handler.FormHandler;
public class StreamingServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger("servlet");
private String cometContextPath;
@Override
public void init(ServletConfig config) throws ServletException {
cometContextPath = config.getServletContext().getContextPath() + "/streaming";
CometContext context = CometEngine.getEngine().register(cometContextPath);
context.setExpirationDelay(10 * 60 * 60 *1000);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info("connect");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
FormHandler handler = new FormHandler();
handler.attach(response.getWriter());
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.addCometHandler(handler);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Address address = new Address();
address.setName(request.getParameter("name"));
address.setId(request.getParameter("id"));
address.setTel(request.getParameter("tel"));
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.notify(address);
}
}
package web.handler;
import com.sun.grizzly.comet.CometEvent;
import com.sun.grizzly.comet.CometHandler;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;
import net.sf.json.JSONObject;
import web.Address;
public class FormHandler implements CometHandler{
private PrintWriter writer = null;
private final static Logger logger = Logger.getLogger("handler");
private final static String SCRIPT_START_TAG = "";
public void attach(PrintWriter writer) {
this.writer = writer;
}
public void onEvent(CometEvent event) throws IOException {
if (event.getType() == CometEvent.NOTIFY) {
if (event.attachment() instanceof Address) {
Address output = (Address) event.attachment();
JSONObject jsonObject = JSONObject.fromObject(output);
logger.info(jsonObject.toString());
writer.write(SCRIPT_START_TAG);
writer.write("window.parent.update(" + jsonObject + ");");
writer.write(SCRIPT_END_TAG);
writer.flush();
}
}
}
public void onInitialize(CometEvent event) throws IOException {
}
public void onTerminate(CometEvent event) throws IOException {
onInterrupt(event);
}
public void onInterrupt(CometEvent event) throws IOException {
writer.close();
event.getCometContext().removeCometHandler(this);
}
}
2009年4月22日、東京千代田区にある放送会館で「アジャイルジャパン2009」が開催されました。本イベントは「ソフトウェア開発の次世代リーダーをつくる」ことを合い言葉に、200人以上の参加者を集めてスタートしました。
今週(2009年6月1日)AdobeはFlex 4の正式な初ベータ版をリリースしました。Flex 4はGumboというコードネームで開発されています。今回のリリースには大きな変更が多数含まれています。このRIAフレームワークの最新バージョンにおいて変更された事柄についての概要を以下のリストで見ていきましょう。
ビジネス領域の深い理解を反映したドメインモデルを設計するための、ヴィジョンとアプローチです。この本は、Eric Evans氏の「Domain Driven Design」の主要点を短く読みやすく要約しました。
この記事ではJavaプログラマがなぜFlexとBlazeDSを学ぶべきなのかについて13の理由を述べています。なぜ高度にインタラクティブなWeb サイトからJavaで開発されたバックエンドをもつエンタープライズ・アプリケーションまでを含む、リッチ・インターネット・アプリケーション(RIA)の開発にFlexとBlazeDSの組み合わせが最適な選択肢となるのかについて述べています。
Mary Poppendieck氏、Ron Jeffries氏、Jeff Patton氏、David West氏、Steve Freeman氏、Jason Yip氏が、バックログに関する彼らの意見とアジャイルチームを成功させるために必要な事を語った。
この記事ではAlex Devine氏が、Java開発者がPerf4Jをどのように利用できるかと、タイミングステートメントにコードを追加し、ロギング、結果の解析とモニタリングを行うオープンソースツールセットの説明をします。
この記事では低レベルのサービス・レイヤやリポジトリ・レイヤを肥大化させることなく、フェッチング・ストラテジによってモジュール化された方法でバックエンドにあるシステムからデータを取得する処理を最適化する方法について説明します。
No comments
スレッド表示 返信