
# Resource

`Resource` は読み込むコンテンツを表し、`ResourceLoader` は `resources:mail.txt` のようなロケーション文字列を `Resource` に変換します。

## 使う理由

ファイルを読む Bean は、ロケーションを設定から受け取って読むだけにするべきで、保存先ごとのコードを持つべきではありません。各プレフィックスの意味はローダーが知っています。

1. 登録された `ProtocolResolver`(登録順。例: Flutter のアセット)。
2. `resources:`: ジェネレーターが埋め込んだ `lib/resources/` のファイル。
3. `file:`: ディスク上のファイル。パーセントデコードされる(`file:my%20dir/a.txt`)。
4. `http:` と `https:`: 非同期で読む `UrlResource`。
5. `package:`: パッケージ設定を通したパッケージのファイル(Web と AOT 実行ファイルでは使えない)。
6. その他の `scheme:`: 存在しないリソース。
7. プレーンなパス(`app.properties`): `resources:` のリソース。

Bean が受け取るローダーはアプリケーションコンテキストです。コンテキストは `ResourcePatternResolver` でもあり、`resources:mail/*.txt` のようなパターンに一致するファイルをすべて見つけます。

固定のロケーションなら `@Value('resources:...') Resource` が最も短い書き方です。設定値を読むなら、ファイルを読まずに `${...}` 付きの [@Value](../config/value.md) を使ってください。

## 例

```dart
import 'package:spring_core_dart/spring_core_dart.dart';

@Service()
class Imports {
  Imports(this.resources, @Value(r'${shop.import.file}') this.location);

  // アプリケーションコンテキスト: ResourceLoader かつ ResourcePatternResolver。
  final ResourcePatternResolver resources;

  // 本番では `file:./import.csv`、開発では `resources:demo.csv`。
  final String location;

  Future<String> read() => resources.getResource(location).readAsString();

  // lib/resources/mail/ に同梱したメールテンプレートすべて。
  List<Resource> templates() => resources.getResources('resources:mail/*.txt');
}
```

同期読み込みは、リソースが対応している場合にだけ使えます。

```dart
final resource = DefaultResourceLoader().getResource('resources:banner.txt');
if (resource.exists() && resource.supportsSyncRead) {
  // ファイルと埋め込みの lib/resources/ は待たずに読める。
  final banner = resource.readAsStringSync();
}
```

新しいプレフィックスは `ProtocolResolver` で追加します。組み込みのルールより先に問い合わせられます。

```dart
// `inline:greeting` をメモリから返す。
final loader = DefaultResourceLoader(
  protocolResolvers: [
    FunctionProtocolResolver(
      (location, _) => location == 'inline:greeting'
          ? ByteArrayResource.fromString('hello=world')
          : null,
    ),
  ],
);
```

パターンについて:

- `resources:` は、パターンのベースディレクトリを持つ最初のクラスパスルートだけを検索します。`resources*:` はすべてのルート(`Resources.addDirectory` で追加したディレクトリ、次に埋め込みファイル)を検索します。
- ワイルドカードは [AntPathMatcher](path-matcher.md) に従います。一致したディレクトリも返されます。
- ベースディレクトリが存在しない `resources:` パターンは空のリストを返します。

## メンバー

`Resource`:

| 名前 | 型 | 意味 |
|---|---|---|
| `exists()` | `bool` | コンテンツが存在するか。`UrlResource` は常に `true`。サーバーに問い合わせるには `existsAsync()` を使う。 |
| `isReadable` | `bool` | 読み込めるか。ディレクトリは `false`。 |
| `filename` | `String?` | パスの最後の部分。ない場合(メモリ上のバイト列)は `null`。 |
| `description` | `String` | エラーメッセージ用の説明。例: `file [/app/config/application.properties]`。 |
| `uri` | `Uri?` | リソースの URI。ない場合は `null`。 |
| `supportsSyncRead` | `bool` | `...Sync` の読み込みが使えるか。ファイルと埋め込みリソースでは true。 |
| `readAsBytes()` | `Future<Uint8List>` | 内容全体を読む。 |
| `readAsString({encoding})` | `Future<String>` | 内容全体をテキストとして読む。指定がなければ UTF-8。 |
| `readAsBytesSync()` | `Uint8List` | 同期読み込み。`supportsSyncRead` でなければ `UnsupportedError`。 |
| `readAsStringSync({encoding})` | `String` | テキストとしての同期読み込み。 |
| `createRelative(relativePath)` | `Resource` | このリソースからの相対パスにあるリソース。存在は確認しない。 |

ローダーと実装:

| 名前 | 意味 |
|---|---|
| `ResourceLoader.getResource(location)` | ロケーション文字列に対応するリソース。 |
| `ResourcePatternResolver.getResources(pattern)` | パターンに一致するすべてのリソース。 |
| `DefaultResourceLoader({protocolResolvers})` | 標準のローダー。`addProtocolResolver` でリゾルバーを追加できる。 |
| `PathMatchingResourcePatternResolver({resourceLoader, pathMatcher})` | 標準のパターンリゾルバー。 |
| `ProtocolResolver`、`FunctionProtocolResolver`、`InMemoryProtocolResolver` | ローダーに新しいプレフィックスを教える。 |
| `FileSystemResource`、`UrlResource`、`ByteArrayResource`、`DescriptiveResource` | ディスク上、URL、メモリ上のリソース、および存在しないことを表すプレースホルダー。 |
| `Resources.addDirectory(directory)` | `resources:` がディスク上のディレクトリを先に探すようにする。解除する関数を返す。 |

## Spring (Java) との対応

```java
@Service
class Imports {
  private final ResourcePatternResolver resources;

  Imports(ResourcePatternResolver resources) {
    this.resources = resources;
  }

  String read(String location) throws IOException {
    return resources.getResource(location)
        .getContentAsString(StandardCharsets.UTF_8);
  }

  Resource[] templates() throws IOException {
    return resources.getResources("classpath:mail/*.txt");
  }
}
```

重要な違い:

- プレフィックスは `classpath:`(と `classpath*:`)ではなく `resources:`(と `resources*:`)です。「クラスパス」は `lib/resources/` です。
- Flutter のアセットや URL は非同期でしか読めないため、読み込みは基本的に非同期です。`getInputStream()` や `getFile()` はなく、内容全体を読みます。
- `UrlResource.exists()` はリクエストを送りません。`existsAsync()` が `HEAD` リクエストを送ります。
- クラスパスルートより上に出る `..` は何も見つけません。`file:` パターンでは `\` を使った Windows のパスも使えます。

## 関連項目

- [AntPathMatcher](path-matcher.md)
- [@Value](../config/value.md)
- [@PropertySource](../config/property-source.md)
- [MessageSource](../messages/message-source.md)
- [ApplicationContext](../beans/application-context.md)
