Spring MVCフレームワークでは、コントローラーからビューへデータを渡すための複数のメカニズムを提供しています。主な手段として、ModelAndViewオブジェクトの利用、メソッド引数へのMapやModelインターフェースの注入、セッションスコープでの属性共有を実現する@SessionAttributesアノテーション、そしてリクエストパラメータをバインディングする@ModelAttributeアノテーションがあります。
1. ModelAndView オブジェクトによる制御
ハンドラーメソッドの戻り値型として ModelAndView を指定する場合、ビュー名とモデルデータを同時に設定できます。このオブジェクトに追加されたデータは、自動的にリクエストスコープに配置され、JSPなどのビューで参照可能になります。
// index.jsp からのリンク例
<a href="/mvc/model/test-mav">ModelAndView テスト</a>
package com.example.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.Date;
@Controller
@RequestMapping("/model")
public class ModelDataController {
private static final String VIEW_NAME = "result";
@RequestMapping("/test-mav")
public ModelAndView handleWithModelAndView() {
// ビュー名を指定してインスタンス化
ModelAndView mav = new ModelAndView(VIEW_NAME);
// モデルデータ(属性)を追加
mav.addObject("currentTimestamp", new Date());
return mav;
}
}
ビュー側では、EL式を用いて値を取得します。
<%-- result.jsp --%>
<p>現在時刻: ${requestScope.currentTimestamp}</p>
2. Map または Model インターフェースの注入
ハンドラーメソッドの引数に java.util.Map、org.springframework.ui.Model、または ModelMap を宣言すると、Springがこれらの実装を注入します。マップに格納したキー・バリューペアは、メソッド実行完了後にモデル属性として自動転送されます。
// index.jsp からのリンク例
<a href="/mvc/model/test-map">Map/Model テスト</a>
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
import java.util.Map;
@RequestMapping("/test-map")
public String handleWithMap(Map<String, Object> model) {
// Map に直接データを追加
model.put("userList", Arrays.asList("Alice", "Bob", "Charlie"));
// 戻り値は単なるビュー名文字列でOK
return "list-view";
}
ビューでの表示:
<c:forEach items="${requestScope.userList}" var="name">
<li>${name}</li>
</c:forEach>
3. @SessionAttributes によるセッション共有
特定のモデル属性を HTTP セッションに保持し、複数のリクエスト間で状態を維持したい場合、コントローラークラスに対して @SessionAttributes アノテーションを使用します。これにより、指定された属性名または型を持つデータがセッションにコピーされます。
// index.jsp からのリンク例
<a href="/mvc/model/test-session">Session Attributes テスト</a>
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
// クラスレベルでアノテーションを適用
@SessionAttributes(
value = {"currentUser"}, // 属性名で指定
types = {String.class} // 型で指定 (この例では String 型の 'school' も対象になる可能性があるため注意が必要だが、通常は value で明示するのが一般的)
)
@Controller
@RequestMapping("/model")
public class SessionScopeController {
@RequestMapping("/test-session")
public String setupSessionData(Map<String, Object> model) {
User user = new User("David", "password123", "Tokyo");
// この 'currentUser' はセッションにも保存される
model.put("currentUser", user);
// この 'message' は value や types に一致しないため、リクエストスコープのみ
model.put("message", "Welcome back!");
return "dashboard";
}
}
ビューでの確認:
<p>Request Scope User: ${requestScope.currentUser}</p>
<p>Session Scope User: ${sessionScope.currentUser}</p>
<p>Request Scope Message: ${requestScope.message}</p>
<%-- sessionScope.message は存在しないため出力されない --%>
4. @ModelAttribute による事前ロードとバインディング
@ModelAttribute は主に2つの用途で使用されます。一つは、対象メソッドの実行前に呼び出される「プリロードメソッド」として機能し、データベースから既存のエンティティを読み込んでモデルに追加することです。もう一つは、POJO引数に対するバインディングルールを定義することです。
以下の例では、フォーム送信時にIDに基づいてユーザー情報をDBから取得し、その情報にフォーム入力内容をマージして更新を行うシミュレーションを示します。
<%-- form.jsp --%>
<form action="/mvc/model/update-user" method="post">
<input type="hidden" name="id" value="101"/>
Name: <input type="text" name="username" value="ExistingName"/><br/>
Email: <input type="text" name="email" value="new@example.com"/><br/>
Age: <input type="number" name="age" value="25"/><br/>
<input type="submit" value="Update"/>
</form>
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
/**
* 対象メソッド実行前に Spring MVC によって呼び出される
*/
@ModelAttribute
public void loadUserForEditing(@RequestParam(value = "id", required = false) Integer id,
Map<String, Object> model) {
if (id != null) {
// 実際には DAO 経由で DB から取得
User existingUser = new User(id, "OriginalName", "orig@email.com", 20);
System.out.println("Pre-loading user from DB for ID: " + id);
// キーはクラス名の先頭小文字 ('user') と一致させる必要がある
model.put("user", existingUser);
}
}
/**
* フォーム送信時に呼ばれるメインメソッド
* 引数の User オブジェクトには、loadUserForEditing でセットアップされた
* プロパティに、フォームからの新しい値がバインドされている。
*/
@RequestMapping("/update-user")
public String processUserUpdate(User updatedUser) {
// updatedUser には DB の元データとフォーム入力がマージされている
System.out.println("Updating user: " + updatedUser.getUsername());
return "success-page";
}
動作フローの詳細:
- リクエストを受信後、まず
@ModelAttributeが付与されたメソッド (loadUserForEditing) が実行され、Implicit Model にキー"user"で初期オブジェクトが登録されます。 - 次に、ターゲットメソッド (
processUserUpdate) の引数であるUserオブジェクトの解決が行われます。 - Spring MVC は、引数の型と名前(または
@ModelAttributeの value 属性)に基づき、Implicit Model から対応するキーを検索します。 - 該当するオブジェクトが存在すれば、それをベースとしてリクエストパラメータをバインディングします。存在しない場合は、新規インスタンスを作成してバインディングします。
- 最終的に、バインディング後のオブジェクトがメソッド引数として渡されます。
この仕組みにより、パスワードなど変更すべきでないフィールドを隠蔽しつつ、他のフィールドのみを更新するような複雑な編集処理を簡潔に記述することが可能になります。