Open17

サーブレット&JSP勉強メモ

ちゃまちゃま

サーブレット&JSP入門の勉強メモ

参考書 : スッキリわかるサーブレット&JSP入門 第四版
https://sukkiri.jp/books/sukkiri_servlet4

ドキュメント

Java21 : https://docs.oracle.com/javase/jp/21/docs/api/index.html

Jakarta EE : https://spring.pleiades.io/specifications/platform/10/apidocs/

web付録

プロジェクトの作成 : https://sukkiri.jp/technologies/ides/eclipse/eclipse2021-03_dwp.html

オートリロードの有効化 : https://sukkiri.jp/technologies/ides/eclipse/autoreload.html

ちゃまちゃま

chapter2 webアプリケーションの仕組み

HTTPリクエスト

リクエスト : ブラウザからwebサーバへの要求。HTTPリクエストでは,次のような文字列が送信される。HTTPリクエストの1行目をリクエストライン,2行目以降をヘッダ部と呼ぶ。

GET /intdex.html HTTP/1.1
accept: image/jpeg. image/gif, ...
accept-language: ja-JP, en-US;q=0.5
user-agent: Mozilla/4.0...
accept-encoding: gzip, deflate
host: www.example.com

リクエストラインはリクエストメソッド,リクエスト対象,使用するプロトコルの3つから構成される。

  • リクエストメソッド(ex. GET) : リクエストの種類を識別するためのもの。代表的なものとしてGET(情報を取得する),POST(フォーム内容等を送信する) がある。
  • リクエスト対象(ex. /index.html) : リクエストするものを指定する。
  • 使用するプロトコル(ex. HTTP/1.1) : 通信に使用するプロトコルを指定する。

HTTPレスポンス

レスポンス : webサーバからブラウザへの応答。HTTPレスポンスでは次に示す文字列が返される。HTTPレスポンスの先頭からcontent-typeまでをヘッダ部,<html>タグから末尾までをボディ部を呼ぶ。

HTTP/1.1 200 OK
content-length: 4122
server: Apache
...
content-type: text/html; charset=UTF-8
<html>
...
</html>

ヘッダ部の1行目はステータスラインと呼ばれ,使用するプロトコルと動作結果の2つから構成される。

  • 使用するプロトコル(ex. HTTP/2.1) : 通信に使用するプロトコルを指定する。
  • 動作結果(ex. 200 OK) : 動作結果の状態を表す。

ヘッダ部の最終行はcontent-typeヘッダと呼ばれ,ボディ部のデータの種類を表す。例としてHTMLをレスポンスする場合はtext/html; charset=UTF-8,JPEGをレスポンスする場合はimage/jpegと記述される。

サーブレットとJSP

サーブレット : Javaを用いてサーバーサイドプログラムを実現する技術。ブラウザから実行できるサーブレットクラスと呼ばれるクラスを使用してサーバーサイドプログラムを実現する。また,サーブレットクラスの実行環境をサーブレットコンテナと呼ぶ。
JSP : Javaを用いてサーバーサイドプログラムを実現する技術。サーブレットとの違いは,サーブレットクラスではなくJSPファイルというプログラムを使用すること。JSPファイルはサーブレットクラスに変換されるため,実現できることはサーブレットと同じ。

ちゃまちゃま

chapter2 サーブレットでHello World

Eclipse環境で次の作業を行う。

  1. ファイル>新規>動的Webプロジェクトを選択する。
  2. プロジェクト名にexampleと入力する。またターゲット・ランタイムの設定を確認しておく。筆者の環境ではTomcat10 (Java21)になっている。
  3. ビルド・パス上のソースフォルダはデフォルトのsrc\main\javaを削除してsrcを追加する。
  4. Webモジュールは,コンテキスト・ルートはデフォルトのexampleのまま,コンテンツ・ディレクトリはWebContentに変更して完了する。
  5. exampleプロジェクトのWebContentにindex.htmlを作成し,次の内容を記述する.
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>ServletHelloWorld</title>
</head>
<body>
    Hello World!
</body>
</html>
  1. サーバーウィンドウから,2.で確認したサーバーを起動する。Tomcat10_Java21を右クリックし,追加および削除を選択する。表示されたウィンドウですべて追加をクリックし,exampleが使用可能から構成済みに移ったことを確認して,完了する。完了したら,実行をクリックする。
  2. ブラウザでhttp://localhost:8080/example/hello.htmlを開く。Hello World!と表示されれば成功である。
ちゃまちゃま

section3 基本的なサーブレットクラスの作成

基本的なサーブレットクラスを作成する。作成手順は次の通りである。

  1. srcディレクトリにHelloServlet.javaを作成する。パッケージ名はservletとする。
  2. 次に示す内容をHelloServlet.javaに記述する。サーブレットクラスのURLはサーバ名/アプリケーション名/URLパターンの形式になっている。ここではサーバがlocalhost:8080,アプリケーションがexampleになっている。URLパターンは@WebServletアノテーションで設定する。ここでは@WebServlet("/hello")に設定することでlocalhost:8080/example/helloにアクセスしたときにページを表示するようにしている。
    URL入力や,リンクのクリックでのwebページへのアクセスでは,リクエストがGETで行われる。サーブレットクラスでは,GETリクエストが来たらdoGetメソッドが実行される。doGetメソッドは引数にリクエスト情報と,レスポンス情報を受け取る。ちなみにPOSTリクエストが来たらdoPostメソッドが実行される。
package example;
import java.io.IOException;
import java.io.PrintWriter;

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

@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		response.setContentType("text/html; charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("This is from HelloServlet.java");
		out.println("</html>");
	}
}
  1. サーバを再起動し,ブラウザでhttp://localhost:8080/example/helloを開く。This is from HelloServlet.javaと表示されれば成功。
ちゃまちゃま

section4 JSPファイルの構成

JSPとは何か?

サーブレットクラスにout.println();を連呼するのは大変 → JSPを使う

JSP(Jakarta Server Pages) : サーブレットクラスの代わりにJSPファイルを使用して,開発しやすくするためのもの。これまではリクエストを受け取ると,サーブレットクラスで処理を行って,結果を返していた。JSPを用いると,ブラウザからJSPファイルをリクエストし,APサーバでリクエストされたJSPファイルをサーブレットクラスに変換,コンパイルして実行するようになる。

JSPファイルはhello.htmlと同様にWebContent配下に設置する。例としてtestJsp.jspを作成した場合,ブラウザからはhttp://localhost:8080/example/jspTest.jspにアクセスする。JSPファイルの変更後はサーバ再起動は必要ない。

  • サーブレットクラス : 変更後再起動,リクエストし直しが必要。
  • JSPファイル,HTML : 変更後再起動は不要だが,リクエストし直しが必要。

JSPファイルの構成要素

JSPファイルは次のようなものである。JSPファイルのうちJavaコードの記述された部分をスクリプト,HTMLが記述された部分をテンプレートと呼ぶ。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
%>

<%
String name = "ちゃま";
int age = 23;
%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
私の名前は<%= name %>です。年齢は<%= age %>歳です。
</body>
</html>

pageディレクティブ : JSPファイルの設定を記述する。設定できるものとしてはレスポンスのcontent-typeヘッダcontentType,クラスやインターフェースのインポートimport,JSPファイルの文字コード設定pageEncoding,使用する言語languageがある。

<%@ page 属性名="値" 属性名="値" %>
<%@ page contentType="text/html; charset=UTF-8" import="java.util.*"%>

スクリプトレット : Javaのコードを埋め込む部分。スクリプトレットはJSPファイルの任意の場所(HTMLの中への埋め込みもOK)に複数回記述できる。またスクリプトレットで宣言した変数は,別のスクリプトレットで使いまわせる。

<% Javaコード %>
<%
String name = "ちゃま";
int age = 22;
%>

JSPコメント : コメントアウト

<%-- ここにコメントを記述する --%>

テンプレート : HTMLを記述する。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
私の名前は<%= name %>です。年齢は<%= age %>歳です。
</body>
</html>

スクリプト式 : テンプレート中に変数の値や,演算結果,戻り値を埋め込む部分。

<%= 変数名・演算式・オブジェクト.メソッド()・オブジェクト(.toStringの戻り値)>
ちゃまちゃま

section5 form

formの機能

form : ユーザーの入力や選択を,サーバサイドプログラムに送信するための仕組み

formタグの構文は次の通りである。formタグにはaction属性に送信先,method属性にリクエストメソッドを記述する。action属性で記述する送信先はサーブレッククラスではURLパターン,JSPファイルではwebappからのパスを記述する。
formタグのコンテンツには入力欄やラジオボタンを代表とするフォームの内容と,送信ボタンを設置する。フォームの内容では必ずname属性を指定して,一意に識別できるようにする。また送信ボタンを1つ必ず配置する。

<form action="送信先" method="リクエストメソッド">
<!-- フォームの内容を記述する -->
<input type="text" name="address">
<input type="submit" value="送信">
</form>

送信ボタンをクリックすると,内部的には次のような形式のリクエストパラメータが送信される。送信時にリクエストパラメータが付加される場所は,次で述べるリクエストメソッドにより異なる。

name=ちゃま&age=22...

method属性にはGET(デフォルト),POSTのどちらかを指定する。
GETリクエスト : リクエストパラメータを検索を代表とする,情報を取得するために使用する。リクエストパラメータはURLの末尾に付加される。
POSTリクエスト : リクエストパラメータがユーザ登録や掲示板投稿を代表とする,情報を登録する場合に使用する。リクエストパラメータはリクエストのボディ部に付加される。

GETメソッドの場合(リクエスト先?リクエストパラメータ) : http://localhost:8080/example/Register?name=chama&age=22
POSTメソッドの場合(リクエスト先) http://localhost:8080/example/Register

サーブレットクラスでリクエストパラメータを取得する

request.getParameterメソッドでname属性に指定した識別子を指定して取り出す。

package servlet;
import java.io.IOException;

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

@WebServlet("/FormServlet")
public class FormExample extends HttpServlet{
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		request.setCharacterEncoding("UTF-8"); // URLエンコーディングの対応
		String gender = request.getParameter("gender"); // formのname="gender"から取得
	}
}

JSPファイルでリクエストパラメータを取得する

JSPファイルの場合は,暗黙オブジェクトとしてrequest,session,applicationが定義されているため,サーブレットクラスよりも省略して記述できる。

<%
request.setCharacterEncoding("UTF-8");
String gender = request.getParameter("gender");
%>

【おまけ】hiddenフィールド

hiddenフィールド : ユーザーの入力以外の項目で,リクエストパラメータに乗せてサーバーにデータを送信したいときに用いる。使い方は,formタグのコンテンツに次のように記述する。hiddenフィールドは画面に表示されない。

<input type="hidden" name="hoge" value="foo">

もしくは次のようにaタグで指定する方法もある。

<a href="FormServlet?hoge=foo">リンク</a>
ちゃまちゃま

section6 MVCモデル

MVCモデル

MVC : アプリケーションをModel,View,Controllerの3つに分けて開発するモデル 。役割分担を行うことでアプリケーションの保守性や拡張性を向上するメリットがあるため,このようなモデルを用いている。

Model : アプリケーションの主たる処理に関するデータを格納する。Modelは内部でデータモデルとロジックモデルの2種類に分けられる。データモデルはデータや実行結果,ロジックモデルは処理機構を担当する。
View : ユーザに対して画面表示を行う
Controller : ユーザからの要求を受け取り,処理の実行をモデルに依頼する。その結果表示をビューに依頼する。

MVCモデルの動作イメージ

  1. ユーザから「近所に美味しいお店はないか?」というリクエストがコントローラに届く
  2. コントローラは「近所の店舗検索」をモデルに依頼する。
  3. モデルは近所の店舗検索を実行し,コントローラに結果を返す。
  4. コントローラは結果の表示をビューに依頼する。
  5. ビューは画面表示を行い,それを要求結果として返す。

どういう風に実現するのか?

Controller : サーブレットクラスでリクエストを受け取る
Model : 一般的なJavaのクラス
View : JSPファイルでレスポンスする

処理の転送

フォワード

フォワード : サーブレットクラスからJSPファイル/別のサーブレットクラスに処理を転送すること。ControllerからJSPファイルに処理を依頼するときなどに用いる。

フォワードの指示。フォワード先にJSPファイルを指定するときはwebappからのパス,サーブレットクラスの場合はURLパターンを指定する。

RequestDispatcher dispatcher = request.getRequestDispatcher("フォワード先");
dispatcher.forward(request, response)

MVCモデルを用いると,ユーザからJSPファイルを直接リクエストされることはなくなる。しかし,前節の通りブラウザから直接JSPファイルを要求することができてしまう。そこでブラウザから直接リクエストできないJSPファイルの配置場所として,WEB-INFディレクトリが用意されている。

リダイレクト

リダイレクト : ブラウザのリクエスト先を変更して,処理の転送を行うこと。ユーザからのリクエストに対してサーブレットクラスが,「ここにリダイレクトする」という内容をレスポンスする。ブラウザは自動的に指示されたリクエスト先に再リクエストを行う。

リダイレクトの指示。リクエスト先にJSPファイルを指定するときはwebappからのパス,サーブレットクラスを指定するときはURLパターンを指定する。

response.sendRedirect("リダイレクト先のURL");

フォワードとリダイレクトの使い分け

フォワードは同じアプリケーション内のサーブレットクラス/JSPファイルに処理を移し,リクエスト/レスポンスが1往復する。リダイレクトは別のサーブレットクラスやJSPファイルをリクエストさせ,実行し直す。リクエスト/レスポンスは2往復する。このため,同じアプリケーション(内部転送)であればフォワードを用いる方が高速である。一方で転送元と転送先のアプリケーションが異なる場合(外部転送)はリダイレクトを用いるしかない。例外としてフォワードでは転送後のURLは変化しないため,表示内容とURLのズレを生じさせないためにリダイレクトを用いることがある。

ちゃまちゃま

section7 スコープ

スコープとは?

フォワード元のサーブレットクラスで生成したインスタンスを,フォワード先のJSPファイルで使用したいことがある。このような時にはスコープと呼ばれるインスタンスを保存できる領域を用いて,インスタンスの共有を行う。スコープに保存可能なものはインスタンスだけであり,基本データ型のデータは保存できない。さらに,スコープには一般的なクラスインスタンスが保存できるが,原則としてJavaBeansと呼ばれるクラスのインスタンスのみを保存する。

JavaBeans : Javaのクラスの独立性を高め,部品として再利用しやすくするためのルール,またはそのルールを守っているクラスのこと
JavaBeansのルール

  1. 直列化可能(java.io.Serializableを実装している→インスタンスのフィールドをバイト列に変換・保存し,そのインスタンスを復元できる技術)
  2. クラスはpublicであり,パッケージに属する
  3. publicで引数のないコンストラクタを持つ。
  4. フィールドがカプセル化されている。

JavaBeansクラスの例

package model;
import java.io.Serializable;

public class Human implements Serializable{
    private String name;
    private int age;
    public Human(){}
    public Human(String name, int age){
        this.name = name;
        this.age = age;
    }

    // name, ageフィールドのaccessor(略)
}

JavaBeansのプロパティ

プロパティ : フィールドのaccessor(getter, setterどちらか一方があればよい)から定義されるもの。フィールド=プロパティとなることが一般的だが,厳密にはフィールドとプロパティは異なる。例として,次のname, ageフィールドと,そのプロパティを考える。プロパティ名はgetter/setterのget/setの後につくワードで決まる。この例では,nameフィールドに対応するプロパティはそのまま「name」,ageフィールドに対応するプロパティは「nenrei」である。このように,プロパティはフィールド名と一致していなくても定義でギル。

public class Human implements Serialize{
    private String name;
    private int age;
    public void setName(String name){ this.name = name}
    public String getName(){ return name;}

    public void setNenrei(int nenrei){ this.age = nenrei}
    public int getNenrei(){ return age;}
} 

リクエストスコープ

リクエストスコープ : リクエストごとに生成されるスコープ。リクエストスコープに保存したインスタンスはレスポンスが返されるまで利用できる。これによりフォワード元/先でインスタンスを共有できる。

リクエストスコープの利用例

Human human = new Human("ちゃま", 22);

// リクエストスコープへの保存
request.setAttribute("human", human); // 属性名,インスタンス

// リクエストスコープからインスタンスを取得
Human h = (Human)request.getAttribute("human");
ちゃまちゃま

MVCモデルとリクエストスコープで作るBMI計算アプリ

作成するアプリ

コントローラー HealthCheck.java

// controller
package model;

import java.io.IOException;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/HealthCheck")
public class HealthCheck extends HttpServlet{
	private static final long serialVersionUID = 1;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		RequestDispatcher dispatcher = request.getRequestDispatcher("WEB-INF/jsp/healthCheck.jsp");
		dispatcher.forward(request, response);
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		String weight = request.getParameter("weight");
		String height = request.getParameter("height");
		
		Health health = new Health();
		health.setWeight(Double.parseDouble(weight));
		health.setHeight(Double.parseDouble(height));
		
		HealthCheckLogic healthchecklogic = new HealthCheckLogic();
		healthchecklogic.execute(health);
		request.setAttribute("health", health);
		
		RequestDispatcher requestdispatcher = request.getRequestDispatcher("WEB-INF/jsp/healthCheckResult.jsp");
		requestdispatcher.forward(request, response);
		
	}
}

データモデル Health.java

// data model
package model;

import java.io.Serializable;

public class Health implements Serializable{
	private double height, weight, bmi;
	private String bodyType;
	public double getHeight() {
		return height;
	}
	public void setHeight(double height) {
		this.height = height;
	}
	public double getWeight() {
		return weight;
	}
	public void setWeight(double weight) {
		this.weight = weight;
	}
	public double getBmi() {
		return bmi;
	}
	public void setBmi(double bmi) {
		this.bmi = bmi;
	}
	public String getBodyType() {
		return bodyType;
	}
	public void setBodyType(String bodyType) {
		this.bodyType = bodyType;
	}
	
}

ロジックモデル HealthCheckLogic.java

// logic model

package model;

public class HealthCheckLogic {
	public void execute(Health health) {
		double height = health.getHeight();
		double weight = health.getWeight();
		double bmi = weight / (height/100.0 * height/100.0);
		health.setBmi(bmi);
		
		String bodyType;
		if(bmi < 18.5) {
			bodyType = "痩せ型";
		}else if(bmi < 25) {
			bodyType = "普通";
		}else {
			bodyType = "肥満";
		}
		health.setBodyType(bodyType);
	}
}

HealthCheck.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>健康診断</title>
</head>
<body>
<h1>スッキリ健康診断</h1>

<form action="HealthCheck" method="post">
身長 : <input type="text" name="height">(cm)<br>
体重 : <input type="text" name="weight">(kg)<br>
<input type="submit" value="送信">
</form>
</body>
</html>

HealthCheckResult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="model.Health"%>

<% Health health = (Health)request.getAttribute("health");%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>健康診断</title>
</head>
<body>
<h1>健康診断の結果</h1>
身長 : <%= health.getHeight() %><br>
体重 : <%= health.getWeight() %><br>
IBM : <%= health.getBmi() %><br>
体型 : <%= health.getBodyType() %>
</body>
</html>
ちゃまちゃま

section8 セッションスコープ

セッションスコープ : リクエストをまたいでインスタンスを共有するときに用いる。インスタンスの保存期間は開発者側が決定できる。セッションスコープを用いるときはHttpSessionインスタンスを利用する。

// HttpSessionインスタンスを取得する
HttpSession session = request.geSession(); // requestにセッションを付随するのでrequest

// Humanインスタンスを属性名humanで保存する
session.setAttribute("human", human); // humanはHumanクラスのインスタンス

// セッションスコープからインスタンスを取得する
Human h = (Human)session.getAttribute("human");

// セッションスコープからインスタンスを削除する
session.removeAttribute("human");

またJSPファイルではsessionインスタンスは暗黙オブジェクトになっているため,request.geSession()で取得しなくてもよい。

ちゃまちゃま

section8 ユーザ登録画面の作成

ポイント

  • 1つの実行メソッドに対してリクエスト元が複数ある(ユーザ登録入力画面の取得・ユーザ登録確認画面の取得)があるときはリクエストパラメータでリクエスト元を識別する。
  • コントローラにprivate static final long serialVersionUID = 1L;を記述する。(シリアライズ用らしいが細かいことは分からん)
  • formでパスワードを扱うときはtype="password"に設定する。
  • 登録時にセッションからUserインスタンスを削除する。

データモデル User.java

package model;

import java.io.Serializable;

public class User implements Serializable{
	private String loginID;
	private String password;
	private String name;
	
	public User() {};
	public User(String loginID, String password, String name) {
		this.loginID = loginID;
		this.password = password;
		this.name = name;
	}
	public String getLoginID() {
		return loginID;
	}
	public String getPassword() {
		return password;
	}
	public String getName() {
		return name;
	}
	
}

ロジックモデル RegisterUserLogic.java(DBにつないでないのでMock)

package model;

public class RegisterUserLogic {
	public boolean register(User user) {
		return true; // Mock データベースに登録完了とみなす
	}

}

コントローラ RegisterUser.java

package servlet;

import java.io.IOException;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import model.RegisterUserLogic;
import model.User;

@WebServlet("/RegisterUser")
public class RegisterUser extends HttpServlet{
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		request.setCharacterEncoding("utf-8");
		String action = request.getParameter("action");
		if(action == null) { // 1回目 actionパラメータが設定されていないとき
			// registerForm.jspにフォワード
			RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/registerForm.jsp");
			dispatcher.forward(request, response);
		}else if(action.equals("done")) { // 2回目 登録ボタンが押されてたとき
			HttpSession session = request.getSession();
			User user = (User)session.getAttribute("user");
			RegisterUserLogic registerUserLogic = new RegisterUserLogic();
			registerUserLogic.register(user);
			session.removeAttribute("user");
			RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/registerDone.jsp");
			dispatcher.forward(request, response);
		}
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		// セッションスコープにユーザ情報を保存する
		request.setCharacterEncoding("utf-8");
		String loginID = request.getParameter("loginID");
		String password = request.getParameter("password");
		String name = request.getParameter("name");
		User user = new User(loginID, password, name);
		
		HttpSession session = request.getSession();
		session.setAttribute("user", user);
		// ユーザ登録確認画面にフォワード
		RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/registerConfirm.jsp");
		dispatcher.forward(request, response);
	}
}

ユーザ登録入力画面 registerForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ユーザ登録入力画面</title>
</head>
<body>
<form action="RegisterUser" method="post"> 
ログインID <input type="text" name="loginID"> <p>
パスワード <input type="password" name="password"> <p>
名前 <input type="text" name="name"> <p>
<input type="submit" value="送信">
</form>
</body>
</html>

ユーザ登録確認画面 registerConfirm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="model.User"%>

<%-- sessionは暗黙オブジェクト --%>
<% User user = (User)session.getAttribute("user");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ユーザー登録確認画面</title>
</head>
<body>
ログインID <%= user.getLoginID() %> <br>
名前 <%= user.getName() %> <br>
<a href="RegisterUser">戻る</a>
<a href="RegisterUser?action=done">登録</a>
</body>
</html>

ユーザ登録完了画面 RegisterDone.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
登録しました。
</body>
</html>
ちゃまちゃま

section9 アプリケーションスコープ

アプリケーションスコープ : アプリ全体で共有したいインスタンスを保存するためのスコープ。アプリケーションスコープに保存したインスタンスは,アプリケーションの起動から終了まで利用できる。

// ServletContextインスタンスの取得
ServletContext application = this.getServletContext(); // コンテクスト単位で持っているものなのでthis

// アプリケーションスコープにインスタンスを保存
application.setAttribute("human", human); // humanクラスのインスタンスを保存

// アプリケーションスコープからインスタンスを取得
Human h = (Human)application.getAttribute("human");

// インスタンスをアプリケーションスコープから削除する
application.removeAttribute("human");
ちゃまちゃま

section9 評価ボタン機能の作成

ポイント

  • アプリケーション起動時に,アプリケーションスコープは空であることを考慮する
  • s1.equals(s2)でs1 = nullのとき,ぬるぽになるので先にNULL判定を行う

データモデル SiteEV.java

package model;

import java.io.Serializable;

public class SiteEV implements Serializable{
	private int goodCount;
	private int badCount;
	
	public SiteEV() {
		goodCount = 0;
		badCount = 0;
	}
	public int getGoodCount() {
		return goodCount;
	}
	public void setGoodCount(int goodCount) {
		this.goodCount = goodCount;
	}
	public int getBadCount() {
		return badCount;
	}
	public void setBadCount(int badCount) {
		this.badCount = badCount;
	}	
}

ロジックモデル SiteEV.java

package model;

public class SiteEVLogic {
	public void good(SiteEV site) {
		site.setGoodCount(site.getGoodCount() + 1);
	}
	
	public void bad(SiteEV site) {
		site.setBadCount(site.getBadCount() + 1);
	}
}

コントローラ MinatoIndex.java

package servlet;

import java.io.IOException;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import model.SiteEV;
import model.SiteEVLogic;

@WebServlet("/MinatoIndex")
public class MinatoIndex extends HttpServlet{
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		// アプリケーションスコープからSiteEVを取得する
		ServletContext application = this.getServletContext();
		SiteEV site = (SiteEV) application.getAttribute("site");
		if(site == null) { // アプリケーション起動時にsiteEVインスタンスがないとき
			site = new SiteEV();
		}
		SiteEVLogic siteEVLogic = new SiteEVLogic();
		
		// リクエストパラメータの取得
		request.setCharacterEncoding("utf-8");
		String action = request.getParameter("action");
		if(action != null && action.equals("good")) {
			siteEVLogic.good(site);
		}else if(action != null && action.equals("bad")) {
			siteEVLogic.bad(site);
		}
		
		application.setAttribute("site", site);
		
		
		// MinatoIndex.jspの表示
		RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/MinatoIndex.jsp");
		dispatcher.forward(request, response);
	}
}

ビュー MinatoIndex.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="model.SiteEV"%>

<%  
SiteEV site = (SiteEV)application.getAttribute("site");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>湊さんのページ</title>
</head>
<body>
<h1>湊さんのページへようこそ</h1>
<a href="MinatoIndex?action=good">よいね</a> : <%=site.getGoodCount() %>人 <a href="MinatoIndex?action=bad">よくないね</a> : <%=site.getBadCount() %>人
<h2>湊くんとは!?</h2>
...
</body>
</html>
ちゃまちゃま

section11 サーブレットクラス実行の仕組み

Q. サーブレットクラスはインスタンス化していないのに,どうやって実行されているのか?
A. 最初のリクエストが来た時に,アプリケーションサーバ内のサーブレットコンテナによってインスタンス化され,保持される。以降のリクエストでは,このインスタンスを再利用する。アプリケーション終了時はサーブレットコンテナがインスタンスを破棄する。

init/destroyメソッド

サーブレットクラスはinit()とdestroy()というメソッドをスーパクラスであるHttpServletから継承している。initメソッドはサーブレットクラスがインスタンス化された直後,destroyメソッドはサーブレットクラスが破棄される直前に実行される。

initメソッドのオーバーライド

注意点として,initメソッドの実行は,そのサーブレットクラスがリクエストされる順番に左右される。このため,ServletAのinitメソッドで,アプリケーションスコープにインスタンスを保存し,ServletBでその値を取り出して処理を行うような場合に,先にServletBが実行されるとnullが取得される。

public void init(ServletConfig config) throws ServletException{
    super.init(config);
    /* サーブレットクラスのインスタンス化直後に実行したい処理 */
}

destroyメソッドのオーバーライド

public void destroy(){
    /* サーブレットクラスが破棄される直前に実行したい処理 */
}
ちゃまちゃま

section11 リスナー

initメソッドの実行がリクエストの順番に左右される問題を解決し,順番に左右されない初期化を行う仕組みとしてリスナーと呼ばれるクラスがある。リスナークラスに定義されたメソッドは,リクエストではなく,アプリケーションの特定のイベントを検知して動作する。
リスナーは対応したいイベントごとに用意されたリスナーインターフェースを実装して作成する。リスナーインターフェースには次のようなものがある。

  • ServletContextListener : webアプリケーションの開始時または終了時
  • ServletContextAttributeListener : アプリケーションスコープへのインスタンスの保存・上書き・削除
  • HttpSessionListener : セッションスコープの作成・破棄

リスナーの例: webアプリケーション起動時にcount=0をアプリケーションスコープに保存する。

package listener;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class CounterListener implements ServletContextListener{
	public void contextInitialized(ServletContextEvent sce) {
		ServletContext context = sce.getServletContext();
		Integer count = 0;
		context.setAttribute("count", count);
		System.out.println("CountListener execute");
	}
	
	public void contextDestroyed(ServletContextEvent sce) {
		
	}
}
ちゃまちゃま

secion11 フィルタ

フィルタ : doGet()が実行された実行された直前やdoPost()が実行された直後に自動で処理を実行する仕組み。例えばリクエストパラメータ取得時のrequest.setCharacterEncoding("UTF-8");のような共通処理,ログインしていな場合にログイン画面にリダイレクトする処理をまとめて行うことができる。

フィルタの例 : リクエストが来るとrequest.setCharacterEncoding("UTF-8");を実行するフィルタ

package filter;

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;


// URLが/Sample/*のときは@WebFilter("/Sample/*")
@WebFilter("/*")
public class SetEncodingFilter extends HttpFilter{
	protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException{
		request.setCharacterEncoding("UTF-8");
		System.out.println("execute SetEncoding");
		chain.doFilter(request, response);
	}
}
ちゃまちゃま

section11 まとめ

次のようなサーブレットクラスA,フィルタB,リスナーCがあるときの実行順序を考える。

  • サーブレットクラスA : init(), doGet(), destroy()
  • フィルタB : サーブレットクラスAに設定
  • リスナーC : ServletContextListenerインターフェースを実装
  1. サーバ起動時 : リスナーCのcontextInitialized()を実行する
  2. サーブレットAの初回リクエスト時 : フィルタBのdoFilter()を実行する → サーブレットAのinit()を実行する → サーブレットAのdoGet()を実行する
  3. サーブレットAの2回目リクエスト時 : フィルタBのdoFilter()を実行する → サーブレットAのdoGet()を実行する
  4. サーバ終了時 : サーブレットAのdestroy()を実行する → リスナーCのcontextDestroyed()を実行する