[目次] [その5]

Javaによるオブジェクト指向プログラミング (その6)

前回の練習&宿題について

練習については単に実行するだけといって良いので、説明は略す。

宿題であるが、doGetメソッドの中に以下のような行を追加すれば良い。

RpnCalc calc = new RpnCalc();
calc.push(new Float(1.2));
calc.push(new Float(3.4));
calc.add();
out.println("1.2 + 3.4 = ");
out.println(calc.pop());
out.println("
");

クラスの探索について

上記のように他のクラスを参照するプログラムを Java で書いた場合、以下のよ うな手順でコンパイル時と実行時のそれぞれでそのクラスを探索する。
  1. クラスパスを求める。
    1. コマンドライン引数に「-classpath」が指定されて いればそれをクラスパスとする。
    2. コマンドラインで指定されない場合で、環境変数 「CLASSPATH」が設定されている場合はその値をクラ スパスとする。
    3. 環境変数も設定されていない場合は、使用する Java 開発キッ トのデフォルト値をクラスパスとする。(通常カレントディレ クトリと開発キット付属のクラスファイルのありかとなる)
  2. クラスパスでコロン (「:」、Windows の場合はセミコロン 「;」) で区切られた各ディレクトリ(またはファイル)につ いて前から順にクラスファイルを探す。
    1. ディレクトリの場合は、そのディレクトリ以下からクラスファ イルを探す。
    2. ファイルの場合はそのファイルが ZIP 形式あるいは JAR形式 であり、そのファイルにアーカイブされているクラスファイル を探す。
  3. クラスパスに指定されたディレクトリ(またはファイル)に対してクラ スファイルを探す場合は、そのパッケージに応じて以下のように探す。
    1. ディレクトリの場合、パッケージ名に含まれるピリオドをディ レクトリの区切りとしてそのディレクトリを頂点として探す。 例えば、/abc/def というディレクトリで クラスjava.lang.Objectを探す場合は、 「/abc/def/java/lang/Object.class」というファイ ルを探す。
    2. ファイルの場合、やはりパッケージ名に含まれるピリオドをディ レクトリの区切りとして、そのファイルにアーカイブされてい るクラスファイルを探す。例えば、/abc/def.jarと いうファイルでクラスjava.lang.Objectを探す場合、 その JAR ファイルの中で「java/lang/Object.class」 を探す。
    3. パッケージ名が指定されていないクラスは無名パッケージとし てクラスファイルにディレクトリ名がつかないものとして上記 2つと同様に探す。
通常、クラスパスなどでカレントディレクトリが指定されていることが通例なの で、自分が作成したクラスが無名パッケージであれば、同じディレクトリにおい ておけば良い。

なお、クラスの探索はコンパイル時と実行時のそれぞれで独立して行われる。サー ブレットの場合は、コンパイルした環境と実行する (WWW サーバ内で実行される) 環境が異なることがあり得るので、注意すること。

サーブレットにおける入力

サーブレットで利用者からの入力を受け付けるには通常 HTML におけるフォーム などを用いる (注: リンクやその他の方法でも構わない)。今回はその方法につ いて説明する。

次のプログラム例は入力として文字列を一つ受け取り、それを単に表示するもの である。

     1|	import java.io.*;
     2|	import javax.servlet.*;
     3|	import javax.servlet.http.*;
     4|	
     5|	public class Serv1 extends HttpServlet
     6|	{ 
     7|	    public void doGet (HttpServletRequest req, HttpServletResponse res) 
     8|		    throws ServletException, IOException
     9|		{
    10|		    String title = "Serv1 Result";
    11|	            res.setContentType("text/html");
    12|	            PrintWriter out = res.getWriter();
    13|		    String val = req.getParameter("abc");
    14|	            out.println("<html><head><title>");
    15|	            out.println(title);
    16|	            out.println("</title></head><body>");
    17|	            out.println("<h1>" + title + "</h1>");
    18|		    out.println("Value of abc = '" + val +"'<br>");
    19|		    for (int i = 0; i < val.length(); i++) {
    20|			out.println(val.charAt(i));
    21|			out.println("<br>");
    22|		    }
    23|	            out.println("</body></html>");
    24|	            out.close();
    25|	        }
    26|	}
利用者からの入力された文字列をこのサーブレットに渡すフォームの例は以下の ようになる。
     1|	<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
     2|	<html>
     3|	<head>
     4|	<title>Test Form1</title>
     5|	</head>
     6|	<body>
     7|	<h1>Test Form1</h1>
     8|	<form action="http://avalon.ulis.ac.jp:8085/saka/servlet/Serv1" method="GET">
     9|	Please Input: <input type="text" name="abc">
    10|	</form>
    11|	</body>
    12|	</html>
これは1行の文字列を入力するテキストフィールドを表示し、そこに文字列が入 力され、Enterキーが押されるとその値をパラメータとしてサーブレットを呼び 出す (実際には、サーブレットの URI を WWW サーバにパラメータ付きで要求と して送る)。

13行目の getParameter()メソッドが値を取り出す。引数はフォームの 各要素に付けられた name属性の値となる。なお、値を取り出すために は、この他 getParameters()getParameterNames() とい うメソッドを用いることができる。

なお、クラス Serv1 では、デバッグ用に受け取った文字列 (変数 val) の値を1文字ずつ表示している。

非ASCII文字の入力

前出の Serv1 は ASCII に含まれない文字を受け取ると一部分に文字 化けを起こす。文字列としてブラウザに表示している範囲では正常に見えるが1 文字毎に処理をしてみると例えば、漢字1文字が2文字に分解されてしまっている ことがわかる。

これは、WWW サーバからブラウザに対してはどういう文字コード系を用いている かを伝える規格が整っているのに対して、フォームなどで入力された文字列をサー バに伝える文字コードに関する扱いに曖昧な点が残るため、標準では ISO-8859-1 という文字コードだという前提で受け取るためである。

そのため、日本語などの文字列を受け取るためには少しだけ工夫が必要となる。 その工夫とは文字列として受け取ったものを一旦 ISO-8859-1 文字コードを用い たバイト列に変換し(これでブラウザから届いたバイト列に戻る)、それを改めて その文字列に用いられている「はず」の文字コード系を指定して文字列に変換す るというものである。

例えば、Serv1doGetメソッドの場合、13行目を以下のよう に変えることになる。

String val = req.getParameter("abc");
val = new String(val.getBytes("ISO-8859-1"), "ISO-2022-JP");
この第2行目がその変換を行っている。ここではブラウザから渡される時の文字 コード系としては ISO-2022-JP を指定しているが、ブラウザによって以下のよ うに振る舞いが異なるので、それを配慮して指定する必要がある。 最近のブラウザは最後のような動作をするのが通例なので、上のコード変換を問 題なく行うためには、フォームを書いた HTML の head要素に
<meta http-equiv="Content-type" content="text/html; charset=ISO-2022-JP">
と指定しておく。

なお、様々な言語に対応するためにはどちらも ISO-2022-JP ではなく、UTF-8 にするべきである。

入力データが大きくなる場合

例えば、アンケート調査の自由記入欄や画像などファイルの内容をアップロード するような場合、HTTP の GET メソッドを用いることができなくなる。GETメソッ ドでは入力された値などを URI の一部としてサーバに送るため、その長さに上 限があるからである。

このような場合には HTTP の POST メソッドを用いる。それには、まずフォーム のmethod属性には「"POST"」を指定する。次にサーブレット のクラス定義では doGet() ではなく、doPost()メソッドを 定義する。なお、メソッドの名称が代わる他は特に違いはない。それぞれの例を 以下に示す。

     1|	<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
     2|	<html>
     3|	<head>
     4|	<meta http-equiv="Content-type" content="text/html; charset=ISO-2022-JP">
     5|	<title>Test Form3</title>
     6|	</head>
     7|	<body>
     8|	<h1>Test Form3</h1>
     9|	<form action="http://avalon.ulis.ac.jp:8085/saka/servlet/Serv3" method="POST">
    10|	Subject:
    11|	<input type="text" name="subject" value="Please Input Subject" size="40"><br>
    12|	<textarea name="content" cols="40" rows="12">
    13|	Please Input Text
    14|	</textarea><br>
    15|	<input type="submit" value="Submit"> <input type="reset" value="Reset">
    16|	</form>
    17|	</body>
    18|	</html>
     1|	import java.io.*;
     2|	import javax.servlet.*;
     3|	import javax.servlet.http.*;
     4|	
     5|	public class Serv3 extends HttpServlet
     6|	{ 
     7|	    public void doPost (HttpServletRequest req, HttpServletResponse res) 
     8|		    throws ServletException, IOException
     9|		{
    10|		    String title = "Serv3 Result";
    11|	            res.setContentType("text/html; charset=ISO-2022-JP");
    12|	            PrintWriter out = res.getWriter();
    13|		    String sub = req.getParameter("subject");
    14|		    sub = new String(sub.getBytes("ISO-8859-1"), "ISO-2022-JP");
    15|		    String cont = req.getParameter("content");
    16|		    cont = new String(cont.getBytes("ISO-8859-1"), "ISO-2022-JP");
    17|	            out.println("<html><head><title>");
    18|	            out.println(title);
    19|	            out.println("</title></head><body>");
    20|	            out.println("<h1>" + title + "</h1>");
    21|	            out.println("<h2>" + sub + "</h2>");
    22|		    out.println("<pre>");
    23|		    StringBuffer buf = new StringBuffer();
    24|		    for (int i = 0; i < cont.length(); i++) {
    25|			char c = cont.charAt(i);
    26|			switch (c) {
    27|			    case '<':
    28|				buf.append("&lt;");
    29|				break;
    30|			    case '>':
    31|				buf.append("&gt;");
    32|				break;
    33|			    case '&':
    34|				buf.append("&amp;");
    35|				break;
    36|			    case '"':
    37|				buf.append("&quot;");
    38|				break;
    39|			    default:
    40|				buf.append(c);
    41|				break;
    42|			}
    43|		    }
    44|		    out.println(buf.toString());
    45|		    out.println("</pre>");
    46|	            out.println("</body></html>");
    47|	            out.close();
    48|	        }
    49|	}
これは電子掲示板的なアプリケーションの枠組を想定した例である。このように 入力された文字列をそのまま WWW ブラウザに表示するような場合には注意が必 要である。入力された文字列中にタグが埋め込まれている場合、そのまま出力す るとブラウザはそのタグを解釈してしまう。この例では content に入力された ものに関しては 24〜43行目で変換して問題が起きないようにしている。実際に はこのような処理は別途メソッドで準備して用いる方が良い。

なお、この例のフォームでは入力フィールドが2つあるので、「Submit」ボタン をクリックすることによってリクエストがサーバに送られるようにしている。

課題

これまでの解説に基づき、WWW ブラウザ経由で使用できる逆ポーランド方式の 「電卓」システムを作成せよ。課題ができたら阪口に連絡をとり、スケジュール を調整の上で、実演とプログラムの解説を対面で行ってもらう。とりあえずの締 切は2月末とする。
[目次] [その5]
This page is written by <saka@ulis.ac.jp>.
2003年 1月22日 水曜日 15時55分20秒 JST