2003-1-31作成

 ■サーブレット編5

 ■サーブレット固有の問題

 ここまで順に読んできて、課題等を試してきて、ここでいきなり「どんでん返し」です。
 「■サーブレット編1」のソースの構成の中でインスタンス変数にちょっとだけ触れて以来、特に「■サーブレット編2」の内部ルーチン以降ではサンプルでも気軽にインスタンス変数を使ってきました。

 しかし実を言うとこれは、なかなか危険な行為なんです。
 いったい何が危険かを説明する前に、先にサンプルを提示しますのでこれを実行してみてください。ctst_svltctst_clsです。ctst_clsはパッケージ化しており、パッケージ名はここの一連のサンプルと同じyokパッケージになっています。これに関連する部分(ctst_svltのインポートも)を各自の環境に合わせて修正後、コンパイル・実行してください。

 ・サンプルの実行方法

 サンプルで実験をするにはまずブラウザを起動して、ctst_svltを開いておいてください。そうすると次のような画面が表示されます。

インスタンス変数テスト

インスタンス変数テスト

ID:   文字:   色: Blue   Fuchsia




 この中の「ID」、「文字」、「色」に適当な値を指定しておいてください。「ID」と「文字」は1文字入れておけば十分です。

 サンプルのサーブレットおよびクラスで行っている内容は次のとおりです。

・「文字」と「色」(文字の色です)をサーブレットのインスタンス変数に格納する
・3秒間隔で3回、インスタンス変数の文字列に「文字」を連結し、同じくインスタンス変数に持っているカウンタに1を加算する
・この変更のたびに、内容を出力する

 サーブレット側で以上の処理を行った後、サンプルのクラスを呼び出します。サンプルクラスを new 演算子でインスタンス化する際、引数でこの「ID」、「文字」、「色」などの情報を渡しています。クラス側ではコンストラクタの中でこれらの情報を、クラスのインスタンス変数に格納しています。
 呼び出されるクラス側で行っている内容は次のとおりです。

・インスタンス化されるとき、「ID」、「文字」、「色」をクラスのインスタンス変数に格納する
・3秒間隔で3回、インスタンス変数の文字列に「文字」を連結し、同じくインスタンス変数に持っているカウンタに1を加算する
・この変更のたびに、内容を出力する

 というように、やっている内容はサーブレット側と同じようなことです。ただそれを行っている母体がサーブレットか通常のクラスかの違いです。

 さて画面上で「ID」、「文字」、「色」を入力したら、実行ボタンを押してください。そしてどのように表示されるかを見ておいてください。

 ・次の段階の実験(危険その1)

 ブラウザを一つだけ起動した状態で動作内容の確認が終わったら、それをいったん閉じて(終了させて)、今度はブラウザを二つ起動してください。そして両方のブラウザでctst_svltを開いておきます。
 今度も画面上の「ID」、「文字」、「色」に値を設定するのですが、二つのブラウザでそれぞれ異なる値を設定してください。
 まずどちらかのブラウザの画面で実行ボタンを押します。
 次に変更内容が一つか二つ表示されたら、もう片方のブラウザの画面も実行ボタンを押してください。両方の実行が終わったら両方の画面を見比べて、何が起こっているのかを考えてみてください。

 ・さらに次の段階の実験(危険その2)

 ブラウザを二つ起動させた実験が終わったら今度は、ブラウザを閉じずに、そのまま両方の実行ボタンを押してみましょう(二つ目のブラウザの実行ボタンを押すタイミングは、先ほどの実験と同じ方がいいでしょう)。
 変更文字列の表示のされ方は先ほどの実験と同じようになると思います。しかし今度はカウンタの内容に注目してください。

 ・もっとやばい実験(危険その1から派生)

 今度はctst_svlt.javaのソースを一部修正します。クラスの変数宣言、doGet メソッドの変数宣言はこうなっています。

public class ctst_svlt extends HttpServlet
  {

//    private PrintWriter w_out;
    private String      wc_dat;      // 出力文字列
    private int         wc_cnt;      // 変更カウント
    private String      wc_chclr;    // 表示文字の色
    private String      wc_chr;      // 表示文字

    public void doGet (HttpServletRequest p_request, HttpServletResponse p_response)
      throws IOException, ServletException
      {

        PrintWriter        w_out = null;
        ctst_cls           w_ctst_cls = null;
        SimpleDateFormat   w_dfmt = null;
        GregorianCalendar  w_gclndr = null;
        String             w_id = null;       // ブラウザID
        int                w_lpcnt = 0;       // ループカウンタ

 PrintWriter 型の変数w_outはインスタンス変数とローカルの変数の二つ定義していますが、インスタンス変数は注釈になっています。
 これを逆にし、インスタンス変数を有効にしてローカルの変数を注釈にしてください。つまりこう修正してください。

public class ctst_svlt extends HttpServlet
  {

    private PrintWriter w_out;  // こちらは注釈を外す
    private String      wc_dat;      // 出力文字列
    private int         wc_cnt;      // 変更カウント
    private String      wc_chclr;    // 表示文字の色
    private String      wc_chr;      // 表示文字

    public void doGet (HttpServletRequest p_request, HttpServletResponse p_response)
      throws IOException, ServletException
      {

//        PrintWriter        w_out = null;  // こちらを注釈にする
        ctst_cls           w_ctst_cls = null;
        SimpleDateFormat   w_dfmt = null;
        GregorianCalendar  w_gclndr = null;
        String             w_id = null;       // ブラウザID
        int                w_lpcnt = 0;       // ループカウンタ

 この修正後にコンパイルし、先ほどと同じように二つのブラウザを起動して実行してみてください。

 ◇実行結果とそうなる理由

 (危険その2)の実験の実行結果は概ね次のようになると思います(ブラウザ1側で一つ目のデータ表示がされた段階でブラウザ2の実行ボタンを押した場合)。
ブラウザ1 ブラウザ2

インスタンス変数テスト

ID:  文字:  色: Blue Fuchsia



Before: :6:ID=A
ctst_svlt:7:ID=A
ctst_svlt:9:ID=A
ctst_svlt:11:ID=A


ctst_cls:1:ID=A Start at 10:53:58.256
ctst_cls:2:ID=A Start at 10:53:58.256
ctst_cls:3:ID=A Start at 10:53:58.256


After: :12:ID=A
 
インスタンス変数テスト

ID:  文字:  色: Blue Fuchsia



Before: :7:ID=B
ctst_svlt:8:ID=B
ctst_svlt:10:ID=B
ctst_svlt:12:ID=B


ctst_cls:1:ID=B Start at 10:53:59.949
ctst_cls:2:ID=B Start at 10:53:59.949
ctst_cls:3:ID=B Start at 10:53:59.949


After: :12:ID=B

 まず変更する文字列データ、文字列に結合する文字データ、カウンタ、は、サーブレットでインスタンス変数として定義しているところが原因です。
 ブラウザ1側で最初の
ctst_svlt:7:ID=A
を表示した後、ブラウザ2側のサーブレットが開始され、インスタンス変数の変更する文字列データをクリアし、文字列に結合する文字データに画面で指定した文字と色を設定しています。
 次の
ctst_svlt:9:ID=A
ですが、ブラウザ2側で変更した結果に対して文字列を連結した形になっています。ブラウザ1側で結合する文字は""のはずなのですが、これもブラウザ2側で設定した文字と色になっています。

 またカウンタ情報もブラウザ1側では7、8、9となるはずが7、9、11と飛んでおり、ブラウザ2側と併せて見ると7、8、9、10、11、12と1ずつ足されています。

 しかしその後のctst_cls.javaで表示している内容はctst_svlt.javaと同様に、変更する文字列データ、文字列に結合する文字データ、カウンタ、はクラスのインスタンス変数として定義してあっても混在することはありませんでした。それぞれの画面で指定した文字が結合され、カウンタ情報もちゃんと画面毎に1ずつ足されています。

 ・これの原因

 結論を先に書くとサーブレットのインスタンス変数は、いわゆる大域変数(グローバル変数)になってしまうためです。
 クライアントからの要求がある度にスレッドというものが生成され、サーブレットに割り当てられます。複数のクライアントから同じサーブレットの要求がくると、その数分のスレッドが生成されます(スレッド数の上限はJava実行環境であるAPサーバーの設定による)。


※ここはOS(オペレーティングシステム)周りの細かい話なんで、興味ない人は飛ばしてもらって構いません。

スレッドとは
 コンピュータを機械だけでなくシステムとして考えた場合、その中心になるのはOSです。ハードウェアとしてのコンピュータにとってはCPUが中心ですが、OSから見た場合は全てのハードウェアは資源になります。CPUも例外ではありません。
 スレッドとはOSがCPU資源に処理を割り当てる最小単位である、と思ってください。ある瞬間に一つのCPUは一つのスレッドが占有します。
 ちなみにスレッドとはunixやWindows系では10年弱くらい前から出てきた概念で、昔はLight Weight Process(軽量のプロセス)などと呼ばれていたこともありました。スレッド以前はCPU割り当ての最小単位はプロセスでした。プロセスとスレッドの違いは「専用のアドレス空間を持つか否か」です。
 プロセスが生成されるとOSはそのプロセス専用のプロセス空間(アドレス空間)を生成します。その空間用に新たにページ変換テーブルやセグメント変換テーブルといったアドレス空間制御用のテーブルなどの生成も必要になり、それ相応のオーバーヘッドが発生します。
 スレッドの場合は独自の空間は持ちません。そのスレッドを生成した親にあたるプロセスのプロセス空間内で動きます。そのためアドレス空間生成にかかるオーバーヘッドは無く、そういう面で軽量プロセスと言われてきました。

 スレッドの概念に該当するものは汎用機では30年程前からあり(タスク自体はもっと前からですが、アドレス空間との絡みからいえばMVS(多重仮想記憶)になったS/370以降となるので約30年です)、タスクがこれにあたります(厳密には汎用機の場合はタスクもしくはSRBになるんですが、まあいいでしょう)。
 そしてプロセス、プロセス空間にあたるのがジョブ、ジョブ空間です(ジョブ空間も厳密にはSTCジョブやMOUNTジョブ空間、TSOユーザー空間、SUBMITジョブ空間の三つに分かれますが、まとめてジョブ空間とします)。一つのジョブ空間には一つの親にあたるユーザータスクがあり、そこから子タスクを生成していきます。やはりジョブ生成に比べると新規アドレス空間の生成が無い分、オーバーヘッドはかかりません。

 JavaはJavaVM(仮想マシン)上で稼動し、それ自体がある程度OSの機能も包含しています。JavaVMはそれが動くホストOSの1プロセスとして動き、その内部でJavaのスレッドが複数動く、という仕組みです。


 インスタンス変数は同一サーブレットのスレッド間で共有領域として扱われる、ということです。そのためブラウザ1側のサーブレットスレッドが設定したインスタンス変数を、後から動いたブラウザ2側のサーブレットスレッドが上書きしてしまう。ブラウザ1側はブラウザ2側が後から設定した内容をもとに動作してしまう、ということです。

 またカウンタ情報も変でしたね。
 変というのは、ctst_svltを開いて最初に実行ボタンを押したときはカウントは1から始まったが、2回目以降に実行ボタンを押したときは、さっきの続きとしてカウントされてしまう、ということです。

 もう一度ctst_svlt.javaのソースを見て欲しいのですが、変更する文字列データであるインスタンス変数、wc_datは doGet の中で必ず初期化されます。しかしカウンタであるインスタンス変数、wc_cntは初期化していません(初期化の部分が注釈になっています)。
 サーブレットのクラスは、クライアントからの要求によってメモリ中にロードされてから処理を行います。処理( doGet メソッド)が終わっても消されることはなく、メモリ中に残ったままです。同じサーブレットに対する要求がまたあったときはその都度メモリ中にロードするのではなく、既にメモリ中にあるクラスが使いまわされるのです。
 もちろん各要求毎にスレッドが生成されてインスタンス化されるので、各インスタンス内のローカル変数領域はスレッド毎に別のものになります。

 ・3番目の実験の実行結果とそうなる理由

 (危険その1から派生)の実験の実行結果は概ね次のようになると思います(ブラウザ1側で二つ目のデータ表示がされた段階でブラウザ2の実行ボタンを押した場合)。
ブラウザ1 ブラウザ2

インスタンス変数テスト

ID:  文字:  色: Blue Fuchsia



Before: :0:ID=A
ctst_svlt:1:ID=A
ctst_svlt:2:ID=A
ctst_cls:1:ID=A Start at 15:45:49.453
ctst_cls:2:ID=A Start at 15:45:49.453
ctst_cls:3:ID=A Start at 15:45:49.453











 
インスタンス変数テスト

ID:  文字:  色: Blue Fuchsia



Before: :2:ID=B
ctst_svlt:3:ID=B
ctst_svlt:4:ID=A
ctst_svlt:5:ID=B


ctst_svlt:6:ID=B


ctst_cls:1:ID=B Start at 15:45:53.769
ctst_cls:2:ID=B Start at 15:45:53.769


After: :6:ID=A
ctst_cls:3:ID=B Start at 15:45:53.769


After: :6:ID=B

 今度はカウンタの値と結合している文字の数は画面ごとに並んでいますが(ctst_svltで表示している分)、本来ブラウザ1に表示されるべきID=Aのデータも途中からブラウザ2側に表示されてしまっています。
 原因は先ほどと同じでインスタンス変数は大域(グローバル)変数になっているためです。ただ今回はソースを修正して、クライアントに応答を返すときに使用する PrintWriter 型変数もインスタンス変数として定義しました。

 最初にブラウザ1側のサーブレットスレッドが、応答メッセージの出力先としてブラウザ1の情報を設定します。しかし途中でブラウザ2側のサーブレットスレッドが出力先情報をブラウザ2に設定しなおしてしまいます。そのためブラウザ1側サーブレットスレッドは、ブラウザ2向けに設定された PrintWriter 型変数を使って出力要求を行うため、結果の応答メッセージは途中からブラウザ2に出力されるようになってしまったのです。

 たぶんこの一連の実験は1台のパソコンで、ブラウザを二つ起動して行ったと思います。しかし最後の実験は応答メッセージが全く別のクライアントに送られてしまうこともありえる、ということを意味しています。


 以上のようにサーブレットクラスは他のクラスと動作が異なるので、特にインスタンス変数を使うときは注意が必要、ということです。
 なおJSPもAPサーバーでサーブレットに変換されるので同じ問題が起こりえます。
 JSPはHTML上で、 <%%> タグで囲んだ中にJavaプログラムを記述しますが、Java開始タグを <%! として変数定義部分だけを<%!%> タグで囲んでしまうと、その変数はインスタンス変数として定義するサーブレットに変換されます。実際に試してみて、(TOMCATの場合)%CATALINA_HOME%\work\Standalone\localhost\TestAp下にある、変換結果のサーブレットソースを確認してみてください。

 ◇解決方法

 この問題の解決方法として大きく分けて二通り、細かく分けて三通りの方法を説明します。

 ・シングルスレッドにする

 サーブレットは複数のクライアントからの要求を効率よくこなすためマルチスレッドで動いています。複数クライアントからの要求を、同時に並行して処理してしまうのです。
 一つ目の方法は、これを止めてしまうというものです。

 サーブレットのクラス定義部分は

import java.lang.Thread;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
// ユーザーパッケージのインポート
import yok.ctst_cls;

public class ctst_svlt extends HttpServlet
  {

となっていました。この部分を

import java.lang.Thread;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;  // このインポートを追加
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
// ユーザーパッケージのインポート
import yok.ctst_cls;

public class ctst_svlt extends HttpServlet
                       implements SingleThreadModel  // これも追加
  {

というように SingleThreadModel というクラスをインポートし、クラス宣言でこのクラスを implements (実装)するだけです。

 こうするとAPサーバーがTOMCAT4の場合は同一サーブレットに対する新規要求は、別サーブレットのように処理されます。二つのブラウザを起動してctst_svltを動かしたときも、両方で平行して(一つのブラウザだけで動かしたときと同じように)動きます。

 ・排他制御を行う

 大きく分けたときの二つ目の方法は自分で(プログラム内で)排他制御を行う、というものです。これは排他制御を行う範囲によってさらに二通りに分けられます。

排他制御その1

 最初の方法はインスタンス変数を使うメソッド全体に対して排他制御を行う、というものです。メソッド宣言時に synchronized を加えます。インスタンス変数を使用している(変更を加える)メソッドに対して付け加えればいいので、ctst_svlt.javaであれば doGet メソッドに対してこれを行えばよいでしょう。
 こうするとそのメソッドに対してロックが取得され、そのメソッドが終了するときにロックが解除されます。あるスレッドでロックが取得されるとそれが解除されるまでは、他のスレッドでそのメソッドを使用(実行)しようとしても排他制御により待ちになります。
 doGet の宣言部分を

    public void doGet (HttpServletRequest p_request,
                       HttpServletResponse p_response)
      throws IOException, ServletException

から

    public synchronized void doGet (HttpServletRequest p_request,
                                    HttpServletResponse p_response)
      throws IOException, ServletException

に変えてください。

排他制御その2

 その1ではメソッド全体を排他制御の対象にしましたが、もう少し範囲を狭めて排他制御を行うこともできます。こちらのときには synchronized 文を使い、ブロックにより排他制御を行う範囲を指定します。
 書き方は
 synchronized (参照データ型)
  {
   // このブロック内の処理が排他制御される
  }
となります。
 今動いている自分自身を指定して、
 synchronized (this)
とすればよいでしょう。なお ( ) の中は参照データ型なので、特定のデータを対象に排他制御することも可能です。

 ctst_svlt.javaの doGet メソッドの「ボタンが押されたときの処理」からctst_cls.updtを呼び出す前までの部分を、

        // 実行ボタンが押されたときだけ処理をする
        if (p_request.getParameter("SUB") == null)
          {}
        else
          {
            // 実行開始時間取得
            w_dfmt = new SimpleDateFormat("HH時mm分ss秒SSS");
            w_gclndr = new GregorianCalendar();

            synchronized (this)    // この2行追加
              {                    //
//            wc_cnt = 0;
                w_id = "ID=" + p_request.getParameter("ID");
                wc_dat = " ";
                wc_chr = p_request.getParameter("CHR");
                wc_chclr = p_request.getParameter("CLR");
                // テスト用クラスのインスタンス化
                w_ctst_cls = new ctst_cls(w_out,
                                          w_id +
                                          " Start at:" +
                                          w_dfmt.format(w_gclndr.getTime()),
                                          wc_chr, wc_chclr);

                w_out.println("<br />Before:" +
                              wc_dat + ":" + wc_cnt + ":" + w_id);

                try
                  {
                    // 3秒ごとに3回、インスタンス変数の文字列とカウンタを変更する
                    for (w_lpcnt = 1; w_lpcnt <= 3; w_lpcnt++)
                      {
                        wc_dat = wc_dat +
                                 "<font color=\"" + wc_chclr + "\">" + wc_chr +
                                 "</font>";
                        wc_cnt = wc_cnt + 1;
                        w_out.println("<br />ctst_svlt : " +
                                      wc_cnt + ":" + w_id + wc_dat);
                        w_out.flush();
                        Thread.sleep(3 * 1000);
                      }
                  }
                catch(Exception wl_excp)
                  {
                    w_out.print("<br />エラーが起きました <br />"
                             + wl_excp.getMessage());
                  }
              }                    // この1行追加

に変えてください。
 そうしてブラウザを二つ起動して実験し、「排他制御その1」との動作の違いを確認してください。ブラウザ2側がいつまで待機して、どの時点で動き始めるかが違うはずです。


 システム基盤を担当していたような人間にとっては、排他制御の粒度(排他制御の範囲や対象)はなるべく小さいほうが好みにあいます。
 しかし粒度を小さくするとそれだけ設計が厄介になってきます。ここでいう設計とは1プログラムの設計ではなく、プログラム間の設計、システム処理方式レベルの設計です。これを間違えて排他制御に失敗すると、思わぬ動きをしたりデータの整合性が狂うからです。
 細かい単位の排他制御が必要なのはシステム基盤系のプログラムだけであり、業務系のプログラムでは SingleThreadModel にしたり、メソッドレベルの synchronized で十分だと思います(むしろそのような設計にすべきでしょう)。

 ただし SingleThreadModel の場合は全ての場合に完全ではなく、同時に synchronized を使わないといけない場合があります。次はそれについて触れておきます。

 ◇共有変数

 それが共有変数というものです(超入門編としては本当はあえて教えないほうがいいのかもしれませんが、危険を知らせるという意味で教えておきます)。正確にはクラス変数というもので、ソースの中で定義する場所はインスタンス変数と同じところです。
 インスタンス変数のときは private データ型 変数名; と定義すると説明しました。クラス変数の場合はこれに static という修飾子を付けて private static データ型 変数名; と定義します。

 インスタンス変数はそれを定義したクラス内で有効な(共有される)変数として説明しました。通常はクラスから生成されたインスタンス毎に変数の領域が確保されます。ただしサーブレットクラスの場合はこれまで説明してきたように、何もしなければそのサーブレットクラスから生成されたインスタンス間でも共有されてしまいます。そのサーブレットクラスを SingleThreadModel として実装したときのみ、個々の生成されたインスタンス個別に領域が確保されました。

 これに対してクラス変数にすると常に、それを定義したクラスに一つだけ領域が確保されます。そのクラスからいくつインスタンスを生成しようとも、常にクラスに一つだけある領域を共有する形になるのです。これはそのクラスが通常のクラスか、サーブレットクラスかに関わり無くこうなります。
  SingleThreadModel というのはある意味でサーブレットクラスと通常のクラスの、インスタンス変数に関する動作の違いを無くしてくれます。しかしクラス変数だとこれらにそもそもの違いがありません。そのため SingleThreadModel を実装しただけではだめなのです。

 ctst_svlt.javaとctst_cls.javaの変数定義で、いくつかのインスタンス変数をクラス変数に変えたctst_svlt2ctst_cls2というサンプルを用意しました。画面表示で先ほどのサンプルと違う点は表示に表を使い、左側の列にインスタンス変数の内容、右側の列にクラス変数の内容を表示するようにしたところです。
 またこれは SingleThreadModel を実装したものです。先ほどと同様、排他制御その1排他制御その2の変更に変えて動作を確認してみてください。

 ■パッケージの配布(JAR)

 ここではJava言語そのものからは少し外れて、Javaユーティリティの一つであるJAR(Java ARchive)について説明します。

 特にパッケージの場合はそれを使う側のJavaソースをコンパイルするときにも、インポートするクラスのクラス( .class)ファイルが必要になります。クラスファイルが3つや4つならいいんですが、多くなってくると開発者への配布が面倒になってきます(一人だけ古いバージョンのクラスファイルを使っていたり)。

 このコンパイル結果のクラスをまとめるためにJavaにはJARコマンドがあり、JARファイルを作成することが出来ます。
 JARファイルは拡張子が .jarとなっているファイルで、ZIP形式でクラスファイルをまとめて圧縮したものです。単にクラスファイルだけではなくパッケージのパス情報も一緒に入ります。パッケージ化するときはパッケージ名の構成に対応したディレクトリ構成下にクラスファイルを置く、と説明しましたが、そのパス情報のことです。

 ◇使い方

 JARコマンドとJARファイルには他にも機能があるのですが、ここでは基本的な使い方、JARファイルの作成とそこからの展開についてのみ説明します。

 WinNT、Win2000系の場合ですが、JARコマンドは名前のとおりコマンドベースですので、コマンドプロンプトの画面で実行します。
 「スタート」メニューから「ファイル名を指定して実行」を選択します。
 実行するファイル名を聞いてくるので、「cmd」と入力してOKボタンを押してください。そうするとコマンドプロンプトの画面が表示されます。

 まずはここで「jar」と入力し、Enterを押しましょう。そうするとJARコマンドのパラメータ説明などの使い方が表示されます。これが出てこない場合は環境設定でPATHを設定していないので、「サーブレット編1」の「準備」を参照してください(これが出来ていないとJAR以前にコンパイルが出来ない気がしますが)。

 以下の説明ではJavaソースを置く基準ディレクトリは、D:\WORK\Java_Srcであることを前提にします。この部分は各自の環境で読み替えてください。

 ・使用例

 あるディレクトリ下のパッケージをまとめてJARファイルを作成する方法について説明します。

 まずディレクトリを基準ディレクトリに移動します。

Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.

C:\>D:

D:\>cd WORK\Java_Src

D:\WORK\Java_Src>_

 次にyokパッケージ下の全クラスファイルを圧縮し、新規にyok.jarファイルを作成します。


D:\WORK\Java_Src>jar cvf yok.jar yok\*.class
マニフェストが追加されました。
yok/ctst_cls.class を追加中です。(入 = 1290) (出 = 785)(39% 収縮されました)
yok/ctst_cls2.class を追加中です。(入 = 1540) (出 = 911)(40% 収縮されました)
yok/Tools.class を追加中です。(入 = 1584) (出 = 940)(40% 収縮されました)

D:\WORK\Java_Src>_

 この例ではクラスファイルだけをまとめるため、入力元はyok\*.classという指定にしました。
 そうではなく全ファイルをまとめてしまうのならディレクトリ名のみ、この場合yokだけを指定してください。こうしたときには指定ディレクトリ以下に、さらに階層のディレクトリがある場合も再帰的に検索されます。再帰的にディレクトリを検索し、かつクラスファイルだけ、という指定は出来ないようです。

 作成済みのJARファイルの内容を確認する場合は次のようにします。


D:\WORK\Java_Src>jar tvf yok.jar
     0 Wed Jan 22 11:37:00 JST 2003 META-INF/
    68 Wed Jan 22 11:37:00 JST 2003 META-INF/MANIFEST.MF
  1290 Fri Jan 17 10:52:54 JST 2003 yok/ctst_cls.class
  1540 Mon Jan 20 15:17:56 JST 2003 yok/ctst_cls2.class
  1584 Thu Dec 26 11:07:34 JST 2002 yok/Tools.class

D:\WORK\Java_Src>_

 これでyokというパッケージに対応したディレクトリ構造も取り込んだ状態で、yok下のクラスファイルが圧縮されていることが分かります。
 この状態であればyok.jarファイルを配布して、配布先のJavaソースを置く基準ディレクトリにこのファイルを置いておけば、基準ディレクトリ以下にパッケージ名対応のディレクトリを作成したり個別のクラスファイルを配布する必要はありません。

 実行環境であるAPサーバーのTOMCATへの配布については、「TOMCAT インストール・設定」または「TOMCAT5.5 インストール・設定」を参照してください。この中の「アプリ毎の環境設定」、「個別サーブレット毎の設定」が参考になると思います。

 ◇コマンドの説明

 JARコマンドのマニュアルは、http://java.sun.com/j2se/1.4/ja/docs/ja/tooldocs/tools.htmlからjarの[Win32]を選択してください。
 ここでは例で使ったものについて、基本的な部分だけ説明しておきます。

 ・形式

 JARコマンドの形式は次のとおりです。
jar オプション JARファイル名 [入力ファイル]

 ・説明

オプション
 c JARファイルを新規に作成する。既存のものがあるときは、削除して再作成のと同じ内容になる。
 u 既存のJARファイルの内容を更新する。
 x JARファイル名の内容を全て、または指定指定したファイルを展開する
 v 詳細メッセージを標準出力に出力する。
 t JARファイルの内容を一覧表示する
 f 処理対象のJARファイル名を指定する

JARファイル名
 処理対象のJARファイル名を指定する。オプションが cu のときはここに出力され、 t のときはこの内容が一覧表示される。

入力ファイル
 オプションが t のときは不必要。

 オプションが cu のときは入力元になるファイルかディレクトリを指定する。ディレクトリ名を指定したとき、そのディレクトリ以下が再帰的に検索される。
 入力するファイル、ディレクトリが複数あるときは、空白で区切ることによって複数指定できる。

 オプションが x のときは指定しないときはJARファイル名で指定したJARファイルの内容が全て展開される。
 ファイル名を指定する場合、JARファイル名で指定したJARファイル内のファイル名を指定し、このファイルだけを展開する。JARファイル内にディレクトリ構造付きで圧縮されているとき、そのディレクトリ名まで含めて指定すること。




このページの先頭  Java編先頭  サーバーJava入門先頭

Copyright © 2003 YOK. All Rights Reserved.