【Java Web入門 #5】オブジェクトの保持とスコープ|Webアプリケーション内のデータ有効範囲
2025.01.10
前回の記事では、サーブレットからJSPにデータを連携して表示させる方法について駆け足で説明しました。このようにWebアプリケーションの中で、サーブレット同士や、サーブレットとJSPの間でデータを連携する場合、扱いを誤ってしまうと必要な情報が取得できなかったり、Webアプリケーションの性能が低下してしまったりする場合があります。
当記事では、Webアプリケーションの中でデータを保持するための方法や、保持すべき場所について説明します。
◆Java Web入門の過去記事はこちら
◆Java入門 記事一覧はこちら
目次
Webアプリケーション内のスコープ
Webアプリケーションでは、ユーザーが行う一連の処理で利用するために、ユーザーが送信した情報(パラメーター)やアプリケーション側の処理結果などのデータを、必要に応じて一時的に保持しておく必要があります。
この一時保存するデータの有効範囲(どこまで、いつまで保持しておくか)のことを、Javaの変数の有効範囲と同様にスコープと呼びます。JavaのWebアプリケーションでは、基本的にリクエスト、セッション、アプリケーションの有効範囲の異なる3種類のスコープを利用し、データとしてのJavaのオブジェクトを保持させます。
スコープが複数必要な理由
例えばショッピングサイトであれば、カートへの商品の投入や、カートの中の商品を後から確認したり、発送先を入力して注文を完了するなど、様々な処理を行う必要があります。
このように、Webアプリケーションでは、1画面で処理が完了する機能や、複数画面にわたって処理を完了させる機能があります。また利用するデータについても、1処理が終われば必要がなくなるものがあれば、一連の機能で何度も利用される情報もあります。
扱う情報に応じて適したスコープにデータ(オブジェクト)を保持することで、必要な情報を必要な期間だけ利用できるようにするのとともに、メモリなどサーバーのリソースを最適に利用することができ、Webアプリケーションのパフォーマンス向上にもつなげることができます。逆に言えば、不要な情報をいつまでも保持し続けることで、サーバーのメモリ不足によるシステム停止などが引き起こされることもあり得ます。
また、利用者の入力したパスワードなど、不要となった情報はすぐにサーバー上から破棄することで、セキュリティの向上にもつながります。
リクエストスコープ(HttpServletRequest)
リクエストスコープは、その名の通りWebブラウザやアプリなどから送信された1つのリクエスト(要求)に対し、レスポンス(応答)を返すまでが有効範囲となるスコープです。
例えば、ショッピングサイトで商品をカートに追加する際のパラメーター(例:商品ID)など、後続の処理で利用されないデータはリクエストスコープでの保持となります。
他にも、ログイン画面に入力した認証情報(例:ログインIDとパスワード)など、処理が終われば保持すべきではないデータについても、リクエストスコープの中での保持に留めるようにします。
また、エラーがあった場合にJSPと連携させて画面に表示させるエラーメッセージなども、リクエストスコープに保持するのが良いでしょう。
リクエストスコープにオブジェクトを保持させる場合は、HttpServletRequestインターフェイスを利用します。
Jakarta EE Platform API v10.0.0 - HttpServletRequest
セッションスコープ(HttpSession)
セッションスコープは、一定の期間、Webブラウザーやアプリとやり取りを続けるための情報を保持するためのスコープです。例えば、ログイン後に表示されるユーザー情報や、カート内の商品などの情報を保持します。
セッションスコープの有効期限は、例えば「最終アクセスから30分」のように、Tomcatなどのサーブレットの設定や、内部のプログラムによりWebアプリケーション側で指定します。また、ユーザーがログアウトした際など、特定のタイミングでオブジェクトを破棄するようにすることもできます。
特に、指定した時間の経過によってセッションスコープ上の情報が破棄されることを「セッションタイムアウト」と呼びます。
セッションスコープに保持される情報は、セッションIDと呼ばれる一意のキーで識別されます。このセッションIDをWebブラウザなどのクライアントとやり取りすることにより、利用者を判別して管理することが可能です。
このセッションIDは、主にCookie(クッキー)と呼ばれる仕組みを利用してWebブラウザなどのクライアントとWebアプリケーションの間でやり取りされ、他の利用者の情報にアクセスすることが無いように制御されます。
セッションスコープにオブジェクトを保持させる場合は、HttpSessionインターフェイスを利用します。
Jakarta EE Platform API v10.0.0 - HttpSession
アプリケーションスコープ(ServletContext)
アプリケーションスコープは、Webアプリケーションの起動から終了までの間、全て有効となるスコープです。アプリケーションの再起動や、サーバーの再起動など、アプリケーションが停止した場合にリセットされ、保持していた情報が失われます。
このため、例えばユーザーごとのカート情報など、一時的に利用するオブジェクトの保持先としては利用すべきではありません。アプリケーションスコープには、メールサーバーの設定といったようなアプリケーションの起動中に変更されることのない情報や、頻繁にアクセスされるシステム設定のキャッシュデータなどを保持させることが一般的です。
アプリケーションスコープにオブジェクトを保持させる場合は、ServletContextインターフェイスを利用します。
Jakarta EE Platform API v10.0.0 - ServletContext
ページスコープ(PageContext)※JSPのみ
これらのスコープに加え、JSPを利用する場合はページスコープも利用することができます。これは1つのJSPページ内でのみ有効となるスコープで、有効範囲が最も狭いスコープです。
例えば、スクリプトレットの中で宣言した変数はJSP内が有効範囲となります。また、この他にも同一のJSPのスクリプトレットで処理した結果をEL式で出力したい場合などに、pageContextというオブジェクトを利用してページスコープ内で値を引き渡すことが可能です。
ページスコープにオブジェクトを保持させる場合は、PageContextインターフェイスを利用します。
Jakarta EE 8 仕様 API - PageContext
各スコープでのオブジェクトの保持と利用
それでは、実際に各スコープへオブジェクトを保持させたり、取得して利用してみましょう。
※下記のコードのうち、サンプル①~③(SetAttrServlet.java、GetAttrServlet.java、scopedata.jsp)はリクエストを①→②→③と転送し、連続した処理として動かすことができますので、実際に動かしてみたい方は是非お試しください。
オブジェクトを保持する(setAttribute)
各スコープにオブジェクト(データ)を保持させたい場合は、setAttributeメソッドを利用します。setAttributeメソッドは、pageContextを含めた全てのスコープに対して定義されているメソッドです。
以下のサンプルコードでは、リクエストスコープ、セッションスコープ、アプリケーションスコープの各スコープに対し、setAttributeメソッドでオブジェクトを保持させて、別のサーブレット(GetAttrServlet)の処理にリクエストを転送しています。
【サンプル① SetAttrServlet.java】
package site.knovus.example;
import java.io.IOException;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContext;
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;
/**
* 各スコープに情報を保持させるサーブレット
*/
@WebServlet("/SetAttrServlet")
public class SetAttrServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userName = request.getParameter("username");
// リクエストスコープ
request.setAttribute("servname", "SetAttrServlet");
// セッションスコープ(requestに紐づくセッションを取得)
HttpSession session = request.getSession();
session.setAttribute("username", userName);
// アプリケーションスコープ(requestに紐づくアプリケーションを取得)
ServletContext context = request.getServletContext();
context.setAttribute("appname", "WebPractice");
// GetAttrServlet にリクエストを転送
RequestDispatcher dispatcher =
request.getRequestDispatcher("./GetAttrServlet");
dispatcher.forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
ページスコープへのオブジェクトの格納は、JSP(スクリプトレット)の中で行うことが可能です。
下記のサンプルでは、スクリプトレットで現在日時の文字列を、「datetime」という名前でページスコープに保持させて、下部のEL式で出力しています。
<%@ page contentType="text/html; charset=UTF-8"
import="java.util.Date, java.text.SimpleDateFormat"%>
<!DOCTYPE html>
<%
// 現在日時を指定した形式にフォーマットする
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datetimeStr = df.format(new Date());
// ページスコープに整形された文字列を保持
pageContext.setAttribute("datetime", datetimeStr);
%>
<html>
<head>
<meta charset="UTF-8">
<title>現在日時の表示</title>
</head>
<body>
現在日時は ${datetime} です。
</body>
</html>
同じスコープに対して同じ名前で属性を設定した場合、後から設定したオブジェクトで上書きされます。しかし、スコープが異なる場合は同じ名前で属性を設定しても、各々のスコープにオブジェクトが保持されますので注意してください。
// リクエストスコープ
request.setAttribute("companyName", "GSI");
// 同じスコープに同じ名前で設定した場合は上書きになる
request.setAttribute("companyName", "GSI-Req");
// セッションスコープ(同じ名前(companyName)で別途保持できる)
HttpSession session = request.getSession();
session.setAttribute("companyName", "GSI-Ses");
// アプリケーションスコープ(同じ名前(companyName)で別途保持できる)
ServletContext context = request.getServletContext();
context.setAttribute("companyName", "GSI-App");
オブジェクトを取得して利用する(getAttribute)
各スコープにsetAttributeメソッドで保持させたオブジェクトは、各スコープのgetAttributeメソッドでアクセスすることが可能です。これは、サーブレットのJavaのコードの中でも、JSPのスクリプトレットや式の中でも利用することができます。
getAttributeメソッドで取得できる値はObject型なので、本来の型(setAttributeで設定したオブジェクトと同じ型)にキャストして利用する必要があります。
【サンプル② GetAttrServlet.java】
package site.knovus.example;
import java.io.IOException;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContext;
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;
/**
* 各スコープに保持した情報を取得して出力するサーブレット
*/
@WebServlet("/GetAttrServlet")
public class GetAttrServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// リクエストスコープに保持した情報
String servletName =
(String) request.getAttribute("サーブレット名");
// セッションスコープに保持した情報
HttpSession session = request.getSession();
String userName =
(String) session.getAttribute("ユーザー名");
// アプリケーションスコープに保持した情報
ServletContext context = request.getServletContext();
String appName =
(String) context.getAttribute("アプリケーション名");
// 取得した各情報をコンソールに出力(標準出力)
System.out.println("サーブレット名:" + servletName);
System.out.println("ユーザー名:" + userName);
System.out.println("アプリケーション名:" + appName);
// 画面表示用のJSPにリクエストを転送
RequestDispatcher dispatcher =
request.getRequestDispatcher("/jsp/scopedata.jsp");
dispatcher.forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
以下のコードは、JSPの中で式のタグ(<%= %>)を利用して、各スコープからオブジェクトを取り出して画面上に出力させるサンプルです。各スコープへのアクセスに、JSP上で宣言していない「request」「session」「application」「pageContext」という変数を利用していますが、これはJSPの「暗黙オブジェクト」と呼ばれる仕組みを利用しています。(詳細は次回以降の記事で説明します)
【サンプル③ scopedata.jsp】
<%@ page contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<%
// ページスコープにJSPファイル名を保持
pageContext.setAttribute("jspname", "scopedata.jsp");
%>
<html>
<head>
<meta charset="UTF-8">
<title>各スコープの情報を表示</title>
</head>
<body>
<p>サーブレット名:<%= request.getAttribute("servname") %></p>
<p>ユーザー名:<%= session.getAttribute("username") %></p>
<p>アプリケーション名:<%= application.getAttribute("appname") %></p>
<p>JSPファイル名:<%= pageContext.getAttribute("jspname") %></p>
</body>
</html>
オブジェクトを出力する(EL式を用いた出力)
JSPでは、EL式を用いることで各スコープに設定されたオブジェクトを利用することも可能です。EL式では、各スコープにはEL式の暗黙オブジェクトである「requestScope」「sessionScope」「applicationScope」「pageScope」を利用してアクセスできます。
以下のJSPでは、サンプル③のJSPと同じ内容を、EL式を用いて出力しています。
<%@ page contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<%
// ページスコープにJSPファイル名を保持
pageContext.setAttribute("jspname", "scopedata.jsp");
%>
<html>
<head>
<meta charset="UTF-8">
<title>各スコープの情報を表示</title>
</head>
<body>
<p>サーブレット名:${requestScope.servname}</p>
<p>ユーザー名:${sessionScope.username}</p>
<p>アプリケーション名:${applicationScope.appname}</p>
<p>JSPファイル名:${pageScope.jspname}</p>
</body>
</html>
また、他のスコープに同じ名前で設定されたオブジェクトが無い場合、EL式ではスコープの指定を省略することも可能です。(同じ名前がある場合はスコープの狭いほうに保持されているものが優先されます。こちらも詳細は次回以降の記事で説明します)
<%--// スコープの指定を省略する場合 -->
<body>
<p>サーブレット名:${servname}</p>
<p>ユーザー名:${username}</p>
<p>アプリケーション名:${appname}</p>
<p>JSPファイル名:${jspname}</p>
</body>
いかがでしたでしょうか。本記事では、Webアプリケーションでデータを保持するためのスコープについて説明しました。効率的かつ安全なWebアプリケーション開発のためには、スコープの概念を理解することは不可欠となります。
実際のWebアプリケーションの中で扱うデータは多岐に渡り、その利用方法も様々です。アプリケーションに送信されたパラメーターや、処理結果などの情報を取り扱う際には、それらのデータがどこで、いつ(いつまで)利用されるかを意識してみることが、スコープの感覚を養う第一歩です。皆さんもSNSやショッピングサイトなど、インターネット上のサービスを利用する際に、どのような情報がいつ、どこに表示されるのか、ちょっとだけ意識してみるのも良いと思います。
次回以降の記事では、これらのスコープに保持したデータを、JSPを用いて表示するいくつかの方法について説明します。