Skip to content
Playground

Spring MVC

@Controller や、ビュー名からテンプレートが選ばれる仕組みは、Spring が MVC パターンを実装した Web フレームワーク Spring MVC が提供しています。MVC は言語やフレームワークを問わない一般的なパターンで、Spring MVC はその実装の 1 つです。リクエストの受け取りやコントローラーへの振り分けといった共通の処理を Spring MVC が引き受けるため、@Controller のメソッドが受け持つのは、アプリケーション固有の処理です。

Spring MVC は、リクエストの受け取りとコントローラーへの振り分け、ビューの解決を、それぞれ別の部品が担います。

HTTP とサーブレット のサーブレットは、URL ごとに 1 つずつ対応します。Spring MVC では、すべてのリクエストを 1 つの部品が受け取り、適切なコントローラーのメソッドへ振り分けます。この部品が DispatcherServlet です。入り口を 1 つにまとめて全体の流れを管理する部品を、フロントコントローラー と呼びます。

DispatcherServlet は、リクエストの URL(/books)と HTTP メソッド(GET)を各コントローラーの @GetMapping などと照らし合わせ、一致するメソッドを呼び出します。このメソッドを ハンドラー と呼びます。@GetMapping("/books") のメソッドが /books のリクエストで呼ばれるのは、この振り分けによります。

DispatcherServlet 自体もサーブレットで、Tomcat 上で動き、Spring Boot が自動で登録します。リクエストの振り分け・ビューの解決・レスポンスの組み立てといった、アプリケーションによらず共通の処理を担います。

ハンドラーは、表示するテンプレートをビュー名で返します。このビュー名を実際のテンプレートに対応づけるのが ViewResolver です。ビュー名 "books" の前に templates/、後ろに .html を補い、テンプレート templates/books.html を特定します。コントローラー でビュー名に拡張子やフォルダーを含めないのは、この補完によります。

DispatcherServlet は、これらの部品を順に呼び出し、結果を受け取って次へ渡します。/books のリクエストは、次の順に処理されます。

  1. ブラウザーが送った GET /books を Tomcat が受け取り、サーブレットである DispatcherServlet に渡す。
  2. DispatcherServlet がハンドラー(BookControllerbooks)を呼び出す。ハンドラーは Model を用意し、ビュー名 "books" を DispatcherServlet に返す。
  3. DispatcherServlet が ViewResolver にビュー名 "books" を渡し、ViewResolver がテンプレート books.html を特定する。
  4. テンプレートエンジンの Thymeleafbooks.htmlModel の値を埋め込み、組み立てた HTML をレスポンスに書き込む。
  5. DispatcherServlet が組み立てたレスポンスを、Tomcat がブラウザーに返す。

コントローラーのメソッドが呼ばれる前に、リクエストを横断して共通の処理を挟む仕組みが HandlerInterceptor です。DispatcherServlet がハンドラーを呼ぶ前に割り込み、ログイン確認のような共通処理を 1 か所にまとめられます。

セッション で保護した管理操作は、登録フォーム・登録・編集フォーム・更新・削除と増えます。各メソッドの先頭に同じログイン確認を書くと、保護対象が増えるたびに同じコードが各所に散らばり、確認の追加・修正をすべてのメソッドに反映する必要が出てきます。

2-2. preHandle でコントローラーの前に挟む

Section titled “2-2. preHandle でコントローラーの前に挟む”

preHandle メソッドに処理を書き、true を返すとコントローラーのメソッドへ進み、false を返すと進みません。ログイン確認を preHandle に移した LoginInterceptor です。

LoginInterceptor.java
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;
@Component
public 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; // ログイン済み。処理を続行する
}
}

どのパスに LoginInterceptor を適用するかは、WebMvcConfigureraddInterceptors で登録します。

WebConfig.java
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;
@Configuration
public 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/edit1 を受けます。登録後は、各コントローラーのメソッドからログイン確認のコードを取り除け、確認は LoginInterceptor の 1 か所に集約されます。preHandle は、DispatcherServlet がコントローラーのメソッドを呼ぶ前に通り、false を返したリクエストはコントローラーまで届きません。