剥いだ布団とメモランダム.old

情報系のことをかいてゆく

JavaServletで階乗を計算してくれるページをつくる

実験でJavaServletを使ってみようみたいなことをしたので、適当に階乗を返してくるページをつくりました。果たしてこれは実験なのか?
まあ、いつかのために備忘録。

環境

PCについては全く不明です(笑

まず、JavaServletって何よ

Java Servlet(ジャバ サーブレット)とは、サーバ上でウェブページなどを動的に生成したりデータ処理を行うために、Javaで作成されたプログラム及びその仕様である。単にサーブレットと呼ばれることが多い。Java EEの一機能という位置づけになっている。この機能を用いてショッピングサイトやオンラインバンキングなどをはじめとする多種多様な動的なWebサイトが構築されている。
サーブレットの登場以降、サーバ側でJavaプログラムを稼動させる形態が急速に普及した。こうしたサーバ側でJavaプログラムを稼動させる形態をサーバサイドJavaと呼ぶ。

Java Servlet - Wikipediaより。

要するに、サーバーサイドで動くweb向けのJavaだよ!Javaのプログラムを走らせられるから、動的なwebサイトが作れちゃうよ!っていう話みたいです。
ちなみに、サーバー側じゃなくてクライアント側で働いてくれるJavaはApplet。

じゃあApache Tomcatって何よ

Apache Tomcat (アパッチ トムキャット) は、Java Servlet や JavaServer Pages (JSP) を実行するためのサーブレットコンテナ(サーブレットエンジン)である。Apache License, Version 2.0を採用したオープンソースソフトウェアであり、商用利用も多い[要出典]。バージョン 7.0 は、Java Servlet 3.0、JavaServer Pages 2.2対応で、Java 6 以降が必要。

Apacheソフトウェア財団のトップレベルプロジェクトのひとつであるApache Tomcat Project 内で開発されている。

Apache Tomcat - Wikipediaより。

つまりは、JavaServletを実行するための環境がApache Tomcatってわけ。
こいつがいるおかげで、実際のJavaServletをサーバで動かせるらしいです。
ちょっと特殊なwebサーバという認識でいいのかな。

書いてみた

とりあえずEclipseでファイル生成

Eclipseサーブレット用のファイルを生成すると、自動的に下のようなソースコードを生成しておいてくれる。

package test;

import java.io.IOException;

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

/**
 * Servlet implementation class HelloServlet
 */
public class HelloServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}
}

HttpServletクラスを継承(extends)したクラスを作ってあるのがわかる。

HttpServletクラスの中身

このHttpServletクラスは

public abstract class HttpServlet extends GenericServlet implements java.io.Serializable

と、GenericServletクラスとSerializableインタフェースを実装している。

GenericServletもみてみる。

public abstract class GenericServlet extends java.lang.Object implements Servlet, ServletConfig, java.io.Serializable

長い。若干見る気失せるレベル長さだけど、Servlet, ServletConfig, java.io.Serializableの各インタフェースを実装している。
サーブレットServlet, ServletConfigの両インタフェースを実装する必要があるので、まあ良しとしよう。

ちなみに、Servletインタフェースの中身は、

  • init...初めてロードされたときにコンテナによって一度だけ呼び出し
  • service...クライアントからのリクエストごとにコンテナによって呼び出し
  • destroy...アプリを破棄するときにコンテナによって一度だけ呼び出し

ServletConfigインタフェースの中身は、

  • getServletContext...ServletContextのオブジェクトを取得
  • getInitParameter...init-paramで定義した値を取得
  • getInitParameterNames... init-paramで定義した名前を取得

サーブレットに関する初期化や基本的な機能をこの2つのインタフェースで提供しているみたい。なるほど。

で、java.io.Serializableってなんぞ。

クラスの直列化可能性は、java.io.Serializable インタフェースを実装するクラスによって有効になります。このインタフェースを実装していないクラスでは、その状態が直列化または直列化復元されることはありません。直列化可能クラスのサブタイプは、すべてそれ自体が直列化可能です。直列化インタフェースはメソッドまたはフィールドがなく、直列化可能であるという意味を識別する機能だけを備えています。

Serializable (Java Platform SE 6)より。
ファー!?なにこれ。
どうやら、インスタンスはメモリ上のポインタうんぬんの話で、「通常時じゃファイルとかに書き出しができないからこの直列化という機能を使ってちゃんと整えてね」っていうことらしい。
で、今回、ファイル保存に絡んでHTTPセッションに保存するオブジェクトもjava.io.Serializableインタフェースを実装して、直列化可能なオブジェクトにせなアカンと。なんやねん。

話を戻す

一応、生成したクラスが継承していた中身についてリサーチを済ませた所で、
次にEclipseが勝手に生成してくれたメソッドについてみてみる。
コンストラクタについては飛ばすとして、他はdoGetメソッドとdoPostメソッド。
この子たちは、各HTTPリクエストと対応したメソッドになっている。

HTTPリクエストの種類 ソースコードで定義するメソッド
GET protected void doGet(HttpServletRequest req, HttpServletResponse resp)
POST protected void doPost(HttpServletRequest req, HttpServletResponse resp)

実はこの他にPUTとかDELETEとかリクエストの種類はあるんだけども、ここでは省略。
とにかく、作られたdoGetメソッドはGETリクエスト時に呼び出し、doPostメソッドはPOSTリクエスト時に呼び出しということ。
すなわち、HttpServletクラスを継承したサーブレット用クラスは少なくとも1つはHTTPリクエストに対応したメソッドを作っておかないといけないということだね。

はい、コーディング前の予習はここまで。長いです。

実際にコーディングしていく

ここからは、実際に階乗計算ページをコーディングしていきます。

階乗計算メソッド

階乗ってなんだっけとかは省略。
階乗の処理自体は下のメソッドを使います。さっき生成したHelloServletクラス内に記述します。

private long factorial(long v){
	//階乗計算メソッド
	if(v==0){
		return 1;
	}else{
		return v * factorial(v-1);
	}
}

再帰的アルゴリズムの模範回答。笑
int型だとすぐオーバーフローしてしまうので、気休め程度にlongにしました。
(これでも20!くらいしか計算できません)

コンストラクタになにか書いてみる

正しくコンストラクタの内容も実行されているかチェックするため、コンストラクタに適当になにか書いておきます。
(実験だからね)

ここでは、ページタイトルの内容を引数に渡すようにコーディングしました。

private String pagetitle;	//ページタイトル
    public HelloServlet() {
        super();
        pagetitle = "<title>Calc Factorial!!!!!!!</title>";
    }
doGetメソッドのコーディング

ページにアクセスした際に行われるリクエストはGETなので、最初に表示する内容をdoGetメソッドに書いていきます。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=Shift_JIS");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println(pagetitle);
		out.println("</head>");
		out.println("<STYLE>");
		//CSS
		out.println("*{background-color:#fffacd;}");
		out.println("div{border-style:solid;border-color:blue;width:250px;text-align:center;}");
		out.println("</STYLE>");
		//body html
		out.println("<body>");
		out.println("<h1>Calc Factorial!!!!!!!</h1>");
		out.println("<h2>このページは入力された値の<a href=\"http://ja.wikipedia.org/wiki/%E9%9A%8E%E4%B9%97\" target=\"blank\">階乗</a>を求めるページです</h2>");
		out.println("<img src=\"/program_fact.gif\">");
		out.println("<div id=\"box\"><form action=\"HelloServlet\" method=\"post\">");
		out.println("<table><tr><td>input value!</td><td><input type=\"text\" size=\"5\" value=\"\" name=\"val\"></td>");
		out.println("<td><input type=\"submit\" name=\"button\" value=\"calc!\"></td></tr></table></form></div>");
		out.println("</body>");
		out.println("</html>");
	}

java.io.PrintWriterをつかってHTMLソースをアウトプットしていくので、import java.io.PrintWriter;しました。
Styleの部分やめんどくさくてタグを続けて書いてある部分があるので、少し見づらいですがご了承を。
HTMLでは""がよく出てくるけども、Javaでの記述では\"\"とエスケープシーケンスなので、少し面倒。
っていうか、普通はHTML出力はサーブレットに書かないよね。

実際にブラウザでみるとこんな感じ。
f:id:sheemaa:20130627211224p:plain

ここで大事なポイントは、

out.println("<div id=\"box\"><form action=\"HelloServlet\" method=\"post\">");

の部分。
actionにボタンが押された際に実行するサーブレット名、methodに使用するリクエストの種類を指定する。
クライアントからのデータの送信は通常POSTなのでPOSTを指定。
つまり、ボタンが押された時点でPOSTリクエストが発生し、doPostメソッドが呼び出される。

doPostメソッドのコーディング

先のdoGetメソッド内のボタンが押された時にPOSTリクエストが発生するので、ボタンが押された後の処理をこのメソッドに書いてやればOK。
具体的には、

  • textフィールド"val"に入力された値の取得
  • 数値以外の処理、マイナス値の処理
  • 階乗計算
  • オーバーフロー時の処理
  • HTML出力

って感じです。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String tmp = request.getParameter("val");
		String output;
		long val;
		if (tmp == null || tmp.length() == 0){
			val = -1;
		}else{
			try{
				val = Long.parseLong(tmp);
			}catch (NumberFormatException e){
				val = -1;
			}
		}
		if (val <= -1){
			output = "<td><h3>Error!</h3></td></tr></table>";
		}else{
			long result = factorial(val);
			if (result < 0){
				output = "<td><h3>Overflow! Sorry...</h3></td></tr></table>";
			}else{
				output = "<td><h3>" + result + "</h3></td></tr></table>";
			}
		}
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println(pagetitle);
		out.println("</head>");
		out.println("<STYLE>");
		//CSS
		out.println("*{background-color:#fffacd;}");
		out.println("</STYLE>");
		//body html
		out.println("<body>");
		out.println("<h1>Calc Factorial!!!!!!!</h1>");
		out.println("<table><tr><td><h3>Result:</h3></td>");
		out.println(output);
		out.println("<a href=\"javascript:history.back()\">go back the page!</a>");
		out.println("</body>");
		out.println("</html>");
	}

要点だけみていくと、

String tmp = request.getParameter("val");

で"val"フィールドから値を取得(このときはまだStringオブジェクト)。

if (tmp == null || tmp.length() == 0){
	val = -1;
}else{
	try{
		val = Long.parseLong(tmp);
	}catch (NumberFormatException e){
		val = -1;
	}
}

では、未入力時や数値以外のものが入力された際に変数valへ-1を代入。
もしも、正しく値が入力されていればlong型へキャストした入力値をvalへ。

if (val <= -1){
	output = "<td><h3>Error!</h3></td></tr></table>";
}else{
	long result = factorial(val);
	if (result < 1){
		output = "<td><h3>Overflow! Sorry...</h3></td></tr></table>";
	}else{
		output = "<td><h3>" + result + "</h3></td></tr></table>";
	}
}

では、valの値が-1の時(未入力or型が違う)にエラーを出力し、その後さっき作成した階乗メソッドを呼び出して、結果が正しく1以上の値が返ってくれば、そのままoutputに結果を、1未満であればオーバーフローしているので、オーバーフロー通知を出力する。

あとは、doGetメソッドと同じようにHTMLを吐いているだけ。
実際に5を入力してボタンを押した結果がこちら
f:id:sheemaa:20130627212657p:plain

ソースコード最終形

はいじゃあ、結局のソースコードでございます。

package test;

import java.io.IOException;
import java.io.PrintWriter;

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

/**
 * Servlet implementation class HelloServlet
 */
public class HelloServlet extends HttpServlet {
	private String pagetitle;	//ページタイトル
	
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloServlet() {
        super();
        pagetitle = "<title>Calc Factorial!!!!!!!</title>";
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=Shift_JIS");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println(pagetitle);
		out.println("</head>");
		out.println("<STYLE>");
		//CSS
		out.println("*{background-color:#fffacd;}");
		out.println("div{border-style:solid;border-color:blue;width:250px;text-align:center;}");
		out.println("</STYLE>");
		//body html
		out.println("<body>");
		out.println("<h1>Calc Factorial!!!!!!!</h1>");
		out.println("<h2>このページは入力された値の<a href=\"http://ja.wikipedia.org/wiki/%E9%9A%8E%E4%B9%97\" target=\"blank\">階乗</a>を求めるページです</h2>");
		out.println("<img src=\"/program_fact.gif\">");
		out.println("<div id=\"box\"><form action=\"HelloServlet\" method=\"post\">");		
		out.println("<table><tr><td>input value!</td><td><input type=\"text\" size=\"5\" value=\"\" name=\"val\"></td>");
		out.println("<td><input type=\"submit\" name=\"button\" value=\"calc!\"></td></tr></table></form></div>");
		out.println("</body>");
		out.println("</html>");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String tmp = request.getParameter("val");
		String output;
		long val;
		if (tmp == null || tmp.length() == 0){
			val = -1;
		}else{
			try{
				val = Long.parseLong(tmp);
			}catch (NumberFormatException e){
				val = -1;
			}
		}
		if (val <= -1){
			output = "<td><h3>Error!</h3></td></tr></table>";
		}else{
			long result = factorial(val);
			if (result < 0){
				output = "<td><h3>Overflow! Sorry...</h3></td></tr></table>";
			}else{
				output = "<td><h3>" + result + "</h3></td></tr></table>";
			}
		}
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println(pagetitle);
		out.println("</head>");
		out.println("<STYLE>");
		//CSS
		out.println("*{background-color:#fffacd;}");
		out.println("</STYLE>");
		//body html
		out.println("<body>");
		out.println("<h1>Calc Factorial!!!!!!!</h1>");
		out.println("<table><tr><td><h3>Result:</h3></td>");
		out.println(output);
		out.println("<a href=\"javascript:history.back()\">go back the page!</a>");
		out.println("</body>");
		out.println("</html>");
	}
	private long factorial(long v){
		//階乗計算メソッド
		if(v==0){
			return 1;
		}else{
			return v * factorial(v-1);
		}
	}

}

雑感

まあ地味な感じですけれども、一応、動的なwebサイトがこれで作成できたことになります。
基本構造はこんな感じなので、これをベースにいろいろいじればネットショップとかがつくれる、と。(やる気ない)