C
Spring Boot/Web MVC/Lesson 05

Web MVC — *リクエストがレスポンスになるまで*

45分·theory
このチャプター
1/2

Web MVC — *リクエストがレスポンスになるまで*

🎯 このレッスンを読んだあとに

このレッスンを最後まで読めば、以下の 3 つを自信を持ってできるようになります。

  • ✅ @RestController + @RequestMapping で REST API 4 種 (GET/POST/PUT/DELETE) を実装
  • ✅ @RequestBody / @PathVariable / @RequestParam の違いを理解して使い分ける
  • ✅ @RestControllerAdvice でグローバル例外処理パターンを設定する

これらの学習目標をチェックリストとして手元に置き、すべてに答えられるようになったらレッスンを閉じてください。

リクエストは*どのように*処理されるか — DispatcherServlet のフロー

核心の一文

ブラウザが GET /users/42 リクエストを送ると、Spring は DispatcherServlet という交通整理係を経由して、適切なコントローラーへ転送します。

6 ステップのフロー

1. HTTP リクエスト到着 — 組み込み Tomcat が受け取る
2. DispatcherServlet — Spring の入口。すべてのリクエストがここを通る
3. HandlerMapping — 「この URL はどのコントローラーどのメソッド?」
4. コントローラー実行 — ビジネスロジック + Service 呼び出し
5. レスポンスオブジェクト — DTO を返す → Jackson が JSON にシリアライズ
6. HTTP レスポンス — ブラウザへ

code
リクエスト: GET /api/users/42
       │
       ▼
┌──────────────────────┐
│   Tomcat (組み込み)   │
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│ DispatcherServlet    │  ← すべてのリクエストの入口
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│   HandlerMapping     │  ← 「どのメソッド?」
└────────┬─────────────┘
         │
         ▼
┌──────────────────────┐
│  UserController      │
│   .get(42)           │  ← 実際のロジック
└────────┬─────────────┘
         │
         ▼  Jackson → JSON
{"id":42, "name":"ホン・ギルドン"}

各ステップは Spring が自動的に処理します。開発者は @RestController クラスの中に @GetMapping("/users/{id}") メソッドを書くだけです。

Filter vs Interceptor vs AOP — 処理の途中に割り込む

リクエスト処理の途中に共通ロジック(認証・ロギング・圧縮)を挿入したい場合、3 つの選択肢があります。

Filter — サーブレット標準

DispatcherServlet のに動作。HTTP レスポンス全体を扱います。

java
@Component
public class LoggingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                     HttpServletResponse res,
                                     FilterChain chain) {
        log.info("リクエスト: {} {}", req.getMethod(), req.getRequestURI());
        chain.doFilter(req, res);     // 次のフィルター・サーブレットへ
        log.info("レスポンス: {}", res.getStatus());
    }
}

用途: すべてのリクエストに共通 — ロギング・圧縮・CORS・認証トークン抽出

Interceptor — Spring MVC レイヤー

DispatcherServlet の、コントローラー呼び出しの前後に割り込みます。

java
@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        // コントローラー呼び出し *前*
        String token = req.getHeader("Authorization");
        if (!isValid(token)) {
            res.setStatus(401);
            return false;       // 中断
        }
        return true;
    }
}

用途: 特定のコントローラーパターンにのみ適用。URL ベースの権限管理・ロギング。

AOP — メソッド単位

特定のメソッド呼び出しの前後。最も細粒度

java
@Aspect
@Component
public class TimingAspect {
    @Around("@annotation(Timed)")
    public Object measure(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        log.info("{} 実行 {}ms", pjp.getSignature(), System.currentTimeMillis() - start);
        return result;
    }
}

@Service
public class OrderService {
    @Timed
    public void process() { ... }
}

@Transactional@Cacheable などはすべて AOP ベースです。

いつ何を使う?

  • すべてのリクエストに共通 → Filter
  • URL パターンベース → Interceptor
  • 特定のメソッド → AOP

まとめ

DispatcherServlet がすべてのリクエストを受け取り、適切なコントローラーへルーティングします。途中に共通ロジックを挿入したい場合は、Filter・Interceptor・AOP の中から選択してください。細粒度が必要なほど後ろの選択肢を使います。

@RestController + @GetMapping — *REST API を作る*

@Controller vs @RestController

従来の Spring の @ControllerView (HTML) を返していました。JSP や Thymeleaf などのテンプレートエンジンと組み合わせてサーバーサイドレンダリング (SSR) を行っていました。

@RestControllerデータ (JSON) を返します。SPA やモバイルアプリと通信する REST API の標準。実質的には @Controller + @ResponseBody の組み合わせです。

java
@RestController         // すべてのメソッド応答が JSON
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public UserDto get(@PathVariable Long id) {
        return userService.findById(id);
    }
}

返り値の UserDto オブジェクトが Jackson によって自動的に JSON に変換され、ブラウザへ送信されます。シンプルです。

HTTP メソッドマッピング

メソッドアノテーション用途
GET@GetMapping取得
POST@PostMapping作成
PUT@PutMapping全体置換
PATCH@PatchMapping部分更新
DELETE@DeleteMapping削除

すべて @RequestMapping(method = RequestMethod.GET) の短縮形です。明示的な意味があるため、常に上記の短縮形を使いましょう。

パラメーターを受け取る 4 つの方法

1. @PathVariable — URL パスの変数:

java
@GetMapping("/users/{id}")
public User get(@PathVariable Long id) { ... }
// GET /users/42 → id = 42

2. @RequestParam — クエリ文字列:

java
@GetMapping("/users")
public List<User> list(@RequestParam(defaultValue = "0") int page,
                        @RequestParam(required = false) String name) { ... }
// GET /users?page=2&name=山田 → page=2, name="山田"

3. @RequestBody — JSON 本文 (POST/PUT):

java
@PostMapping("/users")
public UserDto create(@RequestBody @Valid CreateUserDto dto) { ... }
// POST /users { "name":"ホン" } → dto.name = "ホン"

4. @RequestHeader / @CookieValue — HTTP ヘッダー・クッキー:

java
@GetMapping("/me")
public UserDto me(@RequestHeader("Authorization") String token) { ... }

レスポンス — HTTP ステータスコードの明示

返り値だけ指定すると、デフォルトで 200 OK が返ります。別のステータスが必要な場合:

java
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)        // 201

public UserDto create(@RequestBody CreateUserDto dto) { ... }

// またはより細かく:

@GetMapping("/{id}")

public ResponseEntity get(@PathVariable Long id) {

return userService.findById(id)

.map(u -> ResponseEntity.ok(u)) // 200

.orElse(ResponseEntity.notFound().build()); // 404

}

code

## 例外処理 — @RestControllerAdvice

各コントローラーに try-catch を散りばめるのではなく、*グローバル例外処理*を設定しましょう:

java

@RestControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse notFound(EntityNotFoundException e) {
return new ErrorResponse("NOT_FOUND", e.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class)

@ResponseStatus(HttpStatus.BAD_REQUEST)

public ErrorResponse validation(MethodArgumentNotValidException e) {

return new ErrorResponse("VALIDATION_FAILED",

e.getBindingResult().getAllErrors().toString());

}

}

code

これで、どのコントローラーからでも `EntityNotFoundException` をスローすると → *自動的に* 404 レスポンスが返ります。

## 入力バリデーション — @Valid

java

public record CreateUserDto(

@NotBlank @Email String email,

@NotBlank @Size(min=8, max=100) String password,

@NotBlank String name

) { }

@PostMapping("/users")
public UserDto create(@RequestBody @Valid CreateUserDto dto) { ... }
``

バリデーション失敗 → MethodArgumentNotValidException が自動的にスロー → 上記の GlobalExceptionHandler がクリーンな 400 レスポンスを返します。

まとめ

  • @RestController + @GetMapping で即座に REST API を構築
  • @PathVariable@RequestParam@RequestBody で入力を受け取る
  • @ResponseStatusResponseEntity でステータスコードを制御
  • @RestControllerAdvice でグローバル例外処理
  • @Valid` + Bean Validation で入力バリデーション

🤖 AI にはこう依頼してみましょう

このレッスンの概念を理解していれば、AI に具体的な指示を出すことができます。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。

  • 「このコントローラーに @ControllerAdvice ベースのグローバル例外処理を追加して」
  • 「User CRUD API 4 つ (GET/POST/PUT/DELETE) のコントローラーを作って」
  • 「このレスポンスを ResponseEntity でラップして、201 / 204 ステータスコードを正確に返すようにして」

なぜトークンが減るのか

概念を知らないと、AI の回答を受け取っても「それって何ですか?」と再度聞き返す必要があります。その「聞き返し」がトークンを消費します。概念を一度理解しておけば、会話が一発で終わります。

Web MVC — リクエストからレスポンスまで - Spring Boot