seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

Google Web Toolkit(GWT)を使ってみる

Google Web Toolkit(GWT)とは

Google Web Toolkit(GWT)とは、Googleオープンソース(Apache 2.0 licence)で提供している、ウェブブラウザを使ったクライアントサイドのアプリケーションを作成するための開発ツールとライブラリである。
HTMLファイルはハコだけを提供し、それ以外のほとんどのもの、たとえば画面上のコンポーネントの配置やロジックは、すべてJava言語を使って記述する。
これらはコンパイルすることにより静的なHTML/XMLファイルとJavaScriptに変換され、これがクライアント上のブラウザを実行環境として動的にページを組み立てるように動作するようになる。

特徴
  • JavaのAWT/Swingのような形でプログラミング的にレイアウトやコンテンツを作成してゆく
    • SwingやAWTが使えるわけではない。
    • com.google.gwt.user.client.ui.*パッケージのUIクラスを使う
  • Javaのライブラリとして、java.lang.util.*系のコレクションクラスが使える。
  • Javaの文法はJavaScriptに変換される

…ということらしい。

インストール方法

http://code.google.com/webtoolkit/
から、GWTをダウンロード。
インストーラは存在せず、普通に圧縮解除するだけで良い。

サンプルの実行方法

パスにjava.exeが通っているならば、/samplesディレクトリに各種サンプルがあるので、そのサンプルディレクトリに移動し、

    xxxx-shell.cmd

のように叩けば、サンプルを試すことができる。

タブや、ドラッカブルなメッセージボックス(ダイアログやポップアップではない)、Windowsライクなツリービューやリストビュー、YahooのWEBサービスへのアクセス例など、Ajax的なウェブアプリケーションを作成する主要な動機となりそうな多くのUIや機能を試すことができる。
これは、さっそく自分でも試したくなる。

ためしにプロジェクトを作成してみる。

プロジェクトを作成するには2つのツールを使う。

  • projectCreator.cmd (Eclipseのプロジェクトを作成するためのツール)
  • applicationCreator.cmd (プロジェクトの雛形ソースコード(スケルトン)を作成するツール)
コマンド例
    projectCreator.cmd -out TestGWTApp -eclipse TestGWTApp
    applicationCreator.cmd -out TestGWTApp -eclipse TestGWTApp jp.seraphyware.client.TestGWTApp

-out のオプションは、出力先フォルダで、指定しないとカレントディレクトリ上に作成される。
パッケージ名のネーミングルールとして、パッケージ名の末尾は「.client」でなければならないらしい。
また、クラス名は、そのままHTML名になるのでアプリケーション名をつけたほうが良いと思われる。
引数なしで、これらのコマンドを呼び出すとusageが表示されるので、詳細は、それで確認すること。

生成されたファイルとEclipseへのインポート
TESTGWTAPP
│  .classpath
│  .project
│  TestGWTApp-compile.cmd
│  TestGWTApp-shell.cmd
│  TestGWTApp.launch
│
├─src
│  └─jp
│      └─seraphyware
│          └─gwt
│              │  TestGWTApp.gwt.xml
│              │
│              ├─client
│              │      TestGWTApp.java
│              │
│              └─public
│                      TestGWTApp.html
│
└─test

こんなファイルができあがる。
これは、そのままEclipseのFile Menu -> Importで、既存プロジェクトのインポートとして取り込むことが出来る。
また、「*.launch」ファイルによって、Runの選択肢でプロジェクトを実行する設定も取り込まれているので、すぐにRunで試すことが出来る。

ちょっと修正して試してみる。

TestGWTApp.java

package jp.seraphyware.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class TestGWTApp implements EntryPoint {

    private boolean _sw;
    
    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {
        final Button button = new Button("Click me");
        final HTML msg = new HTML();

        button.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                TestGWTApp.this._sw = !TestGWTApp.this._sw;
                if (TestGWTApp.this._sw) {
                    msg.setHTML("hello, world!!");
                }
                else {
                    msg.setHTML("");
                }
            }
        });
        
        final DockPanel panel = new DockPanel();
        panel.setBorderWidth(1);
        panel.setWidth("100%");
        panel.add(button, DockPanel.NORTH);
        panel.add(msg, DockPanel.CENTER);

        RootPanel.get().add(panel);
    }
}

TestGWTApp.html

<html>
    <head>
        <meta http-equiv="Content-Type"
            content="text/html; charset=ISO-8859-1" />
        <meta name='gwt:module' content='jp.seraphyware.gwt.TestGWTApp'>
        <title>TestGWTApp</title>
    </head>
    <body>
        <script language="javascript" src="gwt.js"></script>
    </body>
</html>

とりあえず、クラスのインスタンス変数も普通に使えるし、無名クラスも使えるし、文法上強い制約があるようには見えない。本当に普通に使えそうである。

基本的な使い方としては、RootPanel#get()でHTMLページを取得して、ここにadd()Widgetを登録してゆく形となるらしい。RootPanel#get(ID)で、HTMLページのdivやらspanやらtdやらを取得できるので、ここにウェジットを仕込むことも可能。
また、DockPanelでBorderLayout風のレイアウトを設定できるようである。(HTMLの実体としてはtableらしい。)

この手のやり方は、なんとなくASP.NET的な感じであるが、これはクライアント上でDOMを使ってページを組み立てている。

これをコンパイルすると、/wwwというフォルダにコンパイル結果が格納されるのだが、

www
└─jp.seraphyware.gwt.TestGWTApp
        7207A899A5A833E3B89D124EE1505A33.cache.html
        7207A899A5A833E3B89D124EE1505A33.cache.xml
        9DBA93D9D0128B1F42AFE803B54BF067.cache.html
        9DBA93D9D0128B1F42AFE803B54BF067.cache.xml
        AAB69FD0EA1CB6C6902AAB81E8B13384.cache.html
        AAB69FD0EA1CB6C6902AAB81E8B13384.cache.xml
        CD9F52A9319A1352E0F22791968436B1.cache.html
        CD9F52A9319A1352E0F22791968436B1.cache.xml
        gwt.js
        history.html
        jp.seraphyware.gwt.TestGWTApp.nocache.html
        TestGWTApp.html
        tree_closed.gif
        tree_open.gif
        tree_white.gif

などというランダムチックなネーミングのxmlとhtmlが大量に作成される。
ここにコンパイルされたjavascriptなどが格納されている。
gwt.jsは、このGoogle Web ToolkitのフレームワークJavaScriptである。

日本語を使う

当然、日本語も使いたいと思うので、ためしにメッセージに日本語を書いてみた。
結果は文字化けてしまってぜんぜん使えない。htmlページのエンコードを変えてみたりしたが、無駄であった。

リテラルのメッセージは、あのランダムチックなxml/htmlの中に仕込まれており、仕込む時点で文字化けを起こしている。これはHTMLの問題ではない。
ならば、もしかすれば、リソースファイルを使うことで解決できるかもしれない。

JAVADOCを参照してみると、やはり、その仕組みが存在した。

メッセージリソースの定義

以下のようにメッセージを追加する。
Msg.properties

    buttonTitle=BTN TITLE
    greetingMessage=HELLO, WORLD!!!

Msg_ja.properties.txt

    buttonTitle=ボタンタイトル
    greetingMessage=こんにちは、世界!!

これはnative2asciiツールで、unicodeエスケープに変換する。

    native2ascii Msg_ja.properties.txt > Msg_ja.properties
メッセージリソースとバインドされるインターフェイスの定義

このリソースファイルとバインドされるインターフェイスファイルを作成する。
Msg.java

package jp.seraphyware.gwt.client;

import com.google.gwt.i18n.client.Messages;

public interface Msg extends Messages {

    String buttonTitle();
    
    String greetingMessage();
    
}

インターフェイスのメソッド名は、そのままプロパティ名と同じとする。
getXXXのような接頭語はつけない。
メッセージリソースで中に「{0}」のような形式で引数を受け取るものについては引数をつけることができる。

リソースファイルを読み取るようにコードを修正

ソースコードを以下のように変更。
TestGWTApp.java

 (前略)
        final Msg msgResource = (Msg) GWT.create(Msg.class);
        
        final Button button = new Button(msgResource.buttonTitle());
        final HTML msg = new HTML();

        button.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                TestGWTApp.this._sw = !TestGWTApp.this._sw;
                if (TestGWTApp.this._sw) {
                    msg.setHTML(msgResource.greetingMessage());
                }
                else {
                    msg.setHTML("");
                }
            }
        });
 (後略)

このGWT.create(...)というメソッドにより、現在のクライアントで設定されているロケールに従ったリソースファイルの読み取りが行われて、自動的にインターフェイスを経由して、それらにアクセスできるようになる。

GWTコンパイル時の設定を追加

GWTコンパイル時の設定ファイルを修正する。

TestGWTApp.gwt.xml

<module>
    <!-- Inherit the core Web Toolkit stuff.  -->
    <inherits name='com.google.gwt.user.User'/>
    <inherits name="com.google.gwt.i18n.I18N"/>

    <!-- Specify the app entry point class. -->
    <entry-point class='jp.seraphyware.gwt.client.TestGWTApp'/>
    <extend-property name="locale" values="ja_JP" />
</module>
HTMLのMETAタグにデフォルトロケールを設定

メタタグでgwtのプロパティとして「locale=ja_JP」とすることで、このクライアントがデフォルトでja_JPリソースを使うことを指示できる。
TestGWTApp.html

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
        <meta name='gwt:module' content='jp.seraphyware.gwt.TestGWTApp'>
        <meta name="gwt:property" content="locale=ja_JP">
        <title>TestGWTApp</title>
    </head>
    <body>
        <script language="javascript" src="gwt.js"></script>
    </body>
</html>

charsetが「ISO-8859-1」である理由、正当性はわからない。これは、I18Nサンプルが使用していたので、これで正しいのではないか、という気がするが、確証はない。
リソースはDOMでJavaScriptを経由して仕込まれるため、ソース上はISO-8859-1であって正しいのかもしれない。

以上で日本語を使ったボタンやラベルなどが使えるようになる。

結論

普通にストレスもなくインストールできたし、試すこともできた。未熟なオープンソースフレームワークにありがちな、そもそもインストールができないよ、サンプルもバギーだよ、などということはない。完成度は高そうだ。

また、少し使ってみた感触では、JavaでSwingをコードとして書くことに慣れているのであれば、これはラクに使い始めることができるような感じである。
開発スタイルも、JAVA的であり、ウェブ開発につきものの泥臭さが激減している。

懸念された日本語の扱いなども、コード中にリテラルとして扱えないようではあるが*1、しかし、仕組みとしては、きっちりとサポートされているっぽい。

しかし、使ってみて分かったことが1つある。

どうやら、これはウェブアプリケーションJavaScriptを仕込むというよりは、むしろ、ウェブで動作するスタンドアロンアプリケーションを作成して、それがウェブ上で動作して、サーバとはC/Sスタイルで連携するようなイメージであろうか。*2(既存のウェブサイトにちょっとだけ組み込むようなことは難しいと思う。)

これを見ると、たしかにGoogleがウェブブラウザを足がかりにMS転覆を画策しているような印象をもたれても仕方あるまい。
たしかに、すばらしいツールだ。今後、より一層の成熟が望まれる。

*1:JAVAソースコードエンコーディングを変えればいけるのかもしれないが…。

*2:サーバ側の負荷も減りそうだが、それ以上にクライアントの負荷がものすごいことになりそう。長いテーブルを組み立てたりすると、かなりトロトロ感がでてくる。今後、DOMが活発に使われるようになればブラウザ側も最適化して速くなる余地はあるとは思うけれど。