位置に依存しない方法でのリソースへのアクセス

Java 1.0 プラットフォーム用のコードでは、リソースへのアクセスに 2 つの機構を使っています。

Java 1.0 プラットフォームには、コードに依存しない方法でリソースを探す機構がありませんでした。つまり Java 1.0 プラットフォームでは、複数の http 接続によってネットワーク上からロードされたアプレットや、JAR ファイルを使用してロードされたアプレット、ロードされた Bean、CLASSPATH にインストールされた Bean、CLASSPATH にインストールされたライブラリなどから、リソースを見つける手段がありませんでした。ここでは、このような機構を提供する API について説明します。

国際化 API はこの API を基本オペレーションとして使用し、ResourceBundle を探します。詳細については、最新の国際化についてのドキュメントを参照してください。

リソース、名前、コンテキスト

リソースは String によって表されます。この String は空のこともありますが、基本的には「/」で区切られた一連の部分文字列で、それぞれが有効な Java プログラミング言語の識別子のあとに <shortName> または <shortName>.<extension> という形式の名前が続きます。shortName と extension は、どちらも有効な Java Letters と Numbers (JLS 3.8 節) から成ります。shortName のあとに任意指定のシーケンスが続く場合は、shortName との間が「/」で区切られます。

リソースの名前は Java の実装には依存しません。特に、区切り文字としては必ず「/」が使われます。ただし、リソースの内容が、ファイル、データベース、その他実際のリソースを含むオブジェクトにどのようにマップされるかについては、Java の実装が制御します。

リソース名の解釈は、ClassLoader のインスタンスと関連します。ClassLoader によって実装されるメソッドがこの解釈を行います。

システムリソース

システムリソースは、システムクラス (JLS の 20.14.5 節) に似ています。システムリソースとは、システムに組み込まれているリソース、またはホストの実装に組み込まれているリソースのことで、ローカルファイルシステムがその一例です。システムリソースにアクセスするには、特殊なメソッド (getSystemResource と getSystemResourceAsStream) を使用し、基本ホストの実装を実行します。

たとえば、一部の実装機能では、システムリソースを見つけるために CLASSPATH 内の項目を検索しなければならないことがあります。CLASSPATH 内の各ディレクトリ、zip ファイル、JAR ファイル内でリソースファイルを探し、見つかった場合はその InputStream か名前が返されます。リソースが見つからない場合は null が返されます。リソースは、クラスファイルをロードしたのと同じ CLASSPATH 項目にあるとは限りません。

システムリソース以外

特定の ClassLoader での getResource の実装方法は、ClassLoader の詳細によって異なります。AppletClassLoader の場合は次のようになります。

すべての ClassLoader は、クラスファイルを探すのと同じように、リソースをまずシステムリソースとして探します。このため、すべてのリソースをローカルに上書きすることが可能になります。リソース名には一意の名前を選択する必要があります (接頭辞として会社名やパッケージ名などを使用する)。

リソース名

各クラスは一般的に、リソース名を表す際に、クラスのパッケージの完全修飾名を使用し、すべての「.」を「/」に変換し、リソース名を <Name>.<ext> の形式で追加するという規則を使用します。この規則をサポートし、システムクラス (getClassLoader が これらのメソッドは ClassLoader 内の適切なメソッドを呼び出します。null を返すクラス) の詳細の処理を簡略化するために、Class クラスには 2 つの便利なメソッドが用意されています。

Class クラスのメソッドに渡されるリソース名は、先頭が「/」で始まるものがあります。これは「絶対」名を表します。先頭が「/」ではないリソース名は「相対」名です。

リソースを探す際、絶対名は、先頭の「/」が除去されただけの状態で、適切な ClassLoader のメソッドに渡されます。相対名は、前述の規則に従って修正されたあと、ClassLoader のメソッドに渡されます。

リソースの操作

getResource() メソッドはリソースの URL を返します。URL (およびその表現) は、実装方法および JVM* インスタンスに特有であり (つまり、あるランタイムインスタンスで取得された URL を別のインスタンスで使えないことがある)、実装方法の詳細によっても変わることがあります。このメソッドのプロトコルは、通常、リソースを読み込む ClassLoader に固有です。リソースが見つからない場合は、null が返されます。また、セキュリティの関係でリソースにアクセスできない場合も、null が返されます。

クライアントコードでリソースの内容を InputStream として読み込むには、その URL に openStream() メソッドを適用します。getResourceAsStream() を Class と ClassLoader に追加することは、通常は正しいこととされます。 getResourceAsStream() は、入出力例外がキャッチされて null の InputStream として返されることを除いて、getResource().openStream() とセマンティクスとしては同一です。

クライアントコードは、URL に対して getContent() メソッドを適用することによって、リソースの内容をオブジェクトとして要求することもできます。このメソッドは、リソース内にイメージデータが含まれている場合などに便利です。この場合、結果は Image オブジェクトではなく、awt.image.ImageProducer オブジェクトになります。

Class に追加された API

Class クラスの各メソッドの形式は次のとおりです。


class Class {

    /**
     * Find a resource with a given name.  Will return null if no
     * resource with this name is found.  The rules for searching a
     * resources associated with a given class are implemented by the
     * ClassLoader of the class.
     *
     * The Class methods delegate to ClassLoader methods, after applying
     * a naming convention: if the resource name starts with "/", it is used
     * as is.  Otherwise, the name of the package is prepended, after
     * converting "." to "/".
     *
     * @see java.lang.ClassLoader
     */
    public InputStream getResourceAsStream(String name) {
	name = resolveName(name);
	ClassLoader cl = getClassLoader();
	if (cl==null) {
	    // A system class.
	    return ClassLoader.getSystemResourceAsStream(name);
	}
	return cl.getResourceAsStream(name);
    }

    public java.net.URL getResource(String name) {
	name = resolveName(name);
	ClassLoader cl = getClassLoader();
	if (cl==null) {
	    // A system class.
	    return ClassLoader.getSystemResource(name);
	}
	return cl.getResource(name);
    }

    /**
     * Add a package name prefix if the name is not absolute
     * Remove leading "/" if name is absolute
     */
    private String resolveName(String name) {
	if (name == null) {
	    return name;
	}
	if (!name.startsWith("/")) {
	    Class c = this;
	    while (c.isArray()) {
		c = c.getComponentType();
	    }
	    String baseName = c.getName();
	    int index = baseName.lastIndexOf('.');
	    if (index != -1) {
		name = baseName.substring(0, index).replace('.', '/')
		    +"/"+name;
	    }
	} else {
	    name = name.substring(1);
	}
	return name;
    }



あまり一般的ではありませんが、2 つの異なるパッケージ内にある 2 つのクラスが、同じリソースを共有することも可能です。

ClassLoader に追加された API

リソースにアクセスするためのメソッドが、2 種類用意されています。このうちの一方は、リソースの InputStream を返します。もう一方は、URL を返します。InputStream を返す各メソッドは、比較的使いやすく、用途も多くあります。もう一方の URL を返す各メソッドを使うと、Image オブジェクトや AudioClip オブジェクトなど、さらに複雑な情報にアクセスすることができます。

リソースは、クラスと同じような方法で、ClassLoader を通して管理されます。ClassLoader はリソース名とその内容のマップ方法を制御します。またシステムクラスの場合と同じように、ClassLoader には、システムリソースにアクセスするためのメソッドも用意されています。Class クラスには、機能を ClassLoader クラスの各メソッドに任せるという便利なメソッドがあります。

多くの Java プログラムは、国際化 API を使用してこれらのメソッドに間接的にアクセスします。Class クラスのメソッドを介してアクセスするプログラムもあります。ClassLoader クラスのメソッドを、直接呼び出すことはほとんどありません。

ClassLoader のメソッドは、受け取った String をリソース名として使用します。絶対名と相対名との変換は行いません (Class のメソッドとは対照的)。名前の先頭には「/」を付ける必要はありません。


class ClassLoader {

    /**
     * A resource is some data (images, audio, text, etc) that wants to be
     * accessed by some class code in a way that is independent of the
     * location of the code.  Resources are found with cooperation of the
     * class loaders, since they are the only ones who know where the class
     * actually came from. 

* * System resources are those that are handled by the host implemenation * directly. For example, they may be located in the CLASSPATH.

* * The name of a resource is a "/"-separated sequence of identifiers. * The class Class provides convenience methods for accessing resources; * the methods implement a convention where the package name is prefixed * to the short name of the resource.

* * Resources can be accessed as an InputStream, or as a URL. * * @see Class */ /** * Get an InputStream on a given resource.. Will return null if no * resource with this name is found.

* * The resource name may be any system resource (e.g. follows CLASSPATH order) * @param name the name of the resource, to be used as is. * @return an InputStream on the resource, or null if not found. */ public static final InputStream getSystemResourceAsStream(String name) { ... this is equivalent to getSystemResource() call plus a openStream() } /** * Find a resource with a given name. The return is a URL to the resource * Doing a getContent() on the URL may return an ImageProducer, an AudioClip, or * an InputStream.

* * The resource name may be any system resource (e.g. follows CLASSPATH order) * @param name the name of the resource, to be used as is. * @return the URL on the resource, or null if not found. */ public static final java.net.URL getSystemResource(String name) { ... } /** */ /** * Get an InputStream on a given resource. Will return null if no * resource with this name is found.

* * The class loader can choose what to do to locate the resource. * @param name the name of the resource, to be used as is. * @return an InputStream on the resource, or null if not found. */ public InputStream getResourceAsStream(String name) { return null; } /** * Find a resource with a given name. The return is a URL to the resource. * Doing a getContent() on the URL may return an ImageProducer, an AudioClip, * or an InputStream.

* * The class loader can choose what to do to locate the resource. * @param name the name of the resource, to be used as is. * @return an InputStream on the resource, or null if not found. */ public java.net.URL getResource(String name) { return null; } }

クライアントコード

次に、クライアントコードの例を 2 つ示します。1 番目の例では「絶対リソース」名と従来の機構を使用して Class クラスのオブジェクトを取得しています。


package pkg;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

class Test {

    private static final String absName = "/pkg/mumble.baf";

    public static void test1() {
	Class c=null;
	try {
	    c = Class.forName("pkg.Test");
	} catch (Exception ex) {
	    // This should not happen.
	}
	InputStream s = c.getResourceAsStream(absName);
	// do something with it.
    }

    public void test2() {
	InputStream s = this.getClass().getResourceAsStream(absName);
	// do something with it.
    }


2 番目の例では「相対リソース」名と新しい機構を使用して Class クラスのオブジェクトを取得しています。この新しい機構は、コンパイル時に -experimental フラグを使用すると利用できます。


package pkg;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

class Test {

    private static final String relName = "mumble.baf";

    public static void test1() {
	InputStream s = Test.class.getResourceAsStream(relName);
	// do something with it.
    }

    public void test2() {
	InputStream s = Test.class.getResourceAsStream(relName);
	// do something with it.
    }


セキュリティの詳細

getResource() は、情報へのアクセスを提供するため、この機構の目的に沿った使用をサポートするセキュリティの規則が、適切に定義および構築されている必要があります。以下に、Java Development Kit の リリース 1.1.5 から指定および実装されている詳細を説明します。セマンティクスは、ClassLoader.getResource および ClassLoader.getSystemResource() の場合だけを説明し、前の項で定義した AsStream メソッドの説明に追加します。

セキュリティ上の配慮により、あるセキュリティコンテキストであるリソースへのアクセスが許可されていない場合は、getResource() メソッドは、あたかもそのリソースが存在しないかのように失敗する (null を返す) ようになっています。これは、存在性攻撃に対する配慮です。

すべてのクラスローダは、.class ファイルの内容へのアクセスは提供しません。これは、セキュリティとパフォーマンスの問題です。.class ファイル内の URL を取得できるかどうかは、以下に示す詳細によって異なります。

システム以外のクラスローダによって検索されるリソースに関する、セキュリティの問題または制限は指定されていません。AppletClassLoader は、あるソースの場所からロードされた情報への個々のアクセスまたは JAR ファイルによるグループでのアクセスを提供します。このため、AppletClassLoader は、getResource() を使って複数の URL を扱うときは、同じ checkConnect() を適用する必要があります。

システムのクラスローダは、CLASSPATH の情報へのアクセスを提供します。CLASSPATH には、ディレクトリまたは JAR ファイル、あるいはその両方があります。JAR ファイルは意図的に作成されるので、物事がより時に応じた方法で行われるディレクトリとは重要性が異なるといえます。特に、ディレクトリから情報を取得する場合は、JAR ファイルから取得する場合よりも厳密に行います。

リソースがディレクトリ内にある場合は、以下のようになります。

リソースが JAR ファイル内にある場合は、以下のようになります。

関連項目および既知のバグ

getResource インタフェースでは、地域対応されたリソースの位置を特定することはできません。地域対応されたリソースは、国際化によってサポートされます。

* この Web サイトで使用されている用語「Java 仮想マシン」または「JVM」は、Java プラットフォーム用の仮想マシンを表します。


Copyright © 1996-98 Sun Microsystems, Inc. All Rights Reserved. コメントの送付先: jdk-comments@java.sun.com  Sun Microsystems, Inc
Java ソフトウェア