Javaの@WebServletとは?URLとServletを結び付ける仕組み

StockServlet.java を作ったのに、ブラウザでは /stock を開く。クラスには main() もない。最初は「どこでこのJavaの処理が呼ばれるの?」と感じるかもしれません。
@WebServlet は、Webアプリ内のURLとServletを結び付けるための指定です。ブラウザはJavaのファイルを直接実行しません。リクエストを受けたサーバーが、登録された対応関係からServletを選んで呼び出します。
この記事では、在庫確認ページのURLを例に、「どのアプリの、どのServletが、どのメソッドで処理するか」を整理します。Schoo「Java入門 中級」第5回「サーバーサイドアプリケーションの実装基礎」に対応しています。未受講の方も読み進められる内容です。
@WebServletがあると、URLとクラス名を分けて決められる
@WebServlet("/stock")
public class StockServlet extends HttpServlet {
// リクエストを処理するメソッドを書く
}
この指定は「このWebアプリに届いた /stock 宛てのリクエストを、StockServletで処理する」という意味です。@ で始まるアノテーションは、プログラムを扱う仕組みに情報を伝える記述です。この場合は、Tomcatなどのサーバーがアプリを読み込むときに処理します。
クラス名を InventoryServlet に変えても、対応するJavaファイル名などを整え、@WebServlet("/stock") を維持すれば、公開するURLはそのままにできます。逆に、クラス名がStockServletでも @WebServlet("/inventory") と書けば、アクセスするURLは /inventory です。
Javaの実装上の名前と、利用者がアクセスするURLは別のものです。URLをクラス名に自動で合わせてくれるわけではありません。
イラストで見る:URLからServletが呼ばれるまで

図の「対応表」は登録のイメージであり、自分で表のファイルを作るという意味ではありません。図では比較のためにHelpServletも描いていますが、後の動作確認コードではStockServletだけを作ります。
- ブラウザが
/lesson05/stockへGETリクエストを送る。 - Tomcatが
/lesson05として配置されたアプリを選ぶ。 - そのアプリ内の
/stockに登録されたStockServletを選ぶ。 - GETなので、HttpServletの
service()を通してdoGet()が呼ばれる。 - 処理結果をHTTPレスポンスとしてブラウザへ返す。
/lesson05と/stockは、どちらも@WebServletに書く?
今回の例では、アプリが /lesson05 に配置され、Tomcatが8080番ポートで動いているものとします。
http://localhost:8080/lesson05/stock
http://localhost:8080/lesson05コンテキストパス
/stock@WebServletに指定
/lesson05 は、そのWebアプリの入口を表すコンテキストパスです。プロジェクト名と同じになる構成もありますが、常に同じとは限らず、アプリの配置設定で決まります。
@WebServlet に指定するのは、コンテキストパスを除いたアプリ内のURLパターンです。この例では "/stock" と書き、"/lesson05/stock" やURL全体は書きません。アプリがサイトのルートに配置されている場合は、URLに /lesson05 のような部分が付かないこともあります。
ここで扱う "/stock" は、そのパスに一致させる指定です。/stock と /stock/ は別のパスです。大文字小文字も区別します。複数のURLやパスの範囲を指定する方法もありますが、まずは1つのURLとの対応で確認します。
mainがないのに動くのは、サーバーが呼び出すから
通常のJavaアプリでは、自分で用意した main() を入口として処理を始めます。一方、Servletは起動中のサーバーに管理されます。Tomcatの中でServletを管理する仕組みをServletコンテナと呼びます。
コンテナはServletのインスタンスを用意して初期化し、リクエストが来ると service() を呼びます。HttpServletが持つserviceの処理がHTTPメソッドを見て、GETならdoGet、POSTならdoPostへ振り分けます。この例で自分のmainやserviceを書く必要はありません。
図ではクラスの処理をJavaファイルの絵で示していますが、サーバーがリクエストのたびにソースファイルを開いて実行するわけではありません。また、通常は同じServletのインスタンスが複数のリクエストで使われます。入力値などリクエストごとの値を、Servletのフィールドに保存しないことも覚えておきましょう。
実際にURLと処理を対応させる
Java 17・Tomcat 10.1の jakarta.servlet を使う例です。src/main/java/example/StockServlet.java をUTF-8で作成します。環境構築手順ではなく、配置済みのWebアプリで呼び出しを確認するコードです。
package example;
import java.io.IOException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/stock")
public class StockServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().println("在庫を確認しました。");
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().println("POSTでStockServletに届きました。");
}
}
この例はどのメソッドに届いたかを確認するためのサンプルです。実際の在庫データの検索・更新は行いません。GETでは「在庫を確認しました。」、POSTでは「POSTでStockServletに届きました。」という固定メッセージを返します。
コードをビルドし、変更をサーバーへ反映したら、ブラウザで http://localhost:8080/lesson05/stock を開きます。アドレスバーからのアクセスはGETなので、doGetのメッセージが表示されます。/StockServlet.java を開くのではありません。
@WebServlet はクラスに付ける指定です。doGet() や doPost() の上に付けるものではありません。また、この例では web.xml による別のURL設定は追加せず、アノテーションを読み取る通常の設定で実行します。
リンクとフォームのactionは、ServletのURLに合わせる
src/main/webapp/stock-menu.html を作成します。アプリ直下にあるこのHTMLを、/lesson05/stock-menu.html で開きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Servletの呼び出し確認</title>
</head>
<body>
<h1>Servletの呼び出し確認</h1>
<p><a href="stock">在庫を確認する</a></p>
<form action="stock" method="post">
<button type="submit">POSTの到着を確認する</button>
</form>
</body>
</html>
リンクの href="stock" とフォームの action="stock" は、どちらもこの配置では /lesson05/stock を指します。URLが同じなのでStockServletに届きますが、リンクはGET、フォームはPOSTのため、呼ばれるメソッドが変わります。
| 操作 | URL | 動くメソッド |
|---|---|---|
| リンクをクリック | /lesson05/stock | StockServletのdoGet |
| フォームを送信 | /lesson05/stock | StockServletのdoPost |
action="StockServlet" とクラス名を書くと、ブラウザはそれをURLとして解釈します。Javaのクラスを探して呼んでくれるわけではありません。
先頭の/があるかどうかで、送信先が変わる
次の表は、HTMLを http://localhost:8080/lesson05/stock-menu.html で開き、<base> 要素による変更がない場合です。
| 指定 | ブラウザが送る先 |
|---|---|
action="stock" | /lesson05/stock |
action="/stock" | /stock(/lesson05が付かない) |
action="/lesson05/stock" | /lesson05/stock |
HTMLで先頭に/を付けると、同じサイトのルートからのパスになります。一方、@WebServlet("/stock") の/は、アプリ内のURLパターンの指定です。同じ /stock でも、HTMLとアノテーションでは基準が違います。
HTMLを /lesson05/forms/stock-menu.html へ移すと、action="stock" は /lesson05/forms/stock になります。その配置では action="../stock" とするか、action="/lesson05/stock" と指定します。後者はアプリのコンテキストパスを変更したら、HTML側も見直す必要があります。
動かないときは、URLとHTTPメソッドを分けて調べる
このサンプルだけを配置し、別のURLマッピングや転送設定がない場合は、次のように切り分けられます。
| 状態 | 先に確かめること |
|---|---|
/lesson05/StockServlet が404 | クラス名ではなく、登録した/stockにアクセスしているか。 |
/stock が404 | HTMLのactionの先頭に/を付け、/lesson05を抜かしていないか。 |
| 正しいURLでも404 | アプリが正しい場所に配置されているか、アノテーション変更をビルド・反映したか。 |
| GETは動くがPOSTが405 | そのServletにdoPostを実装しているか。URLの問題とは分けて確認する。 |
| サーバー起動時にエラー | 同じアプリで、別々のServletへ同じURLパターンを登録していないか。 |
404はリソースが見つからない場合など、405はそのHTTPメソッドに対応していない場合のステータスです。ただし、アプリ自身がエラーを返すこともあるため、番号だけで原因を断定せず、ブラウザが実際に送ったURL・メソッドとサーバーのログを確認します。
確認問題
アプリのコンテキストパスは /lesson05、アノテーションは @WebServlet("/stock")、クラス名はStockServletとします。特に断りがなければ本文のコードを使い、他のマッピングや転送はないものとします。
- ブラウザのアドレスバーからStockServletのdoGetを呼ぶURLを書いてください。ホストはlocalhost、ポートは8080です。
- stock-menu.htmlをアプリ直下からformsフォルダへ移しました。action=”stock”の送信先はどう変わりますか。StockServletへ送るための相対パスに直してください。
- クラス名をStockServletのまま、アノテーションだけ@WebServlet(“/inventory”)へ変更して反映しました。新しいアクセス先のURLと、アプリ直下のHTMLに書くhref・actionを答えてください。
- 本文のServletからdoPostだけを削除して反映しました。リンクのクリックとフォームの送信は、それぞれどうなりますか。mainを追加すると解決しますか。
演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。
まとめ
- @WebServletは、アプリ内のURLとServletを結び付ける。クラス名がそのままURLになるわけではない。
- コンテキストパスでアプリを、その後のパスでアプリ内の処理先を選ぶ。
- Servletはサーバーに管理され、serviceを通してdoGet・doPostなどが呼ばれる。
- HTMLのhref・actionはURL。相対パスか、サイトのルートからのパスかを区別する。