この章ではServletコンテナが準備した多くのメソッドが出てきます。 そこで、本題に入る前にまずメソッドの呼び出し方の確認をします。 次にServletの重要な機能であるセッション管理機能の内容とその使い方を示します。 さらにスコープの考え方およびスコープデータの格納と取り出し方を説明し、最後にセッション管理においてアプリ開発者が注意しなければならないポイントを示します。

メソッドの呼び出し方

※ この項ではJavaの文法の復習をしながら話を進めます。 Javaのクラスとオブジェクトの概念をご存じない方は、次に進む前に「Web開発のためのJava入門 6章:クラス」の『クラスの例』と『インスタンス』のところをざっとお読みください。 コンストラクタのところは跳ばしてかまいません。

オブジェクトの必要性

メソッドは必ずどこかのクラスで定義されています。ですからメソッドを使うには、まずそれがどのクラスで定義されているかを知らねばなりません。 またクラスは単なる鋳型ですので、実際にメソッドを使うにはクラス自身ではなくクラスから生成されたオブジェクトを使う必要があります。

例えば、クラスNantaraでメソッドkantaraが定義されているとき、メソッドkantaraを使うことを考えてみましょう。まずkantaraがNantaraクラスで定義されていることを突き止めます。 次に(a)のようにNantaraクラスのオブジェクトを取得して適当な名前を付けます(以下ではnantと命名)。そしてそれを使って(b)のようにkantaraを呼ぶのです。


図解: メソッドの呼び出し

この様子を実例で見てみましょう。2章のサンプルソース(の一部)をもう一度示します。


図解: サンプルソース

このソースで最初に出てくるメソッドは(3)のsetCharacterEncodingです。このメソッドは何というクラスで定義されているでしょうか。 ServletのAPI仕様書(注5)で確認すると、それはServletRequestであることがわかります。 なお、ServletRequestはインターフェースでありクラスではありませんが、どこかでクラスに実装されているので、クラスと同じと思ってかまいません。

クラスがわかったら次はそのクラスのオブジェクトを入手することを考えます。ところが幸いなことに、このクラスのオブジェクトは(2)でdoGetのパラメーター(仮引数)として渡されreqと名付けられています。 したがってこのreqを使って次の(3)ようにsetCharacterEncodingメソッドを呼ぶことができたのです。


図解: メソッドの呼び出し

ただし、厳密にいうとreqはHttpServletRequestのオブジェクトであって、ServletRequestのオブジェクトではありません。 しかし、HttpServletRequestがServletRequestを継承しているので、これで良いのです。なぜなら継承していると元のクラスが持っているメソッドを使うことができるからです。 これは 2章のコラム で説明しました。

同様に(4)のgetParameterメソッドもServletRequestインターフェースで定義されているのでreqを使って呼ぶことができます。 また、(5)のsetContentTypeメソッドはServletResponseインターフェースで定義されているのでそ(れを継承しているHttpServletResponse)のオブジェクトであるresを使って呼ぶことができます。

オブジェクトの取得

上の例では必要とするオブジェクトreqやresは、都合の良いことにdoGetのパラメーターとして与えられていました。 しかし、そのようなオブジェクトが与えられていないクラスのメソッドを使うには、自分でオブジェクトを手に入れなければなりません。 例えばこの後に出てくるセッションに値を格納するsetAttributeメソッドはHttpSessionインターフェースで定義されているので、そのオブジェクトを取得する必要があります。

HttpSessionのオブジェクトを取得するにはgetSessionメソッドを使います。ところでメソッドを使うには、まず何をしなければならなかったでしょうか。 そう、そのメソッドが定義されているクラス(またはインターフェース)名を知り、そのオブジェクトを手に入れねばなりませんでした。 幸いgetSessionメソッドはHttpServletRequestインターフェースで定義されています。これのオブジェクトはreqとして与えられているのでした。 そこで次のようにしてgetSessionメソッドを発行できます。


図解: getSessionメソッドの発行

このメソッドはHttpSessionのオブジェクトを取得できるのでした。取得したオブジェクトには名前が必要です。HttpSessionのオブジェクトなのでsesと名付けたのが次です。


図解: オブジェクト名

そしてこのsesを使って次のようにsetAttributeメソッドを発行できるのです。この例は実際に後で出てきます。


図解: setAttributeメソッドの発行

長くなったので以上の説明をもう一度まとめておきます。

   a. setAttributeメソッドを発行するためHttpSessionのオブジェクトが必要
   b. HttpSessionのオブジェクトの取得のためにgetSessionメソッドを発行する
   c. getSessionメソッドの発行のためにHttpServletRequestのオブジェクトが必要
   d. HttpServletRequestのオブジェクトはreqとして入力パラメーターで受け取り済

おまけの話です。いま以下の(1)と(2)で目的が達成できると説明しましたが、これらは実は(3)のように1行で記述できます。 こうするとsesと言う名付けも不要です。ただし、この方法はプログラムの可読性が低下する恐れがあります。 また取得したオブジェクトsesをこの後で使う可能性があるときも適切ではありません。


図解: おまけ

オブジェクト名が不要なとき

メソッドの前にはオブジェクト名を付けるとお話しましたが、実は付けなくてよい時があります。 それは自クラスのメソッドを呼ぶときです。また自クラスでなくても、継承しているクラスのメソッドはそのまま呼べます。 これは 2章のコラム で説明しました。

例えば2章のサンプルソースでは(GreetingServletクラスが)HttpServletクラスを継承しています。 そしてHttpServletクラスはServletインターフェースおよびServletConfigインターフェースを実装しているので、これらの3つで定義されているメソッドは、すべてそのまま呼ぶことができます。

これも実例で示しましょう。例えばServletContextのオブジェクトを取得するgetServletContextメソッドはServletConfigインターフェースで定義されているので、 次のように(頭に xxx. を付けずに)そのまま呼び出せます。


図解: オブジェクト名なしの呼び出し

まとめ

ここで説明したメソッドの呼び出し方をまとめておきます(注6)

  1. ServletRequestインターフェースまたはHttpServletRequestインターフェースで定義されているメソッド:
    req.xxx で呼び出す
  2. ServletResponseインターフェースまたはHttpServletResponseインターフェースで定義されているメソッド:
    res.xxx で呼び出す
  3. HttpServletクラス、ServletインターフェースまたはServletConfigインターフェースで定義されているメソッド:
    何も付けずメソッド名だけで呼び出す
  4. それ以外のメソッド:
    そのメソッドが定義されているインターフェースまたはクラスのオブジェクトを取得し、その名前を頭に付けて呼び出す
(注5) API(Application Program Interface)仕様書: あるメソッドが、どのクラスやインターフェースで定義されているかはServletのAPI仕様書を参照してください。 これらはインターネットで簡単に検索できます。 例えばgoogle(http://www.google.co.jp/)でServletRequestを検索すると、 Sunが提供しているServletインターフェースのAPIのページが表示されます。
以下にこのようにして検索したServletRequestのAPIのサイトのURLを示します。
http://mergedoc.osdn.jp/tomcat-servletapi-5-ja/javax/servlet/ServletRequest.html

(注6) この説明ではHttpServletRequestオブジェクトの名前としてreqを使って来ましたが、 これは2章のサンプルソースの(2)で、そのような名前を付けたからです。 別の名前を付ければ当然それを使わなければなりません。resについても同様です。

セッション管理

セッション管理

さて、いよいよここからが本題のセッション管理の話です。セッションとは、サーバとクライアント間で一連の処理の間だけ一時的に成立するある関係のことです。 1章で述べたようにHTTPはステイトレス(過去の状態を覚えていない)プロトコルなので、Servletではセッション管理の仕組みが役立ちます。

セッションは常に成立している必要はありません。例えばショッピングサイトでユーザが商品を閲覧しているだけのときには不要です。 しかし何か商品を購入した後は、そのデータを後の決済画面まで保存しておくためにセッションを成立させる必要があります。

Servletの前身であるCGIではセッション管理の仕組みがなかったため、購入した商品の種類などのセッションデータの保存に苦労していました。 例えばhiddenタグ(画面表示抑制タグ)ではさんだデータをこっそりとクライアントに送っておき、次のリクエストでそれを送り返させるといった工夫が必要だったのです。

Servletではセッション管理の仕組みによりコンテナがこのような処理の手助けをしてくれます。セッション管理は次のようにして行います。 この手順のうち 2.~5. はコンテナやブラウザが自動的に行うのでアプリはなにも意識する必要はありません。

  1. アプリはセッション管理の開始時に、セッションIDとセッションオブジェクトを取得する。 セッションオブジェクトはセッションデータの入れ物であり、アプリは買った商品の情報などをここに格納することができる。
  2. コンテナは取得したセッションIDをレスポンスとしてクライアントに送る。
  3. クライアントはそのセッションIDをクッキーという情報格納域に格納する。
  4. クライアントは次のリクエストの送信時に、そのセッションIDを付加して送信する。
  5. Servletは付加されたセッションIDを見て、クライアントを認識する。
  6. アプリがセッションオブジェクトからデータを取り出す処理をすると、コンテナは 1. で取得したそのクライアントに対応するオブジェクトを割り当てる。

セッションオブジェクトとセッションIDの取得はgetSessionメソッドで行います。 このメソッドはHttpServletRequestインターフェースで定義されているので、HttpServletRequestのオブジェクトであるreqを使って次のようにします。 これについては前項で例として取り上げました。


図解: セッションオブジェクトの取得

ここで取得したsesはセッションIDではなく、セッションオブジェクトです。セッションオブジェクトを得るとこれを使ってセッションに共通なデータを保持しておくことができます。 その具体的なやり方は次項で説明します。

なお、getSessionメソッドにはちょっとした賢い仕組みがあります。 それはまだセッションが生成されていなければ、新たにセッションオブジェクトを生成して返し、すでに生成されていれば、そのセッションオブジェクトをそのまま返すことです。 これによりアプリは生成済か否かを意識せずにgetSessionを出すことができます。

セッションオブジェクト取得時にセッションIDも生成されます。 セッションIDは毎回異なった値であることが保証された十分に長い(例えば30桁くらい)文字列です。 アプリは必要ならses.getIDメソッドで取得できます。セッションIDは前述のようにクライアントに送られて、セッションの維持に活躍しますが、それらは先の説明のようにすべて自動的に行われるので、アプリを作成するプログラマーはあまり気にする必要はありません。

ただし、アプリのプログラマーが意識しなければならないことが1つだけあります。それはクッキーの使用を拒否したり、携帯電話ブラウザのようにクッキーが使えないケースがあることです。 その場合は次にアクセスすべきURLをクライアントに送る時、URLにセッションIDを付加して送る「URL書き換え方式」を使用します。

URLにセッションIDを付加するには、次のようにHttpServletResponseインターフェースのencodeURLメソッドを使います。 すると以下の変数nextURLには本来のURLである ./next にセッションIDが付加されたものがセットされます。


図解: encodeURLメソッド

このnextURLを次のように出力しておけば、出力された画面のnextの部分をクリックするとセッションID付きのURLがサーバに送信されます。 したがってクッキーを使わなくてもセッションIDが引き継がれることになります。


図解: nextURLの出力

ところで一定時間アクセスが途絶えるとセッションは無効化されますが、サーバの資源を有効に使うためにセッションは不要になり次第、アプリが積極的に無効にすべきです。 それには以下のようにします。セッションをできるだけ早く解放すべしとの話は、この章の最後でまた出てきます。


図解: セッションの解放

セッションデータの格納と取り出し

セッションデータはキー(key)と値(value)のペアで管理されます。 キーには任意の名前を付けることができますが、1つのキーには1つの値しか格納できません。 この方法はJavaのMapインターフェースなどでも採用されているうまいやり方です。 setAttributeで値を格納する例を示します。以下を実行するとidと名付けられたキーにuserIdの値が格納されます。


図解: セッションデータの格納

次に格納した値を取り出す例を示します。取り出しは以下のようにキー名を引数としてgetAttributeメソッドを発行します。 Attributeに格納するとObject型になるので、取り出し時にはこの例のようにキャストが必要です。このようにキャストが要求される仕様はMapインターフェースに共通のものです。


図解: セッションデータの取り出し

以下にセッションデータの格納と取り出しに使うメソッドを示しておきます。

Object getAttribute(String name)
   nameで指定されたキーで格納されている値を返す。ないときにはnullを返す

Enumeration getAttributeNames()
   値が定義されているすべてのキーの名前を列挙オブジェクトとして返す

void setAttribute(String name, Object value)
   キーと値をペアで格納する。 既存のキーを指定すると旧値は削除される

void removeAttribute(String name)
   nameで指定されたキーで格納されているオブジェクトを削除する

データの有効範囲(スコープ)

データの有効範囲(スコープ)

前項で示したセッションオブジェクトに格納されたデータは、セッションが成立している間だけ有効で、セッションが消滅すると消えてしまいます。 このようにデータが特定の期間だけ有効なとき、その期間をそのデータのスコープ(有効範囲)と言います。 Servletにはセッションを含めて以下の3種のスコープがあります。その範囲はリクエストスコープ<セッションスコープ<アプリケーションスコープの順に大きくなります。

a.リクエストスコープ
このスコープのデータは1つのリクエストのあいだ有効です。リクエストとは1章で説明したようにクライアントがサーバに投げるGETやPOSTなどのことです。 この後、5章で処理の途中で別のアプリを呼び出すリクエストディスパッチャの説明をしますが、リクエストスコープのデータは、その際に呼び出したアプリと呼び出されたアプリとの間で共有できます。

b.セッションスコープ
これはすでに説明しました。セッションが継続しているあいだ有効です。

c.アプリケーションスコープ
1つのServletシステムでは「インターネット販売」と「登録ユーザ管理」といった複数の業務を扱うことができます。 Servletの世界ではこのような独立した業務をアプリケーション(注7)と呼んでいます。 アプリケーションスコープのデータは同一アプリケーション内のServletプログラム間で共有することができます。

(注7) 「アプリケーション」について: 本稿ではServletシステムを構成するプログラムの内、 Servletを利用するプログラマーが作る部分を「Servletアプリ(ケーション)」(または単に「アプリ」)と呼び、 システムが提供する部分を「コンテナ」と呼んでいました(これは本稿に固有の表現です)。 しかし、アプリケーションスコープが指しているアプリケーションとは、上の説明のように一まとまりの業務、 あるいはその業務を遂行するためのプログラム群のことで、ちょっと違いますのでご注意ください。 一般に1つのアプリケーションは複数のServletプログラムから構成されることがあります。 例えば「インターネット販売」アプリケーションは商品を購入して買い物かごに入れるServletプログラムと、 購入後にそれらの支払をするServletプログラムから構成されるかもしれません。

データ格納のためのオブジェクトとその取得方法

各スコープにデータを格納するには、そのためのオブジェクトが必要です。 オブジェクトの種類はスコープごとに異なり、それぞれ次のようなインターフェースを実体化したものです。 (a)と(b)はオブジェクト名とインターフェース名が対応していますが、(c)は対応していないことに注意してください。


図解: スコープごとのオブジェクトとインターフェース

各オブジェクトの取得方法を示します。 まず(a)のServletRequestインターフェースのオブジェクトはdoGetの引数で渡されるreqでした。 また(b)のHttpSessionインターフェースのオブジェクトは以下の(1)のようにreq.getSessionで取得できます。 (c)のServletContextのオブジェクトの取得は次の(2)のようにします。これらについては、この章の最初で例として取り上げました。


図解: オブジェクトの取得方法

スコープデータの格納と取り出し

スコープデータの格納と取り出しに使うメソッドは、セッションデータの時と同じで、以下の通りです。


図解: スコープデータの格納と取り出しに使うメソッド

これらの使い方もセッションのときと同じです。 オブジェクトの取得を含めて各スコープのデータの格納方法を示します。


図解: 各スコープのデータの格納方法

同様に各スコープのデータの取り出し方を示します。


図解: 各スコープのデータの取り出し方

セッション管理のポイント

ここではセッション管理機能を使うに当たって、アプリを開発するプログラマーが留意すべき事項を説明します。 まず格納するデータを多くしすぎないことが大切です。 セッションデータはセッションが確立している間ずっと保存されるので、メモリを圧迫し同時にサービスできるクライアント数に大きく影響するからです。

したがってセッションデータとして格納する情報は厳選しましょう。 例えば顧客のidや住所・氏名などのデータ全部を格納するのではなく、顧客idのみを格納しておき住所や氏名は必要なときに顧客idでDBを検索して入手するなどの処理にしても良いかも知れません。 ただしCPUやDBの負荷も考慮して総合的に判断すべきです。 例えば住所は長いし使用頻度も少ないのでDBに入れるが、氏名は短いし良く使うのでセッションデータとして格納するといった考察も必要です。

同様な理由でセッションは不要になったら確実に解放すべきです。 それにはクライアントのユーザに解放のきっかけとなる操作を確実にしてもらう工夫が大切です。 例えばすべての処理を終わった後にログオフボタンを押してもらうのではなく、その直前の画面(契約内容を表示し、ありがとうございましたなどと表示する画面)を表示する際にセッションを解放できないかを考えてください。

また、セッションが確立している時間はなるべく短くすべきです。 例えばセッションを確立させるユーザID/パスワードの入力をさせてから商品を選ばせるのではなく、最初の商品を買い物かごに入れた後にユーザID/パスワードを入力させるべきです(多くのインターネット販売サイトはこのようになっていると思いますが)。 またセッションに入ってから必要な情報をあらかじめ提示し、用意してからセッションに入ってもらうとよいでしょう。 セッションは窓口業務に例えられます。 窓口と同様にセッションを貴重な資源と考え、短時間で多くのサービスができるように、事前準備や処理の手順をうまく設計すべきです。

上のような色々な工夫をしても、セッションを確立したままユーザが席を外してしまうかもしれません。 またWebですので途中でリンクが切れてしまうかもしれません。 そのために一定時間アクセスが無いとセッションを解放するセッションタイムアウトを設定する必要があります。 タイムアウト時間の値の検討も必要です。 但しこれは一定数以上のユーザがいて、同時アクセス数が問題となるシステムでの話です。 サーバのパワーに比べてユーザ数が少ないときは、このような余計なことは考えずシンプルに作るべきでしょう。

確認問題

(1) ServletアプリがdoGetの中で使うメソッドで、メソッド名の前にオブジェクト名を付けずに直接呼べるメソッドはどんなクラスやインターフェースで定義されていますか。 3つ挙げてください。
(2) セッション管理に際しブラウザ側で重要な役割を果たす情報格納領域は何と呼ばれますか。またそれが使えないとき、アプリはどんな手段をとりますか。
(3) Servletのスコープの種類を小さい順に3つ挙げてください。
(4) 上記3種の各スコープのデータを操作するメソッドは、それぞれ何というインターフェースで定義されていますか。
(5) スコープの共有データの格納、取り出し、削除に使用するメソッドの名称はそれぞれ何ですか。

解答

(1) HttpServletクラス、Servletインターフェース、ServletConfigインターフェース
(2) クッキー、URL書き換え
(3) リクエスト、セッション、アプリケーション
(4) HttpRequestインターフェース、HttpSessionインターフェース、ServletContextインターフェース
(5) setAttributeメソッド、getAttributeメソッド、removeAttributeメソッド

ページの先頭へ