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

最終更新日

Schoo Java入門 中級 第5回 サーバーサイドアプリケーションの実装基礎

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が呼ばれるまで

ブラウザからlesson05/stockへGETリクエスト。Tomcatがlesson05アプリを選び、stockに対応するStockServletを選ぶ。HttpServletのserviceを通してdoGetが動き、結果がブラウザへ返る。
アプリ、Servlet、HTTPメソッドの順に処理先が決まります。図のJavaファイルは、そのクラスの処理を表しています。

図の「対応表」は登録のイメージであり、自分で表のファイルを作るという意味ではありません。図では比較のためにHelpServletも描いていますが、後の動作確認コードではStockServletだけを作ります。

  1. ブラウザが /lesson05/stock へGETリクエストを送る。
  2. Tomcatが /lesson05 として配置されたアプリを選ぶ。
  3. そのアプリ内の /stock に登録されたStockServletを選ぶ。
  4. GETなので、HttpServletの service() を通して doGet() が呼ばれる。
  5. 処理結果をHTTPレスポンスとしてブラウザへ返す。

/lesson05と/stockは、どちらも@WebServletに書く?

今回の例では、アプリが /lesson05 に配置され、Tomcatが8080番ポートで動いているものとします。

http://localhost:8080/lesson05/stock
同じURLを3つに分けて見る
http://localhost:8080
自分のPCの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/stockStockServletのdoGet
フォームを送信/lesson05/stockStockServletの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 が404HTMLのactionの先頭に/を付け、/lesson05を抜かしていないか。
正しいURLでも404アプリが正しい場所に配置されているか、アノテーション変更をビルド・反映したか。
GETは動くがPOSTが405そのServletにdoPostを実装しているか。URLの問題とは分けて確認する。
サーバー起動時にエラー同じアプリで、別々のServletへ同じURLパターンを登録していないか。

404はリソースが見つからない場合など、405はそのHTTPメソッドに対応していない場合のステータスです。ただし、アプリ自身がエラーを返すこともあるため、番号だけで原因を断定せず、ブラウザが実際に送ったURL・メソッドとサーバーのログを確認します。

確認問題

アプリのコンテキストパスは /lesson05、アノテーションは @WebServlet("/stock")、クラス名はStockServletとします。特に断りがなければ本文のコードを使い、他のマッピングや転送はないものとします。

  1. ブラウザのアドレスバーからStockServletのdoGetを呼ぶURLを書いてください。ホストはlocalhost、ポートは8080です。
  2. stock-menu.htmlをアプリ直下からformsフォルダへ移しました。action=”stock”の送信先はどう変わりますか。StockServletへ送るための相対パスに直してください。
  3. クラス名をStockServletのまま、アノテーションだけ@WebServlet(“/inventory”)へ変更して反映しました。新しいアクセス先のURLと、アプリ直下のHTMLに書くhref・actionを答えてください。
  4. 本文のServletからdoPostだけを削除して反映しました。リンクのクリックとフォームの送信は、それぞれどうなりますか。mainを追加すると解決しますか。

演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。

まとめ

  • @WebServletは、アプリ内のURLとServletを結び付ける。クラス名がそのままURLになるわけではない。
  • コンテキストパスでアプリを、その後のパスでアプリ内の処理先を選ぶ。
  • Servletはサーバーに管理され、serviceを通してdoGet・doPostなどが呼ばれる。
  • HTMLのhref・actionはURL。相対パスか、サイトのルートからのパスかを区別する。

関連する記事

シェアする