Bean切り替え部品

概要

WebOTX Batch ServerではSpring Batchが提供するStepスコープの他にChunkスコープを利用することができます。
Chunkスコープを指定すると、Chunkの切り替わりのタイミングでBeanのインスタンスを切り替えることができます。

StepスコープとChunkスコープ

Spring Batchでは、ジョブステップ開始時にBeanをインスタンス化するStepスコープの指定が可能です。このStepスコープを利用すると、Beanの依存関係をジョブステップの初期化時に解決することができます。
例えば、次のようにscope="step"を指定し、#{jobParameters['input.file.name']}と記載するとことで、ジョブステップ初期化時にジョブ実行コマンドで指定したジョブパラメータのうちinput.file.nameで指定した値を#{jobParameters['input.file.name']}の箇所に設定することができます。

<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobParameters['input.file.name']}" />
</bean>

同様に、次のように指定することで、それぞれJobExecutionContext、StepExecutionContextのinput.file.nameキーに割り当てられた値をジョブステップ初期化時に解決できます。

<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobExecutionContext['input.file.name']}" />
</bean>
<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{stepExecutionContext['input.file.name']}" />
</bean>

Batch Serverでは、Stepスコープと同様の仕組みを利用したChunkスコープを提供し、チャンクの切り替わりのタイミングでBeanをインスタンス化する機能を提供しています。

Stepスコープを指定した場合とChunkスコープを指定した場合とでは、次の図のような違いがあります。

StepスコープとChunkスコープ

Stepスコープを指定した場合、対象のステップが実行後にBeanが初期化され、一度初期化されたBeanのインスタンスをそのステップを実行している間再利用し続けます。
一方、Chunkスコープを指定した場合、Chunkの開始後に初期化したBeanをChunk実行中の間は再利用し、Chunk毎に利用するBeanのインスタンスを分けることができます。

Stepスコープを利用した場合、ステップ実行中のスレッド以外からBeanを初期化しようとすると例外が発生し、そのBeanを利用することができません。一方、Chunkスコープを指定したBeanをChunk外から利用した場合、その都度(Beanのメソッドを呼び出す毎に)Beanを初期化します。

Chunkスコープ

Chunkスコープ機能を利用すると、同一のジョブステップの実行中であっても、個別のチャンクで異なるBeanを利用することができます。チャンクの切り替わるタイミングでBeanを初期化するため、チャンクスコープに設定されたBeanの依存関係の解決もこのタイミングで行うことができます。

ChunkスコープのBeanインスタンスは実行中のチャンク終了時に破棄されます。破棄のタイミングで、該当するBeanがSpring FrameworkのDisposableBeanを実装する場合にDisposableBean#destroy()メソッドを呼び出します。該当インスタンスの破棄タイミングに必要な処理がある場合は同インタフェースを実装してください。
※ Javaのオブジェクトが確保した実際のリソースが開放されるわけではありません。リソースの開放はGCのタイミングに依存します。

チャンク処理の外側で利用されるBeanにChunkスコープを設定した場合、そのBeanのメソッドが呼び出されるたびにそのBeanが初期化されます。このBeanインスタンスは、そのインスタンスに対するメソッド呼び出し終了時にインスタンスが破棄されます。DisposableBean#destroy() に時間のかかる処理を定義する場合、対象となるBeanのメソッドのレスポンスに影響する点を注意してください。

Chunkスコープを利用するにはバッチアプリケーションの定義ファイルに下記の一行を追加します。

<bean class="com.nec.webotx.batch.util.scope.BSChunkScope"/>

次にChunkスコープに設定したいBeanのscopeを"chunk"に設定します。

<bean id="target" class="..." scope="chunk"/>

BSChunkScopeはSpring Frameworkが提供するBeanFactoryPostProcessorの機能を利用して、スコープに"chunk"と指定されているBean定義を加工しています。
独自にBeanFactoryPostProcessorを利用している場合、次のように独自のBeanFactoryPostProcessorよりも後に処理されるようBSChunkScopeのorederプロパティを適切(BSChunkScopeのorderの値が大きくなるよう)に設定してください。

<bean class="sample.UserBeanFactoryPostProcessor">
    <property name="order" value="10"/>
</bean>

<bean class="com.nec.webotx.batch.util.BSChunkScope">
    <property name="order" value="100"/>
</bean>
BSChunkScopeのorderにはInteger.MIN_VALUE〜Integer.MAX_VALUEの値が設定可能です。orderの指定を省略した場合、デフォルト値のInteger.MAX_VALUEが設定されます。複数のBeanFactoryPostProcessorが同じorderの値を持つ場合、定義ファイルに記載されている順に適用されます。

Late Binding の拡張

Chunkスコープに設定したBeanはStepスコープと同様に、JobParameters、JobExecutionContext、StepExecutionContextに設定された値をBeanの初期化時に解決することができます。

<bean id="sample" class="..." scope="chunk" >
    <property name="resource" value="#{jobParameters['input.file.name']}" />
</bean>
<bean id="sample" class="..." scope="chunk" >
    <property name="resource" value="#{jobExecutionContext['input.file.name']}" />
</bean>
<bean id="sample" class="..." scope="chunk" >
    <property name="resource" value="#{stepExecutionContext['input.file.name']}" />
</bean>

ただし、ジョブ、および、ステップを実行していないスレッドからChunkスコープに設定したBeanを参照した場合、上記の値を解決した結果はnullが返却され、対象のBeanプロパティにnullを設定しようと試みます。
また、Chunkスコープでは上記の他にもChunkScopedValueによる値の解決を行うことができます。

<bean id="sample" class="..." scope="chunk" >
    <property name="resource" value="#{chunkScopedValue['input.file.name']}" />
</bean>

ChunkScopedValueの利用方法については次節を参照してください。

ChunkScopedValueの利用

ChunkScopedValueへ値を格納するには任意の箇所で下記のメソッドを呼び出します。

ChunkScopedValue.put("sample.jndi.name", value);

ChunkScopedValueに格納した値を取り出す場合は下記のメソッドを呼び出します

ChunkScopedValue.get("sample.jndi.name");

ChunkScopedValueは、チャンクの処理内ではputメソッドによる変更の影響を受けません。チャンク処理内で初めて利用する際に値の解決をし、チャンク処理中はその値を返却します。チャンク処理の外からの呼び出しにおいては、getメソッドの呼出し毎に値を解決します。

ChunkScopedValueはプロセス内で共有する唯一のMapオブジェクトを保持します。
つまり、同一のキーを利用してChunkScopedValueを解決する場合、ジョブ実行中に変更した値は別のジョブ実行中に解決した値にも反映されます。

ChunkScopedValueに格納した値は永続化されない点に注意してください。ChunkScopedValueの値の初期化や永続化を行うためには、適切なタイミングで永続化先への格納と読込を独自に行ってください。

Bean切替部品

Batch Serverでは、Chunkスコープを利用した2種類のBean切替部品を提供しています。

JNDIベースのBean切替部品

Chunkスコープを利用して、Chunkの切れ目のタイミングでJNDIに登録されているObjectを取得しなおすBean切替部品を提供しています。

例えば、下記のように設定することでChunkの中で利用するDataSourceオブジェクトを実行時に切り替えることができます。

<bean id="targetDataSource"
      class="com.nec.webotx.batch.util.scope.BSChunkScopedJndiObjectFactoryBean">
    <property name="targetInterface" value="javax.sql.DataSource"/>
    <property name="jndiNameKey" value="sample.jndi.name"/>
    <property name="defaultJndiName" value="jdbc/DS"/>
    <property name="jndiEnvironment">
        <props>
            <prop key="java.naming.factory.initial">jp.co.nec.WebOTX.jndi.SerialInitContextFactory</prop>
            <prop key="javax.rmi.CORBA.UtilClass">jp.co.nec.orb.rmi.UtilDelegateImpl</prop>
            <prop key="javax.rmi.CORBA.StubClass">jp.co.nec.orb.rmi.StubDelegateImpl</prop>
            <prop key="java.naming.provider.url">rmiiiop://localhost:7880</prop>
            <prop key="java.naming.security.principal">bsadmin</prop>
            <prop key="java.naming.security.credentials">bsadmin</prop>
        </props>
    </property>
</bean>

jndiNameKeyに設定した値("sample.jndi.name")をキーにして、ChunkScopedValue からJNDI名を取得し、そのJNDI名を利用してJNDIサーバからDataSourceをlookupします。
ChunkScopedValue に対応するjndiNameKeyの値が設定されていない場合、defaultJndiNameによるlookupを試みます。ChunkScopedValueがnullを返す場合もdefaultJndiNameをJNDI名としてlookupを行います。

関連

コンテキスト内の定義済BeanのBean切替部品

Chunkスコープを利用して、Chunkの切れ目のタイミングでコンテキスト内から定義されているBeanを取得しなおすBean切替部品を提供しています。

例えば、下記のように設定することでChunkの中で利用するDataSourceオブジェクトを実行時に切り替えることができます。

<bean id="targetDataSource"
      class="com.nec.webotx.batch.util.scope.BSChunkScopedProxyFactoryBean">
    <property name="targetInterface" value="javax.sql.DataSource"/>
    <property name="beanNameKey" value="sample.jndi.name"/>
    <property name="defaultBeanName" value="dataSource1"/>
</bean>
関連