Spring のリソース抽象:Resource と ResourceLoader
Spring は Resource と ResourceLoader インターフェースで強力で柔軟なリソース処理抽象システムを提供します。このシステムは異なるタイプのリソース(システムファイル、クラスパス、URL)を物理的位置に関わらず統一的にアクセスできます。
Resource インターフェース:抽象の基礎
org.springframework.core.io.Resource インターフェースは Spring リソース抽象システムの中心的支柱です。標準 java.net.URL クラスのより強力な代替として設計され、クラスパスリソースや ServletContext 相対リソースへのアクセスの制限を克服します。
Resource の基本メソッド
| メソッド | 説明 |
|---|---|
exists() | リソースが物理的に存在する場合 true を返す |
getInputStream() | リソースを開き、各呼び出しで新しい InputStream を返す |
isOpen() | リソースが既に開かれたストリームを持つハンドルを表すかを示す |
getDescription() | エラーメッセージで使用するリソース説明を返す |
getURL() | 利用可能な場合リソース URL を返す |
getFile() | リソースがシステムファイルの場合 File オブジェクトを返す |
Resource の主な実装
Spring は異なるリソースタイプ向けにいくつかの専門実装を提供:
| 実装 | 説明 | プレフィックス | 例 |
|---|---|---|---|
UrlResource | 標準 URL でリソースにアクセス | http:、https:、file: | https://api.example.com/config.json |
ClassPathResource | アプリケーションのクラスパスリソース | classpath: | classpath:application.properties |
FileSystemResource | システムファイルリソース | file: またはプレフィックスなし | /data/config/app.xml |
ServletContextResource | Web アプリケーション内のリソース | プレフィックスなし | /WEB-INF/views/home.jsp |
ByteArrayResource | バイト配列ベースのリソース | N/A | メモリ内リソース向け |
ResourceLoader:統一ロード戦略
ResourceLoader インターフェースは場所(String)からのリソースロード戦略を定義します。すべての ApplicationContext は ResourceLoader を実装し、直接注入が可能です。
解決ルール
- プレフィックスなし: リソースタイプは ApplicationContext に依存
- プレフィックスあり: 特定 Resource タイプを強制
@Component
public class ResourceExample {
private final ResourceLoader resourceLoader;
public ResourceExample(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void loadResources() throws IOException {
// コンテキスト依存解決
Resource template1 = resourceLoader.getResource("config/template.txt");
// ClassPathResource 強制
Resource config = resourceLoader.getResource("classpath:app.properties");
// UrlResource 強制(ファイルシステム)
Resource fileLog = resourceLoader.getResource("file:/var/log/app.log");
}
}ワイルドカードを使用した複数リソースロード
パターンに一致するリソースを検索するには、Spring は ResourcePatternResolver を提供:
@Service
public class ConfigurationService {
private final ResourcePatternResolver resolver;
public ConfigurationService(ResourcePatternResolver resolver) {
this.resolver = resolver;
}
public void loadAllConfigurations() throws IOException {
// クラスパスからすべての XML をロード
Resource[] configs = resolver.getResources("classpath*:META-INF/*.xml");
// ディレクトリからすべてのプロパティファイルをロード
Resource[] properties = resolver.getResources("file:/config/*.properties");
for (Resource config : configs) {
// 各設定を処理
try (InputStream is = config.getInputStream()) {
// ファイルを読み取り処理
}
}
}
}@Value でのリソース注入
リソースを注入する最もモダンで推奨される方法は @Value 注釈を使用することです:
@Service
public class TemplateService {
private final Resource emailTemplate;
private final Resource logoImage;
public TemplateService(
@Value("${app.email.template:classpath:templates/default.html}") Resource emailTemplate,
@Value("classpath:static/images/logo.png") Resource logoImage) {
this.emailTemplate = emailTemplate;
this.logoImage = logoImage;
}
public void processTemplate() throws IOException {
if (emailTemplate.exists()) {
try (InputStream is = emailTemplate.getInputStream()) {
// テンプレートを処理
String content = StreamUtils.copyToString(is, StandardCharsets.UTF_8);
System.out.println("テンプレートを以下からロード:" + emailTemplate.getDescription());
}
}
}
}高度なユースケース
1. モジュラー設定ロード
@Configuration
public class ModuleConfiguration {
@Bean
public Properties moduleProperties(ResourceLoader resourceLoader) throws IOException {
Properties props = new Properties();
// クラスパスからすべてのモジュールプロパティをロード
Resource[] moduleResources = resourceLoader.getResources("classpath*:modules/*.properties");
for (Resource resource : moduleResources) {
try (InputStream is = resource.getInputStream()) {
Properties moduleProps = new Properties();
moduleProps.load(is);
// メインプロパティと統合
props.putAll(moduleProps);
}
}
return props;
}
}2. リソース検証
@Component
public class ResourceValidator {
public void validateResource(Resource resource) throws IOException {
if (!resource.exists()) {
throw new IllegalArgumentException("リソースが存在しません:" + resource.getDescription());
}
if (!resource.isReadable()) {
throw new IllegalArgumentException("リソースが読み取り不可:" + resource.getDescription());
}
// ファイルサイズ検証
if (resource.isFile()) {
File file = resource.getFile();
if (file.length() > 10 * 1024 * 1024) { // 10MB
throw new IllegalArgumentException("ファイルが大きすぎます:" + file.getName());
}
}
}
}3. キャッシュリソースロード
@Service
public class CachedResourceService {
private final Map<String, Resource> resourceCache = new ConcurrentHashMap<>();
private final ResourceLoader resourceLoader;
public CachedResourceService(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public Resource getResource(String location) {
return resourceCache.computeIfAbsent(location, loc -> {
Resource resource = resourceLoader.getResource(loc);
// 検証後にキャッシュ
try {
if (resource.exists()) {
return resource;
}
} catch (IOException e) {
throw new RuntimeException("リソース検証エラー:" + loc, e);
}
throw new IllegalArgumentException("リソースが見つかりません:" + loc);
});
}
}ベストプラクティス
1. クラスパスリソースを優先
// ✅ 良い:ポータブルで JAR で動作
@Value("classpath:config/app.properties")
Resource config;
// ⚠️ 避ける:開発中だけ動作
@Value("file:src/main/resources/config/app.properties")
Resource configFile;2. 外部設定にプレースホルダー使用
@Value("${app.template.location:classpath:templates/default.html}")
Resource template;3. 安全ストリーム処理
public void processResource(Resource resource) {
try (InputStream is = resource.getInputStream()) {
// リソースを処理
// ストリームは自動クローズ
} catch (IOException e) {
throw new RuntimeException("リソース処理エラー:" + resource.getDescription(), e);
}
}4. リソース検証
@PostConstruct
public void validateResources() {
if (!requiredResource.exists()) {
throw new IllegalStateException("必須リソースが見つかりません:" + requiredResource.getDescription());
}
}Spring Boot との統合
Spring Boot はさらにリソース処理を簡素化:
@ConfigurationProperties(prefix = "app.resources")
@Component
public class ResourceProperties {
private String templatesLocation = "classpath:templates/";
private String staticLocation = "classpath:static/";
private String externalLocation = "file:/var/app/resources/";
// getter と setter
public Resource getTemplate(String name) {
return new PathMatchingResourcePatternResolver()
.getResource(templatesLocation + name);
}
}パフォーマンス考慮
- ClassPathResource: JAR 内リソースで高速
- FileSystemResource: 大ファイルとランダムアクセスで高速
- キャッシュ: 頻繁にアクセスするリソースでキャッシュ実装
- 遅延ロード: 必要時のみリソースロード
結論
Spring のリソース抽象は異なるタイプのリソースを扱うための優雅で統一された方法を提供します。Resource と ResourceLoader を使用することで:
- 場所に依存しないリソースアクセス
- よりポータブルでテスト可能なコード作成
- Spring の依存注入の恩恵
- ワイルドカードでの柔軟なロードパターン
この抽象は設定、テンプレート、静的リソースを一貫してアクセスする必要がある堅牢で保守しやすい Spring アプリケーション構築に不可欠です。
推奨: 歧義を避けコードのポータビリティを向上させるために、可能な限り常に明示的プレフィックス(classpath:、file:)付き @Value を使用してください。_