JavaのServletからJSPへ値を渡すには?setAttributeとgetAttributeの使い方

最終更新日

Schoo Java入門 中級 第5回 サーバーサイドアプリケーションの実装基礎

ServletやJSPを使うWebアプリには、処理の間でデータを共有するための置き場所が用意されています。名前を付けてデータを保存できるこれらのオブジェクトを、一般に「スコープオブジェクト」と呼びます。「スコープ」は、保存したデータを共有できる範囲のことです。

今回は、その中のリクエストスコープを使います。Servlet側で求めた割引額を request に保存し、処理を引き継いだJSPで取り出して表示します。フォームから届くリクエストパラメータとは別のデータなので、取得に使うメソッドも区別して見ていきましょう。

Schoo「Java入門 中級」第5回「サーバーサイドアプリケーションの実装基礎」に対応しています。授業を受けていない方も、購入金額から割引額を表示する例を通して確認できます。

今回はリクエストスコープを使って、ServletとJSPで値を共有する

スコープオブジェクトには、リクエストごとの request、同じ利用者の複数のリクエストで共有できる session、アプリ全体で共有する ServletContext などがあります。どこまで共有し、いつまで使うデータなのかによって、置き場所を選びます。

今回の割引額は、送られてきた購入金額を処理して、その結果を表示するまで使えれば十分です。そこで、1回のリクエストの処理中に値を共有できるrequestの属性へ保存します。ここでいう「属性」は、名前と値を対応付けて保存したデータのことです。

requestには、ブラウザから届くamountというパラメータと、サーバー側で保存するdiscountという属性が別々にある。Servletが属性に保存した500を、同じリクエストのforward先のJSPで取得する。JSPを直接開いた新しいリクエストには前回の属性は引き継がれない。
青い欄は送信された入力、緑の欄はサーバー側で保存する属性です。Servletとforward先のJSPは、同じリクエストの属性を利用できます。画像を選択すると拡大できます。

Servletで setAttribute() を使って保存し、forward() でJSPへ処理を引き継ぎます。JSPでは getAttribute() で同じ名前の値を取り出せます。ServletからJSPへ進んでも、新しいリクエストにならないので、この置き場所を共有できます。

一方、リンクをクリックしたり、JSPのURLを直接開いたりすると、新しいリクエストになります。前のリクエストへ保存した属性は自動で引き継がれません。requestは、次の画面操作までデータを残しておくための保存場所ではありません。

注意:リクエストスコープの属性と、リクエストパラメータは別

  • リクエストパラメータ:フォームやURLから届いた入力。今回なら購入金額の文字列 "12000" で、getParameter() で読みます。
  • リクエストスコープの属性:サーバー側で名前を付けて保存するデータ。今回なら計算した割引額500で、setAttribute() で保存し、getAttribute() で読みます。

どちらもrequestから扱えますが、同じものではありません。setAttributeで保存しても、getParameterでその値を取得できるようにはなりません。また、フォームの値が自動で同名の属性になるわけでもありません。

getParameterはServlet専用、getAttributeはJSP専用という区別でもありません。JSPからパラメータを読むことも、Servletから属性を読むこともできます。読みたいのが送信された入力なのか、サーバー側で保存した属性なのかで使い分けます。

Servletの変数は、JSPからそのまま使えるわけではない

int discount = 500;

これをServletのdoPost内に書いても、別のファイルであるJSPに discount という変数が自動的に作られるわけではありません。Servletのローカル変数が使える範囲は、その変数を宣言したメソッドやブロックの中です。

そこで、Servletの変数に入っている値を、先ほどのrequestの属性へ保存します。Servletコンテナが用意したrequestを使うため、共有用のオブジェクトを自分でnewする必要はありません。

int discount = 500;
request.setAttribute("discount", discount);

この1行で、"discount" という名前に500を対応付けます。setAttributeを書いただけでは画面は切り替わらず、HTMLも表示されません。保存したあと、JSPへ処理を引き継ぎます。

イラストで見る:保存した500が画面に出るまで

Servletがdiscountという名前で500をrequestの属性へ保存し、forward先のJSPが同じ名前で取り出す。JSPが生成したHTMLにより、ブラウザに割引額500円が表示される。
青は保存名、緑は値を扱う変数や数値です。図では受け渡しに注目するため500を直接代入しています。実際の計算と入力チェックは後の全体コードに示します。
  1. Servletが setAttribute("discount", discount) で、計算結果に名前を付けてrequestへ保存する。
  2. forward(request, response) で、同じリクエストの処理をJSPへ引き継ぐ。
  3. JSPが getAttribute("discount") で、保存した値を取り出す。
  4. JSPが値をHTMLへ埋め込み、ブラウザには「割引額: 500円」と表示される。

ServletからJSPへの引き継ぎはサーバー内で行われます。属性の表がそのままブラウザへ送られるわけではありません。ブラウザが受け取るのは、JSPの実行によって作られたHTMLです。

入力を数値へ変換し、Modelの戻り値を保存する

入力欄が type="number" でも、getParameterで受け取る値は文字列です。Servletで整数へ変換し、Modelへ渡して割引額を求めます。受け取った戻り値をsetAttributeで保存するところまで、コードでつなげてみましょう。

String text = request.getParameter("amount");
int amount = Integer.parseInt(text);

DiscountModel model = new DiscountModel();
int discount = model.calculateDiscount(amount);
request.setAttribute("discount", discount);

これは正常な入力の流れだけを抜き出したコードです。未入力や整数でない値への対応は、後のServlet全体のコードに含めます。

購入金額もJSPに表示したい場合は、request.setAttribute("amount", amount); を追加します。フォームの入力名と同じ "amount" ですが、元のパラメータは書き換わりません。12000を送った場合、getParameterではStringの "12000"、getAttributeでは保存したIntegerの12000を取得します。

setAttributeの第1引数は保存名、第2引数は値

int discountAmount = 500;
request.setAttribute("discount", discountAmount);
部分意味
"discount"JSPから探すための保存名。文字列なのでダブルクォートで囲む。
discountAmount保存したい値が入っているServletの変数。今回の値は500。

保存名とJavaの変数名は一致させる必要がありません。この例でJSPが指定するのは "discountAmount" ではなく、"discount" です。揃えるのはsetAttributeとgetAttributeの第1引数です。大文字・小文字も区別されます。

同じ名前でsetAttributeを呼び直すと、その名前の値は後の値に置き換わります。複数の値を渡す場合は、"amount""discount" のように名前を分けます。

getAttributeでIntegerとして取り出す理由

Integer discount =
        (Integer) request.getAttribute("discount");

getAttribute() はさまざまな種類の値を返せるように、戻り値の型がObjectになっています。今回は整数を保存したので、(Integer) と書いてInteger型として扱います。このキャストは、文字列を数値へ変換する処理ではありません。

setAttributeの値はオブジェクトとして保存されます。int型の500を渡すと、自動的にIntegerへ変換されます。この仕組みをオートボクシングと呼びます。Integer は整数をオブジェクトとして扱うためのクラスです。

名前に対応する属性がない場合、getAttributeは0ではなく null を返します。Integer ならnullを受け取り、「値が渡されていない」と確認できます。受け取ってすぐintへ代入すると、nullを整数へ変換しようとしてNullPointerExceptionになることがあります。

Integer discount =
        (Integer) request.getAttribute("discount");

if (discount != null) {
    // ここでは割引額を使える
}

setAttribute("discount", "500") のように文字列を保存したのにIntegerへキャストすると、ClassCastExceptionになります。保存名だけでなく、渡す値の型もServletとJSPで揃える必要があります。0円という結果と、値がないnullも区別しましょう。

forwardで、属性を持ったままJSPへ引き継ぐ

request.setAttribute("discount", discount);
request.getRequestDispatcher("/discount-result.jsp")
        .forward(request, response);

getRequestDispatcher() で表示先を指定し、forward() で処理を引き継ぎます。先頭の / は、このWebアプリのルートを表します。アプリのURLが /lesson05 なら、そのアプリ内のdiscount-result.jspです。

この例ではブラウザへ別URLへのアクセスを指示せず、サーバー内でJSPを実行します。そのため、ブラウザのアドレス欄はServletへの送信先のままです。JSPのrequestから、Servletが保存した属性を取得できます。

値の保存はforwardより前に行います。また、forwardはJavaのreturnではなくメソッド呼び出しです。通常どおり戻ればServletの後続コードも実行されるため、このサンプルではdoPostの最後に置きます。画面の出力が始まってから転送する構成にもせず、表示はJSPへ任せます。

動くコードで確認する:割引額をJSPに表示する

購入金額が10,000円以上なら500円引き、それ未満なら0円とします。Java 17・Tomcat 10.1の jakarta.servlet を使う構成です。各ファイルはUTF-8で保存します。前の記事と同じ計算例ですが、ここではJSPへの受け渡しに注目します。

1.入力画面:src/main/webapp/discount-form.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>割引額の確認</title>
</head>
<body>
    <h1>割引額の確認</h1>
    <form action="discount" method="post"
          accept-charset="UTF-8">
        <label for="amount">購入金額(円)</label>
        <input id="amount" name="amount" type="number"
               min="0" max="2147483647" step="1" required>
        <button type="submit">割引額を確認する</button>
    </form>
</body>
</html>

2.計算するクラス:src/main/java/example/DiscountModel.java

package example;

public class DiscountModel {
    public int calculateDiscount(int amount) {
        if (amount < 0) {
            throw new IllegalArgumentException(
                    "購入金額は0円以上にしてください。");
        }
        if (amount >= 10000) {
            return 500;
        }
        return 0;
    }
}

Modelは整数の購入金額を受け取り、割引額を返すだけです。requestへの保存やHTMLの出力は行いません。

3.値を用意して渡す:src/main/java/example/DiscountServlet.java

package example;

import java.io.IOException;
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("/discount")
public class DiscountServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        String text = request.getParameter("amount");

        if (text == null || text.isBlank()) {
            request.setAttribute("error", "購入金額を入力してください。");
        } else {
            try {
                int amount = Integer.parseInt(text.trim());
                DiscountModel model = new DiscountModel();
                int discount = model.calculateDiscount(amount);

                request.setAttribute("amount", amount);
                request.setAttribute("discount", discount);
            } catch (NumberFormatException e) {
                request.setAttribute("error",
                        "購入金額は0〜2147483647の整数で入力してください。");
            } catch (IllegalArgumentException e) {
                request.setAttribute("error",
                        "購入金額は0円以上にしてください。");
            }
        }

        request.getRequestDispatcher("/discount-result.jsp")
                .forward(request, response);
    }
}

正常時には amountdiscount、入力に問題があれば error を属性へ保存します。HTML側でrequiredやminを指定していても、それだけには頼らずServlet側でも確認します。

4.受け取って表示する:src/main/webapp/discount-result.jsp

<%@ page pageEncoding="UTF-8"
         contentType="text/html; charset=UTF-8" %>
<%
    String error = (String) request.getAttribute("error");
    Integer amount = (Integer) request.getAttribute("amount");
    Integer discount = (Integer) request.getAttribute("discount");
%>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>割引額の確認結果</title>
</head>
<body>
    <h1>割引額の確認結果</h1>
    <% if (error != null) { %>
        <p><%= error %></p>
    <% } else if (amount != null && discount != null) { %>
        <p>購入金額: <%= amount %>円</p>
        <p>割引額: <%= discount %>円</p>
    <% } else { %>
        <p>入力画面から割引額を確認してください。</p>
    <% } %>
    <a href="discount-form.html">入力画面に戻る</a>
</body>
</html>

JSPの <% ... %> の中では、サーバー側でJavaの処理を実行します。<%= discount %> はdiscountの値を出力する記述です。discountが500なら、<p>割引額: 500円</p> というHTMLが作られます。JSPのJavaコード自体をブラウザへ送るわけではありません。

この例でHTMLに埋め込むのは、整数とプログラム内で決めた固定のエラーメッセージだけです。フォームの入力文字列はそのまま表示しません。外部から届く商品名などの文字列を表示する場合、<%= ... %> は自動でHTMLエスケープしないため、別途適切なエスケープ処理が必要です。

実際の注文処理では、フォームで送られた金額をそのまま請求の根拠にせず、サーバー側の商品情報と数量から金額を確定します。今回は値の受け渡しを確認するサンプルです。

フォームから開く場合と、JSPを直接開く場合を比べる

アプリのコンテキストパスが /lesson05 なら、http://localhost:8080/lesson05/discount-form.html を開いて送信します。

操作・入力結果理由
12000を送信購入金額12000円・割引額500円を表示ServletがModelから割引額を取得し、2つの属性を保存してからforwardする。
9999を送信割引額0円を表示0も有効な計算結果として属性へ保存される。
未入力やabcがサーバーへ届く入力エラーのメッセージを表示Servletがerror属性を保存し、JSPが取り出す。
discount-result.jspを直接開く「入力画面から割引額を確認してください。」と表示新しいリクエストでJSPへ直接アクセスするため、Servletによる属性の保存が行われていない。

フォームから結果を表示したあと、別のタブでJSPを直接開いてみてください。前の割引額ではなく案内文が表示されれば、リクエストごとに属性が別であることを確認できます。

リンクでJSPを開いたり、sendRedirectでブラウザにアクセスし直してもらったりする場合も、新しいリクエストになります。forwardと同じつもりで置き換えると、保存した属性を取得できません。なお、ServletのURLで画面を再読み込みしたときはPOSTが再送信される場合があり、単に「再読み込みしたら必ず値が消える」とは限りません。

表示されないときは、保存名・型・経路を確認する

症状確認する場所直し方
getAttributeがnullになる"discount""discountAmount" など、名前が食い違っていないか保存名と取得名を揃える。
getParameterで結果が取れないsetAttributeで保存した値を読もうとしていないか属性はgetAttributeで読む。
ClassCastException数値を保存したのにStringへキャストしていないか保存したオブジェクトに合う型で受け取る。
NullPointerException属性がないのにintへ自動変換していないかIntegerで受け、nullを確認する。
JSPの直接アクセスだけ値がないServletを通り、保存後にforwardしているか入力画面から送信し、想定した処理経路で確かめる。

JSP側で割引額をもう一度計算して埋め合わせるのではなく、Servletで保存した値が、同じ名前・型で届いているかを順に確認します。

確認問題

それぞれ独立した問題です。本文の割引額は10,000円以上で500円、それ未満で0円とします。

  1. Servletの変数 int discountAmount = 500; を、保存名 "discount" でrequestに保存し、JSPの変数 Integer result へ取り出すコードを書いてください。
  2. フォームから amount=12000 が届き、Servletが request.setAttribute("amount", 12000); を実行しました。getParameter(“amount”)とgetAttribute(“amount”)で取得する値と型を答えてください。
  3. Servletが request.setAttribute("discount", 500); と書き、JSPが (Integer) request.getAttribute("discountAmount") と書いています。結果が取得できない理由と修正方法を答えてください。
  4. 一度フォームから結果を表示したあと、新しいタブでdiscount-result.jspを直接開きました。本文のJSPでは何が表示されますか。同じ値を表示できない理由も説明してください。

演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。

まとめ

  • Servletの変数をJSPで使うには、setAttributeでrequestへ名前を付けて値を保存する。
  • getParameterは送られてきたパラメータ、getAttributeはサーバー側で保存した属性を読む。
  • JSPでは同じ保存名を指定し、保存した値に合う型で受け取る。属性がない場合はnullになる。
  • forwardなら同じリクエストの処理をJSPへ引き継げる。JSPの直接アクセスは別のリクエスト。
  • ブラウザへ返るのは、JSPが値を埋め込んで生成したHTML。

関連記事

シェアする