5.

Java Servlet 完全ガイド (HttpServlet / ライフサイクル / Jakarta EE 移行)

編集
この記事の要点
  • Servlet = Java で動的 Web レスポンスを返すサーバサイドコンポーネント。HttpServlet を継承して doGet / doPost を実装
  • マッピングは web.xml または @WebServlet("/path") アノテーション
  • ライフサイクル: init() → service() → doGet/doPost() → destroy()
  • Servlet 4.0 までは javax.servletJakarta EE 9+ は jakarta.servlet に移行
  • 現代の Web アプリは Servlet を直接書かず Spring MVC (内部で DispatcherServlet) / Spring Boot / Vert.x が主流

Servlet とは

Servlet (サーブレット) はサーバ上で動作する Java のクラスで、HTTP リクエストを受け取り動的なレスポンスを返します。1997 年に Sun (現 Oracle) が発表した、Java サーバサイドの基礎仕様です。

Tomcat / Jetty / WildFly / GlassFish などのサーブレットコンテナ上で動作します。

最小サンプル

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

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.setContentType("text/html; charset=UTF-8");
        try (PrintWriter out = resp.getWriter()) {
            out.println("<h1>Hello, Servlet!</h1>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        String name = req.getParameter("name");
        resp.setContentType("text/plain; charset=UTF-8");
        resp.getWriter().println("Hello, " + name);
    }
}

Servlet のライフサイクル

フェーズメソッド呼ばれるタイミング
初期化init()★ サーブレット生成直後 (1 度だけ)
サービスservice()リクエストごと → doGet/doPost にディスパッチ
処理doGet / doPost / doPut / doDeleteHTTP メソッドに応じて
破棄destroy()★ サーブレットコンテナ停止時 / リロード時

重要: Servlet インスタンスはシングルトンで、複数スレッドが同時に service() を呼びます。インスタンスフィールドは thread-unsafe なので使わないか同期します。

@WebServlet("/lifecycle")
public class LifecycleServlet extends HttpServlet {
    private DataSource ds;
    // ⚠️ インスタンスフィールドはスレッド共有

    @Override
    public void init() {
        // 1 度だけ呼ばれる → 初期化に最適
        ds = lookupDataSource();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        // ★ ローカル変数のみ使う → スレッドセーフ
        try (Connection conn = ds.getConnection()) { ... }
    }

    @Override
    public void destroy() {
        // クリーンアップ (コネクションプール close 等)
    }
}

HttpServletRequest と HttpServletResponse

@WebServlet("/users")
public class UserServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException, ServletException {

        // クエリパラメータ
        String name = req.getParameter("name");
        String[] tags = req.getParameterValues("tag");

        // ヘッダ
        String userAgent = req.getHeader("User-Agent");

        // パス情報
        String uri = req.getRequestURI();         // /app/users
        String path = req.getServletPath();        // /users
        String method = req.getMethod();           // GET

        // セッション
        HttpSession session = req.getSession();
        session.setAttribute("user", name);
        Object u = session.getAttribute("user");

        // Cookie
        Cookie[] cookies = req.getCookies();

        // Body (POST)
        BufferedReader reader = req.getReader();

        // === レスポンス ===
        resp.setStatus(200);  // HttpServletResponse.SC_OK
        resp.setContentType("application/json; charset=UTF-8");
        resp.setCharacterEncoding("UTF-8");

        // ヘッダ
        resp.setHeader("Cache-Control", "no-cache");
        resp.addCookie(new Cookie("session_id", "abc"));

        // ボディ
        resp.getWriter().println("{\"name\": \"" + name + "\"}");

        // リダイレクト
        resp.sendRedirect("/login");

        // エラー
        resp.sendError(404, "Not Found");

        // フォワード (内部転送)
        req.getRequestDispatcher("/WEB-INF/views/index.jsp").forward(req, resp);
    }
}

マッピング: web.xml vs アノテーション

web.xml (古典的)

<!-- WEB-INF/web.xml -->
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="5.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.example.HelloServlet</servlet-class>
        <init-param>
            <param-name>greeting</param-name>
            <param-value>Hello</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

@WebServlet アノテーション (Servlet 3.0+)

@WebServlet(
    name = "HelloServlet",
    urlPatterns = {"/hello", "/greet"},
    initParams = {
        @WebInitParam(name = "greeting", value = "Hello")
    },
    loadOnStartup = 1
)
public class HelloServlet extends HttpServlet { ... }

セッション管理

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        String user = req.getParameter("username");
        String pass = req.getParameter("password");

        if (authenticate(user, pass)) {
            HttpSession session = req.getSession(true);  // 無ければ作成
            session.setAttribute("user", user);
            session.setMaxInactiveInterval(30 * 60);     // 30 分
            resp.sendRedirect("/dashboard");
        } else {
            resp.sendRedirect("/login?error=1");
        }
    }
}

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        HttpSession session = req.getSession(false);
        if (session != null) {
            session.invalidate();
        }
        resp.sendRedirect("/");
    }
}

Filter とListener

// Filter: リクエスト前後の共通処理
@WebFilter("/*")
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        long start = System.currentTimeMillis();
        try {
            chain.doFilter(req, res);    // 次のフィルタ or サーブレットへ
        } finally {
            System.out.println("elapsed: " + (System.currentTimeMillis() - start));
        }
    }
}

// Listener: 各種イベント (起動 / セッション生成 / 属性変更)
@WebListener
public class AppListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // アプリ起動時 (DB プール構築など)
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // アプリ停止時
    }
}

JSP との関係

JSP (JavaServer Pages) はHTML 中に Java コードを埋め込めるテンプレート技術で、内部的にはServlet にコンパイルされる関係です。

<%-- WEB-INF/views/users.jsp --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="jakarta.tags.core" prefix="c" %>
<html>
<body>
    <h1>Users</h1>
    <ul>
        <c:forEach var="user" items="${users}">
            <li>${user.name}</li>
        </c:forEach>
    </ul>
</body>
</html>

典型的な MVC では Servlet がコントローラ役、JSP が View 役を担います。

javax.servlet → jakarta.servlet 移行 (Jakarta EE 9+)

Oracle から Eclipse Foundation への Java EE 移管 (Jakarta EE) に伴い、Java EE 8 / Servlet 4.0 までは javax.servletJakarta EE 9+ / Servlet 5.0+ は jakarta.servlet に変わりました。

EE バージョンServletパッケージサーバ例
Java EE 73.1javax.servletTomcat 8 / WildFly 8
Java EE 84.0javax.servletTomcat 9 / WildFly 18
★ Jakarta EE 95.0jakarta.servletTomcat 10 / WildFly 27
Jakarta EE 106.0jakarta.servletTomcat 10.1
Jakarta EE 116.1jakarta.servletTomcat 11

現代の選択肢: Spring MVC / Spring Boot

Spring Framework は内部に DispatcherServlet (フロントコントローラ) を持ち、Servlet 仕様の上に MVC を構築しています。現代の Java Web 開発は素の Servlet を書くことは稀で、Spring Boot / Quarkus / Micronaut / Vert.x が主流です。

// Spring Boot Controller (Servlet を直接書かない)
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    @PostMapping
    public User create(@RequestBody UserCreateRequest req) {
        return userService.create(req);
    }
}
// → Spring が内部で DispatcherServlet → HandlerMapping → このメソッドへルーティング

FAQ

Q: いまから Servlet を学ぶ価値はある?
A: 直接書く機会は減ったが、Spring / Tomcat の動作理解の基礎なので仕様の理解は有益。新規プロジェクトでは Spring Boot を直接学んだ方が実用的。

Q: javax.servlet ライブラリが見つからない
A: Jakarta EE 移行で jakarta.servlet-api に変わりました。spring-boot-starter-web 3.x も Jakarta 系。

Q: Servlet のスレッド数は?
A: コンテナ (Tomcat) のスレッドプール (デフォルト 200) で同時処理されます。server.tomcat.threads.max で調整。

編集
Post Share
子ページ
  1. web.xml
同階層のページ
  1. プラットホーム
  2. 環境構築
  3. 文法
  4. API
  5. Servlet(サーブレット)
  6. JSP
  7. Applet(アプレット)
  8. デザインパターン
  9. フレームワーク
  10. ライブラリ
  11. Androidアプリケーション
  12. Project Jigsaw
  13. エラー一覧
  14. 日付の加算、減算
  15. 文字列の数字チェック
  16. 改行コードの削除
  17. 先頭と末端の文字の削除
  18. warファイルの中身を確認する方法
  19. nullもしくは空文字の判定
  20. beanの中身を確認する方法
  21. org.apache.log4j.Logger のログ出力で printStackTrace() のエラー内容を出力する方法
  22. Javaのバージョン確認方法

最近更新/作成されたページ