JSP(JavaServer Pages)はServletと同じようにサーバ上で動作するJavaプログラムです。 JSPの仕様書には「最大の記述力と柔軟性を保ったまま動的コンテンツが簡単に作成できる」と書かれています。 実際、JSPはServletより簡単で、しかもServletと同等のことができるのです。 この章では、このようなJSPの特徴と使い方について説明します。
初めにこれまで何度も取り上げた、Servletのサンプルソースを示します。 これは yourName="tanaka" というパラメーターを持つHTTPリクエストに対して「こんにちはtanakaさん」と表示させるものでした。
これと同じ機能を果たすコードをJSPで記述すると次のようになります(本来はheadタグなども出力すべきですが省略しています)(注10)。
どうでしょうか、JSPの方がずっと短いですね。
そして、ServlerはJavaのプログラムそのものであるのに対し、JSPはHTMLにプログラムを埋め込んだものであることが、おわかりいただけるかと思います。
特に文字を出力する部分が、ServletはJavaのprint文でいちいち書かねばならないのに、JSPではそのまま書けば良いので非常にスッキリしています。
上の例でHTMLの仕様と異なるものは(a)(d)(e)の部分です。 これらでは通常のHTMLタグと区別するために "<" の直後に % を書いています。 そして % の次の @ や = でさらにJSPコードの働きを決めています(これらの文字の間に空白を入れてはいけません)。 これらの要素の使い方を覚えて行くことがJSPの学習になります。
(注10) このサンプルも Servletのサンプル と同じようにセキュリティ上の問題があります。
JSPは機能的にはServletと殆ど変わりません。 そして、上の例ではServletに比べてずっと簡単なように見えます。 ではServletは使わずに全部JSPで済ますべきでしょうか。 いいえ、もちろんそうではありません。 JSPにも欠点があるのです。 それは複雑なロジックの記述が苦手なことです。 特に業務アプリケーションに必須である詳細な例外処理やログ処理を書こうとするとかなり大変です。 この点ではServletの方が優れています。
実は論理に強いServletと表現に強いJSPを組み合わせ、それぞれの長所を活かして使うのが良いとされています。 それはMVCモデルという考え方です。 MVCモデルの説明は「J2EE入門 3章:JSP」の『MVCモデル』をご覧ください。
ServletとJSPには上述のような相違もありますが、似ている点もたくさんあります。 先に示した例でも、contentTypeの指定やgetParameterメソッドで入力パラメーターを受け取るところなどはServletとそっくりです。
それもそのはず、JSPとServletは親子の関係にあるのです。 と言うのはJSPはいったんServletに変換してから実行されるからです。 SunがJSPエンジンを開発するときServletはすでに存在していました。 そこでSunのエンジニア達はJSP固有のエンジンを作る代わりにServletへの変換プログラムを作り、実際の処理はServletのエンジンにまかせたのです。
それでは上の例のJSPはどんなServletに変換されるのでしょうか。 じつは Tomcat のフォルダの中を探していくと変換されたServletを見つけることができます。 それを章末の 変換結果 に示します。 もとのJSPと比較して見てください。
JSPを理解するにはまずその文法を知らねばなりません。 JSPで使われる構文の要素を挙げてみましょう。
| 名称 | 形式 | 説明 |
|---|---|---|
| ディレクティブ | <%@ ~ %> | JSPに係わる情報を設定する |
| 式(expression) | <%= ~ %> | 出力用の簡易表示形式 |
| 宣言 | <%! ~ %> | メソッドや変数を宣言する |
| スクリプトレット | <% ~ %> | Javaのプログラムコードを記述 |
| アクション | <jsp:~ /> | 別に定義した処理を呼ぶ |
| コメント文 | <%-- ~ --%> | JSPプログラムのコメントを記述 |
以上の6種です。 繰り返しになりますが、これらの機能と使い方を覚えることがJSP理解につながります。 以下、これらの要素を順に見て行きましょう。
ディレクティブはJSPに関する指示を記述するところです。 directiveには「指令する」と言う意味があります。 ディレクティブには次の3種類があります。
| 名称 | 説明 |
|---|---|
| pageディレクティブ | JSP全体に関わる様々な属性を指定します |
| includeディレクティブ | 外部ファイルの内容を取り込みます |
| taglibディレクティブ | JSPが使用するタグライブラリを宣言します |
ディレクティブはJSPに固有のもので、それなりに覚えるべきことがたくさんあります。 以下に項を改めて説明します。
pageディレクティブはJSPの属性の指定に使われます。 以下に主なpageディレクティブを示します。
| 属性 | 説明 |
|---|---|
| import | そのJSPがインポートして使用するパッケージを指定する |
| buffer | そのJSPが出力時に使用するバッファのサイズを指定する |
| autoFlush | バッファフル時に強制送信(true)か例外発生(false)かを指定する |
| errorPage | 例外発生時にジャンプするページのURLを指定する |
| isErrorPage | 現在のページが例外を処理するページか否かを示す |
| contentType | JSPが出力する応答のMIMEタイプと文字セットを指定する |
| pageEncoding | JSPページを生成する際の文字セットを指定する |
各属性は 属性="属性値" と言う形式で指定します。 属性値は必ずダブルクオーテーション(")で囲まねばなりません。 空白で区切ることで複数の属性が指定できます。
以下にpageディレクティブのそれぞれの属性の意味と使用例を示します。
import属性はJavaのインポートと同じ意味を持ちます(注11)。 すなわちこれから使用するパッケージ名をあらかじめ宣言しておくことにより、そのパッケージ内のクラスをクラス名だけで使用できます。 宣言しておかないと長い正式な名前(完全限定名)で指定する必要があったのでした。 インポートするパッケージ名はカンマで区切って複数指定できます。
ただし、JSPでよく使う以下の3つのパッケージはすでに指定されているのであえて指定する必要はありません。
章末の 変換結果 を見てください。 3~5行目に上記のパッケージのインポート文が書かれているのがわかります。 つまりimport属性で何も指定しなくてもこれらのインポート文はServletへの変換の際に自動的に生成されるのです。
(注11) Javaのインポートについては「COBOL技術者のためのJava言語入門 5章:ソースの構造」の『インポート文』をご覧ください。
JSPではhtmlで書いた内容とプログラムが生成した内容とが組み合わされて出力されます。 その内容はそのつどクライアントに出力されるのではなく、いったん出力バッファに格納され、JSPのページが最後まで実行されたときにまとめて出力されます。
buffer属性ではこのバッファのサイズをKB単位で指定します。 特別な値としてnoneを指定するとバッファは使われません。 サイズを指定するときには数値の後ろにkbを付けます。 buffer属性を明に指定しないときのデフォルト値は8KBです。
以下はbuffer属性に16KBを指定する例です。
章末の 変換結果 を見てください。 35行目に8192の数字が見えます。 ここではgetPageContextメソッドの引数としてバッファサイズをセットしているのです。 サンプルソースではbuffer属性を指定していないので、デフォルト値の8KBが指定されているのです。
バッファの内容がいったん出力されてしまうと、その後にエラーが発生しても、その情報はクライアントに伝わりません。 このことは5章の エラーページ で説明しました。 したがって、処理の後半でエラーが発生する可能性があるときは、出力データの大きさを予測し、それに合わせてバッファサイズを大きくとらねばなりません。
出力バッファが一杯になった時に強制的に出力(フラッシュ)するか(true)、または例外を投げるかどうか(false)、を指定します。 デフォルト値はtrueです。 bufferがnoneに指定されている場合はautoFlashはfalseにできません。
そのJSPで処理されなかった例外を処理するページのURLを指定します。 例外を処理するページはpageディレクティブでisErrorPage属性をtrueに設定します。 これについてはすぐ次の項で述べます。
別のJSPからの例外を処理するかどうかを指定します。 trueの場合は別のJSPページで発生した例外を参照するexecptionオブジェクトが暗黙的に宣言されます。 デフォルト値はfalseです。 サンプルソースではこの属性を指定していないので、デフォルトのfalseが働いて章末の 変換結果 ではexecptionオブジェクトが宣言されていません。
JSPが出力する応答のMIMEタイプと文字セットを指定します。 この属性は2章で説明したServletの setContentTypeメソッド と同じ働きをします。 日本語の文字を使う応答を出力するときは必ず以下のように日本語文字セットを指定する必要があります。
上述のcontentType属性がJSPが出力する応答の文字セットを指定するのに対して、pageEncoding属性はJSPページを生成する際の文字セットを指定するものです。 特に次に述べるincludeディレクティブや、後にアクションの項で説明するincludeアクション、forwardアクションで別のページを指定する場合に有効です。 これらの別ページで日本語の文字を使うときは必ず以下のように日本語文字セットを指定する必要があります。
pageディレクティブの属性の説明をしてきましたが、JSPのディレクティブにはpage以外にincludeとtaglibがあります。 これら2つはともにJSPの部品化や再利用に使われます。
その位置に別のファイルを取り込むことを指示します。 以下の例ではheader.htmlと言うファイルをこのディレクティブが記述された位置に取り込みます。
taglibディレクティブはカスタムタグと呼ばれる自作の処理ルーチンと、それを呼び出す接頭辞を関連づけるものです。 以下、例1はtaglibディレクティブ、例2はそこで定義された接頭辞utlを使ったカスタムタグの例です。
カスタムタグの話は後のアクションの項でも出てきます。
先にJSPはServletに変換されて実行されるとお話ししました。 次の宣言とスクリプトレットに入る前にJSPから生成されるServletの構造を説明しておきます。 章末の 変換結果 を見てください。 全体として次のような構造をしていることがわかります。 つまりfoo_jspと言うクラスの中に_jspServiceと言うメソッドがあり、その中にjavaのプログラムが書かれているのです。
_jspServiceメソッドは頭に_jspがついているので、気づきにくいかもしれませんが、これは2章の Servletの起動から終了まで で説明したserviceメソッドに相当するものです。 つまりクライアントからのリクエストのたびにこのメソッドが呼び出されます。
宣言ではJSPで使用する変数とメソッドを記述します。 変数の初期値を設定することも可能です。 以下ではint型の変数cを定義して初期値10を代入しています。 宣言で定義された変数は最初の一度だけ初期化されます。
宣言の中にはJavaの文をそのまま書きます。 したがって行末にセミコロン";"を必ず付ける必要があります。 宣言では上の例のような変数だけでなく、次のようにメソッドも宣言して実装することができます。
宣言はServletに変換されるときに前項で説明した_jspServiceメソッドの外側に配置されます。 また、宣言で記述した変数とメソッドは基本的には次項で述べるスクリプトレットから呼び出されるだけで、それだけで単独に実行されることはありません。 ただし、例外が2つあります。 1つは変数の初期化です。 先に述べたように変数の初期化は最初に一度だけ実行されます。
2つ目は特殊メソッドです。 jspInitとjspDesrtoyと言う名前のメソッドを宣言しておくとそれらは、それぞれ初期化時と破棄時に呼ばれます。 お気づきのようにこれらはそれぞれServletのinitとdestroyに相当するものです。 呼ばれるタイミングについては後述のJSPのライフサイクルのところで説明します。
スクリプトレットにはJavaのプログラムを記述します。 スクリプト(script)とは元々は手書きの文字のことですが、ソフトウェアの世界ではあまり本格的ではないプログラムのことを指すのに使われます。 スクリプトレットは小さいスクリプトと言うことで、HTMLに埋め込まれたJavaプログラムの断片というニュアンスを良く表しています。
スクリプトレットは_jspServiceメソッドの中に埋め込まれます。 この埋め込みはhtmlの地(じ)の文も含めて記述順に行われます。 例を見てみましょう。
この例は次のように変換されます。
スクリプトレットは宣言と同じようにJavaの構文そのものなので文の末尾には必ずセミコロン";"が必要なことに注意してください。 スクリプトレットで宣言した変数は_jspServiceメソッドのローカル変数(注12)として宣言されます。 したがって、初期値をセットせずに使うとコンパイルエラーになります。 これに対し「宣言」で宣言した変数はインスタンス変数となりデフォルト値で初期化されています。
以下の例はあまり現実的ではありませんが、JSPの仕様を理解するのに役立ちます。 これを実行するとどうなるでしょうか。
01行目で型が定義されていない i を使ったのでコンパイルエラーになると考えた方、着想はするどいですが、正しくはありません。 なぜなら、01行目のスクリプトレットより先に03行目の宣言がまず実行されるからです。 その後_jspServiseメソッドが呼ばれ01行目が実行されて、その i に5が代入されます。 この i はインスタンス変数です。
次に02行目で再度 i が宣言されますが、これは_jspServiceメソッドのローカル変数であり、01行目の i とは別ものになります。 04行目で単に i とするとローカル変数の i が呼ばれ、this.iとするとインスタンス変数の i が呼ばれます。 したがって9,5と表示されるのが正解です。
(注12) ローカル変数とはメソッドの中で宣言された変数のことです。 「COBOL技術者のためのJava言語入門 4章:メソッド」の『変数のスコープと初期化』を参考にしてください。
式は出力文の中にJavaの出力結果を埋め込む時に使います。 式の構文は次の通りです。 スクリプトレットは文末に必ずセミコロン";"が必要でしたが、式はJavaの構文ではないのでセミコロンを付けてはいけません。
この章の最初にServletとの比較で提示したJSPのサンプルソースでは、式は次のように使われていました。
式はスクリプトレットの出力文の代わりなので、式の部分は必ずスクリプトレットで置き換えることができます。 この例は全部をスクリプトレットにして次のように記述することもできます。
もちろん次のように書いてもかまいません。
これまで述べた宣言、スクリプトレット、式はJavaのプログラムを1行ずつ記述するものでした。
これに対しアクションはひとつのまとまった処理を実行させることができます。
この意味ではサブルーチンに似ています。
ここではJSPが標準的に提供するアクションのうちincludeとforwardについて説明します。
別のページを呼び出して実行することを指示します。 以下の例ではheader.htmlと言うページを呼び出して実行します。
アクションの構文はこれまでの % を使った構文とは異なり、XMLの仕様に従っています。 そこで終了タグかまたはそれに代わる最後の / が必要になります。 以下は呼び出すページにパラメーターを与える例です。
1行目のjsp:includeは3行目に終了タグがあるので行末には / が無く、2行目のjsp:paramは独立した終了タグが無いので行末に / が付いていることに注意してください。 パラメーターはパラメーター名(name)とその値(value)のペアで記述します。 上の例ではtitleと言う名前のパラメーターに"JSP入門"と言う値がセットされてheader.htmlに渡されます。
includeアクションの機能は先に説明したincludeディレクティブに似ています。 includeディレクティブはその位置に指定されたファイルを読み込み、全体をServletに変換して実行されます。 これに対しincludeアクションはクライアントからリクエストが来てJSPが実行されるたびに呼び出されて実行されます。
forwardアクションも他のページと協力して処理を行う仕組みです。 includeとの相違はincludeが、処理が終わると元のページに制御が戻ってくるのに対し、forwardは行ったきり戻ってこないことです。 これは5章で説明したリクエストディスパッチャのforwardメソッドとincludeメソッドの関係と同じです。
標準提供のアクションではなく、ユーザーが独自に定義したアクションを呼び出せるものがあります。 これがカスタムタグと言われるものです。 カスタムタグとそれを呼び出す接頭辞を関連づけるのがtaglibディレクティブであることは、ディレクティブの項で説明しました。
JSPにはJavaのプログラムを埋め込むことができますが、そうするとJSP自身が見通しの悪いものになってしまいます。 カスタムタグを使ってプログラムをJSPの外に追い出すことによってJSPの可読性を上げることができます。
カスタムタグは自分で作ることもできますが、Sun自身や他の会社、団体が有償または無償で様々なカスタムタグを公開しています。 またアプリケーションサーバのメーカーが他社との差別化のために独自のカスタムタグ(注13)を提供しています。
このようにカスタムタグはJSPの可能性を大きく広げるものです。 しかし、一般の業務アプリ開発者がカスタムタグを作るケースは少ないと思いますので、ここではこれ以上説明しません。 ただし、提供されたものを活用していく機会は多くあります。 使い方はそれぞれのカスタムタグによって異なるので、必要になったときに学習してください。
(注13) 富士通のアプリケーションサーバ「Interstage」ではujiと言う接頭辞のタグ(うじタグ)を提供しています。
JSPにもライフサイクルがあります。
図を見てください。
何度か説明しましたが、JSPはServletに変換されて実行されます。
プログラマーが書いたJSPに対して、最初のリクエストが来ると、JSPコンテナはServletソースに変換するのです。
そして次にそれをコンパイルします。 Servletではプログラマーがコンパイルしなければならなかったのに対し、JSPコンテナは自動変換の後、コンパイルも自動的にやってくれるのです。 そしてそれがメモリにロードされます。 もしjspInitメソッドが宣言されていればまずそれが実行され、次に_jspServiceメソッドが実行されます。 jspInitメソッドが宣言されていなければ、直ちに_jspServiceメソッドが実行されます。 これらの実行の結果、クライアントにレスポンスが返ります。
2度目のリクエストからはメモリ上にある_jspServiceメソッドがすぐに実行され、レスポンスが返ります。 したがって、最初のリクエストだけはレスポンスが出るまでに少し時間が多くかかります。 必要なら事前にコンパイル&ロードしておくことも可能です。
JSPコンテナの消滅時にはjspDestroyが宣言されていれば、それが呼ばれます。
JSPにもServletと同じように スコープ があります。
スコープとはデータが有効な範囲のことでした。
Servletにはリクエスト、セッション、アプリケーションの3種のスコープがあり、用途に応じて選択していました。
JSPではこれに加えてページというスコープがあります。
ページはリクエストよりさらに小さい範囲になります。
ページスコープのオブジェクトはPageContextインターフェースを実体化したものです。 各スコープのオブジェクトと対応するインターフェースをまとめておきます。 ページスコープ以外はServletと同じです。
| (a)ページスコープのオブジェクト | PageContextインターフェース |
|---|---|
| (b)リクエストスコープのオブジェクト | ServletRequestインターフェース |
| (c)セッションスコープのオブジェクト | HttpSessionインターフェース |
| (d)アプリケーションスコープのオブジェクト | ServletContextインターフェース |
ページスコープの有効範囲はJSPの1ページ、すなわち <http> から </http> までです。 これは普通1つのJSPとして記述されます。 こんなものにどうしてわざわざ共有データ領域などが必要なのでしょうか。 それはincludeディレクティブやカスタムタグで述べたようなJSPの部品化した時に、それらの各コンポーネント間でデータを共有するために使われるからです。
この章の最初にServletとの比較で提示したJSPのサンプルソースではServletのオブジェクト名でreqだったところがrequestになっていたのに気づかれたでしょうか。
ServletのサンプルソースではdoPostメソッドの引数としてreqを定義していたので、その名前が使われました(Servletのサンプルソースの(2)行目)。
ところが、JSPではそのような定義がどこにもありません。
どうなっているのでしょうか。
実はJSPではServletに変換するときHttpServletRequestインターフェースのオブジェクトにはrequestという名が付けられます。 章末の 変換結果 の16行目を見てください。 これを暗黙オブジェクトと言います。 JSPには9種の暗黙オブジェクトがあります。 以下にその名称と概要を説明します。
| 名称 | スコープ | オブジェクトの内容(変数の型) |
|---|---|---|
| request | request | 現ページへのリクエスト情報(HttpServletRequest) |
| response | page | レスポンス送信のための情報(HttpServletResponse) |
| session | session | セッション情報(HttpSession) |
| application | application | Webアプリケーション環境(ServletContext) |
| config | page | Servletの設定情報(ServletConfig) |
| out | page | クライアントへの出力情報(JspWriter) |
| page | page | このページ自身(this) |
| pageContext | page | pageスコープ情報/ページの環境情報(PageContext) |
| exception | page | 例外処理のための情報(Throwable) |
ページスコープの情報を扱うオブジェクトはpageではなく、pageContextですので注意してください。
章末の 変換結果 でこれらの変数が宣言され初期化されているのを確認してください。 ただし変数exceptionは宣言されていません。 pageディレクティブのisErrorPageがデフォルトのfalseのままだからです。
(1)
以下の説明でJSPのことを述べているものを全部挙げてください。
(2)
JSPの構文要素とその形式で対応するものを選んでください。
[構文要素]
[形式]
<% ~ %><%! ~ %><%@ ~ %><%= ~ %>(3)
pageディレクティブでbufferがnoneに指定されている場合はautoFlashはfalseにできない理由は何ですか。
(4)
次のうち式として正しいのはどれですか。 またスクリプトレットとして正しいのはどれですか。
<% mydata(); %><%= mydata(); %><%= mydata() %><% mydata() %>(5)
次のJSPを実行するとどうなりますか。
<html><body>
<%! int a; %>
<% int b; %>
<%= a+b %>
</body></html>
(6)
JSPのライフサイクルで実行される順序に並べてください。
(7)
JSPのスコープを小さい順にすべて挙げてください。
(8)
前問の各スコープに対応する暗黙オブジェクトを挙げてください。
(1) 正しいのはa、c、e
(2) 1-c、2-d、3-b、4-a
(3) bufferがnoneに指定されていると出力データはすぐに送信されてしまうが、このときautoFlashがfalseになっていると例外が発生してしまうから
(4) 正しい式はc、正しいスクリプトレットはa
(5) コンパイルエラー(bが初期化されていない)
(6) c、a、d、b
(7) page、request、session、application
(8) pageContext、request、session、application