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

今回はコントローラーの役目を果たす(ことが多い)Servletについて学んでいきましょう。

1. Servletとは?

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

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

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

図3.1 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(urlPatterns = { "/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」 はどこに表示されているのでしょうか?

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

図3.2 Eclipseのコンソール

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

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

1.2. urlPatterns

10行目を再掲します。

@WebServlet(urlPatterns = {"/Hello1"})

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

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

「localhost:8080/03_JavaWeb03Servlet/Hello1」

実は、 urlPatternはJavaのクラス名とは無関係に付けることができます。しかし、当社の新人エンジニア研修では特段の理由がない限り、urlPatternは1つだけ、クラス名からServletを削除したものを用います

なお、同じプロジェクトの複数のServletの中に同じurlPatternを指定するとServletも、JSPもHTMLさえも実行できなくなります。Servletのファイルをコピーして、うっかり urlPattern を書き換えるのを忘れていると起こります。(しかも、コピーした後 urlPattern を書き換えてもキャッシュに元の urlPattern が残ることがあります。その場合にはコピーしたファイルを削除するのが確実に問題解決する手段です)一度経験しておけば安心ですので、ぜひ、試してみてください。

実験1

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

それをやってみてください。

実験2

カンの良い方は、 urlPatterns と複数形になっていることに気づかれた方もいらっしゃるかもしれません。

複数のURLのパターンを指定できるようになっています。

urlPatterns に以下のように書き足してみましょう。

@WebServlet(urlPatterns = {"/Hello1","/sample"})

そうすると以下のURL指定でもこのServletが実行できるはずです。

「localhost:8080/03_JavaWeb03Servlet/sample」

コンテキストルート

urlPatternの先頭の“/”はこのServletがアプリケーションの直下(プロジェクト名の直下)にできることを示しています。つまり、ServletのURLは以下になります。

http://ドメイン名:ポート番号/プロジェクト名/urlPattern

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

「http://localhost:8080/」

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

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

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

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

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

なお、前章で見たように${pageContext.request.contextPath}というEL式を使い「/プロジェクト名」を取得することができます。つまりこの章の場合は「/03_JavaWeb03Servlet」です。プロジェクト名は変更になることもありますので直接記述するよりも賢い方法です。この記述は、以下のようにJPSでのCSSやJavaScriptの読み込みなどでも必要になりますのでここで記憶してください。

<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">

Servletクラス

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

1.3. doGet()メソッド

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

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

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

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

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

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

1.4. 属性とは?

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

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

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

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

属性には、文字列はもちろん、オブジェクト(インスタンス)であれば何でも入ります。また、1つだけではなく複数のオブジェクトを格納することができます。複数のオブジェクトを格納できますからそれぞれに名前をつけて区別する必要があるのですね。

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

図3.3 リクエスト属性のイメージ
※someという名前は何がしかのオブジェクトが入ることをイメージしています。

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

JSPでは、ELを使ってその値をリクエスト属性から取り出してHTMLを組み立てます。

HTMLファイルがブラウザにレスポンスされて皆さんの画面に表示されます。

次のようなServletを作成して実行してみましょう。(19~22行目の記述は通常は1行ですが、今回のみ印刷時にA4サイズに収まるように折り返していますのでご了承ください)

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)」のように複数のメソッドを“.”でつなぐ書き方を何といいましたか?
あなたの答え:

ポイントは、18行目以降です。

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

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

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

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

オブジェクトはリクエスト属性に複数保存できます。ただし、それぞれを区別するために違う名前が必要になります。また、この名前が遷移先のjspで変数のように利用できるわけです。

1.5. リクエスト属性の特徴

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

1.6. フォワードとは

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

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

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

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

request.getRequestDispatcher("/view/result.jsp")

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

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

①コンテキストルートからの絶対パス(本研修ではこの書き方に統一します)

"/view/result.jsp"


②通常の相対パス(本研修ではこの書きは使用しません)

"view/result.jsp"

以下のようなルートパス指定や絶対パス指定はできません。

③ルートパス指定は不可

"/03_JavaWeb03Servlet/view/result.jsp"

④絶対URL指定は不可

"http://localhost:8080/03_JavaWeb03Servlet/view/result.jsp"

このパスの指定方式は初学者がつまずきやすいところです。HTMLとは違い、フォワード時は、ルートパス指定や絶対URL指定はできず、コンテキストルートからの絶対パスで指定しないといけないことを記憶してください。または通常の相対パス見分け方としては③④にはプロジェクト名がはいっているので、フォワード先にプロジェクト名を書いてはダメだと記憶してください。


今回この処理はメソッドチェーンを使い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なのかがすぐには判断しづらいことがあります。

  • ①フォワードした後の行に何か処理を書いた場合、その処理は実行されますか?
あなたの答え:

1.7. ELを使ったリクエスト属性のオブジェクトの取り出し方

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

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

${message}

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

${requestScope.message}

また、このとき、もちろんJSPだけを単体で実行しても何も表示されません。ServletからJSPにフォワードしないとHello Worldが表示されないことを確認してください。

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

実験3

以下のソースコードにはJSPのコメントとHTMLのコメントが書かれています。

このファイルを実行して「ページのソースを表示」を選択してみてください。

何か分かったことはありますか?

また、なぜそのような違いがあるのでしょうか?

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>comment.jsp</title>
    </head>
    <body>
        <%-- これはJSPのコメントです --%>
        <!--これはHTMLのコメントです-->
    </body>
</html>
実験結果のメモ:

例題1

以下の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 = { "/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}
あなたの答え:

例題2

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

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

${requestScope.ans + 1}

あなたの答え:

例題3

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

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

あなたの答え:

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

あなたの答え:

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


サーブレットなどのコントローラを経由してJSPにアクセスさせる

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

  1. ビジネスロジックが露出してしまう。
    JSPページが直接アクセス可能であると、ビジネスロジックが直接クライアントに露出する可能性があります。これは、アプリケーションの脆弱性を引き起こす可能性があります。
  2. URL設計の問題がある。
    JSPページに直接アクセスを許可すると、URL設計が非常に難しくなる可能性があります。たとえば、あるJSPページが直接アクセスされると、それがどのコントローラから遷移されたものなのか、どのようなパラメータを持つべきなのかを必ずしも保証できません。
  3. セキュリティの問題がある。
    JSPページに直接アクセスさせると、セキュリティ上のリスクが高まります。ユーザーが直接ページにアクセスすることを許可すると、認証や認可の機能をバイパスする可能性があります。

サーブレットなどのコントローラを経由してJSPにアクセスさせるようにして下さい。

インターネットでサンプルコードを探す際の注意点

学習が進んでシステム開発演習をするようになるといろいろなサンプルコードをインターネット検索することもあるでしょう。その際に、以下の9~11行目のような<% %>で囲われた記述を目にすることもあると思います。<% %>の中はHTMLではなく、Javaのコードです。

これはスクリプトレット【 scriptlet 】といいます。【script】=「プログラム」、【let】=「短い」、ということで文字通りJSPの中に短いJavaのプログラムが書ける仕様です。

先に学んだあいさつを表示させるプログラムをJSP1つに書くと以下のようになります。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <%
            request.setAttribute("message", "こんにちは");
        %>
        ${message}
    </body>
</html>

ずいぶんコードが短くなり、見通しがよくなりました。

しかし、当社の新人エンジニア研修では後の1章でのみ便宜的に使うのを除いて、スクリプトレットを使いません。

なぜなら、JSPにJavaプログラムを直接書くことはMVCモデルの考え方に反し保守性を低下させるから、というのは以前お話した通りです。

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

□ 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は$で始めて波括弧の中にリクエスト属性に付けた名前を書く

今回はServletからJSPにデータを渡して表示する方法について学びました。

次回は、フォームから送ったデータをServletで受け取る方法を学んでいきます。

そうすれば、ブラウザからのデータをServletで受けて、JSPで表示する一連のMVCの処理が書けるようになります。

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