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となります。
他のメソッドとしてはリソースを表す実URL
やFile
オブジェクトを取得するものがあります(ただし実装に互換性がありその機能をサポートしている場合に限る)。
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
UrlResource
はjava.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
を扱うものです。File
やURL
としての解決をサポートします。
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
を実装します)。
なお、ApplicationContext
はResourceLoader
を継承するので、ApplicationContextAware
を実装して与えられたアプリケーションコンテキストで直接リソースをロードすることも可能ですが、通常は、特化したResourceLoader
を使うほうがベターです。コードがリソースをロードするインタフェースにだけ結合し、このインタフェースはユーティリティインタフェースと見なすことが出来て、Springでより広い意味を持つApplicationContext
インタフェースとの結合は避けられます。
Spring 2.5以降では、ResourceLoaderAware
インタフェースを実装する代わりにResourceLoader
のオートワイヤを使用可能です。"伝統的な"constructor
とbyType
オートワイヤモード(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
を使用します。よって、もしmyBean
がResource
型のテンプレートプロパティを持つ場合、そのリソースには文字列で設定が可能です。
<bean id="myBean" class="..."> <property name="template" value="some/resource/path/myTemplate.txt"/> </bean>
リソースパスには何もプレフィクスを付けていないことに注意してください。アプリケーションコンテキスト自体がResourceLoader
としても使われるので、コンテキストの拡張型に応じて、(適切な)リソースがClassPathResource
, FileSystemResource
, ServletContextResource
経由でロードされます。
特定のResouce
型を強制したい場合、プレフィクスを使用します。以下の二つの例はClassPathResource
とUrlResource
を強制する例です(後者はファイルシステムになります)。
<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);
詳細についてはClassPathXmlApplicationContext
のjavadocの各種コンストラクタを参照してください。
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()
を一番最後の非ワイルドカードパスまでで呼び出して取得します。これはパスのノード(末端のファイルでは無い)なので、動作は(ClassLoader
のjavadocにおいては)未定義であり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のルートからファイル検索するのでは無く、展開ディレクトリのルートのみ検索します。この制限の大本はJDKのClassLoader.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
(つまりFileSystemApplicationContext
はResourceLoader
ではない)は、期待通りに絶対と相対パスを扱えます。相対パスはカレントのワークディレクトリからのパスとなり、絶対パスはファイルシステムのルートからのパスとなります。
しかし、過去の(歴史的な)互換性という理由のため、FileSystemApplicationContext
をResourceLoader
として使う場合には注意が必要です。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");
つまり、絶対のファイルシステムパスが必要な場合、FileSystemXmlApplicationContext
とFileSystemResource
による絶対パスの使用をせず、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/ が返ってくる