spring bootの使い方を勉強しててざっくりの使い方は分かったが、印象としては黒魔術的というか「なぜこれでwebリクエストを受けられてレスポンスを返せるのか」が全くわからなかった。
全くわからなかった原因として、Spring Bootは構成としてかなり上部レイヤーに位置していて、その下にはSpring FrameworkやJava Servletなどが存在しており、これらの知識が不足している事があった。
裏で何が行われているかをなんとなくでも知っておきたかったので調べた。
分かったことをメモとして記載する。
(想像で補完している部分もあるので間違いがあったら教えてほしいです。)
Spring Web MVC
Spring BootでWebアプリケーションを書く際、以下のようなコードをControllerに書く。
@Controller public class HelloController { @GetMapping("/hello") public String handle(Model model) { model.addAttribute("message", "Hello World!"); return "index"; } }
こう書けば、getで/helloでリクエストがあればこのhandle()メソッドが実行され、index.htmlのviewテンプレートでレスポンスが返される。
この機能はSpring Web MVCによって実現されている。
※アノテーションやメソッド引数の書き方で、他にも色んな書き方で機能が実現できる。そのあたりが黒魔術っぽいと感じてしまった。
Spring Web MVC サーブレットスタック - リファレンス
Spring Web MVCとは、Spring Frameworkで提供されているモジュールの一つで、いわゆるModel View Controllerアーキテクチャの骨格を提供してくれるものである。
自分は「Spring Bootを使っていると思っていたらいつの間にかSpring Frameworkを使っていた??」
となったりして混乱したのだが、これらの関係について次の項目に記載する。
Spring BootとSpring Frameworkの関係
そもそもSpring Frameworkというものが存在していて、これはjavaでアプリケーション(webに限らない)を作るのに便利な機能一式を提供してくれているようなフレームワークである。
ただ、使うには色々煩雑に思える設定が必要らしく、これをもう少し気軽に使えるように再構成したのがSpring Bootである。(と、自分はざっくり認識した。)
だから、Spring Bootにて使うことになる機能は基本的にSpring Frameworkのものである。
今回webアプリケーションを作るのに使っていたのはSpring Frameworkの提供するSpring Web MVCだった、ということだった。
以上のことが分かったところで、このSpring Web MVCのコントローラーに処理が回ってくるのはどういう仕組みなのか理解しようとしたのだが、記述する指定ディレクトリがあるわけでもなく、指定のClassを継承するわけでもなく、ただアノテーションをつければ処理が回ってくるという仕組みがどのように実現されているのか全くわからなかった。
このあたりを理解するためには下位のレイヤーから理解せねばならぬと思い、Javaでwebアプリケーションを作成する際に使用するJava ServletとServlet Containerについて調べる所から始めた。
Java ServletとServlet Container(Tomcat)
JavaでWebリクエストに対して処理を行うにはJava ServletとServlet Containerが必要になる。
Java ServletがJavaで書かれたロジックコードに当たるもので、リクエストを受け付けるエントリーポイントを指定の記述形式で用意しておけば後述するServlet Containerがリクエスト情報とともにエントリーポイントを実行してくれる。
単純な処理で良いのであれば別にSpring MVCが無くとも処理を実装できる。
Servlet Containerとは、上記で用意したJava Servletのコードを内部に抱えて動作するwebサーバー的なものである。
Servlet Container内に特定のルールでJava Servletのコード(をコンパイルしてバイトコードになったもの)を配置しておき、
受けたhttpリクエストをJava Servletに記載された内容で処理し返却するような動作をする。
このServlet Containerの実装の一つがTomcatである。
※Spring FrameworkとServlet Containerは別物なので、webアプリを動かす際はコードと別にServlet Containerが必要なのだが、Spring BootはServlet Containerを内包しており、この機能をスタンドアロンで提供できる。
ここまで理解してSpring MVCのコードを見るとJava ServletのエントリーポイントでリクエストをDispatcherServletクラスに移譲している部分があり、このDispatcherServletクラスがリクエスト情報に基づきControllerメソッドを呼び分けている事が分かった。
じゃあ、DispatcherServletクラスはどうやって僕が書いたController(アノテーションがついているだけで、どこに定義されているかも予想できないはず)とそのメソッドを見つけるのか?
ここを理解するにはSpring Frameworkが提供するDIコンテナとComponent Scanという仕組みの存在を知る必要があった。
Spring FrameworkのDIコンテナとComponent Scan
Spring Bootを使っているとまず特徴的に感じたのは、Classのインスタンス化の方法だった。
通常であれば、定義したClassのインスタンスを得たい場合、
var object = new ClassName();
とするところ、Spring Bootではコンストラクタの引数に書いておけば使える事になっている。
public class SomeClass{ private final ClassName object; public SomeClass(ClassName object) { this.object = object; // 外で誰かがインスタンス化してくれてセットしてくれる。 } private void SomeMethod() { .... object.call(); // インスタンスが使える! .... } }
これはSpring FrameworkのDIコンテナの機能によって提供されている。(DI対象になるClassのことをBeanと呼ぶらしい)
Classを定義して特定のアノテーションを付加しておくと、Spring Frameworkの起動時にComponent Scanという機能によってDIコンテナに登録される。 いざそのClassがインスタンス化されるとき(コンストラクタが呼ばれる時)はDIコンテナ機能が引数として指定されている型のクラスをインスタンス化して引数にセットしてコンストラクタを実行するので、自分でnewする必要がない。
こんなことをしてなんのメリットがあるのかについては、DIの必要性について調べると良い。
つまりこの機能によってSpring Frameworkは僕が書いたControllerクラスをDIコンテナ内に保持している。
だからDispatcherServletはサーブレットから受け取ったリクエスト情報を使ってDIコンテナ内のClassを検索(実際にはそこから必要なClassだけの集合を抜き出して持ってると思う)し、メソッドを特定して実行しているっぽいことが分かった。