はじめに (対象読者・この記事でわかること)

この記事は、Javaを用いたWebアプリケーション開発に携わる方、特にApache Strutsフレームワークを利用している方を主な対象としています。HTTPの基本やServlet/JSP、Strutsの基礎知識がある方を想定していますが、リダイレクト時のデータ引き継ぎに困っている方であれば、どのレベルの方でも参考にしていただけます。

この記事を読むことで、ActionRedirect(または一般的なHTTPリダイレクト)を実行する際に、HttpServletRequestオブジェクトに格納された情報を失わずに次の画面や処理に引き継ぐための具体的な方法がわかります。セッション、クエリパラメータ、そして擬似的なフラッシュスコープといった主要なアプローチそれぞれのメリット・デメリットを理解し、ご自身のプロジェクトの要件に応じた最適な解決策を選択できるようになるでしょう。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 * Java言語の基本的な文法とオブジェクト指向の概念 * Servlet/JSPの基本的な仕組みとHTTPリクエスト/レスポンスサイクル * HTTPプロトコルの基礎(GET/POST、ステータスコードなど) * Apache Struts 1フレームワークの基本的な設定とActionクラスの利用方法

ActionRedirectとHttpServletRequest情報の引き継ぎ課題

Webアプリケーション開発において、特定の処理の後に別のURLへユーザーを誘導したい場面は頻繁に発生します。例えば、フォームの入力処理が完了した後、完了画面へ遷移させたり、認証失敗時にログイン画面へ戻したりする場合などです。Strutsフレームワークでは、このようなリダイレクト処理に ActionRedirect を利用することが一般的です。

しかし、ここで一つ大きな課題に直面します。それは、ActionRedirect が実行されると、現在の HttpServletRequest オブジェクトに格納されていた情報(リクエストパラメータ、リクエスト属性など)が失われてしまうという点です。これは、リダイレクトがブラウザに対して「新しいリクエストを送信してください」と指示するHTTPステータスコード(通常302 Found)を発行することで機能するためです。ブラウザは指定された新しいURLに対して、全く新しいHTTPリクエストを送信するため、元々 request スコープに存在していたデータは引き継がれません。

例えば、ユーザーからの入力値や、前の処理で生成された一時的なメッセージ、エラー情報などをリダイレクト先の画面で表示したい場合、単に ActionRedirect を返すだけではこれらの情報が失われ、期待通りの表示ができません。この課題を解決するためには、リクエストスコープ以外の場所に情報を一時的に保持し、新しいリクエストでその情報を再度取得する仕組みが必要となります。次のセクションでは、この課題に対する具体的な解決策をいくつかご紹介します。

HttpServletRequestの情報をActionRedirectで引き継ぐ具体的な戦略

HttpServletRequestの情報をActionRedirectで引き継ぐための主要な戦略はいくつかあります。それぞれの方法にはメリットとデメリットがあり、引き継ぎたい情報の種類、量、セキュリティ要件、そして実装の手間などを考慮して選択する必要があります。ここでは、代表的な3つの方法について詳しく解説します。

ステップ1: HttpSession(セッション)を利用する

最も一般的で汎用性の高い方法が、HttpSession を利用することです。セッションは、ユーザーがWebサイトを訪問している間、そのユーザー固有の情報をサーバー側で保持し続ける仕組みです。リダイレクトは新しいリクエストを生成しますが、セッションIDは通常引き継がれるため、セッションに保存された情報はリダイレクト先でも利用できます。

実装方法

  1. 情報をセッションに設定する: リダイレクト元のActionクラスで、HttpServletRequest から HttpSession を取得し、setAttribute() メソッドで引き継ぎたい情報を保存します。

    ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionRedirect; // Struts 1.x の場合

    public class SourceAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // フォームからの入力値や処理結果などを取得
        String message = "データが正常に登録されました。";
        User user = new User("John Doe", 30); // 複雑なオブジェクトもOK
    
        // 情報をセッションに保存
        HttpSession session = request.getSession();
        session.setAttribute("successMessage", message);
        session.setAttribute("registeredUser", user);
    
        // リダイレクト先を指定
        // mapping.findForward("redirectTarget") が ActionRedirect を返すように設定されているか、
        // 直接 ActionRedirect オブジェクトを返す
        return new ActionRedirect("/target.do");
        // または return mapping.findForward("successRedirect"); // struts-config.xml で <forward name="successRedirect" path="/target.do" redirect="true"/>
    }
    

    } ```

  2. リダイレクト先でセッションから情報を取得し、削除する: リダイレクト先のActionクラス(またはJSP)で、再度 HttpSession を取得し、getAttribute() メソッドで保存した情報を読み出します。重要なのは、情報を読み出した後に、セッションからその情報を削除することです。これを怠ると、次回のアクセス時にも古い情報が表示されてしまったり、メモリを無駄に消費したりする原因となります。

    ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping;

    public class TargetAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        HttpSession session = request.getSession();
    
        // セッションからメッセージを取得し、削除
        String message = (String) session.getAttribute("successMessage");
        if (message != null) {
            request.setAttribute("displayMessage", message); // requestスコープに移し替えてJSPで表示
            session.removeAttribute("successMessage"); // 情報を削除
        }
    
        // セッションからユーザーオブジェクトを取得し、削除
        User user = (User) session.getAttribute("registeredUser");
        if (user != null) {
            request.setAttribute("displayUser", user);
            session.removeAttribute("registeredUser");
        }
    
        // 通常のフォワードでJSPに遷移
        return mapping.findForward("success"); // JSPにフォワード
    }
    

    } ```

    JSPでの表示例: jsp <p>${displayMessage}</p> <p>登録ユーザー名: ${displayUser.name}, 年齢: ${displayUser.age}</p>

メリット

  • どんな情報でも引き継ぎ可能: プリミティブ型から複雑なカスタムオブジェクトまで、どのような型の情報でも保存できます。(ただし、カスタムオブジェクトはSerializableインターフェースを実装することが推奨されます。)
  • 情報量に制限が少ない: URLの長さ制限などを気にせず、大量の情報を引き継ぐことができます。
  • セキュリティ: クライアント側(URLなど)に情報が露出しないため、機密性の高い情報も比較的安全に引き継げます。

デメリット

  • 削除忘れ: 情報を削除し忘れると、意図しないデータ残存やメモリ消費の原因になります。
  • セッション切れ: ユーザーが長期間操作しなかったり、サーバーが再起動したりすると、セッションが切れて情報が失われる可能性があります。
  • サーバーステートフル: サーバー側で状態を保持するため、負荷分散環境でのSticky Session設定やセッションレプリケーションが必要になる場合があります。

ステップ2: クエリパラメータとして付与する

引き継ぎたい情報が少量かつ非機密なものであれば、リダイレクト先のURLにクエリパラメータとして直接情報を付与する方法が手軽です。

実装方法

  1. クエリパラメータを付与してリダイレクト: ActionRedirect のコンストラクタや addParameter() メソッド(Struts 1.xの場合、ActionRedirectはコンストラクタでURLを指定し、必要であればURLエンコードを考慮する)で、URLにパラメータを追加します。

    ```java import java.net.URLEncoder; import org.apache.struts.action.ActionRedirect;

    public class SourceAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        String status = "success";
        String userName = "Alice";
    
        // クエリパラメータを構築
        // URLエンコードを忘れずに行うことが重要
        String redirectUrl = "/target.do?"
                           + "status=" + URLEncoder.encode(status, "UTF-8")
                           + "&userName=" + URLEncoder.encode(userName, "UTF-8");
    
        return new ActionRedirect(redirectUrl);
        // または、Struts 1.xでは<forward>タグでparameter属性を使うことも可能
        // <forward name="successRedirect" path="/target.do" redirect="true" parameter="status=${statusParam},userName=${nameParam}"/>
        // この場合、Actionでrequest.setAttribute("statusParam", status); のように設定し、JSPで取得する
        // しかし、ActionRedirect直接の利用がより一般的
    }
    

    } ```

  2. リダイレクト先でクエリパラメータを取得: リダイレクト先のActionクラスやJSPで、request.getParameter() メソッドを使用してパラメータを取得します。

    ```java import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping;

    public class TargetAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        String status = request.getParameter("status");
        String userName = request.getParameter("userName");
    
        // 取得したパラメータをrequestスコープに移し替えてJSPで表示
        request.setAttribute("displayStatus", status);
        request.setAttribute("displayName", userName);
    
        return mapping.findForward("success");
    }
    

    } ```

    JSPでの表示例: jsp <p>処理結果: ${displayStatus}</p> <p>ユーザー名: ${displayName}</p>

メリット

  • 実装が簡単: セッション管理の必要がなく、手軽に利用できます。
  • セッションレス: サーバー側で状態を保持しないため、セッション切れの心配がなく、負荷分散環境での設定も容易です。
  • ブックマーク可能: パラメータを含むURLをブックマークしても、同じ状態を再現できます。

デメリット

  • URLの長さ制限: HTTPプロトコルにはURLの長さに制限があり(ブラウザやサーバーによって異なるが、一般的に2000文字〜8000文字程度)、大量の情報は引き継げません。
  • セキュリティ: URLに情報が露出するため、機密性の高い情報を渡すのには不向きです。パスワードなどを渡してはいけません。
  • 複雑なオブジェクトは不向き: プリミティブ型や文字列以外の複雑なオブジェクトを渡すには、独自のシリアライズ/デシリアライズ処理が必要となり、手間がかかります。
  • URLエンコードの忘れ: 日本語などのマルチバイト文字や特殊文字を扱う場合、URLEncoder.encode() による適切なエンコードが必要です。

ステップ3: フラッシュスコープ(Flash Scope)を利用する(擬似実装)

「一度だけ」次のリクエストで情報を引き継ぎたい、というニーズは非常に多いです。例えば、フォーム送信後の成功メッセージやエラーメッセージなどです。セッションを利用する方法は汎用性が高いですが、削除忘れによる問題が発生しやすいというデメリットがありました。Spring FrameworkのRedirectAttributesaddFlashAttribute()のような「フラッシュスコープ」は、次の一回のリクエストでのみ情報が保持され、その後は自動的に削除されるという便利な仕組みを提供します。

Struts 1.xには標準のフラッシュスコープはありませんが、セッションを利用して同様の振る舞いを擬似的に実装することは可能です。

実装方法

基本的に、ステップ1のセッション利用とほとんど同じですが、情報を取得した直後に必ず removeAttribute() でセッションから削除するというルールを徹底します。このルールをより確実に実施するために、共通のユーティリティクラスやカスタムのフィルタを作成することも有効です。

  1. 情報をセッションに設定する: リダイレクト元のActionクラスで、セッションに情報を保存します。

    ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.struts.action.ActionRedirect;

    public class SourceAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // フォーム入力エラーメッセージなど
        String errorMessage = "入力内容に誤りがあります。";
    
        // フラッシュメッセージとしてセッションに保存
        HttpSession session = request.getSession();
        session.setAttribute("flashErrorMessage", errorMessage);
    
        // エラー画面へリダイレクト
        return new ActionRedirect("/errorPage.do");
    }
    

    } ```

  2. リダイレクト先でセッションから情報を取得し、即座に削除する: リダイレクト先のActionクラスやJSPで、情報を取得したら、その直後に removeAttribute() でセッションから削除します。JSP側で直接処理を行う場合は、JSPの先頭でスクリプトレットなどを使って処理を記述することもできます。

    ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping;

    public class ErrorPageAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

        HttpSession session = request.getSession();
    
        // フラッシュメッセージを取得し、即座に削除
        String errorMessage = (String) session.getAttribute("flashErrorMessage");
        if (errorMessage != null) {
            request.setAttribute("displayErrorMessage", errorMessage);
            session.removeAttribute("flashErrorMessage"); // ★取得後、すぐに削除!
        }
    
        return mapping.findForward("showError"); // エラー表示JSPへフォワード
    }
    

    } ```

    JSPでの表示例: ```jsp <%-- JSPで直接処理する場合 --%> <% String errorMessage = null; HttpSession session = request.getSession(false); // セッションが存在しない場合、作成しない if (session != null) { errorMessage = (String) session.getAttribute("flashErrorMessage"); if (errorMessage != null) { session.removeAttribute("flashErrorMessage"); // 取得後、すぐに削除! } } request.setAttribute("displayErrorMessage", errorMessage); // requestスコープに移す %>

    ${displayErrorMessage}

    ```

メリット

  • 一度きりのデータ引き継ぎに最適: セッションを誤って残すリスクを減らせます。
  • セッションのクリーンアップ: 不要なデータがセッションに残ることを防ぎ、メモリ消費を抑えられます。
  • セキュリティと情報量: セッションを利用するため、セキュリティと情報量の制限はセッション利用の場合と同様です。

デメリット

  • 手動での削除: フラッシュスコープの自動的なライフサイクル管理がないため、開発者が明示的に削除処理を記述する必要があります。これを徹底しないと、結局セッション利用のデメリットを抱えることになります。
  • 実装の手間: 共通処理としてユーティリティクラスやフィルタを導入しない場合、各所で削除処理を書く手間が発生します。

ハマった点やエラー解決

1. セッションのremoveAttribute()忘れによる古い情報の表示

現象: リダイレクト先の画面で、以前のリクエストで設定されたメッセージやデータが繰り返し表示されてしまう。 原因: セッションに保存した情報を、次のリクエストで取得した後に削除し忘れているため。セッションはユーザーがログアウトするか、タイムアウトするまで情報が保持され続けます。 解決策: 情報を取得し、利用した直後に session.removeAttribute("key"); を必ず実行するように徹底します。共通のユーティリティメソッドを作成し、情報の取得と削除をセットで行うように強制するのも良い方法です。

2. クエリパラメータのURLエンコード忘れによる文字化けや不正なURL

現象: クエリパラメータに日本語や記号が含まれる場合、リダイレクト先のURLが壊れてしまったり、パラメータが文字化けして正しく取得できない。 原因: URLに直接埋め込む文字列は、HTTPの仕様上、特定の文字(スペース、日本語、一部記号など)を%XX形式でエンコードする必要があります。これを怠ると、ブラウザやサーバーがURLを正しく解釈できません。 解決策: java.net.URLEncoder.encode(value, "UTF-8") を使用して、クエリパラメータの値を必ずエンコードします。デコードは通常request.getParameter()が自動的に行いますが、サーバーのエンコード設定によっては明示的なデコードが必要な場合もあります。

3. 複雑なオブジェクトをクエリパラメータで渡そうとして失敗

現象: カスタムオブジェクト(例: Userクラスのインスタンス)をクエリパラメータとして渡そうとしたが、文字列としてうまく変換できず、リダイレクト先で元のオブジェクトを復元できない。 原因: クエリパラメータは基本的にシンプルな文字列として情報を渡すためのものです。複雑なオブジェクトをそのまま渡すことはできません。オブジェクトの各フィールドを個別のパラメータとして渡すか、自分でシリアライズ(JSON文字列など)して渡す必要があります。 解決策: 複雑なオブジェクトを渡したい場合は、セッションを利用するのが最も安全で確実です。もしクエリパラメータで渡したい場合は、そのオブジェクトをJSON文字列などに変換してエンコードし、リダイレクト先でデコード・逆シリアライズする手間が発生します。ただし、この方法はURLの長さ制限やセキュリティのリスクを高めるため、非推奨です。

まとめ

本記事では、JavaのWebアプリケーション開発、特にStrutsフレームワークにおいて、ActionRedirect実行時にHttpServletRequestの情報を安全かつ効率的に引き継ぐための主要な戦略を解説しました。

  • リダイレクトの特性を理解する: ActionRedirectは新しいHTTPリクエストを生成するため、リクエストスコープのデータは失われます。
  • HttpSessionの活用: 汎用性が高く、どんな情報でも引き継げますが、removeAttribute()による削除を徹底しないと意図しないデータ残存やメモリリークのリスクがあります。
  • クエリパラメータの利用: 少量の非機密な情報を手軽に引き継ぐ際に便利ですが、URL長制限、セキュリティ、複雑なオブジェクトへの不向きといった制約があります。URLEncoderによるエンコードが必須です。
  • 擬似フラッシュスコープ: セッションを使いつつ、情報を取得したらすぐに削除するという運用で、一度きりのメッセージなどに最適です。手動での削除を仕組み化することで、より堅牢になります。

この記事を通して、開発シナリオに応じた最適なデータ引き継ぎ方法を選択し、より堅牢でユーザーフレンドリーなWebアプリケーションを構築できるようになることを願っています。

今後は、Spring MVCなどのモダンなフレームワークにおけるFlash Attributesの利用方法や、これらのデータ引き継ぎロジックを共通化・ライブラリ化する方法についても記事にする予定です。

参考資料