Dev Study
← Spring コース

39. Servlet・WAR・アプリケーションサーバ

(サーブレット)は、HTTP リクエストに応答する Java のです。 をして doGet を書きますが、 はありません。 と対応表の を という1つのファイルにまとめ、 などのに置くと、が読み込みます。コンテナが呼ぶのは service で、GET ならそこから doGet が呼ばれます。

Node では自分のプログラムが listen を呼んでポートを開きますが、この形ではポートを開くのもを持つのもコンテナ側です。Jakarta Servlet 6.1 の仕様は、分散環境でない既定の場合、コンテナは1つの宣言につきを1つだけ使い、service メソッドの中を複数のスレッドが同時に走りうると定めています。Tomcat 11.0.26 に4本同時に送ると、同じインスタンスに別々のスレッドで届きました。

つまずきやすいのは、に置いた値が全リクエストで共有される点です。リクエストごとの値をフィールドに持たせると、別のスレッドに書き換えられます。なお Servlet API の始まりについては、1997年末に Sun が開発キットを無償で配っていたことまでは当時のページで確かめられますが、初版の年月はこのコースでは確定していません。

点線の付いた語はタップすると説明が出ます。用語集を開く →

アプリがサーバの中に入れられる形(Tomcat 11.0.26 での実測をもとに作図)
クライアントcurl /demo/hello を4本同時に
HTTP リクエスト
Tomcat(サーブレットコンテナ)ポートを開く・スレッドを持つ・main を持つ
先に起動しているのはこちら
コンテナが持つスレッドがそれぞれ受け持つ
http-nio-8080-exec-7
http-nio-8080-exec-8
http-nio-8080-exec-9
web.xml の対応表を見て doGet を呼ぶ
HelloServlet のインスタンス(1つ)demo.war の WEB-INF/classes から読み込まれた
どのスレッドからも同じインスタンス
  • ・実測では4本目は exec-1 が受け持ちました。図は横幅の都合で3本だけ描いています
  • ・URL の /demo は WAR のファイル名(demo.war)から決まりました
  • ・クラスを読み込んだのは Tomcat がアプリごとに用意するクラスローダ(ParallelWebappClassLoader)でした

サンプルコード(フレームワーク環境が必要なため表示のみ)

// HelloServlet.java — main が無い。呼び出すのはコンテナ(Tomcat)
package hello;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends HttpServlet {
    public HelloServlet() {
        System.out.println("[HelloServlet] constructor called by " + Thread.currentThread().getName());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/plain;charset=UTF-8");
        resp.getWriter().println("hello from servlet");
        resp.getWriter().println("thread   = " + Thread.currentThread().getName());
        resp.getWriter().println("instance = " + System.identityHashCode(this));
        resp.getWriter().println("loader   = " + getClass().getClassLoader().getClass().getName());
        resp.getWriter().println("server   = " + getServletContext().getServerInfo());
    }
}

// demo.war にして Tomcat 11.0.26(JDK 25)の webapps に置き、2回呼んだ結果
// (instance の数値は起動のたびに変わる。見るのは「2回とも同じ」こと。
//  18080 は Docker の外から見た番号で、Tomcat 自身は 8080 で待ち受けている)
//
// $ curl http://localhost:18080/demo/hello
// hello from servlet
// thread   = http-nio-8080-exec-2
// instance = 1141094684
// loader   = org.apache.catalina.loader.ParallelWebappClassLoader
// server   = Apache Tomcat/11.0.26
//
// $ curl http://localhost:18080/demo/hello   (2回目。2行を抜粋)
// thread   = http-nio-8080-exec-3
// instance = 1141094684

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

サンプルコードで身につける(5本) →

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

公式ドキュメントで詳しく ↗