Spring FrameworkのDIや、Java EEのCDIの対象となるBeanには「スコープ」が定義されてます。
この「スコープ」の定義によって、Beanのライフサイクルが変わることは広く知られていると思います。
例えば、アプリケーションスコープのBeanは、アプリケーション内で1つだけインスタンスが作成され、そのインスタンスが使いまわされます。 リクエストスコープのBeanは、Webのリクエスト単位で1つのインスタンスが作成され、リクエスト内ではそのインスタンスが使いまわされ、別リクエストであれば別のインスタンスが使われます。
異なるスコープを持つBeanのインジェクトの勘違い
以下のようなリクエストスコープのBeanがあったとします。
@RequestScope public class ReqScope { ・ ・ ・ }
このリクエストスコープのBeanをアプリケーションスコープのBeanがインジェクトするとします。
@ApplicationScope public class AppScope { @Autowired private ReqScope reqScope; ・ ・ ・ }
アプリケーションスコープのインスタンスは1つなので、アプリケーションスコープのBeanでインジェクトしたリクエストスコープも1つになると思ってる人もいると思います。
なぜなら、このサンプルソースのAppScopeクラスが持つフィールド「reqScope」も1つだから、アプリケーションスコープ1つに対して、リクエストスコープを複数持てないからです。
次の図のようになると思っている人もいると思います。
しかし、これは勘違いなんです。
異なるスコープを持つBeanのインジェクトの実際
では、実際には各スコープのインスタンスはどのようになるのでしょうか。
おそらく、次の図のようなスコープの構成を期待していると思います。そして、実際に期待通りのスコープの構成となります。
アプリケーションスコープのインジェクトフィールドに複数のリクエストスコープのインスタンスが結びついてます。
配列でもない1つのJavaの変数に複数のオブジェクトは格納できません。しかし、実際にリクエストごとに別のインスタンスが結びつきます。この仕組みについては後程解説します。
まずは、アプリケーションスコープ1つに対して、複数のリクエストスコープが結びついているのかを確認します。
それぞれのスコープのメソッドでオブジェクトIDを出力します。
@ApplicationScope public class AppScope { @Autowired private ReqScope reqScope; public String getMessage(String request) { System.out.println("Start Request " + request + " AppScope Object ID : " + ObjectUtils.getIdentityHexString(this)); String message = reqScope.getMessage(request); System.out.println("End Request " + request + " AppScope Object ID : " + ObjectUtils.getIdentityHexString(this)); return message; } }
@RequestScope public class ReqScope { public String getMessage(String request) { System.out.println("Start Request " + request + " ReqScope Object ID : " + ObjectUtils.getIdentityHexString(this)); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("End Request " + request + " ReqScope Object ID : " + ObjectUtils.getIdentityHexString(this)); return request; } }
ほぼ同じタイミングで2つのリクエストを実行してみます。
Start Request A AppScope Object ID : 7aaa1b39 Start Request A ReqScope Object ID : 468bf1a6 Start Request B AppScope Object ID : 7aaa1b39 Start Request B ReqScope Object ID : 475926a2 End Request A ReqScope Object ID : 468bf1a6 End Request A AppScope Object ID : 7aaa1b39 End Request B ReqScope Object ID : 475926a2 End Request B AppScope Object ID : 7aaa1b39
AppScopeのオブジェクトIDは、すべて7aaa1b39
と同じことから、アプリケーションスコープのインスタンスは1つであることがわかります。
それに対して、ReqScopeのオブジェクトIDは、リクエストAでは468bf1a6
、リクエストBでは475926a2
であることから、リクエストスコープのインスタンスは1リクエストにつき1つであることがわかります。
異なるスコープを持つBeanのインジェクトの仕組み
検証により、アプリケーションスコープのBeanにリクエストスコープのBeanをインジェクトしても、正しいスコープで動作していることがわかりました。
Spring FrameworkのDI、Java EEのCDIともに、広いスコープのBeanに、より狭いスコープのBeanをインジェクトすることができます。
このようなことを可能とする仕組みとして、インジェクトされるインスタンスはプロキシクラスであるためです。
実際のインスタンスの構成は次のようになります。
アプリケーションスコープのフィールドにインジェクトされるインスタンスは、対象クラスのプロキシになります。プロキシクラスは1つであり、1つしかないアプリケーションスコープのフィールドに格納されます。
そして、リクエストスコープのメソッドが呼び出されると、プロキシクラスのメソッドが呼び出され、そこでリクエストごとの振り分け処理が実行され、リクエストに応じたBeanのメソッドが呼び出されます。