Restaurantアプリケーション・プログラムの幾つかのポイント
|
Restaurantアプリケーション・プログラムの幾つかのポイント
-
共有オブジェクトの生成と追加
3つのサーブレットが共有するRestaurantMenuとRestaurantMenuBusyFlag二つのオブジェクトは、いったい誰が設定するのか疑問を感じる人がいるかもしれない。これはRestaurantChef、RestaurantMaster、RestaurantWaitressのいずれのサーブレットからもこのオブジェクトのインスタンス化とコンテキストの属性への(参照の)追加を行うことができる。誰が行うかといえばエンジンが最初にコンテナにロードし、インスタンス化したサーブレットの初期化にあたってinitメソッドを呼ぶことで行う。つまり3つのサーブレットのinitメソッドには以下のようなコード片が存在する。
ServletContext context = getServletContext(); if
(context.getAttribute("menu") == null){ RestaurantMenu menu =
new RestaurantMenu();
context.setAttribute("menu", menu); RestaurantMenuBusyFlag
menuBusyFlag = new RestaurantMenuBusyFlag();
context.setAttribute("menuBusyFlag", menuBusyFlag); } |
このmenuとmenuBusyFlagの2つのオブジェクトを生成したサーブレットは継続してコンテナ上に存在することは保証されていない。一般にはこのことはいえるものの、運用途中でソフトウエアの変更をして、その変更が自動再ロードされ、新規のサーブレットのインスタンスと置き換わることもある。更にはサーブレット2.2版仕様書の3.3.4節には「サーブレット・コンテナは、いつもサーブレットをロードしたままにしなければならないとはされていない」と記されている。メモリー空間やその他の状況でコンテナが勝手にそのサーブレットを終了させ、コンテナから外してしまい、このサーブレットへの要求が到来したらまたロードすることもある。その場合、コンテキストに属性として追加したオブジェクトを生成したサーブレットのインスタンスが削除されたとき、このオブジェクトはガーベージ・コレクションの対象にならないか心配する人がいるかも知れない。しかしこれは大丈夫である。ServletContextは存続しており、これにより該オブジェクトが参照されたままになっているので、メモリー上から削除されることは無い。
-
共有オブジェクトの参照
二つの共有オブジェクトは次のように各サーブレットが参照している。
ServletContext
context = getServletContext();
RestaurantMenu menu =
(RestaurantMenu)context.getAttribute("menu"); RestaurantMenuBusyFlag menuBusyFlag = (RestaurantMenuBusyFlag)context.getAttribute("menuBusyFlag"); |
-
別のサーブレットへの作業依頼
RstaurantWaitressは、顧客の注文をシェフに伝えて調理を依頼したり、仕込残以上の注文を受けるとマスターにお断りを依頼したりする。そのようなメカニズムとしてincludeとforwardの二つのメソッドが用意されている。forwardは要求と応答を転送して、HTTP応答の一切をそのサーブレットに依頼する。従ってforwardの場合は転送する前に出力処理(コミット)をしてはならない。もししてしまっているときは相手のサーブレットを呼ぶ前に出力バッファをリセットしておかないとIllegalStateException例外が発生する。includeは応答生成の一部をそのサーブレットに依頼する。この事例ではincludeが以下のように使われている。
//out.println("getContextPath: " +
request.getContextPath());
//このパスの後に続くパスをパラメタとする context.getRequestDispatcher("/servlet/RestaurantMaster").include(request,
response); context.getRequestDispatcher("/servlet/RestaurantChef").include(request, response); |
getRequestDispatcherメソッドに渡すpathストリングが判らないときは、そのサーブレットのURLのgetContextPath()で得られる文字列以降がpathである。includeメソッドで呼ばれたサーブレットでは
PrintWriter out = response.getWriter(); |
のようにresponseからoutを取得して応答メッセージを書き込む。
-
メニュー・ビジー・フラグの取扱い
ビジー・フラグについては既に詳細に説明した。ここでは強制的にビジーを開放できるRestaurantMasterがどうやってビジーを開放するかを説明する。
//初めて、このメニューを強引にロックする
while (menuBusyFlag.isBusyFlagOn() == true){
menuBusyFlag.freeBusyFlag(); try{
Thread.sleep(250); }catch (InterruptedException e){} } menuBusyFlag.getBusyFlag(); //それからメニューをビジーに |
このプログラム片のように、MenuBusyFlagのbusyflagのフィールドがオンであると、一度freeBusyFlagメソッドを呼び出して待機中のスレッドをひとつ再開させる。しかるのち適当な時間(ここでは250ミリ秒:多分その間に再開させられたスレッドはサーブレットを抜ける)中断する。これを繰り返して最終的に待機中のスレッドが無くなったら今度は自分がこのメニューのビジーを獲得する。とはいってもビジー解除したスレッドが進行しており、それがこのフラグを解除してしまうのは避けられない。マスターが値づけに悩んでいるうちに解除されたウェイトレスが注文を受けてしまうタイミングがあることに注意しよう。
-
セッションの取扱い
各サーブレットのセッションが終了したとき、つまりウェイトレスで言えば顧客が食事を終了して支払いを請求したときに、そのsessionオブジェクトを無効にする。
session.invalidate(); //この場合はセッションはこれで終了 menuBusyFlag.freeBusyFlag(); //メニューのロックを開放 |
このように、一般にはセッションとビジー・フラグとは対応するので、顧客がメニューを見たままで長時間席をはずしたりしてセッションにタイムアウトが生じたときにもメニューのロックを解除するのは良いアイデアである。セッションを無効にする理由は、クライアントがブラウザの「更新(reload)」ボタンをクリックして、このサーブレットを再呼び出ししたときに、セッションの最初の画面を表示できないからである。ブラウザは「更新」ボタンが押されると、直前に送信したHTTP要求と全く同じパケットをサーバに送信する。
-
表示順の制御
RestaurantMenuはHashtableをサブクラス化しているので、順番は保証されない。このことは、ときには不都合を生じる。このアプリケーションでは、料理はアペタイザ(前菜)、アントレー(メインディシュ)、デザートの順番でメニューに表示され、また顧客に調理して出さねばならない。顧客は勝手でそのような順番で注文するとは限らないとしたら、何らかの順番の基準となるものが必要である。この例では、シェフの持つrepertory(シェフの料理レパートリー)というString[]型のフィールドをその基準としている。これはもともとRestaurantChefのフィールドなので、initメソッドでこれをmenuオブジェクトのrepertoryにコピーしている。但し、シェフが最初にインスタンス化されていないときもあるので、doMakeMenuメソッドのなかでもこれを行っている。
-
セッションがあるアプリケーションと、ブラウザやプロキシのキャッシング
ブラウザはクライアントが以前アクセスしたことのあるURLをアクセスしようとしたとき、その情報をブラウザにキャッシングされていると、その情報を高速で表示しサーバをアクセスしない。このことはセッションを使って同じURLでダイナミックに画面が変わるアプリケーションでは不都合である。同様なメカニズムがプロキシ・サーバにも存在する。プロキシ(Proxy)はISPなどに設置されている。他にもキャッシングを行うゲートウエイが介在する可能性がある。これを防止する目的で以下のようなプログラム片が各サーブレットに挿入されている。
response.setHeader("Cache-Control",
"no-cache"); //ブラウザやProxyにキャッシングさせない PrintWriter out = response.getWriter(); out.println("<HTML><HEAD><TITLE>RestaurantChef</TITLE>"); out.println("<META HTTP-EQUIV=\"pragma\"
CONTENT=\"no-cache\">"); //本ページはキャッシングをさせない out.println("</HEAD><BODY><H1>本日のメニュー作成</H1><PRE>"); |
最初の行はHTTPパケットのヘッダにキャッシングをさせない制御行を挿入するものである。これはブラウザやプロキシが認識することができる。3行目はHTMLのヘッダに付加情報タグとして指示するもので、これはブラウザが認識する。但しブラウザによってはこが無視されるので注意が必要である。筆者の場合はIEは無視し、Netscapeはこれを認識した。
いずれにしても、クライアントがなるべくブラウザの「更新(再読み込み)」ボタンをクリックしたり「アドレス(場所)」を操作しないようにすることが重要である。このアプリケーションでは、顧客がRestaurantWaitressをアクセスして食事を終わったら、ブラウザには「新規顧客ご来店」のボタンを表示して、再度このサーブレットを簡単にアクセスできるようにしている。シェフやマスターもセッションが終了したときはボタン操作で再呼び出しするようにしている。このボタンを押す限り、ブラウザはクエリーを送信するので、キャシュしていたページを表示することは無い。