前回はMVCモデルについて概略を学びました。

今回は本研修でコントローラーの役目を果たすServletについて学んでいきましょう。

1. Servletとは?

Java Servletとは、Javaで書かれたプログラムであり、サーバ上で動きます。以前はJava Appletというクライアント(ブラウザ)側で動くJavaプログラムがあり、Java Servletと対になっていたためこの名前があります。(現在では Java Appletはほとんど使われていません)

Servletの概念は下図の通りです。

ブラウザからアプリケーションサーバにリクエストが送られると、初回のみServletコンテナがJavaのクラスファイルを実行してServletのインスタンスを作り、レスポンスを返します。それ以降、Servletのインスタンスはアプリケーションサーバのメモリに常駐し、リクエストに対してレスポンスを返します。リクエストメソッドに応じてgetにはdoGet()メソッド、postにはdoPost()メソッドが呼び出されます。

Servletの概念

1.1. ServletでHello World

ここでは、ひとまずServletを単体で実行してみることにします。JavaSEでも一番最初に学んだ「Hello World」をやってみましょう。

以下のようなHelloServlet1.javaだけを作成して実行してみてください。

package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/Hello1")
public class Hello1Servlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		System.out.println("Hello World");
	}
}

すると真っ白なブラウザの画面が表示されましたね。

1回目は先述の通りServletのインスタンスを作ったり、Tomcatが停止中であれば立ち上げから始めますので時間がかかりますが、2回目以降は早くなるはずです。

さて、「Hello World」 はどこに表示されているのでしょうか?

実は、下図のように「コンソール」というタブがあり、ログとして表示されているのでした。

Eclipseのコンソール

コンソールではプリントデバッグを行うことができます。また、各種エラーが表示されるところですから今後も頻繁に目にすることになります。

ここからServletの中身の解説に入りますが、ひとまずServlet特有の決り文句は無視して、urlPatternsとdoGet()メソッドという2つの重要ポイントを解説します。

1.2. urlPatterns

まず注目すべきは、urlPatternsです。urlPatternsはこのServletがどのようなURLの指定で呼び出せるかを規定しています。

@WebServlet("/Hello1")

ブラウザのアドレスバーを見ると以下のようになっていますね。

「http://localhost:8080/05_Servlet/Hello1」

実は、 urlPatternはJavaのクラス名とは無関係に付けることができます。

しかし、当社の新人エンジニア研修では特段の理由がない限り、urlPatternは1つだけ、クラス名からServletを削除したものを用います。(例. Hello1Servlet → Hello1)

なお、同じプロジェクトの複数のServletの中に同じurlPatternを指定するとServletも、JSPもHTMLさえも実行できなくなることがあります。Servletのファイルをコピーして、うっかり urlPattern を書き換えるのを忘れていると起こります。(しかも、コピーした後 urlPattern を書き換えてもキャッシュに元の urlPattern が残ることがあります。その場合にはプロジェクトの「リフレッシュ」が必要になりますので方法は講師にお尋ねください)

実験

同じプロジェクトの中にurlPatternsが重複するServletを作ると全てのファイルが実行不可能になります。

Hello1Servletをコピーして実験してみてください。。

2. コンテキストルート

urlPatternの先頭の“/”はこのServletがアプリケーションの直下(プロジェクト名の直下で当社の研修環境の場合はwebappフォルダの中)にできることを示しています。つまり、ServletのURLは以下になります。

この時、以下のポート番号までがサーバのドメインです。

「http://localhost:8080/」

また、以下の「プロジェクト名/」までをコンテキストルートといいます。

「http://localhost:8080/05_Servlet/」

【context】とは英語で文脈という意味です。文脈が変われば言葉の意味が変わるように、プロジェクトが変われば同じ urlPatternでも呼び出されるServletが変わります。

さらに、以下がHelloServlet1のURLです。

「http://localhost:8080/05_Servlet/」

なお、前章で見たように${pageContext.request.contextPath}というEL式を使い「/プロジェクト名」を取得することができます。つまりこの章の場合は「/03_JavaWeb03Servlet」です。プロジェクト名は変更になることもありますので直接記述するよりも賢い方法です。

また、オリジナルのCSSやJavaScriptを組み込む際にも以下のように記述する必要があります。

href="${pageContext.request.contextPath}/css/mystyle.css"

  • ①最終課題でオリジナルのCSSやJavaScriptを組み込む予定はありますか?
チームの答え:

例題

2章の数当てゲームのresult.jspファイルからindex.jspにリンクするとします。

①単体で実行して正しくリンクするためには相対パスでどのように記述しますか?

②index.jspからGameServletを介して実行する際に正しくリンクするためには相対パスでどのように記述しますか?

上記の事実は何を意味していますか?

上記①②のいずれでも正しくリンクするためには相対パスでどのように記述すればよいのでしょうか?

3. Servletクラス

ServletはHttpServletクラスを継承します。HttpServletクラスに関して当社の新人エンジニア研修では特に深入りはしません。JavaSEのときのような詳細な説明が欲しい方はJakarta EE 8 仕様 APIを御覧ください。

3.1. doGet()メソッド

次にdoGet()メソッドを見ていきます。

doGetメソッドは HttpServletクラスのdoGetメソッドをオーバーライドしているため@Overrideアノテーションを付けています。このメソッド内で起きる可能性のある例外を呼び出し元に投げるためにthrowsキーワードが使われていますが、決り文句ですし、JavaSEでも詳しく学んだので今回は深入りはしません。

doGetメソッドの引数は2つです。HttpServletRequest型の変数名requestというインスタンスとHttpServletResponse型の変数名responseというインスタンスです。(本研修では単純にリクエストとレスポンスと呼ぶことにします)

それぞれ、リクエストとレスポンスでやり取りされるオブジェクトのフィールドやメソッドが含まれています。リクエストとレスポンスについては、今回の新人エンジニア研修で折に触れて紹介することになりますので、ひとまず先を急ぐことにします。

では先の文字列「Hello World」をブラウザに表示するにはどうすればよいでしょうか?

それには、JSPにデータを渡す方法とJSPのEL【Expression Language】を使って表示する方法を学ぶ必要があります。(本当はServlet単体でもHTMLの出力は可能ですが、MVCを学ぶという本研修の趣旨からは外れますので解説しません。)

例題

ServletのdoGetメソッドをdoPostメソッドで書き換えたとします。

実行したときにブラウザには何が出力されますか?

あなたの答え:


doPostメソッドしか持たないServletは単体で実行できますか?

あなたの答え:


HTTP通信でWebブラウザ等のクライアントからWebサーバへと送られるリクエストはGETメソッドだったことを思い出してください。

つまり、開発中はdoGetメソッドにしておいたほうがServlet単体で実行可能なのでテストがしやすくなります。

3.2. リクエスト属性とは?

あるServletからJSPへデータを渡すには属性というアプリケーション・サーバーのデータ領域を使います。

そして属性にはスコープ【scope】という有効範囲があります。英語の【scope】には範囲という意味があるということを私達はJavaSEで学びました。

JavaEEにはデータの有効範囲の異なる4種類のスコープがあるのですが、今回はそのうちスコープが狭い方から2番めのリクエスト属性(Request Attributes)を使います。(本研修ではあともう一つセッション属性(Session Attributes)というものを「5.ログイン処理の実装とセッション属性」で学びます)

次のようなServletを作成して実行してみましょう。

package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = { "/Hello2" })
public class Hello2Servlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setAttribute("message", "Hello World");
		request.getRequestDispatcher("view/result.jsp").forward(request, response);
	}
}
  • ①「request.getRequestDispatcher("/view/result.jsp").forward(request, response)」のように複数のメソッドを“.”でつなぐ書き方を何といいましたか?
あなたの答え:

ServletではrequestのsetAttributeメソッドを使いインスタンスを属性に保存します。 【Attribute】=属性を、【set】=セットする、という意味のメソッドです。

ポイントは以下の記述です。

request.setAttribute("message", "Hello World");

リクエスト属性という保存領域にmessageという名前で"Hello World"という文字列のインスタンスを格納しています。

このメソッド名は「属性をセットする」というそのままの意味でしたね。

第2引数はセッションに格納したいインスタンスです。Object型なのでJavaのインスタンスであれば何でも格納できます。

第1引数は名前です。変数名のようなもので JSPのEL式でこの名前で取り出すことができます。

このようなデータ構造をキーバリュー(Key-Value)と呼びます。文字通り、データを取り出す際のキーと値というわけです。

オブジェクトはリクエスト属性に複数保存できます。それぞれを区別するために違う名前が必要になるのですね。

下図のnameが名前、attributeがオブジェクトになります。

リクエスト属性のイメージ

そして、requestのforward()メソッドを使って指定のJSPにフォワード処理をします。ちょうどサッカーのフォワードがボールを前に前に運ぶイメージでしょうか。

リクエスト属性は、その名の通り、1回のリクエストとレスポンスの間だけ有効な記憶領域(スコープ)です。レスポンスを返し終わると消えてしまう記憶領域ですのでメモリを圧迫しません。他のプログラミング言語ではリクエスト変数と呼ばれることもあります。

/

データ検索に向いたデータ構造のKey-Value

キーと値のペアは、キーを使って値に高速にアクセスできるため、よく使われます。データベースやプログラミングで広く使用されています。Javaでは、マップ【Map】、他の言語では辞書【Dictionary】と呼ばれています。

3.3. フォワードとは

フォワードとは、処理中のServletからJSP(やServlet)に処理を移す(画面を切り替える)ことです。JSPに画面表示の処理を任せてしまうのがフォワードです。

以下の1行でフォワードが成立します。

request.getRequestDispatcher("view/result.jsp").forward(request, response);

画面を切り替える方法としてあとの章でリダイレクトというものも出てきますので比較を通じて、おいおい理解いただければ大丈夫です。このメソッドの前半部分でrequestのgetRequestDispatcher()メソッドを使っています。

「ブラウザにデータを戻すのであれば、 request ではなく、response のメソッドを使うのではないか?」そう思った人もいるかも知れません。しかし上図「リクエスト属性のイメージ」をよく見ると、ServletからJSPにデータを渡してからレスポンスをしているのでこれで良いのですね。

このフォワードのメソッド名は【Request】=「リクエストを」【Dispatcher】= 「送るものを」【get】=「ゲットする」という程度の意味です。送り先のURLは引数として文字列で渡します。以下のように2通りの書き方があります。この研修ではフォワード先のパス指定は①通常の相対パスで統一したいと思います。

なぜなら、それが一番短くかける上に、後で学ぶリダイレクト処理でも同じ書き方ができるからです。

"view/result.jsp"

今回この処理はメソッドチェーンを使い1行で書いています。2行で書くこともできますが、シンプルですからこの書き方に慣れてください。なお、以下の部分は決り文句と考えていただいて結構です。

.forward(request, response)

初めてServletを学んだときに面食らうのがforward後のURLです。

「http://localhost:8080/03_JavaWeb03Servlet/Hello2」

となっています。JSPのURLは以下の通りですが、表示はServletのURLのままですね。

「http://localhost:8080/03_JavaWeb03Servlet/view/result.jsp」

したがって、もしもエラーが起きた場合に問題の切り分けが困難になることがあります。つまり、エラーの発生箇所がServletなのか、JSPなのかが直ぐには判断しづらいことがあります。

4. JSPにおけるELの記述方法

JSPでは、ELを使ってその値をリクエスト属性から取り出してHTMLを組み立てます。HTMLファイルがブラウザにレスポンスされて皆さんの画面に表示されます。

result.jspには、HTMLの定型文をすべて消して、ELだけを記述することにしました。論点を明確にするためです。

実は、下記のようにJSPのELは$で始めて波括弧の中にリクエスト属性に付けた名前だけを書けば表示されます。この例では”message"です。

${message}

ただし、この書き方ですとこの後セッション属性というものが出てきたときに区別できません。そこでリクエスト属性であることを明確化するために以下のように書くことにします。

${requestScope.message}

また、このとき、もちろんJSPだけを単体で実行しても何も表示されません。Servletを介さないと「Hello World」が表示されないことを確認してください。

なお、変数に$マークを付けるのはIT業界の古くからの習慣です。古くはBasic、そしてLinuxのシェルスクリプトやPHPでも$は変数の目印として使われます。さらにJavaScriptのテンプレートリテラルでは全く同じ表記をします。豆知識として覚えておいて損はありません。

例題

以下のServletを実行したときにブラウザには何が出力されますか?

なお、各ファイルはそれぞれ正しい構成でプロジェクト内にあるものとします。

package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/Addition")
public class AdditionServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		int num1 = 1, num2 = 2;

		request.setAttribute("ans", num1 + num2);
		request.getRequestDispatcher("view/ans.jsp").forward(request, response);
	}
}

${requestScope.ans}

あなたの答え:

例題

上記のELを以下のように書き換えるとします。

何が出力されるか予想しなさい。

${requestScope.ans + 1}

あなたの答え:

例題

上記例題1のServletのdoGetメソッドをdoPostメソッドで書き換えたとします。

実行したときにブラウザには何が出力されますか?

あなたの答え:


doPostメソッドしか持たないServletは単体で実行できますか?

あなたの答え:


HTTP通信でWebブラウザ等のクライアントからWebサーバへと送られるリクエストはGETメソッドだったことを思い出してください。

つまり、開発中はdoGetメソッドにしておいたほうがServlet単体で実行可能なのでテストがしやすくなります。

常にサーブレットを経由してJSPにアクセスさせる

直接JSPページにアクセスさせることは推奨されません。その理由は以下の3点です。

1.ビジネスロジックが露出してしまう。
JSPページが直接アクセス可能であると、ビジネスロジックが直接クライアントに露出する可能性があります。これは、アプリケーションの脆弱性を引き起こす可能性があります。

2.URL設計の問題がある。
JSPページに直接アクセスを許可すると、URL設計が非常に難しくなる可能性があります。たとえば、あるJSPページが直接アクセスされると、それがどのコントローラから遷移されたものなのか、どのようなパラメータを持つべきなのかを必ずしも保証できません。

3.セキュリティの問題がある。
JSPページに直接アクセスさせると、セキュリティ上のリスクが高まります。ユーザーが直接ページにアクセスすることを許可すると、認証や認可の機能をバイパスする可能性があります。

常にサーブレットを経由してJSPにアクセスさせるようにして下さい。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ Java Servletとは、Javaで書かれたプログラムであり、サーバ上で動く

□ Servletのインスタンスはアプリケーションサーバのメモリに常駐し、リクエストに対してレスポンスを返すだけが仕事(細々とした処理はモデルに任せる)

□ リクエストメソッドに応じてgetにはdoGetメソッド、postにはdoPostメソッドが呼び出される

□ コンソールではプリントデバッグを行うことができ、また、各種エラーが表示される

□ urlPatternsはこのServletがどのようなURLの指定で呼び出せるかを規定しているが本研修では基本的にクラス名からServletを除いたものと同じにしている

□ 同じプロジェクトの中に全く同じurlPatternを指定したServletが複数あると、JSPもHTMLさえも実行できなくなる

□ ServletのURLはhttp://ドメイン名:ポート番号/プロジェクト名/urlPatternである

□${pageContext.request.contextPath}というEL式を使い「/プロジェクト名」を取得することができる

□ doGetメソッドの引数は2つあり、HttpServletRequest型のインスタンスとHttpServletResponse型のインスタンスである

□ ServletからJSPへデータを渡すにはリクエスト属性という使い捨てのデータ領域を使うのが一般的である

□ requestのsetAttributeメソッドの引数は2つあり、1つ目が名前、2つ目がオブジェクトである

□ requestのgetRequestDispatcherメソッドを使いJSPにフォワード処理する

□ この研修ではフォワード先のパス指定は相対パスで統一する

□ JSPのELは$で始めて波括弧の中にリクエスト属性に付けた名前を書く

今回はMVCモデルについて学びました。

次回は「フォームから送ったデータをServletで受け取る方法」を学んでいきます。ブラウザからのデータをServletで受けて、JSPで表示する一連のMVCの処理が書けるようになります。

「ServletからJSPにデータを渡してWebに表示する」 最後までお読みいただきありがとうございます。