Dev Study
← 解説「DispatcherServlet と組み込み Tomcat」に戻る

サンプルコードで身につける: DispatcherServlet と組み込み Tomcat

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1起動ログに出る Tomcat の4行

から起動したときのログのうち、 に関する行です(行頭の時刻などは落としています)。別に用意したサーバへ置いたのではなく、自分のプロセスの中で Tomcat 11.0.24 が立ち上がり、ポート 8080 を開いたことが読み取れます。

起動ログの抜粋(Spring Boot 4.1.1)コマンド出力
o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
o.apache.catalina.core.StandardService : Starting service [Tomcat]
o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/11.0.24]
o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'

2Servlet は1つ、フィルタは3つ

--logging.level.org.springframework.boot=DEBUG を付けて起動し、最初のリクエストを送ったときのログです。登録された は の1つだけで、対応づけは / でした。初期化の行は main ではなく nio-8080-exec-1 ので出ており、最初のリクエストが来てから行われたことが分かります。

DEBUG ログの抜粋(行頭の時刻などは落としてある)追跡ログ
DEBUG [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: characterEncodingFilter urls=[/*] order=-2147483648, formContentFilter urls=[/*] order=-9900, requestContextFilter urls=[/*] order=-105
DEBUG [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
INFO [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
DEBUG [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/books/42", parameters={}

3ハンドラの中で取ったスタックトレース

の中でを作り、そのを文字列にして返した結果の抜粋です。全体は45行(フレームは44)あり、... の行は省略を表します。下から順に、スレッド、Tomcat の HTTP 解釈、、Servlet API、DispatcherServlet、、自分のと積み上がっています。

GET /stack の応答(Framework 7.0.9 / Tomcat 11.0.24 / JDK 25。行番号はこの版のもの)追跡ログ
java.lang.Exception: stack at handler
at l19.web.BookController.stack(WebApp.java:35)
...
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:252)
...
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:853)
...
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
...
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:892)
...
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:710)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:132)
...
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:199)
...
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:165)
...
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:347)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:406)
...
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:2036)
...
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:483)
...
at java.base/java.lang.Thread.run(Thread.java:1474)

4登録されていた変換係の一覧

が持つ を列挙した結果です。6つあり、application/json を担当するのは最後の JacksonJsonHttpMessageConverter でした。 本体は 3.1.5 で名は tools.jackson ですが、の だけは com.fasterxml.jackson.core の 2.21 が入っていました。

GET /converters の応答(スレッド番号は実行ごとに変わる)コマンド出力
org.springframework.http.converter.ByteArrayHttpMessageConverter [application/octet-stream, */*]
org.springframework.http.converter.StringHttpMessageConverter [text/plain, */*]
org.springframework.http.converter.ResourceHttpMessageConverter [*/*]
org.springframework.http.converter.ResourceRegionHttpMessageConverter [*/*]
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter [application/x-www-form-urlencoded, multipart/form-data, multipart/mixed, multipart/related]
org.springframework.http.converter.json.JacksonJsonHttpMessageConverter [application/json, application/*+json]
jackson-databind = 3.1.5
thread = Thread[#45,http-nio-8080-exec-5,5,main]

5対比: 同じ応答を素の Servlet で書くと

DispatcherServlet が肩代わりしている仕事を見るために、同じ応答を だけで書いた形です。URL から ID を取り出す、Content-Type を決める、JSON の文字列を組み立てる、をすべて自分で書いており、 ではこの3つが DispatcherServlet の内側の部品に移っています。/books/* に対応づけて Tomcat 11.0.26 に置くと同じ本文が返り、Content-Type は application/json;charset=UTF-8 でした。

// 素の Servlet で GET /books/42 に答える形(対比用)
// web.xml などで /books/* に対応づけて登録した場合に動く。完全一致で登録すると getPathInfo() は null になる
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

public class BookServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 1. URL から ID を取り出す(Spring MVC では @PathVariable)
        String path = req.getPathInfo();                 // 例: "/42"
        long id = Long.parseLong(path.substring(1));

        // 2. Content-Type を決める(Spring MVC では変換係が決める)
        resp.setContentType("application/json");

        // 3. JSON を組み立てる(Spring MVC では JacksonJsonHttpMessageConverter)
        resp.getWriter().print(
            "{\"id\":" + id + ",\"title\":\"Spring in Depth\",\"tags\":[\"java\",\"spring\"]}");
    }
}

// Spring MVC ではこれが次の形になる
// @GetMapping("/books/{id}")
// Book book(@PathVariable long id) {
//     return new Book(id, "Spring in Depth", List.of("java", "spring"));
// }

図やコードに出てくる用語(タップで説明)