JavaのWebアプリで値が渡らないのはなぜ?Servlet・Model・JSPの確認方法

最終更新日

Schoo Java入門 中級 第6回 Webアプリケーション構築演習(基礎編)

フォームには入力したのに、Servletでは値を取得できない。Servletで用意した結果が、JSPではnullになる。こうしたとき、まず確認したいのは「値を渡すときの名前」と「取り出すときの名前」です。

Webアプリでは、別のファイルに書かれた処理が、名前を使ってデータを受け渡します。フォームがquantityという名前で送った値を、Servletがcountという名前で探しても取得できません。大切なのは、すべての名前を同じにすることではなく、どの名前とどの名前を対応させるのかを理解することです。

本記事は、Schoo「Java入門 中級」第6回「Webアプリケーション構築演習(基礎編)」を学んだあとに理解を深めるステップアップ学習記事です。未受講の方も、掲載した入力例・コード・結果を読み比べて理解できる構成です。コードは説明に必要な部分を抜粋しており、授業の配布ファイルや実行環境を用意する必要はありません。

名前は、別々のファイルの処理をつなぐ約束

ブラウザやJavaが、「これは数量だから、countでもquantityでも同じだろう」と判断してくれるわけではありません。指定された名前を使って値を探します。意味が似ていても、綴りが違えば別の名前です。

HTMLとServletで揃える名前:quantity

  • HTMLの送信項目:
    name="quantity"
  • Servletで探す名前:
    getParameter("quantity")

ServletとJSPで揃える名前:quote

  • Servletで保存する名前:
    setAttribute("quote", result)
  • JSPで探す名前:
    getAttribute("quote")

この二組を、それぞれ見比べます。quantityとQuantityのような大文字・小文字の違いにも注意してください。名前が文字列として書かれているため、両側が食い違っていてもJavaのコンパイルだけでは気付けないことがあります。

同じにする名前と、同じでなくてもよい名前

HTMLからServletへ:nameとgetParameterの引数を揃える

<input id="quantityField" name="quantity" type="text">
String text = request.getParameter("quantity");

ここで対応するのは、二つの"quantity"です。id="quantityField"はHTML内で要素を識別するための指定、textは取り出した値を受け取るJavaの変数名です。idや変数名をquantityに揃えなくても取得できます。

ServletからJSPへ:setAttributeとgetAttributeの属性名を揃える

request.setAttribute("quote", result);
QuoteResult displayResult = (QuoteResult) request.getAttribute("quote");

こちらで対応するのは、二つの"quote"です。Servletの変数名がresult、JSPの変数名がdisplayResultでも、同じリクエストのquote属性からオブジェクトを取り出せます。変数名を同じにしたからデータが届くのではなく、属性名を指定して取り出しているのです。

JSPの変数名をdisplayResultにする場合、そのJSPで値を表示する箇所もdisplayResult.getTotal()のように揃えます。以降の例では、JSP側の変数名もresultとします。

なお、quantityは入力の項目名、quoteは計算結果の属性名です。別の受け渡しなので、この二つまで同じ名前にする必要はありません。

確認に使う例:数量5を送ると合計2800円になる

  • 単価は500円。数量は1〜20の整数を受け付ける。
  • 小計は単価×数量。小計3000円未満なら送料300円、3000円以上なら送料0円。
  • 合計は小計+送料。DBへの保存は行わない。
確認する値数量5の期待値
送信する項目quantity=5
Servletが受け取る文字列“5”
整数へ変換した数量5
小計2500円
送料300円
合計2800円

この例に登場するQuoteModelは送料と合計を計算するクラス、QuoteResultは計算結果を持つDTOです。QuoteResultには小計・送料・合計のフィールドがあり、getSubtotal・getShippingFee・getTotalでそれぞれの値を取り出せるものとします。授業のファイル名を知っている必要はありません。

正常な受け渡しの例:QuoteServlet.javaの処理の抜粋

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

QuoteModel model = new QuoteModel();
QuoteResult result = model.calculate(quantity);

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

ここでは有効な数量5が送られた場合に絞っています。文字列”5″を整数5へ変換し、Modelの計算結果をquote属性でJSPへ渡します。実際の入力チェックは、HTML・Servlet・Modelの入力チェックの記事で扱っています。

画面に2500円と出ても、それだけでは「送料を加え忘れた」のか「合計欄に小計を表示した」のか分かりません。同じ症状でも原因は複数あります。コードをあちこち変える前に、どこまで期待値と一致しているかを確かめます。

イラスト:名前の対応を、データの流れの中で確認する

数量5をフォームから送信。Servletで文字列5を整数に変換し、Modelが小計2500・送料300・合計2800のDTOを返す。Servletはquote属性に結果を入れてJSPへforwardし、JSPが合計2800をHTMLとして表示する。
Modelの結果はServletへ戻ります。Servletがリクエスト属性に入れ、JSPへ処理を渡します。

図では、送信するquantity=5と①のgetParameter("quantity")、③のsetAttribute("quote", result)と④のgetAttribute("quote")を見比べてください。データが受け渡されるところで、どの名前が対応しているかがポイントです。②はModelで計算する処理です。

ケース1:nameとgetParameterの名前が違う

入力欄には5を入れたのに、Servletでtextがnullになっていました。次の組み合わせを見てください。

HTMLはquantityという名前で送信

<input name="quantity" type="text">

間違い:Servletはcountという名前で取得

String text = request.getParameter("count");

画面に書かれた「数量」というラベルやidではなく、nameの値が送信する項目の名前になります。quantityとして送った値をcountで探しても取得できず、textはnullになります。入力した5が空になったのではなく、違う名前の項目を探しています。

修正:取得する名前をquantityに揃える

String text = request.getParameter("quantity");

quantity=5が送信されている条件なら、修正後に取得するtextは文字列”5″です。自分のコードで名前を揃えてもnullになる場合は、送信元のフォーム・nameの有無・disabledの指定も確認します。名前が合っているだけで必ず送信されるわけではありません。

状態意味まず見る場所
textがnullその名前のパラメータがないHTMLのnameとgetParameterの引数、実際の送信内容
textが空文字パラメータはあるが値が空入力欄と送信した値
textが”5″今回の受け取りは期待どおり整数へ変換した後とModelの呼び出し

ケース2:setAttributeとgetAttributeの名前が違う

Servletの出力にはtotal=2800があるのに、JSPではquote is null=trueでした。

Servlet:quoteという属性名で保存

request.setAttribute("quote", result);

間違い:JSPではresultという属性名を探している

QuoteResult result = (QuoteResult) request.getAttribute("result");

Javaの変数名resultと、属性名”quote”は別です。setAttributeの第1引数が受け渡し用の名前、第2引数が渡すオブジェクトです。JSPの変数名をresultにしても、属性名が自動でresultになるわけではありません。

修正:getAttributeでもquoteを指定

QuoteResult result = (QuoteResult) request.getAttribute("quote");

属性名が違えばresultはnullです。nullを確認して案内文を出すJSPもあれば、確認せずresult.getTotal()を呼んでNullPointerExceptionになるコードもあります。取得できない原因と、そのあと画面に何が出るかは分けて考えます。

名前が一致している場合は、setAttributeの行を通っているか、JSPへforwardしているか、結果JSPを直接開いていないかを確認します。リダイレクト後の新しいリクエストには、元のリクエスト属性はそのまま残りません。

ケース3:表示したい項目とgetterの名前が合っていない

Servletの合計も、JSPで確認したresult.getTotal()も2800です。それでも画面の合計欄が2500円なら、次に見るのはその欄の表示コードです。

間違い:合計欄で小計のgetterを呼んでいる

<p>合計: <%= result.getSubtotal() %>円</p>

修正:合計のgetterを使う

<p>合計: <%= result.getTotal() %>円</p>

getSubtotalは小計、getShippingFeeは送料、getTotalは合計を返します。表示が間違っていてもDTOの値は正しいため、ここでModelの計算式を変えると、別の場所まで壊してしまいます。

何の名前か分かるように付け、変更するときは対応先も確認する

名前は一致していれば何でも読みやすい、というわけではありません。quantityなら数量、shippingFeeなら送料、totalなら合計だと読み取れます。用途の違う値をすべてdataやvalueと呼ぶより、何を受け取り、何を表示しているのか分かる名前にすると、間違いを見つけやすくなります。

たとえばフォームのnameをquantityからcountへ変更するなら、ServletのgetParameterもcountへ変更します。quote属性を別名にするなら、setAttributeとgetAttributeの両方を確認します。一方、Javaのローカル変数を改名する場合は、その変数を使っているコードを揃えます。変数の改名と、受け渡しに使う文字列の変更は別の作業です。

途中の値を出力すると、名前の間違いをどう見つけられる?

System.out.println()で途中の値を出力すると、どこまでデータが届いているかを調べられます。ここでは確認するコードと出力例を並べるので、実行環境がなくても読み比べられます。

出力先はブラウザのConsoleではなく、Javaを実行しているTomcat側のコンソールです。自分の学習用アプリを調べる場合は、一時的に確認行を入れて使えます。実際の顧客情報・パスワードなどは出力せず、調査が終わったら確認用の行を外します。

Servlet:値を取得した直後

String text = request.getParameter("quantity");
System.out.println("Servlet text=[" + text + "]");

角括弧は確認用の目印です。[5]なら文字列5、[]なら空文字です。[null]ならパラメータが存在しない可能性があります。ただし文字列としてnullを送っても同じ表示になるため、必要ならtext == nullも確認します。

Servlet:Modelを呼ぶ直前と、結果が返った直後

System.out.println("Servlet quantity=" + quantity);
result = model.calculate(quantity);
System.out.println("Servlet subtotal=" + result.getSubtotal());
System.out.println("Servlet shippingFee=" + result.getShippingFee());
System.out.println("Servlet total=" + result.getTotal());

JSP:属性を取り出した直後

<%
    QuoteResult result = (QuoteResult) request.getAttribute("quote");
    System.out.println("JSP quote is null=" + (result == null));
    if (result != null) {
        System.out.println("JSP total=" + result.getTotal());
    }
%>

JSPのこの部分もサーバー側のJavaとして実行されるため、出力先はTomcat側です。result == nullがtrueなら、ここではDTOを取得できていません。nullのままgetterを呼ばないよう、確認してから値を読みます。

数量5を送ったときの正常な出力

Servlet text=[5]
Servlet quantity=5
Servlet subtotal=2500
Servlet shippingFee=300
Servlet total=2800
JSP quote is null=false
JSP total=2800

ServletやJSPという文字を付けているのは、どこで確認した値かを見分けるためです。図の①で受け取った値、②の計算を呼ぶ前後、③を経て④で取り出した値の順に照合します。

名前が合っている場合は、実際に渡す値も確認する

Servletでは文字列5を受け取り、整数のquantityも5でした。しかしModelから返った小計は500円、合計は800円です。

System.out.println("Servlet quantity=" + quantity);
result = model.calculate(1);

変数quantityを表示していても、実際の引数が1なら、Modelには1が渡ります。「quantityは5だった」で確認を終えず、呼び出す行そのものを見ます。

修正:受け取った数量を引数にする

result = model.calculate(quantity);

quantityを渡せば、数量5なら合計2800円、数量6なら合計3000円というように、入力に応じた結果になります。calculate(5)に直すだけでは、別の数量を送っても5として計算するため、修正にはなりません。

引数が正しいのに結果が違う場合は、Modelの中を見る

int subtotal = 500 * quantity;
int shippingFee = 300;
if (subtotal >= 3000) {
    shippingFee = 0;
}
int total = subtotal; // 間違い:送料を足していない

この場合、数量5の小計2500円と送料300円は正しくても、合計が2500円です。修正箇所はJSPではなく、int total = subtotal + shippingFee;の行です。DTOへ渡すコンストラクタの引数も、new QuoteResult(subtotal, shippingFee, total)の順になっているか確認します。

症状ではなく、途中の値を根拠に修正箇所を決める

数量5を送ったときの観察次に調べる場所
Servletでtextがnullフォームの送信項目とgetParameter
quantityは5なのにModelの小計が500calculateに実際に渡した引数
小計2500・送料300なのにDTOの合計2500Modelの加算、DTOのコンストラクタとgetter
Servletの合計2800、JSPのresultはnull属性名・設定の有無・forward・開いたURL
JSPのgetTotalは2800、画面の合計2500合計欄が使うgetterと、表示しているJSP

これは今回の例で調べる順番であり、症状だけで原因を断定する表ではありません。正常な値が確認できた場所と、違いが確認できた場所の間を調べることで、確認するコードを狭められます。

読み比べる練習:どこで値が変わったかを追う

  1. ケース1〜3から一つを選び、渡す側と取り出す側の名前を紙などに並べる。
  2. 本文の取得値や表示結果を見て、どこまでは期待どおりだったかを示す。
  3. 直す箇所を一つ決め、その修正で結果がどう変わるか説明する。
  4. 数量が5ではなく6だった場合も、正しい結果になる修正か考える。
入力小計送料合計
1500300800
525003002800
6300003000
2010000010000

数量6は送料が0円になる境目です。上の仕様を使って小計・送料・合計を計算し、表と比べられます。数量5の結果だけを固定して返す修正では、他の数量で正しい値にならないことが分かります。

確認問題

数量5で小計2500円・送料300円・合計2800円になる仕様です。特に「どの名前同士を対応させるか」に注目して答えてください。

  1. HTMLはid="quantityField" name="quantity"です。ServletではgetParameter("count")を使い、値を取得できませんでした。どの名前に合わせて直しますか。idに合わせない理由も答えてください。
  2. ServletはsetAttribute("quote", result)で結果を保存しています。JSPのQuoteResult displayResult = (QuoteResult) request.getAttribute("quote");は、変数名が違っても取得できますか。同じリクエストでforwardしているものとして、理由を答えてください。
  3. ServletはsetAttribute("quote", result)を実行し、同じリクエストでJSPへforwardしています。JSPはgetAttribute("result")で取得してnullになりました。修正する名前を答えてください。また、保存する属性名をorderQuoteに変更するなら、どこを変更しますか。
  4. JSPの確認出力はtotal=2800ですが、画面の合計欄には2500円と出ます。合計欄のコードがgetSubtotal()を呼んでいる場合、どのメソッド名へ直しますか。理由も答えてください。

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

まとめ

  • データの受け渡しでは、渡す側と取り出す側の名前を対応させる。
  • HTMLのnameとgetParameterの引数、setAttributeとgetAttributeの属性名をそれぞれ揃える。
  • HTMLのid・Javaの変数名・受け渡し用の名前を混同しない。すべてを同じ名前にする必要はない。
  • 値の意味が分かる名前を付け、名前を変更するときは対応先も確認する。
  • 名前の対応を確認したうえで、実際に取得した値と表示結果を確かめる。

関連記事

シェアする