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

概要

リソースとは、プログラムのコードの位置とは無関係な方法でプログラムがアクセスする必要のあるデータ(イメージ、オーディオ、テキストなど)です。Javaプログラムでは、リソースへのアクセスに2つのメカニズムを使用できます。アプレットの場合は、Applet.getCodeBase()を使用してアプレット・コードのベースURLを取得した後、そのベースURLを相対パスで展開し、Applet.getAudioClip(url)を使用するなどして必要なリソースをロードします。アプリケーションの場合は、System.getProperty("user.home")System.getProperty("java.home")などの「よく知られた場所」を使用し、「/lib/resource」を付加して、そのファイルを開きます。

クラスClassおよびClassLoaderのメソッドでは、リソースを特定するために、位置に依存しない方法を提供します。たとえば次のような場合のリソースを特定できます。

これらのメソッドでは、ローカライズされたリソースの位置を特定することはできません。ローカライズされたリソースは、国際化によってサポートされます。

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

リソースは文字列で特定され、この文字列はスラッシュ(/)で区切られた一連のサブ文字列と、末尾のリソース名で構成されます。各サブ文字列は、有効なJava識別子である必要があります。リソース名は、shortNameまたはshortName.extensionという形式です。shortNameextensionは、どちらもJava識別子である必要があります。

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

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

システム・リソース

システム・リソースとは、システムに組み込まれているリソース、またはホストの実装が保持するリソースのことで、ローカル・ファイル・システムがその一例です。プログラムがシステム・リソースにアクセスするには、ClassLoaderのメソッドであるgetSystemResourceおよびgetSystemResourceAsStreamを使用します。

たとえば、一部の実装では、システム・リソースを見つけるためにCLASSPATH内の項目を検索しなければならないことがあります。ClassLoaderメソッドによってCLASSPATH内のディレクトリ、ZIPファイル、またはJARファイルのそれぞれのエントリでリソース・ファイルが検索され、ファイルが見つかると、InputStreamまたはリソースの名前が返されます。ファイルが見つからなかった場合は、nullが返されます。リソースは、クラス・ファイルをロードした位置と同じCLASSPATH項目にあるとはかぎりません。

システム・リソース以外

クラス・ローダーでのgetResourceの実装方法は、ClassLoaderクラスの詳細によって異なります。たとえば、AppletClassLoaderの場合は次のようになります。

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

リソース名

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

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

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

java.lang.Classのメソッドの使用

Classクラスでは、リソースをロードするためのメソッドをいくつか実装しています。

getResource()メソッドはリソースのURLを返します。このURLおよびその表現は、実装およびJVMに固有です。つまり、ある実行時インスタンスで取得されたURLは、別の実行時インスタンスでは動作しない可能性があります。プロトコルは通常、リソースをロードするClassLoaderに特有です。リソースが存在しない場合や、セキュリティ上の理由で不可視である場合、メソッドはnullを返します。

クライアント・コードでリソースの内容をInputStreamとして読み取るには、そのURLにopenStream()メソッドを適用します。これはよく行われるため、getResourceAsStream()ClassClassLoaderに追加されています。getResourceAsStream()は入出力例外をキャッチしてnullのInputStreamを返しますが、それ以外はgetResourceAsStream()getResource().openStream()と同じです。

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

getResourceおよびgetResourceAsStreamメソッドは、指定された名前のリソースを検索します。これらのメソッドは、指定された名前のリソースが見つからないとnullを返します。指定されたクラスに関連するリソースを検索するための規則は、そのクラスのClassLoaderによって実装されます。Classのメソッドは、命名規則の適用後にClassLoaderメソッドに委譲されます。リソース名が「/」で始まる場合は、その名前がそのまま使用されます。そうでない場合は、すべてのピリオド(.)がスラッシュ(/)に変換されてから、パッケージ名が先頭に付加されます。

public InputStream getResourceAsStream(String name) {
  name = resolveName(name);
  ClassLoader cl = getClassLoader();
  if (cl==null) {
    return ClassLoader.getSystemResourceAsStream(name); // A system class.
  }
  return cl.getResourceAsStream(name);
}

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

resolveNameメソッドによって、名前が絶対パスでない場合はパッケージ名の接頭辞が追加され、絶対パスの場合は先頭の「/」が削除されます。一般的ではありませんが、同じリソースを共有するクラスを別々のパッケージに含めることもできます。

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;
}

java.lang.ClassLoaderのメソッドの使用

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

ClassLoaderは、クラスを管理するのと同様の方法で、リソースを管理します。ClassLoaderはリソース名とその内容のマップ方法を制御します。またシステム・クラスの場合と同じように、ClassLoaderシステム・リソースにアクセスするためのメソッドも提供します。Classクラスには、機能をClassLoaderクラスの各メソッドに任せる簡易メソッドがあります。

多くのJavaプログラムは、I18N (ローカリゼーション) APIを使用してこれらのメソッドに間接的にアクセスします。Classのメソッドを介してアクセスするプログラムもあります。ClassLoaderクラスのメソッドを、直接呼び出すことはほとんどありません。

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

システム・リソースは、ホスト実装によって直接処理されるリソースです。たとえば、CLASSPATHに配置されている場合があります。

リソースの名前は、一連の識別子を「/」で区切った形式です。Classクラスでは、リソースへアクセスする簡易メソッドを提供しています。これらのメソッドでは、パッケージ名をリソースのショート名に接頭辞として追加するための規則を実装しています。

リソースはInputStreamまたはURLとしてアクセスできます。

getSystemResourceAsStreamメソッドは、指定されたシステム・リソースに対するInputStreamを返します。そのリソースが見つからない場合は、nullを返します。リソース名は、任意のシステム・リソースです。

getSystemResourceメソッドは、指定された名前を持つシステム・リソースを検索します。このメソッドは、リソースへのURLを返します。リソースが見つからない場合はnullを返します。そのURLを使用してjava.net.URL.getContent()を呼び出すと、ImageProducerAudioClipInputStreamなどのオブジェクトが返されます。

getResourceAsStreamメソッドは、指定されたリソースに対するInputStreamを返します。そのリソースが見つからない場合は、nullを返します。

getResourceメソッドは、指定された名前を持つリソースを検索します。このメソッドは、リソースへのURLを返します。リソースが見つからない場合はnullを返します。そのURLを使用してjava.net.URL.getContent()を呼び出すと、ImageProducerAudioClipInputStreamなどのオブジェクトが返されます。

セキュリティ

getResource()は、情報へのアクセスを提供するため、セキュリティの規則が適切に定義および構築されている必要があります。セキュリティ上の配慮により、あるセキュリティ・コンテキストであるリソースへのアクセスが許可されていない場合はgetResource()メソッドは、あたかもそのリソースが存在しないかのように失敗する(nullを返す)ようになっています。これは、存在性攻撃に対する配慮です。

クラス・ローダーは、セキュリティおよびパフォーマンス上の理由から、.classファイルの内容へのアクセスは提供しません。.classファイルのURLを取得できるかどうかは、次に示す詳細によって異なります。

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

システムのClassLoaderは、CLASSPATHの情報へのアクセスを提供します。CLASSPATHには、ディレクトリやJARファイルがあります。JARファイルは意図的に作成されるので、意図せずに生成されるディレクトリとは重要性が異なるといえます。特に、ディレクトリから情報を取得する場合は、JARファイルから取得する場合よりも厳密に行います。

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

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

このセクションでは、クライアント・コードの例を提供します。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.
  }
}

この例では「相対リソース」名とメカニズムを使用して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.
  }

APIリファレンス


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.