ルーティング表という発想
このレッスンでは、Spring Boot が「どの URL のときにどのメソッドを呼ぶか」をどうやって決めているのかを掴みます。答えは意外なほど素朴で、表を引いている だけです。
入口はいつも1つです
Spring Boot の Web アプリでは、届いたリクエストはまず DispatcherServlet という 1 つのサーブレットに集まります。URL がいくつあっても入口は 1 つで、そこから先の振り分けを DispatcherServlet が担当します。この設計をフロントコントローラと呼びます。
DispatcherServlet がやることは、大きく次の流れです。
- リクエストのメソッドとパスを見る
- 対応するハンドラ (コントローラのメソッド) を ハンドラマッピング に問い合わせる
- 見つかったら引数を組み立てて呼び出す
- 戻り値を JSON などに変換して書き戻す
- 見つからなければ 404 を返す
この 2 番目、ハンドラマッピングが今回の主役です。中身は「メソッドとパスの組み合わせから、呼ぶべきメソッドを引く表」です。
@GetMapping は、表への登録です
次のコントローラを見てください。
Java
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public List<User> list() { ... }
@GetMapping("/{id}")
public User find(@PathVariable Long id) { ... }
@PostMapping
public User create(@RequestBody UserRequest req) { ... }
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { ... }
}アプリの起動時に、Spring はコンポーネントスキャンで @RestController の付いたクラスを見つけ、その中のメソッドに付いた @GetMapping などを全部読み取ります。そして次のような表を組み立ててメモリに持ちます。
| メソッド | パス | 呼ぶもの |
|---|---|---|
| GET | /users | UserController#list |
| GET | /users/{id} | UserController#find |
| POST | /users | UserController#create |
| DELETE | /users/{id} | UserController#delete |
クラスに付いた @RequestMapping("/users") は、その中の全メソッドのパスの前に付く接頭辞として働きます。だから @GetMapping("/{id}") の実際のパスは /users/{id} になります。
ここで重要なのは、表が作られるのは起動時の一度きり だという点です。リクエストが来るたびにアノテーションを読み直しているわけではありません。だから 1 リクエストあたりの処理は、単に表を引くだけの軽い処理で済みます。
メソッドが違えば別の行です
同じ /users でも GET と POST は表の別々の行です。ここを取り違えると、動かないときの原因が分からなくなります。たとえば POST /users に対する登録しか無い状態でブラウザから /users を開くと、ブラウザは GET で送るので表に無く、404 ではなく 405 Method Not Allowed が返ります。Spring はパスは一致したがメソッドが違う、という状況を区別できるからです。
演習では話を単純にして、パスもメソッドも一致しなければ一律 404 を返す形で表を実装します。
表を引くコードは、実は普通の Map です
概念としては、キーが「メソッドとパスの組」、値が「ハンドラ名」の Map そのものです。今回の演習では、キーを "GET /users" のような 1 本の文字列に潰してしまうのが一番素直です。実際の Spring はパス変数やワイルドカードを含む照合をするので、単純な Map の完全一致だけでは足りず、もっと複雑な仕組みを持っています。その照合の部分は次のレッスンで扱います。
まずは「フレームワークがやっているのは表引きだ」という骨格を、自分の手で組み立ててみてください。
要件
- Map にメソッドとパスの組を鍵としたルーティング表を作り、5件を登録すること
- 表に一致する組があればハンドラ名を、無ければ "404" を返すこと
- HTTP メソッドは大文字小文字を区別せずに照合すること (get でも GET と同じ扱い)
入出力例
route("GET", "/users") → "listUsers"
route("POST", "/users") → "createUser"
route("GET", "/users/1") → "findUser"
route("DELETE", "/users/1") → "deleteUser"
route("get", "/health") → "health"
route("PUT", "/users") → "404"
route("GET", "/orders") → "404"