Google Web Toolkit(GWT)を比較してみる

Google Web Toolkit(GWT)はなかなか面白いWebフレームワークですね。WicketやASP.NETとはまったく違ったアプローチでWebページをデザインできます。
いまさらながら、その特徴について書いてみようと思います。

いきなりまとめ

結論を先にまとめてみました。この比較を導いた考察を説明していきます。

Wicket ASP.NET GWT
イベントハンドラの記述 Java .NET Java
イベントハンドラの実行 サーバ サーバ クライアント(JavaScript)
コンポーネントの状態保持 サーバセッション ViewState なし(クライアントでの動作なので不要)
画面デザイン HTML ASP(デザイナあり) コードによるコンポーネント階層構築(HTMLも可?)

JavaScriptコンパイラ

GWTはコンポーネント志向でWebアプリケーションを作成できるわけですが、最大の特徴はJava→JavaScriptへのコンパイル機能です。Javaで記述したコンポーネントのイベントハンドラのロジックは、JavaScriptに変換されて実行されます。ただし、開発時に使用するホステッドモードという状態ではJavaのまま動作します。
WicketやASP.ENTはイベントハンドラのロジックはサーバーサイドで動作します。
ASP.NETやWicket
GWTはJavaScriptに変換されてクライアントサイドで動作します。でもコードはJavaで記述できます。ここが大きく異なります。
JavaScriptへコンパイル
クライアントで実行

例えば、ボタンを押したら、”AフィールドとBフィールドの値をから、ゴニョゴニョ計算してその結果をCフィールドに表示する”といったコードを記述した場合、WicketとGWTはほとんど似たような記述になると思います。でもWicketの場合はサーバーサイドで処理を実行します。一方GWTは全部クライアントで行うわけです。不思議ですねえ。

ホステッドモードの場合は、コンパイルを行わずにJavaコードのままで実行されます。ホステッドモードは開発用の専用ブラウザでのみ使用可能です。JavaScriptへのコンパイルは結構時間がかかるので、開発時の動作確認はホステッドモードで行うわけです。(ロジックはJavaで記述されているので、Javaコードとして実行するのは簡単)Javaコードとして実行するのでJVMの強力なデバッグ機能(ステップ実行や変数参照)が使用可能です。

RPC

GWTのもうひとつの機能としてRemote Procedure Calls(RPC)が用意されています。これはサーバーサイドのメソッドを呼び出すための機能です。サーバーサイドのメソッドをJavaScriptから呼び出し、引数の変換(JavaScript→Java)や戻り値の変換(Java→JavaScirpt)を行います。ただし前述の通り、クライアントサイドの処理をJavaで記述しているので、ソースコード的には普通にJavaでメソッドを呼び出しているような記述になります。不思議ですねえ。(ホステッドモードのときはそのままJavaメソッドを呼び出すように動作している)

私の勘違い

私は最初はこのような勘違いをしていました。
「GWTはAjaxをバリバリ使ったWebフレームワークで、ASP.NET Ajaxと似たようなもんだろう」
いや違います。Ajaxの機能もありますが、真髄はJavaScriptコンパイラにあります。クライアントサイドでバリバリ動作するようなアプリケーションこそGWTに最適かと思います。
「TextBoxに値をセットする」とか、クライアントでもできる処理を全部サーバに持っていったのがWicketやASP.NETです。おかげでサーバサイド言語で統一して記述できるようになりました。一方、GWTはJavaScriptで実行しますが、Java言語で記述できるようにしたものです。「TextBoxに値をセットする」という処理はクライアントで実行します。セットする値がサーバサイドで取得する場合はRPCを使って取得します。

他フレームワークのAjaxコンポーネント

WicketのAjaxコンポーネントやASP.NET Ajaxでは、ページ全体を送信してないわけですが、(私の認識が間違っていなければ)ページの描画HTMLはサーバサイドで作っています。フォーム情報を全部サーバに送信し処理結果のHTMLの“一部”を返却して描画を置き換えています。GWTの場合はメソッドの引数にあたるオブジェクトを送信し、結果をオブジェクトとして受け取ります。そして描画の書き換えはJavaScriptで行います。HTMLを生成して書き換える方式に比べて処理効率がよいと考えられます。

GWTの不安点

GWTのデメリットとしては「JavaScriptコンパイラをどこまで信頼できるか」でしょう。私が評価として作成している間はコンパイルの不具合はなかったですが、もしも「ホステッドモードでの動作とJavaScriptでの動作が異なる」という現象が起きたときには手も足もでなくなりそうです。そういった意味でWicket AjaxやASP.NET Ajaxのアプローチは安全です。
JavaScriptコンパイラは、JRE1.4の基本的なAPIについて対応しています。
1画面ですべて完結するようなGmailのような画面はいいのですが、画面遷移する場合はどうしたらよいのか分からない。(当然、遷移したら画面の状態は保持できなくなる)

Java徹底活用 Google Web ToolkitによるAjaxアプリケーション開発
Java徹底活用 Google Web Toolkitによる
Ajaxアプリケーション開発


Eclipse Plugin「シェル拡張」

初めてEclipseのPlugin作ってみた。
ファイルやフォルダを右クリックして、エクスプローラで開くとかそういったプラグインです。似たようなプラグインは少なくとも3つ知っているけど、自分好みの機能をまとめて用意してみました。

シェル拡張プラグイン

シェル拡張プラグイン

  • フォルダ(もしくは、ファイルの親フォルダ)をエクスプローラで開く
  • フォルダ(もしくは、ファイルの親フォルダ)をコマンドプロンプトで開く
  • ファイル、フォルダのフルパスをコピー
  • ファイル名、フォルダ名をコピー
  • ファイル、フォルダのURLをコピー
  • ファイルを実行


Eclipseプラグイン開発
Eclipseプラグイン開発の良書
最後のファイルを実行は、「アプリケーションから開く」-「システム・エディタ」(標準機能)とほとんど同じなのですが、実行するときの作業フォルダがファイルの置いてあるフォルダである点が違います。例えばバッチファイルをEclipse上から実行するときなどに便利です。
URLをコピーは、リソースのクラスパスを書くときなんか便利です。/jp/hoge/foo/A.properties なんていう文字列を作るときに便利じゃないかなぁと。
Plugin開発はなかなか分かりにくいんだけど、Eclipse自体にPlugin開発のプロジェクトが用意されてて至れりつくせりなのは便利だと思った。GUIとか使わないプラグインなら簡単に作ることができるな。


iBATISのGenerics対応を考える

ActiveObjectsのGenericsの使い方はとても参考になるなあ。Genericsって使うことはあったけどAPI作成側になって考えると奥深い。
愛用しているiBATISもGenricsが使えたらいいのに」と思って考案してみました。

SqlMapClientGenというクラスを作って、処理の大部分を内包しているSqlMapClientへ委譲する。Generics対応したいところだけ書き換えた。@deprecatedになっているメソッドは廃止した。あとGenerics対応したRowHandler、RowHandlerGenを作成した。
select系だけしか関係ないので、updateやinsertは変更していない。

実装コードはこちら。

シンプルなコード例(simple examples)


//生成
SqlMapClientGen client = new SqlMapClientGen(originalSqlMapClient);

newしてるけど、DIコンテナなんかで生成するようにしたほうがかっちょこいいでしょう。


//queryForList

List empList = client.queryForList("select");
List empList = client.queryForList("select");

//拡張for文 (enhanced for statement)
for (EmpEntity emp : empList){
System.out.println(emp);
}

//queryForObject
EmpEntity emp = client.queryForObject("selectByPK", 7698);
EmpEntity emp = client.queryForObject("selectByPK", 7698);

メソッド名の前にパラメータクラスを指定。
2008/06/04追記:戻り値で型の特定が出来るので、メソッド名の前に型パラメータを指定するのは、必ずしも必要ではなかったです。


//queryWithRowHandler
client.queryWithRowHandler("select", new RowHandlerGen(){
@Override
public void handleRow(EmpEntity row) {
System.out.println("[" + row.getEmpno() + "]" + row.getEname());
}
});

RowHandlerはクラス名のあとにパラメータクラス名を指定。

Generics対応といっても本格的なことはしておらず、SqlMapClientGen側で@SuppressWarningsしているだけです。なのでSqlMap.xmlで定義したresultClassと型があっていなくても、コンパイルは出来てしまいます。(タイプセーフとはいえない)
「パラメータクラス<t>を書くのと、キャスト(T)するのは手間は同じじゃないか!」と言われそうですが、キャストよりもすっきりしませんか?(しないか)でもRowHandlerあたりはちょっといい感じじゃないかしら?それに利用コード側には@SuppressWarningsがつかないから良いかと。
でも、SqlMap.xmlとGenericsで同じクラス名書いているのはDRY原則に反するので、SqlMap.xmlのresultClass名は記述不要にするとか。そうしたら大改造だな。

2008/06/04追記:戻り値でListを受け取る場合などは、型パラメータを指定する必要はなかったです。
例えば、次のような拡張forの場合は型パラメータを指定しなければなりません。
for (EmpEntity emp : client.queryForList("select"))
一度Listで受け取る場合には不要でした。

List empList = client.queryForList("select");
for (EmpEntity emp : empList)

いっそう便利に使えるかと思います。

iBATIS in Action
iBATIS in Action