Java 重複リソースの取得 ClassLoader::getResources

この記事では、
ClassLoader::gerResourceと、
ClassLoader::gerResourcesの挙動について記載します。

同名リソースが複数ある場合、さきに見つかったリソースが取得される。

ClassLoader::gerResource は、
クラスPATHの指定順に従ってファイル検索をしてくるため、
同名リソースが存在する場合は、
先にみつかったリソースが優先され、
クラスPATHの後ろの方にあたるリソースは取得されません。

def classLoader = getClass().getClassLoader()
def url = classLoader.getResource("sample.txt")

println new File(url.toURI()).getText()
//==> This resource is in main.

同名リソースがあると、
先にみつかったリソースが取得される。

リソースの重複とは

リソースの重複とはどういう状況か?
Javaはソースセットという単位でビルド単位を分割することができますので、
mainとtest、それぞれにresourcesディレクトリを
作成してリソースファイルを配置することができます。

  • src/main/resources/sample.txt
  • src/test/resources/sample.txt

のように同名リソースで重複が起こります。

ClassLoaderとは

リソース取得するには、クラスローダーを利用します。
classpathで指定したディレクトリ、jarファイルが保持している
リソースファイル、またはクラスを取得するためのクラスです。

System Class Loader

システムクラスローダー
またはアプリケーションクラスローダーと呼ばれるものがあります。
アプリケーションレベルのクラス専用のローダーになります。

Java起動時に、-classpath もしくは -cp
といったコマンドラインのオプションでクラスパスを指定します。
または、CLASSPATH環境変数で指定する事も可能。

AppClassLoaderの実装

System Class Loader は、
実際には AppClassLoaderというクラス実装になっています。
※基本的にはそうですが、SystemClassLoaderは差し替えることも可能なため、フレームワークによっては異なる場合もあります。

ClassLoader cl = getClass().getClassLoader();
//==> jdk.internal.loader.ClassLoaders$AppClassLoader@2c13da15

AppClassLoaderクラスの継承関係

ClassLoader <- SecureClassLoader <- BuiltinClassLoader <- AppClassLoader

このAppClassLoaderクラスの実装は、
ClassPathに指定したURL(パッケージPATH)を順番にみてリソースを検索しています。

AppClassLoaderは、BuiltinClassLoaderを継承しています。
BuiltinClassLoader がメンバ変数でもっているURLClassPathクラスが
指定したクラスパスを順番に保持している。

// the URL class path, or null if there is no class path
private final URLClassPath ucp;

getResourcesを代わりにつかう

同名リソースがある場合は、
先にみつかったものを優先で取得しますが、
ClassLoaderにはgetResourceメソッドのほかに、
getResourcesというメソッドが用意されています。

ClassLoader::getResoucesは、
マッチするリソースをすべて取得する事が可能です。

返り値は、Enumration<URL> で返してくるので、
Enumration::hasMoreElementsでチェックして、
Enumration::nextElementで取得して利用します。

ClassLoader::getResouces を利用した例

def classLoader = getClass().getClassLoader()

def enumerator = classLoader.getResources("sample.txt")
while (enumerator.hasMoreElements()) {
    def url = enumerator.nextElement()
    println new File(url.toURI()).getText()
}

//==> This resource is in test.
//==> This resource is in main.

ちなみに、ClassLoader::getResouceAsStream というのもありますが、
こちらはgetResourceメソッドはURLで返すのに対し、
ファイルの内容をInputStreamクラスで取得する用のものです。

まとめ

ClassLoader::getResourceは、ClassPath指定順に検索し、
パッケージ内でさきに見つかったリソースを取得する。

重複パッケージも含めて、全リソースを取得したい場合は、
ClassLoader::getResources を利用する。


ClassLoaderの種類について

コメントを残す