目次へ戻る 下へ↓
24.、サーバーサイドJavaの世界
作成者:Fumitaka Makino 更新日: 2003-05-12 17:26

・サーバーサイドJava

私たちが普段利用しているインターネットのサービスの多く(掲示板、WEBメール、会員制のサイト、オンラインショッピング等)はWEBアプリケーションと呼ばれております。WEBアプリケーションの多くは下図のような仕組みで動いています。

図:WEBアプリケーションの簡易モデル

ユーザーはブラウザでサーバーに対してURLなどの形でリクエスト(要求)を送信し、サーバーはそのリクエストをアプリケーションに渡します。そしてリクエストを取得したアプリケーションは内部でHTMLを生成しブラウザに対してレスポンス(応答)を返します。最後にHTMLのレスポンスを受け取ったブラウザはそのHTMLを画面に表示します。さらに簡単に言ってしまうと

WEBアプリケーションはクライアントの要求にしたがって動的にHTMLを生成する

となります。これがWEBアプリケーションと呼ばれるものです。開発言語にはASP、PHP、Perl、Physon、Javaなど無数にありますが、どの言語であろうとWEBアプリケーションの目指すところは変わりません。そしてサーバにJ2EEサーバー(またサーブレットコンテナ)準拠のソフトを利用し、開発言語にJavaを用いたものが一般にサーバーサイドJavaと呼ばれています。そしてサーバーサイドJava全般を一般にはJava2 Enterprise Edition(J2EE)と呼びます。J2EEはサーバーサイドJavaを含む企業向けのAPI、規格を指しており、API的にはJava2 Standard Edition(J2SE)の拡張セットとなっています。またSunMicroSystemsのJ2EE互換性テストによりJ2EEに対応した製品は下記のようなロゴが与えられます。ロゴを取得した製品は基本的にJ2EEで開発したアプリケーションが確実に動作することが保証されます。

 

・サーブレットとサーブレットコンテナ

サーバーサイドでJavaを効率的に利用するために策定されたコンポーネント仕様のことをサーブレット(Servlet)と呼びます。当然サーブレットの主機能はリクエストの処理とHTMLの生成にあります。またサーブレットの一種でJSPと呼ばれるHTMLの生成に特化した環境(スクリプト、ソリューションとも言える)も存在します。これらサーブレットは以下のようJava言語で記述されたコンテナを含むアプリケーションサーバー(J2EEサーバー)上に配置され稼動します。

図:アプリケーションサーバー

サーブレットはサーブレットコンテナ(アプリケーションサーバー)に配置することで、サーバープログラムを作る上で必要なHTTPの解釈やURLのマッピングヘッダの解析などを気にかける必要はありません。つまり全てのサーバーサイドプログラムに共通して必要な手順やロジックの実装はコンテナが行い、各画面やアプリケーション独特のロジックを作るだけでよいのです。ではこのアプリケーションサーバにはどのような種類があるのでしょうか?有名なところではWebLogic(BEA)、WebSphereAS(IBM)、hpAS(HP)、SunONE(SunMicroSystems)など他にも多数の商用J2EE対応サーバーが存在します。これらは基本的には有償で、運用にも高度な知識が必要とされるため初心者には向きません。今回の学習のようにサーブレットコンテナだけでよければ無償であり、非常に高速に動作するJakartaプロジェクトのTomcatを利用するとよいでしょう。また同様にEJBコンテナも無償で利用したいと思う人はSunのJ2EE SDK(注:実用不可、開発のみ)やJBOSSコンテナなどを検討してください。またJakarta TomcatなどはJ2EEサーバーでこそありませんが、J2EEを構成するServletAPIのリファレンス実装です。また、JBOSSなども近くJ2EEの互換性ロゴを取得するとの話です。

では具体的なサーブレットについて話をしましょう。下記にサーブレットによるHelloWorldを記述します。これはコンテナに配置されるとHelloWorldのHTMLを表示するサーブレットです。

例:サーブレットによるHelloWorld

     
 

import java.io.*;

//サーブレットAPIのインポート
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {//抽象クラスであるHttpServletを継承

  /**
    HTTP命令のGETに対応するメソッドで、スーパークラスのdoGetを
    オーバーライドしています。WEBサーバーに対してGETによって要
    求があったときにdoGetが呼ばれます。
    @param request HTTPリクエストされたときの情報等がカプセル化されたオブジェクト
    @param response 一般的にブラウザ(リクエスト元)に対して応答する情報をカプセル化したオブジェクト
    @exception ServletException サーブレット内部で発生した問題全般
    @exception IOException Stream系の問題が発生したときなど
  */

  public void doGet(HttpServletRequest request, HttpServletResponse response)
  throws IOException, ServletException
  {
    response.setContentType("text/html");//配信する情報のコンテンツタイプを指定
    PrintWriter out = response.getWriter();//レスポンスに対するWriterを取得
    
    //HTMLを書き出し
    out.println("<html>");
    out.println("<body>");
    out.println("<head>");
    out.println("<title>Hello World!</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>Hello World!</h1>");
    out.println("</body>");
    out.println("</html>");
  }
}

 
     

まずインポート文を見て下さい。サーブレット関連のクラスを利用するためには、必ずjavax.servletパッケージとjavax.servlet.httpパッケージを取り込む必要があります。これらをのパッケージを総称してServletAPIと呼び、ServletAPIの実装クラスはサーブレットコンテナによって違います。クラス宣言の部分を見て下さいHelloWorldクラスはHttpServletを継承しています。このHttpServletクラスは下記のようなHTTPの命令に対応するそれぞれのメソッドを持っており、開発者はそれらのメソッドをオーバーライドすることによってサーブレットを作成します。通常ブラウザからURLを表示するさいのリクエストメソッドは「GET」で、送信ボタンなどによる情報の送信は「POST」となります。現在の一般的なWEBアプリケーションにおいて利用するのは、この2つのみです。今回はdoGetをオーバーライドしているのでGETリクエストの時に反応します。

表:HTTPプロトコルで定義されている命令
リクエスト・メソッド
定義されている振る舞い
GET ファイルの要求や検索を要求
HEAD ファイル属性などの情報を要求
POST 任意のデータをサーバーへ送る。検索などに利用
DELETE ファイルなどを削除
OPTION サポートしている機能を紹介
PUT ファイルなどを更新
TRACE 要求パケット通過過程をトレース

引数として渡されているHttpServletRequestとHttpServletResponseは、それぞれクライアントから来た要求とクライアントへ返す応答を表すクラスです。つまりブラウザから送信された情報はHttpServletRequestから取り出すことができ、ブラウザに返す情報(HTMLなど)はHttpServletResponseに書き出してやればよいと言うことです。今回はリクエストから取り出すべき情報はないので、レスポンスに対して配信するコンテンツのタイプとHTMLを書き出すためのWriterを取得しています。そしてコンパイルし、適切な場所に配置し、ブラウザーから閲覧すると「HelloWorld」という文字が見えるはずです。

このようにサーブレットではブラウザとの通信やHTTP要求の解析などの複雑な処理を行う必要はありません。それらの面倒な処理のほとんどはサーブレットコンテナが行い、サーブレットはオブジェクト化された要求と応答を利用して固有の処理を記述するだけでよいのです。つまりサーブレットコンテナ内においてサーブレットは下図のようにモジュール化された形で扱われています。

図:サーブレットコンテナの簡易モデル

 

・パラメータの取得方法

サーブレットで外部からのパラメータを与える方法には下記のように2種類あります。

  1. URLによってパラメータを受け渡す
  2. フォームの送信によってパラメータを受け渡す

URLによってパラメータを渡す方法は下記のようにします。

http://「アドレス」/「サーブレット名」パラメータ名1=値1パラメータ名2=値2
例:http://localhost:8080/study/servlets/TestServlet?test1=1&test2=2&test3=3

最初のパラメータは「パラメータ名=値」の文字列を「」でURLのサーブレット名の後ろへとつなげます。2つ目以降のパラメータ文字列は「&」によりURLの後ろへと付加していきます。このアドレスを実行するとサーブレット側では以下のコードでパラメータを取得することができます。またパラメータが存在しないときはnullとなります。

String value1 = request.getParameter("test1");
String value2 = request.getParameter("test2");

2番目のフォームの送信によってパラメータを受け渡すには送信元のHTML(HTMLファイルでもサーブレットやJSPによって生成されたHTMLでも良い)に下記のようにFORMセクションを用意し、FORMのmethod属性にはHTTP要求の種類を記述し、action属性にはフォーム情報の送信先(つまりサーブレット)のアドレスを記述します。また送信する各パラメータと値は、HTMLフォームの部品のname属性がパラメータ名となりvalue属性、もしくはそのフィールドに入力された値となります。これらはinputタグのtype属性がsubmitのボタン、もしくはjavascriptによってaction先へとPOST送信されます。

<form method="post" action="/study/servlets/TestServlet" name="sample">
  <input type="text" name="test1" value="1"><br>
  <input type="text" name="test2" value="2" ><br>
  <input type="submit" name="Submit" value="送信">
  <input type="reset" name="Reset" value="リセット">
</form>


このときの値の取得方法は1番目の時と変わらず。getParameterメソッドにより取得することができます。

サンプル用のソースファイル(JavaDoc)

RequestParameter01.java リクエストからパラメータを取得して表示するサーブレットです。表示するためのアドレスは
http://localhost:8080/study/servlet/jp.co.wownet.education.servlet.RequestParameter01

 

・サーブレットのインスタンス

サーブレットは他のWEBアプリケーションシステムよりパフォーマンスが良いといわれることがあります。これはなぜでしょうか?実はサーブレットクラスのインスタンスはサーブレットコンテナ内にたった一つしか存在しません。つまり上のサンプルのサーブレットも10人がアクセス下としてもインスタンスはたった一つで全てのリクエストに応答(マルチスレッド)しているのです。CGIやPHPなどは一般的には10人からのアクセスがあると10のプロセス(10インスタンスだと思ってください)が起動するので、それらと比べるとコンピューターのリソースが少なくてすむのです。下図はその簡易モデル図です。

図:サーブレットのマルチスレッドとCGIなどのマルチプロセスのモデル図
サーブレット:
1つのインスタンスがマルチスレッドにより、ほぼ同時に複数の処理(リクエスト)をこなす。 この時各スレッドのメモリー空間は共通である。インスタンス変数(フィールド)の扱いが若干難しい。
  CGIやPHP:
マルチプロセスにより、1つのリクエストに対して1つのプロセス(インスタンス)が処理を行う。このとき各プロセスのメモリー空間は独立している。変数の扱いが容易な反面大量アクセスに対して弱い

このような1インスタンスマルチスレッドのサーブレットシステムには必ず注意しなければいけない点があります。それは各スレッド(リクエスト)は同じメモリー空間、つまり同じインスタンスで動作していると言う点です。このため今まで普通に利用していたインスタンスのフィールドは各リクエストで共通の変数となってしまうのです。そのためサーブレットにおけるフィールドの扱いには十分注意してください。このようにスレッドの振る舞いを意識したプログラミングをスレッドセーフなプログラミングと言います。スレッドセーフを意識せずにサーブレットを設計すると「なりかわり問題」などが発生しセキュリティ上の問題へと発展しますので十分注意してください。

参考文献
ITPro 記者の眼「他人の情報を表示するサイト」は対岸の火事ではない(IE専用)

サンプル用のソースファイル(JavaDoc)

RequestParameter02.java リクエストからパラメータを取得して表示するサーブレットを改造したもので、簡易掲示板です。

 

・セッションによるオブジェクトの保持

WEBアプリケーションでは、他のサーブレットとオブジェクトを共有する、アクセスしたユーザーごとに、それぞれオブジェクトを保持しつづけるということが必要となります。たとえばオンラインショッピングモールなどで自分が選んだ商品をカートに保存するといったような場面です。ショッピングカートを実現するためには以前の掲示板のようにフィールドを利用することはできません。なぜなら前節で述べたように「なりかわり問題」が発生してしまうからです。このためサーブレットコンテナはオブジェクトを保持しつづけるためのセッション機構を持っています。一般にWEBサーバーはアクセスしてきたブラウザーに対して一意なID番号であるセッションIDを与えます。このセッションIDはクッキーやパラメーターとして保持され、WEBサーバーからブラウザに対してHTMLを配信する際の識別子として利用されます。同じようにサーブレットコンテナもセッションIDを発行します。このセッションIDはブラウザウインドウがクローズされる(回線切断も含む)か、タイムアウトする、プログラム中でセッションを破棄することがない限りは保持され、そのIDに関連づくセッション状態を保持します。このセッション状態はコード中においてはHttpSessionオブジェクトとして扱われます。HttpSessionオブジェクトは内部にハッシュ構造をもっており、文字列を引数にオブジェクトを保持したり、取得したりすることが可能です。HttpSessionオブジェクトの取得は下記のように行います。

HttpSession session = request.getSession()

またオブジェクトをsetしたり、getするには

//セッションへのオブジェクトのセット
session.setAttribute("sample",new String[]{ "あいうえお" , "かきくけこ" , "さしすせそ" });

//オブジェクトの取得、戻り値はObject型なので再キャストが必要
String[] sarray = (String[])session.getAttribute( "sample" );

と記述します。セットしたオブジェクトはセッションが破棄されるまでずっと有効なため、ログイン情報の保持やショッピングカートの実現に利用されます。

サンプル用のソースファイル(JavaDoc)

SessionTest01.java
SessionTest02.java
セッションに値をセットし(SessionTest01)、別なサーブレット(SessionTest02)で取得するサンプルです。
Shop01.java

簡易ショッピングカートのサンプルコード

Goods.java 簡易ショッピングカートで用いる商品クラス

 

 

・スコープの概念

サーブレットではセッション以外にオブジェクトをセットすることができる領域があります。その領域の関係は下図のようになっています。これらのオブジェクトの有効範囲をスコープと呼んでいます。

表:コンテナ領域の種類
 
スコープオブジェクト 説明
ServletContext サーブレットが配置されるコンテナそのもので、WEBアプリケーションの単位であるコンテキストを表すクラスである。サーブレットインスタンスは、コンテキスト内部に配置されコンテナがシャットダウンされるまではコンテキスト内部は消失しない。そのためコンテキストにsetAttributeしたものは、サーブレットと同じレベルでコンテキストに配置され、あらゆるサーブレットからgetAttributeすることができる。
HttpSession 前節で説明したサーバーとブラウザ間でのセッションに基づくスコープである。セッションが破棄、もしくは任意の方法で無効化されない限りはsetAttributeされたものを保持しつづける。ちなみにセッションが破棄されることを「セッションが切れる」と表現することがある。
ServletRequest

クライアントからサーバーまでの1回のリクエストの間だけ有効なスコープである。このスコープは、この後に学ぶ「リクエストとレスポンスの転送」を利用しない限り意味はない。例えば、サーブレットAからサーブレットBに対して転送を行う際に、サーブレットAの処理でServletRequestにsetAttributeしておくとサーブレットBでgetAttributeできます。

ServletRequestには画面に表示する直前のバリューオブジェクトの配列などをセットしておき、表示用のサーブレットやJSPで取得して画面の生成に用います。HttpSessionは前節のようにユーザがWebサイト内の複数のページを訪れる際にユーザを識別したりユーザに関する情報を保存するための方法を提供し、リクエストからgetSessionにより取得します。最後にServletContextですが、取得方法がServletAPI2.2と2.3で違いがあります。またサーブレットコンテナによっても若干の差異があるようです。

ServletAPI 2.3での取得方法
ServletContext context = session.getServletContext();


ServletAPI 2.2での取得方法(サーブレット内で取得したとき、thisはサーブレットを表す)
ServletContext context = this.getServletContext();

一部のAPサーバー(IBMのWebSphereなど)では、実装の差異により下記の場合もあり
ServletConfig sconfig = this.getServletConfig();
ServletContext context = sconfig.getServletContext();

通常ServletContextには、各サーブレット共通で用いる定数やリソースをセットしておきます。

これらをのスコープの用途を整理すると下記のようになります。

ServletContext → 定数や共通リソース(DataSourceやMailSessionなど)の保持

HttpSession → 各クライアント固有の値の保持(ショッピングカートやアカウント情報など)

ServletRequest → 毎リクエストごとに固有の値のセット(次の画面の表示で用いる配列など)

以上のスコープを使い分けることにより、よりシンプルなサーブレットシステムを構築することが可能です。

 

・リクエストの転送

通常のWEBアプリケーションを構築するにあたって単体のサーブレットで完成することはありません。この時、それぞれのサーブレットの連動をするには以下のように2つの方法があります。

  1. レスポンスのsendRedirectを利用する方法
  2. サーブレットディスパッチを利用する方法

1の方法は既に利用したことがあると思います。これはHTTPのリダイレクト命令を用いてブラウザに次の遷移先を指示する方法です。そのため下図のようにリクエストは都合2回されることになります。つまり最初のサーブレットAでServletRequestにセットしたオブジェクトは、次のサーブレットBのリクエストでは取得できません。

図:sendRedirectによる転送処理

一方で2の方法はリダイレクトとは違い、転送はサーブレットコンテナ内部で行われます。この場合リクエストオブジェクトとレスポンスオブジェクト自体を次のサーブレットやJSPに対して転送します。そのため最初のサーブレットAでServletRequestにセットしたオブジェクトは、次のサーブレットBで取得することが可能です。つまりブラウザ側からは飽くまでも一回のリクエスト処理となります。

図:RequestDispatcherによる転送処理

この手法により、一回しか利用しないオブジェクトや配列をリクエストにセットして利用することができます。応用としては、MVCアーキテクチャのコントローラーなどが挙げられます。具体的には下記のように行います。

//リクエストディスパッチャーの取得
RequestDispatcher rd = request.getSession().getServletContext().getRequestDispatcher( "目的のサーブレットのURI" );
//リクエスト、レスポンスの転送
rd.forward( request , response );

このとき「目的のサーブレットのURI」とはコンテキストルートからたどっていったサーブレットのアドレスのことを指します。

URL: http://localhost:8080/study/servlet/jp.co.wownet.education.servlet.ForwardTest01

URI: /servlet/jp.co.wownet.education.servlet.ForwardTest01

下記にリクエストディスパッチャーによる転送のサンプルコードを示します。

サンプル用のソースファイル(JavaDoc)

ForwardTest01.java
ForwardTest02.java
ForwardTest01でリクエストにオブジェクトをセットし、ForwardTest02へリクエストを転送しセットしたオブジェクトを取り出して表示するサンプル

 

 

 
↑上へ 目次へ戻る