ゼロから学ぶServlet:初心者向け徹底解説

ゼロから学ぶServlet:初心者向け徹底解説

はじめに

Webアプリケーション開発の世界へようこそ!現代のインターネットを支える多くのサービスは、Webアプリケーションとして動作しています。皆さんが普段利用しているオンラインショッピングサイト、SNS、各種Webサービスなどは、すべてWebアプリケーションの賜物です。

Webアプリケーションは、大まかに分けてクライアントサイド(Webブラウザなど)とサーバーサイドで構成されます。クライアントサイドはHTML, CSS, JavaScriptなどでユーザーインターフェースを提供し、サーバーサイドはビジネスロジックを実行したり、データベースと連携したりします。

Javaを使ったサーバーサイド開発において、最も基本となる技術の一つが「Servlet(サーブレット)」です。Servletは、Java言語でWebアプリケーションを開発するためのコアテクノロジーであり、様々なJavaフレームワーク(Spring MVC, Strutsなど)も、内部的にはServletの上で動作しています。Servletを理解することは、JavaによるWebアプリケーション開発の基礎を固める上で非常に重要です。

この記事は、「Servletをゼロから学びたい」という完全な初心者の方を対象としています。Javaの基本的な文法を理解している方であれば、問題なく読み進められるように、Servletの概念から始まり、開発環境の準備、基本的なプログラミング手法、そして応用的な機能まで、体系的に解説していきます。

この記事を読むことで、以下のことが習得できます。

  • Webアプリケーションの基本的な仕組み
  • Servletの役割とライフサイクル
  • Servlet開発環境の構築方法
  • 基本的なHTTPリクエスト/レスポンス処理
  • セッションとクッキーを使った状態管理
  • Servletフィルタやリスナーといった応用機能
  • ServletとJSPを使ったMVCモデルの考え方

さあ、Servletの世界へ飛び込み、JavaによるWebアプリケーション開発の第一歩を踏み出しましょう!

1. Webアプリケーションの仕組みとServletの立ち位置

Servletを学ぶ前に、Webアプリケーションがどのように動作しているのか、基本的な仕組みを理解しておきましょう。

1.1 クライアント-サーバーモデルとHTTP

Webアプリケーションは、「クライアント-サーバーモデル」というアーキテクチャに基づいて動作します。

  • クライアント: Webブラウザなど、ユーザーが操作するソフトウェア。サーバーに対して「リクエスト」を送信します。
  • サーバー: クライアントからのリクエストを受け付け、処理を行い、「レスポンス」をクライアントに返送するコンピューターまたはソフトウェア。

このクライアントとサーバー間の通信には、主に「HTTP (Hypertext Transfer Protocol)」というプロトコルが使用されます。

  1. ユーザーがブラウザのアドレスバーにURLを入力したり、リンクをクリックしたりすると、ブラウザ(クライアント)はWebサーバーに対してHTTPリクエストを送信します。
  2. Webサーバーはリクエストを受け取り、要求されたリソース(HTMLファイル、画像、プログラムなど)を特定します。
  3. サーバーは要求に応じた処理(HTMLファイルの読み込み、プログラムの実行など)を行い、結果をHTTPレスポンスとしてブラウザに返送します。
  4. ブラウザはレスポンスを受け取り、表示します(HTMLであればレンダリングして画面に表示)。

HTTPリクエストには、要求の種類を示す「HTTPメソッド」(GET, POSTなど)や、クライアントに関する情報(ブラウザの種類、クッキーなど)を含むヘッダー情報、POSTリクエストの場合は送信データなどが含まれます。HTTPレスポンスには、処理結果を示す「ステータスコード」(200 OK, 404 Not Foundなど)や、コンテンツの種類を示すヘッダー情報、そして本体となるデータ(HTMLコンテンツ、JSONデータなど)が含まれます。

1.2 ステートレスなHTTP

HTTPの重要な特徴の一つに「ステートレス(Stateless)」であるという点があります。これは、サーバーが個々のリクエストの間でクライアントの状態(ログインしているか、ショッピングカートに何が入っているかなど)を記憶しないということです。リクエストごとに独立した処理が行われます。

しかし、実際のWebアプリケーションでは、ログイン状態を維持したり、ショッピングカートの内容を記憶したりする必要があります。この「状態管理」を行うために、Servletでは後述する「セッション管理」や「クッキー」といった仕組みを利用します。

1.3 JavaEE (Jakarta EE) とServletコンテナ

JavaでサーバーサイドのWebアプリケーションを開発するための標準仕様群を「JavaEE(Java Platform, Enterprise Edition)」と呼んでいました。現在はJavaEEはEclipse Foundationに移管され、「Jakarta EE」という名称に変わっています。ServletはこのJakarta EEに含まれる主要な仕様の一つです。

Jakarta EEは様々な技術仕様(Servlet, JSP, EJB, JPA, CDIなど)を含んでおり、これらの仕様を実装したソフトウェアを「アプリケーションサーバー」と呼びます。アプリケーションサーバーの中でも、特にServletやJSPといったWeb関連の仕様のみを実装したものを「Servletコンテナ」(またはWebコンテナ)と呼びます。

Servletコンテナの代表的な例としては、Apache Tomcat, Jetty, GlassFish (Jakarta EE Full Platform), WildFly (Jakarta EE Full Platform) などがあります。Servletは、このServletコンテナ上で動作します。

1.4 Servletの役割

ここでようやくServletの登場です。Servletは、Java言語で記述されたサーバーサイドのプログラムコンポーネントです。クライアントからのHTTPリクエストを受け取り、Javaのコードで処理を行い、HTTPレスポンスを生成してクライアントに返す役割を担います。

かつて、Webサーバーが動的なコンテンツを生成する仕組みとして「CGI (Common Gateway Interface)」が広く使われていました。CGIでは、リクエストごとに新しいプロセスが起動され、プログラムを実行して結果をWebサーバーに渡していました。しかし、リクエストごとにプロセスを起動するのはオーバーヘッドが大きく、効率が悪いという問題がありました。

Servletは、このCGIの欠点を克服するために生まれました。ServletはServletコンテナの管理下で動作し、通常、初回のリクエスト時にインスタンスが生成されてメモリに常駐します。その後のリクエストは、同じServletインスタンスのスレッドとして処理されます。これにより、プロセス起動のオーバーヘッドがなくなり、効率的で高速な処理が可能になりました。

簡単に言うと、Servletは「Javaで書かれた、HTTPリクエストを処理するためのサーバーサイドプログラム」です。

1.5 Servletのライフサイクル

Servletは、Servletコンテナによってその一生(ライフサイクル)が管理されます。Servletのライフサイクルには、主に以下の3つの重要なメソッドがあります。

  1. init() メソッド:

    • Servletインスタンスが生成され、初期化されるときに一度だけ呼び出されます。
    • データベース接続の確立や、アプリケーション全体で共有するリソースの準備など、初期設定処理を記述します。
    • ServletException をスローすることで、初期化に失敗したことをコンテナに伝えることができます。
  2. service() メソッド:

    • クライアントからリクエストが来るたびに呼び出されます。
    • このメソッド内で、実際のリクエスト処理(パラメータの取得、ビジネスロジックの実行、レスポンスの生成など)を行います。
    • 通常、HTTPプロトコルを扱うServlet (HttpServlet) では、このメソッドはHTTPメソッド(GET, POSTなど)に応じて適切な doGet(), doPost() などのメソッドを呼び出すように実装されています。開発者は通常、これらの do*() メソッドをオーバーライドして処理を記述します。
  3. destroy() メソッド:

    • Servletインスタンスが破棄される直前に一度だけ呼び出されます。
    • データベース接続の解放や、使用していたリソースのクリーンアップなど、終了処理を記述します。
    • Servletコンテナの停止や、アプリケーションの再配置などのタイミングで呼び出されます。

Servletコンテナは、これらのメソッドを適切なタイミングで呼び出すことで、Servletのインスタンスを管理します。

2. 開発環境の準備

Servlet開発を始めるためには、いくつかのツールが必要です。ここでは、一般的な開発環境の準備手順を説明します。

2.1 必要なもの

  1. JDK (Java Development Kit): Javaプログラムを開発・実行するために必要です。バージョン8以上を推奨します(この記事ではJava 11以降を想定します)。
  2. IDE (統合開発環境): プログラムの記述、コンパイル、デバッグなどを効率的に行うためのソフトウェアです。Eclipse, IntelliJ IDEA, VS Codeなどが代表的です。ここではEclipseまたはIntelliJ IDEAを想定します。
  3. Servletコンテナ: Servletを動作させるためのソフトウェアです。Apache Tomcatが最も一般的で、学習にも適しています。
  4. ビルドツール (任意): MavenやGradleを使うと、プロジェクトの管理、依存関係の解決、ビルドなどを効率的に行えます。最初は必須ではありませんが、慣れてきたら利用を推奨します。この記事ではMavenを使ったプロジェクト構成を前提とします。

2.2 環境構築手順

ステップ1:JDKのインストール

Oracle JDKやOpenJDKなど、任意のJDKをダウンロードし、PCにインストールしてください。インストール後、コマンドプロンプトやターミナルで java -versionjavac -version を実行し、バージョン情報が表示されることを確認してください。環境変数 JAVA_HOMEPATH の設定が必要な場合があります。

ステップ2:IDEのインストール

  • Eclipse: Eclipse IDE for Enterprise Java and Web Developers を推奨します。Eclipse公式サイトからダウンロードしてインストールしてください。
  • IntelliJ IDEA: Community EditionでもServlet開発は可能です。IntelliJ IDEA公式サイトからダウンロードしてインストールしてください。

ステップ3:Apache Tomcatのダウンロードとインストール

Apache Tomcat公式サイトから、最新安定版のZipまたはtar.gzファイルをダウンロードしてください。ダウンロードしたファイルを、任意のディレクトリ(例: C:\tomcat, /usr/local/tomcat)に展開(解凍)してください。これがTomcatのインストールディレクトリとなります。

ステップ4:IDEへのTomcat設定

使用するIDEに、ダウンロードしたTomcatを認識させる設定を行います。

  • Eclipse:

    1. 「Window」メニュー -> 「Show View」 -> 「Servers」を選択し、サーバービューを開きます。
    2. サーバービュー上で右クリック -> 「New」 -> 「Server」を選択します。
    3. 「Apache」フォルダを展開し、「Tomcat vX.Y Server」(X.YはダウンロードしたTomcatのバージョン)を選択し、「Next」をクリックします。
    4. 「Tomcat installation directory:」に、展開したTomcatのパスを指定します。「Finish」をクリックします。
  • IntelliJ IDEA:

    1. 「File」メニュー -> 「Settings」(macOSでは「IntelliJ IDEA」メニュー -> 「Preferences」)を開きます。
    2. 左側のメニューから「Build, Execution, Deployment」-> 「Application Servers」を選択します。
    3. 左上の「+」ボタンをクリックし、「Tomcat Server」を選択します。
    4. 「Tomcat Home:」に、展開したTomcatのパスを指定します。「OK」をクリックします。

これで、Servlet開発のための基本的な環境が整いました。

3. 初めてのServlet:「Hello, World!」

それでは、実際にServletを作成し、Webブラウザからアクセスして「Hello, World!」と表示させてみましょう。

ここでは、Mavenを使ったプロジェクトを作成し、そこにServletを追加する手順を説明します。Mavenを使うことで、プロジェクトの構造や依存関係(Servlet APIなど)の管理が容易になります。

3.1 Maven Webプロジェクトの作成

使用するIDEで、新しいMavenプロジェクトを作成します。

  • Eclipse:

    1. 「File」メニュー -> 「New」 -> 「Maven Project」を選択します。
    2. 「Create a simple project (skip archetype selection)」のチェックを外します(またはチェックを入れずに「Next」)。
    3. Archetypeリストから maven-archetype-webapp を選択します。これは、Webアプリケーションの基本的なディレクトリ構成(src/main/webapp など)を持つプロジェクトを作成するためのArchetypeです。「Next」をクリックします。
    4. GroupId(例: com.example.servlet)、ArtifactId(例: myfirstservlet)を入力し、「Finish」をクリックします。
  • IntelliJ IDEA:

    1. 「File」メニュー -> 「New」 -> 「Project」を選択します。
    2. 左側のメニューから「Maven」を選択します。
    3. 「Create from archetype」にチェックを入れ、リストから org.apache.maven.archetypes:maven-archetype-webapp を選択します。
    4. 「Next」をクリックします。
    5. GroupId(例: com.example.servlet)、ArtifactId(例: myfirstservlet)を入力し、「Next」をクリックします。
    6. プロジェクト名などを確認し、「Finish」をクリックします。

プロジェクト作成後、IDEがMavenの依存関係などを解決するまで少し待ちます。

3.2 pom.xml の修正

作成された pom.xml ファイルを編集します。特に、Servlet APIの依存関係を追加する必要があります。maven-archetype-webapp で作成されたプロジェクトのpom.xmlには、Servlet APIの依存関係が追加されていることが多いですが、スコープが provided になっているか確認してください。provided スコープは、コンパイル時には必要だが、実行時にはServletコンテナが提供するため含めない、という意味です。また、Javaのバージョンを設定します。

“`xml
4.0.0
com.example.servlet
myfirstservlet war
1.0-SNAPSHOT
myfirstservlet Maven Webapp
http://maven.apache.org

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source> <!-- 使用するJavaバージョン -->
    <maven.compiler.target>11</maven.compiler.target> <!-- 使用するJavaバージョン -->
</properties>

<dependencies>
    <!-- Servlet APIの依存関係 -->
    <!-- Jakarta EE 9以降の場合 -->
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>5.0.0</version> <!-- 使用するTomcatのバージョンに対応するAPIバージョン -->
        <scope>provided</scope>
    </dependency>
    <!-- Java EE 8以前の場合 -->
    <!--
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version> // 使用するTomcatのバージョンに対応するAPIバージョン
        <scope>provided</scope>
    </dependency>
    -->

    <!-- 必要に応じてテストライブラリなどを追加 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
        <!-- Maven Compiler Plugin -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version> <!-- 最新バージョンを使用 -->
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
            </configuration>
        </plugin>
        <!-- Maven WAR Plugin -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.3.2</version> <!-- 最新バージョンを使用 -->
         </plugin>
    </plugins>
</build>

``
**注意**: Jakarta EE 9以降 (Tomcat 10以降) では、パッケージ名が
javax.servletからjakarta.servletに変更されています。使用するTomcatのバージョンに合わせて、適切なServlet APIの依存関係(GroupIdとArtifactId、バージョン)を選択してください。Tomcat 9以前はjavax.servlet、Tomcat 10以降はjakarta.servlet` です。Servlet APIのバージョンもTomcatのバージョンに対応したものを選びます。例えば、Tomcat 10.xならServlet 5.0、Tomcat 11.xならServlet 6.0などです。

ファイルを保存すると、IDEが自動的に依存関係をダウンロードします。

3.3 Servletクラスの作成

プロジェクトのJavaソースフォルダ(例: src/main/java)に、新しいJavaクラスを作成します。

  • パッケージ名:com.example.servlet など
  • クラス名:HelloWorldServlet など

作成したクラスを、Servletとして機能させるために jakarta.servlet.http.HttpServlet (または javax.servlet.http.HttpServlet)を継承します。HttpServlet は、HTTPリクエストを扱うための便利なメソッドを提供する抽象クラスです。

そして、HTTPのGETリクエストに対応する doGet メソッドをオーバーライドします。

“`java
package com.example.servlet;

import java.io.IOException;
import java.io.PrintWriter;

// Jakarta EE 9以降 (Tomcat 10以降)
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

// Java EE 8以前 (Tomcat 9以前) の場合はこちらを使用
/
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/

// @WebServlet アノテーションを使って、このServletにアクセスするためのURLパターンを指定します。
// この例では “/hello” というパスでアクセスできるようになります。
@WebServlet(“/hello”)
public class HelloWorldServlet extends HttpServlet {

// GETリクエストがあった場合に呼び出されるメソッド
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // レスポンスのContent Typeと文字エンコーディングを設定します。
    // これにより、ブラウザにHTMLとして解釈され、UTF-8で表示されるようになります。
    response.setContentType("text/html;charset=UTF-8");

    // レスポンスボディに書き込むためのPrintWriterを取得します。
    PrintWriter out = response.getWriter();

    try {
        // HTMLコンテンツを書き込みます。
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta charset=\"UTF-8\">");
        out.println("<title>Hello, World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello, World from Servlet!</h1>");
        out.println("</body>");
        out.println("</html>");
    } finally {
        // PrintWriterをクローズします。
        out.close();
    }
}

// 必要に応じて他のHTTPメソッド(POST, PUT, DELETEなど)に対応する
// doPost, doPut, doDeleteなどのメソッドをオーバーライドします。
// この例ではGETリクエストのみに対応します。

// Servletのライフサイクルメソッド(init, destroy)もオーバーライドできますが、
// 今回は最低限の実装とします。

}
“`

このコードでは、@WebServlet("/hello") アノテーションを使って、このServletが /hello というURLパスで呼び出されるように設定しています。これはServlet 3.0以降で導入された便利な機能です。それ以前のバージョンでは、 web.xml というデプロイメント記述子ファイルにServletの設定を記述する必要がありました(これについても後述します)。

doGet メソッド内では、引数として渡される HttpServletRequest オブジェクトと HttpServletResponse オブジェクトを使用します。

  • HttpServletRequest: クライアントからのリクエストに関する情報(パラメータ、ヘッダー、クッキーなど)を取得するために使用します。
  • HttpServletResponse: クライアントへのレスポンス(コンテンツ、ステータスコード、ヘッダーなど)を生成するために使用します。

ここでは、response.setContentType("text/html;charset=UTF-8"); でレスポンスがHTMLであり、文字エンコーディングがUTF-8であることをブラウザに伝えています。そして、response.getWriter() で取得した PrintWriter を使って、レスポンスボディにHTMLコンテンツを書き込んでいます。

3.4 Webアプリケーションのデプロイと実行

作成したServletをServletコンテナ(Tomcat)上で実行します。IDEを使っている場合、プロジェクトをTomcatにデプロイして実行するのが最も簡単です。

  • Eclipse:

    1. サーバービュー(Servers View)で、先ほど設定したTomcatを選択します。
    2. 右クリックし、「Add and Remove…」を選択します。
    3. 左側の「Available:」リストから作成したプロジェクトを選択し、「Add >」ボタンをクリックして右側の「Configured:」リストに移動します。
    4. 「Finish」をクリックします。
    5. サーバービューでTomcatを選択し、再生ボタン(Start the server)をクリックします。
  • IntelliJ IDEA:

    1. 画面右上にある実行/デバッグ構成(Run/Debug Configurations)ドロップダウンリストを開き、「Edit Configurations…」を選択します。
    2. 左上の「+」ボタンをクリックし、「Tomcat Server」-> 「Local」を選択します。
    3. サーバー名(例: Tomcat 9.0.x Local)を適切に設定します。
    4. 「Deployment」タブを選択し、左下の「+」ボタン -> 「Artifact…」を選択し、作成したプロジェクトのWAR Exploded (または WAR) アーティファクトを選択します。
    5. 「Application context」を /myfirstservlet (プロジェクト名など、任意のパス)に設定します。これがWebアプリケーションのコンテキストパスになります。
    6. 「OK」をクリックします。
    7. 画面右上の実行/デバッグ構成ドロップダウンリストで設定したTomcat構成が選択されていることを確認し、再生ボタン(Run)をクリックします。

Tomcatが起動し、プロジェクトがデプロイされると、IDEのコンソールに起動ログが表示されます。エラーが出ていないことを確認してください。

3.5 ブラウザからのアクセス

Tomcatが起動したら、Webブラウザを開き、以下のURLにアクセスします。

http://localhost:8080/myfirstservlet/hello

  • localhost: Tomcatが動作しているサーバーのホスト名(通常は自分のPC)。
  • 8080: TomcatのデフォルトのHTTPポート番号。
  • /myfirstservlet: プロジェクトのコンテキストパス(IDEで設定したパス)。
  • /hello: @WebServlet アノテーションでServletに指定したURLパターン。

正しく設定・デプロイできていれば、ブラウザに「Hello, World from Servlet!」という見出しが表示されるはずです。

これで、初めてのServlet作成と実行に成功しました!

3.6 web.xml による設定 (補足)

Servlet 3.0より前は、@WebServlet のようなアノテーションはありませんでした。Servletの設定(どのクラスをServletとして扱うか、どのURLパターンにマッピングするかなど)は、src/main/webapp/WEB-INF ディレクトリにある web.xml というXMLファイルに記述する必要がありました。

以下は、上記の HelloWorldServletweb.xml で設定する場合の例です。

“`xml




<!-- Servletの定義 -->
<servlet>
    <servlet-name>HelloWorld</servlet-name> <!-- Servletの名前 -->
    <servlet-class>com.example.servlet.HelloWorldServlet</servlet-class> <!-- Servletクラスの完全修飾名 -->
</servlet>

<!-- ServletとURLパターンのマッピング -->
<servlet-mapping>
    <servlet-name>HelloWorld</servlet-name> <!-- 上記で定義したServletの名前 -->
    <url-pattern>/hello</url-pattern> <!-- このServletを呼び出すURLパターン -->
</servlet-mapping>

<!-- ウェルカムファイルリスト -->
<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>


``@WebServletアノテーションを使っている場合、web.xmlは必須ではありませんが、その他の設定(ウェルカムファイル、エラーページ、セッションタイムアウトなど)のために使用されることもあります。アノテーションとweb.xmlの両方で同じServletを設定した場合、web.xml` の設定が優先されます。

初心者としては、まずアノテーションを使ってシンプルに設定できる方法から始めるのが良いでしょう。

4. HTTPリクエストとレスポンスの詳細

Webアプリケーション開発において、クライアントからのリクエストを正確に受け取り、適切なレスポンスを返すことは非常に重要です。Servletでは、HttpServletRequestHttpServletResponse オブジェクトを使ってこれを行います。

4.1 HttpServletRequest オブジェクト

HttpServletRequest は、クライアントからサーバーへのHTTPリクエストに関するあらゆる情報を提供するインターフェースです。doGet()doPost() メソッドの第一引数として渡されます。主な取得可能な情報は以下の通りです。

  • リクエストパラメータの取得: HTMLフォームで送信されたデータや、URLのクエリ文字列(?key1=value1&key2=value2 の部分)を取得します。

    • String getParameter(String name): 指定した名前のパラメータの値を1つ取得します。同じ名前のパラメータが複数ある場合は、最初の値が返されます。
    • String[] getParameterValues(String name): 指定した名前のパラメータの値を複数取得します。チェックボックスなどで同じ名前の要素が複数ある場合などに使用します。
    • Enumeration<String> getParameterNames(): すべてのパラメータ名のリストを取得します。
    • Map<String, String[]> getParameterMap(): すべてのパラメータをマップ(キー:パラメータ名, 値:値の配列)として取得します。
  • ヘッダー情報の取得: リクエストヘッダー(User-Agent, Referer, Cookie など)を取得します。

    • String getHeader(String name): 指定した名前のヘッダーの値を取得します。
    • Enumeration<String> getHeaderNames(): すべてのヘッダー名のリストを取得します。
  • クッキーの取得: クライアントから送信されたクッキーを取得します。

    • Cookie[] getCookies(): クライアントから送信されたすべてのクッキーの配列を取得します。クッキーについては後述します。
  • セッションの取得/作成: クライアントとサーバー間のセッション情報を取得または作成します。

    • HttpSession getSession(): 現在のセッションを取得します。セッションが存在しない場合は新しく作成します。
    • HttpSession getSession(boolean create): createtrue ならば上記と同じ動作。createfalse でセッションが存在しない場合は null を返します。セッションについては後述します。
  • リクエスト属性の設定/取得: 1つのリクエスト内で情報を共有するために使用します。主にServletからJSPへのデータ受け渡しに利用します。

    • void setAttribute(String name, Object o): 指定した名前でオブジェクトをリクエスト属性として設定します。
    • Object getAttribute(String name): 指定した名前のリクエスト属性の値を取得します。戻り値は Object なので、必要な型にキャストする必要があります。
    • Enumeration<String> getAttributeNames(): すべてのリクエスト属性名のリストを取得します。
    • void removeAttribute(String name): 指定した名前のリクエスト属性を削除します。
  • パス情報の取得: リクエストURLに関する様々な情報を取得します。

    • String getRequestURI(): リクエストのURI (/myfirstservlet/hello など) を取得します。
    • String getContextPath(): Webアプリケーションのコンテキストパス (/myfirstservlet など) を取得します。
    • String getServletPath(): Servletにマッピングされたパス (/hello など) を取得します。
    • String getQueryString(): URLのクエリ文字列 (?key1=value1&key2=value2 など) を取得します。
  • 入力ストリームの取得: POSTメソッドなどで送信されたリクエストボディ(ファイルアップロードやJSONデータなど)を読み込むために使用します。

    • ServletInputStream getInputStream(): リクエストボディを読み込むための入力ストリームを取得します。
    • BufferedReader getReader(): リクエストボディを文字として読み込むための BufferedReader を取得します。通常は文字データの場合に使用します。

リクエストパラメータ取得の例:

HTMLフォームから送信されたユーザー名と年齢を取得するServletの例。

“`java
package com.example.servlet;

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

@WebServlet(“/processForm”)
public class ProcessFormServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // リクエストの文字エンコーディングを設定(POSTデータが日本語などの場合必須)
    request.setCharacterEncoding("UTF-8");

    // パラメータの取得
    String username = request.getParameter("username");
    String ageStr = request.getParameter("age");
    int age = 0;
    if (ageStr != null && !ageStr.isEmpty()) {
         try {
             age = Integer.parseInt(ageStr);
         } catch (NumberFormatException e) {
             // 数値変換エラーの処理
             System.err.println("Invalid age format: " + ageStr);
         }
    }


    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    try {
        out.println("<!DOCTYPE html>");
        out.println("<html><body>");
        out.println("<h1>Form Data Received:</h1>");
        out.println("<p>Username: " + (username != null ? username : "N/A") + "</p>");
        out.println("<p>Age: " + (age > 0 ? age : "N/A") + "</p>");
        out.println("</body></html>");
    } finally {
        out.close();
    }
}

// GETリクエストでも同じ処理を行う場合はdoGetからもdoPostを呼び出すなどする
// またはdoServiceをオーバーライドしてHTTPメソッドによらず共通処理を記述することも可能
// 今回はPOSTフォームからの送信を想定しているためdoPostのみを実装

}
“`

このServletに対応するHTMLフォームの例 (src/main/webapp/form.html):

“`html





Input Form

Please enter your information:






``
このHTMLファイルをTomcatにデプロイし(
src/main/webappディレクトリの内容は自動的にデプロイされる)、http://localhost:8080/myfirstservlet/form.htmlにアクセスしてフォームを送信すると、ProcessFormServlet` が実行され、入力されたデータが表示されます。

4.2 HttpServletResponse オブジェクト

HttpServletResponse は、サーバーからクライアントへのHTTPレスポンスを生成するために使用されるインターフェースです。doGet()doPost() メソッドの第二引数として渡されます。主な設定可能な情報は以下の通りです。

  • レスポンスの書き出し: クライアントに送信するコンテンツ(HTML, JSON, 画像など)を書き出します。

    • PrintWriter getWriter(): テキストデータを書き出すための PrintWriter を取得します。文字エンコーディングは setContentType で設定します。
    • ServletOutputStream getOutputStream(): バイナリデータ(画像ファイルなど)を書き出すための ServletOutputStream を取得します。
  • コンテンツタイプの設定: レスポンスボディのコンテンツの種類(MIMEタイプ)と文字エンコーディングを指定します。

    • void setContentType(String type): 例: "text/html;charset=UTF-8", "application/json", "image/png"。これはレスポンスヘッダーの Content-Type を設定します。
  • ステータスコードの設定: HTTPレスポンスのステータスコード(200 OK, 404 Not Found, 500 Internal Server Errorなど)を設定します。

    • void setStatus(int sc): 例: response.setStatus(HttpServletResponse.SC_OK); (200), response.setStatus(HttpServletResponse.SC_NOT_FOUND); (404)。
    • void sendError(int sc, String msg): 指定したステータスコードとエラーメッセージを含むエラーレスポンスをクライアントに送信します。通常は例外処理などで使用します。
  • リダイレクト: クライアントを別のURLに転送するように指示します。

    • void sendRedirect(String location): クライアントに指定したURLへリダイレクトするように指示するレスポンス(ステータスコード 302 Found と Locationヘッダー)を送信します。
  • クッキーの設定: クライアントのブラウザに保存させるクッキーを設定します。

    • void addCookie(Cookie cookie): 作成した Cookie オブジェクトをレスポンスヘッダー(Set-Cookie)に追加します。クッキーについては後述します。
  • ヘッダーの設定: その他のレスポンスヘッダーを設定します。

    • void setHeader(String name, String value): 指定したヘッダーを設定します。同名のヘッダーが既に存在する場合は上書きします。
    • void addHeader(String name, String value): 指定したヘッダーを追加します。同名のヘッダーが既に存在する場合でも追加されます。
  • バッファリング: レスポンスボディは、デフォルトでは内部バッファに溜められてからクライアントに送信されます。バッファサイズを調整したり、明示的にフラッシュしたりできます。

    • int getBufferSize(): 現在のバッファサイズを取得します。
    • void setBufferSize(int size): バッファサイズを設定します(コンテンツを書き出す前に呼び出す必要があります)。
    • void flushBuffer(): バッファの内容を即座にクライアントに送信します。
    • void resetBuffer(): バッファの内容をクリアします(既にコミットされている場合は IllegalStateException が発生します)。

リダイレクトの例:

特定の条件を満たした場合に別のページにリダイレクトするServletの例。

“`java
package com.example.servlet;

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

@WebServlet(“/redirectTest”)
public class RedirectTestServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // リクエストパラメータ 'goto' の値を取得
    String gotoPage = request.getParameter("goto");

    if ("bing".equals(gotoPage)) {
        // パラメータが "bing" ならば Bing にリダイレクト
        response.sendRedirect("https://www.bing.com/");
    } else {
        // それ以外の場合は単純なレスポンスを返す
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().println("<!DOCTYPE html><html><body>");
        response.getWriter().println("<h1>No Redirect</h1>");
        response.getWriter().println("<p>Access with ?goto=bing to redirect.</p>");
        response.getWriter().println("</body></html>");
    }
}

}
``
このServletに
http://localhost:8080/myfirstservlet/redirectTest?goto=bingとアクセスするとBingにリダイレクトされ、http://localhost:8080/myfirstservlet/redirectTest` とアクセスすると「No Redirect」というテキストが表示されます。

5. セッション管理

HTTPはステートレスなプロトコルですが、Webアプリケーションではユーザーの状態を維持する必要があります。例えば、ログインしているユーザーを識別したり、ショッピングカートの内容を保持したりする場合です。Servletでは、この状態管理のために「セッション」という仕組みを提供します。

5.1 セッションの仕組み

セッション管理では、クライアント(ブラウザ)とサーバーの間で一連のリクエストを関連付け、同一ユーザーからのものとして扱います。サーバーは、セッションごとに一意の「セッションID」を生成し、そのIDに関連付けてセッション固有のデータをサーバー側のメモリやストレージに保持します。

クライアントとサーバーの間でセッションIDをやり取りする方法としては、主に以下の二つがあります。

  1. クッキー (Cookie): サーバーはレスポンスヘッダーの Set-Cookie でセッションIDをブラウザに送信します。ブラウザはそれを保存し、以降の同じサイトへのリクエスト時にはリクエストヘッダーの Cookie でセッションIDをサーバーに返送します。これが最も一般的な方法です。
  2. URLリライティング: クライアントがクッキーを無効にしている場合などに、セッションIDをURLの末尾やクエリ文字列に埋め込んでやり取りする方法です。例: /myapp/page;jsessionid=ABC12345。Servletコンテナが自動で行うことがありますが、セキュリティやURLの見た目の問題から、可能な限りクッキーを使うのが推奨されます。

Servletでは、HttpSession インターフェースを使ってセッション管理を行います。

5.2 HttpSession オブジェクト

HttpServletRequest オブジェクトの getSession() または getSession(boolean create) メソッドを使って HttpSession オブジェクトを取得します。

  • HttpSession getSession(): セッションが存在すればそれを返し、存在しなければ新しく作成して返します。
  • HttpSession getSession(boolean create):
    • createtrue の場合は getSession() と同じ。
    • createfalse の場合は、セッションが存在すればそれを返し、存在しなければ null を返します。セッションが不要な場合に、無駄に新しいセッションを作成しないために使います。

HttpSession オブジェクトは、以下のメソッドを使ってセッション固有のデータを保存・取得できます。これらのデータは、セッションが有効な間、同一ユーザーからのリクエスト間で共有されます。

  • void setAttribute(String name, Object value): 指定した名前でオブジェクトをセッションに保存します。
  • Object getAttribute(String name): 指定した名前でセッションに保存されたオブジェクトを取得します。取得後は必要な型にキャストして使用します。
  • Enumeration<String> getAttributeNames(): セッションに保存されているすべての属性名のリストを取得します。
  • void removeAttribute(String name): 指定した名前の属性をセッションから削除します。

セッションは、以下のいずれかの条件で無効(破棄)されます。

  • invalidate() メソッドが呼び出されたとき。
  • セッションが一定時間アクセスされなかった(タイムアウトした)とき。
  • Servletコンテナが停止したとき。

セッション管理の例:

ユーザーの訪問回数をセッションに記録するServletの例。

“`java
package com.example.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; // HttpSessionをインポート

@WebServlet(“/sessionCounter”)
public class SessionCounterServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    // セッションを取得または新しく作成
    HttpSession session = request.getSession();

    // セッションIDを表示
    String sessionId = session.getId();
    out.println("<!DOCTYPE html><html><body>");
    out.println("<h1>Session Counter</h1>");
    out.println("<p>Session ID: " + sessionId + "</p>");

    // 'visitCount' という名前でセッション属性を取得
    // 初回アクセス時はnullなので、Integerオブジェクトとして扱うためにキャストに注意
    Integer visitCount = (Integer) session.getAttribute("visitCount");

    if (visitCount == null) {
        // 初回アクセスの場合
        visitCount = 1;
        out.println("<p>Welcome! This is your first visit in this session.</p>");
    } else {
        // 2回目以降のアクセスの場合
        visitCount++;
        out.println("<p>You have visited this page " + visitCount + " times in this session.</p>");
    }

    // 更新した訪問回数をセッションに保存
    session.setAttribute("visitCount", visitCount);

    // セッションの最大非アクティブ時間(秒単位)を取得・設定することも可能
    // int maxInactiveInterval = session.getMaxInactiveInterval(); // デフォルト値はコンテナ設定による
    // session.setMaxInactiveInterval(30 * 60); // 例: 30分に設定 (30分アクセスがなければセッション破棄)

    out.println("<p><a href=\"sessionCounter\">Reload this page</a></p>");
    out.println("</body></html>");

    out.close();
}

}
“`
このServletにブラウザからアクセスし、ページをリロードするたびに訪問回数が増加することが確認できます。異なるブラウザからアクセスしたり、ブラウザを閉じて再度開いたりすると、新しいセッションが開始され、訪問回数は1からリセットされます(ブラウザの設定やセッションタイムアウト時間による)。

5.3 セッションタイムアウト

セッションは、何も操作がないまま一定時間が経過すると自動的に無効化されます。この時間を「セッションタイムアウト」と呼びます。セッションタイムアウト時間は、web.xml またはServletコンテナの設定で指定できます。

web.xmlでの設定例:

xml
<web-app ...>
<session-config>
<!-- セッションの最大非アクティブ時間を分単位で指定 (例: 30分) -->
<session-timeout>30</session-timeout>
</session-config>
</web-app>

session.setMaxInactiveInterval(int seconds) メソッドを使って、Servlet内でプログラム的にセッションごとのタイムアウトを設定することもできます。

6. クッキーの利用

クッキー(Cookie)は、サーバーがクライアントのブラウザに一時的に情報を保存させておくための仕組みです。ステートレスなHTTPにおいて、ユーザーの状態や設定(ログイン情報の一部、カートの内容、表示設定など)をクライアントサイドに保持するために利用されます。

6.1 クッキーの仕組み

  1. サーバーは、レスポンスの一部として Set-Cookie ヘッダーを付けてクッキーをクライアントに送信します。
  2. ブラウザは、受け取ったクッキーを保存します。
  3. 以降、ブラウザは同じドメイン(または設定されたパスやドメイン)へのリクエストのたびに、保存しているクッキーを Cookie ヘッダーに含めてサーバーに送信します。
  4. サーバーは、リクエストに含まれるクッキーを読み取り、ユーザーの特定や状態の把握に利用します。

6.2 jakarta.servlet.http.Cookie クラス

Servletでは、jakarta.servlet.http.Cookie (または javax.servlet.http.Cookie)クラスを使ってクッキーを扱います。

  • クッキーの作成:
    java
    Cookie userCookie = new Cookie("username", "john_doe"); // 名前と値でクッキーを作成
    userCookie.setMaxAge(60 * 60 * 24 * 30); // 有効期限を秒単位で設定 (例: 30日間)
    userCookie.setPath("/"); // クッキーを送信するパスを設定 (例: アプリケーション全体)
    // 必要に応じて setDomain(), setSecure(), setHttpOnly() などを設定

    • setMaxAge(int seconds): クッキーの有効期限を秒単位で設定します。正の値は、ブラウザを閉じても指定した期間クッキーが保持されることを意味します(永続クッキー)。0 はクッキーの削除を意味します。負の値(デフォルト)は、ブラウザを閉じるとクッキーが破棄されることを意味します(セッションクッキー)。
    • setPath(String path): クッキーがどのパス以下のリクエストでサーバーに送信されるかを設定します。デフォルトはクッキーを設定したServletのパスです。/ とするとアプリケーション全体で有効になります。
    • setDomain(String domain): クッキーがどのドメインまたはサブドメインに送信されるかを設定します。
    • setSecure(boolean flag): true に設定すると、HTTPS接続でのみクッキーが送信されます。
    • setHttpOnly(boolean flag): true に設定すると、JavaScriptからクッキーにアクセスできなくなります。XSS (Cross-Site Scripting) 攻撃によるクッキー情報の盗難を防ぐのに有効です。
  • レスポンスへのクッキーの追加:
    作成した Cookie オブジェクトを HttpServletResponse オブジェクトに追加します。
    java
    response.addCookie(userCookie); // レスポンスヘッダーにSet-Cookieとして追加される

  • リクエストからのクッキーの取得:
    クライアントから送信されたクッキーは、HttpServletRequest オブジェクトから取得します。
    “`java
    Cookie[] cookies = request.getCookies(); // すべてのクッキーの配列を取得

    if (cookies != null) {
    for (Cookie cookie : cookies) {
    String name = cookie.getName(); // クッキーの名前を取得
    String value = cookie.getValue(); // クッキーの値を取得
    // 必要なクッキーを探して利用する
    if (“username”.equals(name)) {
    System.out.println(“Found username cookie: ” + value);
    }
    }
    }
    ``request.getCookies()` は、クライアントが送信したすべてのクッキーを配列で返します。特定のクッキーの値を取得するには、配列をループして名前で一致するものを探す必要があります。

クッキー利用の例:

ユーザー名をクッキーに保存し、次回アクセス時に表示するServletの例。

“`java
package com.example.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie; // Cookieをインポート
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet(“/cookieTest”)
public class CookieTestServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    String username = null;
    Cookie[] cookies = request.getCookies(); // リクエストからクッキーを取得

    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if ("username".equals(cookie.getName())) {
                username = cookie.getValue(); // "username"という名前のクッキーの値を取得
                break; // 見つかったらループを抜ける
            }
        }
    }

    out.println("<!DOCTYPE html><html><body>");
    out.println("<h1>Cookie Test</h1>");

    if (username == null) {
        // クッキーが見つからない場合
        out.println("<p>Hello, Guest! No 'username' cookie found.</p>");
        out.println("<form action=\"cookieTest\" method=\"post\">");
        out.println("Enter your name: <input type=\"text\" name=\"username\"><br>");
        out.println("<input type=\"submit\" value=\"Save Name in Cookie\">");
        out.println("</form>");
    } else {
        // クッキーが見つかった場合
        out.println("<p>Hello, " + username + "! Your name was loaded from a cookie.</p>");
        out.println("<form action=\"cookieTest\" method=\"post\">");
         out.println("<input type=\"hidden\" name=\"action\" value=\"delete\">"); // クッキー削除用
        out.println("<input type=\"submit\" value=\"Delete Cookie\">");
        out.println("</form>");
    }

    out.println("</body></html>");
    out.close();
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    request.setCharacterEncoding("UTF-8");

    String action = request.getParameter("action");

    if ("delete".equals(action)) {
         // クッキーの削除 (有効期限を0に設定)
        Cookie userCookie = new Cookie("username", ""); // 値は何でも良い
        userCookie.setMaxAge(0); // 有効期限を0に設定
        userCookie.setPath("/"); // 作成時と同じパスを設定
        response.addCookie(userCookie); // レスポンスに追加

        // 削除後にGETリクエストにリダイレクトして再表示
        response.sendRedirect(request.getRequestURI());

    } else {
        // ユーザー名を取得しクッキーに保存
        String username = request.getParameter("username");
        if (username != null && !username.trim().isEmpty()) {
            Cookie userCookie = new Cookie("username", username);
            userCookie.setMaxAge(60 * 60 * 24 * 365); // 1年間有効なクッキー
            userCookie.setPath("/"); // アプリケーション全体で有効
            response.addCookie(userCookie); // レスポンスにクッキーを追加
        }

         // クッキー設定後にGETリクエストにリダイレクトして再表示
        response.sendRedirect(request.getRequestURI());
    }
}

}
``
この例では、GETリクエストでアクセスした際にクッキーにユーザー名が保存されているか確認し、表示を切り替えます。POSTリクエストでは、フォームから送信されたユーザー名をクッキーに保存するか、または削除ボタンが押された場合にクッキーを削除します。
sendRedirect` を使用することで、POSTリクエスト処理後にGETリクエストで同じページを再表示し、クッキーの変更が反映された状態を確認できるようにしています。

7. リクエストディスパッチャーとフォワード/インクルード

Servletはリクエスト処理やビジネスロジックの実行には向いていますが、HTMLのようなプレゼンテーション層の生成にはあまり向いていません。HTMLタグを文字列として PrintWriter に書き出すのは、コードの見通しが悪くなり、メンテナンスも大変です。

そこで、Servletで処理を行った後、別のリソース(別のServletやJSPファイルなど)に処理を渡す仕組みが用意されています。これが「リクエストディスパッチャー (RequestDispatcher)」です。

リクエストディスパッチャーを使うと、以下の二つの方法でリクエストを別のリソースに渡すことができます。

  1. フォワード (Forward):

    • Servletが受け取ったリクエスト処理を、サーバーサイドで別のリソース(ServletまたはJSP)に完全に引き継ぎます。
    • 引き継ぎ先のServlet/JSPは、引き継ぎ元のServletと同じリクエストおよびレスポンスオブジェクトを使って処理を行います。
    • URLは引き継ぎ元のServletのまま変わらず、クライアントには引き継ぎ先のServlet/JSPが生成したレスポンスが返されます。
    • これは、Controller(Servlet)で処理を行い、View(JSP)に表示を任せるというMVCパターンを実装する際に非常に役立ちます。
  2. インクルード (Include):

    • Servletが受け取ったリクエスト処理の途中で、サーバーサイドで別のリソース(ServletまたはJSP)の処理結果を現在のレスポンスに含めます。
    • 引き継ぎ先のServlet/JSPは、引き継ぎ元のServletと同じリクエストおよびレスポンスオブジェクトを使って処理を行いますが、レスポンスヘッダーの設定などは無視されます。
    • URLは引き継ぎ元のServletのままです。
    • これは、複数のリソースで共通のヘッダーやフッターなどを生成してレスポンスに含める場合に便利です。

7.1 RequestDispatcher の取得

RequestDispatcher オブジェクトは、HttpServletRequest または ServletContext オブジェクトから取得できます。

  • RequestDispatcher request.getRequestDispatcher(String path): 相対パスで別のリソースへのディスパッチャーを取得します。パスは / で始まる場合、コンテキストルートからの絶対パスと解釈されます。
  • RequestDispatcher context.getRequestDispatcher(String path): 相対パスまたは絶対パスで別のリソースへのディスパッチャーを取得します。コンテキストルートからの絶対パスで指定するのが一般的です。

7.2 forward() メソッド

取得した RequestDispatcher オブジェクトの forward(HttpServletRequest request, HttpServletResponse response) メソッドを呼び出します。

“`java
// Servletでデータ処理を行う
// … 処理結果をリクエスト属性に設定するなど …
request.setAttribute(“message”, “データ処理が完了しました!”);

// 表示をJSPに委譲する
String nextView = “/WEB-INF/views/result.jsp”; // 遷移先のJSPパス
RequestDispatcher dispatcher = request.getRequestDispatcher(nextView); // リクエストから取得
// RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(nextView); // ServletContextから取得も可能

dispatcher.forward(request, response); // フォワードを実行
``
通常、フォワード先のJSPファイルは、ブラウザから直接アクセスされないように
/WEB-INFディレクトリ以下に配置します。/WEB-INF` ディレクトリ以下のリソースは、Webコンテナによって保護され、直接アクセスできません。

7.3 include() メソッド

取得した RequestDispatcher オブジェクトの include(HttpServletRequest request, HttpServletResponse response) メソッドを呼び出します。

“`java
// レスポンスボディのメインコンテンツを書き出す
response.getWriter().println(“

Main Content

“);
response.getWriter().println(“

This is the main part of the page.

“);

// 別途定義したフッターをインクルードする
String footerPath = “/WEB-INF/views/footer.jsp”; // フッターJSPのパス
RequestDispatcher footerDispatcher = request.getRequestDispatcher(footerPath);

// フッターの内容を現在のレスポンスに含める
footerDispatcher.include(request, response);

// メインServletの処理は続く(例えば、レスポンスの残りを書き出すなど)
response.getWriter().println(“

End of main content.

“);

// 注意:includeメソッドを呼び出した後も、引き継ぎ元のServletの処理は続行されます。
// また、引き継ぎ先のServlet/JSPでresponse.getWriter().close() などを行うとエラーになる場合があります。
“`
インクルードは、複数のJSPファイルから共通のヘッダーやフッターを部品として再利用する場合などに便利です。

8. エラーハンドリング

Webアプリケーションにおいて、予期せぬエラー(プログラム内の例外、リソースが見つからないなど)が発生した場合に、ユーザーに適切な情報を伝えたり、エラーページを表示したりする仕組みが必要です。

8.1 例外処理

Servletコード内で例外が発生する可能性がある処理は、try-catch ブロックで囲んで適切に処理します。例えば、パラメータの数値変換エラーなど。

java
try {
int id = Integer.parseInt(request.getParameter("id"));
// idを使った処理...
} catch (NumberFormatException e) {
// 数値変換エラーが発生した場合の処理
response.setContentType("text/html;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST); // ステータスコードを400 Bad Requestに設定
response.getWriter().println("<h1>Error: Invalid ID format.</h1>");
e.printStackTrace(); // 開発時にはログに出力
} catch (Exception e) {
// その他の例外が発生した場合の処理
response.setContentType("text/html;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // ステータスコードを500 Internal Server Errorに設定
response.getWriter().println("<h1>Error: An internal server error occurred.</h1>");
e.printStackTrace(); // 開発時にはログに出力
// 必要に応じてログファイルに記録する
}

Servletメソッド (doGet, doPost など) は ServletExceptionIOException をスローするように定義されています。これらの例外をcatchせずスローした場合、Servletコンテナがそれを捕捉し、デフォルトのエラーページを表示したり、ステータスコード500を返したりします。

8.2 sendError() メソッド

HttpServletResponse オブジェクトの sendError(int sc, String msg) メソッドを使って、特定のHTTPステータスコードを含むエラーレスポンスを明示的にクライアントに返すことができます。

java
// ユーザーが見つからなかった場合など
if (user == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "User with specified ID not found."); // ステータスコード 404 とメッセージを返す
return; // 処理を終了
}

sendError を呼び出すと、それまでにレスポンスバッファに書き込まれた内容は通常クリアされます(コンテナの実装による)。

8.3 web.xml によるエラーページ設定

特定のHTTPステータスコードが発生した場合や、特定の例外がスローされた場合に、表示するエラーページ(HTMLやJSPなど)を web.xml で設定できます。

xml
<web-app ...>
<error-page>
<error-code>404</error-code> <!-- ステータスコード 404 の場合 -->
<location>/error/404.html</location> <!-- 表示するエラーページのパス -->
</error-page>
<error-page>
<error-code>500</error-code> <!-- ステータスコード 500 の場合 -->
<location>/error/500.jsp</location> <!-- 表示するエラーページのパス -->
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type> <!-- 全ての例外の場合 -->
<location>/error/error.jsp</location> <!-- 表示するエラーページのパス -->
</error-page>
</web-app>

このように設定することで、Servletコード内で response.sendError(404) を呼び出した場合や、処理中にハンドルされない例外(RuntimeException など)が発生した場合に、指定したエラーページが自動的に表示されるようになります。これは、ユーザーに分かりやすいエラーメッセージを表示するために重要です。

9. Servletフィルタ

Servletフィルタ(Filter)は、リクエストがServletに到達する前、およびServletからレスポンスが返される後に、処理を割り込ませることができるコンポーネントです。複数のフィルタをチェーン(連鎖)させて実行することも可能です。

フィルタは、以下のような共通処理をServlet本体から分離するために非常に役立ちます。

  • 文字エンコーディングの設定
  • 認証・認可(ログインチェックなど)
  • ログ出力
  • リクエスト・レスポンスの圧縮
  • 入力値の検証や変換
  • 画像のサイズ変更など

9.1 jakarta.servlet.Filter インターフェース

フィルタを作成するには、jakarta.servlet.Filter (または javax.servlet.Filter)インターフェースを実装します。このインターフェースには以下の3つのメソッドがあります。

  1. init(FilterConfig filterConfig):

    • フィルタが初期化されるときに一度だけ呼び出されます。
    • 初期設定処理を行います。
    • FilterConfig オブジェクトからフィルタ名や初期化パラメータを取得できます。
  2. doFilter(ServletRequest request, ServletResponse response, FilterChain chain):

    • フィルタが処理を割り込ませるたびに呼び出されます。
    • 実際のリクエスト/レスポンス処理を記述します。
    • chain.doFilter(request, response) を呼び出すことで、フィルタチェーンの次のフィルタ、または最終的なリソース(Servlet/JSP)に処理を渡します。この呼び出しより前に書かれたコードはリクエスト処理前に実行され、後に書かれたコードはレスポンス処理後に実行されます。
    • 重要: chain.doFilter() を呼び出さないと、以降のフィルタやターゲットのServlet/JSPは実行されません。
  3. destroy():

    • フィルタインスタンスが破棄される直前に一度だけ呼び出されます。
    • 終了処理を行います。

9.2 フィルタの実装例:文字エンコーディングフィルタ

リクエストパラメータやPOSTデータの文字エンコーディングを設定するためのフィルタは、非常によく使われます。

“`java
package com.example.filter;

import java.io.IOException;
import jakarta.servlet.Filter; // Filterをインポート
import jakarta.servlet.FilterChain; // FilterChainをインポート
import jakarta.servlet.FilterConfig; // FilterConfigをインポート
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; // ServletRequestをインポート
import jakarta.servlet.ServletResponse; // ServletResponseをインポート
import jakarta.servlet.annotation.WebFilter; // @WebFilterをインポート
import jakarta.servlet.annotation.WebInitParam; // @WebInitParamをインポート

// @WebFilter アノテーションを使ってフィルタを登録
// urlPatternsでフィルタを適用するURLパターンを指定
// initParamsで初期化パラメータを指定(ここではエンコーディング名)
@WebFilter(
urlPatterns = “/*”, // すべてのパスに適用
initParams = {
@WebInitParam(name = “encoding”, value = “UTF-8”)
}
)
public class EncodingFilter implements Filter {

private String encoding; // 初期化パラメータとして受け取ったエンコーディング名を保持

// フィルタの初期化メソッド
@Override
public void init(FilterConfig fConfig) throws ServletException {
    // web.xml または @WebInitParam で設定された初期化パラメータを取得
    this.encoding = fConfig.getInitParameter("encoding");
    if (this.encoding == null) {
        this.encoding = "UTF-8"; // デフォルト値
    }
    System.out.println("EncodingFilter initialized with encoding: " + this.encoding);
}

// 実際のリクエスト/レスポンス処理メソッド
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    System.out.println("EncodingFilter: Before processing request...");

    // リクエストの文字エンコーディングを設定
    // getCharacterEncoding() が null の場合のみ設定するのが一般的
    if (request.getCharacterEncoding() == null) {
         request.setCharacterEncoding(this.encoding);
         System.out.println("Set request encoding to: " + this.encoding);
    }

    // レスポンスの文字エンコーディングを設定
    // setContentType() がまだ呼び出されていない場合に設定
    // JSPなどではContentTypeが設定される場合があるので、ここで設定すると上書きされる可能性がある
    // response.setCharacterEncoding(this.encoding); // この行は使わない方が安全な場合が多い

    // フィルタチェーンの次のフィルタ、またはターゲットのServlet/JSPへ処理を渡す
    chain.doFilter(request, response);

    System.out.println("EncodingFilter: After processing request...");

    // chain.doFilter() 呼び出し以降は、レスポンスがクライアントに返される前に実行される処理
    // 例: レスポンスヘッダーを追加するなど
}

// フィルタの破棄メソッド
@Override
public void destroy() {
    System.out.println("EncodingFilter destroyed.");
}

}
``
このフィルタは、すべてのリクエスト (
urlPatterns = “/*”) に対して適用され、リクエストの文字エンコーディングを初期化パラメータで指定されたエンコーディング(この例ではUTF-8)に設定します。chain.doFilter()` を呼び出すことで、本来のリクエスト処理(ServletやJSPの実行)が実行されます。

9.3 フィルタの登録 (web.xml またはアノテーション)

フィルタもServletと同様に、web.xml または @WebFilter アノテーションを使って登録・マッピングします。

アノテーション (@WebFilter):

上記例のように、@WebFilter アノテーションをフィルタクラスに付与します。urlPatterns または servletNames 属性で、フィルタを適用する対象を指定します。

“`java
// URLパターンで指定
@WebFilter(“/secure/*”) // /secure/ 以下のすべてのパスに適用
public class AuthFilter implements Filter { … }

// Servlet名で指定
@WebFilter(servletNames = {“HelloWorld”, “ProcessFormServlet”}) // HelloWorldServletとProcessFormServletに適用
public class LogFilter implements Filter { … }
“`

web.xml による登録:

“`xml



encodingFilter
com.example.filter.EncodingFilter
encoding UTF-8

<!-- フィルタとURLパターンのマッピング -->
<filter-mapping>
    <filter-name>encodingFilter</filter-name> <!-- 上記で定義したフィルタの名前 -->
    <url-pattern>/*</url-pattern> <!-- このフィルタを適用するURLパターン -->
</filter-mapping>

<!-- フィルタとServlet名のマッピング -->
<!--
<filter-mapping>
    <filter-name>authFilter</filter-name>
    <servlet-name>SomeProtectedServlet</servlet-name>
</filter-mapping>
-->


``
アノテーションと
web.xmlの両方でフィルタを設定した場合、web.xml` の設定が優先されます。

10. Servletリスナー

Servletリスナー(Listener)は、Webアプリケーションのライフサイクル、セッションのライフサイクル、リクエストのライフサイクル、属性の変更など、特定のイベントが発生したときに処理を実行するためのコンポーネントです。

リスナーを使うことで、アプリケーションの起動・終了時の初期化やクリーンアップ、セッション数やアクティブなリクエスト数の監視といった横断的な処理を実装できます。

10.1 主なリスナーインターフェース

Jakarta EE (または Java EE) 仕様では、様々な種類のイベントに対応するためのリスナーインターフェースが提供されています。主なものを以下に示します。

  • ServletContextListener: Webアプリケーションの開始・終了イベントを通知します。

    • void contextInitialized(ServletContextEvent sce): Webアプリケーション開始時に呼び出されます。ServletContextEvent から ServletContext オブジェクトを取得できます。
    • void contextDestroyed(ServletContextEvent sce): Webアプリケーション終了時に呼び出されます。
  • HttpSessionListener: セッションの作成・破棄イベントを通知します。

    • void sessionCreated(HttpSessionEvent se): セッション作成時に呼び出されます。HttpSessionEvent から HttpSession オブジェクトを取得できます。
    • void sessionDestroyed(HttpSessionEvent se): セッション破棄時に呼び出されます。
  • ServletRequestListener: リクエストの作成・破棄イベントを通知します。

    • void requestInitialized(ServletRequestEvent sre): リクエスト処理開始時に呼び出されます。ServletRequestEvent から ServletRequest オブジェクトを取得できます。
    • void requestDestroyed(ServletRequestEvent sre): リクエスト処理終了時に呼び出されます。

他にも、ServletContext, HttpSession, ServletRequest の属性の追加・変更・削除を通知するリスナーや、セッションのアクティベーション・パッシベーション(永続化/復元)を通知するリスナーなどがあります。

10.2 リスナーの実装例:アプリケーション起動時の処理

Webアプリケーション起動時に、一度だけ初期化処理を行いたい場合に ServletContextListener を使用します。

“`java
package com.example.listener;

import jakarta.servlet.ServletContext; // ServletContextをインポート
import jakarta.servlet.ServletContextEvent; // ServletContextEventをインポート
import jakarta.servlet.ServletContextListener; // ServletContextListenerをインポート
import jakarta.servlet.annotation.WebListener; // @WebListenerをインポート

// @WebListener アノテーションを使ってリスナーを登録
@WebListener
public class AppContextListener implements ServletContextListener {

// Webアプリケーション起動時に呼び出されるメソッド
@Override
public void contextInitialized(ServletContextEvent sce) {
    ServletContext context = sce.getServletContext();
    String contextPath = context.getContextPath();

    System.out.println("-------------- Web Application Started --------------");
    System.out.println("Context Path: " + contextPath);

    // アプリケーション全体の初期化処理を記述
    // 例: データベースコネクションプールの初期化、設定ファイルの読み込みなど
    // context.setAttribute("dataSource", initializeDataSource()); // ServletContext属性として共有リソースを保存

    System.out.println("Application specific initialization complete.");
    System.out.println("---------------------------------------------------");
}

// Webアプリケーション終了時に呼び出されるメソッド
@Override
public void contextDestroyed(ServletContextEvent sce) {
     ServletContext context = sce.getServletContext();
    String contextPath = context.getContextPath();

    System.out.println("-------------- Web Application Stopping --------------");
    System.out.println("Context Path: " + contextPath);

    // アプリケーション全体の終了処理を記述
    // 例: データベースコネクションプールの解放、スレッドプールのシャットダウンなど
    // cleanupDataSource((DataSource) context.getAttribute("dataSource"));

    System.out.println("Application specific cleanup complete.");
    System.out.println("----------------------------------------------------");
}

}
``
このリスナーは、Webアプリケーションがデプロイされて起動する際に
contextInitializedメソッドが実行され、停止する際にcontextDestroyed` メソッドが実行されます。これらのメソッド内で、アプリケーション全体に必要なリソースの初期化やクリーンアップ処理を行うことができます。

10.3 リスナーの登録 (web.xml またはアノテーション)

リスナーも、web.xml または @WebListener アノテーションを使って登録します。

アノテーション (@WebListener):

上記例のように、リスナーインターフェースを実装したクラスに @WebListener アノテーションを付与します。

java
@WebListener
public class MySessionListener implements HttpSessionListener { ... }

web.xml による登録:

xml
<web-app ...>
<!-- リスナーの定義 -->
<listener>
<listener-class>com.example.listener.AppContextListener</listener-class> <!-- リスナークラスの完全修飾名 -->
</listener>
<listener>
<listener-class>com.example.listener.MySessionListener</listener-class>
</listener>
<!-- 複数のリスナーを登録可能 -->
</web-app>

アノテーションと web.xml の両方でリスナーを設定した場合、web.xml の設定が優先されます。リスナーの呼び出し順序は、web.xml に記述された順、またはアノテーションの場合はコンテナの実装に依存する場合があります(仕様によっては特定の順序が規定されていることもあります)。

11. ServletとJSP:MVCモデルの考え方

前述のように、Servletはビジネスロジックの処理に向いていますが、HTML生成には不向きです。そこで、ServletとJSP(JavaServer Pages)を組み合わせてWebアプリケーションを開発するのが一般的です。

11.1 JSPとは

JSPは、HTMLの中にJavaコードを埋め込んで記述する技術です。これにより、HTMLの構造の中に動的なコンテンツを生成するJavaコードを簡単に記述できます。JSPファイルは、クライアントからのリクエストを受けると、Servletコンテナによって「JSPコンパイラ」でServletクラスに変換・コンパイルされ、実行されます。つまり、JSPも最終的にはServletとして動作します。

11.2 MVCパターン

ServletとJSPを組み合わせる際の一般的な設計パターンが「MVC(Model-View-Controller)」です。

  • Model: アプリケーションのビジネスロジックやデータ管理を担当します。データベースとの連携、データの加工などを行います。JavaBeansやPOJO(Plain Old Java Object)などがModelとして使われます。
  • View: ユーザーインターフェースを担当します。Modelから渡されたデータを表示します。JSPが主にViewとして使われます。
  • Controller: クライアントからのリクエストを受け取り、どのModelを使ってどのような処理を行うかを決定し、処理結果をどのViewに表示させるかを決定します。ServletがControllerとして使われます。

MVCパターンにおけるServletとJSPの典型的な流れは以下のようになります。

  1. クライアントからHTTPリクエストがServlet(Controller)に送信されます。
  2. Servletはリクエストを受け取り、リクエストパラメータなどを解釈します。
  3. Servletは、必要に応じてビジネスロジックを実行するためにModelを呼び出します。
  4. Modelは処理を実行し、結果データをServletに返します。
  5. Servletは、Modelから受け取ったデータを、表示のためにリクエスト属性などに保存します。
  6. Servletは、処理結果を表示するJSP(View)にフォワードします。
  7. JSPは、リクエスト属性からデータを受け取り、そのデータを元にHTMLなどのレスポンスを生成します。
  8. 生成されたレスポンスがクライアントに返送されます。

この役割分担により、ビジネスロジック(Servlet/Model)と表示ロジック(JSP/View)が分離され、コードの見通しやメンテナンス性が向上します。Servletはあくまでリクエストの受付とViewへの振り分けに徹し、表示の詳細(HTML構造)はJSPに任せるのが良いプラクティスです。

11.3 ServletからJSPへのデータ受け渡し

ServletからJSPにデータを渡すには、主にリクエスト属性またはセッション属性を利用します。

  • リクエスト属性 (request.setAttribute()): そのリクエストの処理中だけデータを共有したい場合に利用します。フォワードでServletからJSPにデータを渡す場合に使います。
  • セッション属性 (session.setAttribute()): セッション期間中データを共有したい場合に利用します。ログインユーザー情報などを保存する場合に使います。

ServletからJSPにフォワードしてデータを渡す例:

Servlet (DataProcessorServlet.java):

“`java
package com.example.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet(“/processData”)
public class DataProcessorServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // Modelからデータを取得する(ここでは簡単なリストを作成するだけ)
    List<String> dataList = new ArrayList<>();
    dataList.add("Item 1");
    dataList.add("Item 2");
    dataList.add("Item 3");

    // 処理結果データをリクエスト属性に設定
    request.setAttribute("myDataList", dataList);
    request.setAttribute("message", "これはServletから渡されたメッセージです。");

    // 表示をJSPに委譲(フォワード)
    String nextView = "/WEB-INF/views/displayData.jsp"; // JSPファイルのパス
    RequestDispatcher dispatcher = request.getRequestDispatcher(nextView);
    dispatcher.forward(request, response);
}

}
``
JSP (
src/main/webapp/WEB-INF/views/displayData.jsp`):

“`jsp
<%– JSPページの文字エンコーディングとコンテンツタイプを設定 –%>
<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″%>
<%– EL (Expression Language) と JSTL (JSP Standard Tag Library) を使用するための設定 –%>
<%@ taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core” %>





Display Data

データ表示

<%-- Servletから渡されたメッセージを表示 (ELを使用) --%>

${requestScope.message}

<%-- requestScopeはデフォルトのスコープなので省略可能 ${message} でもOK --%>

リストデータ:

    <%-- Servletから渡されたリストデータをループして表示 (JSTLのforEachタグを使用) --%>

  • ${item}

  • <%-- requestScopeは省略可能 ${myDataList} でもOK --%>


``
このJSPでは、**EL (Expression Language)** (
${…}) と **JSTL (JSP Standard Tag Library)** () を使って、Servletからリクエスト属性として渡されたデータを簡単に表示しています。ELはリクエスト属性やセッション属性などのスコープからデータを取得するために、JSTLはループや条件分岐などのロジックを表示層で記述するために使われる標準的な技術です。これらを使うことで、JSPファイル内のJavaスクリプトレット(<% … %>)を減らし、Viewのコードを見通し良く記述できます。ELとJSTLの利用には、プロジェクトにそれらのライブラリ(jakarta.servlet.jsp.jstl-api` および実装ライブラリ)の依存関係を追加する必要があります。

Mavenの場合の依存関係例 (Jakarta EE):
xml
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>2.0.0</version>
</dependency>

(Java EEの場合は groupId が javax.servlet.jsp.jstl などとなります)

12. アノテーションベースの設定

Servlet 3.0 (Java EE 6) 以降、web.xml に記述していたServlet、フィルタ、リスナーのマッピングや設定の多くを、クラスに付与するアノテーションで行えるようになりました。これにより、設定ファイルがシンプルになり、コードと設定が近くに配置されるため開発が容易になりました。

主なアノテーション:

  • @WebServlet: Servletクラスに付与し、URLパターンや初期化パラメータなどを設定します。

    • urlPatterns または value: マッピングするURLパターンを指定します (例: @WebServlet("/hello"), @WebServlet(urlPatterns = {"/foo", "/bar"}))。
    • name: Servlet名を指定します。省略した場合はクラス名が使用されます。
    • initParams: 初期化パラメータを指定します (例: @WebInitParam(name="param1", value="value1"))。
  • @WebFilter: Filterクラスに付与し、適用するURLパターン、Servlet名、またはディスパッチャータイプなどを設定します。

    • urlPatterns: 適用するURLパターンを指定します。
    • servletNames: 適用するServlet名を指定します。
    • dispatcherTypes: フィルタを適用するリクエストのディスパッチャータイプを指定します(例: REQUEST, FORWARD, INCLUDE, ERROR, ASYNC)。
  • @WebListener: Listenerクラスに付与します。

これらのアノテーションを使用することで、web.xml をほとんど記述せずにWebアプリケーションを構成できます。ただし、ウェルカムファイルリスト、エラーページ設定、セッションタイムアウトなど、アノテーションでは設定できない項目も依然として web.xml で設定する必要があります。

また、大規模なアプリケーションでServletやフィルタの定義が多い場合など、設定を一元管理したい場合は、あえて web.xml を中心に構成することもあります。プロジェクトの要件やチームの標準に合わせて使い分けることになります。

13. セキュリティに関する基本的な考慮事項

Webアプリケーション開発において、セキュリティは非常に重要です。Servletはサーバーサイドで動作するため、適切に保護しないと様々な脆弱性の原因となり得ます。Servletそのものが直接的な脆弱性を持つわけではありませんが、Servlet内で処理する入力データや、生成するレスポンスには注意が必要です。

基本的なセキュリティ対策として、以下の点を常に意識してください。

  • 入力検証 (Input Validation): クライアントから送られてくるデータ(リクエストパラメータ、ヘッダー、クッキーなど)は、決して信用してはいけません。必ずデータの形式、長さ、範囲などを検証し、不正なデータは拒否または適切にサニタイズ(無害化)してください。SQLインジェクションやコマンドインジェクションを防ぐ基本です。
  • 出力のエスケープ (Output Encoding/Escaping): クライアントから受け取ったデータやデータベースから取得したデータをHTMLとしてブラウザに表示する際は、HTMLエスケープを必ず行ってください。これにより、XSS (Cross-Site Scripting) 攻撃を防ぐことができます。JSPでEL (${...}) やJSTLを使用する場合、デフォルトでエスケープされることが多いですが、明示的に指定したり、JSP以外の方法でHTMLを生成する場合は注意が必要です。
  • CSRF (Cross-Site Request Forgery) 対策: ユーザーの意図しないリクエストが実行されてしまう脆弱性です。POSTリクエストを受け付けるフォームには、予測不可能なトークン(CSRFトークン)を含めるなどの対策が必要です。Servletレベルで手動で実装することも可能ですが、Spring Securityなどのフレームワークを利用するのが一般的です。
  • 認証・認可 (Authentication & Authorization): 誰がアクセスしているか(認証)、そのユーザーは何ができるか(認可)を適切に管理する必要があります。ログイン機能の実装や、リソースへのアクセス制限を行います。web.xml で特定のURLパターンへのアクセスを制限する設定(<security-constraint>)も可能ですが、複雑な要件にはフレームワークが適しています。
  • HTTPSの使用: 機密情報をやり取りする際には、SSL/TLSを使って通信を暗号化するHTTPSを使用してください。これにより、通信経路での盗聴や改ざんを防ぎます。Servletコンテナ(Tomcatなど)でHTTPSを設定します。
  • エラーメッセージの表示: 開発時には詳細なエラー情報を表示するのは有用ですが、本番環境では詳細すぎるエラーメッセージ(スタックトレースなど)を表示しないようにしてください。攻撃者にサーバーの内部情報を与えてしまう可能性があります。一般的なエラーメッセージや、web.xml で設定したエラーページを表示するようにしてください。
  • セッションIDの管理: セッションIDは推測されにくいものである必要があり、クッキーで受け渡しする場合は HttpOnly フラグを立ててJavaScriptからのアクセスを防ぐように設定するのが望ましいです。

これらのセキュリティ対策は、Servlet単体で実装するのは大変な場合があります。Spring Securityのようなセキュリティフレームワークを利用することで、より堅牢なセキュリティ機能を効率的に実装できます。しかし、基礎としてServletレベルでどのようなリスクがあり、どのような対策が考えられるかを理解しておくことは重要です。

14. 最新のServlet仕様(Jakarta EE)について

Java EEは2018年にOracleからEclipse Foundationに移管され、「Jakarta EE」と名称が変更されました。これに伴い、パッケージ名が javax.* から jakarta.* に変更されています。

  • Java EE 8まで: パッケージ名は javax.servlet, javax.servlet.http など。
  • Jakarta EE 9以降: パッケージ名は jakarta.servlet, jakarta.servlet.http など。

Tomcatも、バージョン10以降がJakarta EEに対応し、パッケージ名が jakarta.* に変更されています。Tomcat 9以前はJava EEに対応しており、パッケージ名は javax.* です。

この記事のコード例では、主にJakarta EE (Tomcat 10以降) のパッケージ名 (jakarta.servlet.*) を使用しましたが、古いバージョンのTomcatやJava EE環境で開発する場合は、適宜 javax.* に読み替えてください。

Servlet仕様自体も、Jakarta EEとしてバージョンアップが続けられています。例えば、Servlet 3.0で導入されたアノテーション、Servlet 3.1での非同期処理の強化、Servlet 4.0でのHTTP/2サポートなど、機能が追加・改善されています。非同期処理 (@WebServlet(asyncSupported = true), request.startAsync()) は、Servletが時間のかかる処理を行っている間もスレッドを解放し、他のリクエストを処理できるようにするための重要な機能です。

初心者の方は、まずは基本的な同期処理を理解することから始めれば十分ですが、最新の仕様にも目を向けていくと、より効率的で高性能なWebアプリケーションを開発できるようになります。

15. まとめと次のステップ

この記事では、Servletの基礎から応用までを網羅的に解説しました。

  • Webアプリケーションの仕組み、HTTPとServletの役割を理解しました。
  • 開発環境を構築し、簡単な「Hello, World!」Servletを実行しました。
  • HttpServletRequestHttpServletResponse オブジェクトを使って、リクエストとレスポンスを操作する方法を学びました。
  • セッションとクッキーを使って、ステートレスなHTTPで状態を管理する方法を学びました。
  • RequestDispatcher を使って、Servletから別のリソース(特にJSP)に処理をフォワード/インクルードする方法を学びました。
  • エラーハンドリングの基本的な方法を知りました。
  • 共通処理を担うServletフィルタや、イベント処理を行うServletリスナーの仕組みを学びました。
  • MVCパターンにおけるServletとJSPの連携について解説しました。
  • アノテーションベースの設定と、最新のJakarta EEについて触れました。
  • Webアプリケーションのセキュリティに関する基本的な考慮事項を確認しました。

ServletはJavaによるWebアプリケーション開発の基盤となる技術です。Servletをしっかりと理解することは、その上に構築される様々なフレームワークを学ぶ上でも、非常に役立ちます。

Servletを学んだ次のステップとしては、以下のような学習が考えられます。

  • JSP (JavaServer Pages): Servletと組み合わせてView層を構築するための技術です。ELやJSTLをさらに深く学びましょう。
  • JDBC (Java Database Connectivity): Javaからデータベースに接続し、データを操作するためのAPIです。Webアプリケーションはデータベースと連携することが多いため必須の技術です。
  • JavaBeans/POJO: MVCパターンのModelとして使われるシンプルなJavaクラスです。
  • デザインパターン: MVCパターンだけでなく、DAO (Data Access Object)、Service Layerなどの設計パターンを学ぶことで、より構造化されたアプリケーションを開発できるようになります。
  • ログライブラリ: アプリケーションの動作状況やエラーを記録するためのライブラリ(Logback, SLF4jなど)の利用。
  • ビルドツール: MavenやGradleをさらに使いこなす。
  • Spring Framework: JavaEE/Jakarta EEの仕様をベースにした、より高機能で開発効率の高いフレームワークです。Servletの知識はSpring MVCなどを学ぶ上で大いに役立ちます。

Servletは、最初は少し低レベルで記述量が多いと感じるかもしれませんが、Webアプリケーションの基本的な動作原理を理解するための最良の入り口です。ぜひ、この記事で学んだことを元に、実際に手を動かして様々な機能を実装してみてください。

この徹底解説が、あなたのServlet学習の助けとなれば幸いです。頑張ってください!

付録:参考資料

  • Jakarta Servlet 仕様: Jakarta EE公式ウェブサイトから最新の仕様書(PDFなど)を入手できます。仕様は英語ですが、最も正確な情報源です。
  • Apache Tomcat 公式ドキュメント: Tomcatのバージョンに合わせた公式ドキュメントは、Tomcatの機能や設定について詳しく解説しています。
  • Java/Jakarta EE 関連書籍: Servletを含むJakarta EEの各技術について詳しく解説した書籍が多数出版されています。
  • オンラインチュートリアル/コース: 各種のプログラミング学習サイトで、ServletやWebアプリケーション開発に関するコースが提供されています。

継続的な学習と実践を通じて、JavaによるWebアプリケーション開発スキルを着実に身につけていきましょう。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール