Part3では,「不規則に発生する不正な動作」「SQLインジェクション」「パフォーマンス不足」「チェックボックスのおかしな挙動」「メモリー不足のエラー」の五つを取り上げて,その問題点と解決方法を説明します。

トラブル1 たまにおかしな動作をする!

 Webブラウザからユーザー名などのデータを送信して結果を出力するといったWebアプリケーションで,たまにおかしな結果を出力することがあります。同じように操作をして正しく動くこともあるので,間違いに気づかないこともあります。しかも,おかしな動作に気づいてデバッグしてもソースコードに間違いを見つけられないことが多く,初級者の方ではどこを直せばよいか見当もつかないといった場合があります。

 このような現象は,シングルスレッド・モデルとマルチスレッド・モデルの違いを意識せずにプログラムを作ると多く発生します。スレッドはプログラムを処理する実行単位です。シングルスレッドの場合は,単一スレッドで処理するため,インスタンスを共有しません。マルチスレッドの場合は,複数のスレッドを並行して処理できますが,インスタンスを共有します。

 例えば,ユーザーからのリクエストを受けてデータベースにあるデータを返送するアプリケーションをマルチスレッドで動かすと,データベースを検索している最中にほかのユーザーからのリクエストを受けることができます。一方,シングルスレッドのプログラムでは,データベースの検索が終わるまでほかのリクエストを受けられません。

 マルチスレッドの場合は,あるスレッドが変数の値を書き換えて,結果を設定すると,別のスレッドの動作に影響します。マルチスレッドで動作するプログラムは,それぞれのスレッドがほかのスレッドに悪影響を与えないように設計しなければなりません。ほかのスレッドに悪影響を与えないように対策をしたプログラムを「スレッドセーフである」と呼びます。

 Webアプリケーションは基本的にマルチスレッドで動作するので,変数の扱いに注意しなければなりません。特に,サーブレットのインスタンス変数とクラス変数はトラブルが発生しやすいので注意が必要です。それぞれトラブルの具体例と解決策を紹介しましょう。

サーブレットのインスタンス変数は
複数のスレッドで共有する

 リスト1は,ユーザーがWebブラウザに入力した名前を受け取って,「ようこそ○○さん」と表示するWebアプリケーションです。単一のWebブラウザで操作しているうちは正常に動作するのですが,複数のWebブラウザから同時に名前を送信すると動作がおかしくなることがあります。

package jp.co.nikkeibp.example;

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ExampleServlet extends HttpServlet {
  
  private String loginName = null;
  
  public void doPost(
    HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {
    request.setCharacterEncoding("Windows-31J");

    // ユーザー名を取得し,インスタンス変数に保存
    loginName = (String) request.getParameter("name");

    // インスタンス変数をリクエストへ設定
    request.setAttribute("loginName", loginName);
    
    // JSPを呼び出す
    RequestDispatcher reqDsp =
      request.getRequestDispatcher("list2.jsp");
    reqDsp.forward(request, response);
  }
}
リスト1●ログインしたユーザー名をインスタンス変数に保存し,画面に表示するサーブレット

 Webブラウザを二つ起動して,それぞれに異なるユーザー名を入力し,「ログイン」ボタンを同時に押します。本来はそれぞれのWebブラウザで入力したユーザー名を表示するはずですが,どちらも同じユーザー名になる場合があります。これでは,誰がログインしたのかがわからなくなってしまいます。多数のユーザーがアクセスするWebアプリケーションではあってはならないトラブルです。

 インスタンス変数にはいろいろありますが,サーブレットのインスタンス変数はスレッドセーフではありません。このトラブルは,スレッドセーフではないインスタンス変数を利用したために発生しています。リスト1では,一つ目のWebブラウザから受け取った値をインスタンス変数に保存した後,二つ目のWebブラウザから受け取った値でインスタンス変数を上書きしています。そのため二つの処理が同時に発生すると,両方のWebブラウザに同じ名前を返信してしまうのです。スレッドセーフではない場合,インスタンス変数の上書きが発生するということを覚えておいてください。

 解決するには,サーブレットのインスタンス変数を使用せず,セッションにデータを保存するようにプログラムを書き換えましょう(リスト2)。

// 省略
public class ExampleServlet extends HttpServlet {
  
  public void doPost(
    HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {
    request.setCharacterEncoding("Windows-31J");

    // ユーザー名を取得し,自動変数へ保存
    String loginName = 
      (String) request.getParameter("name");

    // 自動変数をセッションへ保存
    HttpSession session = request.getSession();
    session.setAttribute("loginName", loginName);
    
    // JSPを呼び出す
    RequestDispatcher reqDsp =
      request.getRequestDispatcher("list2.jsp");
    reqDsp.forward(request, response);
  }
}
リスト2●リスト1をセッションに名前を保存するように変更した例

 セッションは,ユーザーが送信するデータを一定期間格納するオブジェクトです。基本的に1ユーザーに対して一つのセッションを使用しますので,サーブレットのインスタンス変数のように複数のユーザーで共有することはありません。ただし,セッションそのものはスレッドセーフではありません。セッションに値(データ)を設定する際に必要な名前(キー)がプログラム中で重複しないように注意しましょう。

 なお,Webアプリケーションをシングルスレッドで動作させて,ほかのスレッドがインスタンス変数を書き換えるのを防ぐ方法もあります。javax.servlet.SingleThreadModelインタフェースを利用するだけで済むのでプログラミングは簡単ですが,性能が悪くなります。なるべく使わないほうがよいでしょう。