
いまさら聞けないサーバーサイドJava 第2回
|
前回は,J2EE(Java2 Platform,Enterprise Edition)(注1)の概要を説明し,「Hello, world!」を表示するサーブレットとJSP(注2)の簡単なサンプルを紹介しました。今回はサーブレットとJSPについて,もう少し詳しく見ていきましょう(図1)。
図1

サーブレットはHTTPリクエストに対応してメソッドを記述する
|
最初にサーブレットから解説します。というのも,JSPは特殊なサーブレットという位置付けにあたるからです。 |
図2 ![]() |
図3

サービス・メソッドは,引数としてHttpServletRequestオブジェクトとHttpServletResponseオブジェクトを受け取ります。HttpServletRequestオブジェクトは、呼び出したクライアントのIPアドレス,Webブラウザの種類vリクエストのパラメータなどをアプリケーションに提供します。一方HttpServletResponseオブジェクトは,HTTPの結果コード、日付などのHTTPヘッダー、HTMLドキュメントなどのHTTPボディをブラウザに戻すためのものです。サービス・メソッドは、HttpServletRequestを解釈して必要な処理を行い、結果をHttpServletResponseを通じてWebブラウザに返却するわけです。
サーブレットが値やセッション情報を保持するには
サーブレットを扱ううえで注意しておかなくてはならないのは「サーブレットは通常、スレッドセーフではない」ということです。スレッドとは、プロセスを分割したもの。つまり、一つのサーブレットのインスタンスを複数のブラウザからのリクエストに対して使いまわすことがある、ということです。Webコンテナそのものはマルチスレッドで複数のブラウザからのリクエストを処理しますが、リクエストがあるたびにサーブレットのインスタンスを作成することはせず、同じサーブレットのインスタンスのサービス・メソッドを呼び出します。そのため、開発者が作成するサーブレットが、クライアントのIDや名前などをサーブレットのフィールドに保存しておこうとすると、並列したリクエストがあったときに問題になります。例えば、次のような処理があったとしましょう。
| 1) | リクエスト1がサーバーに到達 |
| 2) | リクエスト1のためにservice( )メソッドを呼び出す |
| 3) | リクエスト1のクライアント情報をフィールドに保存 |
| 4) | リクエスト2がサーバーに到達 |
| 5) | リクエスト2のためにservice( )メソッドを呼び出す |
| 6) | リクエスト2のクライアント情報をフィールドに保存 |
| 7) | フィールドのクライアント情報をリクエスト1に送信 |
| 8) | フィールドのクライアント情報をリクエスト2に送信 |
ここで問題となるのは7)です。リクエスト1に対するレスポンスの中にサーブレットのフィールドに入っているはずのユーザー名を埋め込もうとすると、それはすでに5)でリクエスト2のユーザー名に変わってしまっているからです。では、リクエストに対応した値を保持したい場合にはどのようにすればよいのでしょう。
そこで、一般的に使われるのがHttpServletRequestオブジェクトです。HttpServletRequestオブジェクトは、クライアントからのリクエストの内容だけでなく、その属性(Attribute)としてリクエストにかかわる値を保存する機能も持っているからです。例えばリスト1のようなコードを書けば、setAttribute(String name, Object o)メソッドで何らかのキーとなる名前と値のセットを登録し、getAttribute(String name)メソッドで名前に対応した値を取得できます。
リスト1
protected void doGet(
HttpServletRequest req,
HttpServletResponse resp) {
String userName =
req.getParameter("username");
User user = queryUser(userName);
// 値を保存
req.setAttribute("user", user);
// 値を取得
user = (User)req.getAttribute("user");
~ 略 ~
実際のWebアプリケーションでは、ユーザーがログインしてからログアウトするまで、あるいはサイトを訪問してから去るまで、といった複数のリクエストにまたがって値を保持したいケースもあります。このような複数のリクエストにまたがった一連のユーザー操作を「セッション」と呼びます。本来、HTTPにはセッションという概念がありませんが、サーブレットにはHttpSessionという仕組みがあります。
HttpSessionは、getSession(boolean create)メソッドによって、「このリクエストが属しているセッションを取得する」という擬似的なセッションを作成します。(1)ログイン時やトップ・ページを表示する際などの「セッションの入り口」のリクエストを受けたときにセッションを作成する、(2)それ以外の時はセッションが開始されていなければ「セッションの入り口」に案内する、といった使い方をします。引数のbooleanをtrueにすると、「もしまだセッションが始められていないなら新しくセッションを開始する」ということになります。セッションへの名前と値の登録はsetAttribute(String name, Object o)メソッド、値の取得はgetAttribute(String name)メソッドを使います。
ただしHttpSessionオブジェクトには注意が必要です。ユーザーがログアウトしてセッションが終了するか、一定時間リクエストが来ないときにタイムアウトするまで、HttpSessionオブジェクトはWebコンテナ上に長時間保持されるオブジェクトだからです。データベースの巨大な検索結果などをHttpSessionに放り込んでしまえば、どんどんサーバー上のリソースを圧迫することになるでしょう。また、大量のリクエストを処理するために、Webコンテナを複数設置してクラスタリングを行う場合にもパフォーマンス上の問題となる場合があります。
したがって、プログラミングするうえで楽をするためにセッションにデータを保持するというのは危険です。ユーザー情報など、毎回必要になるデータや、セキュリティ上重要な情報だけを保持する場合にとどめるのが安全でしょう。
JSPはサーブレットに変換されて動く
では今度はJSPを見てみましょう。前回紹介したJSPのコードを,もう一度見てください(リスト2)。
リスト2
<%@ page contentType="text/html;charset=Shift_JIS" language="java" %>
<html>
<head>
<title>Example JSP</title>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="private" />
</head>
<body>
<h1>Hello, world!</h1>
<p>はじめてのJSPです。</p>
</body>
</html>
ほとんどただのHTML文書ですね。違うのは、1行目に<%@ page ~%>という見慣れないタグが入っていることぐらいです。このタグは「ページ指示子」と呼ばれるJSP要素の一つです。このほかにも、<jsp:で始まる要素や、スクリプトレットと呼ばれるJavaのプログラムをそのまま書くことのできる要素などがJSPにはあります。JSP要素については、米Sun Microsystemsが提供しているリファレンス(http://java.sun.com/products/jsp/technical.html#syntax)を参照するといいでしょう。
前述のように、JSPはサーブレットの一種で、実行時にWebコンテナによってサーブレットに変換されて動作します。では、リスト2のexample.jspがどのようにサーブレットに変換されるかを観察してみましょう。J2EE SDKをインストールしたディレクトリをC:\j2sdkee1.3.1とすると、C:\j2sdkee1.3.1\binにあるj2ee.batを実行してJ2EEサーバーを起動してください。前回example.jspは、C:\j2sdkee1.3.1\public_htmlに保存したので、Webブラウザでhttp://localhost:8000/example.jspにアクセスすれば、JSPのプログラムが実行できるはずです。
ここで注目してほしい点があります。最初にJSPのプログラムにアクセスしたとき(Webブラウザに表示させたとき)、表示までにちょっと時間がかかるなと思いませんでしたか?
実はこのタイムラグは、JSPをサーブレットのJavaソース・ファイルに変換し、コンパイル/実行している時間なのです。2回目以降のアクセスではその作業が必要ないので、すばやく表示されるはずです。実験してみましょう。example.jspをテキスト・エディタで開いて、適当な文字列を追加するなどして保存し直してみます。そしてもう一度ブラウザからアクセスすると、表示までに時間がかかることがわかるはずです。でも2回目は早いですね?
JSPプログラムをコンパイルするかどうかは、JSPファイルのタイムスタンプ(最終更新日時)を見て判断します。変換したファイルが古くなっていれば自動的に再コンパイルするわけです(注8)。
実際にどのようなクラスに変換されているのかも確認してみましょう。C:\j2sdkee1.3.1\repository\<マシン名>\webディレクトリを見てください(注9)。example$jsp.javaとそれをコンパイルした結果であるexample$jsp.classがあるはずです。前者がexample.jspを変換したJavaソース・ファイル、後者がコンパイルしたファイルです。
JSPをデバッグするなら変換後のソース・ファイルを見よう
テキスト・エディタでJSPをサーブレットに変換したソース・ファイルexample$jsp.javaを開いてみましょう(リスト3)(注10)。
リスト3
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class index$jsp extends HttpJspBase {
~ 略 ~
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String _value = null;
try {
if (_jspx_inited == false) {
synchronized (this) {
if (_jspx_inited == false) {
_jspx_init();
_jspx_inited = true;
}
}
}
~ 略 ~
// HTML // begin [file="/index.jsp";from=(0,69);to=(12,0)]
out.write("\r\n<html>\r\n <head>\r\n <title>Example JSP</title>\r\n
<meta http-equiv=\"Pragma\" content=\"no-cache\" />\r\n
<meta http-equiv=\"Cache-Control\" content=\"private\" />\r\n
</head>\r\n <body>\r\n <h1>Hello, world!</h1>\r\n
<p>はじめてのJSPです。</p>\r\n </body>\r\n</html>\r\n");
// end
~ 略 ~
}
よく見ると、(1)の_jspService( )メソッドが処理の実体であることがわかります(注11)。HttpServletRequestとHttpServletResponseをパラメータにとることからもわかるように、Servletのserviceメソッドから呼び出されるようになります。(2)のコメントで囲まれた個所が、HTMLを実際に表示する部分です。out.write( )の引数部分を見てみると、スペースや改行位置まで含めて文字列として出力されているのがわかります。コメント中のfromとtoは、元のJSPファイルの何行目の何文字目から何行目の何文字目までを変換した結果かを表しています。
JSPの開発では、変換したJavaソースをコンパイルする部分で引っかかって、表示がうまくできないということがよくあります。また文字化けするケースもあります。そうしたトラブルが起きた場合は、変換結果のソース・ファイルを参照してみるといいでしょう。解決のヒントになることがあります。
(注1)J2EE(Java2 Platform、Enterprise Edition)は、米Sun Microsystemsが策定したJavaによる企業システム構築のための仕様。現在のバージョンは1.3.1。
(注2)サーブレット(Servlet)とJSP(JavaServer Pages)は、J2EEに含まれるWebアプリケーション構築用のAPI。コンテナは、サーブレットなどのコンポーネントの入れ物。Webコンテナは、Web処理を行うソフトウエア・コンポーネントを扱う。
(注3)コンテナは、サーブレットなどのコンポーネントの入れ物。Webコンテナは、Web処理を行うソフトウエア・コンポーネントを扱う。
(注4)つまりユーザーが作成するサーブレットは、javax.servlet.http.HttpServletを継承している。
(注5)配備(deploy)は、作成したコンポーネントをJ2EEサーバー内に配置し、サーバーがコンポーネントを実行できるように設定すること。配備ツールを使った配備の手順は後述。
(注6)HTTPリクエストは、WebブラウザからのアクセスのようなHTTPを使った通信要求のこと。
(注7)WebブラウザからのHTTPリクエストには、GET、POSTなどの種類がある。
(注8)この動作はJ2EEサーバーの製品や設定による。自動再コンパイルを行わないように設定できる製品もある。なお、自動再コンパイルを行うと、クラス・ファイルとJSPファイルのタイムスタンプを比較するオーバーヘッドがかかるため、パフォーマンスに悪影響を与えることがある。そこで本稼働時には、自動再コンパイルを行わない設定にするのが普通だ。
(注9)変換された結果のJavaソースを参照できるかどうか、どのディレクトリに保存されるかについても、製品や設定によって異なる。
(注10)変換したファイルの文字コードはUnicode(UTF-8)である。
(注11)このメソッドは、javax.servlet.Servletインタフェースを拡張したjavax.servlet.jsp.HttpJspPageインタフェースで宣言されている。















