前回は、サーブレットからJSPにデータを渡して表示するところまでをやりました。

今回は、多くのWebアプリケーションでその前処理にあたる部分、すなわちフォームからデータをサーブレットに渡すところをやってみます。

1. フォームの復習

HTMLのフォームの復習です。

下図のように新しくプロジェクトを作成しwebappフォルダを用意してその中にウェルカムファイル「index.jsp」を用意してください。

index.jsp

index.jspの内容は以下の通りです。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>index.jsp</title>
</head>
<body>
	<form action="${pageContext.request.contextPath}/Add" method="get">
		<input type="text" name="num1" /><br> 
		<input type="text" name="num2" /><br>
		<button type="submit">送信</button>
	</form>
</body>
</html>
  • ①このフォームのhttpメソッドは何ですか?
あなたの答え:
  • ②このhttpメソッドの指定は省略できますか?
あなたの答え:
  • ③フォームの中の部品はいくつありますか?
あなたの答え:
  • ④それぞれどのような部品でしょうか?
あなたの答え:
  • ⑤「name=」で付けられた名前はサーブレットでどのような用途で使われますか?
あなたの答え:

このname属性に設定された名前はリクエストパラメータとして、文字列でサーブレットに送られます。

送られた文字列をサーブレットで受け取るための記述が「request.getParameter("パラメータ名")」です。

フォームは「何を、どのメソッドで、どのプログラムに送るのか」を書くものだと理解しましょう。上記の例でいえば、「num1,num2という名前のつけられた1行テキストに入力された文字列を、getメソッドで、このプロジェクトの「Add」というurlPatternsのサーブレットに送る」ということが書かれているのですね。

また、viewというフォルダを作成して、その中に以下のans.jspを作成して下さい。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ans.jsp</title>
</head>
<body>
	${requestScope.ans}
</body>
</html>

2. Servletの復習

次に下図のようにServletを用意します。

「java」パッケージの中に「controller」というパッケージを作って「AddServlet.java」を作成して下さい。

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;

@WebServlet(urlPatterns = { "/Add" })
public class AddServlet extends HttpServlet {

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

		int num1 = Integer.parseInt(request.getParameter("num1"));
		int num2 = Integer.parseInt(request.getParameter("num2"));

		request.setAttribute("ans", num1 + num2);
		request.getRequestDispatcher("view/ans.jsp").forward(request, response);
	}
}
  • ①このServletはどのようなurlPatternで呼び出されますか?
あなたの答え:

なお、このテキストでは、サーブレットのファイル名はServletで終え、urlPatternsの表記はファイル名からServletを省いたものを使うルールとしています。上記の例でいえば、ファイル名「AddServlet.java」→urlPatterns「Add」といった具合です。

  • ②リクエストパラメータにはどの様な値がどの様な名前で格納されていますか?
あなたの答え:
  • ③リクエスト属性にはどの様な値がどの様な名前で格納されますか?
あなたの答え:

リクエストパラメータとリクエスト属性は名前が似ているので混同しないように注意しましょう。

  • ④フォワード先のJSPのパス名とファイル名をそれぞれ答えなさい。
あなたの答え:
  • ⑤フォワードした後のアドレスバーの表記はどうなりますか? ただし、フォームにはそれぞれ"1"と"2"を入力したものとします。
あなたの答え:http://localhost:8080/

2.1. フォームからServletを呼び出す際の記述方法

今回のポイントはjspのactionで指定している送信先です。

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

${pageContext.request.contextPath}/URLPatternという表記です。

このようにServletはパッケージ名やクラス名とは無関係に URLPattern で呼び出すことができます

決して、以下のようにパッケージ名は必要ではありませんから注意してください。パッケージ構成はよく変更されるのでURLパターンだけでTomcatから呼び出されるようになっているのです。さらにはクラス名も無関係だったことを思い出して下さい。パッケージ構成やクラス名はユーザーには無関係なので、アドレスバーに表示されないようになっていると理解ください。

<form action="${pageContext.request.contextPath}/controller/Add">

実験

2章の数当てゲームで、GameServlet.javaのurlPatternsを「"/Game"」から「"/view/Game"」と変更したときに、他にどこを変更する必要がありますか?

また、index.htmlで「action="Game"」と書き換えた場合は正常に動作しますか?

3. httpリクエストの読み方

ここでは下図を使いhttpリクエスト の読み方について解説します。

①は通信プロトコルです。

②はサーバー名です。別名FQDN【Fully Qualified Domain Name】で絶対ドメイン名とも呼ばれます。
FQDNはwww.saycon.co.jpのようにホスト名(www)とドメイン名(saycon.co.jp)をドットでつなぐ表記が一般的です。しかし、今回はホスト名のlocalhostだけです。なお、localhostの代わりに直接IPアドレス(例.192.168.0.1)を指定することもできますので試してみましょう。

③はポート番号です。8080番はTomcatでデフォルトで使用され、httpの80番に見た目が似ていること、ウェルノウンポート以外であることから使われています。

④Webアプリケーションの名前です。コンテキスト名と呼ばれることもあります。プロジェクト名がそのままコンテキスト名になります。コンテキスト名の後ろの「/」がコンテキストルートで、本研修では「webapp」がコンテキストルートに当たります。

⑤URLパターンはServletが呼び出される際の名前でした。本研修では@WebServletアノテーションで設定しています。また、getリクエストの場合は「?」の後にパラメータが付きます。パラメータが複数ある場合は「&」でつなぎます。

3.1 「WEB-INF」には特別な用途がある

なお、「WEB-INF」というディレクトリがありますから探してみてください。

このディレクトリには外部に公開しない内部処理用の重要なファイルを格納します。そのため「WEB-INF」 は外部から直接アクセスできないようになっています。(時々操作を誤ってこのフォルダにファイルを移動してしまうことがありますので気をつけてください)

WEB-INFディレクトリ

さらにここにある「lib」というディレクトリにはのちほどデータベースなどで使用するjarファイルを格納するので場所を確認しておいて下さい。

4. getParameter()メソッド

先程のAddServlet.javaの18行目を見てください。

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

  • ①Integer.parseInt()はインスタンスメソッドでしょうか? それともstaticメソッドでしょうか?
あなたの答え:
  • ②このメソッドは引数の文字列を何に変換しますか?
あなたの答え:

忘れてしまった人は、Integerクラスまで戻って復習してください。

今回の注目点はこの部分です。

request.getParameter("num1")

requestのgetParameter()メソッドを使っています。

のメソッド はフォームから送られたリクエストのインスタンスに入っている文字列を取り出すことができます。ここでフォームから送られるデータはすべて文字列として送られることを思い出してください。

また、フォームから送られるデータは1つとは限りません。そのため“num1”などのテキストフィールドに付けた名前で区別して受け取る必要があるのです。

今回の処理の概要を下図にまとめておきます。

計算処理の全体像

5. フォームに入力された日本語文字列を表示する

今度はフォームに入力された日本語を表示するWebアプリケーションを作成してみます。フォームに名前を入れると名前を呼んで「こんにちは」と表示するプログラムです。

入力:「今井」

出力:「こんにちは今井さん」

というイメージです。

まずは、入力フォームjapanese-form.jspです。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>japanese-form.jsp</title>
</head>
<body>
	<form action="${pageContext.request.contextPath}/Japanese">
		<input type="text" name="name" /><br>
		<button type="submit">送信</button>
	</form>
</body>
</html>
  • ①送信ボタンを押したあとのブラウザのURL表記はどうなりますか?
あなたの答え:
  • ②どのようなフォーム部品を使って、どのような名前で文字列データを送っていますか?
あなたの答え:

次にControllerとしてのServletです。

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;

@WebServlet(urlPatterns = { "/Japanese" })
public class JapaneseServlet extends HttpServlet {

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

		String name = request.getParameter("name");

		request.setAttribute("name", name);
		request.getRequestDispatcher("view/japanese.jsp").forward(request, response);
	}
}
  • ①「String name = request.getParameter("name");」では何をしていますか?
あなたの答え:
  • ②「request.setAttribute("name", name);」では何をしていますか?
あなたの答え:
  • ③「request.getRequestDispatcher("view/japanese.jsp").forward(request, response);」では何をしていますか?
あなたの答え:

最後にViewとしてのJSPです。

中身は特に今までと変わったところはありません。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>japanese.jsp</title>
</head>
<body>
	<h1>こんにちは${requestScope.name}さん</h1>
</body>
</html>

このように日本語文字列も問題なく表示できました。

※環境によっては文字化けが起こることがありえます。その場合は「便利なFilterを使いたい」という記事の「日本語文字化け対策のFilter」のサンプルコードを参考にしてください。

例題

上記、フォームjapanese-form.jspからからJapaneseServlet.javaに対して確実にデータが送られていることを①printデバッグ、②デバッガを使ったデバッグで確認しなさい。

確かめた結果:

例題

上記、japanese_form.htmlで「name="name"」となっているところを「name="namae"」と書き換えたとします。そうすると他にどこをどのように書き換える必要がありますか?

あなたの答え:

6. エラー入力を適切に処理する

ユーザーはエンジニアが想定しない使い方をするものです。ここではフォームに想定していない入力がされた場合の対処方法を数当てゲームのWebアプリケーションを使って学びましょう。

ところで、ここまで学ぶと下図の通りMVCモデルの解説のところの数当てゲームも理解できたのではないでしょうか?

答えが5で正解したケース

入力画面、ModelとViewは以下の通りでした。(再掲)

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"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=UTF-8">
        <title>result.jsp</title>
    </head>
    <body>
        <h1>ゲームの結果は!</h1>
        ${requestScope.message}<br>
        答えは${requestScope.answer}<br>
    </body>
</html>

このとき、整数値以外が入力された場合や整数値でも0-9以外が入力された場合は、どのファイルのどの部分を変更すればよいでしょうか?

Kazuate クラスの責務を考えるとこのクラスはロジックを担当しているため、ユーザーの入力が有効な範囲内(0-9の整数)であるかをチェックする責任を持つことは適切です。

しかし、入力が数値かどうかのチェックは、一般的には入力を受け付ける部分(例えば、ユーザーインターフェースやコントローラー層)で行うことが適切です。

(配布プロジェクトの中にindex2.jspがありますが掲載は省略しています)

package model;

import java.util.Random;

public class Kazuate {

	private int answer;
	private String message;

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

	public void checkTheAnswer(int guess) {
		if(guess < 0 || guess > 9) {
			this.message = "0-9の整数を入力ください";
			return;
		}

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

	@Override
	public String toString() {
		return "Kazuate [answer=" + answer + ", message=" + message + "]";
	}
}

確かにこれで想定外の整数値をエラーにすることができました。

しかし、フォームに「あ」のような文字列や「5.0」のような浮動小数点数を入力されると下図のNumberFormatExceptionがでます。

NumberFormatException

このような不適切な入力に対処するにはどうしたら良いのでしょうか?

JavaSEで学んだ例外処理が使えます。(配布プロジェクトの中にindex3.jspがありますが掲載は省略しています)

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 = { "/Game3" })
public class Game3Servlet extends HttpServlet {
    private static final int INVALID_GUESS = -1;

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

        try {
            guess = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            request.setAttribute("message", "整数以外が入力されました");
            request.getRequestDispatcher("view/result.jsp").forward(request, response);
            return;
        }

        Kazuate kazuate = new Kazuate();
        String message = kazuate.checkTheAnswer(guess);
        request.setAttribute("message", message);
        request.setAttribute("answer", kazuate.getTheAnswer());

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

※整数以外が入力された時の「答えは」という文言が気になる方は、後でJSTLを学ぶと条件に応じてこのような文字列を消せるようになりますのでしばらくお待ち下さい。

ここでのポイントは整数以外が入力された場合、打ち切りreturn文でServletの処理が終了するという点です。

例題 アドレスバーを使って数当てゲームを遊ぶ

アドレスバーのリクエストパラメータの数値(以下の5)を書き換えることで数当てゲームを遊ぶことができることを確かめなさい。

7. ハイパーリンクを使って送られたデータを受け取る

ここでは、天気予報プログラムのプロトタイプを考えてみましょう。全国のユーザーが現在の天気を送るとリアルタイムで全国の天気が分かり、また、予測も可能になるというシステムのプロトタイプです。

天気予報サイトのイメージ

リンクを使ってデータを送ることができます。以下のように?の後ろにリクエストパラメータを付けて送ります。

<a href="${pageContext.request.contextPath}/Weather1?weather=晴れ">晴れ</a>

このとき「?weather="晴れ"」のように文字列扱いしたくなるかもしれませんが、 「?weather=晴れ」 で大丈夫です。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>link.jsp</title>
</head>
<body>
	<p>今の天気を教えて下さい</p>
	<a href="${pageContext.request.contextPath}/Weather1?weather=晴れ">晴れ</a>
	<a href="${pageContext.request.contextPath}/Weather1?weather=曇り">曇り</a>
	<a href="${pageContext.request.contextPath}/Weather1?weather=雨">雨</a>
</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;

@WebServlet(urlPatterns = { "/Weather1" })
public class Weather1Servlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String weather = request.getParameter("weather");

		request.setAttribute("weather", weather);
		request.getRequestDispatcher("view/weather.jsp").forward(request, response);
	}
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>weather.jsp</title>
</head>
<body>
	今は${requestScope.weather}なんですね。
</body>
</html>

では、もしも仮に「Weather1Servlet.java」にdoGet()メソッドの代わりにdoPost()メソッドしかなかったらどうなると思いますか?

ServletとJSPには変更を加える必要はありませんので試してみてください。

下図のようなエラーになります。

サポートされていないメソッドのエラー画面

aタグを使ったリンクは常にGETメソッドになることを覚えておいてください。

元来、HTTPのGETメソッドは主にWebページや他のリソース(例えば、画像やCSSファイル)を取得するために使用されてきました。ブラウザがリンクをクリックするか、URLを直接入力すると、ブラウザはそのURLに対応するリソースを取得するためにGETリクエストをサーバーに送信するのです。GET=ゲットする、POST=投稿するという元々の意味を考えれば2つのhttpメソッドを記憶するのは容易になると思います。

例題

上記link.jspに雪の場合を加えなさい。

例題

これまで作成してきた数当てゲームをリンクを使って実現しなさい。ただし、再利用できるファイルはできるだけ再利用すること。

8. ボタンを使って送られたデータを受け取る

テキストボックスからリンクに入力方法を変えたことでユーザーの使いやすさが向上しました。さらに入力チェック(バリデーション)も楽になりました。ここでは、さらにユーザー入力がしやすいようにボタンを使ってデータを送ります。ボタンはスマートフォンでも押しやすいですからね。

Weather1.javaには変更点はありませんが、念のため再掲しました。

package p04;

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;


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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        String weather = request.getParameter("weather");
        
        request.setAttribute("weather", weather);
        request.getRequestDispatcher("04Form/weather.jsp").forward(request, response);
    }
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>button.jsp</title>
</head>
<body>
	<p>今の天気を教えて下さい</p>
	<form action="${pageContext.request.contextPath}/Weather1">
		<button type="submit" name="weather" value="晴れ">晴れ</button>
		<button type="submit" name="weather" value="曇り">曇り</button>
		<button type="submit" name="weather" value="雨">雨</button>
	</form>
</body>
</html>

aタグで囲むと何でもリンクにできる

画像などのHTML部品でもaタグで囲むことでリンクにして表現力の高いボタンにすることができます。

例えば、上記のbutton.htmlをfont-awesomeのアイコンを使って以下のように書くこともできることは知っておきましょう。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <title>button1.html</title>
        <meta charset="UTF-8">
        <!-- FontAwesomeのCSSを読み込み -->
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css">
    </head>
    <body>
        <p>今の天気を教えて下さい</p>
        <a href="${pageContext.request.contextPath}/Weather1?weather=晴れ" class="fa fa-sun"> 晴れ</a>
        <a href="${pageContext.request.contextPath}/Weather1?weather=曇り" class="fa fa-cloud"> 曇り</a>
        <a href="${pageContext.request.contextPath}/Weather1?weather=雨" class="fa fa-cloud-rain"> 雨</a>
    </body>
</html>

例題

上記button.htmlに雪の場合を加えなさい。

例題

数当てゲームをボタンを使って実現しなさい。

コードの変更が結果に反映されないときは?

ソースコードを変更したのに表示に反映されないことがあります。そのような場合は以下の3段階の対応で改善することがあります。

  • Webページの強制再読み込み(ショートカット:Ctrl + Shift + r)
  • サーバーの再起動
  • サーバークリーンまたはプロジェクトのクリーン

サーバークリーン

サーバーのキャッシュが邪魔をしてアプリケーションの動作が不安定な場合は、下図の「クリーン」を試して下さい。

また、「新規」から新しくサーバーを作ることで改善することもあります。詳しくは講師にお尋ね下さい。

サーバーのクリーンとプロジェクトのクリーン

上記のサーバーのクリーンはサーバーにデプロイ(配備)されているアプリケーションのデータをクリアし、サーバー環境をリセットします。アプリケーションを新鮮な状態でデプロイし直す際に有用です。

一方でプロジェクトのクリーンという操作もあります。プロジェクトのクリーンは、プロジェクト内のビルドされたクラスファイルなどを削除し、プロジェクトを再ビルド(コンパイル等)することを意味します。動作が不安定なとき、不可解なエラーが起こった際には、プロジェクトのクリーンも試してみましょう。


<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ Servletはパッケージ名やクラス名とは無関係に URLPattern で呼び出すことができる

□ フォームから送られるデータはすべて文字列として送られるため数値にするにはInteger.parseInt()メソッドを使う

□ requestのgetParameter はフォームから送られたリクエストのインスタンスに入っている文字列をname属性を手がかりに取り出す

□ if文や例外処理をつかって入力エラーを適切に処理する

□ aタグを使ったリンクは常にGETメソッドになる

今回はフォームからデータをServletに渡す方法について学びました。

フォームにはラジオボタンやセレクトボックスもあります。最終課題でそれらのフォームも使いたい方は、以下の補講を見てください。

補講:その他のフォームからデータをServletに渡す

次回は、ログイン処理の実装とセッション属性を学んでいきます。

ログイン処理が書けるとかなりWebアプリケーションらしくなります。

「フォームから送ったデータをServletで受け取る」 最後までお読みいただきありがとうございます。