
いまさら聞けないサーバーサイドJava 第2回
|
サーブレットとJSPでオンライン書店のプログラムを作る
さて、解説編はこれでおしまい。ここからは、実際に手を動かしてサンプルのWebアプリケーションを作成してみましょう。次回以降も同じWebアプリケーションを題材にして、サーバーサイド・アプリケーションをどんどん成長させていきます。誌面の関係ですべてのコードを紹介することはできませんが、本誌Webサイトからファイルをダウンロードできるので、ぜひそちらもご覧ください。
サンプルWebアプリケーションの題材は、オンライン書店のWebサイトです。今回はサーブレットとJSPで書籍や雑誌などの取扱商品を一覧表示できるようにします。次回以降、データベースを作成して商品を検索する機能や、EJB(注12)を作成してオンライン発注を行う機能を追加していきます(図4)。
図4

今回の開発範囲は、トップ・ページから商品カテゴリを選択して商品一覧ページに移動するまでの機能です。普通でしたら商品情報やカテゴリの情報はデータベースにおくところですが、データベース・プログラミングは次回以降のお楽しみということにして、今回は簡単にpropertiesファイル(注13)に商品の情報を保存することにしましょう。ただし、データベースに移行した場合もプログラムには影響ができるだけ少ないように作成しておきたいので、ロジックとデータベース・アクセスは分離しておきます。もちろんJSPを使って、HTML表示とロジックも分離します。また、処理の種類ごとに個別のサーブレットを作成してもよいのですが、定型的な処理が入ってきたとき、一元的に扱えるようにするため、一つのサーブレットがリクエストを受け付けて処理を振り分ける、MVCモデル2(注14)を採用します。
Singletonパターンで処理を軽くする
まず、開発するJavaのクラスを順番に紹介しておきましょう(図5)。図の右側から説明していきます。BookとCategoryというクラスがあります。これらは商品情報で、今の時点ではなんの振る舞いも持たないクラスです。Bookは書名、価格、著者名などの情報、Categoryは英名と表示用名称、そのカテゴリに含まれるBookの配列を持っています。
CategoryManagerはCategoryの永続化(注15)をつかさどるクラスです。前述のように今回はデータベースを使わないので、このようなクラスが必要になります。本来ならBookの永続化をつかさどるBookManagerもあったほうが良さそうですが、現在の要件では必要がないため省略しています。CategoryManagerは、Categoryの全件取得(queryAllメソッド)、Categoryの名前指定による取得(queryByNameメソッド)の二つの操作をサポートします。また、デザイン・パターン(注16)のSingletonパターンを使って、常に一つだけのインスタンスが利用されるようになっています。CategoryManagerは、propertiesファイルに登録した情報を読み込むなど重い初期化処理を行います。また、マルチスレッドで動作したとしても、スレッドごとに異なる状態を持つ必要がありません。そこでSingletonパターンを適用して、インスタンスが一つだけ生成されることを保証するわけです。
図5

サーブレットからの制御で画面を変更する二つの方法
今回実装する処理は、「トップ・ページにカテゴリ一覧を表示する」と「カテゴリを指定して含まれる本の一覧を表示する」の2種類だけです。それぞれの処理の実体は、TopBeanクラス(TopBean.java)とBookSearchBeanクラス(BookSearchBean.java)に記述します。TopBeanクラスは、CategoryManagerを使ってカテゴリ一覧を取得しリクエストの属性に設定しておいてから(リスト4の(1))、RequestDispatcher#forward( )メソッド(注17)を呼び出して処理をtop.jspに引き継ぎます(2)。
リスト4(TopBean.java)
package examples;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TopBean {
public static final String CATEGORIES = "categories";
public void exec (HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
CategoryManager cMgr = CategoryManager.getInstance();
Category[] cs = cMgr.queryAll(); //(1)
req.setAttribute(CATEGORIES, cs);
req.getRequestDispatcher("top.jsp").forward(req, res); //(2)
}
}
ちなみに、サーブレットから他の画面に移動させるテクニックには、ほかにHttpServletResponse#sendRedirect( )というメソッドがありますが、RequestDispatcher#forward( )メソッドとの間には大きな違いがあります。RequestDispatcher#forwardでは、リクエストはWebコンテナ内で引き継がれます。これに対してHttpServletResponse#sendRedirect( )では、いったんブラウザにレスポンスし、ブラウザはそれを見て指定のURLにリクエストを発行し直すのです。
BookSearchBeanクラスの処理で注目する必要があるのは、HttpServletRequest#getParameter( )メソッドを使って(リスト5の(1))、リクエストの内容に応じてカテゴリを変更している部分です。処理に応じてHTTPのパラメータを利用するわけです。
リスト5(BookSearchBean.java)
package examples;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class BookSearchBean {
public static final String CATNAME = "cn";
public static final String CATEGORY = "category";
public void exec (HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
CategoryManager cMgr = CategoryManager.getInstance();
Category c = cMgr.queryByName(req.getParameter(CATNAME)); //(1)
req.setAttribute(CATEGORY, c);
req.getRequestDispatcher("bookList.jsp").forward(req, res);
}
}
コンテキスト名でサーブレットを振り分ける
BookStoreServletは、すべてのリクエストを一括して受けて、リクエストの内容を見て処理を振り分けるクラスです。どのクラスに処理を割り振るかは、ブラウザから送られてくるURLを見て判断します。
「BookStoreServletなら、アクセスするURLはhttp://localhost:8000/servlet/examples.BookStoreServletに決まってるんじゃないの?」と思われるかもしれません。前回説明した内容から推察すると確かにその通りですが、実はサーブレットは「どのようなURLにアクセスされたときにどのサーブレットに処理を割り振るか」を設定できるのです。
BookStoreServletクラスで使っているHttpServletRequest#getRequestURI( )というメソッドは、http://hoge.hoge/foo/barというURLによってサーブレットが呼び出された場合に/foo/barという文字列を返します。続けてHttpServletRequest#getContextPath( )を使うと、サーブレットがWebコンテナに配置されているときのWebアプリケーションのコンテキスト名を得ることができます。コンテキスト名とは、Webアプリケーションにアクセスするための名前です。
一つのWebコンテナには、複数のWebアプリケーションを配備することができます。Webアプリケーションを制御するためのURI(注18)は有限の名前空間ですし、再利用性を高めるためにそれぞれのWebアプリケーション内でURIをハードコードすることは避けたくもあります。そこで、J2EEの仕様では、Webアプリケーションに対してアクセスするための基準となる名前=コンテキスト名を定義するようになっているのです。
例えばともに「Servlet1」という名前を持つWebアプリケーションを、二つ同時に同じWebコンテナに配備することを考えてみてください。http://hoge.hoge/Servlet1という名前しか割り当てられないとすると、そのURLでリクエストされたときに、どちらのWebアプリケーションのServlet1を起動すればよいのかわかりません。そこで、それぞれのWebアプリケーションにsample1とsample2というコンテキスト名をつけて配備すると、sample1のServlet1にアクセスするにはhttp://hoge.hoge/sample1/Servlet1、sample2のServlet1にアクセスするにはhttp://hoge.hoge/sample2/Servlet1のURLにアクセスすればよいことになるわけです。BookStoreServletの場合は、/コンテキスト名/bookListでアクセスされたらBookSearchBeanを、それ以外ならTopBeanを呼び出すようになっています。
スクリプトレットにJavaプログラムを直接書く
最後にJSPファイルであるtop.jspを見てみましょう(リスト6)。
リスト6(top.jsp)
<%@ page // (1)begin
contentType="text/html;
charset=MS932"
import="examples.*"
language="java" %>// (1)end
<% // (2)begin
Category[] categories = (Category[])request.getAttribute(
TopBean.CATEGORIES);
%> // (2)end
<html>
<head>
<title>オンライン書店 トップ</title>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="private" />
</head>
<body>
<h1>ようこそ オンライン書店へ!</h1>
<p>ご希望の商品カテゴリを選択してください</p>
<ul>
<% for(int i = 0; categories != null && i < categories.length; i++){ %> // (2)
<li><a href="bookList?<%=BookSearchBean.CATNAME%>=<%=
categories[i].getName()%>"><%=
categories[i].getNameForView()%></a></li>
<% } %> // (2)
</ul>
</body>
</html>
(1)の<%@ page ~%>で囲まれた部分は、前回のexample.jspとほとんど同じですね。一つ付け加えられているのが「import="examples.*"」という記述で、これは「このJSP内ではexampleパッケージをimportしますよ」という宣言です。Javaソースに変換される際にimport文に変換されます。
目新しいのが<% ~ %>の個所です(2)。このタグで囲まれた部分をスクリプトレットと呼び、Javaのプログラムをそのまま書くことができます。ここでは、TopBeanでHttpServletRequestにsetAttribute( )しておいたCategoryの一覧を取り出しています。取り出した内容を表示するためには、<%= ~ %>のタグを使っています。コンテンツに文字列を出力するためのタグです。
書籍一覧を表示するためのリンクは、bookList?<%=BookSearchBean.CATNAME%>=<%=categories[i].getName( )%> に対して張っています。BookSearchBeanで必要とするカテゴリ名を、HTTPパラメータとして埋め込んでいるわけです。
(注12)EJB(Enterprise JavaBeans)は、Javaのコンポーネント仕様JavaBeansをサーバー側で使えるように拡張したもの。
(注13)propertiesファイルは、サーバーの設定情報などを保存するJ2EE用のテキスト・ファイル。
(注14)MVC(Model-View-Controller)モデル2は、Webアプリケーション用のコンポーネント構成モデル。詳しくは連載1回目(日経ソフトウエア2002年9月号)を参照。
(注15)永続化は、ファイルやデータベースなど、メモリー以外にオブジェクトを保存すること。
(注16)デザイン・パターンは、オブジェクト指向プログラミングを行ううえでひな形となる設計。
(注17)あるクラスが持つインスタンス・メソッドを、クラスにひも付けながらクラス・メソッドと区別して表現したい場合、.(ドット)で区切るのではなく#(シャープ)で区切ることが多い。リスト4(2)のgetRequestDispatcher( )はRequestDispatcherのインスタンス・メソッドなので、ここではRequestDispatcher#forward( )と表記している。
(注18)URI(Uniform Resource Identifier)は、リソースを一意に表現するためのURLの上位概念。
