JavaのServletからModelクラスを呼び出すには?入力値を渡して処理結果を受け取る方法

最終更新日

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

「ServletからModelを呼び出す」と聞くと、Web専用の難しい仕組みが必要に感じるかもしれません。今回行うのは、別のJavaクラスのメソッドに購入金額を渡し、割引額を戻り値として受け取ることです。

割引の条件をServletに直接書かず、計算を担当するクラスへまとめると、同じルールを別の画面から呼んだり、ブラウザを使わずに確かめたりできます。この記事では、Modelの意味から、実際に値を受け渡すコードまで順に見ていきます。

Schoo「Java入門 中級」第5回「サーバーサイドアプリケーションの実装基礎」に対応しています。未受講の方も、クラス・引数・戻り値の基本を確認しながら読み進められます。

そもそもModelとは?

Modelは、アプリケーションで扱うデータや、計算・判定などの業務上のルールを担当する部分です。購入金額から割引額を決める、在庫が注文数量を満たすか確認する、といった処理が含まれます。こうしたアプリ固有のルールを「ビジネスロジック」と呼びます。

ModelはJavaの予約語や特別な構文ではなく、MVCにおける役割の名前です。今回は、その役割のうち割引計算を担当する普通のJavaクラスを DiscountModel と名付けます。Model という名前を付けたから自動で動くわけではありません。

よくある疑問今回の考え方
Modelはデータを入れる箱だけ?いいえ。データだけでなく、割引の判定・計算などもModel側の仕事です。
データベースがないと使わない?DBを使わない今回の割引計算でも、Modelの役割があります。
クラス名にModelは必須?必須ではありません。DiscountCalculatorなど、仕事が分かる名前でも構いません。
Modelは必ず1つのクラス?いいえ。規模に応じてデータを持つクラス、業務を処理するService、DB操作のDAOなどに分けます。

ここではMVC全体を作り直すのではなく、Servletが処理を依頼し、Modelから結果が戻る部分に注目します。

今回作るもの:購入金額から割引額を求める

  • 購入金額が10,000円以上なら、割引額は500円。
  • 10,000円未満なら、割引額は0円。
  • 購入金額は0円以上の整数。負の金額は受け付けない。
  • 画面には、購入金額と割引額を表示する。

たとえば12,000円なら500円引きです。戻り値は「割引後の金額11,500円」ではなく、差し引く割引額500円に統一します。まず何を渡し、何を返すメソッドなのかを決めておくと、呼び出す側が迷いません。

担当今回すること
Servlet送信された文字列を受け取り、整数へ変換し、Modelを呼ぶ。結果を表示側へ渡す。
Modelクラス整数の購入金額を受け取り、割引額を返す。
JSPServletから渡された購入金額と割引額をHTMLに表示する。

イラストで見る:引数を渡し、戻り値を受け取る

DiscountServletのcalculateDiscount(amount)から引数12000をDiscountModelへ渡す青い矢印。Modelのreturn 500からServletのdiscountへ戻り値500を返す緑の矢印。
青は引数、緑は戻り値。図は正常な入力の呼び出し部分を抜き出したもので、金額の取得と入力チェックは後のコードに示します。
int amount = 12000;
DiscountModel model = new DiscountModel();
int discount = model.calculateDiscount(amount);
  1. new DiscountModel() で、割引計算を呼び出すためのインスタンスを作る。この時点では割引計算はまだ行わない。
  2. model.calculateDiscount(amount) でメソッドを呼ぶ。Servlet側の amount の値12,000が、メソッドの引数へ渡る。
  3. Modelは amount >= 10000 を判定し、return 500; で500を呼び出し元へ返す。
  4. 呼び出し式の結果500が、int discount = ... の左側の変数へ代入される。その後、Servletの次の行へ進む。

同じ名前の amount を使っていますが、Servlet側の変数とModel側の引数は別の変数です。int型では値が渡されます。Modelの引数名を変えても、型や引数の順番が合っていれば呼び出せます。

return は画面への表示ではありません。Modelが500を返しても、それだけでブラウザには表示されません。Servletが戻り値を受け取り、表示する処理へ渡す必要があります。

1.Modelクラスに割引のルールを書く

src/main/java/example/DiscountModel.java を作成します。以降はJava 17・Tomcat 10.1の jakarta.servlet を使う構成です。ファイルはUTF-8で保存します。

package example;

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

calculateDiscount(int amount)int amount が受け取る金額です。メソッド名の前の int は、結果を整数で返すことを表します。return 500; が実行されたらメソッドはそこで終了するため、最後の return 0; までは進みません。

このクラスには requestresponse も登場しません。フォームの入力項目名や、表示するJSPの名前を知らなくても計算できる形です。負の金額を拒否するのは、この処理が受け付ける値のルールなのでModel側で確認します。

2.ServletからModelを呼び出す

src/main/java/example/DiscountServlet.java を作成します。Modelと同じ example パッケージなので、DiscountModelのimportは不要です。別パッケージに置く場合は、そのクラスのimportなどを追加します。

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);
    }
}

呼び出しの中心は int discount = model.calculateDiscount(amount); です。Servletは割引の条件をもう一度判定せず、Modelが返した割引額を受け取ります。

コード12,000を送ったとき
request.getParameter("amount")String型の "12000" を取得する。
Integer.parseInt(text.trim())int型の 12000 に変換する。
model.calculateDiscount(amount)12000を渡し、int型の500を受け取る。
request.setAttribute("discount", discount)結果の500をJSPへ渡せるようにrequestへ保存する。

type="number" の入力欄でも、getParameter() で取得する値は文字列です。Modelが必要とするint型に変換してから渡します。

未入力と数値変換の失敗はServletで扱い、負の金額はModelが例外で通知します。NumberFormatExceptionIllegalArgumentException の子クラスなので、catchは具体的なNumberFormatExceptionを先に書きます。

ここで作るModelの変数や入力値は、doPost内のローカル変数です。Servletのインスタンスは複数のリクエストで使われるため、利用者ごとの入力値をServletのフィールドへ保存しないようにします。

3.入力フォームと結果表示を用意する

src/main/webapp/discount-form.html を作成します。コンテキストパスが /lesson05 なら、http://localhost:8080/lesson05/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>

アプリ直下に配置したこのフォームの送信先は /lesson05/discount です。Servletの @WebServlet("/discount") に対応し、POST送信によってdoPostが動きます。

続いて src/main/webapp/discount-result.jsp を作成します。Servletがrequestに保存した値を取り出して表示します。

<%@ 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>

Servletの forward() は、サーバー内でJSPへ処理を引き継ぐためのものです。Modelのreturnとは役割が異なります。Modelの戻り値を受け取ったあと、Servletがrequestに値を保存してからJSPへ進みます。

getAttribute() はObject型の値を返すため、ここではStringやIntegerとして取り出します。int型の値をsetAttributeへ渡すとIntegerとして扱われます。JSPは割引の基準を判定せず、計算済みの割引額だけを表示します。

この画面に埋め込んでいるのは、数値とServletで決めた固定メッセージだけです。入力文字列そのものは表示しません。商品名など外部から届く文字列を表示する場合は、HTML用のエスケープが必要です。

フォームから動かして、境界と値の流れを確かめる

入力表示される割引額
00円
99990円
10000500円
12000500円

10,000円以上という条件なので、9,999と10,000で結果が変わります。アドレスバーから /discount へ直接アクセスするとGETになるため、この例ではフォームを開いて送信してください。結果のJSPを直接開いた場合は、計算値がないので入力画面を案内します。

HTMLのrequiredやminはブラウザ上で入力を助けるものです。それだけに頼らず、フォーム以外からリクエストが来ても、サーバー側で未入力・整数以外・範囲外・負の値を扱えるようにしています。

Modelなら、ブラウザを使わず同じ計算を呼べる

src/main/java/example/DiscountCheck.java を作成し、通常のJavaアプリとしてmainを実行してみます。

package example;

public class DiscountCheck {
    public static void main(String[] args) {
        DiscountModel model = new DiscountModel();
        System.out.println(model.calculateDiscount(9999));  // 0
        System.out.println(model.calculateDiscount(10000)); // 500
    }
}

コンソールには0と500が順に表示されます。Webサーバー、フォーム、JSPを起動しなくても、割引のルールだけを確認できます。これはModelクラスがHTTPではなく、購入金額という値を受け取る形になっているためです。

別の画面からも同じメソッドを呼べば、割引条件をそれぞれのServletにコピーせずに済みます。JUnitのテストでも、この呼び出しで得た戻り値を期待値と比較できます。ただし、計算だけの確認でフォーム送信や画面表示まで検証できたことにはなりません。

戻り値と画面表示を混同しない

model.calculateDiscount(amount);
request.setAttribute("discount", 0);

このコードでは計算は呼ばれますが、返ってきた割引額を使っていません。画面へ渡す値は自分で指定した0のままです。メソッドを呼ぶことと、戻り値を受け取って使うことは別です。

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

また、Model内でSystem.out.printlnを使っても、出力されるのはサーバー側のコンソールであり、ブラウザの画面ではありません。計算結果はreturnで返し、表示はJSPへつなぎます。

今回はクラス間の連携を学ぶための金額入力サンプルです。実際の購入処理では、ブラウザから送られた金額をそのまま請求の根拠にせず、サーバー側の商品情報や数量などから金額を確定します。

確認問題

各問題は、本文の元の仕様「10,000円以上なら500円引き」を出発点として、独立して考えてください。

  1. フォームで12000を送信しました。getParameterで取得する型と値、Modelに渡す型と値、戻り値、Servletのdiscountに入る値を答えてください。
  2. 条件を「15,000円以上なら800円引き、それ未満は0円」に変更します。DiscountModelのどの部分を変えますか。14,999円と15,000円の戻り値も答えてください。
  3. 購入金額が12,000円なのに、割引額が0円と表示されました。直前の「戻り値と画面表示を混同しない」にある誤った2行を、戻り値を使う形に修正してください。
  4. 購入金額が未入力、文字列abc、整数-1だったとき、本文のどの処理が問題を見つけますか。それぞれModelのcalculateDiscountが呼ばれるかも答えてください。

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

まとめ

  • Modelはデータや業務ルールを担当する役割。今回は普通のJavaクラスに割引のルールをまとめた。
  • Servletで文字列を整数に変換し、Modelのメソッドへ引数として渡す。
  • Modelは結果をreturnで返し、Servletが変数に受け取ってJSPへ渡す。
  • HTTPや表示から計算を分けると、別の画面や通常のJavaコードから同じ処理を呼び出せる。

関連する記事

シェアする