第12章: ページ遷移
Wicket のページ遷移は、通常の Web フレームワークとは少し異なります。 ステートフルなページとブックマーク可能なページの概念を理解することが重要です。 この章では、ページ遷移の各種方法とURL マウントについて解説します。
ページ遷移の方法
| 方法 | 用途 | ブックマーク可能 |
|---|---|---|
setResponsePage(new Page()) |
インスタンスを渡して遷移 | いいえ(ステートフル) |
setResponsePage(Page.class) |
クラスを渡して遷移 | はい |
BookmarkablePageLink |
リンクコンポーネント | はい |
Link + setResponsePage |
処理後に遷移 | 遷移先による |
setResponsePage
インスタンスを渡す方法(ステートフル)
ページのインスタンスを直接渡すと、そのインスタンスがレスポンスとして使われます。 コンストラクタでデータを渡したい場合に使います。
// Link の onClick 内で遷移
add(new Link<Void>("detailLink") {
@Override
public void onClick() {
User user = getSelectedUser();
setResponsePage(new UserDetailPage(user));
}
});
// フォームの onSubmit 内で遷移
Form<Void> form = new Form<>("form") {
@Override
protected void onSubmit() {
// 処理を実行してから遷移
userService.save(user);
setResponsePage(new UserListPage());
}
};
インスタンスを渡す方法では、URL はランダムなセッション固有のものになります(例: ?2-1.ILinkListener-detailLink)。
ブラウザのブックマークや URL の共有には向きません。
クラスを渡す方法(ブックマーク可能)
ページクラスを渡すと、Wicket がそのクラスのデフォルトコンストラクタ(または PageParameters を受け取るコンストラクタ)を呼び出して新しいインスタンスを生成します。
// クラスだけを指定(デフォルトコンストラクタが呼ばれる)
setResponsePage(HomePage.class);
// パラメータ付きで遷移
PageParameters params = new PageParameters();
params.add("id", 42);
setResponsePage(UserDetailPage.class, params);
BookmarkablePageLink
BookmarkablePageLink は、ブックマーク可能な URL を生成するリンクコンポーネントです。
通常の <a href="..."> タグとして出力されます。
// 基本的な使い方
add(new BookmarkablePageLink<>("homeLink", HomePage.class));
// パラメータ付き
PageParameters params = new PageParameters();
params.add("id", userId);
add(new BookmarkablePageLink<>("userLink",
UserDetailPage.class, params));
HTML:
<a wicket:id="homeLink">ホーム</a>
<a wicket:id="userLink">ユーザー詳細</a>
SEO やブックマーク対応が必要なリンクには BookmarkablePageLink を使いましょう。
サーバー側処理が必要なリンクには Link + setResponsePage() を使います。
PageParameters
PageParameters は、URL パラメータをページに渡すためのクラスです。
ブックマーク可能なページでデータを受け渡す唯一の方法です。
パラメータの設定と取得
// パラメータの設定
PageParameters params = new PageParameters();
params.add("id", 42);
params.add("tab", "profile");
params.add("sort", "name");
// パラメータの取得(ページのコンストラクタで)
public UserDetailPage(PageParameters params) {
Long id = params.get("id").toLong();
String tab = params.get("tab").toString("info"); // デフォルト値付き
String sort = params.get("sort").toOptionalString(); // null 可
// パラメータが存在するかチェック
if (params.get("id").isNull()) {
throw new AbortWithHttpErrorCodeException(400);
}
}
インデックスパラメータ
名前付きパラメータに加えて、インデックス(位置)ベースのパラメータも使えます。
// インデックスパラメータの設定
PageParameters params = new PageParameters();
params.set(0, "users"); // 1番目のパス要素
params.set(1, 42); // 2番目のパス要素
// URL: /page/users/42
// インデックスパラメータの取得
String type = params.get(0).toString();
Long id = params.get(1).toLong();
マウント URL
デフォルトでは Wicket のブックマーク可能な URL は /wicket/bookmarkable/com.example.UserPage
のような長い形式になります。mountPage() を使うと、
わかりやすい URL をページにマッピングできます。
// WicketApplication の init() で設定
@Override
public void init() {
super.init();
// 基本的なマウント
mountPage("/users", UserListPage.class);
mountPage("/about", AboutPage.class);
// パラメータ付きマウント
mountPage("/user/${id}", UserDetailPage.class);
// URL: /user/42 → PageParameters で id=42 として取得
// オプショナルパラメータ(#{} を使用)
mountPage("/user/${id}/#{tab}", UserDetailPage.class);
// URL: /user/42 も /user/42/profile も有効
}
マウントパターンの記法
| 記法 | 意味 | 例 |
|---|---|---|
${param} |
必須パラメータ | /user/${id} → /user/42 |
#{param} |
オプショナルパラメータ | /user/${id}/#{tab} → /user/42 or /user/42/profile |
| 固定文字列 | URL のパス部分 | /admin/users |
マウント URL を使うと、SEO フレンドリーで覚えやすい URL になります。 すべてのブックマーク可能なページにマウント URL を設定することを推奨します。
リダイレクト
RestartResponseException
処理を即座に中断して別のページにリダイレクトしたい場合は RestartResponseException を使います。
// 認証チェック: 未ログインならログインページへリダイレクト
public SecurePage() {
if (!isUserLoggedIn()) {
throw new RestartResponseAtInterceptPageException(
LoginPage.class);
}
// 通常の初期化処理...
}
// ログイン成功後、元のページに戻る
public void onLoginSuccess() {
continueToOriginalDestination();
// 元のページがない場合のフォールバック
setResponsePage(HomePage.class);
}
ステートフル vs ステートレス
ページ遷移の方法によって、ページのステートフル/ステートレスが変わります。
| 観点 | ステートフル | ステートレス |
|---|---|---|
| URL | セッション固有のID付き | きれいなURL(マウント可能) |
| セッション | ページがセッションに保存される | セッション不要 |
| 戻るボタン | ページストアから復元 | 毎回新規生成 |
| スケーラビリティ | セッションメモリが必要 | メモリ消費少 |
| 用途 | フォーム、AJAX を使うページ | 表示のみのページ、一覧 |
エラーページ
Wicket では、エラーページもカスタマイズできます。
@Override
public void init() {
super.init();
// 404 Not Found ページ
getApplicationSettings()
.setPageExpiredErrorPage(PageExpiredPage.class);
// 500 Internal Error ページ
getApplicationSettings()
.setInternalErrorPage(InternalErrorPage.class);
// アクセス拒否ページ
getApplicationSettings()
.setAccessDeniedPage(AccessDeniedPage.class);
}
まとめ
| やりたいこと | 使う方法 |
|---|---|
| 単純なページ間リンク | BookmarkablePageLink |
| パラメータ付き遷移 | BookmarkablePageLink + PageParameters |
| 処理後の遷移 | setResponsePage() |
| きれいな URL | mountPage() で設定 |
| 認証リダイレクト | RestartResponseAtInterceptPageException |
実務でハマりやすい注意点
-
問題ログインに成功しても、さっき開こうとしていたページへ戻れない。利用者体験が分断される。原因認証前の遷移先を保持せず、ログイン後に固定ページへ飛ばしている。意図した導線が失われる。対策認証成功時に元の遷移先へ復帰させる導線を実装する。途中遷移でも目的画面に戻れるようにする。
-
問題URL を共有しても同じ画面が再現できず、サポートや運用が難しくなる。ブックマーク運用にも向かない。原因パラメータを隠すために stateful 遷移へ寄せすぎている。再訪可能なURLの利点を捨ててしまっている。対策「秘匿」と「再現性」の優先度を先に決め、必要最小限だけ stateful 化する。公開導線は bookmarkable を優先する。
-
問題戻る操作やタブ複製をしただけで、PageExpiredException / StalePageException が発生する。通常操作に見えるため混乱しやすい。原因古い renderCount を持つページ状態で再操作している。Wicket の状態整合チェックに引っかかる。対策再読み込みで復帰できる導線とユーザー通知を用意する。更新系画面では多重操作の抑止も入れる。
-
問題同じ画面を複数タブで開くと、片方の更新が失敗しやすい。利用者には不規則な不具合に見える。原因同一ページインスタンスを並行操作し、状態整合が崩れている。更新順やタイミングの競合が起こる。対策多重タブ利用を前提に、操作制限や画面設計を見直す。必要なら編集画面に排他制御を入れる。
-
問題想定していない操作でも stale 扱いになり、ユーザーが戸惑う。問い合わせ時に再現説明が難しい。原因View Source など通常外の操作でも状態差分が生じる場合がある。利用者の操作意図と例外理由が一致しない。対策エラー文言に「再読み込みで復帰」を明記し、サポート手順を統一する。復旧方法を迷わせない。
-
問題StalePageException の調査に時間がかかり、復旧が遅れる。ログはあるのに原因が特定できない。原因ログ解析だけで追っており、実際の操作シナリオを確認していない。再現条件の軸が定まっていない。対策まず別タブ、戻る、二重送信の再現条件から切り分ける。操作ログと例外ログをセットで確認する。