Bean スコープ
Bean 定義を作成するとき、その Bean 定義によって定義されたクラスの実際のインスタンスを作成するためのレシピを作成します。Bean 定義がレシピであるという考え方は重要です。これは、クラスと同様に、単一のレシピから多くのオブジェクトインスタンスを作成できることを意味するためです。
特定の Bean 定義から作成されたオブジェクトにプラグインされるさまざまな依存関係と構成値を制御できるだけでなく、特定の Bean 定義から作成されたオブジェクトのスコープも制御できます。このアプローチは強力で柔軟です。なぜなら、Java クラスレベルでオブジェクトのスコープをベイク処理する代わりに、構成を通じて作成するオブジェクトのスコープを選択できるからです。Bean は、いくつかのスコープのいずれかにデプロイされるように定義できます。Spring Framework は 6 つのスコープをサポートしますが、そのうち 4 つは Web 対応の ApplicationContext
を使用する場合にのみ使用可能です。カスタムスコープを作成することもできます。
次の表に、サポートされているスコープを示します。
スコープ | 説明 |
---|---|
(デフォルト)Spring IoC コンテナーごとに、単一の Bean 定義を単一のオブジェクトインスタンスにスコープします。 | |
単一の Bean 定義を任意の数のオブジェクトインスタンスにスコープします。 | |
単一の Bean 定義を単一の HTTP リクエストのライフサイクルにスコープします。つまり、各 HTTP リクエストには、単一の Bean 定義の背後から作成された Bean の独自のインスタンスがあります。Web 対応 Spring | |
単一の Bean 定義を HTTP | |
単一の Bean 定義を | |
単一の Bean 定義を |
スレッドスコープは利用可能ですが、デフォルトでは登録されていません。詳細については、SimpleThreadScope (Javadoc) のドキュメントを参照してください。このスコープまたはその他のカスタムスコープを登録する方法については、カスタムスコープの使用を参照してください。 |
シングルトンスコープ
シングルトン Bean の 1 つの共有インスタンスのみが管理され、その Bean 定義に一致する 1 つ以上の ID を持つ Bean のすべてのリクエストにより、その 1 つの特定の Bean インスタンスが Spring コンテナーによって返されます。
別の言い方をすれば、Bean 定義を定義し、シングルトンとしてスコープされている場合、Spring IoC コンテナーは、その Bean 定義によって定義されたオブジェクトのインスタンスを 1 つだけ作成します。この単一のインスタンスは、そのようなシングルトン Bean のキャッシュに格納され、その名前付き Bean に対する以降のすべてのリクエストと参照は、キャッシュされたオブジェクトを返します。次の図は、シングルトンスコープの仕組みを示しています。
Spring のシングルトン Bean の概念は、Gang of Four(GoF)パターンブックで定義されているシングルトンパターンとは異なります。GoF シングルトンは、ClassLoader ごとに特定のクラスのインスタンスが 1 つだけ作成されるように、オブジェクトのスコープをハードコードします。Spring シングルトンの範囲は、コンテナーごとおよび Bean ごとと最もよく説明されています。つまり、単一の Spring コンテナー内の特定のクラスに対して 1 つの Bean を定義すると、Spring コンテナーはその Bean 定義によって定義されたクラスのインスタンスを 1 つだけ作成します。シングルトンスコープは、Spring のデフォルトスコープです。Bean を XML のシングルトンとして定義するには、次の例に示すように Bean を定義できます。
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
プロトタイプスコープ
Bean デプロイの非シングルトンプロトタイプスコープでは、その特定の Bean のリクエストが行われるたびに、新しい Bean インスタンスが作成されます。つまり、Bean が別の Bean に注入されるか、コンテナーで getBean()
メソッド呼び出しを介してリクエストされます。原則として、すべてのステートフル Bean にはプロトタイプスコープを使用し、ステートレス Bean にはシングルトンスコープを使用する必要があります。
次の図は、Spring プロトタイプスコープを示しています。
(典型的な DAO は会話状態を保持しないため、データアクセスオブジェクト(DAO)は通常、プロトタイプとして構成されません。シングルトンダイアグラムのコアを再利用する方が簡単でした)
次の例では、Bean を XML のプロトタイプとして定義しています。
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
他のスコープとは異なり、Spring はプロトタイプ Bean の完全なライフサイクルを管理しません。コンテナーは、プロトタイプオブジェクトをインスタンス化し、構成し、その他の方法で組み立ててクライアントに渡しますが、そのプロトタイプインスタンスのそれ以上の記録はありません。初期化ライフサイクルコールバックメソッドはスコープに関係なくすべてのオブジェクトで呼び出されますが、プロトタイプの場合、構成された破棄ライフサイクルコールバックは呼び出されません。クライアントコードは、プロトタイプスコープのオブジェクトをクリーンアップし、プロトタイプ Bean が保持する高負荷なリソースを解放する必要があります。Spring コンテナーがプロトタイプスコープの Bean によって保持されているリソースを解放できるようにするには、クリーンアップする必要がある Bean への参照を保持するカスタム Bean ポストプロセッサーを使用してみてください。
いくつかの点で、プロトタイプスコープの Bean に関する Spring コンテナーのロールは、Java new
オペレーターに代わるものです。その時点以降のすべてのライフサイクル管理は、クライアントが処理する必要があります。(Spring コンテナー内の Bean のライフサイクルの詳細については、ライフサイクルコールバックを参照してください。)
プロトタイプ Bean 依存関係を持つシングルトン Bean
プロトタイプ Bean に依存するシングルトンスコープ Bean を使用する場合、インスタンス化時に依存関係が解決されることに注意してください。プロトタイプスコープの Bean をシングルトンスコープの Bean に依存性注入すると、新しいプロトタイプ Bean がインスタンス化され、シングルトン Bean に依存性注入されます。プロトタイプインスタンスは、シングルトンスコープの Bean に提供される唯一のインスタンスです。
ただし、シングルトンスコープの Bean で、実行時にプロトタイプスコープの Bean の新しいインスタンスを繰り返し取得するとします。プロトタイプスコープの Bean をシングルトン Bean に依存性注入することはできません。これは、Spring コンテナーがシングルトン Bean をインスタンス化し、その依存性を解決して注入するときに、その注入が 1 回だけ行われるためです。実行時にプロトタイプ Bean の新しいインスタンスが複数回必要な場合は、メソッドインジェクションを参照してください。
リクエスト、セッション、アプリケーション、WebSocket スコープ
request
、session
、application
、websocket
スコープは、Web 対応の Spring ApplicationContext
実装(XmlWebApplicationContext
など)を使用する場合にのみ使用できます。これらのスコープを ClassPathXmlApplicationContext
などの通常の Spring IoC コンテナーで使用すると、不明な Bean スコープについて文句を言う IllegalStateException
がスローされます。
Web の初期設定
request
、session
、application
、websocket
レベルでの Bean のスコープ(Web スコープの Bean)をサポートするには、Bean を定義する前にいくつかのマイナーな初期構成が必要です。(この初期セットアップは、標準スコープ singleton
および prototype
には必要ありません。)
この初期設定を達成する方法は、特定のサーブレット環境によって異なります。
Spring Web MVC 内の Spring DispatcherServlet
によって処理されるリクエスト内で、スコープ付き Bean にアクセスする場合、特別な設定は必要ありません。DispatcherServlet
は、関連するすべての状態をすでに公開しています。
Spring の DispatcherServlet
の外部で処理されるリクエストでサーブレット Web コンテナーを使用する場合 (たとえば、JSF を使用する場合)、org.springframework.web.context.request.RequestContextListener
ServletRequestListener
を登録する必要があります。これは、WebApplicationInitializer
インターフェースを使用してプログラムで行うことができます。または、Web アプリケーションの web.xml
ファイルに次の宣言を追加します。
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
または、リスナーの設定に課題がある場合は、Spring の RequestContextFilter
の使用を検討してください。フィルターマッピングは、周囲の Web アプリケーションの構成に依存するため、必要に応じて変更する必要があります。次のリストは、Web アプリケーションのフィルター部分を示しています。
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet
、RequestContextListener
、RequestContextFilter
はすべてまったく同じことを行います。つまり、HTTP リクエストオブジェクトを、そのリクエストを処理している Thread
にバインドします。これにより、リクエストスコープおよびセッションスコープの Bean がチェーン呼び出しのさらに下で使用可能になります。
リクエストスコープ
Bean 定義の次の XML 構成を検討してください。
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring コンテナーは、すべての HTTP リクエストの loginAction
Bean 定義を使用して、LoginAction
Bean の新しいインスタンスを作成します。つまり、loginAction
Bean は、HTTP リクエストレベルでスコープされます。同じ loginAction
Bean 定義から作成された他のインスタンスはこれらの状態の変化を認識しないため、作成されたインスタンスの内部状態を必要なだけ変更できます。これらは個々のリクエストに固有のものです。リクエストの処理が完了すると、リクエストのスコープにある Bean は破棄されます。
アノテーション駆動型コンポーネントまたは Java 構成を使用する場合、@RequestScope
アノテーションを使用して、コンポーネントを request
スコープに割り当てることができます。次の例は、その方法を示しています。
Java
Kotlin
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
セッションスコープ
Bean 定義の次の XML 構成を検討してください。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring コンテナーは、単一の HTTP Session
の存続期間に userPreferences
Bean 定義を使用して、UserPreferences
Bean の新しいインスタンスを作成します。言い換えると、userPreferences
Bean は HTTP Session
レベルで効果的にスコープされます。リクエストスコープ Bean と同様に、同じ userPreferences
Bean 定義から作成されたインスタンスも使用している他の HTTP Session
インスタンスはこれらの状態の変化を認識しないため、作成されるインスタンスの内部状態を必要なだけ変更できます。なぜなら、それらは個々の HTTP Session
に特有です。HTTP Session
が最終的に破棄されると、その特定の HTTP Session
にスコープされた Bean も破棄されます。
アノテーション駆動型コンポーネントまたは Java 構成を使用する場合、@SessionScope
アノテーションを使用して、コンポーネントを session
スコープに割り当てることができます。
Java
Kotlin
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
アプリケーションスコープ
Bean 定義の次の XML 構成を検討してください。
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring コンテナーは、Web アプリケーション全体に対して appPreferences
Bean 定義を 1 回使用することにより、AppPreferences
Bean の新しいインスタンスを作成します。つまり、appPreferences
Bean は ServletContext
レベルでスコープされ、通常の ServletContext
属性として保管されます。これは Spring シングルトン Bean にいくぶん似ていますが、2 つの重要な点で異なります。Spring ApplicationContext
ごとではなく ServletContext
ごとのシングルトンであり(特定の Web アプリケーションに複数存在する可能性があります)、実際に公開されているため、次のように表示されます。ServletContext
属性。
アノテーション駆動型コンポーネントまたは Java 構成を使用する場合、@ApplicationScope
アノテーションを使用して、コンポーネントを application
スコープに割り当てることができます。次の例は、その方法を示しています。
Java
Kotlin
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
WebSocket スコープ
WebSocket スコープは WebSocket セッションのライフサイクルに関連付けられており、WebSocket アプリケーションを介した STOMP に適用されます。詳細については、WebSocket スコープを参照してください。
依存関係としてのスコープ Bean
Spring IoC コンテナーは、オブジェクト(Bean)のインスタンス化だけでなく、コラボレーター(または依存関係)の接続も管理します。(たとえば)HTTP リクエストスコープの Bean を、より寿命の長いスコープの別の Bean に注入する場合、スコープ付き Bean の代わりに AOP プロキシを注入することを選択できます。つまり、スコープオブジェクトと同じパブリックインターフェースを公開するプロキシオブジェクトを挿入する必要がありますが、関連するスコープ(HTTP リクエストなど)から実際のターゲットオブジェクトを取得し、メソッド呼び出しを実際のオブジェクトに委譲することもできます。
また、スコーププロキシは、より短いスコープから Bean にライフサイクルセーフな方法でアクセスする唯一の方法ではありません。また、インジェクションポイント (つまり、コンストラクターまたは setter 引数、あるいは自動フィールドです) を 拡張バリアントとして、 これの JSR-330 バリアントは |
次の例の構成は 1 行のみですが、その背後にある「理由」と「方法」を理解することが重要です。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/> (1)
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
1 | プロキシを定義する行。 |
このようなプロキシを作成するには、子 <aop:scoped-proxy/>
要素をスコープ付き Bean 定義に挿入します ( 作成するプロキシの型の選択および XML スキーマベースの構成を参照)。
一般的なシナリオでは、request
、session
、カスタムスコープレベルでスコープ指定された Bean の定義に <aop:scoped-proxy/>
要素が必要なのはなぜですか ? 次のシングルトン Bean 定義を検討し、前述のスコープに対して定義する必要があるものと対比してください (次の userPreferences
Bean 定義は、そのままでは不完全であることに注意してください)。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
前の例では、シングルトン Bean(userManager
)に HTTP Session
-scoped Bean(userPreferences
)への参照が挿入されています。ここでの重要な点は、userManager
Bean がシングルトンであるということです。コンテナーごとに 1 回だけインスタンス化され、その依存関係(この場合は 1 つだけ、userPreferences
Bean)も 1 回だけ注入されます。これは、userManager
Bean が、まったく同じ userPreferences
オブジェクト(つまり、最初に注入されたオブジェクト)でのみ動作することを意味します。
これは、寿命の短いスコープ Bean を寿命の長いスコープ Bean に注入するときの動作ではありません(たとえば、Bean を依存関係としてシングルトン Bean にコラボレーションする HTTP Session
-scoped を注入します)。むしろ、単一の userManager
オブジェクトが必要であり、HTTP Session
の存続期間中、HTTP Session
に固有の userPreferences
オブジェクトが必要です。コンテナーは、UserPreferences
クラス(理想的には UserPreferences
インスタンスであるオブジェクト)とまったく同じパブリックインターフェースを公開するオブジェクトを作成します。オブジェクトは、スコープメカニズム(HTTP リクエスト、Session
など)から実際の UserPreferences
オブジェクトをフェッチできます。コンテナーは、このプロキシオブジェクトを userManager
Bean に注入します。これは、この UserPreferences
参照がプロキシであることを認識していません。この例では、UserManager
インスタンスが依存関係が注入された UserPreferences
オブジェクトのメソッドを呼び出すとき、実際にはプロキシのメソッドを呼び出しています。次に、プロキシは(この場合)HTTP Session
から実際の UserPreferences
オブジェクトをフェッチし、取得した実際の UserPreferences
オブジェクトにメソッド呼び出しを委譲します。
次の例に示すように、request-
Bean および session-scoped
Bean をコラボレーションオブジェクトに注入する場合は、次の(正しい完全な)構成が必要です。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
作成するプロキシの型の選択
デフォルトでは、Spring コンテナーが <aop:scoped-proxy/>
要素でマークアップされた Bean のプロキシを作成すると、CGLIB ベースのクラスプロキシが作成されます。
CGLIB プロキシはプライベートメソッドをインターセプトしません。このようなプロキシ上でプライベートメソッドを呼び出そうとしても、実際のスコープ付きターゲットオブジェクトには委譲されません。 |
または、<aop:scoped-proxy/>
要素の proxy-target-class
属性の値に false
を指定することにより、Spring コンテナーを設定して、そのようなスコープ Bean の標準 JDK インターフェースベースのプロキシを作成できます。JDK インターフェースベースのプロキシを使用すると、そのようなプロキシに影響を与えるためにアプリケーションクラスパスに追加のライブラリを必要としないことを意味します。ただし、スコープ付き Bean のクラスは少なくとも 1 つのインターフェースを実装する必要があり、スコープ付き Bean が挿入されるすべてのコラボレーターは、そのインターフェースの 1 つを介して Bean を参照する必要があります。次の例は、インターフェースに基づいたプロキシを示しています。
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
クラスベースまたはインターフェースベースのプロキシの選択の詳細については、プロキシメカニズムを参照してください。
リクエスト / セッション参照の直接挿入
ファクトリスコープの代替として、Spring WebApplicationContext
は、他の Bean の通常のインジェクションポイントの横にある型ベースのオートワイヤーを通じて、Spring 管理の Bean への HttpServletRequest
、HttpServletResponse
、HttpSession
、WebRequest
と (JSF が存在する場合) FacesContext
および ExternalContext
の注入もサポートします。Spring は通常、そのようなリクエストおよびセッションオブジェクトにプロキシを挿入します。これには、ファクトリスコープ Bean のスコーププロキシと同様に、シングルトン Bean および直列化可能 Bean でも動作するという利点があります。
カスタムスコープ
Bean スコーピングメカニズムは拡張可能です。独自のスコープを定義することも、既存のスコープを再定義することもできますが、後者は悪い習慣と見なされ、組み込みの singleton
および prototype
スコープをオーバーライドすることはできません。
カスタムスコープの作成
カスタムスコープを Spring コンテナーに統合するには、このセクションで説明する org.springframework.beans.factory.config.Scope
インターフェースを実装する必要があります。独自のスコープを実装する方法のアイデアについては、Spring Framework 自体と Scope
javadoc で提供される Scope
実装を参照してください。これにより、実装する必要があるメソッドが詳細に説明されます。
Scope
インターフェースには、スコープからオブジェクトを取得し、スコープから削除し、破棄するための 4 つのメソッドがあります。
たとえば、セッションスコープの実装は、セッションスコープの Bean を返します(存在しない場合、メソッドは、Bean の新しいインスタンスを、将来の参照のためにセッションにバインドした後に返します)。次のメソッドは、基になるスコープからオブジェクトを返します。
Java
Kotlin
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
たとえば、セッションスコープの実装は、基になるセッションからセッションスコープの Bean を削除します。オブジェクトが返されますが、指定された名前のオブジェクトが見つからない場合は null
を返すことができます。次のメソッドは、基になるスコープからオブジェクトを削除します。
Java
Kotlin
Object remove(String name)
fun remove(name: String): Any
次のメソッドは、スコープが破棄されたとき、またはスコープ内の指定されたオブジェクトが破棄されたときにスコープが呼び出すコールバックを登録します。
Java
Kotlin
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
破棄コールバックの詳細については、javadoc または Spring スコープの実装を参照してください。
次のメソッドは、基になるスコープの会話識別子を取得します。
Java
Kotlin
String getConversationId()
fun getConversationId(): String
この識別子はスコープごとに異なります。セッションスコープの実装の場合、この識別子はセッション識別子にすることができます。
カスタムスコープの使用
1 つ以上のカスタム Scope
実装を作成してテストした後、Spring コンテナーに新しいスコープを認識させる必要があります。以下の方法は、新しい Scope
を Spring コンテナーに登録する中心的な方法です。
Java
Kotlin
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
このメソッドは、Spring に同梱されているほとんどの具体的な ApplicationContext
実装の BeanFactory
プロパティを介して利用可能な ConfigurableBeanFactory
インターフェースで宣言されています。
registerScope(..)
メソッドの最初の引数は、スコープに関連付けられた一意の名前です。Spring コンテナー自体のこのような名前の例は、singleton
および prototype
です。registerScope(..)
メソッドの 2 番目の引数は、登録して使用するカスタム Scope
実装の実際のインスタンスです。
カスタム Scope
実装を作成し、次の例に示すように登録するとします。
次の例では、Spring に含まれていますが、デフォルトでは登録されていない SimpleThreadScope を使用します。手順は、独自のカスタム Scope 実装でも同じです。 |
Java
Kotlin
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
その後、次のように、カスタム Scope
のスコープ規則に準拠する Bean 定義を作成できます。
<bean id="..." class="..." scope="thread">
カスタム Scope
実装を使用すると、スコープのプログラムによる登録に限定されません。次の例に示すように、CustomScopeConfigurer
クラスを使用して、Scope
の登録を宣言的に行うこともできます。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
FactoryBean 実装の <bean> 宣言内に <aop:scoped-proxy/> を配置すると、スコープが設定されるのはファクトリ Bean 自体であり、getObject() から返されるオブジェクトではありません。 |