サーブレット&JSPの基本|Webアプリのイベントとリスナー

リクエストを待たずに自動で動く。リスナーでWebアプリケーションの開始や終了を検知しよう

前回は、サーブレットクラスのinitメソッドとdestroyメソッドについて学習しました。

initメソッドを使うと、サーブレットインスタンスが作成された直後に初期化処理を実行できます。destroyメソッドを使うと、サーブレットインスタンスが破棄される直前に後片付けの処理を実行できます。

ただし、initメソッドには注意点がありました。

initメソッドは、そのサーブレットがインスタンス化されたときに実行されます。通常は、そのサーブレットに初めてリクエストが届いたときです。

つまり、Webアプリケーション全体で必要な初期化処理を、特定のサーブレットのinitメソッドに書いてしまうと、そのサーブレットがまだリクエストされていない場合、初期化処理もまだ実行されていないことになります。

たとえば、ServletAのinitメソッドでアプリケーションスコープに共通データを保存している場合を考えます。先にServletBがリクエストされると、ServletAのinitメソッドはまだ動いていないため、ServletBは共通データを取得できない可能性があります。

このように、サーブレットのinitメソッドは、そのサーブレット自身の初期化には使いやすい一方で、Webアプリケーション全体の開始時に必ず実行したい処理には向かない場合があります。

そこで登場するのが、リスナーです。

リスナーは、Webアプリケーションで特定のイベントが発生したときに、自動的に実行される特殊なクラスです。

リクエストを受けて動くサーブレットとは違い、リスナーはWebアプリケーションの開始、終了、セッションの作成、リクエストの発生など、決まった出来事を検知して動きます。

この記事では、Webアプリケーションのイベントとリスナーの基本を確認し、Webアプリケーション開始時にアプリケーションスコープへ初期データを保存するリスナーを作成します。

リスナーとは

リスナーとは、Webアプリケーション内で特定のイベントが発生したときに、自動的に処理を実行するクラスです。

ここでいうイベントとは、Webアプリケーション内で起こる出来事のことです。

たとえば、次のような出来事がイベントです。

Webアプリケーションで発生するイベントの例

イベント内容
Webアプリケーションが開始するサーバ起動やWebアプリケーションの読み込みによって開始される
Webアプリケーションが終了するサーバ停止やWebアプリケーションの停止によって終了する
セッションスコープが作成される新しい利用者のセッションが作られる
セッションスコープが破棄されるログアウトやタイムアウトなどでセッションが終わる
リクエストが発生するブラウザからリクエストが送られる
レスポンスが完了するリクエストに対する処理が終わる

サーブレットは、ブラウザからURLに対してリクエストされたときに動きます。

一方、リスナーは、特定のイベントが発生したときに動きます。

この違いを理解しておくと、リスナーの役割が見えてきます。

サーブレットとリスナーの違い

種類実行されるきっかけ主な用途
サーブレットブラウザからのリクエスト画面表示、フォーム送信、処理の受付
リスナーWebアプリケーション内のイベント開始時の初期化、終了時の後片付け、スコープの監視

リスナーを使うと、リクエストされる順番に左右されず、Webアプリケーションの状態に合わせて処理を実行できます。

図1:イベントを検知して動作するリスナー

この図から分かること

リスナーは、Webアプリケーション内で発生するイベントをきっかけに動作します。

サーブレットは、ブラウザからのリクエストによってdoGetやdoPostが実行されます。一方、リスナーは、Webアプリケーションが開始された、セッションが作成された、リクエストが発生した、といった出来事を検知してメソッドを実行します。

このため、リスナーを使うと、特定のサーブレットが先にリクエストされるかどうかに左右されず、Webアプリケーションの状態に合わせた処理を書けます。

リスナーを使うと何ができるのか

リスナーを使うと、リクエストとは別のタイミングで処理を行えます。

たとえば、Webアプリケーションが開始されたときに、アプリケーションスコープへ初期データを保存できます。

これは、特定のサーブレットのinitメソッドで初期化するよりも、Webアプリケーション全体の準備処理として分かりやすい方法です。

リスナーが向いている処理

処理理由
Webアプリケーション開始時の初期化リクエスト順に左右されずに実行できる
Webアプリケーション終了時の後片付け終了イベントに合わせて処理できる
セッション作成数の確認セッションが作られたタイミングを検知できる
リクエストの開始や終了の監視リクエストごとの処理を追跡できる
スコープ属性の追加や削除の監視属性が保存、上書き、削除されたことを検知できる

リスナーは、画面を表示するためのクラスではありません。

利用者から直接リクエストされるクラスでもありません。

Webアプリケーションの裏側で、特定のイベントに反応して処理を行う補助的なクラスだと考えると分かりやすいです。

リスナーはインタフェースを実装して作成する

リスナーは、イベントの種類に応じて用意されているリスナーインタフェースを実装して作成します。

どのイベントに反応したいかによって、実装するインタフェースが変わります。

たとえば、Webアプリケーションの開始や終了に反応したい場合は、ServletContextListenerインタフェースを実装します。

セッションスコープの作成や破棄に反応したい場合は、HttpSessionListenerインタフェースを実装します。

リクエストの発生やレスポンスの完了に反応したい場合は、ServletRequestListenerインタフェースを実装します。

イベントと対応するリスナーインタフェース

イベントリスナーインタフェース
Webアプリケーションが開始または終了するServletContextListener
アプリケーションスコープにインスタンスを保存、上書き保存、またはスコープから削除するServletContextAttributeListener
セッションスコープを作成または破棄するHttpSessionListener
セッションスコープにインスタンスを保存、上書き保存、またはスコープから削除するHttpSessionAttributeListener
セッションスコープが待避、または回復するHttpSessionActivationListener
このインタフェースを実装したクラスのインスタンスをセッションスコープに保存、またはスコープから削除するHttpSessionBindingListener
リクエストが発生する、またはレスポンスが完了するServletRequestListener
リクエストスコープにインスタンスを保存、上書き保存、またはスコープから削除するServletRequestAttributeListener

このように、リスナーインタフェースはイベントごとに分かれています。

どのイベントを扱いたいのかを決めてから、対応するインタフェースを選びます。

Webアプリケーション開始時に動くリスナー

今回は、Webアプリケーションが開始されたときに動くリスナーを作成します。

Webアプリケーションの開始に対応するには、ServletContextListenerインタフェースを実装します。

ServletContextListenerには、主に次の2つのメソッドがあります。

ServletContextListenerインタフェースのメソッド

メソッド実行のきっかけとなるイベント
contextInitializedWebアプリケーションが開始する
contextDestroyedWebアプリケーションが終了する

contextInitializedメソッドは、Webアプリケーションが開始されたときに実行されます。

contextDestroyedメソッドは、Webアプリケーションが終了するときに実行されます。

今回のサンプルでは、Webアプリケーション開始時にアクセス確認回数を0で用意し、アプリケーションスコープに保存します。

図2:Webアプリケーション開始時にリスナーで初期化する

この図から分かること

Webアプリケーションが開始されると、ServletContextListenerを実装したリスナーが動作します。

contextInitializedメソッドでは、ServletContextEventからServletContextを取得できます。

ServletContextはアプリケーションスコープの正体です。そのため、context.setAttributeを使うことで、Webアプリケーション開始時にアプリケーションスコープへ初期データを保存できます。

この方法なら、特定のサーブレットが最初にリクエストされるかどうかに左右されず、Webアプリケーション開始時に処理を実行できます。

WebListenerアノテーションを付ける

リスナーとして動作させるクラスには、WebListenerアノテーションを付けます。

WebListenerアノテーションを付けることで、アプリケーションサーバがそのクラスをリスナーとして認識します。

WebListenerアノテーションの役割

項目内容
アノテーション@WebListener
役割クラスをリスナーとして登録する
必要なインポートjakarta.servlet.annotation.WebListener
実行タイミング対応するイベントが発生したとき

WebListenerアノテーションを付けたリスナーは、Webアプリケーション開始時など、対応するイベントが発生したときにサーブレットコンテナによって扱われます。

リスナーでアプリケーションスコープを初期化する

ここでは、Webアプリケーション開始時にアクセス確認回数を0としてアプリケーションスコープへ保存するリスナーを作成します。

前回のinitメソッドでは、特定のサーブレットが初めてリクエストされたときに初期化処理が実行されました。

今回のリスナーでは、Webアプリケーションが開始されたタイミングで初期化処理を実行します。

これにより、リクエスト順に左右されず、アプリケーションスコープへ初期データを用意できます。

Webアプリケーションの開始に対応するリスナーの例

ファイル名: AppStartListener.java

package listener;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class AppStartListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {

        ServletContext context = sce.getServletContext();

        Integer accessCount = 0;

        context.setAttribute("accessCount", accessCount);

        System.out.println("AppStartListenerのcontextInitializedが実行されました");
    }

    public void contextDestroyed(ServletContextEvent sce) {

        System.out.println("AppStartListenerのcontextDestroyedが実行されました");
    }
}

AppStartListener.javaは、listenerパッケージに作成します。

このクラスはServletContextListenerインタフェースを実装しています。

Webアプリケーションが開始されると、contextInitializedメソッドが実行されます。ここでは、Integer型のaccessCountを0で作成し、アプリケーションスコープに保存しています。

Webアプリケーションが終了すると、contextDestroyedメソッドが実行されます。ここでは、動作確認のためにコンソールへメッセージを出力しています。

AppStartListener.javaのポイント

AppStartListener.javaでは、Webアプリケーションの開始と終了に対応する処理を記述しています。

AppStartListener.javaで行っていること

処理内容
WebListenerを付けるリスナーとして登録する
ServletContextListenerを実装するWebアプリケーション開始と終了のイベントに対応する
contextInitializedを実装するWebアプリケーション開始時の処理を書く
contextDestroyedを実装するWebアプリケーション終了時の処理を書く
ServletContextを取得するアプリケーションスコープを操作する
accessCountを保存する初期値0をアプリケーションスコープに保存する

contextInitializedメソッドでは、引数としてServletContextEventが渡されます。

このServletContextEventからgetServletContextを実行すると、ServletContextを取得できます。

ServletContextはアプリケーションスコープの正体なので、setAttributeを使ってaccessCountを保存できます。

contextInitializedで初期化する

contextInitializedメソッドは、Webアプリケーションが開始されたときに実行されます。

今回の例では、次の流れで初期化処理を行っています。

contextInitializedの流れ

順番処理内容
1ServletContextを取得するsce.getServletContextで取得する
2Integerを作成するaccessCountの初期値として0を用意する
3アプリケーションスコープに保存するcontext.setAttributeで保存する
4コンソールへ出力する実行されたことを確認する

この処理により、Webアプリケーションが開始された時点で、アプリケーションスコープにaccessCountが用意されます。

特定のサーブレットが先にリクエストされる必要はありません。

contextDestroyedで終了処理を書く

contextDestroyedメソッドは、Webアプリケーションが終了するときに実行されます。

今回の例では、動作確認のためにSystem.out.printlnでメッセージを出力しています。

Webアプリケーション終了時に後片付けが必要な場合は、contextDestroyedメソッドに処理を書きます。

contextDestroyedに書ける処理の例

処理内容
終了ログの出力Webアプリケーション終了をコンソールへ表示する
共有リソースの後片付けWebアプリケーション全体で使っていたリソースを整理する
一時データの整理終了前に必要な整理処理を行う

今回は、Webアプリケーション開始時の初期化が中心なので、contextDestroyedでは簡単なコンソール出力だけにしています。

リスナーを作成したあとの動作確認

AppStartListener.javaを作成したら、サーバを再起動して動作を確認します。

リスナーは、通常のサーブレットのようにブラウザから直接リクエストして実行するものではありません。

Webアプリケーション開始イベントが発生したときに、自動的に実行されます。

動作確認の流れ

手順内容
1AppStartListener.javaをlistenerパッケージに作成する
2動的WebプロジェクトstudyをTomcat11_Java25に追加する
3サーバを再起動する
4Webアプリケーションstudyが開始される
5contextInitializedが自動的に実行される
6Eclipseのコンソールビューに実行メッセージが表示される
7Webアプリケーション終了時にcontextDestroyedのメッセージも確認する

この確認で大切なのは、リスナーがURLへのリクエストではなく、Webアプリケーションの開始イベントで実行されることです。

ブラウザでAppStartListener.javaを直接開くわけではありません。

initによる初期化との違い

initメソッドでも、初期化処理を書くことはできます。

しかし、initメソッドはサーブレットごとに実行されます。

そのため、そのサーブレットがまだリクエストされていなければ、initメソッドもまだ実行されていません。

一方、ServletContextListenerのcontextInitializedは、Webアプリケーション開始時に実行されます。

initとリスナーの違い

項目initメソッドリスナーのcontextInitialized
実行される単位サーブレットごとWebアプリケーションごと
実行されるきっかけサーブレットインスタンスの作成Webアプリケーションの開始
リクエスト順の影響受ける場合がある受けにくい
向いている処理そのサーブレット自身の初期化Webアプリケーション全体の初期化

この違いにより、Webアプリケーション全体で使うデータを初期化したい場合は、リスナーのほうが適しています。

特定のサーブレットのinitに初期化処理を書くと、そのサーブレットが最初に呼ばれる前提になってしまうことがあります。

図3:initとリスナーによる初期化の違い

この図から分かること

initメソッドは、対象のサーブレットがインスタンス化されたときに実行されます。

そのため、ServletAのinitでアプリケーションスコープに初期データを保存している場合、ServletAがまだリクエストされていなければ、その初期化処理は実行されていません。

一方、ServletContextListenerのcontextInitializedは、Webアプリケーション開始時に実行されます。

そのため、ServletAやServletBのリクエスト順に左右されず、Webアプリケーション全体で使う初期データを用意できます。

CounterServlet側のinit処理は不要になる

Webアプリケーション開始時にリスナーで初期データを保存できるようになると、同じ初期化処理をサーブレットのinitメソッドに書く必要はありません。

たとえば、アクセス回数を表すaccessCountをリスナーでアプリケーションスコープに保存するなら、アクセス回数を表示するサーブレット側では、その値を取得して利用すればよくなります。

役割分担の考え方

クラス役割
AppStartListener.javaWebアプリケーション開始時にaccessCountを初期化する
アクセス回数を表示するサーブレットアプリケーションスコープからaccessCountを取得して利用する

このように、初期化処理をリスナーに分けると、サーブレットはリクエスト処理に集中しやすくなります。

また、Webアプリケーション開始時に初期化が完了しているため、リクエストされる順番を気にしなくて済みます。

リスナーの作り方

リスナーは、決まった手順で作成できます。

難しそうに見えますが、作り方の流れはとても整理されています。

リスナー作成の手順

手順内容
1WebListenerアノテーションを付ける
2対応したいイベントのリスナーインタフェースを実装する
3リスナーインタフェースのメソッドを実装する
4メソッド内に実行したい処理を書く

たとえば、Webアプリケーション開始時のイベントに対応する場合は、ServletContextListenerを実装します。

そして、Webアプリケーション開始時に実行したい処理をcontextInitializedに書きます。

Webアプリケーション終了時に実行したい処理がある場合は、contextDestroyedに書きます。

Eclipseでリスナーを作成する場合

Eclipseでは、リスナーを作成するための画面を使うこともできます。

その場合、WebListenerアノテーションの付与、リスナーインタフェースの実装、メソッドのひな形作成などをEclipseが補助してくれます。

Eclipseで補助される内容

作業Eclipseの補助
WebListenerアノテーションの付与自動で付けられる
リスナーインタフェースの実装選択したイベントに応じて指定される
メソッドの作成必要なメソッドのひな形が作られる
import文の追加必要なクラスを自動で追加できる

Eclipseの機能を使うと、メソッド名やimport文の書き間違いを減らせます。

ただし、どのイベントに対して、どのリスナーインタフェースを使うのかは理解しておく必要があります。

リスナーの作成

動的Webプロジェクトを選択して右クリック→「新規」→「クラス」をクリックします。

リスナーが所属するパッケージとクラス名を指定し、「完了」をクリックします。

作成したクラスに変更を加えます。

リスナーを使うときに意識したいこと

リスナーは、リクエスト処理とは違うタイミングで動くクラスです。

そのため、サーブレットと同じ感覚で直接URLを指定して実行しようとすると、動作のイメージがずれてしまいます。

意識したいポイント

ポイント内容
リスナーはURLで直接呼び出さないイベントが発生したときに自動で動く
イベントに合わせてインタフェースを選ぶ開始、終了、セッション、リクエストなどで異なる
WebListenerを付けるリスナーとして登録する
アプリケーションスコープの初期化に使えるWebアプリケーション開始時に実行できる
リクエスト順に左右されにくい特定のサーブレットの初回リクエストを待たなくてよい

リスナーを理解すると、Webアプリケーション全体の開始や終了、セッションやリクエストの発生など、サーブレットだけでは扱いにくいタイミングの処理を書けるようになります。

特に、Webアプリケーション全体で使う初期データを用意したい場面では、ServletContextListenerが役立ちます。