原文著者: 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章の冒頭でカバーしている。