한국어 | English | 日本語
Webアプリケーションエンジニア (経験8.8年)
技術・開発
engineering
ウェブフロントエンドと バックエンド開発を扱います

Spring MVCの構造、動作原理、および処理フロー

Spring MVC Abstract
ウェブ開発を始めた当初、Springはウェブアプリケーションサーバー(WAS)を非常に簡単にセットアップし、開発できる画期的なフレームワークでした。入社したばかりの私でも数行のコードでコントローラーを作成できるほどで、Springがなかった時代のウェブ開発者たちには畏敬の念を抱きました。しかし、これは数多くの抽象化を経て得られた利便性であり、私にとっては、ウェブサーバーの理解からしばらく遠ざかる暗黒期を作り出してしまいました。その時になって初めて、なぜトビーのSpringの本がそんなに分厚かったのかを理解でき、それ以降は本当に綿密に勉強し、抽象化を一つずつ剥がしていくことで、Springの本質にさらに近づくことができました。
ウェブサーバーからウェブアプリケーションサーバー(WAS)への発展とSpringの登場、そしてServletを皮切りに、Springの動作原理とクライアントからのリクエストに対する処理フローを考察します。最後に、InterceptorとFilterの概念を簡単に説明します。

Webサーバー (静的ページ)

ユーザーにサーバーにあるページを表示します。

ウェブの初期には、サーバーに静的なドキュメント(HTML)を保存し、ユーザーがリクエストすると、そのファイルをユーザーのブラウザにダウンロードして表示する方式でした。例えば、特定のサーバー aaron.com にある hello.html ドキュメントを見たい場合、ブラウザで aaron.com/hello.html を呼び出す形式です。大学で教授が自身の研究室サーバーを利用して講義資料を配布する際に、以下のようなページを見た読者もいるかもしれません。

Static Page from Web Server

この方式では、サーバーが提供したいファイルを実際にサーバー内部に一つずつ配置する必要があり、サーバーに存在しないファイルにアクセスすると 404 Not Found Error が返されました。このようにユーザーに静的なページを提供するサーバーをWebサーバーと呼び、よく知られているApacheNginxがこれに該当します。

例) Nginxのリクエスト/処理フロー

Webサーバーの例として、Nginxではユーザーのリクエストを以下の過程で処理します。

Web Server

Webアプリケーション (動的ページ)

サーバーにないページを、ユーザーの各リクエストごとに動的に生成して表示します。

Webサーバーがユーザーに単純なドキュメントを共有する一方的なサービスを提供するに留まらず、ユーザーとのインタラクションを通じて会員登録が可能になり、記事の投稿や、投稿された記事を相互に閲覧できるといった双方向サービスの要求を受けるようになりました。一般的なアプリケーションのようにデータベースとの接続も必要になり、会員の状態に応じた動的なページレンダリングのための非同期API呼び出しなどが必要になったのです。サーバーはもはやサーバーにある静的リソースを返すだけでなくユーザーが要求した情報に対応するリソース(ページ)を動的に作成して返すようになりました。

ウェブでアプリケーションのような要求を処理するためには、Webサーバー様々な言語で開発されたプログラムを接続し、ユーザーのリクエストをサーバー経由でプログラムに渡す必要がありました。このようにWebサーバーとプログラムの間を接続する方式をCGI (Common Gateway Interface)と呼び、様々な言語で開発されてきましたが、その中でもJavaにおいてはWebサーバーのリクエスト/レスポンスとJavaアプリケーションの間を接続するServletオブジェクトの概念が登場します。

Servletはユーザーのリクエストごとに一つずつ生成されるため、複数のリクエストに応じたServletリソースの管理が必要になります。この役割を担うのがWebコンテナであり、Servletの視点からはServletコンテナとも呼ばれます。

この両者を合わせたものがWebアプリケーションです。

Web Server

例) Tomcatのリクエスト/処理フロー

Webアプリケーションの例として、Tomcatではユーザーのリクエストを以下の過程で処理します。

Web Application

Webサーバーの図と比較すると、Webサーバーの下に追加されたものはすべてWebコンテナに関連するものです。Webコンテナから始めて、下から上に逆順で見ていきましょう。隣に灰色で表示されている名称は実際のクラス/インターフェース名です。

構成が分かったところで、上記の図に従ってWebアプリケーションの起動時の手順を見ていきましょう。

初回起動時

リクエスト処理時

Spring Framework

Java Servletを活用したWebアプリケーション開発が活発になるにつれて、様々なデザインパターンを適用し、Java Web開発をより容易にするSpring Frameworkが登場しました。初期のWebアプリケーションがページを動的にレンダリングするために各リクエストごとにServletを割り当ててリクエストを処理していたのに対し、Springは各リクエストごとにServletよりも小さい単位であるBeanを割り当ててリクエストを処理します。これは、多数のServletを置かず、単一のServlet(後述しますが、これがDispatcherServletです)を置き、バックエンドで多様かつ多数のBeanを配置することで、リクエストに応じた適切な処理を行うという意味です。

Springは基本的にMVCモデルで、Model、View、Controllerの3つのグループに役割を分離して開発を支援するフレームワークであるため、デザインパターンに関する知識が全くない開発者でも、保守性、再利用性に優れたWebアプリケーションを作成できます。また、データベースアクセス用のJPA、トランザクション、セキュリティなど、Webアプリケーションで必要とされるすべてのものをBean設定で提供するため、どんな初心者でもしっかりとした理解があればWebアプリケーションを簡単に作成できます。実際、しっかりとした理解がなくても作成できるのがSpringの利点です。これはつまり、ジュニア/シニアに関わらず最高の生産性を意味します。

Spring MVCの概念

Spring MVCの動作過程を理解するには、MVCFront Controllerパターン (2レベルController) を知っていれば十分です。

MVC (Model, View, Controller)

  1. ユーザーがあるページをリクエストすると、リクエストに適合するControllerがリクエストを受け取り
  2. リクエストされたページに必要な情報であるModelを検索/生成し、
  3. 検索/生成したModelを通じて最終ページであるViewを生成してユーザーに返します

Front (2レベル) Controllerパターン

上記のMVCにおいて、リクエストを受け取る部分をControllerとしましたが、

では、2レベルControllerの意味は以下の通りです。

一番前のTomcat単一Servlet (DispatcherServlet)を、「リクエストを一番最初に受け取る」という意味でFront Controllerと呼び、その後のSpring Controller Beanを、実際のページ生成に使用されるという意味でPage Controllerと呼びます。

Spring MVCのリクエスト/処理フロー

初回起動時

まず、Spring + Tomcatが最初に起動する際にどのようなオブジェクトが生成されて準備されるかを見ていきましょう。Webコンテナの下にSpringコンテナが新しく追加されているのが分かります。

Spring Web Application - 1. Init

上記の図のようにTomcatにSpringを接続するためには、Tomcatの設定ファイルであるweb.xmlに2つの設定が必要です。

リクエスト処理時

初回起動後、Tomcatはすべてのリクエストを単一のDispatcherServletで受け取る準備が完了し、SpringもController Beanが結果を返すための様々なBeanがRoot WebApplicationContextに準備完了します。それでは、ユーザーがリクエストを送信した際の処理を見ていきましょう。少し複雑に見えるかもしれませんが、上記の初回起動時で見た内容に少し拡張が加えられただけですので、心配する必要はありません。

Spring Web Application - 2. Request

SpringのキーワードはIoC、DIと言えますが、簡単に説明すると、従来は開発者が new を通じてオブジェクトを直接生成し、直接注入していたのに対し、Springではインターフェースだけを明示し、ApplicationContext (= Spring Container, BeanFactoryを継承) がBeanと呼ばれるオブジェクトを生成し、開発者の設定によって自動的に注入してくれる概念です。このようにSpringでは、基本単位のJavaオブジェクトをBeanと呼んで使用します。

SpringにおけるBeanは、Webアプリケーションの観点から、以下のように大きく2つのタイプに区分できます。それに伴い、Beanのライフサイクルを管理するSpringコンテナも2つに分けられます。

上記の図を見ると、初回起動時に生成されたSpring Container 1の下にもう一つのSpring Container 2が生成されているのが分かります。特に重要な内容ではありませんが、「parent」と「child」と書かれているのは、2つのコンテナ間に階層があることを意味し、単に子であるServlet WebApplicationContextのBeanは親であるRoot WebApplicationContextのBeanを参照できますが、その逆は参照できないことを意味します。


リクエスト処理は次の流れで進行します。上記の赤線(リクエスト)と緑線(応答)の流れに従って見ていきましょう。

  1. ユーザーは**Webサーバー**に特定のページ(index.html)をリクエストします。
  2. Webサーバーindex.html を検索しますが、存在しないため、**Webコンテナ (ServletContext)**にリクエストを移管します。
  3. A) ServletContextは、web.xmlで定義された、どのリクエストに対しても / **単一のDispatcherServlet**を生成します。
  4. **DispatcherServletは、ユーザーがリクエストしたページに該当するSpring Controllerがあるか、HandlerMapping**を探索します。
  5. DispatcherServletは、見つけた**Spring Controller BeanHandlerAdapter**を介して呼び出します。
  6. HandlerAdapterは**HelloController Bean**を呼び出します。
  7. HelloControllerは**Root WebApplicationContext内のBeanを活用してDispatcherServlet**に結果を返します。
  8. DispatcherServletはControllerの結果を受け取り、ViewResolver結果ページ (= View) (index.html) を生成します。
  9. DispatcherServletは**結果ページ (= View) (index.html)** をユーザーに返します。

上記の過程のコードレベルでの流れは、こちらのブログリンクに詳しくまとめられているので、参考にすると良いでしょう。

このように、Spring MVCがどのようにユーザーのリクエストを受け取り、処理し、返すのかを図で見てきました。これほど詳細に見ていくと、ControllerやServiceでExceptionが発生した際にSpringのログに残る数多くのスタックトレース内のメソッドやクラス(invoke, DispatcherServlet, preHandle, postHandleなど)の意味をより深く理解できるようになるでしょう。

長い記事でしたが、ここからもう少し踏み込んでみようと思います。それはInterceptorとFilterです。この2つの違いは、単純にServletが管理するかSpringが管理するかであり、呼び出しの順序とタイミングを知っていれば、将来Spring Securityを学習/適用する際に大きな問題はないでしょう。もちろん、ここまで来るのに十分に大変だった場合は、後日でも構いませんのでぜひ読んでいただきたいと思います。

Spring InterceptorとFilterの違い

Springに限らず、どのようなWebアプリケーションでも一部のユーザーにサービスを提供するために公開されているため、セキュリティは絶対に必要です。Spring Securityは、基本的にログインとセッションに関連するモジュールや設定を簡単に利用できるように提供するだけでなく、別途開発した認証モジュールを適用したり、リクエストURLに応じて異なるセキュリティ処理を行ったりと、開発者が望むセキュリティ要素を、Spring Controllerに実際のリクエストが渡される前に実行されるように追加できます。この際に使用されるのがInterceptorFilterです。

私たちはこれまで、Springを使用したWebアプリケーションが大きくTomcat (Webコンテナ)Spring (Springコンテナ)の2つで構成されることを学びました。InterceptorFilterは、管理主体および実行時間がこの2つの構成要素、TomcatSpringに分かれると理解すれば良いでしょう。

FilterはServlet仕様の一部であり、サーバー(Tomcat)によって呼び出されます。一方、InterceptorはSpringによって呼び出されます。^2

以下は、InterceptorとFilterの管理主体および実行時間を分かりやすく表現した図です。

Spring Web Application - 3. Filter and Interceptor

Filter (Tomcat)

doFilter 関数がリクエスト進入時と結果返却時の2回呼び出されるため、暗号化/復号化のような、リクエスト前と返却後の両方でグローバルに処理する必要があるロジックに適しています

Interceptor (Spring)

Controller進入時、または結果返却時に詳細な処理が必要なロジックに適しています。例えば、特定のURLに進入するリクエストに対しては、Controller進入直前にそのURLに特化した情報をセッションに事前に設定し、Controller内部ロジックで活用できるようにすることができます。他のURLであれば、このロジックを実行しないように条件を追加することも可能です。


重要な内容として、FilterとInterceptorは管理主体が異なるため、以下のような状況が発生する可能性があります。

FilterはSpring Containerの管理主体ではないため、Filterロジック内部でSpringのBeanを使用するには、@AutowiredのようなBean注入ではなく、まずSpring WebApplicationContextオブジェクトを取得し、その中に設定されたBeanをハードコーディングを通じて直接取得して使用する必要があります。

複数InterceptorとFilterの呼び出し順序

FilterとInterceptorは、状況に応じて複数指定して使用できます。多数のFilterあるいはInterceptorを使用する際、FilterとInterceptorそれぞれにおける呼び出し順序は設定できますが、Filter - Interceptor - Filter - Interceptor のように混ぜる形式では不可能です。

2つのFilter2つのInterceptorを使用する際にどのように動作するのか順序を調べるために、DispatcherServletとHandlerAdapterに焦点を当てて見てみると以下のようになります。

Spring Filters and Interceptors

簡潔に要約すると、下記の図/流れのようになります。

Spring Filters and Interceptors - Summary

  1. doFilter (F1)
  2. doFilter (F2)
  3. preHandler (I1)
  4. preHandler (I2)
  5. Controller リクエスト処理
  6. postHandler (I2)
  7. postHandler (I1)
  8. View レンダリング
  9. afterCompletion (I2)
  10. afterCompletion (I1)
  11. doFilter (F2)
  12. doFilter (F1)

WebサーバーからWebアプリケーションへ、WebサーバーとWebアプリケーションを接続するためのCGIの例としてServlet、そしてコンテナについて学んだ後、SpringコンテナとFilter、Interceptorの違い、そして実行順序について考察しました。Springを学習されている方や使用されている他の開発者の皆様にとって、この記事が役立つことを願っています。参照した記事も素晴らしい内容ですので、お時間があれば一度目を通すことをお勧めして締めくくります。


  1. Springの動作原理 #1, #2, #3
  2. TomcatがSpringを呼び出す方法
  3. Java Servlet
  4. Webサーバー、Webアプリケーションの違い
  5. Spring DispatcherServletの動作原理 #1, #2
  6. Spring web.xml解説 #1, #2
  7. Springの2種類のApplicationContext
  8. Servlet Container & Spring Container
  9. Spring MVC コードベースの動作原理
Spring MVCの構造、動作原理、および処理フロー
Author
Aaron
Posted on
Licensed Under
CC BY-NC-SA 4.0
CC BY-NC-SA 4.0
同じカテゴリーの関連記事
最新記事
LLMフィルターが奪う会話の筋肉とコミュニケーション様式
会話における無礼さを濾過し、洗練された回答を生成するLLMツールが日常化した現代において、私たちは本当に思慮深い会話をしているのだろうか?リアルタイムのコミュニケーションにおける数多くの失敗を通じて磨かれるべき会話能力が、外部ツールに依存することで退化している現象と、それがもたらす社会的な不安や世代間の行動様式の変化について考察する。
シニア採用における年俸交渉の最適なタイミングと戦略
年俸交渉は単なる数字の交換ではなく、心理的な駆け引きとタイミングが重要です。本稿では、企業側にとって、候補者が計算的な態度を取りがちな最終合格後よりも、採用プロセスの初期段階から段階的に交渉を進めることが、なぜより効率的であり、率直な情報の共有に繋がるのかを考察します。
法治主義の限界と人間の多様性
全ての人間の行為を単一の法体系で規制できるという信念は、傲慢であるかもしれない。この記事は、中世の階層的な統制から脱却し、現代の無限の自由を手に入れた人類が直面する法治主義の逆説と、多様性という名のもとに深化する社会的強制力と他者への悪魔化現象を鋭く分析する。
토스트 예시 메세지