前回は、ログイン処理の実装とセッションスコープについて学びました。

属性を扱うのも2つ目でしたから、その概念には慣れてきたのではないでしょうか?

今回は、JavaBeansについて学びます。この章の結論を先取りすると、JavaBeansを使うとプロパティというものを使ってクラスのフィールド(インスタンス変数)にアクセスできるため積極的に使いましょうということです。

1. JavaBeansとは

JavaBeansとは再利用可能なJavaプログラムの部品です。JSPのELと相性が良いのがこのJavaBeansなのです

Javaというネーミングはそもそもジャワコーヒーから来ているのですが、その豆【Beans】ということで部品という意味です。また、新種の「オブジェクトを入れる箱」が出てきたのだとお考えください。


ここでもまた、数あてゲームについて考えてみましょう。

2章で見た数当てゲームの概念図を再掲すると下図6.1の通りでした。

本当はリクエスト属性にはインスタンスが入れられるにも関わらず、文字列と数値を入れていましたね。

kazuate1
図6.1 第2章の数当てゲーム

まずは、これを下図6.2の形に変えます。

この形を今回「属性にオブジェクトを入れるがJavaBeansは使わないケース」と呼びたいと思います。すなわち、リクエスト属性には1つのインスタンスだけを入れて持ち運ぶこととします。それに伴い、ELの表記が変化する点にもご注目ください。

kazuate2
図6.2 リクエスト属性にはインスタンスを入れるようにした
  • 違い探しクイズ 図6.2は、図6.1とどこが違いますか?(ヒント:3箇所違います)
あなたの答え:

そしてさらにJavaBeansを使うことで最終形態の下図6.3に変えます。ここでもまた、ELの表記が変化した点に注目ください。

kazuate_javaBeans
図6.3 JavaBeanを使った数当てゲーム
  • 違い探しクイズ 図6.3は、図6.2とどこが違いますか?(ヒント:2箇所違います)
あなたの答え:

調べてみましょう

今回のJavaBeansのようにデータを持ち運ぶためのオブジェクトのことをDTO【Data Transfer Object】と呼ぶこともあります。DTOについて調べてみましょう。

調べたことのメモ:

1.1 2章で解説した数当てゲーム(再掲)

復習です。以下の4つのファイルを使ってどのようなことをしているか説明してください。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
    <head>
       <meta charset="UTF-8">
        <title>index.jsp</title>
    </head>
    <body>
        <h3>0-9の整数で数を当ててください!</h3>
        <form method="get" action="${pageContext.request.contextPath}/Game">
            <input type ="text" size ="10" name ="guess">
            <button type="submit">送信</button>
        </form>
    </body>
</html>
package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import model.Kazuate;

@WebServlet(urlPatterns = { "/Game" })
public class GameServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String str = request.getParameter("guess");
		int guess = Integer.parseInt(str);

		Kazuate kazuate = new Kazuate();

		request.setAttribute("message", kazuate.checkTheAnswer(guess));
		request.setAttribute("answer", kazuate.getTheAnswer());

		request.getRequestDispatcher("/view/result.jsp").forward(request, response);

	}
}
package model;

import java.util.Random;

public class Kazuate {

	private int answer;

	public Kazuate() {
		Random random = new Random();
		this.answer = random.nextInt(10);
	}

	public String checkTheAnswer(int guess) {

		if (answer == guess) {
			return "あたり";

		} else if (answer > guess) {
			return "もっと大きいよ";

		} else {
			return "もっと小さいよ";
		}
	}

	public int getTheAnswer() {
		return this.answer;
	}

	public void setTheAnswer(int answer) {
		this.answer = answer;
	}
}
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<meta charset=UTF-8">
<title>result.jsp</title>
</head>
<body>
	<h1>ゲームの結果は!</h1>
	${requestScope.message}
	<br> 答えは${requestScope.answer}
</body>
</html>

1.2 属性にオブジェクトを入れるがJavaBeansは使わないケース

2章で解説した数当てゲームでは上図6.1 のとおり、リクエスト属性には2つのインスタンス(String型の文字列とInteger型の整数)を入れていました。しかし、この後、例えば、データベースで扱ったCustomerを扱うような場合に、名前、メールアドレス、住所、誕生日やポイント数などテーブルの列数だけインスタンスを属性に詰め込まなければいけないとしたら大変です。

顧客一人分のすべての情報を持ったCustomerクラスを一度にJSPに送って表示できたら便利だと思いませんか?

さらには複数のCustomer(今井さん、篠原さん、田渕さん、松田さんなど)のデータの入ったCustomersクラス(複数形)があって、データベースから全員分のデータを取得して一度にJSPに送ってELで表示できるとしたらどうでしょうか?

そこで、リクエスト属性にはオブジェクトをまるごと入れるというのが前述の図6.2でした。

以下にそれをソースコードで実現します。

そのためには、kazuateクラスを以下のようにkazuateObjectクラスに変えます。

package p06;

import java.util.Random;

public class KazuateObject{

    private int answer;
    private String message;

    public KazuateObject() {
        Random random = new Random();
        this.answer = random.nextInt(10);
    }

    public void checkTheAnswer(int guess) {

        if (answer == guess) {
            this.message = "あたり";

        } else if (answer > guess) {
            this.message = "もっと大きいよ";

        } else {
            this.message = "もっと小さいよ";
        }
    }

    public int getAnswer() {
        return answer;
    }

    public void setAnswer(int answer) {
        this.answer = answer;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  • kazuate.javaとkazuateObject.javaを比較した場合フィールドの観点での変更点は何ですか?
あなたの答え:
  • 同様にメソッドの観点での変更点は何ですか?
あなたの答え:

answerとmessageをフィールドとしてインスタンス自身が持ち運ぶようにしたわけですね。

サーブレットは以下GameServlet5.javaのようになります。

package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import model.KazuateObject;

@WebServlet(urlPatterns = { "/Game5" })
public class Game5Servlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String str = request.getParameter("guess");
		int guess = Integer.parseInt(str);

		KazuateObject kazuate = new KazuateObject();
		kazuate.checkTheAnswer(guess);

		request.setAttribute("kazuate", kazuate);

		request.getRequestDispatcher("/view/result1.jsp").forward(request, response);

	}
}

JSPは以下result1.jspのようになります。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=UTF-8">
        <title>result1.jsp</title>
    </head>
    <body>
        <h1>ゲームの結果は!</h1>
        ${kazuate.getMessage()}<br>
        答えは${kazuate.getAnswer()}<br>
    </body>
</html>

EL式でインスタンスのメソッドを呼び出しています。これがこの後、JavaBeansを使うことでどのように変わるかに期待してください。

特に新しい論点はありませんが、入力画面index1.jspのindex.jspからの変更部分を以下に掲載します。

<form method="get" action="${pageContext.request.contextPath}/Game5">

1.3 JavaBeansを使うケース

まず最初に結論としてJavaBeansを使った場合のソースコードを紹介します。

以下のKazuateBean.javaはMVCのうちのモデルに該当します。

package model;

import java.io.Serializable;
import java.util.Random;

public class KazuateBean implements Serializable {

	private int answer;
	private String message;

	public KazuateBean() {
		Random random = new Random();
		this.answer = random.nextInt(10);
	}

	public void checkTheAnswer(int guess) {

		if (answer == guess) {
			this.message = "あたり";

		} else if (answer > guess) {
			this.message = "もっと大きいよ";

		} else {
			this.message = "もっと小さいよ";
		}
	}

	public int getAnswer() {
		return answer;
	}

	public void setAnswer(int answer) {
		this.answer = answer;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

今までのKazuateObjectクラスとの変更点を見つけましょう。

  • 実装しているインタフェースの観点での変更点は何ですか?
あなたの答え:

JavaBeansがJavaBeansであるためには、以下の3つのルールを適用する必要があります

  • java.io.Serializableを実装している
  • 引数のないコンストラクタを持つ
  • フィールドはprivateにした上でプロパティへのgetter/setterメソッドを持つ

以下に順を追って解説します。

1.3.1 JavaBeansはjava.io.Serializableを実装している

Serializableインタフェースを実装することでスコープ内のインスタンスを直列化してファイル等に保存したり、ネットワークを介して送信したりすることができるようになります。

インスタンスを直列化してファイルに保存できれば、

  • Tomcat等のアプリケーションサーバーが停止した場合も再起動してインスタンスを復元できます
  • 一時的にメモリが溢れた場合にも退避させることができます

なお、 Serializableインタフェースはマーカーインターフェイスだということを忘れてしまった方はJavaSEのインタフェースのところで復習しておいてください。

1.3.2 JavaBeansは引数のないコンストラクタを持つ

今回のKazuateBeanクラスでは、たまたま引数のないコンストラクタがありましたので特に変更点はありません。

「コンストラクタの無いJavaクラスには見えてないけれどデフォルトコンストラクタがあるはず」そう思った方はコンストラクタの事がよく分かっています。その場合はデフォルトコンストラクタで結構です。

しかし、もしも引数のあるコンストラクタを作った場合はデフォルトコンストラクタは作られません。その場合は引数のないコンストラクタをあらためて作る必要があります。

1.3.3 JavaBeansはフィールドはprivateにした上でプロパティへのgetter/setterメソッドを持つ

上記のKazuateBeanクラスには2つのフィールド(answerとmessage)がありました。

getter/setterメソッドとは、getXxxx、setXxxx としたフィールドにアクセスするためのメソッドのことです。(上記KazuateBeanのgetAnswer()がその例になります)

プロパティというのはこのメソッドの Xxxxの最初を小文字にしてxxxxとしたものです 。英語の【property】には資産という意味があります。(後で見るresult2.jspのkazuate.messageがその例です)

プロパティ化したいメソッド名の先頭にはget/setを付けるというルールがあります。(本当はxxxのところにはフィールド名以外が来てもよいのですが、当社の新人エンジニア研修ではメソッドのXxxxのところには全てフィールド名を入れることにしますので、その限りにおいてフィールドとプロパティの間に特段の区別は必要ありません)


なお、getter/setterメソッドはアクセサメソッド【accessor methods】と総称されることもあります。アクセサメソッドでは単にフィールドの値を設定取得するだけでなく、加工することもできます。アクセサメソッドはIDEを使っていれば簡単に挿入できますので方法を講師にお尋ねください。

フィールドを private で修飾するとクラス外からはアクセスできなくなるということを我々はカプセル化と情報隠蔽のところで学びました。そこでアクセサメソッドを使ってフィールドにアクセスします。


ここである程度勉強している人は「情報隠蔽をしてもアクセサメソッドがあれば同じではないか?」と考えるかも知れません。しかし、思い出していただきたいのですが、int型のフィールドにはint型の値であれば何でも入ったのに対して、メソッドを経由すれば、特定の値(例えば、マイナスの値)は入らないようにすることもできましたね。さらに、フィールドに読み込みだけ可能で、一切書き込みができないクラスを作りたいのであれば、setterメソッドを作らなければよいのです。

なお、このプロパティの考え方は他のオブジェクト指向言語にも発展的に採用されています。例えばC#、Python、Kotlin等のプロパティはより便利に使えるようになっています。

2. JavaBeansを活用した数あてゲーム

では、早速Beansを使ったプログラムがどう変わるかを見てみましょう。

入力画面<index2.jsp>は<index1.jsp>から飛び先以外大きな変更はありません。

以下のGameServlet6.javaを読み込んで質問に答えてください。

package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import model.KazuateBean;

@WebServlet(urlPatterns = { "/Game6" })
public class Game6Servlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		int guess = Integer.parseInt(request.getParameter("guess"));

		KazuateBean kazuate = new KazuateBean();
		kazuate.checkTheAnswer(guess);
		request.setAttribute("kazuate", kazuate);

		request.getRequestDispatcher("/view/result2.jsp").forward(request, response);

	}
}
  • 「request.setAttribute("kazuate", kazuate);」でリクエストスコープに格納しているのは何ですか?
あなたの答え:

以下のresult2.jspはゲームの結果を表示するビューです。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=UTF-8">
        <title>result2.jsp</title>
    </head>
    <body>
        <h1>ゲームの結果は!</h1>
        ${kazuate.message}<br>
        答えは${kazuate.answer}<br>
    </body>
</html>

このELで利用しているのがプロパティです。

プロパティを使ったELの書式

${オブジェクト名.プロパティ名}

kazuate.messageと書くことによってkazuateクラスのインスタンスのgetMessageメソッドが働くのです。

  • では、${kazuate.answer}という記述によって働くのはどのクラスの何というメソッドですか?
あなたの答え:

例題1

以下のCustomersBean.javaというJavaBeansを使って、サーブレットでインスタンスを作成し、JSPで内容を表示させなさい。

なお、toStringメソッドを忘れてしまった方はJavaSEに戻って「toStringメソッドで出力内容をコントロールする」というテーマを復習してください。

//適切なパッケージを用意すること

import java.io.Serializable;

public class CustomerBean implements Serializable {

    private int customerId;
    private String name;
    private int prefectureId;
    private String address;
    private String birthday;
    private int points;

    public CustomerBean() {
    }

    public int getCustomerId() {
        return customerId;
    }

    public void setCustomerId(int customerId) {
        this.customerId = customerId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrefectureId() {
        return prefectureId;
    }

    public void setPrefectureId(int prefectureId) {
        this.prefectureId = prefectureId;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    public int getPoints() {
        return points;
    }

    public void setPoints(int points) {
        this.points = points;
    }

    @Override
    public String toString() {
        return "CustomerBean{" + "customerId=" + customerId + ", name=" + name + ", prefectureId=" + prefectureId
                + ", address=" + address + ", bitrthday=" + birthday + ", points=" + points + '}';
    }
}

<CustomerServlet.java>を作成してください。

下図6.3のイメージの<show_customer.jsp>を作成してください。

図6.3 出力イメージ

なお、JavaBeansはこの例のように冗長なコードになりがちです。できるだけコードをシンプルに保ちたい方は次の「定形コードを書かないで済むようにLombokを使いたい」という記事を参照してください。

例題2

上記 <show_customer.jsp> に以下のようなELを書き入れた場合の実行結果を予想しなさい。

${requestScope.customer}

この実行結果はテストする際に有効であることを銘記してください。

例題3

例題1のCustomerBeanのリストをフィールドに持つ、以下のCustomersBean.クラスを使ってサーブレットでインスタンスを作成し、JSPで内容を表示させなさい。

//適切なパッケージを用意すること

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class CustomersBean implements Serializable {

    private List<CustomerBean> customerArrayList = new ArrayList<>();

    public CustomersBean() {
    }

    public List<CustomerBean> getCustomerArrayList() {
        return customerArrayList;
    }

    public void setCustomerArrayList(List<CustomerBean> customerArrayList) {
        this.customerArrayList = customerArrayList;
    }

    public void addCustomer(CustomerBean aCustomer) {
        this.customerArrayList.add(aCustomer);
    }

    @Override
    public String toString() {
        return "CustomersBean{" + "customerArrayList=" + customerArrayList + '}';
    }
}

なお、出力は以下のshow_customers.jspを使うこと。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=UTF-8">
        <title>show_customers.jsp</title>
    </head>
    <body>
        <h1>顧客情報</h1>          
       ${requestScope.customers}
    </body>
</html>

出力は下図6.4の通りです。

顧客情報
図6.4 出力イメージ

この表示を綺麗に整えたい方は次章までお待ち下さい。

3. 買い物かごの実装

ここまでの知識を応用して買い物かごの仕組みを作ってみましょう。どんな知識を使うかといえば主にセッション属性とJavaBeansです。

目指したいのは下図6.5のような仕組みです。

ポイントは赤で表示されたcartオブジェクトです。このオブジェクトには複数のcarオブジェクトが入るとします。カートの合計金額(変数名:total)はどのオブジェクトが持つべきでしょうか?

そうですね、個々の車に持たせるのは変ですからcartが持つべきでしょうね。また、このcartは車の追加や削除ができないといけません。

セッション属性を使ったカートの仕組み
図6.5 セッション属性を使ったカートの仕組み

以下のCarBean.javaは車のクラスです。セッションスコープに入れることを見越してBeansとして作成します。特に特徴のないアクセサメソッドを持つBeansですが、このあとの説明のためインスタンスを1行で作れるように引数を2つ持つコンストラクタも作成しておきました。

CarBeanクラス

package model;

import java.io.Serializable;

public class CarBean implements Serializable {

    private String name;
    private int price;

    public CarBean() {
        super();
    }

    public CarBean(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

Cartクラス

以下のCart.javaを読み込んで質問に答えてください。

package p06;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Cart implements Serializable {

    private List<CarBean> list;

    public Cart() {
        list = new ArrayList<>();
    }

    public void addCar(CarBean aCar) {
        this.list.add(aCar);
    }

    public List<CarBean> getList() {
        return list;
    }

    public int getTotal() {
        int total = 0;

        for (CarBean carBean : list) {
            total += carBean.getPrice();
        }
        return total;
    }

    public List<CarBean> remove(String name) {
        Iterator<CarBean> ite = list.iterator();

        while (ite.hasNext()) {
            CarBean car = ite.next();
            if (car.getName().equals(name)) {
                ite.remove();
            }
        }
        return list;
    }
}
  • このCartクラスのフィールドとメソッドのそれぞれの役割を答えなさい。
あなたの答え:



削除のメソッドにはイテレータというものを使っています。

なぜ、イテレータが必要なのかという点に関しては、JavaSEのArrayListのところで解説しました。忘れてしまった人は見直しておいてください。


以下のCartTest.javaを読み込んで質問に答えてください。

package test;

import model.CarBean;
import model.Cart;

public class CartTest {

	public static void main(String[] args) {

		CarBean c1 = new CarBean("セダン", 2590000);
		CarBean c2 = new CarBean("クーペ", 4990000);
		CarBean c3 = new CarBean("クーペ", 4990000);

		Cart cart = new Cart();

		cart.addCar(c1);
		cart.addCar(c2);
		cart.addCar(c3);

		for (CarBean car : cart.getList()) {
			System.out.print(car.getName() + ":");
			System.out.println(car.getPrice());
		}
		System.out.print("合計:");
		System.out.println(cart.getTotal());

		cart.remove("クーペ");

		for (CarBean car : cart.getList()) {
			System.out.print(car.getName() + ":");
			System.out.println(car.getPrice());
		}
		System.out.print("合計:");
		System.out.println(cart.getTotal());

	}
}
  • 上記の処理を解説しなさい。
あなたの答え:



これをサーブレットとJSPを使って実現できれば良いわけですね。

そのためにはJSPに繰り返しや条件分岐を使って表示できたほうが良いので先に次章でJSTLを学ぶことにします。

この章ででてきたCustomerクラスやCustomersクラス、CarクラスやCartクラスは次の章でも使いますので、しっかり復習しておいてください。

また、教室では「addCartServlet.java」でカートへの追加、「RemoveCartServlet.java」でカートからの削除のサンプルコードを配布しています。余裕があれば説明を受けてください。

調べてみましょう

セッションを利用するカートの内容はブラウザを閉じたりログアウトすると消えてしまいます。

その理由を説明してください。

調べたことのメモ:


カートの内容を永続的に保存する方法を3つ挙げてください。

調べたことのメモ:

まとめ

□ JavaBeansとは再利用可能なJavaプログラムの部品であり、リクエスト/セッションスコープに入れてデータを持ち運ぶのに便利である

□ JavaBeansには①java.io.Serializableを実装している②引数のないコンストラクタを持つ③フィールドはprivateにした上でプロパティへのgetter/setterメソッドを持つという3つのルールがある

□ プロパティ化したいメソッド名の先頭にget/setを付けるというルールがある

□ プロパティはアクセサメソッドのget/setXxxxの最初を小文字にしてxxxxとしたものである

□ ELでプロパティを扱う場合の書式:${オブジェクト名.プロパティ名}

今回はJavaBeansについて学びました。

次回はELとJSTLで様々な表現ができるようになる方法を学びます。

「JavaBeansを使い再利用できるオブジェクトにする」 最後までお読みいただきありがとうございます。