springのシングルトン問題を@Scopeを使って回避する

spring

springのDIは、デフォルト「シングルトン(singleton)」になっています。
シングルトン(singleton)自体が悪ではないのですが、意識せずに使ってしまうと間違いなくバグります。

このページでは、「シングルトン問題」といっているので、シングルトンによるバグを想定した回避方法を紹介していきます。

[スポンサーリンク]

springのシングルトン(singleton)問題、springを使っている人は一度は経験したことあるのではないでしょうか。
それもリリース前とかに、、、(ヒー)

この章でやること

springの初期設定のシングルトンを@Scopeを使って、無効にする方法を紹介します。

シングルトン問題が発生するソース

Aservice はメンバ変数を扱います。
メンバ変数へのアクセスは、setとgetを使用します。

@Service
public class Aservice {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

上記のクラスをコントローラクラス内から呼び出して使用します。
問題が発生しやすいように、sleep処理を入れます。

@Controller
public class HomeController {
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@Autowired
	private Aservice service;

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(@RequestParam("test") String data,
	                    Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);

		// 名前をセットする
		service.setName(data);

	      // sleep
        try{
            Thread.sleep(3000);
            }catch(InterruptedException e){}
        
        // 名前を取得してログへ出力する。
		System.out.println(service.getName());
		return "home";
	}
}

上記のクラスを2回連続で呼び出してみる

上記のコントローラクラスをTomcat上で動かした後、ブラウザを2つ開いて、2回連続してリクエストを投げてみます。
1回目のリクエスト、http://localhost:8080/blog/?test=1
間髪入れずに2回目のリクエスト、http://localhost:8080/blog/?test=2

ローカル環境でコントローラクラスを動作させる方法は、コチラを参考にして下さい。

実行結果

>実行結果
2
2

解説

@Serviceも@Controllerもシングルトンとして扱われますので、1つのインスタンスを2つのリクエストが共有することとなります。
set・getされている、メンバ変数の「name」も1つのインスタンスになります。

2回連続でリクエストを投げた場合、結果が同じになってしまうのは、1つのインスタンスのメンバ変数「name」を上書きしていることが原因です。
よって、2回連続で実行すると、後で実行したリクエストの内容で、メンバ変数が書き換わってしまいます。

シングルトン問題を回避する。

@Scope("prototype")を追加します。

@Scope("prototype")
@Service
public class Aservice {
・
・
・
}

コントローラクラスも同様に、@Scope("prototype")を追加します。

@Scope("prototype")
@Controller
public class HomeController {
・
・
・
}

上記のクラスを2回連続で呼び出してみる

先ほどと同じように、ブラウザを2つ開いて、2回連続してリクエストを投げてみます。
1回目のリクエスト、http://localhost:8080/blog/?test=1
間髪入れずに2回目のリクエスト、http://localhost:8080/blog/?test=2

実行結果

>実行結果
1
2

解説

今回はそれぞれのリクエストが独立した動きとなりました。
これは期待通りの動きではないでしょうか。
@Scope("prototype")を入れることにより、各インスタンスがシングルトン(共有している状態)ではなく、呼び出される毎に別インスタンスが生成されます。
よって、こちらは値が上書きされることなく、それぞれのリクエスト毎の値がset・get出来ます。

@Scope一覧

@Scope("XXXX")に指定出来るプロパティの一覧です。

singleton
Beanのインスタンスをコンテナに対して1つだけ作ります。

prototype
Beanが呼び出される毎にインスタンスを作ります。

request
1回のHTTPリクエスト毎にインスタンスを作ります。

session
HTTPセッション毎にインスタンスを作ります。

springMVCのバージョン

org.springframework-versionは、3.1.0を使っています。
javaは、1.7を使っています。

最後に

springのシングルトン問題、規模の大きなシステムになるほど発生しやすくなります。
(単純にリクエストが多いので、リクエストが同時刻に発生する可能性が高いです。)

個人的には、シングルトン(singleton)を使わなければならない場面意外では、シングルトンは使うべきではないと考えています。
単体レベルのテストでは発見しにくいため、思わぬバグを生み出します。
シングルトン(singleton)問題はうまく回避していきましょう!
それでは!