発生した問題
SpringのBeanのスコープ理解不足から発生した問題。
複数ユーザーが同時にPDFファイルをダウンロードした際に、異なるユーザーのファイルが取得される事象が発生。
問題の影響
- ユーザーA: 発注書をダウンロード要求
- ユーザーB: 見積書をダウンロード要求(同時刻)
- 結果: ユーザーAに見積書が誤って配信
原因: スコープ設定の誤り
問題のあるコード構造
@Component // デフォルトでsingleton
public class FileBean {
// クラス変数でファイルパスを保持
private String downloadFilePath; // スレッドセーフでない
public void setDownloadFilePath(String path) {
this.downloadFilePath = path;
}
public void downloadFile() {
// downloadFilePathを使用してファイルをダウンロード
}
}
設計上の問題点
-
シングルトンBeanでの状態保持
- デフォルトのシングルトンスコープを使用
- クラス変数で状態を保持
- 複数スレッドで共有される変数が保護されていない
-
スレッドセーフ性の欠如
- 並行アクセスに対する考慮がない
- 変数の可視性が適切に制御されていない
SpringのBeanスコープ解説
スコープ | 説明 | 用途 |
---|---|---|
singleton | コンテナごとに1インスタンス | 状態を持たないサービス |
prototype | 取得ごとに新規インスタンス | 状態を持つ必要がある場合 |
request | HTTPリクエストごとに1インスタンス | リクエスト固有の処理 |
session | HTTPセッションごとに1インスタンス | ユーザー固有の処理 |
application | ServletContextごとに1インスタンス | アプリケーション共通処理 |
正しい実装パターン
1. スコープの適切な設定
@Component
@Scope("prototype") // または @RequestScope
public class FileBean {
private String downloadFilePath;
// ... 他のメソッド
}
2. スレッドローカル変数の使用
@Component
public class FileBean {
private ThreadLocal<String> downloadFilePath = new ThreadLocal<>();
public void setDownloadFilePath(String path) {
downloadFilePath.set(path);
}
public String getDownloadFilePath() {
return downloadFilePath.get();
}
public void clearDownloadFilePath() {
downloadFilePath.remove(); // 重要: リソースの解放
}
}
3. メソッドパラメータでの受け渡し
@Component
public class FileBean {
public void downloadFile(String filePath) {
// filePathを直接使用
}
}
実装時の注意点
-
シングルトンBeanの原則
- 状態を持たない設計を心がける
- 必要な状態は適切なスコープで管理
-
スレッドセーフ性の確保
- 共有リソースへのアクセスを保護
- 適切な同期メカニズムの使用
- 不変オブジェクトの活用
-
リソース管理
- ThreadLocalを使用する場合は確実な解放
- メモリリークの防止
参考文献