TargetSource 実装の使用

Spring は、org.springframework.aop.TargetSource インターフェースで表現される TargetSource の概念を提供します。このインターフェースは、ジョインポイントを実装する「ターゲットオブジェクト」を返すロールを果たします。TargetSource 実装は、AOP プロキシがメソッド呼び出しを処理するたびにターゲットインスタンスを要求されます。

Spring AOP を使用する開発者は、通常 TargetSource 実装を直接操作する必要はありませんが、これはプーリング、ホットスワップ可能、その他の高度なターゲットをサポートする強力な手段を提供します。例: プールを使用してインスタンスを管理することにより、プーリング TargetSource は呼び出しごとに異なるターゲットインスタンスを返すことができます。

TargetSource を指定しない場合、デフォルトの実装がローカルオブジェクトのラップに使用されます。(予想どおり)呼び出しごとに同じターゲットが返されます。

このセクションの残りの部分では、Spring で提供される標準ターゲットソースとその使用方法について説明します。

カスタムターゲットソースを使用する場合、通常、ターゲットはシングルトン Bean 定義ではなくプロトタイプである必要があります。これにより、Spring は必要に応じて新しいターゲットインスタンスを作成できます。

ホットスワップ可能なターゲットソース

org.springframework.aop.target.HotSwappableTargetSource は、AOP プロキシのターゲットを切り替えながら、発信者がそれへの参照を保持できるようにするために存在します。

ターゲットソースのターゲットの変更はすぐに有効になります。HotSwappableTargetSource はスレッドセーフです。

次の例に示すように、HotSwappableTargetSource で swap() メソッドを使用してターゲットを変更できます。

  • Java

  • Kotlin

HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)

次の例は、必要な XML 定義を示しています。

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
	<constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="targetSource" ref="swapper"/>
</bean>

上記の swap() 呼び出しは、交換可能な Bean のターゲットを変更します。その Bean への参照を保持しているクライアントは、変更を認識しませんが、すぐに新しいターゲットにヒットし始めます。

この例ではアドバイスを追加しませんが(TargetSource を使用するためにアドバイスを追加する必要はありません)、TargetSource は任意のアドバイスと組み合わせて使用できます。

ターゲットソースのプーリング

プーリングターゲットソースを使用すると、ステートレスセッション EJB と同様のプログラミングモデルが提供され、同一インスタンスのプールが維持され、メソッド呼び出しがプール内のオブジェクトを解放します。

Spring プーリングと SLSB プーリングの重要な違いは、Spring プーリングを任意の POJO に適用できることです。一般的な Spring と同様に、このサービスは非侵襲的な方法で適用できます。

Spring は、かなり効率的なプーリング実装を提供する Commons Pool 2.2 のサポートを提供します。この機能を使用するには、アプリケーションのクラスパスに commons-pool Jar が必要です。org.springframework.aop.target.AbstractPoolingTargetSource をサブクラス化して、他のプーリング API をサポートすることもできます。

Commons Pool 1.5+ もサポートされていますが、Spring Framework 4.2 で非推奨になりました。

次のリストは、構成の例を示しています。

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
		scope="prototype">
	... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
	<property name="targetBeanName" value="businessObjectTarget"/>
	<property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="targetSource" ref="poolTargetSource"/>
	<property name="interceptorNames" value="myInterceptor"/>
</bean>

ターゲットオブジェクト(前の例では businessObjectTarget)はプロトタイプでなければならないことに注意してください。これにより、PoolingTargetSource 実装はターゲットの新しいインスタンスを作成し、必要に応じてプールを拡大できます。AbstractPoolingTargetSource の javadoc およびそのプロパティに関する情報については、使用する具象サブクラスを参照してください。maxSize は最も基本的なものであり、常に存在することが保証されています。

この場合、myInterceptor は同じ IoC コンテキストで定義する必要があるインターセプターの名前です。ただし、プーリングを使用するインターセプターを指定する必要はありません。プーリングのみを行い、その他のアドバイスは必要ない場合は、interceptorNames プロパティを設定しないでください。

プールされたオブジェクトを org.springframework.aop.target.PoolingConfig インターフェースにキャストできるように Spring を構成することができます。これにより、導入を通じてプールの構成と現在のサイズに関する情報が公開されます。次のようなアドバイザを定義する必要があります。

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
	<property name="targetObject" ref="poolTargetSource"/>
	<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

このアドバイザは、AbstractPoolingTargetSource クラスでコンビニエンスメソッドを呼び出すことによって取得されるため、MethodInvokingFactoryBean が使用されます。このアドバイザーの名前(ここでは poolConfigAdvisor)は、プールされたオブジェクトを公開する ProxyFactoryBean のインターセプター名のリストに含まれている必要があります。

キャストは次のように定義されます。

  • Java

  • Kotlin

PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
通常、ステートレスサービスオブジェクトをプールする必要はありません。ほとんどのステートレスオブジェクトは本来スレッドセーフであり、リソースがキャッシュされている場合はインスタンスプーリングに問題があるため、これがデフォルトの選択であるべきではないと考えています。

自動プロキシを使用すると、よりシンプルなプーリングが可能になります。自動プロキシ作成者が使用する TargetSource 実装を設定できます。

プロトタイプターゲットソース

「プロトタイプ」ターゲットソースのセットアップは、プーリング TargetSource のセットアップに似ています。この場合、メソッド呼び出しのたびにターゲットの新しいインスタンスが作成されます。新しいオブジェクトを作成するコストは最新の JVM では高くありませんが、新しいオブジェクトを接続する(IoC 依存関係を満たす)コストはより高くなる可能性があります。非常に正当な理由がない限り、このアプローチを使用しないでください。

これを行うには、前述の poolTargetSource 定義を次のように変更できます(わかりやすくするために名前も変更しました)。

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
	<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

唯一のプロパティは、ターゲット Bean の名前です。TargetSource 実装では、一貫性のある命名を保証するために継承が使用されます。プーリングターゲットソースと同様に、ターゲット Bean はプロトタイプ Bean 定義でなければなりません。

ThreadLocal ターゲットソース

ThreadLocal ターゲットソースは、受信リクエストごとに(つまり、スレッドごとに)オブジェクトを作成する必要がある場合に役立ちます。ThreadLocal の概念は、スレッドとともにリソースを透過的に格納する JDK 全体の機能を提供します。ThreadLocalTargetSource のセットアップは、次の例が示すように、他の型のターゲットソースで説明したものとほとんど同じです。

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
	<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal インスタンスは、マルチスレッドおよびマルチクラスローダー環境で誤って使用すると、深刻な課題 (潜在的にメモリリークを引き起こす) を伴います。常に ThreadLocal を他のクラスでラップすることを検討し、ThreadLocal 自体を直接使用しないでください (ラッパークラスを除く)。また、スレッドのローカルリソースを正しく設定および設定解除 (後者は ThreadLocal.remove() の呼び出しを含む) することを常に忘れないでください。設定解除は必ず行う必要があります。設定解除しないと、課題のある動作が発生する可能性があります。Spring の ThreadLocal サポートはこれを実行し、他の適切な処理コードなしで ThreadLocal インスタンスを使用することを常に優先して検討する必要があります。