プロのように Ajax アプリケーションを開発する: 第 3 回 DWR、Java、そして Dojo Toolkit を使って Java と JavaScript を統合する

Java™ による Web 開発のためのフレームワークやライブラリー、ツールキットでパッと思いつくものの名前を挙げるとしたら、皆さんはいくつ挙げられるでしょう。世の中にはあまりにも多くの種類があるため、どれがどんなことをし、どれが実際に問題解決の役に立つのかを知ろうとしても種類の多さに圧倒されてしまうかもしれません。しかし Ajax 開発を行うのであれば絶対に知っておく必要があるライブラリーがあります。それが DWR (Direct Web Remoting) です。このライブラリーは Java 言語と Java を使った Web 技術を活用して Ajax 開発を大幅に単純化します。DWR は Ajax をシームレスに Java Web アプリケーションに統合するための標準的な方法となっています。実際、DWR は、よく使われるオープンソースの Ajax 技術を広範に集めた Dojo の基礎の上に構築されています。この記事では、DWR を使用することで Ajax がどれほど容易になるかを説明します。

Michael Galpin, Developer, eBay

Galpin Michael photoMichael Galpin は、1998年以来、プロとして Java ソフトウェアを開発してきています。彼は現在 eBay に勤務しています。彼は California Institute of Technology で数学の学位を取得しています。



2008年 8月 05日

この記事は、Ajax を活用したアプリケーションを作成する際によく使われる JavaScript ライブラリーに関する 3 回シリーズの第 3 回目であり、最終回でもあります。第 1 回では曲を管理するための Web アプリケーションを Prototype ライブラリーを使って作成する方法を学びました。第 2 回では写真を管理するための Web アプリケーションを Scriptaculous ライブラリーを使って作成する方法を説明しました。この記事では DWR を使って Ajax 開発を単純化する方法について説明します。

この記事では DWR のバージョン 2.0 を使用しています。この記事で紹介するサンプル・コードでは Generics とアノテーションの両方を使用するため、Java 5 以上が必要です。またサンプル・アプリケーションでは MySQL 5.12 と Tomcat 6.0.14 を使っていますが、他のものを使ったとしても、そのための作業はそれほど大変ではないはずです。さらに、このアプリケーションはデータ・アクセスに JPA を使っており、JPA の実装としては OpenJPA 1.0 を使っていますが、この実装に関しても、代わりに他の JPA 実装 (Hibernate や Kodo など) を使うこともできます。この記事では Firefox 用の Firebug プラグインを使っています。それは Firebug が Ajax のデバッグ用のツールとして非常に優れているためです。これらのツールへのリンクは「参考文献」を参照してください。

DWR (Direct Web Remoting) の紹介

Ajax アプリケーションは、最初は魔法のように思えるかもしれませんが、幸いなことに Ajax アプリケーションを作成するためのプロセスは単純です。Ajax のすべてのやり取りに対して、サーバー・サイドのコードとクライアント・サイドのコードにエンドポイント (Web サービスの友人達から用語を借用しました) を作成し、そのエンドポイントを呼び出す必要があります。また、クライアントとサーバーとの間で送受信されるデータをシリアライズするためのすべてのコードを作成する必要があります。サーバー・サイドのエンドポイントは汎用のサービスにすることができます。おそらく RESTful なサービスにすることもできますが、ほとんどの場合、クライアントのニーズに合うように非常に特化されたものになっています。密結合を避けなければならない場合もあれば、密結合を受け入れなければならない場合もあります。後者の場合において、DWR はターンキー・ソリューションです。DWR を利用すると、サーバー・サイドのコードを Ajax エンドポイントとして宣言して公開することができ、そのための基本作業はすべて魔法のように自動的に DWR が行ってくれるのです。では DWR がどのように動作するのか、具体的な例を調べてみましょう。


サンプル・アプリケーション: Ajax によるメッセージ・ボード

ここで使用するサンプル・アプリケーションは単純なメッセージ・ボードです。データ・モデルは非常に単純化されているため、DWR によって仲介される Ajax のやり取りに集中することができます。最初にこのアプリケーションのバックエンドを検証し、このバックエンドの上に DWR がどのように階層化され、アプリケーションの Ajax 機能を実現しているかを調べます。


バックエンドをセットアップする

まず、メッセージ・ボード用のデータベース・テーブルを作成します。リスト 1 に、このテーブルを作成するための SQL スクリプトを示します。

リスト 1. messages テーブルを作成するための SQL スクリプト
CREATE TABLE 'messages' (
  'id' int(11) NOT NULL auto_increment,
  'title' varchar(40) NOT NULL,
  'body' text,
  'created_at' timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  'author' varchar(40) NOT NULL default 'anonymous',
  PRIMARY KEY  ('id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ここで唯一注意しなければならないことは、メッセージ・ボードの各メッセージに対して title、body、author を提供する必要があるという点です。あとはこの SQL スクリプトが処理してくれます。ここに示したスクリプトは MySQL 用ですが、他の RDBMS にも容易に適用することができます。データ・アクセスに関しては JPA を使います。このテーブルに対応する Java クラスをリスト 2 に示します。

リスト 2. メッセージの Java クラス
package org.developerworks.msgb;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Column;

import org.directwebremoting.annotations.DataTransferObject;

@DataTransferObject
@Entity
@Table(schema="msgb", name = "messages")
public class Message {
     @Id
     @GeneratedValue(strategy=IDENTITY)
     private int id;
     
     @Column(name="title")
     private String title;
     
     @Column(name="author")
     private String author;
     
     @Column(name="body")
     private String body;
     
     @Column(name="created_at")
     private Date createdAt;

     public int getId() {
          return id;
     }

     public void setId(int id) {
          this.id = id;
     }

     public String getTitle() {
          return title;
     }

     public void setTitle(String title) {
          this.title = title;
     }

     public String getAuthor() {
          return author;
     }

     public void setAuthor(String author) {
          this.author = author;
     }

     public String getBody() {
          return body;
     }

     public void setBody(String body) {
          this.body = body;
     }

     public Date getCreatedAt() {
          return createdAt;
     }

     public void setCreatedAt(Date createdAt) {
          this.createdAt = createdAt;
     }
}

このクラスの大部分はボイラープレート・コードであり、ゲッターとセッターを持ついくつかのフィールドがあるにすぎません。またアノテーションの大部分は、Java のフィールドをデータベースの列にマッピングする標準的な JPA アノテーションにすぎません。ただし、注目に値する風変わりなアノテーションが 1 つあり、それが @DataTransferObject アノテーションです。これは DWR のアノテーションであり、このアノテーションを付けたクラスが自動的にマーシャリングされて Ajax レスポンスの一部としてネットワーク全体に送信されることを DWR に伝えます。DWR には Java の型をマーシャリングするためのコンバーターがいくつか含まれています。またオプションとして、どのフィールドを含め、どのフィールドを含めないかを DWR によって指定することもできますが、この場合にもアノテーションを使用します。これでデータ・アクセスのセットアップができたので、アプリケーション用のサービスの作成について調べてみましょう。


リモート・サービスを作成する

このサンプル・アプリケーションでは簡単な 2 つのことが行われます。このアプリケーションでは、メッセージ・ボードに投稿されたすべてのメッセージを一覧表示し、またユーザーが新しいメッセージをメッセージ・ボードに投稿できるようにします。それを念頭に置いた上で、リスト 3 を見てみましょう。リスト 3 にはアプリケーションのサービスのコードを示してあります)。

リスト 3. MessageService クラス
package org.developerworks.msgb;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;

@RemoteProxy
public class MessageService {
     private EntityManagerFactory factory;
     private EntityManager em;
     
     public MessageService(){
          factory = Persistence.createEntityManagerFactory("msgb");
          em = factory.createEntityManager();
     }
     
     @RemoteMethod
     @SuppressWarnings("unchecked") // thanks type erasure
     public List<Message> getMessages(){
          List<Message> messages = em.createQuery("select m from 
		                                  Message m").getResultList();
          return messages;
     }
     
     @RemoteMethod
     public void saveMessage(Message msg){
          em.getTransaction().begin();
          em.persist(msg);
          em.getTransaction().commit();
     }
}

このコードは、メッセージに対するクエリーを実行したり、メッセージを作成したりするための標準的な JPA コードにすぎません。この場合も、興味深いコードはアノテーションにあります。このクラスには RemoteProxy というアノテーションが付けられており、このアノテーションは DWR に対して、リモート・クライアントがこのクラスの中のメソッドを呼び出せることを伝えています。もちろん、そうしたリモート呼び出しのための仕組みが、DWR を使用した Ajax です。このクラスの各メソッドは RemoteMethod アノテーションを使って明示的にリモート・クライアントに公開されています。公開したくないメソッドがある場合には、このアノテーションを省略します。ここまでに作成したコードはすべて、単なるバックエンド・コードと少しばかりのアノテーションにすぎません。バックエンドに DWR をセットアップするためには、もう 1 つのことをする必要があります。


DWR サーブレット

DWR は Java サーブレットを使って Ajax リクエストを受け付け、そして応答します。このサーブレットは DWR のライブラリーに含まれているため、必要なことは Web アプリケーションの web.xml ファイルの中でこのサーブレットを有効にするようにアプリケーションを構成することだけです。リスト 4 にこのサンプル・アプリケーションの web.xml を示します。

リスト 4. メッセージ・ボードの web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://java.sun.com/xml/ns/javaee"
     xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	     http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
     id="msgb" version="2.5">
     <display-name>MessageBoard</display-name>
     <welcome-file-list>
          <welcome-file>index.html</welcome-file>
     </welcome-file-list>
     <servlet>
          <display-name>DWR Servlet</display-name>
          <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.developerworks.msgb.MessageService, 
			   org.developerworks.msgb.Message</param-value>
          </init-param>
     </servlet>
     <servlet-mapping>
          <servlet-name>dwr-invoker</servlet-name>
          <url-pattern>/dwr/*</url-pattern>
     </servlet-mapping>
</web-app>

このコードでは単に DWR サーブレットを公開し、/dwr/ で始まるすべての URL をこのサーブレットに送信しています。また、init-params 要素の classes というパラメーターの値には、DWR アノテーションが付けられたすべてのクラスをカンマで区切って指定しています。アノテーションを使うのを好まない場合は、同じ情報を XML ファイルを使って保持できることに注意してください。バックエンドに対して必要なことはこれだけです。今度はアプリケーションのフロントエンドで DWR を使ってみることにしましょう。


フロントエンドを作成する

DWR を利用すると、アプリケーションのバックエンドで容易に Ajax を実現することができます。必要なことは、いくつかの宣言を行い、そして DWR サーブレットを有効にすることだけです。ではフロントエンドに関してはどうするのでしょう。大部分の JavaScript ツールキットにはフロントエンドで使えるライブラリーが用意されており、普通はそれらのライブラリーを Web ページの中で参照し、ドキュメントを読んで使い方を学びます。しかし DWR の場合には、その必要はありません。

DWR は JavaScript を動的に作成してくれます。幸いなことに、DWR を利用すると、その JavaScript の使い方も容易にわかるのです。リスト 4 の web.xml ファイルをもう一度見てください。init-param 要素で debug パラメーターを指定していることに気が付いたでしょうか。この設定によって、DWR によって作成された動的な DWR JavaScript のイントロスペクションを行えるのです。そのためには Web アプリケーションをデプロイし、http://<root>/<web_app_name>/dwr という URL を指定します (図 1)。

図 1. DWR のデバッグ画面
DWR のデバッグ画面

この画面は @RemoteProxy 宣言によりアノテーションが付けられたすべての Java クラスを表示します。これらのクラスのうちの任意のクラスをクリックすると、そのクラスの詳細が表示されます (図 2)。

図 2. MessageService JavaScript の情報
MessageService JavaScript の情報

このインターフェースには、Web ページから動的な JavaScript を参照する方法が表示されます。参照する JavaScript としては、必ず含めなければならないコア・ライブラリー (engine.js) と、このアプリケーション専用の純粋に動的なライブラリー (MessageService.js) があります。そしてオプションとして、無数のヘルパー関数を提供するユーティリティー・ライブラリーがあります。

これで DWR の JavaScript を参照する方法がわかりましたが、この JavaScript の使い方を理解しなければなりません。MessageBoard ページでは、その JavaScript を試しに使ってみることができます (図 3)。

図 3. DWR の Ajax を試しに使ってみる
DWR の Ajax を試しに使ってみる

これは getMessages() 呼び出しに対する Ajax リクエストの結果を示しています。このデータは JSON (JavaScript Object Notation) の配列として示されています。この API が何を返すのかはわかりますが、この API を呼び出すにはどうすればよいのでしょう。では、このページのソースを見てみましょう (リスト 5)。

リスト 5. MessageBoard テスト・ページのソース
<li>
  getMessages(  );
  <input class='ibutton' type='button' onclick='MessageService.getMessages(reply0);'
     value='Execute'  title='Calls MessageService.getMessages(). 
     View source for details.'/>
  <script type='text/javascript'>
    var reply0 = function(data)
    {
      if (data != null && typeof data == 'object') 
	            alert(dwr.util.toDescriptiveString(data, 2));
      else dwr.util.setValue('d0', dwr.util.toDescriptiveString(data, 1));
    }
  </script>
  <span id='d0' class='reply'></span>
</li>
<li>
  saveMessage(    <input class='itext' type='text' size='10' value='' 
    id='p10' title='Will be converted to: org.developerworks.msgb.Message'/>  );
        <input class='ibutton' type='button' onclick='MessageService.saveMessage
       (objectEval($("p10").value), reply1);' value='Execute'  title='Calls 
	    MessageService.saveMessage(). View source for details.'/>
  <script type='text/javascript'>
    var reply1 = function(data)
    {
      if (data != null && typeof data == 'object') alert(dwr.util.
	                                                 toDescriptiveString(data, 2));
      else dwr.util.setValue('d1', dwr.util.toDescriptiveString(data, 1));
    }
  </script>

  <span id='d1' class='reply'></span>
</li>

これを見るとわかるように、例えば getMessages を呼び出すためには MessageService.getMessages(callbackFunction) を使います。callbackFunction に何が渡されるかは、このメソッドを試しに使ったときに確認できました。これで Web ページを作成する準備が整いました。このページをリスト 6 に示します。

リスト 6. MessageBoard の Web ページ
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN"
 "http://www.w3.org/TR/html4/strict.dtd">
 <html>
<head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
     <title>The developerWorks Message Board</title>
      <script type="text/javascript" src="/MessageBoard/dwr/interface/
                                      MessageService.js"></script>
      <script type="text/javascript" src="/MessageBoard/dwr/
                                      engine.js"></script>
      <script type="text/javascript" src="/MessageBoard/dwr/
                                      util.js"></script>
      <script type="text/javascript">
           var columns = ["title", "author", "body", "createdAt"];
           function loadData(){
                MessageService.getMessages(addRows);
         }
           // messages should be an array of Message hashes
           function addRows(messages){
                var cellfuncs = [];
                for each (var prop in columns){
                     cellfuncs.push(createFunction(prop));
                }
                dwr.util.addRows("messageTableBody", messages, cellfuncs);     
           }
           function createFunction(prop){
                return function(msg) { return msg[prop]; };
           }
           function sendMsg(){
                var msg = {};
                msg.title = dwr.util.getValue("title");
                msg.author = dwr.util.getValue("author");
                msg.body = dwr.util.getValue("body");
                MessageService.saveMessage(msg);
                msg.createdAt = Date();
                var messages = [msg];
                addRows(messages);
                clearForm();
           }
           function clearForm(){
                dwr.util.setValue("title","");
                dwr.util.setValue("author","");
                dwr.util.setValue("body","");
           }
      </script>     
      <style type="text/css">
           label {
               float: left;
               text-align: right;
               margin-right: 15px;
               width: 100px;
          }
          #messageTableHead {
               font-weight: 900;
               color:navy;
          }
          body {
               color:MidnightBlue;
               background-color:PaleTurquoise;
               margin:20px;
               padding:0px;
               font:11px verdana, arial, helvetica, sans-serif;
          }
          .pageTitle {
               margin:0px 0px 15px 0px;
               padding:0px;
               font-size:28px;
               font-weight:900;
               color:#aaa;
          }
          #formDiv{
               padding-top:12px;
               color:Indigo;
          }
      </style>
</head>
<body onLoad="loadData()">
     <div class="pageTitle">The developerWorks Message Board</div>
     <div class="pageContent">
          <table id="messageTable" border="1" cellpadding="4">
               <thead id="messageTableHead">
                    <tr>
                         <td>Title</td>
                         <td>Author</td>
                         <td>Message</td>
                         <td>Posted</td>
                    </tr>
               </thead>
               <tbody id="messageTableBody">
               </tbody>
          </table>
     </div>
     <div id="formDiv">
          <form id="messageForm">
               <label>Message Title:</label><input type="text" 
	                                name="title" id="title"/><br/>
               <label>Your Name:</label><input type="text"
	                              name="author" id="author"/><br/>
               <textarea name="body" cols="80" rows="15"
	                              id="body"></textarea><br/>
               <input type="button" id="msgBtn" value="Add Message"
	                              onClick="sendMsg()"/>
          </form>
     </div>
</body>
</html>

このコードを分析してみましょう。まず、これは単純な HTML ファイルです。JSP や JSF ページではなく、JavaScript と CSS を持つ静的な HTML にすぎません。このページはロードされると loadData() 関数を呼び出します。この関数では、今学んだばかりの MessageService.getMessages() を呼び出し、引数として、DWR が作成してくれる Ajax リクエストに対するレスポンスを処理する addRows 関数を渡します。

addRows 関数は、サーバーから返されるデータを取得し、さらに DWR に用意されているユーティリティー関数の 1 つを使います。そのユーティリティー関数が dwr.util.addRows. であり、この関数は HTML の表、表のヘッダー、表のフッター、または表の本体のいずれかの ID を取得し、その表に行を追加します。そして関数の配列 (コードの中では cellfuncs) に格納されている各関数を使うことで、表のセルに入れられるようにデータの抽出と変換を行います。つまり表の (i,j) 番目の要素のデータは cellfuncs[j](messages[i]) です。これはデータを表にマッピングするための方法として非常に簡潔です。このページをブラウザーで表示すると、図 4 のようになるはずです。

図 4. MessageBoard のページ
MessageBoard のページ

このページは DWR によって作成された Ajax を使って非同期にデータをロードします。これでメッセージを表示することができますが、新しいメッセージの保存はどうするのでしょう。そのためには、このページの一番下にあるフォームを使います。Add Message をクリックすると、DWR のユーティリティー・メソッド dwr.util.getValue を使ってフォームからデータが取得されます。このユーティリティー関数は、div からテキスト入力やチェックボックス、選択リストに至るまで、すべての HTML 要素に対して機能します。データを取得し、JavaScript オブジェクトの中に入れ、MessageService.saveMessage() を呼び出します。ここではハンドラーを指定しませんでしたが、それはレスポンスが無効であるためです。代わりに、新しい行を表に追加するために先ほど使用した addRows 関数を即座に再利用しています。こうすることによって UI の応答性を大きく高めることができます。

ページをテストするためには、フォームに内容を入力して Add Message をクリックします。HTTP のトラフィックを観察するためには Firebug のようなツールを使うと便利です。リスト 7 は saveMessage を呼び出すことで送信されたデータを示しています。

リスト 7. saveMessage 呼び出し
callCount=1
page=/MessageBoard/
httpSessionId=
scriptSessionId=B88B0681A9BB674C14786B7DCA3EA6E3153
c0-scriptName=MessageService
c0-methodName=saveMessage
c0-id=0
c0-e1=string:The%20One%20I%20Love
c0-e2=string:Michael
c0-e3=string:This%20goes%20out%20to%20the%20one%20I%20love.
                  %20This%20one%20goes%20out%20to%20the%20one
%20I%20left%20behind.
c0-param0=Object_Object:{title:reference:c0-e1, 
                  author:reference:c0-e2, body:reference:c0-e3}
batchId=1

リスト 7 は DWR によって送信されるデータのフォーマットを示していますが、このデータのフォーマットはあまり重要ではありません。DWR のようなフレームワークを使うことによる利点の 1 つは、こうしたデータのフォーマットなどを気にする必要がなくなることです。しかし、これらのデータがどのように動作しているかを見るのは興味深いものです。

※訳注: 上記段落の原文に Figure 7 とありますが、Listing 7 の誤りのようなのでそのように解釈して訳しています。


まとめ

世の中には Ajax 用のツールキットが多数あり、それらを利用することで Ajax 開発のさまざまな部分が容易になります。DWR は Java を使って作成した Web アプリケーションのツールであり、何から何までをやってくれます。DWR を利用することでサーバー・サイドのアプリケーションとクライアント・サイドのアプリケーションのどちらも容易に作成することができます。サーバー・サイドでは、いくつか Java アノテーションを付加するだけで任意のサービスを Ajax サービスに変えることができます。またクライアント・サイドに関しては、サーバー・サイドの機能に対応する機能を持つ API があります。追加することが必要なものはコールバック関数のみであり、これ以上簡単にならないほど簡単なのです。


ダウンロード

内容ファイル名サイズ
Part 3 sample codewa-aj-ajaxpro3.zip1087KB

参考文献

学ぶために

製品や技術を入手するために

  • DWR のバージョン 2.0 を利用すると、ブラウザー上の JavaScript を使ってサーバー上の Java とやり取りすることができ、またその結果を使用する Web ページを操作することができます。
  • Firefox 用の Firebug 拡張機能を入手してください。
  • Apache Tomcat 6.0.14 を入手してください。
  • MySQL 5.0.41 を入手してください。
  • OpenJPA 1.0.2 を入手してください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=337125
ArticleTitle=プロのように Ajax アプリケーションを開発する: 第 3 回 DWR、Java、そして Dojo Toolkit を使って Java と JavaScript を統合する
publish-date=08052008