👋

Servlet/JSPのスコープ[Java]

に公開

はじめに

こんにちは。
プログラミング初心者wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。

十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。

記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。

対象読者

  • Javaを勉強中の方
  • Servlet/JSPのスコープについて知りたい方

動作環境

  • MacBook(Intel CPU 搭載)
  • Oracle Java 21
  • eclipse 2023 (pleiades)

記事のテーマ

  • Servlet/JSPのスコープについて勉強しました。この記事では、リクエストスコープ・セッションスコープ・アプリケーションスコープについて説明します

目次

1. スコープとは
2. JavaBeans
3. リクエストスコープ
4. セッションスコープ
5. アプリケーションスコープ

1. スコープとは

Webアプリケーションでは、あるサーブレットクラスで生成したインスタンスを、別のサーブレットクラスやJSPファイルに渡したいという場合があります。
しかし、 それぞれのサーブレットクラスやJSPファイルは独立しているため、フォワード元のサーブレットクラスでインスタンスを生成しても、そのインスタンスを直接フォワード先に渡すことができません。

フォワード先にインスタンスを渡したい場合は、「スコープ」と呼ばれる領域にインスタンスを一時保存します。サーブレットクラスとJSPファイルは、任意のインスタンスをスコープに保存したり、 保存したインスタンスを取得したりできます。
つまり、スコープを経由することで、フォワード元とフォワード先との間でインスタンスを共有できるようになるのです。

スコープの利用には、注意点が2点あります。

1つ目は、いくつかの形式ルールを守っているインスタンスしか、スコープに保存できないという点です。ルールを守っていないインスタンスは、スコープを利用できません。

2つ目は、スコープの種類によって利用範囲が違う点です。
スコープは4つの種類があり、種類ごとに有効範囲の広さが異なります。有効範囲の広さは、「ページ」が最も狭く、「リクエスト」、「セッション」、「アプリケーション」の順に広くなります

種類 スコープの有効範囲
ページ 同じページ内
リクエスト 同じリクエスト内
セッション 同じセッション内
アプリケーション 同じWebアプリケーション内

この記事では、まずインスタンスの形式ルールの説明から行います。
次にスコープの4つのうち、リクエストスコープ、セッションスコープ、アプリケーションスコープの詳細について説明します。
使用頻度の少ないページスコープについては、この記事で紹介しません。

2. JavaBeans

JavaBeansとは、部品として再利用しやすくするためのルール、またはそのルールに従って作成されたJavaクラスやインスタンスを指します。
Webアプリケーションに限らず、幅広い分野で利用されている形式です。

スコープに保存できるのは、原則としてJavaBeansのルールに則ったインスタンスです。

JavaBeansのルールとは、以下の4つです。

  1. 直列化が可能である(java.io.Serializableを実装している)
  2. パッケージに所属し、publicである
  3. publicで引数のないコンストラクタを持つ
  4. フィールドはカプセル化する

厳密には、①の直列化のみがスコープ保存の必須条件であり、他の3点はJavaBeansとして推奨される一般的なルールです。

これらすべてのルールを守ったJavaBeansのコード例は、以下の通りです。

package model; // ②パッケージに所属している

import java.io.Serializable;

// ②publicである
public class User implements Serializable { //①java.io.Serializableを実装している

    // ④ フィールドはprivateで宣言し、カプセル化する
    private String userName;
    private int id;

    // ③ publicで引数のないコンストラクタを持つ
    public User() {}

    public User(String userName, int id) {
        this.userName = userName;
        this.id = id;
    }

    // ④ 各プロパティに対するGetter/Setterメソッドを作成し、カプセル化する
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
}

3. リクエストスコープ

3-1. リクエストスコープとは

「リクエストスコープ」とは、 リクエストごとに生成されるスコープです。
このスコープに保存したインスタンスは、レスポンスが返されるまで利用でき、フォワード元とフォワード先でインスタンスの共有が可能になります。

リクエストスコープの正体は、HttpServletRequestインスタンスです。
レスポンスを返すとHttpServletRequestインスタンスは消滅し、リクエストスコープに保存していたインスタンスも消えてしまいます。
フォワード元とフォワード先間でのインスタンス共有は可能です。
しかし、リダイレクトは転送前に一度レスポンスを返して、新しいリクエストを送信してしまうため、リクエストスコープは消滅してしまいます。
そのため、リダイレクト元でリクエストスコープに保存したインスタンスは、リダイレクト先で取得できません。

3-2. リクエストスコープのメソッド

リクエストスコープを利用する場合はHttpServletRequestインターフェースのメソッドを呼び出します。
利用手順は、以下の通りです。

  1. リクエストスコープに保存するインスタンスを生成

  2. HttpServletRequestインターフェースのsetAttribute()メソッドで、スコープにインスタンスを保存

メソッド 振る舞い
void setAttribute(String name, Object object) 第1引数で指定した名前の属性に、第2引数のデータを設定します

第2引数の型はObject型であるため、あらゆるクラスのインスタンスが格納できます。
また、すでに同じ属性名のインスタンスがスコープに保存されている場合は、データが上書きされます。

  1. HttpServletRequestインターフェースのgetAttribute()メソッドで、スコープからインスタンスを取得
メソッド 振る舞い
Object getAttribute(String name) 引数で指定した名前の属性から、データを取得します

getAttribute()メソッドの戻り値は、Object型であるため、インスタンスを取得後、キャストして元の型に戻す必要があります。
また、指定した属性名が存在しない場合はnullを返します。

3-3. リクエストスコープのコード例

では実際の処理をコード例で見てみましょう。

//スコープを保存するサーブレットクラス
package servlet; 

//JavaBeansのUserクラスをインポートしておく
import model.User; 
import java.io.IOException;
import javax.servlet.RequestDispatcher;
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("/User")
public class UserServletByPost extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        //エンコーディング処理
        request.setCharacterEncoding("UTF-8");

        // 1. 保存するインスタンスを作成
        User user = new User("Yamada", 1);
        
        // 2. リクエストスコープにインスタンスを保存
        request.setAttribute("user", user);
        
        // JSPへフォワード
        request.getRequestDispatcher("/user.jsp").forward(request, response);
    }
}
<!-- スコープを受け取るJSPファイル -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 取得するインスタンスのクラスをインポートしておく -->
<%@ page import="model.User" %>
<% 
//スクリプトレットでリクエストスコープから保存されたインスタンスを取得 
User user = (User) request.getAttribute("user");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>リクエストスコープ</title>
</head>
<body>    
<p>
ユーザー名:<%= user.getUserName()%><br>
ID:<%= user.getId()%><br>
</body>
</html>

4. セッションスコープ

4-1. セッションスコープとは

リクエストを跨いで、インスタンスを利用するには、リクエストスコープではなく、「セッションスコープ」を利用します。

セッションスコープは、同一ユーザーによる一連のリクエスト(ログインからログアウトまでなど)の間、データを保持するためのスコープです。
レスポンス後も、インスタンスを保存できるので、複数リクエストを跨いでインスタンスを共有することができます。

また、セッションスコープは各ユーザー固有のもので、同じ Webサイトを見ている別の人には共有されません。
そのため、セッションスコープはユーザー情報の管理やショッピングカートの管理などに用いられます。

セッションスコープの正体は、HttpSessionインスタンスです。
ユーザーが初めてWebアプリケーションにアクセスすると、アプリケーションサーバはユーザー固有の「セッションID」を生成し、HttpSessionインスタンスを作成します。
セッションIDは主に「クッキー」として、ブラウザに送信されて設定されます。
セッションIDを設定されたブラウザは、以後サーバへのリクエストのたびに「クッキー」を付加します。
サーバは、「クッキー」と対応するHttpSessionオブジェクトを検索し、そのリクエストとセッションを紐付けることで、複数リクエストにまたがってインスタンス情報を共有することができるのです。

4-2. セッションスコープの破棄とインスタンスの削除

セッションスコープやセッションスコープに保存されたインスタンス情報は、他のインスタンスと違い、使用されない状態になっても、すぐガベージコレクションの対象になりません。
セッションタイムアウト(後述)するまでサーバに保持され続けるため、アクティブなセッションが多いとメモリを圧迫する可能性があります。
そのため、セッションスコープを利用する場合は、保存されたインスタンスやセッションスコープそのものを、いつ削除するかを開発者が管理する必要があります。

自動的にセッションスコープが削除されるのは、以下の状況です。

状況 説明
アプリケーションサーバが停止したタイミング アプリケーションサーバが停止すれば、管理下のHttpSessionインスタンス(セッションスコープ)は破棄されます
ブラウザが閉じられたタイミング ブラウザが閉じられるとクッキーが失われるため、ユーザーはセッションに戻れなくなります(ただし、サーバ側のHttpSessionインスタンス(セッションスコープ)は後述するセッションタイムアウトまで残る場合があります)
セッションタイムアウト セッションタイムアウトになると、HttpSessionインスタンスおよびその中の保存データは無効化され、ガベージコレクションの対象となります。Apache Tomcatのデフォルトのセッションタイムアウトは30分間ですが、開発者が任意で設定することもできます

セッションスコープやスコープに保存されたインスタンスを任意のタイミングで、削除するには後述するremoveAttribute()メソッドやinvalidate()メソッドを呼び出します。

4-3. セッションスコープのメソッド

セッションスコープを利用する場合はHttpSessionインターフェースのメソッドを呼び出します。
利用手順は、以下の通りです。

  1. セッションスコープに保存するインスタンスを生成

  2. HttpSessionのインスタンスを生成

HttpSessionのインスタンス(セッションスコープ)は、HttpServletRequestインターフェースの getSession()メソッドを利用します。

メソッド 振る舞い
HttpSession getSession() このリクエストに関連付けられている現在のセッションを返すか、リクエストにセッションがない場合は作成します。

getSession()は、引数ありのオーバーロードもあります

メソッド 振る舞い
HttpSession getSession(boolean create) このリクエストに関連付けられている現在のHttpSessionインスタンスを返す。現在のセッションがなく、引数trueの場合、新しいセッションを返します。現在のセッションがなく、引数がfalseの場合、nullを返します
  1. HttpSessionインターフェースのsetAttribute()メソッドで、スコープにインスタンスを保存
メソッド 振る舞い
void setAttribute(String name, Object object) 第1引数で指定した属性名に、第2引数のデータを設定します

第2引数の型はObject型であるため、あらゆるクラスのインスタンスが格納できます。
また、すでに同じ属性名のインスタンスがスコープに保存されている場合は、データが上書きされます。

  1. HttpSessionインターフェースのgetAttribute()メソッドで、スコープからインスタンスを取得
メソッド 振る舞い
Object getAttribute(String name) 引数で指定した名前の属性から、データを取得します

getAttribute()メソッドの戻り値は、Object型であるため、インスタンスを取得後、キャストして元の型に戻す必要があります。
また、指定した属性名が存在しない場合はnullを返します

  1. セッションスコープからインスタンスを削除
メソッド 振る舞い
void removeAttribute(String name) 引数で指定された属性名のインスタンスを削除します

セッションスコープに保存されているインスタンスへの参照が削除されるため、インスタンス情報はガベージコレクションの対象になります。
セッションスコープ(HttpSessionインスタンス)そのものは破棄されません。

  1. セッションスコープを破棄
メソッド 振る舞い
void invalidate() セッションスコープを破棄します

セッションスコープが破棄されます。保存されているインスタンス情報も同時に消滅します。

4-4. セッションスコープのコード例

では実際の処理をコード例で見てみましょう。

//スコープを保存するサーブレットクラスの一部

request.setCharacterEncoding("UTF-8")

// 1. 保存するインスタンスを作成
 User user = new User("Yamada", 1);

// 2. セッションスコープを生成
// // true: セッションが存在しない場合は新しく作成
// false: セッションが存在しない場合は null を返す
HttpSession session = request.getSession(true);

// 3. セッションスコープにインスタンスを保存
session.setAttribute("user", user);

// 4. JSPへフォワード
// リクエストが完了してもセッションデータは保持されます
request.getRequestDispatcher("/user.jsp").forward(request, response);
//スコープを取得するJSPファイル
<!-- スコープを受け取るJSPファイル -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- 取得するインスタンスのクラスをインポートしておく -->
<%@ page import="model.User" %>
<%
//スクリプトレットでリクエストスコープから保存されたインスタンスを取得 
User user = (User) session.getAttribute("user");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>セッションスコープの表示</title>
</head>
<body>
  <p>
  ユーザー名:<%= user.getUserName()%><br>
  ID:<%= user.getId()%><br>
</body>
</html>

5. アプリケーションスコープ

5-1. アプリケーションスコープとは

アプリケーションスコープは、1つのアプリケーションにつき1つだけ作成されるスコープです。
そのため、保存したインスタンスは、Webアプリケーションが終了されるまでの間、アプリケーションを利用するすべてのユーザーが利用でき、アプリケーション内のすべてのサーブレットクラスとJSPファイルから利用できます。
「いいね」の数など、すべてのユーザーで共有したい情報などを管理する際に用いられます。

アプリケーションスコープの正体は、ServletContextインスタンスです。

5-2. Webアプリケーションが終了するタイミングとは

アプリケーションスコープは、Webアプリケーションが終了されるまでの間、インスタンスを保存し続けます。
では、Webアプリケーションが終了する状況とはどのような時でしょうか。
具体的には、3つのパターンがあります。

状況 説明
サーバの起動と停止 アプリケーションサーバの起動/停止に伴い、そのサーバで動くWebアプリケーションも一緒に開始/終了します
オートリロード時 Webアプリケーションサーバには、一度実行したことのあるサーブレットクラスのコードが修正されると、そのサーブレットクラスのWebアプリケーションを再読み込みする機能があります。再読み込みされると、Webアプリケーションは自動的に終了/開始します
管理ツールによる開始と終了 アプリケーションサーバは、Webアプリケーションを管理するツールを提供しています。この管理ツールを使うと、特定のWebアプリケーションを開始/終了させることができます

上記のように、サーバの停止や再起動を行なった場合に、Webアプリケーションが終了し、アプリケーションスコープと保存したインスタンスは消滅します。

5-3. アプリケーションスコープのメソッド

アプリケーションスコープを利用する場合はServletContextインターフェースのメソッドを呼び出します。
利用手順は、以下の通りです。

  1. アプリケーションスコープに保存するインスタンスを生成

  2. ServletContextのインスタンスを生成

ServletContextのインスタンス(アプリケーションスコープ)は、HttpServletインターフェースの getServletContext()メソッドを利用します。

メソッド 振る舞い
ServletContext getServletContext() 呼び出し元が実行されている ServletContext への参照を返します
  1. ServletContextインターフェースのsetAttribute()メソッドで、スコープにインスタンスを保存
メソッド 振る舞い
void setAttribute(String name, Object object) 第1引数で指定した属性名に、第2引数のデータを設定します

第2引数の型はObject型であるため、あらゆるクラスのインスタンスが格納できます。
また、すでに同じ属性名のインスタンスがスコープに保存されている場合は、データが上書きされます。

  1. ServletContextインターフェースのgetAttribute()メソッドで、スコープからインスタンスを取得
メソッド 振る舞い
Object getAttribute(String name) 引数で指定した名前の属性から、データを取得します

getAttribute()メソッドの戻り値は、Object型であるため、インスタンスを取得後、キャストして元の型に戻す必要があります。
また、指定した属性名が存在しない場合はnullを返します

  1. アプリケーションスコープからインスタンスを削除
メソッド 振る舞い
void removeAttribute(String name) 引数で指定された属性名のインスタンスを削除します

アプリケーションスコープに保存されているインスタンスへの参照を削除します。

5-4. アプリケーションスコープのコード例

では実際の処理をコード例で見てみましょう。

//スコープを保存するサーブレットクラスの一部
@Override
public void init() throws ServletException {

    // 1. 保存するインスタンスを作成
    User adminUser = new User("Yamada", 1);

    // 2. アプリケーションスコープを生成
    ServletContext context = getServletContext();
    
    // 3. アプリケーションスコープにインスタンスを保存 
    // このデータは、アプリケーションが停止するまで保持される
    context.setAttribute("adminUser", adminUser);
}
//スコープを取得するJSPファイル
<!-- スコープを受け取るJSPファイル -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="model.User" %>
<%
//スクリプトレットでアプリケーションスコープから保存されたインスタンスを取得 
User user = (User) application.getAttribute("adminUser");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>アプリケーションスコープの表示</title>
</head>
<body>
  <p>
  ユーザー名:<%= user.getUserName()%><br>
  ID:<%= user.getId()%><br>
</body>
</html>

5-5. 同時アクセスによる不整合

アプリケーションスコープのデータは全ユーザーで共有されるため、複数のユーザーが同時に共有データを書き換えると、意図しないデータの上書きや読み込み(競合状態)が生じ、データが不整合になる可能性があります。

そのため、アプリケーションスコープの共有データを更新する際には、スレッドセーフを確保するために「同期化」の仕組みが必要です。
Javaでは、「synchronized」キーワードを使って、クリティカルな処理を一度に一つのスレッドしか実行できないように制御します。

ServletContext context = getServletContext();
// このブロック内の処理は同時に一つのスレッドしか実行できない
synchronized (context) { 
    Integer counter = (Integer) context.getAttribute("accessCount");
    if (counter == null) {
        counter = 0;
    }
    context.setAttribute("accessCount", counter + 1);
}

まとめ

  • スコープとは、フォワード元とフォワード先などの間でインスタンスを共有するために、インスタンスを一時保存する領域です。原則としてjava.io.Serializableを実装したJavaBeans形式のインスタンスが格納されます

  • リクエストスコープは、1つのリクエスト(フォワードを含む)内でのみ有効で、HttpServletRequestインスタンスに対応します。リダイレクトを跨ぐとインスタンスは消滅します

  • セッションスコープは、同一ユーザーの一連のリクエスト(セッション)の間有効で、HttpSessionインスタンスに対応します。ユーザー固有のデータを保持し、ログイン情報などに利用されます

  • アプリケーションスコープは、Webアプリケーション全体で共有され、アプリケーションが終了するまでインスタンスを保持し続けます。ServletContextインスタンスに対応し、全ユーザーで共有されるデータ(例:アクセス数)の管理に用いられますが、スレッドセーフの考慮が必要です


記事は以上です。
最後までお読みいただき、ありがとうございました。

参考情報一覧

この記事は以下の情報を参考にして執筆しました。

GitHubで編集を提案

Discussion