じゃぁ、1つで済ませましょ (3) [プログラムからJARを使う: getResource, java.util.jarパッケージ]
2002-03-23
JAR(Java Archive)には、いろいろな機能がありますが、第3回目の今回は、jarツールやjarファイルからは、離れることにしましょう。jarファイルを用いて、javaのアプリケーションを1つのファイルに固める場合、jarファイルには、プログラムをコンパイルしてできたclassファイルだけではなく、イメージやサウンドなども格納することになります。これらのアプリケーションを構成するリソースには、プログラムからアクセスする必要が発生します。
- jarツール
- jarファイルとManifest、クラスパス
- プログラムからjarファイルを使う
- 入れ子のJARの取り扱い
以下では、jarファイルの中身にアクセスして、格納されているリソースをプログラムから使う、ということを考えてみましょう。
■リソースなら理想っす
前回、Javaのアプリケーションやアプレットを起動した際に、クラスをどのようにして探すか、という話をしました。自動的に探される範囲は、以下のようになっていました。
- ブートストラップクラス
- 拡張ディレクトリに格納されたjar
- 環境変数CLASSPATH、javaコマンドの-classpathオプション、jarのManifestファイルに記述されたClass-Path
これらに含まれている場合は、jarに格納されていることを意識せずにファイルを取り出すことができます。
| イメージなどファイルの位置 |
java.lang.ClassLoaderクラスのgetResourceメソッドで、ファイル名を指定して、java.net.URLクラスのインスタンスを取得 |
| イメージなどファイルのストリーム |
java.lang.ClassLoaderクラスのgetResourceAsStreamメソッドで、ファイル名を指定して取得 |
| importされていないパッケージのclass |
java.lang.ClassクラスのforNameメソッドで、クラスの名前を指定して取得 |
イメージやサウンドといったリソースのファイルは、URLクラスのインスタンスで位置を取得したい場合と、ストリームで中身を直に取り出したい場合があります。これらは、メソッド1つで実行することができます。これにより、jarを意識することなく、また、リソースのファイル名や、クラス名だけを知っていれば取得することができるようになっています。
MorePasteの1.1では、バージョンや版権などの情報を表示するための画像を、jarから取得する際に以下のように実行しています。全てのソースは、こちらです。
…
private final String imgfile = "morepaste/MPSplash.jpg";
private ImageIcon splash;
private void createUI() {
splash =
new ImageIcon( getClass().getClassLoader().getResource(
imgfile ) );
…
|
| SplashPluginクラス
(MorePaste 1.1) |
イメージの取得 |
ImageIconクラスは、コンストラクタにURLクラスのインスタンスを指定すると、そこからイメージのデータを読み出してくれます。URLを取得するために、自分自身をロードしたClassLoaderを取り出し、getResrouceメソッドを呼び出しています。
■じゃぁ、直接
jarファイルを意識して、その中からデータを取り出すには、どうすればいいのでしょうか。このようなjarを直接処理するプログラムは、それほど書くニーズも機会もないでしょう。しかし、WinZipのようなプログラムを自分で作るんだ、という場合や、インストーラを自分で書くんだ、というような場合には使うことになるかもしれません。
jarファイルを扱う場合には、java.util.jarパッケージのクラスを使用します。jarファイルをFileInputStreamで読み込んでも、jarに格納された目的のファイルを読み出すことはできません。これを実現しようと思うと、目的のファイルの開始と終了のバイトを調べて、とか、取り出したデータの圧縮を解凍して、といった処理を自分で書かなくてはならなくなります。このような機能が全て含まれているのが、java.util.jarパッケージです。java.util.jarのクラスの多くは、java.util.zipのクラスを継承しており、jarがzipと同じ形式になっていることが伺えます。
jarファイルに格納されたファイルのそれぞれの情報は、JarEntryクラスに格納されています。JarFileクラスを用いると、getEntryメソッドで目的のファイルのJarEntryを取得することができます。また、entriesメソッドを用いれば、含まれている全てのファイルのJarEntryを得ることができます。getInputStreamメソッドで、JarEntryを引数に渡すと目的のファイルのストリームを取得することができます。
MorePasteの1.0.1を作った時点では、getResourceメソッドを知らなかったため、JarFileクラスから上記のような手順で、バージョンや版権などの情報を表示するための画像を取り出していました。プログラムは、以下のようになっています。全てのソースは、こちらです。
…
private final String dir = "morepaste";
private final String imgfile = "MPSplash.jpg";
private final String jarfile = "MorePaste.jar";
private ImageIcon splash;
public SplashPanel() {
// jarファイルからイメージデータを取り出す
String sep
= "/";
String path
= dir + sep + imgfile;
File theFile
= new File( jarfile );
if(
theFile.exists() ) {
try{
JarFile
jFile = new JarFile( theFile
);
InputStream
in = jFile.getInputStream( jFile.getEntry(
path ) );
ByteArrayOutputStream
baos = new ByteArrayOutputStream();
…
byte[]
pix = baos.toByteArray();
Image
img = Toolkit.getDefaultToolkit().createImage( pix );
splash
= new ImageIcon( img );
}
catch(
IOException e ){}
}
// イメージファイルからイメージを読み出す
else {
sep
= System.getProperty( "file.separator" );
path
= "." + sep + dir + sep + imgfile;
theFile
= new File( path );
if(
theFile.exists() )
splash
= new ImageIcon( path );
}
…
}
…
|
| SplashPanelクラス
(MorePaste 1.0.1) |
イメージの取得 |
jarファイルが存在していれば、画像ファイルのJarEntryを取得して、画像を読み込むためのInputStreamを取り出しています。jarが存在しなかった場合は、画像ファイルを直に読み出すように記述しています。これは、jarファイルで起動されていない場合に備えるためで、プログラム作成時など、jarに固める前の段階でデバッグ中のためにコマンドラインから起動している場合に必要になります。
jarファイルの存在を意識した処理をすると、このようにjarが存在しない場合の処理も記述する必要が出ています。また、このようなプログラムでは、jarのファイル名を変えただけで正常に動作しなくなります。MorePasteの1.0では、MorePaste.jarというファイル名が変更できなかったのですが、これはgetResourceを使用していなかったためなのです。
■マニフェストなどなど
jarファイルに格納されたファイルは、以上のような方法でアクセスすることができます。jarの中身があらかじめわかっている場合には、それでいいのですが、わかっていない場合にはアクセスする前に、jarそのものについての情報を取得する必要があります。例えば、プラグイン的なアーキテクチャを採用して、個々のプラグインをjarで提供するような場合や、JavaBeanを呼び出すプログラムを作成するような場合などは、そのjarに何が含まれていて、最初にどのクラスからインスタンスを作ればいいのか、といったことを調べる必要があります。
jarそのものについての情報や、クラスのインスタンスを作るために必要となる情報は、Manifestファイルに属性として記述する、ということを前回お話ししました。java.util.jarパッケージには、Manifestファイルをハンドリングするクラスも用意されています。前述した、JarFileやJarEntryと合わせて、jarの中身を調べるためのクラスを以下にまとめておきます。
| Manifest |
Manifestファイルをラップし、記述された項目の一覧などを取得するためのメソッドを持つ |
| Attributes |
Manifestファイルに記述された項目をkeyとvalueを一組にして保持する |
| Attributes.Name |
Manifestのあらかじめ定義されたkey値をフィールドとして持つ |
| JarFile |
jarファイルをラップし、jarに格納されているファイルの一覧や、個々のファイルのInputStream、Manifestなどを取得するためのメソッドを持つ |
| JarEntry |
jarに格納されている個々のファイルに関する情報を保持する |
これらのクラスを用いて、jarの中身を調べるプログラムを作ってみました。全てのソースは、こちらです。
Attributesについては、jarファイル固有のものの場合と、格納されているファイルに関するものとで、取り出し方に違いがあります。Main-Classといった前者の属性は、ManifestクラスのgetMainAttributesメソッドで取り出します。後者の属性は、ManifestクラスのgetAttributesメソッドに、引数としてそれぞれのファイル名を与えるか、JarEntryのgetAttributesメソッドを呼び出すことで取得できます。ファイル名は、JarEntryのtoStringメソッドで取得することができます。
|
D:\Develop\test\jar>java jarscape MorePaste.jar
MorePaste.jar: Manifest Main Attributes ***********
key: Main-Class, value: MPMain
key: Created-By, value: 1.3.1_02 (Sun Microsystems Inc.)
key: Manifest-Version, value: 1.0
MorePaste.jar: Manifest File Attributes ***********
File Entry: morepaste/format/BoxFill.class
key: Java-Bean, value: True
File Entry: morepaste/format/PasteQuote.class
key: Java-Bean, value: True
File Entry: morepaste/format/Tab2Space.class
key: Java-Bean, value: True
MorePaste.jar: File entries ***********************
META-INF/
META-INF/MANIFEST.MF
MPMain.class
morepaste/BoxFillPanel$1.class
morepaste/BoxFillPanel$2.class
morepaste/BoxFillPanel.class
morepaste/ComboPanel$AddAction.class
morepaste/ComboPanel$Popupper.class
…
|
| |
jarファイルの情報を見る |
ところで、ManifestのgetAttributesメソッドを呼び出す際には、引数として渡すファイル名をStringで指定します。Stringではなくて、JarEntryを渡せると自然だと思うのですが、いかがでしょうか。JavaSoftはそのように考えなかったのか、疑問を感じます。
■前回の訂正
前回、jarファイルを使用する場合、CLASSPATH環境変数や-classpathオプションが無視されることについて、ドキュメントには明確に記述されていない、という旨のことを書きました。よく見てみたら、「-jarオプションは、-classpathなど他の全ての値を上書きして、-jarオプションで指定されたアーカイブに由来するクラスだけを使用する」と書いてありました。この場で訂正したいと思います。
由来するというのは、適切な日本語が思いつかなかったので、勝手に付けた訳なのですが、要はjarに直接含まれるクラスとManifestのClass-Path属性に記述されたjarに含まれるクラス、ということですね。
プログラムからjarを扱うということで長々と書いてきましたが、環境変数のCLASSPATHや-classpathオプション、jarのManifestに記述されたClass-Pathに含まれるファイルにアクセスする場合は、全くjarファイルの存在を意識する必要がないようになっています。通常は、この範囲に収まり、jarを直接ハンドリングすることはないでしょう。
なのですが、次回はさらに突っ込んで、入れ子のjarを扱うことを考えてみることにしましょう。Streamクラスを使うことで、展開することなしにjarに格納されたjarにアクセスすることができます。jarを扱うということ以上に、Streamクラスの使い方のおもしろいサンプルになると思います。

|