タイトル: Servlet(サーブレット)
SEOタイトル: Java Servlet 完全ガイド (HttpServlet / ライフサイクル / Jakarta EE 移行)
| この記事の要点 |
|
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("Hello, Servlet!
");
}
}
@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 / doDelete | HTTP メソッドに応じて |
| 破棄 | 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 (古典的)
HelloServlet
com.example.HelloServlet
greeting
Hello
1
HelloServlet
/hello
@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" %>
Users
- ${user.name}
典型的な 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.servlet、Jakarta EE 9+ / Servlet 5.0+ は jakarta.servlet に変わりました。
| EE バージョン | Servlet | パッケージ | サーバ例 |
|---|---|---|---|
| Java EE 7 | 3.1 | javax.servlet | Tomcat 8 / WildFly 8 |
| Java EE 8 | 4.0 | javax.servlet | Tomcat 9 / WildFly 18 |
| ★ Jakarta EE 9 | 5.0 | jakarta.servlet | Tomcat 10 / WildFly 27 |
| Jakarta EE 10 | 6.0 | jakarta.servlet | Tomcat 10.1 |
| Jakarta EE 11 | 6.1 | jakarta.servlet | Tomcat 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 で調整。