NetBeans E コマースのチュートリアル - セッションの管理
NetBeans E コマースのチュートリアル - セッションの管理
何らかのショッピングカート機能を提供するすべての E コマースアプリケーションでは、ユーザーが Web サイトの中でクリックしたときにユーザー固有のデータを覚える必要があります。開発者にとって残念ですが、インターネットの通信で使用される HTTP プロトコルはステートレス なプロトコルです。サーバーが受け取る各要求は、以前に受け取った要求とは無関係の独立した情報です。このため、顧客がボタンをクリックして自分のショッピングカードに項目を追加したら、アプリケーションはこのユーザーのカートの状態を更新するだけでなく、このアクションが、同時にサイトをブラウズしているユーザーのカートに影響しないようにする必要があります。
上記のシナリオを適切に処理するには、ユーザーがサイトにアクセスしている間、セッション を作成し、セッションを維持できるようにする機能を実装する必要があります。このために、すべての Java ベースの Web アプリケーションの基盤であるサーブレットテクノロジには HttpSession インタフェースが用意されています。また、セッションが維持されている間、アプリケーションが一時的にユーザーデータを格納できるようにする ShoppingCart および ShoppingCartItem というクラスを定義する必要があります。
このチュートリアルユニットでは、ほかの NetBeans E コマースのチュートリアルとは異なる方法を取ります。プロジェクトファイルを作成してそのプロジェクトにコピー&ペーストできるコードスニペットを提供するという手順に従うのではなく、このユニットの完成したプロジェクトのスナップショットを開き、IDE デバッガなどのツールを使用してコードを調べます。このプロセスの中で、HttpSession オブジェクトをコードに適用して、Web サイトへのアクセスごとに専用のセッションを作成する方法を学習します。また、スコープ指定された変数 について、およびそれを Java クラスや JSP ページで使用する方法についても学習します。このユニットでは、セッションを維持するための HttpSession のデフォルト機構 (Cookie) についても説明し、ユーザーのブラウザで Cookie が無効になっている場合に必要となる手順を示します。ユニットの最後ではセッションタイムアウトについても触れ、要求をインターセプトしてセッションが存在するかどうかを確認する単純なフィルタを作成して、セッションタイムアウトを処理する方法を示します。
このチュートリアルで構築するアプリケーションのライブデモを、「NetBeans E コマースのチュートリアルのデモアプリケーション 」で確認できます。
注:
NetBeans IDE が正常に動作するには、JDK (Java Development Kit) が必要です。上記に一覧表示されているいずれのリソースもインストールされていない場合は、最初に JDK をダウンロードしてインストールするようにしてください。
NetBeans IDE の Java バンドル版には、このチュートリアルで構築するアプリケーションに必要な Java Web および EE テクノロジが含まれています。
NetBeans IDE の Java バンドル版には、このチュートリアルに必要な GlassFish サーバーも含まれています。GlassFish サーバーを別個にダウンロードする こともできますが、NetBeans ダウンロードに付属するバージョンを使用すると、IDE に自動的に登録されるので便利です。
このチュートリアルユニットは、以前のユニットを完了させていなくても進めることができます。そのために、データベースの準備や、IDE、GlassFish、および MySQL 間の接続の確立について説明した設定手順 を参照してください。
セッションデータの処理
アプリケーションは、HttpSession オブジェクトを使用してユーザーセッションを管理できます。ユーザー固有のデータを HttpSession オブジェクトにバインドして、あとでこのデータにアクセスできます。バインドとアクセスの両方のアクションは、Java クラスから行うことも、EL 式のセッションスコープ指定された変数から行うことも可能です。
HttpSession オブジェクトの操作
AffableBean アプリケーションは HttpSession オブジェクトを使用して、複数の要求にわたってユーザーを識別します。HttpSession オブジェクトを取得するには、特定の要求で次のように getSession() を使用します。
HttpSession session = request.getSession();
その要求のセッションオブジェクトがまだ存在しない場合、このメソッドは新しいセッションを作成して返します。
セッションオブジェクトは、要求間でデータを渡すための輸送手段として使用できます。オブジェクトをセッションにバインドするには、setAttribute メソッドを使用します。同様に、オブジェクトをセッションから取得するには、getAttribute を使用します。たとえば AffableBean アプリケーションでは、次の方法でユーザーのショッピングカートが作成され、ユーザーセッションにバインドされます。
ShoppingCart cart = new ShoppingCart();
session.setAttribute("cart", cart);
セッションからカートを取得するために、次のように getAttribute メソッドが適用されます。
cart = (ShoppingCart) session.getAttribute("cart");
JSP ページでは、セッションにバインドされているオブジェクトに EL 式を使用してアクセスできます。上記の例を続けて使用します。「cart」という名前の ShoppingCart オブジェクトがセッションにバインドされている場合、次の EL 式を使用することでこのオブジェクトにアクセスできます。
${cart}
しかし、ShoppingCart オブジェクト自体にアクセスしてもほとんど意味がありません。本当に必要なのは、オブジェクトに格納されている値にアクセスするための方法です。プロジェクトのスナップショットで新しい ShoppingCart クラスを調べると、次のプロパティーが含まれていることがわかります。
double total
int numberOfItems
List<String, ShoppingCartItem> items
プロパティーに対応する取得メソッドがあれば、EL 式で単純なドット表記法を使用して個々のプロパティーの値にアクセスできます。cart.jsp ページを調べると、次のように、ちょうどこの方法で numberOfItems の値にアクセスしているのが確認できます。
<p>Your shopping cart contains ${cart.numberOfItems} items.</p>
上記の items リストのような複数の値を含むプロパティーからデータを抽出するために、cart.jsp ページでは次のように <c:forEach> ループを使用しています。
<c:forEach var="cartItem" items="${cart.items}" varStatus="iter">
<c:set var="product" value="${cartItem.product}"/>
<tr class="${((iter.index % 2) == 0) ? 'lightBlue' : 'white'}">
<td>
<img src="${initParam.productImagePath}${product.name}.png"
alt="${product.name}">
</td>
<td>${product.name}</td>
<td>
€ ${cartItem.total}
<br>
<span class="smallText">( € ${product.price} / unit )</span>
</td>
...
</tr>
</c:forEach>
ShoppingCartItem の product プロパティーは、カート項目の製品タイプを識別します。上記のループでは、最初に product 変数を ${cartItem.product} の式に設定することで、これを利用しています。そのあと、この変数を使用して、名前や価格などの製品に関する情報を取得しています。
Web アプリケーションでのスコープ指定された変数の操作
JSP/サーブレットテクノロジを扱う場合、アプリケーションのレルム内で使用できる 4 つのスコープオブジェクトがあります。JSP テクノロジには、サーブレット API によって定義されるクラスにアクセスできる「暗黙オブジェクト 」が実装されています。
エディタでプロジェクトの category.jsp ファイルを開くと、EL 式に ${categories}、${selectedCategory}、および ${categoryProducts} などのさまざまなスコープ指定された変数が含まれているのが確認できます。${categories} 変数はアプリケーションスコープ指定されており、次のように ControllerServlet の init メソッドで設定されています。
// store category list in servlet context
getServletContext().setAttribute("categories", categoryFacade.findAll());
ほかの ${selectedCategory} と ${categoryProducts} の 2 つは、ControllerServlet からアプリケーションのセッションスコープに置かれています。例:
// place selected category in session scope
session.setAttribute("selectedCategory", selectedCategory);
注: 前のチュートリアルユニットから続けている場合、${selectedCategory} と ${categoryProducts} はもともと要求スコープ内に置かれていたことに気付くかもしれません。前のユニットではこれで問題ありませんでしたが、ここではユーザーがカテゴリページで「add to cart」ボタンをクリックしたらどうなるかを考えてください。サーバーは、現在表示されているカテゴリページを返すことによって、addToCart 要求に応答します。したがって、選択されたカテゴリに関係する selectedCategory と categoryProducts を知る必要があります。この情報は、要求ごとに確立するのではなく、複数の要求にまたがって保持し、必要なときにアクセスできるように、category 要求からセッションスコープに置きます。また、カートページによって提供される機能を調べます。(機能についてはあとで 説明します。)「continue shopping」ボタンを押すと、ユーザーは前に表示されていたカテゴリに戻ります。再度 selectedCategory 変数と categoryProducts 変数が要求されます。
EL 式でスコープ指定された変数を参照する場合、(異なるスコープに同じ名前の 2 つの変数がないと仮定して) 変数のスコープを指定する必要はありません。JSP エンジンは 4 つすべてのスコープを調べて、最初に一致した変数を返します。たとえば、category.jsp にある次の式を見てください。
${categoryProducts}
これは、次の式の短縮形です。
${sessionScope.categoryProducts}
詳細については、次のリソースを参照してください。
Java デバッガによるセッションデータの確認
アプリケーションが実行時にどのように動作するかを調べます。IDE のデバッガを使用してコードを段階的に実行し、HttpSession がどのように作成されるのか、また、あとで取得できるようにほかのオブジェクトをセッションスコープに置く方法を調べます。
このチュートリアルユニットのプロジェクトスナップショット を IDE で開きます。「プロジェクトを開く」( ) ボタンをクリックし、ウィザードを使用して、このプロジェクトをダウンロードしたコンピュータ上の場所に移動します。前のチュートリアルユニット から続けている場合は、このプロジェクトスナップショットに ShoppingCart クラスと ShoppingCartItem クラスを含む新しい cart パッケージが含まれています。また、次のファイルも変更されています。
WEB-INF/web.xml
css/affablebean.css
WEB-INF/jspf/header.jspf
WEB-INF/jspf/footer.jspf
WEB-INF/view/cart.jsp
WEB-INF/view/category.jsp
WEB-INF/view/checkout.jsp
controller/ControllerServlet
プロジェクトを実行 ( ) して、使用しているデータベースとアプリケーションサーバーで適切に構成されていることを確認します。
プロジェクトの実行時にエラーが発生した場合は、データベースの準備や、IDE、GlassFish、および MySQL 間の接続の確立について説明した設定手順 をもう一度確認してください。
ブラウザでアプリケーションの機能をテストします。前のチュートリアルユニット から直接継続している場合は、次のような機能拡張に気付くでしょう。
カテゴリページ
はじめて「add to cart」をクリックするとショッピングカートが有効になり、「proceed to checkout」ウィジェットがヘッダーに表示されます。
「add to cart」をクリックすると、ヘッダーにあるショッピングカートウィジェットのカート項目の数が更新されます。
「view cart」をクリックすると、カートページが表示されます。
「proceed to checkout」をクリックすると、チェックアウトページが表示されます。
カートページ
「clear cart」をクリックすると、ショッピングカートの項目が空になります。
「continue shopping」をクリックすると、前に表示されていたカテゴリページに戻ります。
「proceed to checkout」をクリックすると、チェックアウトページが表示されます。
項目の「quantity」フィールドに 1 から 99 までの数字を入力してから「update」をクリックすると、項目の合計と小計が再計算されます。
項目の「quantity」フィールドに 0 を入力してから「update」をクリックすると、表示された表からその項目が削除されます。
チェックアウトページ
「view cart」をクリックすると、カートページが表示されます。
「submit purchase」をクリックすると、(ユーザー固有のデータなしで) 確認ページが表示されます。
「ファイルに移動」ダイアログを使用して、エディタで ControllerServlet を開きます。Alt-Shift-O (Mac の場合は Ctrl-Shift-O) を押してから、ダイアログで「Controller」と入力して「了解」をクリックします。
HttpSession オブジェクトを作成する行 (150 行目) の doPost メソッドにブレークポイントを設定します。ブレークポイントを設定するには、エディタの左マージンをクリックします。
エディタの行番号表示を切り替えるには、左マージンを右クリックして「行番号を表示」を選択します。
デバッガを実行します。IDE のメインツールバーにある「プロジェクトをデバッグ」( ) ボタンをクリックします。GlassFish サーバーが起動 (すでに実行中の場合は再起動) し、そのデバッグポート番号でソケットを開きます。アプリケーションの開始ページがブラウザで開きます。
デバッグポート番号は、「サーバー」ウィンドウ (「ツール」>「サーバー」) から表示および変更できます。使用しているサーバーの「Java」タブを選択します。「デバッグ設定」の下の「使用するアドレス」にポート番号を指定します。
アプリケーションの開始ページがブラウザに表示されたら、いずれかのカテゴリ画像をクリックしてカテゴリページに移動します。「add to cart」ボタンをクリックすると、次のようにサーバーに addToCart 要求が送信されることを思い出してください。
<form action="addToCart" method="post">
「ページビューおよびコントローラサーブレットの準備 」で説明したように、ControllerServlet の doPost メソッドは、/addToCart の URL パターンの要求を処理します。このため、ユーザーが「add to cart」ボタンをクリックすると doPost メソッドが呼び出されることを想定できます。
カテゴリページで、いずれかのカテゴリの「add to cart」をクリックします。IDE に戻ると、デバッガがブレークポイントで中断されていることがわかります。
getSession() への呼び出しにカーソルを置き、Ctrl- スペースキーを押して Javadoc ドキュメントを呼び出します。
ドキュメントによると、getSession() は現時点で要求に関連付けられている HttpSession を返し、セッションが存在しない場合、このメソッドは新しいセッションオブジェクトを作成します。
IDE の Javadoc サポートの利用
IDE には、Java EE 開発向けの Javadoc が組み込まれています。IDE には Java EE 6 API 仕様がバンドルされており、「ヘルプ」>「Javadoc 参照」>「Java EE 6」を選択して外部ブラウザで開けます。
IDE にはこのほかにも、API ドキュメントに簡単にアクセスできる、次のようなさまざまな機能が含まれています。
Javadoc ウィンドウ: 「ウィンドウ」>「その他」>「Javadoc」を選択します。Javadoc ウィンドウは IDE の下部領域で開き、エディタのカーソル位置に関連する API ドキュメントを表示します。
Javadoc 索引検索: 「ヘルプ」>「Javadoc 索引検索」(Shift-F1、Mac の場合は fn-Shift-F1) を選択します。探しているクラス名を入力してから、一覧表示された結果からクラスを選択します。ウィンドウの下部の区画に、API 仕様からクラスの完全な説明が表示されます。
エディタのドキュメントポップアップ: エディタの特定の要素で Ctrl- スペースキーを押すと、Javadoc ドキュメントがポップアップウィンドウに表示されます。「外部ブラウザ」( ) ボタンをクリックすると、ブラウザでドキュメントが開きます。Ctrl- スペースキーをコード補完のためだけに使用する場合は、「ツール」>「オプション」(Mac の場合は「NetBeans」>「設定」) で「オプション」ウィンドウを開いてから「エディタ」>「コード補完」を選択して、ドキュメントポップアップを無効にできます。「ドキュメントウィンドウを自動ポップアップ」オプションを選択解除します。
独自の作業をドキュメント化する場合、作成したクラスおよびメソッドに Javadoc コメントを追加することを検討してください。ShoppingCart クラスを開き、クラスメソッドに追加されている Javadoc コメントを確認します。Javadoc コメントは、/** ... */ 区切り文字でマークされています。たとえば、addItem メソッドには、メソッドの署名の前に次のようなコメントが入っています。
/**
* Adds a <code>ShoppingCartItem</code> to the <code>ShoppingCart</code>'s
* <code>items</code> list. If item of the specified <code>product</code>
* already exists in shopping cart list, the quantity of that item is
* incremented.
*
* @param product the <code>Product</code> that defines the type of shopping cart item
* @see ShoppingCartItem
*/
public synchronized void addItem(Product product) {
これによって開発者 (およびプロジェクトにかかわるほかの人) が、メソッドについての Javadoc ドキュメントを確認できるようになります。これを示すために、Ctrl-7 (Mac の場合は ⌘-7) で「ナビゲータ」を開き、カーソルを addItem メソッドの上に移動します。
IDE を使用して、一連の Javadoc HTML ページを生成することもできます。「プロジェクト」ウィンドウでプロジェクトノードを右クリックし、「Javadoc を生成」を選択します。IDE によって、プロジェクトのディレクトリの dist/javadoc フォルダに Javadoc が生成され、ブラウザにインデックスページが表示されます。
Javadoc の詳細については、次のリソースを参照してください。
session 変数の上にカーソルを移動します。デバッガは、それが実行しようとしている 行で中断されています。getSession() によって返される値はまだ session 変数に保存されておらず、ポップアップには「"session" は、現在のコンテキスト内で既知の変数ではありません。」と表示されます。
エディタの上にあるデバッガツールバーの「ステップオーバー」( ) ボタンをクリックします。この行が実行され、デバッガはファイルの次の行に進みます。
再度 session 変数の上にカーソルを移動します。今度は、session 変数に現在設定されている値を確認します。
NetBeans 6.9 では、ポップアップでグレーのポインタ ( ) をクリックすると、強調表示された要素に含まれている変数の値の一覧を展開できます。
「ステップオーバー」(「 ) ボタン (F8、Mac の場合は fn-F8) をクリックして if 文 (154 行目) に達します。ブラウザで「add to cart」ボタンをクリックしたばかりなので、userPath.equals("/addToCart") の式は true として評価されるはずです。
Ctrl を押しながらマウスでクリックして userPath.equals("/addToCart") の式を強調表示します。今度は、強調表示した式の値を示すポップアップが表示されます。
F8 (Mac の場合は fn-F8) を押して次の行 (158 行目) に進みます。このアプリケーションは、ユーザーがはじめてカートに項目を追加するときにだけユーザーセッションの ShoppingCart オブジェクトを作成するように設計されています。このデバッグセッションで addToCart 要求が受け取られたのはこれが最初であるため、cart オブジェクトは null と等しいと想定できます。
F8 (Mac の場合は fn-F8) を押して次の行 (160 行目) に進みます。次に、ShoppingCart オブジェクトが作成される 160 行目で、「ステップイン」( ) ボタンをクリックします。呼び出されるメソッドにデバッガがステップインします。この場合、直接 ShoppingCart のコンストラクタに移動します。
Ctrl-Tab を押して ControllerServlet に戻ります。IDE によって 160 行目に「呼び出しスタック」( ) バッジが表示されます。これは、現在デバッガが呼び出しスタックの上位にあるいずれかのメソッドで中断されていることを示しています。
Alt-Shift-3 (Mac の場合は Ctrl-Shift-3) を押すと IDE の「呼び出しスタック」ウィンドウが開きます。
F8 (Mac の場合は fn-F8) を押して、コードの実行を進めます。デバッガが ShoppingCart コンストラクタを完了すると、ControllerServlet に戻ります。
ControllerServlet の 161 行目では、新しく作成された cart オブジェクトをセッションにバインドします。
session.setAttribute("cart", cart);
これを確認するには、デバッガの「変数」ウィンドウを開きます。「ウィンドウ」>「デバッグ」>「変数」を選択するか、Alt-Shift-1 (Mac の場合は Ctrl-Shift-1) キーを押します。
「session」>「session」>「attributes」ノードを展開すると、セッションにバインドされているオブジェクトを表示できます。上記の画像では、現時点でセッションにバインドされている (強調表示された) 2 つの項目があります。これらは、それぞれ ControllerServlet の 83 行目と 89 行目でインスタンス化された selectedCategory と categoryProducts です。これらの項目は両方とも、以前カテゴリ画像をクリックして ControllerServlet がカテゴリページの要求を処理したときにバインドされました。
F8 (Mac の場合は fn-F8) を押して 161 行目を実行します。cart オブジェクトはセッションにバインドされており、「変数」ウィンドウは変更を反映して更新されます。「変数」ウィンドウでは、現在、セッションに 3 つの属性が含まれていることを確認できます。3 つ目の変数は、新しく初期化された ShoppingCart オブジェクト (次で強調表示) です。
これまでは、「変数」ウィンドウに一覧表示されたセッションが HttpSession セッションを示していることを「証明」しませんでした。前述のように、HttpSession は実際にはインタフェースであるため、HttpSession オブジェクト (セッションオブジェクト) について説明する場合、実際には HttpSession インタフェースを実装するオブジェクトを指しています。「変数」ウィンドウで「session」の上にカーソルを移動すると、この変数が HttpSession オブジェクトを表していることを示すポップアップが表示されます。表示されているように、StandardSessionFacade 型は、GlassFish が HttpSession インタフェースを実装するために使用する内部クラスです。Tomcat に詳しいユーザーが「値」列に表示された「org.apache.catalina」のパスに戸惑うのは、GlassFish Web/サーブレットコンテナが実は Apache Tomcat コンテナの派生クラスであるためです。
新しい ShoppingCart がセッションに追加され、要求の処理が続行されます。「add to cart」機能の実装を完成させるために、次のアクションが取られます。
選択された製品の ID が要求から取得される (165 行目)
ID を使用して Product オブジェクトが作成される (169 行目)
product を使用して新しい ShoppingCartItem が作成される (170 行目)
ShoppingCartItem が ShoppingCart の items リストに追加される (170 行目)
上記で一覧表示された 4 つのアクションを意識しながら、F8 (Mac の場合は fn-F8) を押して、コードの実行を進めます。デバッガが 170 行目で中断したら一時停止します。
セッションにウォッチを作成します。これによって、次の手順で addItem メソッドにステップインするときに、セッションに含まれている値を表示できるようになります。「変数」ウィンドウでセッションを右クリックして、「固定ウォッチを作成」を選択します。
または、エディタ内の session 変数にカーソルを置いてから、右クリックして「新規ウォッチ」を選択します。「新規ウォッチ」ダイアログでは、アプリケーションのデバッグ時に継続的に監視する変数または式を指定できます。(式の場合は、最初に式を強調表示してから、右クリックして「新規ウォッチ」を選択します。)
session 変数とそれに含まれるすべての変数の新しいウォッチが作成されます。ウォッチは、「ウォッチ」ウィンドウ (「ウィンドウ」>「デバッグ」>「ウォッチ」) に表示させるか、「変数」ウィンドウの左マージンにある「ウォッチ」( ) ボタンを切り替えて「変数ウィンドウ」の一番上の行に表示させることができます。
コードを段階的に実行しながら、デバッガで変数を監視できるようになります。これは、たとえば特定の変数の値をたどる場合 (そして各手順で「変数」ウィンドウに示される全リストから選択しなくても済むようにする場合) や、調べる必要のある変数が含まれていないクラスに一時的にステップインする場合に役立ちます。
「ステップイン」( ) ボタンをクリックして、ShoppingCart の addItem メソッドにステップインします。
53 行目まで addItem メソッドを段階的に実行します。Javadoc に記述されているとおり、addItem は「ShoppingCart の items リストに ShoppingCartItem を追加します。指定された product の項目がすでにショッピングカートリストに存在する場合、その項目の数量が増加します。」
(上記の手順 21 で) ウォッチを作成した session 変数を調べます。51 行目の items.add(scItem) 文によって、ShoppingCart の items リストに新しい ShoppingCartItem が追加されました。これは、セッションに含まれている 3 つ目の属性 (cart 変数) を調べるとわかります。
この段階で、要求のために HttpSession が作成される方法、ShoppingCart オブジェクトが作成されてセッションに接続される方法、および ShoppingCartItem がユーザーの製品選択に基づいて作成され、ShoppingCart の items のリストに追加される方法を確認できます。残っているアクションは、category.jsp ビューへの要求の転送だけです。
エディタで JSP フラグメント (header.jspf) を開き、86 行目にブレークポイントを設定します。この行には、カート項目の数を表示する、ショッピングカートウィジェット内の EL 文が含まれています。
デバッガツールバーの「続行」( ) ボタンをクリックします。デバッガは実行が完了するか、別のブレークポイントに達するまで続行されます。この場合、デバッガはヘッダーの JSP フラグメントの 86 行目で中断されます。
注: JSP ページでデバッガを中断させるには、ブレークポイントを設定する必要があります。たとえば、ControllerServlet が要求を適切なビューに転送したとき、デバッガは JSP ページ内で自動的に中断されません。
まだ開いていない場合は「変数」ウィンドウを開きます (Alt-Shift-1、Mac の場合は Ctrl-Shift-1)。Java クラスとは異なり、JSP ページではデバッガで変数や式の上にカーソルを移動してもツールチップは表示されません 。ただし、コードを段階的に実行しながら、「変数」ウィンドウで変数の値を判定できます。では、${cart.numberOfItems} の値はどこにあるでしょうか。
「変数」ウィンドウで、「暗黙的なオブジェクト」>「pageContext」>「session」>「session」>「attributes」ノードを展開します。これによって、前に ControllerServlet をデバッグしていたときのように、セッションオブジェクトにアクセスできるようになります。上記の手順 21 でウォッチを作成したセッションは、実は同じオブジェクトを指しています。ここで、${cart.numberOfItems} の値が「1」と等しいことを確認できます。
「変数」ウィンドウなどの IDE のウィンドウは、いずれもウィンドウのヘッダーを右クリックしてから「ウィンドウを最大化」(Shift-Esc) を選択することで最大化できます。
デバッガで、pageContext の暗黙オブジェクトにアクセスできるようになります。pageContext は JSP ページのコンテキストを示しており、HttpServletRequest、HttpSession、および ServletContext オブジェクトなどのさまざまなオブジェクトへの直接的なアクセスを提供します。詳細については、「Java EE 5 チュートリアル: 暗黙オブジェクト 」を参照してください。
「セッションを終了」( ) ボタンをクリックします。ランタイムが実行を完了し、デバッグセッションが終了します。ブラウザにカテゴリページが完全に描画され、ページヘッダーにあるショッピングカートウィジェットに 1 つの項目が含まれているのが確認できます。
IDE デバッガは、プロジェクトが想定どおりに動作しない場合の検査用としてだけでなく、コードに詳しくなるためのツールとしても便利であることがわかるでしょう。ほかにも、デバッガツールバーには次のような便利なボタンがあります。
( ) ステップアウト : 現在のメソッド呼び出しをステップアウトします。呼び出しスタックの最上位のメソッド呼び出しを実行して削除します。
( ) カーソルまで実行 : カーソルが置かれている行まで実行します。
( ) コードの変更を適用 : ファイルを編集してからこのボタンを押すと、ファイルが再コンパイルされ、変更がデバッグセッションに反映されます。
( ) 式をステップオーバー : 式のメソッド呼び出しごとの入力パラメータおよび結果の出力値を表示できるようにします。「局所変数」ウィンドウで、前のメソッドの出力値と次のメソッドの入力パラメータを検査できます。次のメソッド呼び出しが存在しない場合、「式をステップオーバー」は「ステップオーバー」( ) コマンドと同様に機能します。
セッション追跡オプションの確認
クライアントとサーバー間のセッションを追跡するための慣習的な方法が 3 つあります。圧倒的に多く使用されているのは Cookie です。URL 書き換えは、Cookie がサポートされていないか無効になっている場合に適用できます。隠しフォームフィールドも、複数の要求にわたって「状態を維持する」ための手段として使用できますが、これらはフォーム内での使用に制限されます。
AffableBean プロジェクトでは、カテゴリとカートの両方のページに隠しフィールドメソッドの例が含まれています。製品項目用に表示する「add to cart」および「update」ボタンには、ボタンがクリックされると製品 ID をサーバーに渡す隠しフィールドが含まれています。エディタで cart.jsp ページを開くと、<form> タグに隠しフィールドが含まれているのが確認できます。
<form action="updateCart" method="post">
<input type="hidden"
name="productId"
value="${product.id}">
...
</form>
このように、製品 ID は要求パラメータとして送信されます。この製品 ID は、ユーザーのカート内の項目の数量を変更する必要があるときに、項目を識別するためにサーバーによって使用されます。
サーブレット API は、セッションを管理するための高水準な機構を提供します。基本的にこの機構では、要求と応答のサイクルごとにクライアントとサーバー間で Cookie を作成して渡します。クライアントのブラウザで Cookie が使用できない場合、サーブレットエンジンは自動的に URL 書き換えに戻ります。次の 2 つの課題でこの機能を示します。
HTTP モニターによるクライアントとサーバー間の通信の確認
デフォルトでは、サーブレットエンジンは、Cookie を使用して要求間のセッションを維持および識別します。セッションオブジェクトごとにランダムな英数字が生成され、一意の識別子として使用されます。この識別子は、「JSESSIONID」Cookie としてクライアントに渡されます。クライアントが要求を作成すると、サーブレットエンジンは JSESSIONID Cookie の値を読み込み、その要求が属しているセッションを判定します。
これを示すために、IDE の HTTP モニターと連携してデバッガを使用します。
まず、使用しているサーバーの HTTP モニターを有効にします。「ツール」>「サーバー」を選択します。「サーバー」ウィンドウの左の列で、使用しているサーバー (GlassFish) を選択します。次にメインの列で、「HTTP モニターを有効化」オプションを選択します。
サーバーがすでに実行されている場合は再起動する必要があります。しかし、ここではデバッガを使用する予定であり、デバッガを実行すると異なるポートで通信するためにサーバーが再起動されるため、単に IDE のメインツールバーにある「プロジェクトをデバッグ」( ) ボタンをクリックします。サーバーが再起動し、デバッグセッションが開始して、アプリケーションの開始ページがブラウザで開きます。HTTP モニターが IDE の最下部の領域に表示されます。
上記の画像で示したように、左の列の AffableBean レコードをクリックします。左の列でレコードを選択すると、右の (メインの) 列が更新され、対応するデータが表示されます。上記の画像の「要求」タブには、要求された URI (/AffableBean/) および HTTP メソッド (GET) が表示され、さらに要求と一緒に送信されたクエリー文字列はなかったことが示されています。
「セッション」タブを選択します。「この要求の結果、セッションが作成されました」という文が表示されています。これは、サーバーが応答として JSESSIONID Cookie の Set-Cookie ヘッダーを送信したためです。また、新しいセッション ID が「セッションプロパティー」の下に表示されています。あとで示すように、セッション ID は JSESSIONID Cookie の値です。
サイトの開始ページの要求からセッションオブジェクトが作成された方法について疑問に思うかもしれません。結局、ControllerServlet は /AffableBean/ の最初の要求を処理しておらず、この要求が getSession() の影響を受ける機会はどこにもありません。それとも、これが行われたのでしょうか。JSP ページは、配備時にサーブレットにコンパイルされることを思い出してください。サーバーにプロジェクトを配備すれば、実際に IDE を使用して、サーバー上の JSP のコンパイルされたサーブレットを表示できます。
「プロジェクト」ウィンドウで index.jsp ファイルを右クリックし、「サーブレットを表示」を選択します。エディタで index_jsp.java ファイルが開きます。これは、index.jsp ページから自動的にコンパイルされたサーブレットです。
このファイルで getSession を検索します。Ctrl-F (Mac の場合は ⌘-F) を押し、検索バーで「getSession」と入力してから Enter を押します。
Ctrl-F (Mac の場合は ⌘-F) は、「編集」>「検索」のキーボードショートカットです。
実は、getSession メソッドは呼び出されます。これが起こる理由は、JSP ページにはデフォルトで pageContext.session の暗黙オブジェクトが含まれるためです。この動作を無効にするには、JSP ファイルの最初に次の指令を追加します。
<%@page session="false" %>
こうすると、コンパイルされたサーブレットの getSession メソッドが削除されます。
サーバー上のコンパイルされたサーブレットの場所を見つけるには、エディタの上にあるサーブレット名のタブ上にカーソルを移動します。ポップアップに、コンピュータ上のファイルへのパスが表示されます。
ブラウザで、カテゴリを選択してからカートに項目を追加します。IDE に戻ります。以前に設定した ControllerServlet のブレークポイント (150 行目) でデバッガは中断されます。すべてのブレークポイントは、セッション間で記憶されます。ブレークポイントを削除するには、エディタの左マージンにあるブレークポイント ( ) バッジをクリックします。しかし、このプロジェクトにはすでに複数のブレークポイントが設定されているため、「ウィンドウ」>「デバッグ」>「ブレークポイント」でデバッガの「ブレークポイント」ウィンドウを開きます。
「ブレークポイント」ウィンドウから、IDE で開いているプロジェクトで設定されたすべてのブレークポイントを表示してアクションを呼び出せます。
header.jspf に設定されたブレークポイントを右クリックして、「削除」を選択します。そのあと、ControllerServlet に設定されたブレークポイントを右クリックして、「無効」を選択します (この課題の後半で再度有効にします)。
「続行」( ) ボタンをクリックします。要求の実行が終了し、カートに項目が 1 つ追加されたカテゴリページがブラウザに表示されます。
HTTP モニターの左の列で addToCart 要求を検索して選択すると、メインの列に詳細が表示されます。
「昇順ソート」( ) ボタンをクリックすると、もっとも最近のレコードが最上部に表示されます。
「要求」タブの下で、要求された URI (/AffableBean/addToCart)、HTTP メソッド (POST)、および要求されたパラメータ (productId および submit) を確認してください。
「Cookie」タブを選択します。ここでは、JSESSIONID という名前の Cookie が存在し、クライアントからサーバーに送信されたことを確認できます。Cookie の値は、「セッション」タブの下に表示されたセッション ID と同じです。
同様に、「ヘッダー」タブをクリックすると Cookie が表示されます。これは、「Cookie」がクライアントによって送信された要求ヘッダーであるためです。
要求および応答ヘッダーの詳細については、ウィキペディアの「HTTP ヘッダーの一覧 」を参照してください。
「セッション」タブを選択します。「この要求の前にセッションが存在しました」という文が表示されています。また、cart 属性が「要求後のセッション属性」の下に表示されています。addToCart 要求がはじめて処理されるときに cart オブジェクトがセッションにバインドされるため、これは理にかなっています。
以降のいくつかの手順では、「変数」ウィンドウでセッション ID および JSESSIONID Cookie を確認します。
以前に ControllerServlet に設定したブレークポイントを再度有効にします。Alt-Shift-5 (Mac の場合は Ctrl-Shift-5) を押して「ブレークポイント」ウィンドウを開き、ブレークポイントエントリの横にあるチェックボックスをクリックして、再度有効にします。
ブラウザで、一覧表示された製品のうちの 1 つで「add to cart」ボタンをクリックします。
IDE に切り替えると、デバッガが ControllerServlet に設定されたブレークポイントで中断されています。「ステップオーバー」( ) ボタンをクリックして、session 変数をセッションオブジェクトに割り当てます。
「変数」ウィンドウを開き (Alt-Shift-1、Mac の場合は Ctrl-Shift-1)、「session」>「session」を展開します。セッション ID が「id」変数の値として表示されます。
JSESSIONID Cookie を見つけるために、通常は HttpServletRequest で getCookies メソッドを呼び出すことで、サーブレットから Cookie にアクセスできることを思い出してください。したがって request オブジェクトを、「request」>「継承」>「request」>「request」>「継承」>「cookies」と展開します。これで cookies ArrayList が表示されます。リストを展開すると JSESSIONID Cookie があり、この値がセッション ID になります。
「セッションを終了」( ) ボタンをクリックして、デバッグセッションを終了します。
URL 書き換えによるセッションの維持
前述のように、サーブレットエンジンはクライアントブラウザで Cookie がサポートされているかどうかを検出し、サポートされていない場合は、セッションを維持する手段を URL 書き換えに切り替えます。これはすべて、クライアントにとって透過的に行われます。開発者にとっては、このプロセスは完全に透過的なわけではありません。
Cookie が無効になっている場合に必ず URL 書き換えができるようにアプリケーションを実装する必要があります。このために、アプリケーション内でサーブレットによって返されるすべての URL に対して、応答の encodeURL メソッドを呼び出すようにします。このようにすると、Cookie が使用できない場合にはセッション ID が URL に付加されるようになります。これを行わないと、URL を変更せずに返すことになります。
たとえば、ブラウザが次のような AffableBean の 3 つ目のカテゴリ (bakery) の要求を送信します: category?3。サーバーは次のように、セッション ID を URL に含めて応答します。
/AffableBean/category;jsessionid=364b636d75d90a6e4d0085119990 ?3
前述のように、アプリケーションのサーブレットによって返されるすべての URL をエンコードする必要があります 。JSP ページはサーブレットにコンパイルされることを思い出してください。JSP ページでどのように URL をエンコードするのでしょうか。JSTL の <c:url> タグがこの機能を果たします。以下の課題で、問題と解決法を示します。
ブラウザの Cookie を一時的に無効にします。Firefox を使用している場合、「ツール」>「オプション」(Mac の場合は「Firefox」>「設定」) を選択します。表示されたウィンドウで「プライバシー」タブを選択してから、表示されたドロップダウンで「記憶させる履歴を詳細設定する」を選択します。「サイトから送られてきた Cookie を保存する」オプションを選択解除します。
AffableBean プロジェクトを実行します。開始ページが表示されたら、カテゴリをクリックしてからカートに項目を追加してみてください。現状ではアプリケーションがまったく機能しません。
以前と同様に、サーバーはセッションを生成して、そのセッションにオブジェクトをバインドします。このようにして、選択されたカテゴリおよび製品をカテゴリページに表示できます。しかし、サーバーは JSESSIONID Cookie の設定に失敗しました。したがって、クライアントが 2 つ目の要求を作成したとき (ユーザーが「add to cart」をクリックしたとき)、サーバーには要求が属しているセッションを識別する方法がありません。このため、以前にセッションで設定された selectedCategory や categoryProducts などの属性をいずれも見つけることができません。このような理由により、応答の描画にはこれらの属性によって指定される情報が欠如しています。
エディタでプロジェクトの category.jsp ページを開きます。「add to cart」ボタンを実装する行 (58 行目) を見つけます。<form> 要素の action 属性によって、サーバーに送信される要求が決まります。
<form action="addToCart" method="post">
要求を変更して、<c:url> タグを通して渡されるようにします。
<form action="<c:url value='addToCart'/> " method="post">
Ctrl-S (Mac の場合は ⌘-S) を押して、ファイルへの変更を保存します。IDE は、デフォルトで有効な「保存時に配備」機能が備わっていることを思い出してください。これによって、保存された変更はすべて自動的にサーバーに配備されます。
ブラウザで異なるカテゴリを選択して、新しく変更されたカテゴリページをアプリケーションに描画させます。
このページのソースコードを調べます。Firefox では Ctrl-U (Mac の場合は ⌘-U) を押します。各製品の「add to cart」ボタンに、URL に付加されたセッション ID が一緒に表示されています。
<form action="addToCart;jsessionid=4188657e21d72f364e0782136dde " method="post">
いずれかの項目で「add to cart」ボタンをクリックします。サーバーが、要求が属しているセッションを判定して、適切に応答を描画できるようになっていることを確認できます。
次に進む前に、ブラウザで Cookie を再度有効にしてください。
前述のように、アプリケーション内でユーザーがクリックできるすべてのリンクは、その応答が何らかの形式のセッション関連のデータを必要とする場合、適切にエンコードされる必要があります。上記の例ほど実装が単純ではない場合もあります。たとえば、cart.jsp で使用されている「clear cart」ウィジェットは、現時点ではリンクがクリックされると clear パラメータを true に設定します。
<%-- clear cart widget --%>
<c:if test="${!empty cart && cart.numberOfItems != 0}">
<a href="viewCart?clear=true " class="bubble hMargin">clear cart</a>
</c:if>
<c:url> タグは、次の方法で URL に適用できます。
<%-- clear cart widget --%>
<c:if test="${!empty cart && cart.numberOfItems != 0}">
<c:url var="url" value="viewCart">
<c:param name="clear" value="true"/>
</c:url>
<a href="${url} " class="bubble hMargin">clear cart</a>
</c:if>
clear=true パラメータは、<c:url> タグ間に <c:param> タグを追加することで設定されます。「url」という名前の変数が <c:url> の var 属性を使用して設定されたあと、var が ${url} 式を使用して HTML アンカータグでアクセスされます。
スナップショット 6 をダウンロードして調べれば、プロジェクトのすべてのリンクがどのようにエンコードされているかを確認できます。
URL 書き換えは、追跡メソッドとして Cookie が使用できない場合にのみ使用する必要があります。セッション ID がブラウザのアドレスバーだけでなく、ログ、ブックマーク、リファラヘッダー、およびキャッシュされた HTML に表示されるため、通常は URL 書き換えは次善手段と見なされています。また、受け取る要求ごとに URL からセッション ID を抽出して、既存のセッションと組み合わせるためのサーバーの追加手順が必要なため、サーバー側リソースも多く必要になります。
セッションタイムアウトの処理
スナップショット 6 は、このチュートリアルユニットのプロジェクトの完成版を示しています。最後に、セッション管理に関する 1 つのトピックについて説明します。セッションオブジェクトで invalidate メソッドを呼び出すことで、セッションを明示的に終了させることができます。セッションが不要になったら、サーバーが使用するメモリーを節約するために、そのセッションは削除するようにしてください。次のユニット「ビジネスロジックの取引の統合 」を完了すると、顧客の注文を正常に処理したときに ControllerServlet が invalidate メソッドを使用してどのようにユーザーの cart オブジェクトを破棄し、セッションを終了するかがわかります。
// if order processed successfully send user to confirmation page
if (orderId != 0) {
// dissociate shopping cart from session
cart = null;
// end session
session.invalidate();
...
}
これは、プロジェクトスナップショット 8 (およびそれ以降のスナップショット) に示されています。
関連項目
NetBeans リソース
GlassFish リソース
技術記事およびその他のリソース