読者です 読者をやめる 読者になる 読者になる

kagamihogeの日記

kagamihogeの日記です。

Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 6. Resourcesをテキトーに訳した

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。Spring Framework Reference Documentation 4.1.6のIII. Core Technologies 5.12から5.16までをテキトーに訳した - kagamihogeの日記 の続き。

6. Resources

6.1 Introduction

Java標準のjava.net.URLクラスと各種のURLプレフィクスハンドラは不幸なことに低レベルリソースへのアクセスには十分とは言い難いです。たとえば、クラスパスやServletContext相対パスから取得する必要のあるリソースにアクセスするための標準URL実装がありません。特殊なURLプレフィクス(http:などのプレフィクス用の既存ハンドラと同じような)用の新規ハンドラを登録可能ですが、一般的には極めて複雑で、URLインタフェースは望ましい機能を依然として欠いており、たとえば指しているリソースの存在をチェックするメソッドなどです。

6.2 The Resource interface

SpringのResourceインタフェースは低レベルリソースアクセスを抽象化するインタフェースです。

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

Resourceインタフェースで最も重要なメソッドは以下のとおりです。

  • getInputStream(): リソースの位置指定とオープンを行い、リソース読み込みのためのInputStreamを返します。このメソッドは呼び出すごとに新しいInputStreamを返すことが期待されます。ストリームのクローズは呼び出し側の責任です。
  • exists(): このリソースが実際に物理的な形式で存在するかどうかを示すbooleanを返します。
  • isOpen(): このリソースがオープン中のストリームのハンドルを表現しているかどうかを示すbooleanを返します。もしtrueの場合、InputStream複数回の読み込みは出来ず、一度だけ読み込み、それからリソースリークを避けるためにクローズします*1。すべての一般的なリソース実装でfalseになると、InputStreamResourceでは例外が発生します。
  • getDescription(): このリソースの説明を返し、これはリソース使用時のエラー出力に使われます。大抵の場合、完全修飾ファイル名かリソースの実URLとなります。

他のメソッドとしてはリソースを表す実URLFileオブジェクトを取得するものがあります(ただし実装に互換性がありその機能をサポートしている場合に限る)。

ResourceはSpring内部で広範に使われており、リソースを必要とする多数のメソッドの引数型になります。Spring APIのいくつか(各種のApplicationContext実装のコンストラクタなど)では、そのコンテキスト実装に適切なResourceを生成するのに使うための単純な形式のStringを取ります。もしくは、Stringのパスに特殊なプレフィクスをつけることで、呼び出し側が特定のResouce実装を生成して使うように指定する事も出来ます。

ResourceインタフェースはSpringで広く使われており*2、リソースにアクセスするにはリソース自体が持つ汎用のユーティリティクラスを使うのが便利であり、たとえSpringの各種機能を使わないとしても有用です。その場合コードとSpringが結合してしまいますが、わずかなユーティリティクラスのみに結合するのみで、これらのユーティリティはURLの代わりとなる機能を提供し、この用途での他のライブラリと同等の機能を有します。

重要な注意点として、Resourceはラッブするクラスが十分な機能を持つ場合にはその機能を置換しません。たとえば、UrlResourceをURLをラップし、ラップするURLで十分な場合にはそちらを使います。

6.3 Built-in Resource implementations

Springには独創的なResource実装が多数あります*3

6.3.1 UrlResource

UrlResourcejava.net.URLをラップし、URL経由でアクセス可能な、例えばファイル・HTTPターゲット・FTPターゲットなど、のオブジェクトにアクセスするために使います。すべてのURLは標準化されたString表現を持ち、適切なプレフィクスをURLの区別に使います。ファイルシステムパスにアクセスするためのfile:、HTTPプロトコル経由でリソースアクセスをするhttp:FTP経由でリソースアクセスをするftp:、があります。

UrlResourceコンストラクタを使用してJavaコード内で明示的に生成しますが、パス表現のString引数を取るAPIメソッドを呼ぶ時、暗黙的に生成される場合もあります。後者の場合では、生成するResource型の決定は最終的にはJavaBeansPropertyEditorが行います。パス文字列がclasspath:などwell-knownプレフィクスを持つ場合には、そのプレフィクス専用のResourceを適切に生成します。ただし、プレフィクスが不明な場合には、標準的なURL文字列であると仮定し、UrlResourceを生成します。

6.3.2 ClassPathResource

このクラスは、クラスパスから取得すべきリソースを表現します。このクラスは、スレッドコンテキストクラスローダーや与えられたクラスローダー、もしくは、リソースロード用に与えられるクラス、のどちらかを使います。

このResource実装は、クラスパスがファイルシステム内にある場合、java.io.File形式での解決をサポートしますが、jar内にクラスパスが無いとファイルシステムを(サーブレットエンジンやその他の環境が)展開できません*4。各種のResource実装はどれもjava.net.URLとしての解決をサポートします。

ClassPathResourceコンストラクタを使用して明示的に生成しますが、パス表現のString引数を取るAPIメソッドを呼ぶ時、暗黙的に生成される場合もあります。後者の場合、JavaBeans PropertyEditorはパス文字列の特殊なプレフィクスclasspath:を認識し、その場合にはClassPathResourceを生成します。

6.3.3 FileSystemResource

このResource実装はjava.io.Fileを扱うものです。FileURLとしての解決をサポートします。

6.3.4 ServletContextResource

このResource実装はServletContextを表現するもので、webアプリケーションルートディレクトリ内の相対パスを解釈します。

このクラスはストリームとURLアクセスをサポートしますが、webアプリケーションアーカイブの展開時とリソースがファイルシステム上に物理的に存在する場合、java.io.Fileアクセスのみ許可します。JARから直接アクセスされたりDBなど別の場所から、最終的にファイルシステム上に展開される場合、Servletコンテナに依存します*5

6.3.5 InputStreamResource

InputStreamを与える場合のResource実装です。このクラスは適切なResouce実装が無い場合にだけ使うべきです。具体的には、可能なら他のファイルベースのResouce実装かByteArrayResourceを使って下さい。

他のResouceとは対照的に、このクラスはオープン済みのリソースのディスクリプターです。よって、isOpen()trueを返します。ストリームを複数回読み込む必要がある場合や、どこか別の場所でリソースディスクリプターを保持し続ける必要がある場合には、使用しないでください。

6.3.6 ByteArrayResource

バイト配列を与える場合のResource実装です。与えられたバイト配列でByteArrayInputStreamを生成します。

使い捨てのInputStreamResourceを使わずに、任意のバイト配列からコンテンツをロードする場合に有用です。

6.4 The ResourceLoader

ResourceLoaderインタフェースは、Resourceインスタンスを返す(=ロードする)能力を持つオブジェクトが実装するものです。

public interface ResourceLoader {

    Resource getResource(String location);

}

アプリケーションコンテキストはResourceLoaderインタフェースを実装しているため、すべてのアプリケーションコンテキストはResouceインスタンスを取得可能です。

特定のアプリケーションコンテキストでgetResource()を呼び、引数で指定したロケーションパスが特定のプレフィクスを持たない場合、そこで使っているアプリケーションコンテキストが適当なResouce型を返します。たとえば、以下のコードをClassPathXmlApplicationContextインスタンスで実行したとします。

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

ここで返されるのはClassPathResourceです。同じメソッドFileSystemXmlApplicationContextインスタンスで実行する場合、FileSystemResourceが返されます。WebApplicationContextでは、ServletContextResourceになります。

このように、ある特定のアプリケーションコンテキストでは、ある特定の方法でリソースをロードします。

一方で、ClassPathResourceを使うように強制する事も可能で、アプリケーションコンテキスト型に関わらず、特殊なclasspath:プレフィクスを指定します。

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

同様に、任意の標準java.net.URLプレフィクスを指定することでUrlResourceの使用を強制できます。

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");

以下の表はStringからResourceへの変換規則をまとめたものです。

Table 6.1. Resource strings

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml クラスパスからロード
file: file:///data/config.xml ファイルシステムからURLとしてロード(注:Section 6.7.3, “FileSystemResource caveats”も参照)
http: http://myserver/logo.png URLとしてロード
(none) /data/config.xml 使用するApplicationContextに依存

6.5 The ResourceLoaderAware interface

ResourceLoaderAwareインタフェースは特殊なマーカーインタフェースで、オブジェクトにResourceLoader参照が渡されるよう指示します。

ResourceLoaderAwareを実装してアプリケーションコンテキストに(Springマネージドビーンとして)デプロイする場合、このクラスはアプリケーションコンテキストによってResourceLoaderAwareと認識されます。アプリケーションコンテキストはsetResourceLoader(ResourceLoader)を呼び出し、引数に自分自身を渡します(SpringのすべてのアプリケーションコンテキストはResouceLoaderを実装します)。

なお、ApplicationContextResourceLoaderを継承するので、ApplicationContextAwareを実装して与えられたアプリケーションコンテキストで直接リソースをロードすることも可能ですが、通常は、特化したResourceLoaderを使うほうがベターです。コードがリソースをロードするインタフェースにだけ結合し、このインタフェースはユーティリティインタフェースと見なすことが出来て、Springでより広い意味を持つApplicationContextインタフェースとの結合は避けられます。

Spring 2.5以降では、ResourceLoaderAwareインタフェースを実装する代わりにResourceLoaderのオートワイヤを使用可能です。"伝統的な"constructorbyTypeオートワイヤモード(Section 5.4.5, “Autowiring collaborators”で解説)でコンストラクタ引数とセッターメソッド引数のいずれでもResourceLoader型の依存性を解決可能です。より柔軟性な方法(オートワイヤフィールドと複数パラメータメソッドなど)では、新しいアノテーションベースのオートワイヤ機能の使用を考えて下さい。この場合、ResourceLoaderは、ResourceLoader型のフィールド・コンストラクタ引数・メソッド引数に、@Autowiredがあれば、オートワイヤされます*6。詳細はSection 5.9.2, “@Autowired”を参照してください。

6.6 Resources as dependencies

もしビーンが提供するリソースパスを動的に決定する必要がある場合、リソースのロードにResourceLoaderを使うことになるでしょう。いま、ユーザのロールに応じて、いくつかのテンプレートをロードするサンプルを考えてみます。もしリソースが静的であれば、ResourceLoaderは全く使う必要は無く、ビーンは必要なResourceプロパティを公開し、そのプロパティにインジェクトするだけです

必要な作業としてはプロパティにインジェクトするだけです*7。すべてのアプリケーションコンテキストは、StringパスをResourceオブジェクトに変更可能な、特別なJavaBeans PropertyEditorを使用します。よって、もしmyBeanResource型のテンプレートプロパティを持つ場合、そのリソースには文字列で設定が可能です。

<bean id="myBean" class="...">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

リソースパスには何もプレフィクスを付けていないことに注意してください。アプリケーションコンテキスト自体がResourceLoaderとしても使われるので、コンテキストの拡張型に応じて、(適切な)リソースがClassPathResource, FileSystemResource, ServletContextResource経由でロードされます。

特定のResouce型を強制したい場合、プレフィクスを使用します。以下の二つの例はClassPathResourceUrlResourceを強制する例です(後者はファイルシステムになります)。

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

6.7 Application contexts and Resource paths

6.7.1 Constructing application contexts

アプリケーションコンテキストのコンストラクタ(特定のアプリケーションコンテキスト型に固有)は、通常、コンテキスト定義のXMLファイルなどのリソースのロケーションパスの引数を文字列もしくは文字列の配列に取ります。

これらのロケーションパスがプレフィクスを持たない場合、指定したアプリケーションコンテキストに応じて、パスから適切なResource型を構築してビーン定義をロードします。例として、以下のClassPathXmlApplicationContextを作成する場合、

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

ビーン定義はクラスパスからロードし、ClassPathResourceが使われます。なお、以下のようにFileSystemXmlApplicationContextを使う場合、

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");

ビーン定義はファイルシステムからロードし、この場合は現在のワーキングディレクトリからの相対パスを使用します。

ただし、特殊なクラスパスプレフィクスやロケーションパスに標準URLプレフィクスを使う場合、ビーン定義のロードに使うデフォルトのResource型をオーバーライドします。よってFileSystemXmlApplicationContextの場合、

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

ビーン定義のロードそのものはクラスパスから行います。ただし、アプリケーションコンテキストはFileSystemXmlApplicationContextのままです。もし後続の処理でResourceLoaderとして使う場合、プレフィクス無しのパスはファイルシステムパスとして扱われます。

Constructing ClassPathXmlApplicationContext instances - shortcuts

ClassPathXmlApplicationContextインスタンス化に便利なコンストラクタ複数公開しています。一つ目の引数はXMLファイル名(そのファイルの属する親のパスを含まない)のみからなる文字列の配列で、もう一つはClassです。ClassPathXmlApplicationContextでは二つ目の引数のクラスをパスに使います。

簡単な例を考えます。ディレクトリレイアウトは以下のようになっているとします。

com/
  foo/
    services.xml
    daos.xml
    MessengerService.class

'services.xml''daos.xml'から成るClassPathXmlApplicationContextインスタンスは以下のようにインスタンス化出来ます。

ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "daos.xml"}, MessengerService.class);

詳細についてはClassPathXmlApplicationContextjavadocの各種コンストラクタを参照してください。

6.7.2 Wildcards in application context constructor resource paths

アプリケーションコンストラクタに渡すリソースパスの値は、ターゲットResourceと1対1に対応する(上述のような)単一のパスか、特殊な"classpath*:"プレフィクスと内部Antスタイル正規表現*8(SpringのPathMatcherユーティリティーを使用するマッチング)の両方またはいずれか一方、が可能です。後者は両方とも実質的にはワイルドカードです。

このメカニズムを使用するのはコンポーネントスタイルアプリケーションをアセンブリする場合が挙げられます。すべてのコンポーネントはwell-knownなロケーションパスにコンテキスト定義のフラグメントをpublishし、最後のアプリケーションコンテキストはclasspath*:プレフィクスのパスを使用して作成し、全コンポーネントのフラグメントを自動的にピックアップします。

注意点として、このワイルドカードはアプリケーションコンテキストコンストラクタでリソースパスを使う場合に固有(もしくはPathMatcherユーティリティでクラス階層を直接使う場合)なので、コンストラクタ呼び出し時(construction time)に解決が行われます。これはResource型とは無関係です。一度に一つだけのリソースを指し示す用途として、実Resourceコンストラクタするのにclasspath*:プレフィクスを使うことは出来ません*9

Ant-style Patterns

パスロケーションにはAntスタイルパターンを使えます。

/WEB-INF/*-context.xml
  com/mycompany/**/applicationContext.xml
  file:C:/some/path/*-context.xml
  classpath:com/mycompany/**/applicationContext.xml

リゾルバは複雑ですが定義された手続きに沿ってワイルドカードの解決を試みます。

一番最後の非ワイルドカードセグメントまでのパスでResourceを生成し、そこからURLを取得します。URLが"jar:"URLかコンテナ固有(WebLogicの'zip:'、WebSphereの'wsjar'など)で無い場合、ファイルシステム走査でワイルドカードを解決してjava.io.Fileを取得します。jar URLの場合、リゾルバはjava.net.JarURLConnectionを取得するか、jar URLをマニュアルでパースしてからワイルドカードを解決するためにjarの内容をトラバースします。

Implications on portability

指定パスがファイルURL(明示的な指定か、あるいはベースとなるResourceLoaderが暗黙的にファイルシステムを使用)の場合、ワイルドカードは移植可能なことが保証されます。

指定パスがクラスパスの場合、リゾルバはClassloader.getResource()を一番最後の非ワイルドカードパスまでで呼び出して取得します。これはパスのノード(末端のファイルでは無い)なので、動作は(ClassLoaderjavadocにおいては)未定義でありURLによって何が返されるかは場合に依ります。実際には、このノードは常に、あるディレクトリを表現するjava.io.Fileになります。このディレクトリは、クラスパスリソースがファイルシステムやjar URLもしくはjarファイルに解決するための基点となります。この場合、移植性に関する不安材料となります。

最後の非ワイルドカードまででjar URLを取得する場合、リゾルバはjava.net.JarURLConnectionを取得するか、jarを走査可能なようにjar URLをマニュアルでパースする必要があり、それからワイルドカードを解決します。これは大抵の環境で動作しますが、それ以外では失敗するため、jarでリソースのワイルドカード解決を行う場合はユーザ固有の環境で十分なテストを行うことを強く推奨します。

The Classpath: portability classpath: prefix

XMLベースのアプリケーションコンテキストを生成する場合、ロケーション文字列は特殊なclasspath*:プレフィクスを使います。

ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

この特殊なプレフィクスは与えられた名前にマッチするすべてのクラスパスリソースを取得(内部的にはClassLoader.getResources(...)を呼び出します)し、最終的なアプリケーションコンテキスト定義の形式にマージします。

ワイルドカードクラスパスは基底のクラスローダーのgetResources()メソッドに依存します。最近の大抵のアプリケーションサーバは自前のクラスローダー実装を備えており、その振る舞いはjarファイルの扱いにおいて特に異なります。classpath*が動作するかの簡単なチェックテストは、クラスパスのjarの内部からファイルをロードするのにクラスローダーを使うことです。getClass().getClassLoader().getResources("<someFileInsideTheJar>")。このテストを同一名称だが異なる別のロケーション内に位置するファイルでやってみてください。不適切な結果が返される場合、アプリケーションサーバの設定ドキュメントのうちクラスローダーの振る舞いに影響する項を参照してください。

"classpath*:"プレフィクスはロケーションパスの残りの部分にPathMatcherパターンを組み合わせることも可能です。例えば、"classpath*:META-INF/*-beans.xml"です。この例では、解決は極めて単純で、クラスローダー階層ですべてのマッチするリソースを取得するために最後の非ワイルドカードパスまででClassLoader.getResources()を呼び出し、各リソースごとに上述と同様のPathMatcherをワイルドカードのサブパスに適用します。

Other notes relating to wildcards

注意点として、Antスタイルパターンとclasspath*:を組み合わせる場合はパターンの先頭に少なくとも一つのルートディレクトリを付ける時のみ確実に動作することに気を付けてください。つまり、"classpath*:*.xml"というパターンは、jarのルートからファイル検索するのでは無く、展開ディレクトリのルートのみ検索します。この制限の大本はJDKClassLoader.getResources()に起因しており、このメソッドは空文字を渡されるとファイルシステムロケーション*10のみ返します。

複数のクラスパスロケーションに検索対象となるルートパッケージがある場合、"classpath:"のAntスタイルパターンはリソースのマッチングを保証しません。この理由は以下のようなリソースがある場合、

com/mycompany/package1/service-context.xml

上記は唯一のロケーションにマッチしますが、以下のようなパスの場合、

classpath:com/mycompany/**/service-context.xml

リゾルバはgetResource("com/mycompany")に削ったURLから開始します。もし、このベースとなるパッケージが複数のクラスローダーに存在する場合、最終的に返されるリソースはディレクトリツリーで最も底のものとはならない可能性があります。よって、出来れば、こうした場合に前述のAntスタイルパターンを使う場合は"classpath*:"と組み合わせて下さい。こちらはルートパッケージに含まれるすべてのクラスパスを検索します。

6.7.3 FileSystemResource caveats

FileSystemApplicationContextにアタッチされないFileSystemResource(つまりFileSystemApplicationContextResourceLoaderではない)は、期待通りに絶対と相対パスを扱えます。相対パスはカレントのワークディレクトリからのパスとなり、絶対パスファイルシステムのルートからのパスとなります。

しかし、過去の(歴史的な)互換性という理由のため、FileSystemApplicationContextResourceLoaderとして使う場合には注意が必要です。FileSystemApplicationContextは、スラッシュで始まってるかどうかに関わらず、アタッチされたすべてのFileSystemResourceインスタンス相対パスとして扱います。つまり、以下は同じ意味となります。

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");

以下も同様です。(相対と絶対で、両者は異なっているように見えますが)

FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");

つまり、絶対のファイルシステムパスが必要な場合、FileSystemXmlApplicationContextFileSystemResourceによる絶対パスの使用をせず、file:URLプレフィクスを使用してUrlResourceを使うよう強制してください。

// 実際のコンテキストが何かは重要でなく、Resourceは常にUrlResourceとなります。
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// UrlResource経由で定義をロードするようFileSystemXmlApplicationContextを強制する
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");

*1:InputStream cannot be read multiple times, and must be read once only and then closed to avoid resource leaks.が原文。よくわからん

*2:with Spring and by Springが原文。もうちょい気の利いた訳がしたいところだが……

*3:There are a number of Resource implementations that come supplied straight out of the box in Spring:が原文。come ~以下がサッパリ分からんから適当に訳した

*4: but not for classpath resources which reside in a jar and have not been expanded (by the servlet engine, or whatever the environment is) to the filesystem.が原文。ちょっと上手く訳せてないような…

*5:Whether or not it’s expanded and on the filesystem like this, or accessed directly from the JAR or somewhere else like a DB (it’s conceivable) is actually dependent on the Servlet container.が原文。like thisはその次のor~以降を指してるという解釈であってるか自信無い

*6:as long as the field, constructor, or method in question carries the @Autowired annotation.が原文。in question carriesがよくわからんので適当に誤魔化した

*7:What makes it trivial to then inject these propertiesが原文。上手く訳せなかったのでテキトーに意訳してしまった

*8:internal Ant-style regular expressionsてまぁそういうことなんだけどいざ日本語にすると迷う…

*9:It’s not possible to use the classpath: prefix to construct an actual Resource, as a resource points to just one resource at a time.が原文。classpath:がワイルドカードだからリソースを一つに限定することは出来ない、って意味だと思うのだが……自信が無い

*10:たとえばclassLoader.getResource("")とかやると file:/C:/Users/kagamihoge/workspace/hoge/target/classes/ が返ってくる