Spring MVC
@Controller や、ビュー名からテンプレートが選ばれる仕組みは、Spring が MVC パターンを実装した Web フレームワーク Spring MVC が提供しています。MVC は言語やフレームワークを問わない一般的なパターンで、Spring MVC はその実装の 1 つです。リクエストの受け取りやコントローラーへの振り分けといった共通の処理を Spring MVC が引き受けるため、@Controller のメソッドが受け持つのは、アプリケーション固有の処理です。
1. リクエスト処理の仕組み
Section titled “1. リクエスト処理の仕組み”Spring MVC は、リクエストの受け取りとコントローラーへの振り分け、ビューの解決を、それぞれ別の部品が担います。
1-1. DispatcherServlet
Section titled “1-1. DispatcherServlet”HTTP とサーブレット のサーブレットは、URL ごとに 1 つずつ対応します。Spring MVC では、すべてのリクエストを 1 つの部品が受け取り、適切なコントローラーのメソッドへ振り分けます。この部品が DispatcherServlet です。入り口を 1 つにまとめて全体の流れを管理する部品を、フロントコントローラー と呼びます。
DispatcherServlet は、リクエストの URL(/books)と HTTP メソッド(GET)を各コントローラーの @GetMapping などと照らし合わせ、一致するメソッドを呼び出します。このメソッドを ハンドラー と呼びます。@GetMapping("/books") のメソッドが /books のリクエストで呼ばれるのは、この振り分けによります。
DispatcherServlet 自体もサーブレットで、Tomcat 上で動き、Spring Boot が自動で登録します。リクエストの振り分け・ビューの解決・レスポンスの組み立てといった、アプリケーションによらず共通の処理を担います。
1-2. ViewResolver
Section titled “1-2. ViewResolver”ハンドラーは、表示するテンプレートをビュー名で返します。このビュー名を実際のテンプレートに対応づけるのが ViewResolver です。ビュー名 "books" の前に templates/、後ろに .html を補い、テンプレート templates/books.html を特定します。コントローラー でビュー名に拡張子やフォルダーを含めないのは、この補完によります。
1-3. 処理の流れ
Section titled “1-3. 処理の流れ”DispatcherServlet は、これらの部品を順に呼び出し、結果を受け取って次へ渡します。/books のリクエストは、次の順に処理されます。
- ブラウザーが送った
GET /booksを Tomcat が受け取り、サーブレットである DispatcherServlet に渡す。 - DispatcherServlet がハンドラー(
BookControllerのbooks)を呼び出す。ハンドラーはModelを用意し、ビュー名"books"を DispatcherServlet に返す。 - DispatcherServlet が ViewResolver にビュー名
"books"を渡し、ViewResolver がテンプレートbooks.htmlを特定する。 - テンプレートエンジンの Thymeleaf が
books.htmlにModelの値を埋め込み、組み立てた HTML をレスポンスに書き込む。 - DispatcherServlet が組み立てたレスポンスを、Tomcat がブラウザーに返す。
2. HandlerInterceptor
Section titled “2. HandlerInterceptor”コントローラーのメソッドが呼ばれる前に、リクエストを横断して共通の処理を挟む仕組みが HandlerInterceptor です。DispatcherServlet がハンドラーを呼ぶ前に割り込み、ログイン確認のような共通処理を 1 か所にまとめられます。
2-1. メソッドごとの確認の重複
Section titled “2-1. メソッドごとの確認の重複”セッション で保護した管理操作は、登録フォーム・登録・編集フォーム・更新・削除と増えます。各メソッドの先頭に同じログイン確認を書くと、保護対象が増えるたびに同じコードが各所に散らばり、確認の追加・修正をすべてのメソッドに反映する必要が出てきます。
2-2. preHandle でコントローラーの前に挟む
Section titled “2-2. preHandle でコントローラーの前に挟む”preHandle メソッドに処理を書き、true を返すとコントローラーのメソッドへ進み、false を返すと進みません。ログイン確認を preHandle に移した LoginInterceptor です。
package com.example.bookstore;
import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import jakarta.servlet.http.HttpSession;import java.io.IOException;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;
@Componentpublic class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { HttpSession session = request.getSession(); if (session.getAttribute("loggedIn") == null) { response.sendRedirect("/login"); return false; // 未ログイン。コントローラーのメソッドは呼ばれない } return true; // ログイン済み。処理を続行する }}2-3. 保護するパスの登録
Section titled “2-3. 保護するパスの登録”どのパスに LoginInterceptor を適用するかは、WebMvcConfigurer の addInterceptors で登録します。
package com.example.bookstore;
import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configurationpublic class WebConfig implements WebMvcConfigurer { private final LoginInterceptor loginInterceptor;
public WebConfig(LoginInterceptor loginInterceptor) { this.loginInterceptor = loginInterceptor; }
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/books/new", "/books/*/edit"); }}addPathPatterns に保護するパスを並べます。/books/*/edit の * はパスの 1 区切りに一致し、/books/1/edit の 1 を受けます。登録後は、各コントローラーのメソッドからログイン確認のコードを取り除け、確認は LoginInterceptor の 1 か所に集約されます。preHandle は、DispatcherServlet がコントローラーのメソッドを呼ぶ前に通り、false を返したリクエストはコントローラーまで届きません。