今回Ryan Dewsbury著"Google Web Toolkit"(source)から"Integrating with a GWT-RPC Servlet"の1節(PDF・英語)をサンプルとしてお届けする。
Ajax がこれほど人気があるのは主にそのパフォーマンスに理由があります。私たちは多くのAjaxアプリにおいてユーザへの一番の訴求手段となっている派手なエフェクトがその理由だと考えがちです。そしてユーザも自分たちがAjaxアプリを好むのはそのためだと思っているのでしょう。それは当然のことです。旧来のウェブアプリは見ていても変化がなく退屈なだけですから。けれども派手なエフェクトが劇的にユーザエクスペリエンスを向上させるのであれば、アニメーションGIFはもっと広範囲で使われていたでしょう。幸いなことにアニメーションGIFがそこまで流行ることはありませんでした。AjaxがアニメーションGIFと同じ運命を辿らない理由は、外見の派手さだけがAjaxのもたらす価値ではないからです。Ajaxによってユーザエクスペリエンスを良くする本当の理由は、ユーザが意識しているしてないに拘わらず、そのパフォーマンスにあるのです。
Ajaxが旧来のウェブアプリケーションより本質的に高いパフォーマンスを持つ理由をここで説明するつもりはありません。もし分からないようであればGoogle mapsを見ながら以前のウェブ地図アプリを思い出す、あるいはHotmailとGmailを比べてみればいいでしょう。Ajaxのアーキテクチャ上にアプリケーションを作ると劇的にパフォーマンスを上げユーザエクスペリエンスを向上させることができるのです。ここで述べるのは、このパフォーマンス向上を次のレベルへ推し進める方法です。これによって他のAjaxアプリとの差別化が実現されます。
なぜGWTか
Google Web Toolkit(GWT)はAjax開発を強力に後押ししてくれます。この手の新しい技術の売り文句というのは強引なもので、同様の技術が他にもある場合は特にそうです。しかしGWTがAjaxアプリにもたらすものは他の何よりも勝るものなのです。もし他のフレームワークに縛られてないのであればGWTを使わない手はありません。GWTによってあなたのAjaxアプリケーションは高いパフォーマンスをフリーに得ることができるのです。
フリーにというのは、GWTについて考える必要がないということです。あなたはアプリケーションロジックを書くことに専念すればよく、GWTはそこにあるだけであなたのために物事を良くしようと働いてくれます。ご存知の通り、GWTはJavaコードをJavaScriptにコンパイルするコンパイラを備えています。もしあなたがコンパイル型言語(CやJavaなど)についてご存知であれば、その目的の一つが言語をプラットフォームに依存させないことであるのがお分かりでしょう。コンパイラはコンパイル対象のプラットフォームに応じてコードを最適化することができます。おかげでコードの可読性や構成に注力できるのです。GWTのコンパイラも同じことを行います。コンパイラはJavaコードを、高度に最適化されたJavaScriptが書かれたいくつかのファイルへとコンパイルします。それぞれのファイルはブラウザの種類ごとに生成されたものです。このコンパイルによりコンパクトかつブラウザの種類を意識しないコードが作られます。この最適化では実際のコンパイラの最適化手法が用いられていて、呼び出されないメソッドをコードから削除したり逆にコードを挿入したりします。そこではJavaScriptがウェブのアセンブリ言語として扱われています。そうして出来上がるのがコンパクトで速いコードなのです。ブラウザがそのJavaScriptを読み込む時、そのブラウザに必要なコードだけが読み込まれることになり、使用されないメソッドで膨れ上がったフレームワークの類は含まれません。GWTを使って作られたアプリケーションはJavaScriptで作られた他のアプリケーションよりもコンパクトで速く、控え目で多くを語らないGWTチームが「GWT1.5のコンパイラはどんな人が書いた手書きコードよりも速いJavaScriptを生成する」と自信を持って言うほどです。これだけでAjaxアプリケーションに対してGWTを使うのに十分な説明となると思いますが、それでも納得しない人には他にもGWTを使うべき多くの理由を挙げられます。それにはJava向けのツールが利用可能であることも含まれます(EclipseでAjaxアプリケーションのデバッグができるのは私にとって大きなプラスです)。
よりお望みであれば
話はまだ続きます。Ajaxアプリケーションは旧来のウェブアプリケーションよりパフォーマンスが良く、GWTアプリケーションは通常のAjaxアプリケーションよりパフォーマンスが良い、そのためGWTと数種類の技術を使えば真にパフォーマンスの良いアプリケーションの構築が可能で、アプリケーションの提供する機能に注力することができます。かかる時間も半分ですむようになるでしょう。しかし、GWTは魔法のごとく全てをかなえてくれるわけではありません。これから4つのテクニックをご紹介します。これらはAjaxアプリケーションのパフォーマンスをさらに上げるためにみなさん自身でできることです。
1. アプリケーションを永続的にキャッシュする
GWT アプリケーションをJavaScriptにコンパイルした時に生成されるファイルは、それぞれがブラウザの種類に対応し、ユニークな名前を持っています。このファイルにあるのがアプリケーションのコードで、単純にウェブサーバにコピーするだけで利用可能になります。ファイル名はコードのハッシュ値となっているので何もしなくてもバージョン管理が可能です。もしコード変更があれば、それをコンパイルすると新しいファイル名によって生成されるのです。これはつまりブラウザがこのファイルのコピーを既に読み込んでいるか、一度も読み込んでないかのどちらかになるということです。新しいバージョンが利用可能かどうかを確かめるのに更新時間を調べる必要ありません。HTTPの If-Modified-Sinceヘッダのやり取りを解消できるのです。このやり取りは些細なものですが、ユーザの裾野が広がると膨れ上がります。またブラウザは同一ドメインに対して一度に2つのリクエストしかできないので、クライアントの動作を遅くする原因にもなります。Ajaxでの読み込み時間に対する最適化手法の多くにはサーバへのリクエスト数を減らすことが含まれているくらいです。
ブラウザからのバージョンリクエストをなくすためにはウェブサーバにExpires HTTPヘッダを送信させる必要があります。このヘッダはウェブコンテンツが新しいものであるかをいつまで考慮しなくていいのかをブラウザに知らせます。ブラウザは期限日時が過ぎるまでは新しいバージョンのチェックをしないですみます。Apacheでこの設定をするのは簡単です。以下の設定を. htaccessファイルに追加すればいいのです。
<Files *.cache.*>
ExpiresDefault "now plus 1 year"
</Files>
これによりApacheは*.cache.*のパターンにマッチするファイルに対して1年の期限持たせるようなExpiresヘッダに付加します。そしてGWTアプリケーションファイルがこのパターンにマッチすることになります。
もしTomcatを直接使っているのならサーブレットフィルタを通じてこのようなヘッダを加えることになります。そのためには新しくサーブレットフィルタを追加するのが分かりやすい方法でしょう。WEB_INF/web.xmlファイルに以下のようにフィルタを宣言してください。
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>com.rdews.cms.filters.CacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>/gwt/*</url-pattern>
</filter-mapping>
これによりフィルタクラスを参照する場所とどのファイルをフィルタを通して送ればいいかをTomcatに知らせます。この場合/gwt/*のパターンによってgwtという名前のディレクトリにある全てのファイルが対象になります。またこのフィルタクラスはExpiresヘッダを付加するdoFileter ()メソッドを実装しています。ただGWTにおいて現行バージョンを選択するためのロジックを含むnocacheファイルはキャッシュさせるべきではありません。そのためGWT用に*.nocache.*にマッチしないものだけにこのヘッダを付加させるため、以下のようにフィルタの実装をおこないます。
public class CacheFilter implements Filter {
private FilterConfig filterConfig;
public void doFilter( ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
String requestURI = httpRequest.getRequestURI();
if( !requestURI.contains(".nocache.") ){
long today = new Date().getTime();
HttpServletResponse httpResponse = (HttpServletResponse)response;
httpResponse.setDateHeader("Expires", today+31536000000L);
}
filterChain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
}
2. アプリケーションの圧縮
GWT コンパイラは使われないメソッドを削ったり変数名や関数名を短いものに変換したりすることでサイズを小さくしてくれますが、コンパイルされたコードはまだ圧縮されていません。デプロイ時にアプリケーションコードをgzipすることでよりサイズを減らすことができます。gzipを使った場合最大70%まで圧縮されるのでアプリケーションの読み込みが早くなります。
幸いこれをサーバに設定するのも簡単にすみます。Apacheでファイルを圧縮するなら単に.htaccessファイルに以下の設定を加えるだけです。
SetOutputFilter DEFLATE
Apacheは自動的にcontent negotiationを接続してきたブラウザに対して実行し、ブラウザが圧縮に対応しているかしていないかに応じてコンテンツを圧縮して送ります。現在のブラウザ全てがgzip圧縮に対応しています。
Tomcatを使っているのであればserver.xmlファイル内にあるConnector要素の圧縮属性を利用できます。やり方は単純に圧縮属性の値をonにするだけです。
compression="on"
3. 画像をバンドルする
Ajaxアプリケーションの通信ではブラウザおよびHTTPの通信の力を活用します。しかしブラウザやHTTPはAjaxアプリケーションの通信に対して最適化されているわけではありません。Ajaxアプリケーションはデプロイに関してよりデスクトップアプリケーションに近いといえます。しかし旧来のウェブアプリケーションが用いている方法は共有リソース分散モデルに基づいており、ページのレンダリングに必要な全てのリソースを管理するためにはブラウザとウェブサーバとのやり取りをするようになっています。この管理方法はページ間でリソースの共有やキャッシュを保証するためで、新しいページがなるべく少ないダウンロードだけですむようにします。しかしAjaxアプリケーションのリソースは通常XML文書間で分散することはなく、別々に読み込む必要もありません。それでも旧来のウェブアプリケーションの管理モデルをそのままアプリケーションリソースの読み込むに使うのが簡単で、多くのアプリケーションがそのようにしています。
そうではなく一つのファイルに画像もバンドルさせると、アプリケーションの読み込みにかかるHTTPリクエスト数を減らすことが可能です。こうすることでアプリケーションは1度に2回でなく1回のリクエストで全ての画像を読み込みます。
GWT1.4 以降ではImageBundleインターフェイスがサポートされています。これはアプリケーションで使うことになる画像それぞれに対するメソッドを持ったインターフェイスを定義するものです。アプリケーションがコンパイルされる時、このインターフェイスが読み込まれコンパイラが全ての画像を結合し一つの画像ファイル内に配置します。このファイルの名前には含んだ画像から生成されたハッシュ値が使われます(アプリケーションコードと同じようにこの画像ファイルを永続的にキャッシュさせることができます)。バンドルする画像の数はどれだけでもよく、アプリケーションで利用する時にはHTTPリクエスト1回分のオーバーヘッドしか掛かりません。
例えば、私がこれまで開発に加わった2つのアプリケーションでは必要な画像に対して以下のようなイメージバンドルを用いました。
public interface Images extends ImageBundle {
/**
* @gwt.resource membersm.png
*/
AbstractImagePrototype member();
/**
* @gwt.resource away.png
*/
AbstractImagePrototype away();
/**
* @gwt.resource starsm.gif
*/
AbstractImagePrototype star();
/**
* @gwt.resource turn.png
*/
AbstractImagePrototype turn();
/**
* @gwt.resource user_add.png
*/
AbstractImagePrototype addFavorite();
}
それぞれのメソッドがコメント形式のアノテーションを持ち、そこで画像ファイルを指定している点と、メソッドが AbstractImagePrototypeを返す点を見てください。このAbstractImagePrototypeはcreateImage() メソッドを持ち、これはアプリケーションのインターフェイスが扱えるImageインスタンスを返します。以下のコードではこのイメージバンドルの利用法を示しています。
Images images = (Images) GWT.create(Images.class);
mainPanel.add( images.turn().createImage() );
この方法はとてもシンプルですが起動時のパフォーマンスを大きく向上させます。
4. StyleInjectorを使う
アプリケーションリソースとしてのCSSファイルおよびCSSで指定される画像はどうでしょうか。旧来のウェブ分散モデルでは外部リソースとして扱われ、読み込みやキャッシュは別に行われます。Ajaxアプリケーションでこの方法を用いると、余分なHTTPリクエストが生じてアプリケーションの読み込みが遅くなります。今のところこの点に関してGWTが提供している手段はありませんが、GWT-incubatorプロジェクトでは将来のバージョンで取り入れられる可能性のあるいくつかのGWT用コードが書かれています。特に興味深いのはImmutableResourceBundle
(不変リソースバンドル)と StyleInjector
(スタイル注入器)です。
ImmutableResourceBundle クラスはImageBundleクラスとよく似ていますが、CSSやCSSで指定される画像も含んだどのようなリソースに対しても使うことができます。これが目指すのはアプリケーションを実行しているブラウザ上で可能なかぎり最適化された方法で扱われるように、画像以外のリソースも抽象化することです。以下のコードはCSSファイルといくつかのリソースを読み込むためにこのクラスを使った例です。
public interface Resources extends ImmutableResourceBundle {
/**
* @gwt.resource main.css
*/
public TextResource mainCss();
/**
* @gwt.resource back.gif
*/
public DataResource background();
/**
* @gwt.resource titlebar.gif
*/
public DataResource titleBar();
/**
* @gwt.resource dialog-header.png
*/
public DataResource dialogHeader();
}
ImageBundle とほぼ同じようにそれぞれのリソースに対してファイルやメソッドを指定していますが、メソッドの返り値はDataResourceあるいは TextResourceのどちらかになっています。TextResourceクラスではコンテンツを取得するのにgetText()が使え、 DataResorceクラスではデータを参照するのにgetUrl()が使えます(例えばIMGタグやIFRAMEの中で)。どのようにデータが読み込まれどのように扱われるかはブラウザ毎に異なりますが、開発者がそれを気にする必要はありません。ほとんどの場合データはインラインURL参照で、そのインラインURLはURLプリフィックス(http://www.abc.com/def/など)と組み合わせて扱われます。このクラスのもつ可能性は非常に大きいものです。しかし一番身近な使い方はCSSをアプリケーションファイルに直接バンドルすることです。
このインターフェイスでCSSファイルといくつかの画像が参照されているのを見てください。この場合インターフェイスはCSSとそこで使われる画像をアプリケーションにバンドルするためのもので、これによりHTTP呼び出しの回数と起動時間を小さくすることができます。CSSテキストではアプリケーションのいくつかの要素での背景画像を指定しますが、実際の画像のURLを指定するのではなく画像の格納場所を指定します。これらの格納場所はバンドル内のほかの要素、この場合は画像、から参照されます。例えばmain.cssファイルがgwt-DialogBoxというスタイルのCSS規則を持っているとします。
.gwt-DialogBox{ background-image:url('%background%') repeat-x; }
このCSSファイルとそこで指定される画像を利用するにはGWT-incubatorプロジェクトで作られたStyleInjectorクラスを使う必要があります。StyleInjectorクラスはCSSデータを取り出して、そこにある格納場所とリソースバンドル内のリソースとをマッチングさせます。それからブラウザにCSSを注入してアプリケーションで利用できるようにします。こう書くととても複雑に見えますが、使い方は簡単でパフォーマンスも上がります。以下の例ではStyleInjectorによってリソースバンドル内のCSSをアプリケーションに注入しています。
Resources resources = (Resources)GWT.create(Resources.class);
StyleInjector.injectStylesheet( resources.mainCss().getText(), resources );
ただし、このテクニックはincubatorプロジェクトに含まれるもので、将来変更されることが十分ありえることに注意してください。
まとめ
Ajaxアプリケーションは旧来のウェブアプリケーションより遥かに優れたユーザビリティを備え、GWTはより優れたAjaxのパフォーマンスをフリーでもたらすツールを提供します。GWTのメールアプリケーションのサンプル(source)と他のAjaxアプリケーションのサンプルを比べてみると分かるでしょう。旧来のウェブアプリケーションとAjaxアプリケーションとのデプロイに関する相違点についても考慮すればよりパフォーマンスを高めることもできます。私は次世代のAjaxアプリケーションを目にすることを楽しみにしています。
著者について
Ryan Dewsbury氏は1998年以来C++とJavaでの開発、設計、コンサルティングに関わってきた。当初の数年は半導体製造システム向けのフレームワーク構築に携わり、最近は2,3のウェブスタートアップ企業とともに最先端のソフトウェアを通じて優れたユーザエクスペリエンスを生み出す仕事をおこなっている。転職の合間には自らのソフトウェアプロジェクトをおこなっており、それにはEasy Message(2004年に買収された)やGpokr(gpokr.com)、KDice(kdice.com)、GWTをベースにした2つのちょっとしたウェブゲームなどがある。
注意:コードや本文ではセキュリティ問題やエラー処理について対処していません。
原文はこちらです:http://www.infoq.com/articles/gwt-high-ajaxCopyright: このコンテンツはRyan Dewsbury氏著”Google Web Toolkit Applications”(Prentice Hall Professionalより2007年12月発行、Copyright 2008 Person Education Inc.、ISBN0321501969)から抜粋されたものです。この本の詳細についてはこちらを参照してください。http://www.informit.com/title/0321501969 (英語)