#155 フォワードとリダイレクトの違いを計測してみる
はじめに
JavaのWebアプリ開発において、ページの遷移方法としてフォワード(forward())とリダイレクト(sendRedirect()) がよく用いられます。これらの処理については、用途ごとの使い分けや、フォワードの方がリダイレクトよりも高速に動作するということなどが知られています。あまりこの2つの処理時間の違いを実感できていなかったのですが、実際にどれくらい違うのか、処理速度と情報保持に焦点を当て、この2つの処理の違いを計測してみました。
検証環境
計測には以下の環境を使用しています。
- Windows 11 Home 24H2
- Eclipse 2024-12 (4.34.0)
- Java21
- Apache Tomcat v10.1
検証方法
System.nanoTime()を使ってそれぞれ100回ずつ試行し、各処理の平均処理時間を算出します。リダイレクトを使用する場合サーバー側だけでは処理完了までの総時間を計測できないため、リダイレクトとフォワードのどちらの場合も処理開始時の時間を遷移先ページに転送して遷移完了時の時間と比較して処理時間を計測する方法に統一します。
また、処理開始時の時間を転送する際にフォワードとリダイレクトでどこまで情報が保持されるかを確認します。
計測結果
リダイレクトではリクエストスコープを引き継ぐことができないため、セッションスコープを活用して処理開始時間の情報を渡しました。スコープの違いによる影響の確認のため、本来はリクエストスコープで値を引き継ぐフォワード処理も意図的にセッションスコープを用いて同様の条件で計測しました。
フォワード、リダイレクトの各処理でページを遷移することを各100回ずつ繰り返した平均の処理時間は以下のようになりました。
| 処理方法 | 100回遷移した時間の平均(μs) |
|---|---|
| リダイレクト(リクエストスコープ) | (スコープ内の値が保持されずエラーとなる) |
| フォワード(リクエストスコープ) | 1578.8 |
| リダイレクト(セッションスコープ) | 7970.1 |
| フォワード(セッションスコープ) | 1863.4 |
計測結果から、フォワードはサーバー内部で処理が完結するため高速である一方、リダイレクトは新しいリクエストが発生するため遅くなるという理論的な説明が、数値としても明確に裏付けられることがわかりました。また、リクエストスコープが使用できるか否かの違いも処理時間にさらに影響を与える可能性があることも確認することができました。
使用したコード
通常、フォワード先のページは WEB-INF 内に配置するのが望ましいですが、リダイレクトの場合 WEB-INF 内ではブラウザから直接表示できません。条件を統一するため、今回はどちらの遷移先ページも /webapp 直下に配置しました。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>遷移前のページ</title>
</head>
<body>
<h1>最初のページです</h1>
<p><a href="ForwardServlet">forward</a></p>
<p><a href="RedirectServlet">redirect</a></p>
<p><a href="ForwardSessionServlet">forwardSession</a></p>
</body>
</html>
package servlet;
import java.io.IOException;
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("/ForwardServlet")
public class ForwardServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 処理開始時間をリクエストスコープに格納
long startTime = System.nanoTime();
request.setAttribute("startTime", startTime);
// フォワード処理
RequestDispatcher dispatcher = request.getRequestDispatcher("forward.jsp");
dispatcher.forward(request, response);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%
// リクエストから開始時間を取得
long startTime = (long)request.getAttribute("startTime");
// 現在の時間を取得
long endTime = System.nanoTime();
// 遷移処理にかかった時間をマイクロ秒で計算
long elapsedTime = (endTime - startTime) / 1000;
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>フォワード先のページ</title>
</head>
<body>
<h1>フォワードされたページです</h1>
<p>実行時間:<%= elapsedTime %>μs</p>
</body>
</html>
// (doGet()内のみ抜粋)
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 処理開始時間をセッションスコープに格納
long startTime = System.nanoTime();
HttpSession session = request.getSession();
session.setAttribute("startTime", startTime);
// リダイレクト処理
response.sendRedirect("redirect.jsp");
}
<!-- (値受け取り部分のみ抜粋) -->
<%
// セッションから開始時間を取得
long startTime = (long)session.getAttribute("startTime");
// 現在の時間を取得
long endTime = System.nanoTime();
// 遷移処理にかかった時間をマイクロ秒で計算
long elapsedTime = (endTime - startTime) / 1000;
// 2回目以降の計測に影響しないようセッションを破棄
session.invalidate();
%>
おわりに
今回の検証では、フォワードとリダイレクトの動作の違いを実際に計測して数値として確認することができました。普段あまり体感では意識できていなかった通り、μs単位での違いにはなりますが、大量に処理を行いたいときにはしっかり使い分けなければいけないと感じました。
今回は処理速度と情報保持の比較に焦点を当てましたが、ほかにもネットワーク負荷の違い(リクエスト数やデータ転送量、RTT、帯域使用率の変化)を確認するなど、検証の視点を広げることで違いがより明確に確認できるかもしれません。
最後までお読みいただきありがとうございました。
参考
参考書籍:
国本大悟(2024)『スッキリわかるサーブレット&JSP入門第4版』インプレス
参考記事:
Discussion