Spring の Classpath 配下の Resource を扱う
前提
- Spring Boot 2.6.3
- macOS BigSur 11.6.6
Spring Boot における Resources 配下のファイルを扱いたい
以下のような構成
│ │ └── resources
│ │ ├── application.yaml
│ │ ├── hoge.json
│ │ └── logback-spring.xml
この hoge.json
をコード内で読み込んで利用したい
IntelliJ を使って手元での実行では問題ないが、 Fat jar に Bundle するとエラーになるコード
val json = Files.readString(Paths.get(ClassLoader.getSystemResource("hoge.json").toURI()))
Fat jar にした Bundle で実行すると以下のエラーが発生する
java.lang.NullPointerException: null
debug すると以下が null
であることがわかる
ClassLoader.getSystemResource("hoge.json")
なぜエラーとなるか
気になるのはこのあたり
このメソッドは、パッケージが無条件でopenedの場合にのみ、名前付きモジュールのパッケージ内のリソースを検索します。
resources
配下に配置された任意のリソースは、実際には BOOT-INF/classes
の Root に配置されるため、このメソッドを利用したリソースの特定はそもそも不適格なのでは?
ClassPathResource
Resource IF の実装がいくつかあるのでどれを使えば良いかの使い分けはあまりはっきりわかっていない。
このクラスが良さそうに思えるのだが、 Javadoc を見ると気になる記載がある。
クラスパスリソースがファイルシステムにある場合は java.io.File として解決をサポートしますが、JAR 内のリソースはサポートしません。常に URL としての解決をサポートします。
JAR 内のリソースはサポートしない?と読める。さらに Spring Boot に気になる Issue が未だ Close されていない
ResourceLoader を Injection
Spring 由来の ResourceLoader がある。
SpringBoot では様々なリソース(例えば application.yaml
等)も resources
に配置されこれらを読んでいるため、 Spring 由来の ResourceLoader は必ず利用しているはず。
Bean として登録されているため、Injection されることを期待したコードを書いておくと実際正常に動作する
class Hoge(
private val resourceLoader: ResourceLoader,
) {
...
fun hoge(): String {
return Files.readString(resourceLoader.getResource("hoge.json").file.toPath())
}
Resource 配下のファイルにアクセスする際には Fixed File としてではなく InputStream として読む
どうも InputStream として読むことでうまくいくようだ。理由についてはまだ判然としていないので要調査。
結果として以下のコードで手元でもBundleした JAR ファイルでも正しく動くことを確認した。
val inputStream = ClassPathResource("hoge.json").inputStream
val json = String(inputStream.readAllBytes(), StandardCharsets.UTF_8)
わかったようで何もわかっていない Classpath, ResourceLoader 周り