Google
 
Web andore.com
Powered by SmartDoc

4. 低レベルリソースへのアクセスの抽象化 (Ver 1.2.7)

4.1 概要

Java標準のjava.net.URLインタフェースと、さまざまなURLプレフィクス用の標準ハンドラは、低レベルリソースへのすべてのアクセスに対しては残念なことに全く使い物にならない。例えば、クラスパス上のどこかから取得する必要のあるリソース、あるいは例えばServletContextに関係するものへアクセスするのに利用されるような標準化されたURLの実装が存在しないのだ。(http:のようなプレフィクス用の既存のハンドラと同様に)特定のURLプレフィクス用に新規にハンドラを登録することは可能だが、これは通常とても複雑で、URLインタフェースに 指し示されているリソースの存在性をチェックするメソッドのようなあるべき機能が不足している。

4.2 Resourceインタフェース

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;

}

もっとも重要なメソッドのうちいくつかを挙げる:

実装に互換性があり、機能がサポートされていれば、他のメソッドでも実際のURLもしくはリソースを表すファイルオブジェクトを取得ことができる。

リソースが必要な場合に多くのメソッドシグネチャの引数型としてResourceはSpring自体で広範囲に利用されている。簡素あるいは単純な形式の文字列をとるSpring APIの(さまざまなApplicationContextの実装のコンストラクタのような)他のメソッドはコンテキストの実装に適したResourceを生成するのに利用される。あるいは、Stringパスの特別なプレフィクスを使って、呼び出し側が生成して利用すべき特定のResourceの実装を指定することができる。内部的には、JavaBeans PropertyEditorがその文字列から適切なResource型への変換に用いられるが、ユーザには関係ないことである。

ResourceがSpringで、あるいはSpringによって多用されているので、リソースへアクセスするのに、あなたのコードの中で、あるいはあなたのコードがSpringの他の部品について知らなかったり意識していない場合でさえも、汎用のユーティリティクラスとして利用することは実際にとても便利だ。これは、あなたのコードをSpringと結びつけているが、実際には少量のユーティリティクラスと結びついてるだけであり、URLへの置き換えに役に立っており、この目的のために使う他のライブラリと等価である、とみなすことができる。

Resourceは機能性を置き換えるものではない、という点に注意することは重要だ。Resourceは有効な場所をラップするものだ。例えば、UrlResourceURLをラップし、ラップされたURLを動作するために利用する。

4.3 組み込み済みResourceの実装

組み込みのResource実装は数多く用意されている。

4.3.1 UrlResource

これは、java.net.URLをラップし、ファイルやhttpのターゲット、ftpのターゲットなど、通常URLでアクセス可能な任意のオブジェクトへアクセスするのに用いられる。URLはプレフィクスがすべて1つのURL型あるいは他のものを示すのに使われる適切に標準化されたString表現に標準化されている。これには、ファイルシステムパスにアクセスするfile:、HTTPプロトコルでリソースにアクセスするためのhttp:、ftpでリソースにアクセスするためのftp:などが含まれている。

UrlResourceはJavaコードにより明示的にUrlResourceのコンストラクタを用いて生成されるが、パスを表すString引数を取るAPIメソッドを叩く場合に、暗黙的に生成されることがある。後者の場合、JavaBeans PropertyEditorが究極的には、どの型のResourceを生成するかを決定する。もし、パス文字列に既知のプレフィクス、例えばclasspath:などのようなものが含まれていれば、適切な、そのプレフィクスに合致した特別のResourceを生成する。しかしながら、そのプレフィクスが認識されない場合は、標準のURL文字列であると仮定して、UrlResourceを生成する。

4.3.2 ClassPathResource

このクラスはクラスパスから取得されるべきリソースを表現する。これは、スレッドコンテキストクラスローダかあるいはリソースをロードするための既存のクラスローダを利用する。

もし、クラスパスリソースがファイルシステムに存在するがjarに存在するクラスパスリソースになく、(サーブレットエンジンによって、あるいはどのような環境においてでも)ファイルシステムに拡張されていなければ、このResourceの実装は、java.io.Fileとして解決をサポートする。これは通常java.net.URLとして解決をサポートする。

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

4.3.3 FileSystemResource

これはjava.io.FileをハンドリングするためのResourceの実装だ。これは明らかに、Fileとして、あるいはURLとして解決を支援する。

4.3.4 ServletContextResource

これは、ウェブアプリケーションのルートディレクトリ配下の早退パスを解釈するServletContextリソースのためのResourceの実装である。

これは通常、ストリームアクセス、もしくはURLアクセスをサポートするが、ウェブアプリケーションアーカイブが展開されてそのリソースが物理的にファイルシステム上にある場合に限りjava.io.Fileアクセスが許される。たとえこのようにファイルシステム上に展開されていようと、あるいはJARから直接、あるいはDBのような何か他のところ(これはありうる)からアクセスされようと、実際にはサーブレットコンテナに依存する。

4.3.5 InputStreamResource

既存のInputStreamのためのResourceの実装である。これは、特定のResource実装、特に、ByteArrayResourceやファイルベースのResource実装のうち利用可能なものが利用できない場合のみに利用すべきだ。

他のResource実装と異なり、これはすでにオープンされているリソースのためのデスクリプタであり、isOpen()は必ず"true"が返る。もし何らかのリソースデスクリプタを保持する必要がある場合、あるいは何度もストリームをreadしないといけない場合は、これを使ってはいけない。

4.3.6 ByteArrayResource

これは、バイト配列用のResource実装である。バイト配列が与えられるとByteArrayInputStreamを生成する。

InputStreamResourceにソートしなおさなくてもよいので任意のバイト配列からコンテンツを読み込むのに有用である。

4.4 ResourceLoaderインタフェース

ResourceLoaderインタフェースは、Resourcesを返す(つまり、ロードする)ことができるオブジェクトにより実装されるべきものである。

public interface ResourceLoader {
    Resource getResource(String location);
}

すべてのアプリケーションコンテキストはResourceLoaderを実装し、しかるにすべてのアプリケーションコンテキストはResourcesを取得するのに利用される。

ある特定のアプリケーションコンテキストでgetResource()を叩いて、指定されたロケーションパスにある特定のプレフィクスがなかった場合、特定のアプリケーションコンテキストに適切なResource型を戻す。例えば、ClassPathXmlApplicationContextを問い合わせる場合、

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

ClassPathResourceが取得できるが、FileSystemXmlApplicationContext上で同じメソッドを叩いた場合は、FileSystemResourceが取得できる。WebAllicationContextの場合は、ServletContextResourceが取得できる、など。

このように、特定のアプリケーションコンテキストに応じた適切な方法でリソースをロードすることができる。

その一方で、特別なclasspath:プレフィクスを指定することで、アプリケーションコンテキストの型に関わらず、ClassPathResourceを強制的に使うようにすることもできる。

    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");

4.5 ResourceLoaderAwareインタフェース

ResourceLoaderAwareインタフェースはResourceLoaderで提供されるオブジェクトのための特別なマーカーインタフェースである。

public interface ResourceLoaderAware {
   void setResourceLoader(ResourceLoader resourceLoader);
}

あるビーンがResourceLoaderAwareを実装し、アプリケーションコンテキストにデプロイされると、アプリケーションコンテキストによって認識され、ResourceLoader引数としてそのアプリケーションコンテキスト自身が渡されコールバックされる。

もちろん、ApplicationContextResourceLoaderなので、このビーンはApplicationContextAwareを実装することもできるし、コンテキスト中で直接リソースをロードすることもできるが、一般的には、それが必要とするもののすべてであるならば、Springとの結合が弱くなるので、特別なResourceLoaderインタフェースを使うほうが望ましい。そうすれば、そのコードは、コンテキストインタフェース全体ではなく、ユーティリティインタフェースとみなすことができる、そのリソースをロードするインタフェースとだけ結合されるようになる。

4.6 Resourcesをプロパティとして設定する

ビーン自身がある種の動的なプロセスを経ることでリソースパスを決定し、提供する場合、そのビーンがリソースをロードするためにResourceLoaderインタフェースを利用することはおそらく有効だろう。ユーザの役割にしたがい特定のものを必要とするある種のテンプレートをロードするという例を考えてみていただきたい。 他方で、リソースが静的であれば、ResourceLoaderインタフェースを使うのを完全に回避し、必要なResourceプロパティを明示化し、それが注入されるようにするのが有効だ。

これらのプロパティを注入することがたいしたものでなくするには すべてのアプリケーションコンテキストがStringのパスをResourceオブジェクトへ変換できる特別なJavaBeans PropertyEditorをレジストし、使うことだ。よって、もしmyBeanResource型のテンプレートプロパティをもっていれば、以下のようにテキスト文字列を使ってリソースの設定を行うことが可能だ。

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

リソースパスにプレフィクスがないのは、アプリケーションコンテキスト自身がResourceLoaderとして利用されることになっているので、そのリソースそのものは適切なコンテキストの型に従ったClassPathResourceFileSystemResourceServletContextResourceなどによりロードされるからである、という点に注意してほしい。

もし、特定のResource型を強制的に利用する必要があるのであればプレフィクスが用いられる。下記の2つの例は、ClassPathResourceUrlResourceを無理やり使う方法を示したものだ(後者はファイルシステムのファイルにアクセスするために利用されている)。

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

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

4.7 アプリケーションコンテキストとResourceパス

4.7.1 アプリケーションコンテキストを構築する

アプリケーションコンテキストのコンストラクタ(特定のアプリケーションコンテキスト型用の)は通常文字列、もしくは文字列の配列をコンテキスト定義を構成するXMLファイルのようなリソースのロケーションパスとしてとる。

このようなロケーションパスがプレフィクスを持たない場合、そのパスから構築され、定義をロードするのに用いられる特定のResource型は 特定のアプリケーションコンテキストにであり、またこれに依存する。例えば、ClassPathXmlApplicationContextを以下のように生成したとする。

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

ここで、この定義はClassPathResourceが使われるので、クラスパスからロードされる。しかし、FileSystemXmlApplicationContextを下記のように生成したとすると、

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

この定義はファイルシステムロケーション、この場合は、カレントのワーキングディレクトリからの相対パスからロードされる。

特別なクラスパスプレフィクスもしくは、標準のURLプレフィクスをロケーションパスで使うのは、この定義をロードするのに生成されたデフォルトのResource型をオーバライドする。よって、このFileSystemXmlApplicationContext

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

は、実際にはこの定義をクラスパスからロードする。しかしながら、これはまだFileSystemXmlApplicationContextだ。引き続きResourceLoaderとして使われるのであれば、プレフィクスが付与されていないどのパスもファイルシステムパスとして扱われる。

4.7.2 classpath*: プレフィクス

XMLベースのアプリケーションコンテキストを構築するとき、ロケーション文字列は特別なclasspath*:プレフィクスが利用される。

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

この特別なプレフィクスは、与えられた名前にマッチするclasspathリソースはすべて取得されるべきものであり(内部的に、これは本質的にはClassLoader.getResource(...)を叩くことにより起こる)、最終的なアプリケーションコンテキストの定義を構成するためにマージされるべきものであることを指定するものだ。

このメカニズムを使うのは、1つにはコンポネントスタイルのアプリケーション構築を行う場合だ。コンポネントはすべてコンテキスト定義の断片を既知のロケーションパスに"パブリッシュ"可能で、最終的なアプリケーションコンテキストが、classpath*:プレフィクスが付与された同じパスを使って生成されている場合、すべてのコンポネント片は自動的に選択される。

この特別なプレフィクスはアプリケーションコンテキストに特有のものであり、構築時に解決される点に注意してほしい。Resource型そのものとは何の関係もない。リソースは同時に1つのリソースしか参照できないので、classpath*:プレフィクスを実際のResource構築に用いることはできない。

4.7.3 FileSystemResource絶対パスの予期しないアプリケーションコンテキストハンドリング

FileSystemApplicationContextにつけられないFileSystemResource(すなわちFileSystemApplicationContextは実際のResourceLoaderではない)は、期待されるように、絶対パスも相対パスも扱う。相対パスは、カレントワーキングディレクトリからの相対であり、絶対パスはファイルシステムのルートに対する相対である。

しかしながら、(歴史的な)後方互換性の理由により、これは、FileSystemApplicationContextResourceLoaderである場合は、変わる。FileSystemApplicationContextは、単純にすべてのロケーションパスを、先頭のスラッシュがあるかどうかにかかわらず相対パスとして扱うためにFileSystemResourcesをすべて付けられる。実際、下記のものは等価である。

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

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

下記も同様。

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

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

たとえ、あるときは相対パス、あるときは絶対パスという風に異なっても意味をなす。

実際、真に絶対的なファイルシステムパスを必要とする場合、FileSystemResource/FileSystemXmlApplicationContextつきの絶対パスを使うのをあきらめ、file: URLプレフィクスを使うことにより、UrlResourceを無理やり使うほうがよい。

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:/some/resource/path/myTemplate.txt");

// force this FileSystemXmlApplicationContext to load it's definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:/conf/context.xml");