0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSP&サーブレットによるwebアプリケーション作成の覚書

Last updated at Posted at 2024-10-27

はじめに

JSP&サーブレットで web アプリケーションを作成する際に学習した事項を絞って覚書にしました。あまり触らなくなりましたが、基本的な部分を忘れかけていたので、ここに覚書として残しておきます。

最新の情報では無い場合があります。

Tomcat

Tomcat はオープンソースのサーブレットコンテナ。サーブレットコンテナとは、端的に言うと「JSP に対応したウェブサーバ」のこと。Tomcat では、サーバ上のプログラムを「アプリケーション」という単位で管理しており、一つのサーバの中に複数のアプリケーションを配備することができる。

アプリケーションフォルダの設定

  1. webapps フォルダに配置する方法
    アプリケーション(WAR ファイルまたは展開されたフォルダ)を Tomcat の webapps ディレクトリに配置する。Tomcat は起動時にこのフォルダ内のアプリケーションを自動的に検出して展開する。
  2. 外部設定ファイルを使用する方法
    Tomcat にアプリケーションフォルダの場所を設定するには、次のフォルダに設定ファイルを作ればよい。
    • フォルダ
      Tomcat をインストールしたフォルダ> conf > Catalina > localhost
    • 設定ファイル
      docBaseでは、サーバ上でファイルが置かれているフォルダを指定する。絶対パスでない場合は、Tomcat の webapps フォルダからの相対パスになる。また、pathでは、ブラウザからアクセスするときに使う URL 上のパスを指定する。URL は、「http://【ホスト名】/【path 名】/」となる。
      xml:設定ファイルの例 <?xml version='1.0' encoding='utf-8'?> <Context docBase="C:\example" path="/example"/>

WEB-INF フォルダ

アプリケーションフォルダの下には様々なフォルダを作ることができるが、WEB-INF という名前を付けると特別な意味を持つフォルダになる。一般に、このフォルダには、次のようなファイルやフォルダが配置される。

  • web.xml
    デプロイメントディスクリプタ
  • classes
    jar 形式ではないクラスファイルを配置するフォルダ
  • lib
    jar 形式のライブラリを配置するフォルダ

WEB-INF の中はブラウザから URL で直接アクセスすることはできない。なので、データファイルなどを置く場所に使ってもよい。

web.xml

web.xml はデプロイメントディスクリプタと呼ばれるファイルで、アプリケーションごとの設定を記述するものである。よく使われる設定の要素は次のとおり。なお、サーブレットの設定は、@WebServletアノテーションでもできる。

  • welcome-file-list
  • error-page
    エラーが発生したときに表示するページを指定するもの。ページの指定はアプリケーションフォルダのルートからの相対パスになる。
  • servlet
  • servlet-mapping
  • resource-ref
  • security-constraint
  • login-config

アプリケーションのリロード

アプリケーションの状態をリセット(リロード)する場合、次の URL にアクセスすればアプリケーションごとにリロードできる。
http://localhost/manager/reload?path=/【アプリケーション名】
停止させる場合はreloadの代わりにstop、開始はstartを指定する。
ここにアクセスし、Tomcat をインストールするときに指定したユーザ名とパスワードを入力すれば実行される。

サーブレットの初期化

サーブレットのpublic void init()メソッドを定義すると、それがTomcat起動時に呼び出される。複数のサーブレットに定義されている場合は、@WebServletアノテーションの引数loadOnStartupの値が小さい順に実行される(web.xmlでサーブレットの設定をしている場合は、web.xmlに記述することもできる)。

標準アクションタグ

JSP ページの中で使える特別な機能を持ったタグのこと。

<jsp:include>

ページ外のリソースを現在のページ位挿入するために用いる。

構文
<jsp:include page="挿入リソースURL(相対URLで指定する)"/>

include ディレクティブでは、JSP ページに最初にアクセスがあった時(サーブレットに変換される時)に挿入処理が静的に行われる。一方で、<jsp:include>では、実行時に挿入処理が動的に行われる。よって、後者では挿入する側とされる側それぞれの出力結果が毎回結合されてクライアントに返される。

<jsp:useBean>

JavaBeans のオブジェクトを新規に作ったり、各スコープからとってきたりする。

構文
<jsp:useBean id="オブジェクト名" [scope="オブジェクトのスコープ"] class="クラス名">
初期化コード
</jsp:useBean>

または、

<jsp:useBean id="オブジェクト名" [scope="オブジェクトのスコープ"] class="クラス名"/>

id 属性の値を参照変数としてスクリプト中で使う。class 属性の値は、FQCN で指定する。初期化コードは、オブジェクトが新規に作られた時のみ実行される。

<jsp:getProperty>

<jsp:useBean>などで作られたオブジェクトのプロパティを文字列へ変換してクライアントへ送信する。対象のプロパティにはゲッターが定義されていなければならない。戻り値がオブジェクトの場合は、その toString()メソッドが呼ばれる。

構文
<jsp:getProperty name="オブジェクト名" property="プロパティ名"/>

<jsp:setProperty>

<jsp:useBean>などで作られたオブジェクトのプロパティを変更するためのもの。対象のプロパティにはセッターが定義されていなければならない。

構文1
<jsp:setProperty name="オブジェクト名" property="プロパティ名" value="セットする値"/>

value 属性の値は、一旦 String 型として解釈され、その後セッターの引数のデータ型に合わせて変換される(基本データ型、String 型、ラッピングクラス型が対象)。

構文2
<jsp:setProperty name="オブジェクト名" property="プロパティ名" param="フォームのパラメータ名"/>

param 属性を使ってフォームの name 属性で指定したパラメータ名を指定すると、フォームから送られてきた情報を使ってプロパティを変更することができる。パラメータ名とプロパティ名が等しい場合、省略可能。

構文3
<jsp:setProperty name="オブジェクト名" property="*"/>

プロパティ名と同じ名前のフォームから送られたパラメータがあった場合、それらすべてが各プロパティにセットされる。

EL

「式言語」とも呼ばれる。${}で囲まれた式を評価し、その結果を出力する。演算式や評価式の結果を出力するために使用され、アクションタグに属性の値として与えることもできる。すでにスコープに格納されたオブジェクトを取り出す場合には、<jsp:useBean>タグで宣言無しに、次の例のように直接呼び出せる。各スコープを明示的に指定する場合は、暗黙オブジェクトを使って次の例のように呼び出す。スコープを明示しなければ、page、request、session、application の順で各スコープを検索する。

使用例
${ 5 }
${ 1 + 2 * 3}
${user.name}
${array[1]}
${pageScope.user.name}
${requestScope.user.name}
${sessionScope.user.name}
${applicationScope.user.name}
${user.age >= 20}
${((user.gender == 0) && (user.age >= 20)) || (user.gender != 0)}
${(100 < 10) ? "Yes" : "No"}

タグライブラリ(主に JSTL について)

スクリプトの代わりに処理を行うことができるタグを種類ごとにまとめたもの。JSTL では、プレゼンテーションロジックの実装に必要な、反復、条件分岐のような頻繁に使用する機能を共通化し、標準的に使えるようにしてある。
EL と組み合わせることにより、JSP ページの中から極力スクリプトレットを排除し、コードを見やすくすることができる。
JSTL のアクションタグの対象は、スコープに格納されたオブジェクトである。

利用のための設定

  1. カスタムタグの定義が入っている jar ファイルを入手して、「webapp のルートディレクトリ/WEB-INF/lib」ディレクトリに配置する
  2. JSP ページ中で使いたいカスタムタグを以下のように taglib ディレクティブで宣言する
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<c:set>

変数に値をセットする。文字列や数値をセットできる。

構文1
<c:set var="変数名" value="セットする値" [scope="オブジェクトのスコープ]/>

変数 var に value 属性の値をセットする。デフォルトでは page スコープ。

構文2
<c:set value="セットする値" target="(オブジェクトの)変数名" property="プロパティ名"/>

target 属性で指定したオブジェクトのプロパティに値をセットする。

<c:remove>

スコープに格納された変数を削除する。

構文
<c:remove var="変数名" [scope="オブジェクトのスコープ"]/>

<c:out>

変数にセットされた値を出力する。escapeXml 属性を明示しなければ、デフォルトでエスケープ処理ができる。一方、EL ではエスケープ処理はされない。

構文
<c:out value="変数名" [escapeXml="true | false"] [default="valueがnullであった場合の値"]/>

<c:forEach>

繰り返し処理を行う。

構文1
<c:forEach [var="変数名"] begin="開始値" end="終了値" [step="ステップ値"] [varStatus="ステータスオブジェクト変数名"]
...
</c:forEach>

var の変数名は、Java の for 文の繰り返し変数と同様の働きをする。ステータスオブジェクトのプロパティには次のようなものがあり、EL の表記で参照できる。

プロパティ名 役割
current 現在 var で参照しているオブジェクトの参照
index 0から始まるループ回数
count 1から始まるループ回数
first ループの開始フラグ(開始直後であれば true)
last ループの終了フラグ(ループの最後であれば true)
begin begin 属性で指定された値
end end 属性で指定された値
step step 属性で指定された値
構文2
<c:forEach [var="オブジェクト参照名"] items="オブジェクトの集合" [begin="開始する順序番号"] [end="終了する順序番号"] [step="ステップ値"] [varStatus="ステータス変数名"]>
...
</c:forEach>

Collection 型、Iterator 型、Enumeration 型、Map 型、配列型、カンマ区切りの文字列については、構文2の方法で、集合に含まれるオブジェクトの数だけループすることができる。Map 型では、名前とキーのセットが var にセットされるので、key と value というプロパティを使って、それぞれ参照できる。

<c:forTokens>

文字列を区切り文字で区切り、先頭から繰り返し参照していく。例えば、abc,d,ef,g,hijという文字列を,で区切った場合、abcdefghijに区切られ、順次繰り返し参照される。

構文
<c:forTokens items="文字列" delims="区切り文字" [begin="開始する順序番号"] [end="終了する順序番号"] [step="ステップ値"] [var="変数名"] [varStatus="ステータス変数名"]>
...
</c:forTokens>

<c:if>

単純条件分岐。test 属性の評価式の値が true であれば、タグの中身を評価する。var 属性で指定した変数名に、評価式の結果を代入し、スコープに格納することもできる。

構文
<c:if test="評価式 [var="変数名"] [scope="オブジェクトのスコープ"]>
...
</c:if>

<c:choose>・<c:when>・<c:otherwise>

複数条件分岐。<c:when>タグ内に、条件に当てはまる場合の記述を行い、どれにも当てはまらない場合の記述を<c:otherwise>タグ内に記述する。

構文
<c:choose>
    <c:when test="評価式A">
        ...
    </c:when>
    <c:when test="評価式B">
        ...
    </c:when>
    ...
    <c:otherwise>
        ...
    </c:otherwise>
</c:choose>

<base>タグ

フォワードを行うと、実際表示されているページのURLとブラウザに表示されているURLの間でずれが生じることがある。このときにJSPページの中でURLを相対パスで指定すると、問題が起こることがある。これを解決するために、<base>タグを利用する。<head>タグ内でhref属性に基準となるURLを絶対パスで指定して記述することで、そのページ内で指定された相対パスは、「その絶対パス+相対パス」として扱われる。

二重サブミットの防止

二重サブミットとは、同じフォームから同じデータが2回以上送信されること。ユーザが何度も「送信」ボタンを押してしまうことや、データを送信した後で「戻る」ボタンで入力画面に戻りもう一度送信することで同じデータが複数追加されてしまう。
これを防止するためには、登録画面を開いた時の時間情報を隠しパラメータに保管し、この時間情報を持ったフォームは一度しか登録を受け付けないようにする。全く同じ時間に別々のユーザが画面を開く可能性があるので、時間情報に加えてユーザ ID をキーとする。

日時データの処理

Java では、日時データを扱うためのクラスがDateCalendarの2種類ある。
Dateクラスは日時データを単なる値として扱う場合に用いる。
一方で、Calendarクラスは年月日などを指定して日付をセットする場合に用いる。

Date クラス

  • newすると、現在の時刻がオブジェクトにセットされる。
Date date = new Date();
  • DateクラスのgetTimeメソッドは、グリニッジ標準時における 1970 年 1 月 1 日午前 0 時を 0 として、そこからの経過時間をミリ秒単位で表した long 型の整数でオブジェクトの持つ時間データを返す

Calendar クラス

  • オブジェクトを生成するには、CalendarクラスのgetInstanceクラスメソッドを用いる。すると、Dateクラスと同様な時間データがセットされる。
Calendar cal = Calendar.getInstance();
  • Calendar型のオブジェクトから年月日を取得するには、getメソッドを用いる。この場合において、get(Calendar.MONTH)で取得する月は、1 月から 12 月に対して 0 から 11 の値が返される(つまり、得た値に 1 を足せば通常のカレンダー通りの月となる)ので注意
年月日取得の例
int intYear = cal.get(Calendar.YEAR);
int intMonth = cal.get(Calendar.MONTH) + 1;
int intDate = cal.get(Calendar.DATE);
  • Calendar型のオブジェクトに年月日をセットするには、setメソッドを用いる。この場合においては、get メソッドとは逆に 1 を引く必要がある
年月日(2024年10月21日)をセットする例
cal.set(Calendar.YEAR, 2024);
cal.set(Calendar.MONTH, 10 - 1);
cal.set(Calendar.DATE, 21);

Date と Calendar の変換

  • CalendarクラスのgetTimeメソッドを用いて、Calendar型からDate型に変換することができる。
Date date = cal.getTime();
  • CalendarクラスのsetTimeメソッドを用いて、Date 型から Calendar 型に変換することができる。
cal.setTime(date);

日時データから文字列表現への変換

  • java.textパッケージのSimpleDateFormatクラスが便利。newするときに表示フォーマットを指定し、そのオブジェクトのformatメソッドに Date 型のオブジェクトを渡すと、文字列表現に変換される
SimpleDateFormat df = new SimpleDateFormat("yyyy年 MM月 dd日");
Date date = new Date();
String strDate = df.format(date);

文字列データから日時データへの変換

String strDate = "2024-10-30";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = dateFormat.parse(strDate);

和暦

Java6 から標準で和暦対応の Calendar クラスが提供されている。また、Java12 からは令和にも対応している(それ以前の ver でもパッチを当てれば対応可)。日本では明治6年からグレゴリオ暦を導入しているため、明治 6 年 1 月 1 日以後の日付をサポートしている。

和暦対応クラス JapaneseImperialCalendar のオブジェクト取得方法

// 方法1
// この方法では、システムのデフォルトロケールを和暦ロケールにしている。
Locale.setDefault(new Locale("ja", "JP", "JP"));
Calendar cal = Calendar.getInstance();

// 方法2
Calendar cal = Calendar.getInstance(new Locale("ja", "JP", "JP"));

JapaneseImperialCalendar はアクセス修飾子が public ではないため、上記の方法でオブジェクトを取得しなければならない。
元号の数値対応は次のとおり。

元号の数値対応
明治 1
大正 2
昭和 3
平成 4
令和 5

任意の年月日の JapaneseImperialCalendar のオブジェクト取得方法

GregorianCalendar クラスを使用する。例は次のとおり。

Locale.setDefault(new Locale("ja", "JP", "JP"));
Calendar cal = Calendar.getInstance();

// 西暦2024年(令和6年)5月10日の日付を指定。月は1月から12月に対して0から11が対応しているので、5月であれば4を指定する。
GregorianCalendar gregorianCalendar = new GregorianCalendar(2024, 5 - 1, 10);

// 和暦に変換
cal.setTime(gregorianCalendar.getTime());

System.out.println(cal.getClass().getName());
System.out.println(cal.get(Calendar.ERA));
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH) + 1);
System.out.println(cal.get(Calendar.DATE));

SimpleDateFormat を使用した和暦へのフォーマット

ロケールを和暦用ロケールに設定しておけば、SimpleDateFormat で行うことができる。

Locale.setDefault(new Locale("ja", "JP", "JP"));

DateFormat dateFormat = new SimpleDateFormat("GGGGyyyy年M月d日");

// JapaneseImperialCalendarからのフォーマット。和暦でフォーマットされる。
Calendar cal = Calendar.getInstance();
System.out.println(dateFormat.format(cal.getTime()));

// GregorianCalendarからのフォーマット。きちんと和暦でフォーマットされる。
GregorianCalendar gregorianCalendar = new GregorianCalendar(2024, 5 - 1, 10);
System.out.println(dateFormat.format(gregorianCalendar.getTime()));

クッキー(Cookie)

クッキーは、ウェブブラウザとサーバー間で情報を保存・管理するための小さなデータ片のこと。セッション管理や状態管理などに用いられる。

基本的な特性

  • 名前と値
    各クッキーは名前と値のペアで構成される
  • 有効期限
    クッキーには有効期限が設定されており、指定された時間が過ぎると自動的に削除される
  • ドメインとパス
    クッキーは特定のドメインとパスに関連付けられ、その範囲内でのみ送信される
  • セキュリティ属性
    Secure や HttpOnly といった属性を設定することで、クッキーの安全性を向上させることができる

注意点

クッキーにはサイズ制限があり、通常は 1 つのクッキーあたり 4096 バイト程度。

基本操作

クッキーの作成

クッキーを作成するには、javax.servlet.http.Cookie クラスを使用する。クッキーの名前と値を設定し、レスポンスに追加する。

Cookie cookie = new Cookie("cookieName", "cookieValue"); // ("クッキー名", "クッキー値")
cookie.setMaxAge(3600); // 有効期限を1時間に設定。単位は秒。
response.addCookie(cookie);

クッキーの取得

クッキーを取得するには、HttpServletRequest オブジェクトの getCookies()メソッドを使用する。このメソッドは、クッキーの配列を返す。

Cookie[] cookies = request.getCookies();
if (cookies != null) {
    for (Cookie cookie : cookies) {
        if (cookie.getName().equals("cookieName")) {
            String value = cookie.getValue(); // クッキーの値を取得
            // 使用する処理
        }
    }
}

クッキーの削除

クッキーを削除するには、クッキーの有効期限を過去に設定します。対象のクッキーを見つけて、setMaxAge(0)を呼び出し、レスポンスに追加します。

// 削除したいクッキーの名前
String cookieName = "myCookie";
// クッキーを取得
Cookie[] cookies = request.getCookies();
  if (cookies != null) {
    for (Cookie cookie : cookies) {
      if (cookie.getName().equals(cookieName)) {
        // 有効期限を過去に設定
        cookie.setMaxAge(0);
        cookie.setPath("/"); // パスを設定(必要に応じて調整)
        response.addCookie(cookie); // クッキーをレスポンスに追加
        break; // 削除対象が見つかったらループを抜ける
      }
    }
  }
}

ハイパーリンクを使ったデータ送信

いくつかの候補(リンク)からユーザに1つ選んでもらうような場合や、移動先のページに何か情報を引き継ぎたいような場合に利用する。一般的には、次のような形式となる。

ハイパーリンクを使ったデータ送信の例
<a href="送信先URL?パラメータ名1=値1&パラメータ名2=値2...">ボディ</a>

フォーム送信時における javascript を利用したバリデーション

form タグにおいて、onsubmit にバリデーションをする関数をイベントハンドラとして登録する。バリデーションに引っかかった場合、false を返すようにしておき、同時にアラートを表示させるようにする。各種のバリデーション例は以下のコードのとおり。

index.html(抜粋)
<body>
    <form action="index.html" onsubmit="return check();">
        <input type="text" name="myId" id="myId">
        <input type="password" name="myPassword1" id="myPassword1">
        <input type="password" name="myPassword2" id="myPassword2">
        <select name="mySelection" id="mySelection">
            <option value="0">---選択してください---</option>
            <option value="1">select1</option>
            <option value="2">select2</option>
        </select>
        <label><input type="radio" name="myRadio" id="myRadio1" value="1">option1</label>
        <label><input type="radio" name="myRadio" id="myRadio2" value="2">option2</label>
        <label><input type="radio" name="myRadio" id="myRadio3" value="3">option3</label>
        <input type="text" name="myNumber" id="myNumber">
        <input type="text" name="myHalfAlphanumeric" id="myHalfAlphanumeric">
        <textarea name="myTextArea" id="myTextArea"></textarea>
        <input type="submit" value="送信">
    </form>

    <script src="script.js"></script>
</body>
script.js
function check() {
  // ID入力欄
  // 空欄チェック
  const myId = document.querySelector("#myId");
  if (myId.value === "") {
    alert("idは空欄にできません");
    return false;
  }

  // パスワード入力欄
  // 空欄チェック
  const myPassword1 = document.querySelector("#myPassword1");
  const myPassword2 = document.querySelector("#myPassword2");
  if (myPassword1.value === "") {
    alert("パスワードは空欄にできません");
    return false;
  } else if (myPassword1.value !== myPassword2.value) {
    // 確認用パスワードのチェック
    alert("確認用のパスワードが違います");
    return false;
  }

  // セレクトボックス
  // 候補を選択していることのチェック
  const mySelection = document.querySelector("#mySelection");
  if (mySelection.value === "0") {
    alert("セレクトボックスの候補を選択してください");
    return false;
  }

  // ラジオボタン
  // 候補を選択していることのチェック
  const myRadio = document.querySelector('input[name="myRadio"]:checked');
  if (!myRadio) {
    alert("ラジオボタンの候補を選択してください");
    return false;
  }

  // 入力内容が数値かどうかのチェック
  // 入力内容を数値に変換したあと、それが数であることを確認する。
  // Number.isFinite()は、暗黙的な型変換を行わずに有限数かどうかを判定する。
  const myNumber = document.querySelector("#myNumber");
  if (!Number.isFinite(Number(myNumber.value))) {
    alert("数値を入力してください");
    return false;
  }

  // 入力内容が半角英数かのチェック
  const myHalfAlphanumeric = document.querySelector("#myHalfAlphanumeric");
  if (!myHalfAlphanumeric.value.match(/^[A-Za-z0-9]*$/)) {
    alert("半角英数字で入力してください");
    return false;
  }

  // 文字列長の制限
  // 全角も半角もそれぞれ一文字として数えられる
  const myTextArea = document.querySelector("#myTextArea");
  if (myTextArea.value.length > 10) {
    alert("文字数が制限(10文字)を超えています");
    return false;
  }

  // フォームを送信してよいかの確認
  return confirm("この内容で登録します。よろしいですか?");
}

ファイルアップロード・ダウンロード

サーブレットバージョンチェック

サーブレットAPI3.0以降はファイルアップロード・ダウンロードのパッケージがデフォルトで含まれるようになったので、Tomcat8・javaSE8環境の場合はどうか確認した。

確認用プログラム
<%@ 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>
JSP version: <%= JspFactory.getDefaultFactory().getEngineInfo().getSpecificationVersion() %><br>
Servlet Specification: <%= application.getMajorVersion() %>.<%= application.getMinorVersion() %><br>
</body>
</html>

結果は、

  • JSP version: 2.3
  • Servlet Specification: 3.1
    となり、新たにライブラリをダウンロードする必要はなかった。

アップロード機能

要点

  1. 画面のフォームでは、enctype="multipart/form-data"属性を付ける
  2. inputのtypeはfileとする
  3. サーブレットクラスに@MultipartConfigアノテーションをつける
  4. request.getPart()メソッドでPartオブジェクトを取得する
  5. Partは複数取得も可。その場合は受け取る変数をコレクション(リストとか)にする
  6. getSubmittedFilename()メソッドでクライアントから示されたファイルの名前を取得する。Servlet API3.1以降は使える
  7. ファイルをアップロードするフォルダを作成しておき、getServletContext().getRealPath()メソッドでパスを取得する
  8. PartオブジェクトのgetSize()メソッドでファイルサイズを取得。ファイルサイズが0より大きいかどうかで取得の例外処理をする
  9. Partオブジェクトのwrite()メソッドでファイル書き出し
画面側のサンプルプログラム(抜粋)
<form action="UploadServlet" method="POST" enctype="multipart/form-data">
	<p>
		<input type="file" name="upfile" />
	</p>
	<p>
		<input type="submit" value="upload" />
	</p>
</form>
サーブレット側のサンプルプログラム(抜粋)
package controller;

import java.io.File;
import java.io.IOException;

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

@WebServlet("/UploadServlet")
// multipart/form-data形式のリクエストに対応できるようにする
@MultipartConfig
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // partオブジェクトの取得。getPart()の引数は、フォームのname属性の値。
        Part part = request.getPart("upfile");
        // 名前の取得
        String partName = part.getName(); // パートの名前を取得。getPartの引数と同じ。
        String submittedFileName = part.getSubmittedFileName(); // クライアントから送信されるファイル名を取得
        // アップロードするフォルダ
        String path= getServletContext().getRealPath("/upload");
        // ファイルサイズ取得
        long fileSize = part.getSize();
        // ファイル書き出し。取得できていないとfileSizeは0なので、それで例外処理。
        if (fileSize > 0) {
            part.write(path + File.separator + submittedFileName);
        }
        // ページ遷移
        request.getRequestDispatcher("/uploadFile.jsp").forward(request, response);
    }

}

ダウンロード

  1. webアプリケーションルート内のファイルをおいているパスを表すFileオブジェクトを作成
  2. ファイル読み込み用バッファの用意
  3. 拡張子からcontentTypeを取得
  4. response.setContentTypeでcontentTypeを出力
  5. response.setHeaderでファイル名の送信。このとき、attachmentを指定しなければ、ブラウザに直接表示されてしまう場合があるため注意
  6. ServletOutputStreamに対して、FileInputStreamからファイル内容を送信する
  7. 画面側では、このサーブレットへのハイパーリンクなどを用いてユーザにダウンロードさせる
サーブレット側のサンプルプログラム
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // webアプリケーションルート内のファイルをおいているパスを表すFileオブジェクトを作成
    ServletContext context = getServletContext();
    String downloadFilePath = context.getRealPath("/upload/");
    File downloadFile = new File(downloadFilePath, "test.csv");
        
    // ファイルのダウンロード処理
    // ファイル読み込み用バッファ
    byte buffer[] = new byte[4096];

    String fileName = downloadFile.getName();

    // 拡張子からcontentTypeを取得
    FileDataSource fds = new FileDataSource(fileName);
    String contentType = fds.getContentType();

    // contentTypeを出力
    response.setContentType(contentType);

    // ファイル名の送信
    response.setHeader("Content-disposition", "attachment; filename=\"" + fileName +"\"");
        
    // ファイル内容の送信
    ServletOutputStream sos = response.getOutputStream();
    FileInputStream fis = new FileInputStream(downloadFile);
    int size;
    while ((size = fis.read(buffer)) != -1) {
        sos.write(buffer, 0, size);
    }
    fis.close();
    sos.close();

}

JDBC によるデータベースアクセス

JDBC は、Java プログラムからデータベースへの接続・切断、SQL の実行、結果の取得を行うためのクラスやメソッドを提供している。データベースによる差異は JDBC ドライバが吸収してくれるため、どのデータベースを扱うときも同じ手順でプログラムを作成することができる。

JDBC 利用の概要

  1. JDBC ドライバのインストール
  2. JDBC ドライバのロード
  3. データベースへの接続
    使用するドライバをロードし、データベースへのコネクション(接続)を確立する
  4. データベースアクセス処理
    SQL 文を文字列として組み立てて、データベースに送信する。このとき、コネクションを使う
  5. 切断処理
    コネクションを開放する

JDBC ドライバのインストール

jar ファイルを「webapp のルートディレクトリ/WEB-INF/lib」ディレクトリに配置する。

JDBC ドライバのロード

JDBC を利用するためには、各データベース用の JDBC ドライバをロードしておく必要がある。JDBC4.0 以降は自動ロードされるが、明示的にロードする場合は、次のようにする。

Class.forName("各データベース用のドライバのクラス名");

クラス名は、例えば、MySQL の場合はcom.mysql.jdbc.Driverである。

データベースへの接続

java.sql.DriverManagerクラスのgetConnection()メソッドを用いて接続をする(コネクションオブジェクトを得る)。

Connection con = DriverManager.getConnection(接続先URL文字列, ユーザ名, パスワード);

接続先 URL 文字列は、次のような文字列である。

jdbc:データベースシステム名://ホストアドレス:ポート番号/データベース名?パラメータ=値...

例えば、次のとおり。

jdbc:mysql://db.hoge.co.jp:5000/exampledb?useUnicode=true&characterEncoding=UTF-8

データベースアクセス処理

SQL の実行

SQL 文を記述した文字列引数にしてコネクションオブジェクトのprepareStatement()メソッドによりPreparedStatementオブジェクトを作成する。次に、そのオブジェクトのset+データ型メソッドを用いてプレースホルダと呼ばれる変数部分に値をセットし、最後にexecuteUpdate()executeQuery()メソッドを用いて SQL を実行する。なお、プレースホルダへの代入時には、エスケープ処理がなされる。例えば、次のようにする。

PreparedStatement pstmt = con.prepareStatement("UPDATE employee SET department=? WHERE number=?");
pstmt.setString(1, "総務");
pstmt.setInt(2, 99);
pstmt.executeUpdate();

結果の取得

SELECT 文を実行した場合、結果として返されるテーブルは ResultSet オブジェクトとして得られる。行と列を指定してデータを取り出すこととなる。
行を指定するには、「カーソル」と呼ばれる「現在どの行を参照しているのか?」という情報を表すものを使う。カーソル移動のためのメソッドは次のようなものがある。なお、初期状態ではどの行も参照していない状態である。多くはnext()メソッドを while 文の条件部分に用いて順次参照していく。

メソッド名 機能
boolean first() 参照行を先頭に移動
boolean next() 参照行を1行下に移動。移動先の行がなければ false を返す。
boolean previous() 参照行を1行上に移動。移動先の行がなければ false を返す。
boolean last() 参照行を最終行に移動

列を指定するには、get+データ型("カラム名")メソッドを利用する。例えば、integer の値を取得するには、getInt()メソッドを利用する。

切断処理

最後は、ResultSet(検索の場合)、PreparedStatement 及び Connection それぞれのオブジェクトのclose()メソッドを用いてクローズをする。ただし、ResultSet オブジェクトと PreparedStatement オブジェクトは、Connection オブジェクトをクローズすれば自動的にクローズされる。

DAO パターン、DTO パターンを用いたデータベース処理

DAO パターン

DAO パターンとは、データベースとアプリケーションの間のデータのやり取り専用の DAO と呼ばれるクラスを用意し、アプリケーションとデータベースの関連をまとめて最小にするデザインパターンのこと。
DAO の中では、データベースへの接続部分と切断部分をそれぞれメソッドにまとめておき、その他データベースへの登録、更新、検索を行うメソッドを定義する。
データベースへの接続メソッドでは、コネクションを返すところまでを担当する。
切断メソッドでは、コネクションを受け取り、それをクローズするところまでを担当する。
各種データベースへの登録、更新、検索メソッドでは、コネクションの接続・切断メソッドを利用しつつ、その他の SQL 実行の処理を行う。このとき、検索条件や登録、更新する DTO を引数として受け取ったり、検索結果を DTO(あるいはそのリスト)としてアプリケーションに返すことをする。

DTO パターン

DTO パターンとは、データベースにおける行とアプリケーションにおけるオブジェクト相互の変換過程において用いられるデータを抽象化した JavaBeans 形式のクラス(DTO)によってデータのやり取りを行うデザインパターンのこと。
DTO は、アプリケーション側で使いやすい形で準備する。必要なフィールド変数とそのゲッター・セッターを持ったクラスとなる。

処理の流れ

データ登録であれば、アプリケーションが登録したい情報を DTO として DAO に渡し、DAO の中で DTO をもとに SQL 文を組み立てデータベースに登録を行い、成否をアプリケーションに返す、という流れとなる。
データの検索であれば、アプリケーションが検索条件を DAO に渡し、DAO がデータベースから取り出した情報を元に DTO を作り、それをアプリケーションに返す、という流れとなる。

DataSource を利用した DB 接続

前述の方法では、DriverManager を利用してコネクションを取得していたが、一般的には、DataSource と JNDI という仕組みを使ってコネクションを取得する方が良いとのこと。
DataSource を用いれば、リソース情報を外部ファイルに記述することができるほか、コネクションプーリングの機能も使える。

DataSource の設定

アプリケーションごとに設定する場合を記述する。(サーバーごとに設定したい場合は、Tomcat 側の設定ファイルに記述を追加する。)
アプリケーションのコンテキストの META-INF/に、context.xmlというファイルを作成し、次のように記述する。

context.xmlの例
<Context>
   <Resource name="jdbc/mydb"
             auth="Container"
             type="javax.sql.DataSource"
             username="dbuser"
             password="dbpassword"
             driverClassName="com.mysql.jdbc.Driver"
             url="jdbc:mysql://db.hoge.co.jp:5000/exampledb?useUnicode=true&amp;characterEncoding=UTF-8
"/>
</Context>

nameは、データソースを取得するときに使用する名前。慣習的にjdbc/を前につけ、任意の名前を記述する。
urlは、JDBC ドライバの URL。前述の「データベースの接続」の項で DriverManager に渡したものと同じ。

DataSouce の利用方法
  1. InitialContext の作成
  2. DataSource の取得
  3. DataSource から Connection オブジェクトを取得する

実際の利用部分のコード例は次のとおり。

例(抜粋)
// リソース名を表すフィールド変数(前項で設定したnameと同じ)
private final static String resourceName = "jdbc/mydata";

// 1. InitialContextの作成
InitialContext ic = new InitialContest();

// 2. DataSourceの取得
DataSource ds = (DataSource)ic.lookup("java:comp/env/" + resourceName);

// 3. DataSourceからConnectionオブジェクトを取得する
Connection con = ds.getConnection();

なお、デフォルトの実装クラスはorg.apache.tomcat.dbcp.dbcp2.BasicDataSource(Apach Common DBCP)となる。

フォームで送られてきたデータの文字コード

フォームから送られてくる文字コードは、文字エンコーディングを識別するための情報がないために、サーブレットコンテナが勝手にiso-8859-1という文字エンコーディングのデータとしてUnicodeに変換しようとする。
したがって、データを正しく扱うための処理が必要となる。
このとき、フィルターという機能を使えば効率的に処理ができる。
eclipseを使用しているなら、プロジェクトに対して新規に「フィルター」を作成する(その他>Webの中にある)。次にパッケージ名を「filter」、クラス名を「EncodingFilter」として「次へ」をクリック。フィルター・マッピングを編集して、「/*」にする。これで、このプロジェクトのすべてのサーブレットが対象となる。作成したフィルタークラスのdoFilter()メソッド内に次の処理を記述すれば完了。

request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
chain.doFilter(request, response);

java Mailによるメール送信

  1. あらかじめjavax.mailのjarファイルを取得し、ビルドパスに追加しておく
  2. サーブレット側では、PropertiesオブジェクトにSMTPサーバの設定を持たせる。内容は外部ファイルに書き込んでおき、クラスパスの通ったフォルダに配置しておく。そのInputStreamをthis.getClass().getClassLoader().getResourceAsStream()で取得し、情報をロードしている
  3. 認証情報をjava.mailの`Sessionに渡して、Sessionオブジェクトを作成する
  4. MimeMessageでメール情報を作成する
  5. Transport.send()で送信する
  6. なお、gmailを用いる場合は、アプリパスワードを取得して利用しなければならない
サーブレットの処理(抜粋)
protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    // メール送信情報の設定
    request.setCharacterEncoding("UTF-8");
    String subject = request.getParameter("subject");
    String body = request.getParameter("body");

    // SMTPサーバの設定
    // properitesファイルに書き込んでおいた設定を読み込む
    Properties properties = new Properties();
    try (InputStream input = this.getClass().getClassLoader().getResourceAsStream("config.properties")) {

        // プロパティファイルを読み込み
        properties.load(input);

    } catch (IOException e) {
        e.printStackTrace();
    }
    // プロパティの取得
    String fromEmail = properties.getProperty("fromEmail");
    String toEmail = properties.getProperty("toEmail");
    String username = properties.getProperty("username"); // メールアカウントのユーザー名
    String password = properties.getProperty("password"); // メールアカウントのパスワード
    
    // 認証情報の設定
    Session session = Session.getInstance(properties, new Authenticator() {
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password);
        }
    });
    
    try {
        // メールメッセージの作成
        Message message = new MimeMessage(session);
        message.setFrom(new InternetAddress(fromEmail));
        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
        message.setSubject(subject);
        message.setText(body);
    
        // メールの送信
        Transport.send(message);
    
        System.out.println("メールが正常に送信されました。");
    
    } catch (MessagingException e) {
        e.printStackTrace();
    }
}
config.properties(重要部分抜粋)
mail.smtp.auth=true
mail.smtp.starttls.enable=true
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587

セキュリティ関連

クロスサイトスクリプティング

悪意のある第三者が、そのサイトを閲覧したユーザのブラウザ上で任意のJavaScriptプログラムを実行できてしまうこと。この脆弱性は、次のような状況が重なった場合に問題となる。

  • ユーザからの入力に含まれるHTML特殊文字の処理が適切に行われていない
  • ユーザからの入力文字列を出力する(ブラウザに渡す)処理が含まれる
  • ブラウザがJavaScriptを実行可能である
    例えば、簡易的な掲示板などで、悪意のあるユーザがJavaScriptを埋め込んだリンクを書き込むなどし、誰かがそれをクリックするとクッキーの値を悪意のあるユーザのもとへ集める、といったことができる。
    対策は、入力時にHTML特殊文字(「<」「&」「"」など)を適切に変換する処理を施すことである。JSTLの\タグを利用することも有効な手段である。

SQLインジェクション

リクエストパラメータから取り出した内容を、そのままデータベースへのSQL文の一部として送っているときに発生する。
例えば、条件(WHERE句)部分に「or true」などの文字が入力されると、すべてのデータを読みだされる危険がある。
対策は、入力をチェックし、想定外の値が指定されたときにきちんとエラーとなるようにしておくこと。また、シングルクォーテーションなどの特殊文字については、エスケープ処理が必要。

フィルターを使った認証処理

認証処理は、文字コードの項で触れたフィルターを使えば、効率的に実装できる。
秘密にしたいページをURLパターンに登録し(ログイン用のページは除いておく)、認証用のBean(ログインページで認証済みかどうかを表すフラグのフィールドを持つBean。セッションに登録する。)で認証済みかを判定し、フィルタを通過させるかどうかという処理をさせる。フィルタを通過できなかった場合は、ログインページなどにリダイレクトさせる。

動作テスト

システムが正しく動作するかどうかを確認すること。システムが一通り組み上がったら、なるべく早い時期にテストを行い、問題になりそうなところを洗い出しておくと良い。完璧なテストというものはないが、妥協できるところとできないところを区別してアプリケーションの規模や開発期間、人員、予算などによってリソースを割り当てる。

システムの動作テスト

  • web アプリケーションの場合は、「画面の遷移」と「入力項目」が主なテスト対象となる
  • テスト担当者、ドキュメント担当者、ユーザーが関わるのも良いこと
  • テスト項目を一覧にしたものを「テスト仕様」という。テスト仕様に従ってテスティングする
テスト仕様の例
【ユーザー管理機能】
・ユーザ一覧画面
 新規画面へのリンク
 編集画面へのリンク
 トップページへのリンク
 ログアウトへのリンク
 システム管理者以外が開けないこと
・新規画面
 ログイン名
  通常の入力で正しく登録できること
  空欄のまま送信できないこと
  ・・・
 氏名
  通常の入力で正しく登録できること
  空欄のまま送信できないこと
  ・・・
 送信ボタンの動作
 戻るボタンの動作
 ・・・
  • 「どのバージョン」で「どのテストを行ったか」を管理する。コードの修正を繰り返すたびにテストを行うから。不具合があれば、操作手順やエラーメッセージ、再現方法を記録する
  • コードが修正された場合は、修正部分の再テストを行う。他の部分も影響を受けている可能性があるので注意

テスト項目

画面遷移のテスト

  • リンク切れがないか
    まず、画面の遷移図に従って、すべての経路をテストする。次に、各画面のリンクをすべてテストし、リンク切れがないか確認する。
  • 上の階層に戻るリンクがあるか
  • フォームデータの送信
    通常の入力での遷移と不正な入力での遷移をテストする。各入力項目に関する詳細なテストは、画面ごとにまとめて行ったほうが良い。

画面のチェック

  • 表示内容のチェック
  • JavaScript の動作チェック
  • レイアウトの崩れがないか
    表示するデータが極端に多い場合や、極端に少ない場合、データが空の場合などに問題がないかどうかをチェックする。また、ブラウザの幅を狭めたり広げたりしてレイアウトが崩れないことを確認する。

入力項目のテスト

様々な値を入力してエラーが生じないかをテストする。次のようなパターンが考えられる。

  • 空欄
  • 数値
    上限値、下限値、その隣接値。ゼロ、マイナス。大きい値、小さい値。
  • 文字数
    データベースのカラムに保存可能な文字数を超えた場合に正しく処理されるか。
  • 全角半角
    文字種を区別している場合に、両方のパターンをテストして正しく処理されるか。
  • HTML タグ
    入力欄に HTML タグが入力されたときにレイアウトが崩れるなどの不具合が生じないかどうかを確認する。
  • SQL インジェクション対策
    入力欄にシングルクオートやセミコロンなどの文字を入力してデータベースのエラーが生じないことを確認する。これらの文字をデータベースに保存するには、エスケープシーケンスに置き換える必要がある。PreparedStatement オブジェクトに setString メソッドを使って文字列を指定した場合は、自動的にエスケープされる。

各種状態のチェック

  • データが空の状態
    運用開始時など、データが空の状態で不具合が生じないかどうか
  • 排他処理
    同じデータに複数の端末からアクセスして編集操作を行い、排他処理が正しく行われることを確認する。
  • 二重サブミット
    フォームを送信した後、ブラウザの戻るボタンをクリックして、もう一度フォームを送信し、二重サブミット対策が正しく機能しているかどうかを確認する。

参考文献

はじめてのJSP&サーブレットプログラミング/アイティーブースト 著/秀和システム
JSP業務アプリケーション短期開発入門/竹形 誠司 著/ラトルズ
標準JSP サーブレット教科書/片山 幸雄 著/ソフトバンククリエイティブ
Java+MySQL+Tomcatで始めるWebアプリケーション構築入門/竹形 誠司 著/ラトルズ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?