トップ » 技術記事 » チャット作成で学ぶWebリモーティング(2) - DWRの基本

チャット作成で学ぶWebリモーティング(2) - DWRの基本

タグ: Ajax DWR Java javascript

DWRの基本的な使用法

ではいよいよ、DWRを使ってチャットアプリケーションを作成する方法を学んでいきましょう。今回はアプリケーションの実装を行う初めての回ですので、なるべく詳しく解説していきたいと思います。今回実装したチャットアプリは、連載全てを通しての基本となりますので、きちんと理解していただければと思います。

今回作成するチャットは以下のようなものです。ページにアクセスすると、今までの発言内容が全て一覧で表示されます。メッセージを投稿する場合は、発言したユーザ名とメッセージを入力し、「送信」ボタンを押すかEnterキーを叩きます。
chat_s.jpg

まずは、チャットの機能を提供する、以下のような簡単なJavaクラスを、DWRを用いて公開するまでの流れを説明します。

package remote;

import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * チャット用のリモーティングAPIを提供するクラス
 * @author Shumpei Shiraishi
 */
public class ChatServiceRemote {
    // メッセージのタイムスタンプを表示する際の日付フォーマット
    private DateFormat timestampFormat = new SimpleDateFormat("MM/dd HH:mm:ss");
    // チャットメッセージを保持するリスト
    private List<ChatMessageDTO> messages = new ArrayList<ChatMessageDTO>();

    /**
     * チャットメッセージを追加します。
     * @param messageDto メッセージを格納したDTO
     */
    public void addMessage(ChatMessageDTO messageDto) {
        messageDto.setTimestamp(timestampFormat.format(new Date()));
        messages.add(messageDto);
    }

    /**
     * 全てのチャットメッセージを取得します。
     * @return 全チャットメッセージ
     */
    public List<ChatMessageDTO> getAllMessages() {
        // HTMLエスケープしながらコピーし、返す

        List<ChatMessageDTO> result = new ArrayList<ChatMessageDTO>();
        for (ChatMessageDTO origMsg : messages) {
            ChatMessageDTO message = new ChatMessageDTO();
            message.setUser(escapeHtml(origMsg.getUser()));
            message.setText(escapeHtml(origMsg.getText()));
            message.setTimestamp(escapeHtml(origMsg.getTimestamp()));
            result.add(message);
        }
        return result;
    }
}

公開するメソッドは二つだけ、addMessage()とgetAllMessages()です。addMessage()はチャットメッセージの追加、getAllMessages()は全てのメッセージを取得します。メッセージは単純に、ArrayListに格納しておくことにします。
これらのメソッドで引数や戻り値となっているChatMessageDTOクラスは、以下のように単純なJavaBeansです。

package remote;

import java.io.Serializable;

/**
 * リモーティングでチャットデータの入れ物として用いられるBean
 * @author Shumpei Shiraishi
 */
public class ChatMessageDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    // 発言したユーザ
    private String user;
    // 投稿されたメッセージ
    private String text;
    // 投稿日時
    private String timestamp;

    … (フィールドのsetter/getterが続く) …
}

では、これらをAJAXアプリから呼び出せるように、DWRを使って公開します。

DWRのセットアップ

まずは、DWRをダウンロードしてセットアップを行いましょう。
DWRは、こちらのページからダウンロードすることができます。この記事を執筆している時点での最新バージョンは2.0.1です。JARファイル、WARファイル、そして全てのソースコードを含むZIPファイルをダウンロードできます。

ダウンロードが終わったら、dwr.jarがDWRを動作させるのに必要なファイルです。そのファイルを、WebアプリケーションのWEB-INF/libディレクトリにコピーします。

そして最後に、WEB-INF/web.xmlファイルに、DWRが提供するサーブレットを使用するよう記述を追加すれば、セットアップは完了です。web.xmlのスキーマに則って、以下のような設定を追加してください。

<!– DwrServletの設定 –>
<servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <display-name>DWR Servlet</display-name>
    <description>Direct Web Remoter Servlet</description>
    <servlet-class>
        org.directwebremoting.servlet.DwrServlet
    </servlet-class>
    <!– デバッグ出力をONにするための設定 –>
    <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<!– DwrServletとURLのマッピング –>
<servlet-mapping>
    <servlet-name>dwr-invoker</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
Javaクラスをリモートアクセス可能にするための設定

次に、Javaクラス内のメソッドにリモートからアクセス可能にするための設定を行います。通常、その設定はWEB-INF/dwr.xmlファイルで行います。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd">

<dwr>
  <allow>
    <!– チャットのリモーティングサービス –>
    <create creator="new" javascript="ChatService" scope="application">
      <param name="class" value="remote.ChatServiceRemote"/>
    </create>
    <!– DTOのコンバータ定義 –>
    <convert converter="bean" match="remote.ChatMessageDTO"/>
  </allow>
</dwr>

上に示したのは、Javaクラスを公開するための最低限の設定ですが、重要と言える点は全て網羅しています。まず<create>タグですが、これは公開するJavaクラスの生成方法を定義しています。属性の持つ意味は以下の通りです。

  • creator
  • … このクラスのオブジェクトを生成する方法を指定します。ここで使用している”new”は、DWRによってnewが行われることを意味します。たとえばここで”spring”を指定すると、Spring Frameworkにオブジェクトの生成を任せることになります。

  • javascript
  • … このクラスのインスタンスをJavaScript上で識別するための名称を指定します。

  • scope
  • … 生成したオブジェクトのスコープです。ここでは”application”を指定しているため、オブジェクトはWebアプリ内でグローバルに使用されます。

<create>の内部で指定している”class”パラメータは、公開するJavaクラス名を指定します。

<convert>タグでは、converter属性を用いてJavaとJavaScriptの間のオブジェクト変換方法を指定します。ここでは”bean”を指定することで、前述したBeanConverterを使用するよう設定しています。match属性では、このコンバータを適用する対象となるクラスの名称を指定します。

以上でサーバサイドでの設定は完了です。web.xmlへの設定などは、最初の一回だけ行えば済む話なので、DWRを用いてJavaクラスをリモートに公開するにはdwr.xmlに記述を追加するだけだということがお分かりでしょうか?

Javaのメソッドに、クライアントからリモートアクセスする

サーバサイドでの設定が完了したので、あとはクライアントを作成するだけです。
クライアント側では、DWRで公開したJavaクラスを利用するために、以下のscriptタグをHTML内に記述する必要があります。

<script src=”dwr/engine.js”></script>
<script src=”dwr/interface/ChatService.js”></script>

engine.jsには、DWRが使用する低レベルのコードが記述されているため、必ず読み込まなくてはなりません。
重要なのは二番目のscriptタグです。そのsrc属性には、「<DwrServletを呼び出すパス>/interface/<dwr.xmlで指定したJavaScript名>.js」と言うパスを指定します。この例のように指定すると、後は「ChatService.メソッド名()」と記述すれば、ChatServiceRemoteクラスのメソッドを呼び出すことができます。

あとは、ChatServiceのメソッドを利用している部分を抜き出して説明を行います。HTMLコード全体については記事の最後に掲載したものを眺めるか、プログラムをダウンロードしてください。

まずは、サーバからメッセージを取得して表示する関数showMessage()内で、メソッドgetAllMessages()の呼び出しを行っています。

    // メッセージを表示
    function showMessages() {
        abortRefresh();
        // リモーティングを用いてメッセージを取得し、表示
        ChatService.getAllMessages({
            callback: _showMessages,
            errorHandler: onError
        });
    }

getAllMessages()が正常に戻った場合は_showMessages()、例外が発生した場合はonError()を呼び出しています。_showMessages()は、取得したメッセージを画面に表示するためのDOM操作を主に行っています。

    // 取得したメッセージをページ内に表示する
    function _showMessages(messages) {
        var s = "";
        for (var i = 0, len = messages.length; i < len; i++) {
            var message = messages[i];
            s +=
                "<div class=’message-row’>" +
                "<span class=’user’>" + message.user + "</span>" + " > " +
                "<span class=’text’>" + message.text + "</span>" +
                " <span class=’timestamp’>(" + message.timestamp + ")</span>" +
                "</div>";
        }
        var messagesElem = document.getElementById("messages");

        // 表示領域にHTMLコードを挿入
        messagesElem.innerHTML = s;
        // スクロール位置を調整
        messagesElem.scrollTop = messagesElem.scrollHeight;

        // 次のチャット更新をスケジューリング
        refreshTimer = setTimeout(showMessages, 1000);
    }

ポイントは2つあります。

まず、サーバから取得した戻り値 (java.util.ArrayList型) を、通常のJavaScript配列のように扱い、かつその要素であるオブジェクトのプロパティに”message.user”のように、「.」演算子でアクセスしていることです。このように、DWRが用意したコンバータのおかげで、JavaオブジェクトをJavaScript内で取り扱うのが非常に容易です。

もう一つのポイントは、メソッドの一番最後でsetTimeout()メソッドを用いて、再度showMessage()を呼び出すようスケジューリングしていることです。これは、チャットがリアルタイムに更新されているよう見せかけるため、定期的にサーバアクセスを行うようにしているのです。このように、一定間隔ごとに処理を繰り返すことをポーリングと言いますが、単純なだけにリアルタイム性も乏しいものとなります。なぜなら、この処理は最大で1秒以上の遅延を伴うからです。
この部分は、次回DWRのReverse Ajax機能を用いて改善する点なので、心に留めておいてください。

また、メッセージを投稿する部分では、以下のようにしてaddMessage()メソッドを呼び出しています。

    // リモーティングを用いてメッセージ送信
    var chatMessage = {
        user: userField.value,
        text: messageField.value
    };
    ChatService.addMessage(chatMessage, {
        callback: function() {
            showMessages();
        },
        errorHandler: onError
    });

addMessage()の引数にJavaScriptオブジェクトを指定し、かつその戻り値を処理するためのコールバックも指定しているのがお分かりでしょう。第一引数に指定したオブジェクトは、DWRのコンバータによりChatMessageDTOクラスのオブジェクトへと変換され、Javaのメソッドに渡されます。このメソッドが正常に終了したら、表示を更新するためにshowMessage()を呼び出しています。

これまで説明したポイントを含む、HTMLコードの完全なリストを以下に示します。

chat.html

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
    <link rel="stylesheet" type="text/css" href="chat.css"></link>
    <script src="dwr/interface/ChatService.js"></script>
    <script src="dwr/engine.js"></script>
    <script type="text/javascript">

    var userField;
    var messageField;
    var refreshTimer;

    // ページ初期化時に呼び出される関数
    function init() {
        userField = document.getElementById("user");
        messageField = document.getElementById("message");
        // Enterキーを処理するためのイベント登録
        if (messageField.addEventListener) {
            messageField.addEventListener("keypress", messageField_onKeydown, false);
        } else {
            messageField.attachEvent("onkeypress", messageField_onKeydown);
        }
        window.onerror = onError;
        showMessages();
    }
    // メッセージフィールド上でEnterキーを押されたらメッセージ送信
    function messageField_onKeydown(e) {
        if (e.keyCode == 13) {
            sendMessage();
        }
    }
    // メッセージを送信する
    function sendMessage() {
        if (userField.value == "") {
            alert("ユーザ名を入力してください。");
            return;
        }
        if (messageField.value == "") {
            return;
        }
        // リモーティングを用いてメッセージ送信
        var chatMessage = {
            user: userField.value,
            text: messageField.value
        };
        ChatService.addMessage(chatMessage, {
            callback: function() {
                showMessages();
            },
            errorHandler: onError
        });
        messageField.value = "";
        messageField.focus();
    }
    // メッセージを表示
    function showMessages() {
        abortRefresh();
        // リモーティングを用いてメッセージを取得し、表示
        ChatService.getAllMessages({
            callback: _showMessages,
            errorHandler: onError
        });
    }
    function _showMessages(messages) {
        var s = "";
        for (var i = 0, len = messages.length; i < len; i++) {
            var message = messages[i];
            s +=
                "<div class=’message-row’>" +
                "<span class=’user’>" + message.user + "</span>" + " > " +
                "<span class=’text’>" + message.text + "</span>" +
                " <span class=’timestamp’>(" + message.timestamp + ")</span>" +
                "</div>";
        }
        var messagesElem = document.getElementById("messages");
        messagesElem.innerHTML = s;
        messagesElem.scrollTop = messagesElem.scrollHeight;
        // 次のチャット更新をスケジューリング
        refreshTimer = setTimeout(showMessages, 1000);
    }
    // エラーが発生した際呼び出される関数
    function onError(errMsg) {
        abortRefresh();
        userField.disabled = true;
        messageField.disabled = true;
        document.getElementById("submitButton").disabled = true;
        alert("エラーが発生したため、アプリケーションを使用不可にします。" + errMsg);
    }
    // チャットのリフレッシュを中止する
    function abortRefresh() {
        if (refreshTimer) {
            clearTimeout(refreshTimer);
            refreshTimer = null;
        }
    }
    </script>
</head>
<body onload="init()">
    <h1>AJAXチャット</h1>
    <a id="dummy-for-scroll-messages" style="visibility: hidden;" href="#message-last"></a>
    <div id="messages"></div>
    お名前:<input type="text" id="user"/>
    <input type="text" id="message"/>
    <button id="submitButton" type="button" onclick="sendMessage()">送信</button>
</body>
</html>

以上で、DWR2を用いたチャットの基本的なプログラムについての解説を終わりとします。様々なことを学びましたが、とりあえず以下のことを押さえておけばDWRは十分に使いこなせるでしょう。

-dwr.xmlで、公開するJavaのクラスとコンバータを設定する
-クライアント側では、scriptタグを用いて公開されたJavaクラスを読み込む
-リモートメソッドの呼び出し方法と戻り値の受け取り方

今回学んだ知識は極めて基本的なDWRの使用方法です。次回は、アノテーションやReverse Ajaxと言った、バージョン2.0の新機能について解説を行います。

以下は、今回のサンプルプログラムをダウンロードするためのリンクです。ぜひダウンロードして動作させてみてください。

サンプルプログラム

Series Navigation«イントロダクションDWRのアノテーションを使用する»

1 2

執筆者紹介

shiraishi

shiraishi

最近書いてばっかりいます。 眠いとおんなじことばかり書きます。 そして、大概眠いです。

TrackBack URL :