原文著者: Authors: Andrea Del Bene, Martin Grigorov, Carsten Hufe, Christian Kroemer, Daniel Bartl, Paul Borș, Tobias Soloschenko, Igor Vaynberg , Joachim Rohde
訳注は で示します。
訳語集
- ページ部品
- org.apache.wicket.Component 原文では単に component
- 状態がある
- stateful
- 状態がない
- stateless
まず一番重要な8章。他の章は欲しいものから順で。
8. ページバージョニング・キャッシュ
この章では Wicket がどのようにページインスタンスを管理するか、状態があるページと状態がないページの差異に下線を引きながら説明する。またこの章では Javaシリアライゼーションやマルチレベルキャッシュのような高度な話題についてもいくらか紹介する。
訳文では訳者が太字にしてます。原文にも本当に下線を引いてください。
8.1. 状態があるページ vs 状態がないページ
Wicketのページは2つのカテゴリに分類できる: 状態があるページと状態がないページだ。状態がある ページはその内部状態とユーザとのやり取りの足跡を保存するためにユーザセッションに依存している。反対に状態をもたないページはその生涯にわたって内部状態を変更することはなく、ユーザセッション上の領域を専有しない。
Wicketの観点からのこの2つ最も大きな違いは状態があるページはバージョニングされる、つまり内部状態が変更されるたびにユーザセッションに保存されるということだ。ユーザが初めて状態があるページをリクエストしたタイミングで、Wicketは自動的にユーザにセッションを割り当てる。それぞれの状態(バージョン)のページはJavaシリアライゼーション機構を用いてユーザセッションに保存される。状態がないページはバージョニングされることはなく、そのため有効なユーザセッションを必要としない。あるページに状態がないかどうかを知りたい場合はPageクラスのisPageStateless()
を呼び出せば良い。
状態のないページを作るためには、ユーザセッションを必要とすることが内容に幾つかのルールに従わなくてはならない。そのルールは8.3.節で示すが、状態がないページについて語る前にまずは状態があるページがどのように取り扱われ、何故バージョニングされているかを理解しなくてはならない。
原文では明記されていないが状態がないページはリクエストのたびに生成されレスポンスが完了したら破棄される。
8.2. 状態があるページ
状態があるページはブラウザの戻るボタンをサポートするためにバージョニングされている: このボタンが押された時、Wicketは直前に使用したページインスタンスを使って応答ページをレンダリングしなくてはならない。
新規のページバージョンは状態のあるページが初めてリクエストされたタイミング、もしくは既にユーザセッション上に存在するインスタンスに変更(例えばページ部品の親子関係を変更するといった)が加えられた場合に作られる。それぞれのページバージョンを識別するために、セッションに紐付いた page id と呼ばれる識別子を用いる。これは新しいページバージョンが作られるたびに増加するユニークな数値になる。
前の章の最後の例 (project LifeCycleStages) でURLの最後に追加れされていた番号に気がついていただろうか。これこそが page id だ。
りぷれぜんてーしょなるすてーととらんすふぁー?なにそれ美味いの?
この章ではこの例からLinkのonClick()内部でページ部品の親子関係を変更するように改定したバージョンを使おう。WicketはonBeforeRender()が実行されるまでに変更が発生した場合にのみ新しいページバージョンを作るのでこの改定が必要になる。新しいホームページのコードは次のとおりだ。
public class HomePage extends WebPage
{
private static final long serialVersionUID = 1L;
private Label firstLabel;
private Label secondLabel;
public HomePage(){
firstLabel = new Label("label", "First label");
secondLabel = new Label("label", "Second label");
add(firstLabel);
add(new Link("reload"){
@Override
public void onClick() {
if(getPage().contains(firstLabel, true))
getPage().replace(secondLabel);
else
getPage().replace(firstLabel);
}
});
}
}
さて新たな例(project LifeCycleStagesRevisited)を実行して「リロード」ボタンを押してみると、新しいページバージョンが作成されて page id が1増加する。
ここで戻るボタンを押せば、以前描写(そしてシリアライズ)されたページバージョンが復元(つまりデシリアライズ)され、リクエストに応答するために再利用される。(そして page id も減る)
ページ保存に関する詳細については「Wicket内部動作」章の「ページ保存」節にある。内容はWikiからの転載になる。https://cwiki.apache.org/confluence/display/WICKET/Page+Storage
この章の冒頭でも言及した通り、ページバージョンはjavaシリアライゼーションを用いて保存される。すなわちページ内の全てのオブジェクト参照はシリアライザブルでなくてはならない。11.6.節では取り外し可能(detachable)なWicketモデルを用いてこの制約を乗り越えてシリアライズ不可能なオブジェクトを利用する方法を示す。
PageReferenceで特定のページバージョンを使う
コード中で特定のページバージョンを復元するには org.apache.wicekt.PageReference のコンストラクタに対応する page id を与えれば良い:
//page id = 3 のページバージョンをロード
PageReference pageReference = new PageReference(3);
//対応するページインスタンスをロード
Page page = pageReference.getPage();
対応するページインスタンスを得るにはgetPage
を使用する。
ページバージョニングを無効化する
何らかの理由でそのページのバージョニングを無効化したい場合はsetVersioned(false)
を呼べば良い。
プラグイン可能なシリアライゼーション
Wicket 1.5 以降、ページバージョンを保存するのにJavaシリアライゼーションのどの実装を用いるかが選択可能になった。Wicketは各ページをインタフェース org.apache.wicket.serialize.ISerializer の実装(具象クラス)を用いてシリアライズする。デフォルトの実装は org.apache.wicket.serialize.java.JavaSerializer で、ObjectOutputStream とObjectOutputStream の各クラスに基づいた標準のJavaシリアライゼーション機構を用いる。何にせよインターネット上には標準のものより高速動作するKyroやFastのような興味深いシリアライゼーションライブラリが存在する。実動作で使用するシリアライザ(org.apache.wicket.serialize.ISerializer)は設定クラスorg.apache.wicket.settings.FrameworkSettingsのsetSerializer(ISerializer)
でカスタマイズできる。
このクラスにはApplicationクラスのinit
の中からgetFrameworkSettings()
でアクセスできる。
@Override
public void init()
{
super.init();
getFrameworkSettings().setSerializer(yourSerializer);
}
Kyroに基づいたシリアライザとFastに基づいたシリアライザはWicketStuffプロジェクトで提供されている。このプロジェクトについてのより多くの情報、含まれるモジュールの紹介は付録B(付録番号Bは振られていない。29.Project Wicket Stuff(Appendix)のこと)で見ることができる。
ページキャッシング
Wicketはデフォルトでページバージョンをセッションと紐付けられたディスク上のファイルに永続化するが、このプロセスの高速化のために2レベルのキャッシュを用いている。第1レベルのキャッシュはwicket:persistentPageManagerData-<APPLICATION_NAME>
という名前のHttpセッション属性をページ保存に用いる。第2レベルのキャッシュはセッションIDとpage idで識別されるアプリケーションスコープの変数に各ページを保存する。
セッションスコープ(第1レベル)のキャッシュは他のレベルよりも高速だが、保存されるのは最後のリクエストで使用されたページのみになる。アプリケーションスコープ(第2レベル)とページ保存ファイル(第3レベル)に割り当てる最大メモリサイズは設定可能になる。どちらも設定クラスorg.apache.wicket.settings.StoreSettings
から設定する。
このインタフェース(さっきクラスって言ったよね!?)はページ保存ファイル(第3レベル)のサイズ設定用のsetmaxSizePerSession(Bytes bytes)
を持つ。パラメータのBytesはこのファイルの最大サイズになる:
@Override
public void init()
{
super.init();
getStoreSettings().setMaxSizePerSession(Bytes.kilobytes(500));
}
クラス org.apache.wicket.util.lang.Bytes はバイト単位のサイズを表現するのにWicketが用いているユーティリティクラスになる(より詳細な情報はJavaDocを参照すること)。第2レベル(アプリケーションスコープ)キャッシュの設定にはsetInmemoryCacheSize(int inmemoryCacheSize)
を使う。パラメータの数値がカッシュに保存される最大のページインスタンス(=ページバージョン:状態が変わるたびにクローンが発生する)数となる。
@Override
public void init()
{
super.init();
getStoreSettings().setInmemoryCacheSize(50);
}
ページの有効期限切れ
ページインスタンスはユーザセッション上に永遠に保存されるわけではない。setMaxSizePerSession
で設定された上限を超えるか(よりありがちなケースとしては)ユーザセッションの有効期限が切れれば破棄される。セッションから除去されたページインスタンスと紐づくpage idが要求されたとき、WicketはPageExpiredExceptionを発行し、以下の様なデフォルトエラーページを出力する:
このエラーページはクラスorg.apache.wicket.settings.ApplicationSettingsのsetPageExpiredErrorPage
からカスタマイズできる。
@Override
public void init()
{
super.init();
getApplicationSettings().setPageExpiredErrorPage(
CustomExpiredErrorPage.class);
}
カスタムエラーページとして設定するページは引数無しもしくはPageParametersを唯一の引数とするpublicなコンストラクタを持たなくてはならない。(言い換えると節10.1.1.で説明するようなブックマーク可能なものでなくてはならない)
8.3. 状態がないページ
Wicketでは状態があるページをとても簡単に作れるようになっているが、状況によってはユーザセッションに状態を保存しない「古式ゆかしい」(わりとRESTafariansな訳者は喧嘩売られている気分になります)状態がないページが必要になるだろう。たとえばサイトの公開エリアやログインページを考えてみてほしい:これらの場合は状態のあるページはリソースの無駄使いであるのみならず、節12.10.で見るようなセキュリティ上の脆弱性ともなる。
Wicketでは以下の要件を全て満たす場合にのみ、そのページは状態がないページとなる
- 引数無しもしくはPageParameters(このクラスについては節10.1で説明する)を唯一の引数とするコンストラクタを用いてWicketによってインスタンス化されたページであること(例えば new オペレータで作ったりしていないということ)
- すべての子となるページ部品(ビヘイビア)に状態がない、つまり
isStateless
がtrueを返さなくてはならない
以下訳者による整理
- Wicketによってインスタンス化されたページであること
- 引数無しもしくはPageParametersを唯一の引数とするコンストラクタを持つこと = 節10.1.1.で説明するようなブックマーク可能なものであること(Wicketによるインスタンス化に必要)
-
org.apache.wicket.Component#setResponsePage(Class<C extends IRequestablePage> cls)
-
org.apache.wicket.Component#setResponsePage(Class<C extends IRequestablePage> cls, PageParameters parameters)
-
org.apache.wicket.Component#setResponsePage(IRequestablePage page)
- ただしorg.apache.wicekt.PageReference#getPage() で復元した状態がないページは可
- すべての子となるページ部品/ビヘイビアに状態がないこと
-
isStateless
がtrueを返すこと - リクエストのたびに初期化されても問題ない作りになっていること
-
最初の要件は自前でページを生成するのではなくsetResponsePage(Class page)
のようなページインスタンスを生成するWicketの機能に依存しなくてはならないということをほのめかす(はっきり書いてよ)。
2つ目の要件に従うには、あるページのすべての子となる部品が状態がないどうかをチェックするのが役立つだろう。これにはvisitChildren
とVisitorパターンを活用して全画面部品を逐次処理し、isStateless
が実際にtrueを返すかをテストすれば良い。
@Override
protected void onInitialize() {
super.onInitialize();
visitChildren(new IVisitor<Component, Void>() {
@Override
public void component(Component component, IVisit<Void> arg1) {
if(!component.isStateless())
System.out.println("Component " + component.getId() + " is not stateless");
}
});
}
別の方法としてユーティリティアノテーションのStatelessComponent
をクラスStatelessCheckerと一緒に使うというのもある(どちらもパッケージ org.apache.wicket.devutils.stateless にある)。 StatelessChecker はStatelessComponent アノテーションのつけられたページ部品が状態がない要件を満たさない場合にIllegalArgumentException を発行する。StatelessComponent アノテーションを使う場合にはアプリケーションのページ部品レンダリングリスナに StatelessChecker を追加しなくてはならない。
@Override
public void init()
{
super.init();
getComponentPostOnBeforeRenderListeners().add(new StatelessChecker());
}
Wicketの組み込みページ部品のほとんどには状態があるので状態がないページでは使うことができない。ただしの幾つかは状態がないページで使うための状態がないバージョンも存在する。このガイドの後の章では組み込みページ部品に状態がないバージョンが存在する場合はその都度指摘する。
setStatelessHint(true)
を用いて適切なフラグをtrueに設定することでページを明示的に状態がないと宣言することもできる。このメソッドは上述の要件に違反しているのを(コンパイル時に)検出したりはしてくれないが、もしそうであった場合は次のような警告ログを出力してくれる。
Page '<page class>' is not stateless because of component with path '<component path>'
8.4. まとめ
この章ではWicketがページインスタンスをどのように管理するかを見てきた。ページは状態があるものと状態がないものの2つのに分類されることを学んだ。タスクに対して適切なページを構成するうえで、それら2つの違いが重要であることを知った。
しかし状態のないページについての話題を終えるには、この章で言及するだけに留めた2つの
主題を取り扱わなくてはならない:クラスPageParametersとブックマーク可能なページだ。これについては10章の冒頭でカバーしている。