NetBeans Eコマースのチュートリアル - セッションの管理

このページの内容は、NetBeans IDEバージョン6.8および6.9に適用されます

なんらかのショッピング・カート機能を提供するすべてのEコマース・アプリケーションでは、ユーザーがWebサイトの中でクリックしたときにユーザー固有のデータを覚える必要があります。開発者にとって残念ですが、インターネットの通信で使用されるHTTPプロトコルはステートレスなプロトコルです。サーバーが受け取る各リクエストは、以前に受け取ったリクエストとは無関係の独立した情報です。このため、顧客がボタンをクリックして自分のショッピング・カートに項目を追加したら、アプリケーションはこのユーザーのカートの状態を更新するのみでなく、このアクションが、同時にサイトをブラウズしているユーザーのカートに影響しないようにする必要があります。

上記のシナリオを適切に処理するには、ユーザーがサイトにアクセスしている間、セッションを作成し、セッションを維持できるようにする機能を実装する必要があります。このために、すべてのJavaベースのWebアプリケーションの基盤であるサーブレット・テクノロジにはHttpSessionインタフェースが用意されています。また、セッションが維持されている間、アプリケーションが一時的にユーザー・データを格納できるようにするShoppingCartおよびShoppingCartItemというクラスを定義する必要があります。

このチュートリアル・ユニットでは、他のNetBeans Eコマースのチュートリアルとは異なる方法を取ります。プロジェクト・ファイルを作成してそのプロジェクトにコピーして貼付けできるコード・スニペットを提供するという手順に従うのではなく、このユニットの完成したプロジェクトのスナップショットを開き、IDEデバッガやその他のツールを使用してコードを調べます。このプロセスの中で、HttpSessionオブジェクトをコードに適用して、Webサイトへのアクセスごとに専用のセッションを作成する方法を学習します。また、スコープ指定された変数について、およびそれをJavaクラスやJSPページで使用する方法についても学習します。このユニットでは、セッションを維持するためのHttpSessionのデフォルト機構(Cookie)についても説明し、ユーザーのブラウザでCookieが非アクティブ化されている場合に必要となる手順を示します。ユニットの最後ではセッション・タイム・アウトについても触れ、リクエストをインターセプトしてセッションが存在するかどうかを確認する単純なフィルタを作成して、セッション・タイム・アウトを処理する方法を示します。

このチュートリアルでビルドするアプリケーションのライブ・デモを、NetBeans Eコマースのチュートリアルのデモ・アプリケーションで表示できます。



ソフトウェアまたはリソース 必須バージョン
NetBeans IDE Javaバンドル版、6.8または6.9
Java Development Kit (JDK) バージョン6
GlassFishサーバー v3またはOpen Source Edition 3.0.1
MySQLデータベース・サーバー バージョン5.1
AffableBeanプロジェクト スナップショット5

注意:

  • 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>
            &euro; ${cartItem.total}
            <br>
            <span class="smallText">( &euro; ${product.price} / unit )</span>
        </td>
        ...
    </tr>

</c:forEach>

ShoppingCartItemproductプロパティは、カート項目の製品タイプを識別します。上記のループでは、最初にproduct変数を${cartItem.product}の式に設定することで、これを利用しています。その後、この変数を使用して、名前や価格などの製品に関する情報を取得しています。

Webアプリケーションでのスコープ指定された変数の操作

JSP/サーブレット・テクノロジを扱う場合、アプリケーションのレルム内で使用できる4つのスコープ・オブジェクトがあります。JSPテクノロジには、サーブレットAPIによって定義されるクラスにアクセスできる暗黙オブジェクトが実装されています。

スコープ 定義 サーブレット・クラス JSP暗黙オブジェクト
アプリケーション Webアプリケーションのグローバル・メモリー javax.servlet.ServletContext applicationScope
セッション ユーザー・セッションに固有のデータ javax.servlet.http.HttpSession sessionScope
リクエスト 個々のサーバー・リクエストに固有のデータ javax.servlet.HttpServletRequest requestScope
ページ 単一のページ(JSPのみ)のコンテキストのみで有効なデータ [n/a] pageScope

エディタでプロジェクトのcategory.jspファイルを開くと、EL式に${categories}${selectedCategory}および${categoryProducts}などの様々なスコープ指定された変数が含まれているのが確認できます。${categories}変数はアプリケーション・スコープ指定されており、次のようにControllerServletinitメソッドで設定されています。

// 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リクエストに応答します。したがって、選択されたカテゴリに関係するselectedCategorycategoryProductsを知る必要があります。この情報は、リクエストごとに確立するのではなく、複数のリクエストにまたがって保持し、必要なときにアクセスできるように、categoryリクエストからセッション・スコープに置きます。また、カート・ページによって提供される機能を調べます。(機能については後で説明します。)「continue shopping」ボタンを押すと、ユーザーは前に表示されていたカテゴリに戻ります。再度selectedCategory変数とcategoryProducts変数が必要です。

EL式でスコープ指定された変数を参照する場合、(異なるスコープに同じ名前の2つの変数がないと仮定して)変数のスコープを指定する必要はありません。JSPエンジンは4つすべてのスコープをチェックして、最初に一致した変数を返します。たとえば、category.jspにある次の式を見てください。

${categoryProducts}

これは、次の式の短縮形です。

${sessionScope.categoryProducts}
詳細は、次のリソースを参照してください。

Javaデバッガによるセッション・データの確認

アプリケーションが実行時にどのように動作するかを調べます。IDEのデバッガを使用してコードをステップ実行し、HttpSessionがどのように作成されるのか、また、後で取得できるように他のオブジェクトをセッション・スコープに置く方法を調べます。

  1. このチュートリアル・ユニットのプロジェクト・スナップショットを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
  2. プロジェクトを実行(「プロジェクトの実行」ボタン)して、使用しているデータベースとアプリケーション・サーバーで適切に構成されていることを確認します。

    プロジェクトの実行時にエラーが発生した場合は、データベースの準備や、IDE、GlassFishおよびMySQL間の接続の確立について説明した設定手順をもう一度確認してください。

  3. ブラウザでアプリケーションの機能をテストします。前のチュートリアル・ユニットから直接継続している場合は、次のような機能拡張に気付くでしょう。

    カテゴリ・ページ

    • 初めて「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」をクリックすると、(ユーザー固有のデータなしで)確認ページが表示されます。
    チェックアウト・ページのブラウザのイメージ
  4. 「ファイルに移動」ダイアログを使用して、エディタでControllerServletを開きます。[Alt]-[Shift]-[O] (Macの場合は[Ctrl]-[Shift]-[O])を押してから、ダイアログで「Controller」と入力して「OK」をクリックします。
    「ファイルに移動」ダイアログ
  5. HttpSessionオブジェクトを作成する行(150行目)のdoPostメソッドにブレークポイントを設定します。ブレークポイントを設定するには、エディタの左マージンをクリックします。
    エディタで設定されたブレークポイント

    エディタの行番号表示を切り替えるには、左マージンを右クリックして「行番号を表示」を選択します。

  6. デバッガを実行します。IDEのメイン・ツールバーにある「プロジェクトをデバッグ」(「プロジェクトをデバッグ」ボタン)ボタンをクリックします。GlassFishサーバーが起動(すでに実行中の場合は再起動)し、そのデバッグ・ポート番号でソケットを開きます。アプリケーションの開始ページがブラウザで開きます。

    デバッグ・ポート番号は、「サーバー」ウィンドウ(「ツール」>「サーバー」)から表示および変更できます。使用しているサーバーの「Java」タブを選択します。「デバッグ設定」の下の「使用するアドレス」フィールドにポート番号を指定します。

  7. アプリケーションの開始ページがブラウザに表示されたら、いずれかのカテゴリ・イメージをクリックしてカテゴリ・ページに移動します。「add to cart」ボタンをクリックすると、次のようにサーバーにaddToCartリクエストが送信されることを思い出してください。
    <form action="addToCart" method="post">
    ページ・ビューおよびコントローラ・サーブレットの準備で説明したように、ControllerServletdoPostメソッドは、/addToCartのURLパターンのリクエストを処理します。このため、ユーザーが「add to cart」ボタンをクリックするとdoPostメソッドがコールされることを想定できます。
  8. カテゴリ・ページで、いずれかのカテゴリの「add to cart」をクリックします。IDEに戻ると、デバッガがブレークポイントで一時停止されていることがわかります。
    エディタのブレークポイントで一時停止されたデバッガ
  9. getSession()へのコールにカーソルを置き、[Ctrl]-[Space]を押してJavadocドキュメントを呼び出します。
    エディタに表示されたJavadocドキュメント
    ドキュメントによると、getSession()は現時点でリクエストに関連付けられているHttpSessionを返し、セッションが存在しない場合、このメソッドは新しいセッション・オブジェクトを作成します。

  10. session変数の上にカーソルを移動します。デバッガは、それが実行しようとしている行で一時停止されています。getSession()によって返される値は、この時点ではsession変数に保存されておらず、ポップアップには「"session"は、現在のコンテキスト内で既知の変数ではありません。」と表示されます。
    エディタに表示されたデバッガのポップアップ
  11. エディタの上にあるデバッガ・ツールバーの「ステップ・オーバー」(ステップ・オーバー・ボタン)ボタンをクリックします。この行が実行され、デバッガはファイルの次の行に進みます。
  12. 再度session変数の上にカーソルを移動します。今度は、session変数に現在設定されている値を確認します。
    エディタに表示されたデバッガのポップアップ

    NetBeans 6.9では、ポップアップでグレーのポインタ(ステップ・オーバー・ボタン)をクリックすると、強調表示された要素に含まれている変数の値の一覧を展開できます。

  13. 「ステップ・オーバー」(ステップ・オーバー・ボタン)ボタン([F8]、Macの場合は[fn]-[F8])をクリックしてif文(154行目)に入ります。ブラウザで「add to cart」ボタンをクリックしたばかりなので、userPath.equals("/addToCart")の式はtrueとして評価されるはずです。
  14. [Ctrl]を押しながらマウスでクリックしてuserPath.equals("/addToCart")の式を強調表示します。今度は、強調表示した式の値を示すポップアップが表示されます。
    ポップアップに表示された評価済の式
  15. [F8] (Macの場合は[fn]-[F8])を押して次の行(158行目)に進みます。このアプリケーションは、ユーザーが初めてカートに項目を追加するときにのみユーザー・セッションのShoppingCartオブジェクトを作成するように設計されています。このデバッグ・セッションでaddToCartリクエストが受け取られたのはこれが最初であるため、cartオブジェクトはnullと等しいと想定できます。
    ポップアップに表示された評価済の式
  16. [F8] (Macの場合は[fn]-[F8])を押して次の行(160行目)に進みます。次に、ShoppingCartオブジェクトが作成される160行目で、「ステップ・イン」(ステップ・イン・ボタン)ボタンをクリックします。コールされるメソッドにデバッガがステップ・インします。この場合、直接ShoppingCartのコンストラクタに移動します。
    ポップアップに表示された評価済の式
  17. [Ctrl]-[Tab]を押してControllerServletに戻ります。「コール・スタック」(「コール・スタック」バッジ)バッジが160行目に表示されます(現在、デバッガがコール・スタックの上位にあるいずれかのメソッドで一時停止されていることが表示されています)。

    [Alt]-[Shift]-[3] (Macの場合は[Ctrl]-[Shift]-[3])を押すとIDEの「コール・スタック」ウィンドウが開きます。

  18. [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行目でインスタンス化されたselectedCategorycategoryProductsです。これらの項目は両方とも、以前カテゴリ・イメージをクリックしてControllerServletがカテゴリ・ページのリクエストを処理したときにバインドされました。
  19. [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行目)
    • ShoppingCartItemShoppingCartitemsリストに追加される(170行目)
  20. 上記で一覧表示された4つのアクションを意識しながら、[F8] (Macの場合は[fn]-[F8])を押して、コードの実行を進めます。デバッガが170行目で一時停止したら一時休止します。
  21. セッションにウォッチを作成します。これによって、次の手順でaddItemメソッドにステップ・インするときに、セッションに含まれている値を表示できるようになります。「変数」ウィンドウでセッションを右クリックして、「固定ウォッチを作成」を選択します。
    「変数」ウィンドウに表示された右クリック・メニュー

    または、エディタ内のsession変数にカーソルを置いてから、右クリックして「新規ウォッチ」を選択します。「新規ウォッチ」ダイアログでは、アプリケーションのデバッグ時に継続的に監視する変数または式を指定できます。(式の場合は、最初に式を強調表示してから、右クリックして「新規ウォッチ」を選択します。)
    「新規ウォッチ」ダイアログ

    session変数とそれに含まれるすべての変数の新しいウォッチが作成されます。ウォッチは、「ウォッチ」ウィンドウ(「ウィンドウ」>「デバッグ」>「ウォッチ」)から表示するか、「変数」ウィンドウの左マージンにある「ウォッチ」(「ウォッチ」ボタン)ボタンを切り替えて「変数」ウィンドウの最初の行に表示可能です。

    コードをステップ実行しながら、デバッガで変数を監視できるようになります。これは、たとえば特定の変数の値をたどる場合(そして各手順で「変数」ウィンドウに示される全リストから選択しなくても済むようにする場合)や、調べる必要のある変数が含まれていないクラスに一時的にステップ・インする場合に役立ちます。
  22. 「ステップ・イン」(ステップ・イン・ボタン)ボタンをクリックして、ShoppingCartaddItemメソッドにステップ・インします。
  23. 53行目までaddItemメソッドをステップ実行します。Javadocに記述されているとおり、addItemShoppingCartitemsリストにShoppingCartItemを追加します。指定されたproductの項目がすでにショッピング・カート・リストに存在する場合、その項目の数量が増加します。」
  24. (上記のステップ21で)ウォッチを作成したsession変数を調べます。51行目のitems.add(scItem)文によって、ShoppingCartitemsリストに新しいShoppingCartItemが追加されました。これは、セッションに含まれている3つ目の属性(cart変数)を調べるとわかります。
    「変数」ウィンドウ
    この段階で、リクエストのためにHttpSessionが作成される方法、ShoppingCartオブジェクトが作成されてセッションにアタッチされる方法、およびShoppingCartItemがユーザーの製品選択に基づいて作成され、ShoppingCartitemsのリストに追加される方法を確認できます。残っているアクションは、category.jspビューへのリクエストの転送のみです。
  25. エディタでJSPフラグメント(header.jspf)を開き、86行目にブレークポイントを設定します。この行には、カート項目の数を表示する、ショッピング・カート・ウィジェット内のEL文が含まれています。
    JSPページに設定されたブレークポイント
  26. デバッガ・ツールバーの「続行」(「続行」ボタン)ボタンをクリックします。デバッガは実行が完了するか、別のブレークポイントに達するまで続行されます。この場合、デバッガはヘッダーのJSPフラグメントの86行目で一時停止されます。

    注意: JSPページでデバッガを一時停止させるには、ブレークポイントを設定する必要があります。たとえば、ControllerServletがリクエストを適切なビューに転送したとき、デバッガはJSPページ内で自動的に一時停止されません。

  27. まだ開いていない場合は「変数」ウィンドウを開きます([Alt]-[Shift]-[1]、Macの場合は[Ctrl]-[Shift]-[1])。Javaクラスとは異なり、JSPページではデバッガで変数や式の上にカーソルを移動してもツールチップは表示されません。ただし、コードをステップ実行しながら、「変数」ウィンドウで変数の値を判定できます。では、${cart.numberOfItems}の値はどこにあるでしょうか。
  28. 「変数」ウィンドウで、「暗黙的なオブジェクト」>「pageContext」>「session」>「session」>「attributes」ノードを展開します。これによって、前にControllerServletをデバッグしていたときのように、セッション・オブジェクトにアクセスできるようになります。上記のステップ21でウォッチを作成したセッションは、実は同じオブジェクトを指しています。ここで、${cart.numberOfItems}の値が「1」と等しいことを確認できます。
    「変数」ウィンドウ

    「変数」ウィンドウなどのIDEのウィンドウは、いずれもウィンドウのヘッダーを右クリックしてから「ウィンドウを最大化」([Shift]-[Esc])を選択することで最大化できます。

    デバッガで、pageContextの暗黙オブジェクトにアクセスできるようになります。pageContextはJSPページのコンテキストを示しており、HttpServletRequestHttpSessionServletContextオブジェクトなどの様々なオブジェクトへの直接的なアクセスを提供します。詳細は、Java EE 5チュートリアル: 暗黙オブジェクトを参照してください。
  29. セッションの終了(セッションの終了ボタン)ボタンをクリックします。ランタイムが実行を完了し、デバッグ・セッションが終了します。ブラウザにカテゴリ・ページが完全にレンダリングされ、ページ・ヘッダーにあるショッピング・カート・ウィジェットに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モニターと連携してデバッガを使用します。

  1. まず、使用しているサーバーのHTTPモニターをアクティブ化します。「ツール」>「サーバー」を選択します。「サーバー」ウィンドウの左の列で、使用しているサーバー(GlassFish)を選択します。次にメインの列で、「HTTPモニターを有効化」オプションを選択します。
    「サーバー」ウィンドウ
  2. サーバーがすでに実行されている場合は再起動する必要があります。しかし、ここではデバッガを使用する予定であり、デバッガを実行すると異なるポートで通信するためにサーバーが再起動されるため、単にIDEのメイン・ツールバーにある「プロジェクトをデバッグ」(「プロジェクトをデバッグ」ボタン)ボタンをクリックします。サーバーが再起動し、デバッグ・セッションが開始して、アプリケーションの開始ページがブラウザで開きます。HTTPモニターがIDEの最下部の領域に表示されます。
    HTTPモニター
  3. 上記のイメージで示したように、左の列のAffableBeanレコードをクリックします。左の列でレコードを選択すると、右の(メインの)列がリフレッシュされ、対応するデータが表示されます。上記のイメージの「リクエスト」タブには、リクエストされたURI (/AffableBean/)およびHTTPメソッド(GET)が表示され、さらにリクエストと一緒に送信された問合せ文字列はなかったことが示されています。
  4. 「セッション」タブを選択します。「このリクエストの結果、セッションが作成されました」という文が表示されています。これは、サーバーがレスポンスとしてJSESSIONID CookieのSet-Cookieヘッダーを送信したためです。また、新しいセッションIDが「セッション・プロパティ」の下に表示されています。後で示すように、セッションIDはJSESSIONID Cookieの値です。
    HTTPモニター - 「セッション」タブ
    サイトの開始ページのリクエストからセッション・オブジェクトが作成された方法について疑問に思うかもしれません。結局、ControllerServlet/AffableBean/の最初のリクエストを処理しておらず、このリクエストがgetSession()の影響を受ける機会はどこにもありません。それとも、これが行われたのでしょうか。JSPページは、デプロイメント時にサーブレットにコンパイルされることを思い出してください。サーバーにプロジェクトをデプロイすれば、実際にIDEを使用して、サーバー上のJSPのコンパイルされたサーブレットを表示できます。
  5. 「プロジェクト」ウィンドウでindex.jspファイルを右クリックし、「サーブレットを表示」を選択します。エディタでindex_jsp.javaファイルが開きます。これは、index.jspページから自動的にコンパイルされたサーブレットです。
  6. このファイルでgetSessionを検索します。[Ctrl]-[F] (Macの場合は[⌘]-[F])を押し、検索バーで「getSession」と入力してから[Enter]を押します。

    [Ctrl]-[F] (Macの場合は[⌘]-[F])は、「編集」>「検索」のキーボード・ショートカットです。

    getSessionメソッドが表示されたエディタ
    実は、getSessionメソッドはコールされます。これが起こる理由は、JSPページにはデフォルトでpageContext.sessionの暗黙オブジェクトが含まれるためです。この動作を非アクティブ化するには、JSPファイルの最初に次のディレクティブを追加します。
    <%@page session="false" %>
    このようにすると、コンパイルされたサーブレットのgetSessionメソッドが除去されます。

    サーバー上のコンパイルされたサーブレットの場所を見つけるには、エディタの上にあるサーブレット名のタブ上にカーソルを移動します。ポップアップに、コンピュータ上のファイルへのパスが表示されます。

  7. ブラウザで、カテゴリを選択してからカートに項目を追加します。IDEに戻ります。以前に設定したControllerServletのブレークポイント(150行目)でデバッガは一時停止されます。すべてのブレークポイントは、セッション間で記憶されます。ブレークポイントを除去するには、エディタの左マージンにある「ブレークポイント」(ブレークポイント・バッジ)バッジをクリックします。しかし、このプロジェクトにはすでに複数のブレークポイントが設定されているため、「ウィンドウ」>「デバッグ」>「ブレークポイント」でデバッガの「ブレークポイント」ウィンドウを開きます。
    「ブレークポイント」ウィンドウ
    「ブレークポイント」ウィンドウから、IDEで開いているプロジェクトで設定されたすべてのブレークポイントを表示してアクションをコールできます。
  8. header.jspfに設定されたブレークポイントを右クリックして、「削除」を選択します。その後、ControllerServletに設定されたブレークポイントを右クリックして、「無効」を選択します。(この課題の後半で再度有効にします。)
  9. 「続行」(「続行」ボタン)ボタンをクリックします。リクエストの実行が終了し、カートに項目が1つ追加されたカテゴリ・ページがブラウザに表示されます。
  10. HTTPモニターの左の列でaddToCartリクエストを検索して選択すると、メインの列に詳細が表示されます。

    昇順ソート(昇順ソート・ボタン)ボタンをクリックすると、最新のレコードが最上部に表示されます。


    「リクエスト」タブの下で、リクエストされたURI (/AffableBean/addToCart)、HTTPメソッド(POST)、およびリクエストされたパラメータ(productIdおよびsubmit)を確認してください。
    HTTPモニター - 「リクエスト」タブ
  11. 「Cookie」タブを選択します。ここでは、JSESSIONIDという名前のCookieが存在し、クライアントからサーバーに送信されたことを確認できます。Cookieの値は、「セッション」タブの下に表示されたセッションIDと同じです。
    HTTPモニター - 「Cookie」タブ
    同様に、「ヘッダー」タブをクリックするとCookieが表示されます。これは、「Cookie」がクライアントによって送信されたリクエスト・ヘッダーであるためです。
    HTTPモニター - 「Cookie」タブ

    リクエストおよびレスポンス・ヘッダーの詳細は、ウィキペディアのHTTPヘッダーの一覧を参照してください。

  12. 「セッション」タブを選択します。「このリクエストの前にセッションが存在しました」という文が表示されています。また、cart属性が「リクエスト後のセッション属性」の下に表示されています。addToCartリクエストが初めて処理されるときにcartオブジェクトがセッションにバインドされるため、これは理にかなっています。
    HTTPモニター - 「セッション」タブ

    以降のいくつかの手順では、「変数」ウィンドウでセッションIDおよびJSESSIONID Cookieを確認します。
  13. 以前にControllerServletに設定したブレークポイントを再度有効にします。[Alt]-[Shift]-[5] (Macの場合は[Ctrl]-[Shift]-[5])を押して「ブレークポイント」ウィンドウを開き、ブレークポイント・エントリの横にあるチェックボックスをクリックして、再度有効にします。
  14. ブラウザで、一覧表示された製品のうちの1つで「add to cart」ボタンをクリックします。
  15. IDEに切り替えると、デバッガがControllerServletに設定されたブレークポイントで一時停止されています。「ステップ・オーバー」(ステップ・オーバー・ボタン)ボタンをクリックして、session変数をセッション・オブジェクトに割り当てます。
  16. 「変数」ウィンドウを開き([Alt]-[Shift]-[1]、Macの場合は[Ctrl]-[Shift]-[1])、「session」>「session」を展開します。セッションIDが「id」変数の値として表示されます。
  17. JSESSIONID Cookieを見つけるために、通常はHttpServletRequestgetCookiesメソッドをコールすることで、サーブレットからCookieにアクセスできることを思い出してください。したがってrequestオブジェクトを、「request」>「継承」>「request」>「request」>「継承」>「cookies」と展開します。これでcookies ArrayListが表示されます。リストを展開するとJSESSIONID Cookieがあり、この値がセッションIDになります。
  18. セッションの終了(セッションの終了ボタン)ボタンをクリックして、デバッグ・セッションを終了します。

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>タグがこの機能を果たします。以下の課題で、問題と解決法を示します。

  1. ブラウザのCookieを一時的に無効にします。Firefoxを使用している場合、「ツール」>「オプション」(Macの場合は「Firefox」>「プリファレンス」)を選択します。表示されたウィンドウで「プライバシー」タブを選択してから、表示されたドロップダウンで「記憶させる履歴を詳細設定する」を選択します。「サイトから送られてきたCookieを保存する」オプションを選択解除します。
    Firefoxの「環境設定」ウィンドウ
  2. AffableBeanプロジェクトを実行します。開始ページが表示されたら、カテゴリをクリックしてからカートに項目を追加してみてください。現状ではアプリケーションがまったく機能しません。
    壊れたカテゴリ・ページ
    以前と同様に、サーバーはセッションを生成して、そのセッションにオブジェクトをバインドします。このようにして、選択されたカテゴリおよび製品をカテゴリ・ページに表示できます。しかし、サーバーはJSESSIONID Cookieの設定に失敗しました。したがって、クライアントが2つ目のリクエストを作成したとき(ユーザーが「add to cart」をクリックしたとき)、サーバーにはリクエストが属しているセッションを識別する方法がありません。このため、以前にセッションで設定されたselectedCategorycategoryProductsなどの属性をいずれも見つけることができません。このような理由により、レスポンスのレンダリングにはこれらの属性によって指定される情報が欠如しています。
  3. エディタでプロジェクトのcategory.jspページを開きます。「add to cart」ボタンを実装する行(58行目)を見つけます。<form>要素のaction属性によって、サーバーに送信されるリクエストが決まります。
    <form action="addToCart" method="post">
  4. リクエストを変更して、<c:url>タグを通して渡されるようにします。
    <form action="<c:url value='addToCart'/>" method="post">
  5. [Ctrl]-[S] (Macの場合は[⌘]-[S])を押して、ファイルへの変更を保存します。IDEは、デフォルトで有効な「保存時にデプロイ」機能が備わっていることを思い出してください。これによって、保存された変更はすべて自動的にサーバーにデプロイされます。
  6. ブラウザで異なるカテゴリを選択して、新しく変更されたカテゴリ・ページをアプリケーションにレンダリングさせます。
  7. このページのソース・コードを調べます。Firefoxでは[Ctrl]-[U] (Macの場合は[⌘]-[U])を押します。各製品の「add to cart」ボタンに、URLに付加されたセッションIDが一緒に表示されています。
    <form action="addToCart;jsessionid=4188657e21d72f364e0782136dde" method="post">
  8. いずれかの項目で「add to cart」ボタンをクリックします。サーバーが、リクエストが属しているセッションを判定して、適切にレスポンスをレンダリングできるようになっていることを確認できます。
  9. 次に進む前に、ブラウザで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を抽出して、既存のセッションと組み合せるためのサーバーの追加手順が必要なため、サーバー側リソースも多く必要になります。


セッション・タイム・アウトの処理

セッションの時間間隔の設定

サーバーがセッションを維持する時間間隔の最大値について考慮する必要があります。Webサイトのトラフィックが多くなると、多数のセッションによってサーバーのメモリー・キャパシティが使い果たされる可能性があります。このため、使用されていないセッションを除去できるように、時間間隔を短くできます。一方で、セッションが短すぎると使い勝手が悪くなり、Webサイトの背後にあるビジネスに悪影響を与える可能性があります。これは避ける必要があります。AffableBeanアプリケーションの例で考えると、ユーザーがショッピング・カートに多くの項目を入れてからチェックアウトに進むとします。その後、クレジット・カードの詳細を入力する必要があることがわかり、財布を探しにいきます。クレジット・カードを持ってコンピュータに戻ってから、チェックアウト・フォームに入力して「submit」をクリックします。しかしこの間に、このユーザーのセッションはサーバーで有効期限切れになっていました。ショッピング・カートは空になり、ユーザーはホーム・ページにリダイレクトされます。このユーザーは、再度時間をかけて同じプロセスを実行するでしょうか。

次の手順では、AffableBeanプロジェクトのセッション・タイム・アウトの間隔を10分に設定する方法を示します。言うまでもなく実際の間隔は最終的に、サーバー・リソース、業務におけるアプリケーションの目標、およびWebサイトの需要によって決まります。

  1. エディタでアプリケーションのデプロイメント・ディスクリプタを開きます。[Alt]-[Shift]-[O] (Macの場合は[Ctrl]-[Shift]-[O])を押して、IDEの「ファイルに移動」ダイアログを使用します。「web」と入力してから「OK」をクリックします。
    「ファイルに移動」ダイアログ
    エディタの「XML」ビューにweb.xmlファイルが表示されます。NetBeansがweb.xmlファイルに提供しているテンプレートでは、デフォルトとして30分が設定されています。
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
  2. 「一般」タブをクリックして、「セッション・タイム・アウト」フィールドに「10」と入力します。
    web.xmlファイルの「一般」タブ
  3. ファイルを保存します([Ctrl]-[S]、Macの場合は[⌘]-[S])。

    「XML」ビューに戻ると、<session-timeout>要素が更新されていることを確認できます。
    <session-config>
        <session-timeout>10</session-timeout>
    </session-config>

注意: かわりに、<session-timeout>要素をすべて除去し、GlassFish固有のデプロイメント・ディスクリプタ(sun-web.xml)でsession-properties要素を編集することもできます。これを行うと、サーバーのWebモジュールにあるすべてのアプリケーションのグローバル・タイム・アウトが設定されます。詳細は、Oracle GlassFish Server 3.0.1アプリケーション開発ガイド: セッションの作成および管理を参照してください。

プログラムによるセッション・タイム・アウトの処理

アプリケーションがセッションに依存している場合、タイム・アウトしたセッションや識別できないセッションに対するリクエストを受け取ったときにも正常に処理できるようにする必要があります。AffableBeanアプリケーションでこれを実現するには、ControllerServletに送信されたリクエストをインターセプトする単純なフィルタを作成します。このフィルタはセッションが存在するかどうかをチェックして、存在しなければリクエストをサイトの開始ページに転送します。

  1. まず、ユーザーがサイトにアクセスしている途中でセッションがタイム・アウトする場合に起こる問題を調べます。一時的に、セッション・タイム・アウトの間隔を1分にリセットします。Webデプロイメント・ディスクリプタ(web.xml)を開き、<session-timeout>タグの間に「1」を入力します。
    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
  2. AffableBeanプロジェクトを実行します。ブラウザでカテゴリ・ページをクリックして、項目をいくつかカートに追加してから「view cart」をクリックします。
    ショッピング・カートの項目が表示されたカート・ページ
  3. 少なくとも1分間待ちます。
  4. カート・ページに表示された項目のうち、1つの項目の数量を更新します。(1から99までのいずれかの数字を入力できます。)「update」をクリックします。サーバーからHTTPステータス500のメッセージが返されます。
    ブラウザに表示されたGlassFishエラー・レポート
  5. IDEでGlassFishサーバー・ログを調べます。「出力」ウィンドウ([Ctrl]-[4]、Macの場合は[⌘]-[4])を開き、「GlassFish Server」タブを選択します。ログの最下部までスクロールして、エラーのスタック・トレースを調べます。
    ブラウザに表示されたGlassFishエラー・レポート
    サーバー・ログに、ControllerServletの184行目でNullPointerExceptionが発生したことが示されています。「出力」ウィンドウには、例外が発生した行へのリンクが表示されます。
  6. リンクをクリックします。ControllerServletの184行目に直接移動します。エディタの左マージンにあるエラー・バッジの上にカーソルを置くと、例外を説明するツールチップが表示されます。
    エディタに表示されたエラー・バッジおよびツールチップ
    リクエストを受け取る前にセッションがすでに有効期限切れになっていたため、サーブレット・エンジンは、対応するセッションにリクエストを関連付けることができませんでした。このため、cartオブジェクトを見つけられませんでした(151行目)。最終的に184行目で、nullと等しい変数のメソッドをエンジンがコールしようとしたときに例外が発生しました。

    これで問題を特定できたので、フィルタを実装して修正しましょう。
  7. IDEのメイン・ツールバーにある「新規ファイル」(「新規ファイル」ボタン)ボタンをクリックします。または、[Ctrl]-[N] (Macの場合は[⌘]-[N])を押します。
  8. Web」カテゴリから「フィルタ」を選択し、「次」をクリックします。
  9. フィルタに「SessionTimeoutFilter」という名前を付けます。「パッケージ」フィールドに「filter」と入力して、フィルタ・クラスが作成時に新しいパッケージに配置されるようにします。
  10. 「次」をクリックします。デフォルトの設定を受け入れ、「終了」をクリックします。SessionTimeoutFilterのテンプレートが生成され、エディタで開きます。

    注意: NetBeans 6.9の時点では、ウィザードを使用してWebデプロイメント・ディスクリプタに登録されていないサーブレットへのマッピングを設定できません。(ControllerServlet@WebServlet注釈を使用して登録されています。)このため、生成されたコードを次の手順で変更します。

  11. @WebFilter注釈署名を次のように変更します。
    @WebFilter(servletNames = {"Controller"})
    public class SessionTimeoutFilter implements Filter {
    これによって、ControllerServletが処理するすべてのリクエストをインターセプトするようにフィルタが設定されます。(または、urlPatterns属性を保持して、ControllerServletが処理するすべてのパターンをリストすることもできます。)

    サーブレットの@WebServlet注釈署名に指定されているように、「Controller」はControllerServletの名前です。また、フィルタ・クラスの名前がデフォルトで使用されているため、filterName属性は除去しています。

    IDEのフィルタ・テンプレート自体に、調べる価値のある有用なコードが多く含まれています。ただし、そのほとんどはここでの目的には不要なものです。どのフィルタ・クラスも、次の3つのメソッドを定義するFilterインタフェースを実装する必要があります。
    • init: フィルタが初期化されてから使用が開始されるまでの間に、任意のアクションを実行します。
    • destroy: 使用を停止してフィルタを除去します。このメソッドは、任意のクリーン・アップ操作を実行するためにも使用できます。
    • doFilter: フィルタがインターセプトするリクエストごとの操作の実行に使用されます。

    Filterインタフェースに関するドキュメントを参照するには、「Javadoc索引検索」を使用します。[Shift]-[F1] (Macの場合は[fn]-[Shift]-[F1])を押してから、検索フィールドに「Filter」と入力して[Enter]を押します。「Interface in javax.servlet」エントリを選択します。索引検索ツールの下部のペインに、Javadocドキュメントが表示されます。

  12. SessionTimeoutFilterの本文を、次の内容に置き換えます。
    @WebFilter(servletNames = {"Controller"})
    public class SessionTimeoutFilter implements Filter {
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest req = (HttpServletRequest) request;
    
            HttpSession session = req.getSession(false);
    
            // if session doesn't exist, forward user to welcome page
            if (session == null) {
                try {
                    req.getRequestDispatcher("/index.jsp").forward(request, response);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                return;
            }
    
            chain.doFilter(request, response);
        }
    
        public void init(FilterConfig filterConfig) throws ServletException {}
    
        public void destroy() {}
    
    }
  13. [Ctrl]-[Shift]-[I] (Macの場合は[⌘]-[Shift]-[I])を押してインポート文を修正します。(HttpServletRequestおよびHttpSessionのためのインポートの追加が必要。)また、エディタのヒントを使用してinitdestroyおよびdoFilterメソッドに@Override注釈を追加します。

    以降の手順では、プロジェクトでデバッガを実行してdoFilterメソッドをステップ実行し、リクエストが既存のセッションにバインドされているかどうかをこのメソッドが判定する方法を確認します。
  14. 「ブレークポイント」ウィンドウを開き([Alt]-[Shift]-[5]、Macの場合は[Ctrl]-[Shift]-[5])、既存のブレークポイントが設定されていないことを確認します。ブレークポイントを削除するには、ブレークポイントを右クリックして「削除」を選択します。(前述の課題であるHTTPモニターによるクライアントとサーバー間の通信の確認を完了した場合は、ControllerServletに未処理のブレークポイントが設定されている可能性があります。)
  15. デバッガを実行します。IDEのメイン・ツールバーにある「プロジェクトをデバッグ」(「プロジェクトをデバッグ」ボタン)ボタンをクリックします。
  16. ブラウザに開始ページが表示されたら、カテゴリを選択してからショッピング・カートにいくつか項目を追加します。
  17. SessionTimeoutFilterdoFilterメソッドで、セッションへのアクセスを試行する行(32行目)にブレークポイントを設定します。
    エディタで設定されたブレークポイント
  18. ブラウザで「view cart」ボタンをクリックします。IDEに切り替えると、デバッガがブレークポイントで一時停止されていることがわかります。

    その時点でセッション・オブジェクトが存在しない場合、getSession()は新しいセッション・オブジェクトを作成することを思い出してください。ここでは、オブジェクトが見つからなくても新規作成を行わないgetSession(false)を使用します。つまり、セッションが存在しない場合、メソッドはnullを返します。
  19. 「ステップ・オーバー」(ステップ・オーバー・ボタン)ボタンをクリックしてから、session変数の上にカーソルを移動します。前回のリクエストが送信されてから1分が経過していなければ、変数がStandardSessionFacadeに割り当てられていることを確認できます。これは、リクエストのセッション・オブジェクトを表します。
    セッション・オブジェクトに割り当られたsession変数を表示するポップアップ
  20. リクエストが処理されるまで、メソッドをステップ実行し続けます。sessionnullと等しくないため、if文をスキップすると、chain.doFilterはリクエストをControllerServletに転送します(44行目)。
  21. ブラウザで、1分間が過ぎたことを確認してから、カートの製品項目のうちの1つの数量を更新します。この手順は、この課題の最初の方で実行してステータス500のメッセージが返されたときと同じ手順です。ここでは、ControllerServletに送信されたリクエストをフィルタがインターセプトするようになったので、セッション・タイム・アウトが起きるとどうなるかを確認しましょう。
  22. 「update」をクリックしてからIDEに切り替えると、フィルタに設定されたブレークポイントでデバッガが再度一時停止されています。
  23. req.getSession(false)の式を強調表示してから、この上にカーソルを移動します。ここでは、セッションがすでに有効期限切れになっているため、式がnullと等しいことが確認できます。
    nullと等しいsession変数を表示するポップアップ
  24. 続けてコードをステップ実行します。ここでは、session変数がnullと等しくなっているため、35行目のif文が処理され、リクエストが/index.jspに転送されます。デバッガが実行を終了すると、ブラウザにサイトの開始ページが表示されます。
  25. セッションの終了(セッションの終了ボタン)ボタンをクリックして、デバッグ・セッションを終了します。
  26. プロジェクトのweb.xmlファイルを開き、セッション・タイム・アウトの間隔を10分に戻します。
    <session-config>
        <session-timeout>10</session-timeout>
    </session-config>
  27. ファイルを保存([Ctrl]-[S]、Macの場合は[⌘]-[S])します。

スナップショット6は、このチュートリアル・ユニットのプロジェクトの完成版を示しています。最後に、セッション管理に関する1つのトピックについて説明します。セッション・オブジェクトでinvalidateメソッドをコールすることで、セッションを明示的に終了させることができます。セッションが不要になったら、サーバーが使用するメモリーを節約するために、そのセッションは除去するようにしてください。次のユニットであるビジネス・ロジックの取引の統合を完了すると、顧客の注文を正常に処理したときにControllerServletinvalidateメソッドを使用してどのようにユーザーの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 (およびそれ以降のスナップショット)に示されています。


関連項目

get support for the NetBeans

Support


By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2018, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo