前回までの記事では、Springのコア技術であるDI(依存性注入)とAOP(アスペクト指向)について解説しました。今回は、これらの知識を活かしながらSpring MVCを使った実践的なWebアプリケーション構築について、コントローラ設計、リクエスト・レスポンス処理、フォームバリデーションを中心に解説します。
Spring MVCアーキテクチャの全体像
Spring MVCは、Springフレームワークが提供するWebアプリケーション構築のためのMVC1アーキテクチャです。リクエスト処理の責務を明確に分離することで、保守性と再利用性に優れたアプリケーションを実現します。
各コンポーネントの役割
-
DispatcherServlet
: すべてのHTTPリクエストを受け付けるフロントコントローラ -
@Controller
: プレゼンテーション層でリクエストを処理(@RestController
はREST API向け) -
@Service
: ビジネスロジックを実装(トランザクション境界はここに配置することが多い) -
@Repository
: データアクセス層を担当(Spring Data JPAなどと連携) -
View
: レスポンスの生成(Thymeleaf、JSPなど)
この階層構造により、各レイヤーの責務が明確になり、変更の影響を局所化できます。
コントローラの実装とリクエストマッピング
Spring MVCでは、@Controller
アノテーションでコントローラクラスを定義し、各種マッピングアノテーションでURLとメソッドを紐付けます。
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.form.ProductForm;
import com.example.demo.service.ProductService;
import jakarta.validation.Valid;
import java.util.List;
@Controller
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
// Spring Boot 3.x以降は単一コンストラクタの場合@Autowired省略可能
public ProductController(ProductService productService) {
this.productService = productService;
}
// 商品一覧表示
@GetMapping
public String list(Model model) {
List<Product> products = productService.findAll();
model.addAttribute("products", products);
return "product/list"; // templates/product/list.html
}
// 商品登録フォーム表示
@GetMapping("/add")
public String showAddForm(Model model) {
model.addAttribute("productForm", new ProductForm());
return "product/add";
}
// 商品登録処理
@PostMapping("/add")
public String addProduct(
@Validated @ModelAttribute ProductForm form,
BindingResult result, // 必ず検証対象の直後に配置
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "product/add"; // エラー時は入力画面へ
}
Long productId = productService.register(form);
redirectAttributes.addFlashAttribute("message",
"商品を登録しました(ID: " + productId + ")");
return "redirect:/products";
}
}
ポイント解説
-
BindingResultの位置: Spring MVCの仕様上、
BindingResult
は必ず検証対象パラメータの直後に配置する必要があります - RedirectAttributes: リダイレクト時にフラッシュスコープでメッセージを渡せます
- Model2: コントローラからビューへデータを渡すためのコンテナ
Bean Validationによるフォームバリデーション
Spring BootではBean Validation(JSR-380)が自動構成され、アノテーションベースのバリデーションが使えます。
package com.example.demo.form;
import jakarta.validation.constraints.*;
public class ProductForm {
@NotBlank(message = "商品名は必須です")
@Size(max = 100, message = "商品名は100文字以内で入力してください")
private String name;
@NotNull(message = "価格は必須です")
@PositiveOrZero(message = "価格は0以上の値を入力してください")
@Max(value = 1000000, message = "価格は100万円以下で入力してください")
private Integer price;
@Size(max = 500, message = "説明は500文字以内で入力してください")
private String description;
// getter/setter
}
@Validated
とValid の使い分け
-
@Valid
: 標準的なBean Validation(jakarta.validation) -
@Validated
: Springの拡張アノテーション(グループバリデーション3をサポート)
グループバリデーションを使わない場合は@Valid
でも十分ですが、将来の拡張性を考慮して@Validated
を使うことを推奨します。
Thymeleafテンプレートとの連携
ビュー層では、Thymeleafを使ってサーバーサイドの値を表示します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>商品登録</title>
</head>
<body>
<h1>商品登録</h1>
<form th:action="@{/products/add}" th:object="${productForm}" method="post">
<!-- CSRF対策トークン(Spring Securityで自動生成) -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div>
<label for="name">商品名:</label>
<input type="text" id="name" th:field="*{name}" />
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"
style="color: red;">エラーメッセージ</span>
</div>
<div>
<label for="price">価格:</label>
<input type="number" id="price" th:field="*{price}" />
<span th:if="${#fields.hasErrors('price')}" th:errors="*{price}"
style="color: red;">エラーメッセージ</span>
</div>
<button type="submit">登録</button>
</form>
</body>
</html>
エラーメッセージのカスタマイズ
Spring Bootではsrc/main/resources/messages.properties
にメッセージを定義すると、自動的にMessageSourceが構成されます。
# messages.properties
product.name.required=商品名を入力してください
product.price.invalid=正しい価格を入力してください
# messages_en.properties(英語版)
product.name.required=Product name is required
product.price.invalid=Please enter a valid price
例外ハンドリングとAOPの活用
@ControllerAdvice
を使うと、横断的な例外処理を実装できます。これもAOPの考え方を活用した仕組みです。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public String handleDatabaseError(DataAccessException e, Model model) {
model.addAttribute("error", "データベースエラーが発生しました");
return "error/database";
}
@ExceptionHandler(BusinessException.class)
public String handleBusinessError(BusinessException e, Model model) {
model.addAttribute("error", e.getMessage());
return "error/business";
}
}
DI/AOPを活用したアーキテクチャ
Spring MVCのレイヤー構造では、DIによる疎結合とAOPによる横断的関心事の分離が実現されています。
※上図は、DIによる依存関係とAOPによる横断的処理の適用を表しています。Mermaidが描画されない環境では、コントローラ→サービス→リポジトリの依存関係と、それらに対するトランザクション・ロギングのアスペクト適用をイメージしてください。
まとめ
今回は、Spring MVCを使った実践的なWebアプリケーション構築について解説しました。
本記事で学んだこと
- Spring MVCのアーキテクチャとコンポーネントの役割
- コントローラの実装とリクエストマッピング
- Bean Validationによる入力値検証
- Thymeleafテンプレートとの連携
- 例外ハンドリングとAOPの活用
これらの要素が、前回学んだDI/AOPと密接に連携して動作することで、Spring MVCの強力かつ柔軟なWebアプリケーション開発が可能になります。
次回は「Springのデータアクセス(Spring Data JPA)とトランザクション管理」について、より実践的な内容を解説する予定です。
この記事が、Spring MVCを使った実践的なWebアプリケーション開発の第一歩となることを願っています!
参考文献
- Spring Framework Documentation
- Spring Boot Reference Documentation
- Thymeleaf Documentation
- Bean Validation specification
- Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発