ページライフサイクル

Tapestryでは、プレゼンテーションに関するオブジェクト、つまりページやコンポーネントクラスは、 インスタンス変数などを完備した普通のオブジェクトとして開発することができます。

これはJavaによるWeb開発では少しばかり革命的なことです。サーブレットやStrutsを使う場合、 プレゼンテーションオブジェクト(サーブレット、StrutsのAction、あるいは他のフレームワークでのこれらに相当するもの)は、ステートレスなシングルトン です。 すなわち、ただ ひとつの インスタンスが生成され、このひとつのインスタンスを通して全てのリクエストがスレッドで処理されます。

複数のリクエストが多数の異なるスレッドで処理されるため、このひとつのインスタンスのインスタンス変数は使い物になりません ... インスタンス変数に書き込まれた値は他のスレッドによってすぐさま書き換えられてしまうでしょう。 そのため、リクエスト毎のデータはサーブレットAPIの HttpServletRequest に格納する必要があり、 リクエストを跨ぐデータは HttpSession に格納する必要があります。

Tapestryはとても異なるアプローチを取っています。

Tapestryでは、どのページにも多数のインスタンスが存在していて、それら各インスタンスは(ひとつのスレッド上の)ひとつのリクエストに対して使用されているか、 ページプール 内で待機しているかのどちらかとなります。

個々のスレッド毎にページインスタンスを割り当てることで、マルチスレッドの複雑で厄介な問題の全てを忘れることができます。 そして、(普通のメソッドとフィールドを使った)慣れ親しんだシンプルなコーディングをすることができます。

しかし、リスクもあります: もしあるリクエストのデータが他のリクエストに漏れ出したら、それは大惨事となります。 ネットバンキングのアプリケーションで、ひとり目のユーザーのアカウント番号とパスワードが次にやってきたユーザーの画面に初期値として入っていたとしたらどうでしょう!

Tapestryはこの問題を防ぐため、各リクエストの終わりに全てのインスタンス変数をデフォルト値に戻す処理を行います。

その結果、ページプール内の全てのインスタンスは全く等価であるということになります; 各リクエストの処理にどのインスタンスが使用されるかということは重要ではありません。

ページインスタンスは氷山の一角だということを覚えていてください: ページコンポーネントとそのテンプレート、パラメータバインディング、 テンプレートから読み込まれたトークン、そして(再帰的に)これらと同じものを含んでいるページ内の全てのコンポーネント、 ページインスタンスはこれら全てのものを含んでいます。

ページインスタンスは、リクエストを処理する数ミリ秒の短い間、ページプールから "チェックアウト" されるのです。 これにより、Tapestryはとても多くのエンドユーザーを処理するために、比較的小数のページインスタンスをプールしておくだけで済みます。

JSPとの比較

JSPもキャッシュ機構を持っています: JSPは、それ自身がJavaサーブレットクラスにコンパイルされ、シングルトンとして動作します。

しかし、個々のJSPタグはプールされています。

これは、性能面でTapestryがJSPよりとても優れている領域のひとつです。 コンパイルされたJSPクラス内のコードの多くを占めるのが、 タグをプールから取り出し、タグインスタンスを設定し、タグインスタンスを使用し、タグインスタンスをクリーンアップし、そしてプールに戻す、という処理です。

Tapestryではリクエスト毎に一度しか行わない処理が、数十回かもしかしたら(複雑なページであったりネストしたループがあったりすれば)数百回も実行されるのです。

JSPタグプーリングは粒度の設定が間違っています。

また、Tapestryではより粗いキャッシングを用いて、パラメータによるコンポーネント間のデータ移動がどのように行われるかについて最適化を行っています。 これにより、Tapestryのページは2回目以降のレンダリングはとても高速になります。

ページプールの設定

Tapestryのページプールはページのインスタンスを格納します。 ページプールは、("start" といった)ページ名と("en" や "fr" といった) ロケール を "キー" としています。

生成されるページインスタンスの数や使用中の(リクエストに結びつけられている)インスタンス数はキー毎に管理されます。

リクエスト内でページへのアクセスがに最初にあった時、そのページのインスタンスがページプールから取り出されます。 ページインスタンスがいつどのように生成されるかの詳細を制御するいくつかの設定値があります。

  • 未使用のページインスタンスが存在する場合、そのインスタンスを使用中としてマークしリクエストに結びつけます。
  • ページインスタンスの数が ソフトリミット より少ない場合、すぐに新しいページインスタンスを生成しリクエストに結びつけます。
  • ソフトリミットに達している場合、新しいページインスタンスを生成する前に少しの間ページインスタンスが利用できるようになるのを待ちます。
  • ハードリミットに達している場合、新しいページインスタンスを生成せずに例外を投げます。
  • そうでなければ新しいページインスタンスを生成します。

したがって、アクセス数の多いアプリケーションではソフトリミットまでのページインスタンスがすぐに生成されるでしょう(ソフトリミットはデフォルトで5インスタンスです)。 もしそのままリクエストが続けば、既存のページインスタンスを再利用しようとしてソフトウェイトで指定した待ち時間が発生するため、 リクエストの処理が遅くなるでしょう。

さらにアクセス数の多いアプリケーションでは、ハードリミットに達するまで新しいページインスタンスが作成され続けるでしょう。

これら全ての設定値は、ページ名とロケールの組み合わせ毎の値であるということを覚えておいてください。 したがって、ハードリミットが20なら、(アプリケーションが英語とフランス語の両方をサポートするように設定されていれば) "en" ロケールのstartページのインスタンス20個 "fr" ロケールのstartページのインスタンス20個がやがては生成されるでしょう。 同様に、startページのインスタンス20個とnewaccountページのインスタンス20個も生成されるでしょう。

Tapestryは、しばらくの間(設定で指定した時間)使用されていないページインスタンスが無いかどうか定期的にチェックします。 しばらくの間使用されてないページインスタンスは解放され、ガベージコレクタにより回収されます。

これらによって、リクエストの処理をとても深くチューニングすることができます。メモリが限られていてスループットを犠牲にしてもよい場合は、 ソフトリミットとハードリミットを小さくし、ソフトウェイトを大きくしてみてください。

パフォーマンスが重要でメモリが沢山ある場合には、ソフトリミットとハードリミットを増やしてソフトウェイトを減らしてみてください。 これにより、より多くのページインスタンスが作成され、既存のインスタンスを再利用するために待たされる時間が少なくなります。

ページライフサイクルメソッド

初期化やキャッシングのようなページのライフサイクルに基づいた処理をコンポーネントが行うのに役立ついくつかの場面があります。

ページのライフサイクルはかなり単純です。ページは必要とされたときに初めてロードされます。 ページがロードされる際にページ内のコンポーネントをインスタンス化し互いに接続します。

ページがロードされると現在のリクエストに 結びつけられます。複数のスレッドがそれぞれにリクエストを処理していることを思い出してください。 多くの場合、同じページの複数のコピーが異なるリクエスト(異なるスレッド)に結びつけられています。 これがTapestryではマルチスレッドの問題を心配しなくて済む理由です ... どのリクエストに関連するオブジェクトも まさしく そのリクエスト(そのスレッド)専用なのです。

クライアントにレスポンスが送られリクエストが終了するときにページはリクエストから 切り離されます。 このとき、(ガベッジコレクタが回収できるように)一時オブジェクトを破棄したり、ページの状態を初期状態に戻したりといったページの様々な後始末を行うことができます。 ページは切り離されるとページプールに置かれ、その後のリクエストで再利用されるのを待ちます(そのリクエストはまったく別のユーザーからのものかもしれません)。

コンポーネントレンダリングと同じように、これらのイベントを待ち受けるメソッドをコンポーネントに持たせることができます。

メソッドにアノテーションする方法とメソッド名による方法を選ぶことができます。

ページライフサイクルメソッドはパラメータ無しで戻り値はvoidである必要があります。

アノテーション / メソッド名:

  • PageLoaded アノテーション、またはメソッド名 "pageLoaded"
  • PageAttached アノテーション、または "pageAttached"
  • PageDetached アノテーション、またはメソッド名 "pageDetached"