原文著者: 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
9. リクエスト処理の内側
Wicketは信頼性のある包括的なオブジェクト志向によるWeb開発の抽象化を提供してはいますが、時としてユーザセッションや、Webリクエスト、クエリパラメータなどなどの『素のまま』のWeb実体を取り扱わなくてはならないことがあります。例えば任意のパラメータをユーザセッションに保存したい場合などがこれにあたります。
JavaServlet仕様の低レベルAPIを取り扱う重荷を取り除き、そうしたWeb実体に容易にアクセスするためのラッパークラスをWicketは提供しています。もちろんWicketアプリケーションの下敷きとなっている(HttpSession
や HttpServletRequest
等々の)標準クラスにアクセスする事はいつでも可能です。この章ではそうしたラッパークラスを紹介し、ユーザブラウザからのWebリクエストをWicketがそれらを使ってどのように取り扱っているかを説明します。
9.1. Application
クラスとリクエスト処理
アプリケーションの初期化と設定を担う一方で、Application
クラスはWicketがリクエストを処理するための内部実体を生成する役割も担っています。こうした実体は以下のクラスのインスタンスになります:RequestCycle
、Request
、Response
そしてSession
です.
次の節ではこれらのクラスそれぞれの概要を示し、リクエスト処理にどのように関わっているかを説明します。
9.2. Request
クラスと Response
クラス
Request
クラスと Response
クラスは org.apache.wicket.request
パッケージに存在し、Webアプリケーションが利用する具体的なリクエストとレスポンスを抽象化します。
どちらのクラスも抽象クラスとして宣言されていますが、アプリケーションが WebApplication
を継承している場合は、 org.apache.wicket.protocol.http.servlet
パッケージに存在するサブクラス ServletWebRequest
クラスと ServletWebResponse
が用いられます。 ServletWebRequest
と ServletWebResponse
はそれぞれ HttpServletRequest
と HttpServletResponse
を内包しています。これらの低レベルインスタンスにアクセスsする場合は Request
の getContainerRequest()
メソッドと Response
の getContainerResponse()
メソッドを呼び出します。
9.3. リクエスト処理の『ディレクター』- RequestCycle
org.apache.wicket.request.cycle.RequestCycle
クラスはWebリクエストの受け入れを担う実体です。アプリケーションはリクエストを受け取る度に createRequestCycle(request, response)
メソッドによって新しい RequestCycle
インスタンスを作成します。
createRequestCycle
メソッドは final として宣言されているため、カスタマイズした RequestCycle
のサブクラスを返却するようにオーバーライドすることはできません。代わりにインタフェース org.apache.wicket.IRequestCycleProvider
を実装するプロバイダを作成し setRequestCycleProvider
メソッドでもってアプリケーションにそれを使うよう指示する必要があります。
現在実行中の RequestCycle
は静的メソッド RequestCycle.get()
を呼び出すことで何時でも取得できます。正確に言うならばこのメソッドは、リクエストの受け入れ処理を行っている現行(もしくはローカル)スレッドと対応する RequestCycle
を返却します。似たような現行スレッドに用いられているインスタンスを取得するための get()
メソッドは org.apache.wicket.Application
(こちらについては4.2.2.節で既に見ています)と org.apache.wicket.Session
にも実装されています。
この get メソッドの実装は標準クラス
java.lang.ThreadLocal
を利用しています。java.lang.ThreadLocal
クラスのスレッド・ローカル変数に関する JavaDoc を参照してください。
org.apache.wicket.Component
クラスは内部で RequestCycle.get()
を呼び出す便利メソッド getRequestCycle()
を提供しています。
public final RequestCycle getRequestCycle() {
return RequestCycle.get();
}
RequestCycle
とリクエスト処理
この節ではリクエスト処理に際して裏側で何が発生するかについての基本的な情報についてのみ提示しています。Wicketを利用するにあたってこの処理をカスタマイズする必要があるとはあまり考えられないため、詳細は省きます。
リクエストを処理するにあたって、RequestCycle
は ```` インタフェースを実装する別の実体にその処理を以上します。このインタフェースの実装はリクエストされたリソースの種類に応じて(描写対象となるページ、AJAXリクエスト、外部ページへのURLなどなど)それぞれ異なるものが存在します。
受け取ったHTTPリクエストに対して適切なハンドラを選択するために、 RequestCycle
は org.apache.wicket.request.IRequestMapper
インタフェースを実装したインスタンスの集合を用います。このインタフェースではマッパーが現在のリクエストに対してどの程度互換性があるか(適切か)をスコアとして返却する
getCompatibilityScore(Request request)
メソッドを定義しています。 RequestCycle
は最も高いスコアとなったマッパーを選択し、 mapRequest(Request request)
メソッドを呼び出して受け取ったHTTPリクエストに対する適切なハンドラを取得します。適切なハンドラが取得できたならば、 RequestCycle
は respond(IRequestCycle requestCycle)
メソッドを呼び出してリクエスト処理を開始します。
次のシークエンス図は RequestCycle
がどのようにリクエストハンドラを選択するかの総括になります:
開発者は IRequestMapper
の実装を追加して WebApplication
クラスの mount(IRequestMapper mapper)
メソッドによって開発中のアプリケーションに追加することができます。10.6.節ではWicketがマウントされたページに対する組み込みマッパーを追加する際に、このメソッドをどのように使っているかを見ることになります。
urlFor
メソッドと mapUrlFor
メソッドによるURL生成
RequestCycle
は以下の実体のURLを(文字列として)生成する役割も担っています:
- ページクラス
-
urlFor(Class<C> pageClass, PageParameters parameters)
メソッド
-
-
IRequestHandler
-
urlFor(IRequestHandler handler)
メソッド
-
-
ResourceReference
-
urlFor(ResourceReference reference, PageParameters params)
メソッド
(リソース実体については19章で紹介します)
-
上記のオーバーロードされた urlFor
メソッドには文字列の代わりに org.apache.wicket.request.Url
インスタンスを返すバージョンも存在します。そちらのバージョンは名前の頭に『map』とついています。(つまり全体で mapUrlFor となります)
setResponsePage
メソッド
RequestCycle
クラスは(4.4.節で見たような)ユーザを特定のページにリダイレクトする際に用いる setResponsePage
メソッドの実装も含んでいます。 org.apache.wicket.Component
クラスの同名メソッドは内部的には現在の RequestCycle
の実装を呼び出す単なる便利メソッドになります:
public final void setResponsePage(final Page page) {
getRequestCycle().setResponsePage(page);
}
RequestCycle
のフックメソッドとリスナー
RequestCycle
にはオーバーライドしてリクエスト処理が特定の段階に達した時に任意の処理を実行するためのフックメソッドが存在します:
- onBeginRequest()
-
RequestCycle
がリクエスト処理を開始する前に呼び出されます。
-
- onEndRequest()
-
RequestCycle
がリクエスト処理を完了した後に呼び出されます。
-
- onDetach()
-
RequestCycle
がリクエスト処理を完了し、スレッドから除去される直前に呼び出されます。このメソッドのデフォルト実装は現行セッションのdetach()
メソッドを呼び出します(Session
クラスについては直後の9.4.節で扱います)。
-
onBeforeRequest
メソッドと onEndRequest
メソッドはビジネスコード実行の前と後に、Hivernate/JPAセッションを開いたり、コード終了時に閉じたりといった何らかのカスタムアクションを実行する必要がある場合に利用できます。
リクエスト処理と呼応するより柔軟な方法として、org.apache.wicket.request.cycle.IRequestCycleListener
リスナーインタフェースを利用することもできます。RequestCycle
で既に見た3つのメソッドに加えて、このインタフェースはリクエスト処理中のさらなるフックを提供します:
- onBeginRequest(RequestCycle cycle)
- (上述の説明参照)
- onEndRequest(RequestCycle cycle)
- (上述の説明参照)
- onDetach(RequestCycle cycle)
- (上述の説明参照)
- onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
-
IRequestHandler
の選択が完了した時に呼び出されます。
-
- onRequestHandlerScheduled(RequestCycle cycle, IRequestHandler handler)
-
IRequestHandler
の実行予約が完了した時に呼び出されます。
-
- onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler)
-
IRequestHandler
の実行が完了した時に呼び出されます。
-
- onException(RequestCycle cycle, Exception ex)
- リクエスト処理中に例外が発生した時に呼び出されます。
- onExceptionRequestHandlerResolved(RequestCycle rc, IRequestHandler rh, Exception ex)
-
IRequestHandler
の選択完了後に例外が発生した時に呼び出されます。
-
- onUrlMapped(RequestCycle cycle, IRequestHandler handler, Url url)
-
IRequestHandler
と対応する URL が生成された時に呼び出されます。
-
こうしたリスナーを利用する場合は createRequestCycle
で新しい RequestCycle
を生成する度に渡されるように、アプリケーションに追加する必要があります:
@Override
public void init() {
super.init();
IRequestCycleListener myListener;
//listener initialization…
getRequestCycleListeners().add(myListener)
}
getRequestCycleListeners
メソッドは org.apache.wicket.request.cycle.RequestCycleListenerCollection
クラスのインスタンスを返却します。このクラスは IRequestCycleListener の型付コレクションの一種で(デザインパターンで言うところの) Composite パターンを実装しています。
Session
クラス
Wicket ではクライアント情報、セッション属性、セッションレベルキャッシュ(8.2.節で見たような)等々のセッションと紐づく情報は org.apache.wicket.Session
クラスでもって操作します。
加えて8.1.節で学んだように、Wicketはユーザセッションを生成して状態を持つページのバージョンを保持しています。 RequestCycle
がそうであったように、新しい Session
のインスタンスは Application
クラスが newSession(Request request, Response response)
メソッドを用いて生成します。このメソッドは final 宣言されていないため Session
クラスのカスタム実装を用いたい場合はオーバーライドすることができます。
アプリケーションが WebApplication
のサブクラスであった場合のデフォルトでは、 newSession
メソッドは org.apache.wicket.protocol.http.WebSession
クラスのインスタンスを返却します。RequestCycle
の説明で言及したように、現行スレッドと対応するインスタンスを取得するための静的メソッド get()
を Session
クラスも提供しています。
Session
とリスナー
RequestCycle
と同様に org.apache.wicket.Session
クラスもリスナーをサポートします。 Session
の場合はこれらのリスナーは onCreated(Session session)
メソッドのみの org.apache.wicket.ISessionListener
インタフェースのコールバックを実装します。名前から推察される通り、このメソッドは新しいセッションが生成された時に呼び出されます。Session
のリスナーは RequestCycle
のリスナーと同様に型付コレクションでもってアプリケーションに追加する必要があります:
@Override
public void init(){
super.init();
//listener initialization…
ISessionListener myListener;
//add a custom session listener
getSessionListeners().add(myListener)
}
セッション属性の操作
Session
クラスは標準インタフェースの javax.servlet.http.HttpSession
とほぼ同様の方法でセッション属性を操作します。セッション属性は以下のメソッドで生成、読み取り、削除します:
- setAttribute(String name, Serializable value)
- 指定された名前の属性を生成します。セッションに同名の属性が既に存在する場合は新しい値で置き換えられます。値にはシリアライズ可能なもののみ指定できます。
- getAttribute(String name)
- 指定された名前の属性の値、もしくは存在しない場合は null を返します。
- removeAttribute(String name)
- 指定された名前の属性を削除します。
WebSession
のデフォルトでは属性の格納の底としてHTTPセッションを用います。Wicketは属性名の前に自動的に接頭辞を付与します。この接頭辞は WebApplication
の getSessionAttributePrefix()
メソッドが返却します。
アプリケーション間のセッション属性名競合をこの接頭辞で回避しているようです。
HTTPセッションのアクセス方法
何らかの理由で底となる HttpSession
インスタンスに直接アクセスしたい場合には、現行リクエストから以下のコードで取得できます:
HttpSession session = ((ServletWebRequest)RequestCycle.get()
.getRequest()).getContainerRequest().getSession();
Wicketによって付与される接頭辞無しで特定のセッション属性を設定する必要があるような場合に、素のままのセッションインスタンスが必要になります。具体例としては Tomcat を Webサーバとして利用しているとしましょう。Tomcatの提供する管理ツールの一つに、指定されたWebアプリケーションのアクティブユーザセッション一覧を表示するページがあります:
Tomcatでは『推定地域』列と『推定ユーザ名』列に表示される値を設定可能です。『Locale』と『userName』という名前のセッション属性を使うのが設定方法の一つになりますが、Wicketの Session
クラスではTomcatの要求する名前そのものの属性を作ることはできないため、素のままの HttpSession
を用いて設定する必要があります:
HttpSession session = ((ServletWebRequest)RequestCycle.get().
getRequest()).getContainerRequest().getSession();
session.setAttribute("Locale", "ENGLISH");
session.setAttribute("userName", "Mr BadGuy");
一時セッションと永続セッション
ユーザがアクセスするのが状態のないページであるならば、Wicketはデータを格納するためのユーザセッションを必要としません。そうした条件下であってもなお、各リクエストを処理するための一時セッションは生成されますが、そうした一時セッションはリクエストの終了と同時に破棄されます。現在のセッションが一時セッションかどうかを調べるには isTemporary
メソッドを使います:
Session.get().isTemporary();
一時セッションでなければ(つまり永続セッションなら)、getId()
メソッドで取得できるユニークなIDでもって識別可能です。この値は一時セッションでは null となります。
Wicketは自動的に一時セッションを永続セッションに置き換える必要性を自動的に検知しますが、当初は一時セッションであったものを永続セッションに切り替えるこの処理は、時として手動で制御しなくてはならなくなります。
これを実現するためのシナリオとして、セッション属性を設定する状態のないホームページと、その後にリダイレクトされ、設定されたセッション属性をラベルとして表示するページを持つ BindeSessionExample プロジェクトについて検討してみましょう。2つのページのコードは以下になります:
ホームページ:
public class HomePage extends WebPage {
public HomePage(final PageParameters parameters) {
Session.get().setAttribute("username", "tommy");
Session.get().bind();
setResponsePage(DisplaySessionParameter.class);
}
}
リダイレクト先:
public class DisplaySessionParameter extends WebPage {
public DisplaySessionParameter() {
super();
add(new Label("username", (String) Session.get().getAttribute("username")));
}
}
繰り返しますが、不必要なコードによる膨張を避けるためにページロジックは極めてシンプルにしています。上記の切り抜きで太字で強調しなくてはならないのは一時セッションを永続セッションに切り替える Session
クラスの bind()
メソッドになります。ホームページでこのメソッドが呼び出されていない場合、リクエストの終わりでセッションは破棄され、リダイレクト先ページでラベルに表示される値は空になります。
セッションデータの破棄
ユーザがWebアプリケーションの利用を終えた時、ログアウトすることでセッションデータを破棄できるようになっているべきです。現行リクエストの完了後、永続セッションが確実に破棄されるようにするために Session
クラスは invalidate()
メソッドを提供しています。リクエストの完了を待たずに即座にセッションを無効化したい場合には invalidateNow()
メソッドを呼び出すことができます。
invalidateNow()
メソッドは即座にあらゆるページ部品(とページ)のインスタンスをセッションから除去する、つまり一度このメソッドを呼び出してしまったら、後の処理ではそれを利用することはできなくなることに注意してください。
任意のオブジェクトをメタデータ付きで保存
JavaServer Pages 仕様(第1版) ではページが生成し、アクセス可能な変数に4つのスコープを定義しています:
- リクエスト
- このスコープで宣言された変数は同一リクエスト内でのみ参照可能です。このスコープの変数の生存期間は(ほとんど)紐付けられたリクエストの生存期間と同じになります。このスコープの変数はすべてのレスポンスが生成されるか、リクエストがどこか別の場所に転送された時点で破棄されます。
- ページ
- このスコープで宣言された変数は生成したページ内からのみ参照可能です。
- セッション
- このスコープで宣言された変数は同一セッション内の全てのページにおいて生成と参照が可能です。
- アプリケーション
- 最も広域のスコープになります。アプリケーション内のあらゆるページから使用できます。(
グローバル変数です。基本的に使っちゃ駄目。)
- 最も広域のスコープになります。アプリケーション内のあらゆるページから使用できます。(
WicketはJSP仕様を実装しているわけではありませんが(どちらかというと代替物です)、スコープ付き変数と良く似た、ただしより強力なメタデータという機能を持ちます。メタデータはユニークキーと対応する値のペアを格納するJavaのMapと非常によく似ています。Wicketでは次のクラスがそれぞれメタデータを格納できます:RequestCycle
、Session
、Application
と Component
メタデータのキーには org.apache.wicket.MetaDataKey<T>
クラスのインスタンスを用います。任意のオブジェクトをメタデータに格納するには2引数の setMetaData
メソッドを用います:値を格納するキーと値そのものです。Session
や Component
のメタデータを使う場合、Wicketはそれらのインスタンスをシリアライズするため、値はシリアライズ可能でなくてはなりません。この制約はあらゆるオブジェクトを格納できる Application
と RequestCycle
のメタデータには当てはまりません。全てのメタデータにおいて値のデータ型はキーの型パラメータTと互換性のあるものでなくてはなりません。
以前に設定したオブジェクトを取得するには getMetaData(MetaDataKey<T> key)
メソッドを使用します。次の例では アプリケーション内の全てのページから利用できるように java.sql.Connection
オブジェクトを Application
のメタデータに設定しています:
Application
クラスのコード:
public static MetaDataApp extends WebApplication{
//Do some stuff…
/**
* Metadata key definition
*/
public static MetaDataKey<Connection> connectionKey = new MetaDataKey<Connection> (){};
/**
* Application's initialization
*/
@Override
public void init(){
super.init();
Connection connection;
//connection initialization…
setMetaData(connectionKey, connection);
//Do some other stuff..
}
}
メタデータからオブジェクトを取得するコード:
Connection connection = Application.get().getMetaData(MetaDataApp.connectionKey);
MetaDataKey<T>
クラスは抽象クラスのため、サブクラスを実装するか(上の例のように)無名クラスを実装する必要があります。
9.5 例外処理
Wicketはアプリケーションの平常運転中にたくさんのカスタム例外を使用しています。ページバージョンが期限切れになった場合に発行される PageExpiredException
については既に見たとおりです。そうした例外の他の例としては AuthorizationException
と RestartResponseException
があります。 これらについては次の章で見ます。レンダリング段階で発生したそれ以外の全ての例外は org.apache.wicket.request.IExceptionMapper
インタフェースの実装、デフォルトでは org.apache.wicket.DefaultExceptionMapper
クラスによって処理されます。DEVELOPMENTモードであれば、このマッパーは例外のスタックトレースを表示するページ(ExceptionErrorPage)にリダイレクトします。一方でDEPLOYMENTモードで動作中の場合、デフォルトでは org.apache.wicket.markup.html.pages.InternalErrorPage となる内部エラーページを表示します。カスタマイズされた内部エラーページを使用したい場合は次の用にアプリケーション設定を変更します:
getApplicationSettings().setInternalErrorPage(MyInternalErrorPage.class);
ExceptionErrorPageを表示すべきか、内部エラーページを使用すべきか、予期しない例外の場合は全く何も表示しないように
するかを手動で背sっていすることもできます:
//show default developer page
getExceptionSettings().setUnexpectedExceptionDisplay( ExceptionSettings.SHOW_EXCEPTION_PAGE );
//show internal error page
getExceptionSettings().setUnexpectedExceptionDisplay( ExceptionSettings.SHOW_INTERNAL_ERROR_PAGE );
//show no exception page when an unexpected exception is thrown
getExceptionSettings().setUnexpectedExceptionDisplay( ExceptionSettings.SHOW_NO_EXCEPTION_PAGE );
またデフォルトの例外マッパー DefaultExceptionMapper
ではなくカスタマイズした例外マッパーを使う選択肢もあります。その場合は Application
の getExceptionMapperProvider
メソッドをオーバライドする必要があります:
@Override
public IProvider<IExceptionMapper> getExceptionMapperProvider()
{
//…
}
このメソッドからカスタム例外マッパーを生成する org.apache.wicket.util.IProvider
インタフェースを返却します。
Ajaxリクエスト
アプリケーションでAjaxリクエストの振る舞いを制御するためには org.apache.wicket.settings.ExceptionSettings
クラスの setAjaxErrorHandlingStrategy(ExceptionSettings.AjaxErrorStrategy)
メソッドを使用することになります。デフォルトではAjaxリクエスト処理中にエラーが発生した場合、設定されたエラーページを描写します。org.apache.wicket.settings.ExceptionSettings
クラスのデフォルト戦略を AjaxErrorStrategy
列挙子の INVOKE_FAILURE_HANDLER
に設定することで、アプリケーションはJavaScriptの onFailure コールバックを呼び出すようになります。
原文の時点で色々説明が足りません。
9.6 まとめ
この章ではWicketの内部でWebリクエストがどの用に取り扱われるかを見てきました。ほとんどの場合においてこれらの内部処理をカスタマイズする必要は無いのですが、どのように動作しているかを知ることはフレームワークを100%活用するうえで非常に大切なことです。
アプリケーションやセッションといった実体は21章でセキュリティに関する話題に突入した時に役立つことになります。