Overview
リスナーとは、ジョブやステップを実行する前後に処理を挿入するためのインターフェースである。
本機能は、チャンクモデルとタスクレットモデルとで使い方が異なるため、それぞれについて説明する。
リスナーには多くのインターフェースがあるため、それぞれの役割について説明する。 その後に、設定および実装方法について説明をする。
リスナーの種類
Spring Batchでは、実に多くのリスナーインターフェースが定義されている。 ここではそのすべてを説明するのではなく、利用頻度が高いものを中心に扱う。
まず、リスナーは2種類に大別される。
- JobListener
-
ジョブの実行に対して処理を挟み込むためのインターフェース
- StepListener
-
ステップの実行に対して処理を挟み込むためのインターフェース
JobListenerについて
Spring Batchには、 |
JobListener
JobListener
のインターフェースは、JobExecutionListener
の1つのみとなる。
- JobExecutionListener
-
ジョブの開始前、終了後に処理を挟み込む。
public interface JobExecutionListener {
void beforeJob(JobExecution jobExecution);
void afterJob(JobExecution jobExecution);
}
StepListener
StepListener
のインターフェースは以下のように多くの種類がある。
- StepListener
-
以降に紹介する各種リスナーのマーカーインターフェース。
- StepExecutionListener
-
ステップ実行の開始前、終了後に処理を挟み込む。
public interface StepExecutionListener extends StepListener {
void beforeStep(StepExecution stepExecution);
ExitStatus afterStep(StepExecution stepExecution);
}
- ChunkListener
-
1つのチャンクを処理する前後と、エラーが発生した場合に処理を挟み込む。
public interface ChunkListener extends StepListener {
static final String ROLLBACK_EXCEPTION_KEY = "sb_rollback_exception";
void beforeChunk(ChunkContext context);
void afterChunk(ChunkContext context);
void afterChunkError(ChunkContext context);
}
ROLLBACK_EXCEPTION_KEYの用途
使用例
|
- ItemReadListener
-
ItemReaderが1件のデータを取得する前後と、エラーが発生した場合に処理を挟み込む。
public interface ItemReadListener<T> extends StepListener {
void beforeRead();
void afterRead(T item);
void onReadError(Exception ex);
}
- ItemProcessListener
-
ItemProcessorが1件のデータを加工する前後と、エラーが発生した場合に処理を挟み込む。
public interface ItemProcessListener<T, S> extends StepListener {
void beforeProcess(T item);
void afterProcess(T item, S result);
void onProcessError(T item, Exception e);
}
- ItemWriteListener
-
ItemWriterが1つのチャンクを出力する前後と、エラーが発生した場合に処理を挟み込む。
public interface ItemWriteListener<S> extends StepListener {
void beforeWrite(List<? extends S> items);
void afterWrite(List<? extends S> items);
void onWriteError(Exception exception, List<? extends S> items);
}
本ガイドラインでは、以下のリスナーについては説明をしない。
これらのリスナーは例外ハンドリングでの使用を想定したものであるが、 本ガイドラインではこれらのリスナーを用いた例外ハンドリングは行わない方針である。 詳細は、例外ハンドリングを参照。 |
How to use
リスナーの実装と設定方法について説明する。
リスナーの実装
リスナーの実装と設定方法について説明する。
-
リスナーインターフェースを
implements
して実装する。 -
コンポーネントにメソッドベースでアノテーションを付与して実装する。
どちらで実装するかは、リスナーの役割に応じて選択する。基準は後述する。
インターフェースを実装する場合
各種リスナーインターフェースをimplements
して実装する。必要に応じて、複数のインターフェースを同時に実装してもよい。
以下に実装例を示す。
@Component
public class JobExecutionLoggingListener implements JobExecutionListener { // (1)
private static final Logger logger =
LoggerFactory.getLogger(JobExecutionLoggingListener.class);
@Override
public void beforeJob(JobExecution jobExecution) { // (2)
// do nothing.
}
@Override
public void afterJob(JobExecution jobExecution) { // (3)
logger.info("job finished.[JobName:{}][ExitStatus:{}]",
jobExecution.getJobInstance().getJobName(),
jobExecution.getExitStatus().getExitCode()); // (4)
// per step execution
// (5)
jobExecution.getStepExecutions().forEach(stepExecution -> {
Object errorItem = stepExecution.getExecutionContext().get("ERROR_ITEM");
if (errorItem != null) {
logger.error("detected error on this item processing. " +
"[step:{}] [item:{}]", stepExecution.getStepName(),
errorItem);
}
});
}
}
<batch:job id="chunkJobWithListener" job-repository="jobRepository">
<batch:step id="chunkJobWithListener.step01">
<batch:tasklet transaction-manager="jobTransactionManager">
<batch:chunk reader="reader" processor="processor"
writer="writer" commit-interval="10"/>
<batch:listeners>
<batch:listener ref="loggingEachProcessInStepListener"/>
</batch:listeners>
</batch:tasklet>
</batch:step>
<batch:listeners>
<batch:listener ref="jobExecutionLoggingListener"/> <!-- (6) -->
</batch:listeners>
</batch:job>
項番 | 説明 |
---|---|
(1) |
|
(2) |
|
(3) |
|
(4) |
ジョブ名と終了コードをINFOログ出力する。必要な情報は、引数の |
(5) |
ステップごとに発生した例外をログ出力する。引数の |
(6) |
Bean定義の |
リスナーのサポートクラス
複数のリスナーインターフェースを サポートクラス
|
アノテーションを付与する場合
各種リスナーインターフェースに対応したアノテーションを付与する。必要に応じて、複数のアノテーションを同時に実装してもよい。
リスナーインターフェース | アノテーション |
---|---|
|
|
|
|
|
|
|
|
|
|
|
これらアノテーションはコンポーネント化された実装のメソッドに付与することで目的のスコープで動作する。 以下に実装例を示す。
@Component
public class AnnotationAmountCheckProcessor implements
ItemProcessor<SalesPlanDetail, SalesPlanDetail> {
private static final Logger logger =
LoggerFactory.getLogger(AnnotationAmountCheckProcessor.class);
@Override
public SalesPlanDetail process(SalesPlanDetail item) throws Exception {
if (item.getAmount().signum() == -1) {
throw new IllegalArgumentException("amount is negative.");
}
return item;
}
// (1)
/*
@BeforeProcess
public void beforeProcess(Object item) {
logger.info("before process. [Item :{}]", item);
}
*/
// (2)
@AfterProcess
public void afterProcess(Object item, Object result) {
logger.info("after process. [Result :{}]", result);
}
// (3)
@OnProcessError
public void onProcessError(Object item, Exception e) {
logger.error("on process error.", e);
}
}
<batch:job id="chunkJobWithListenerAnnotation" job-repository="jobRepository">
<batch:step id="chunkJobWithListenerAnnotation.step01">
<batch:tasklet transaction-manager="jobTransactionManager">
<batch:chunk reader="reader"
processor="annotationAmountCheckProcessor"
writer="writer" commit-interval="10"/> <! -- (4) -->
</batch:tasklet>
</batch:step>
</batch:job>
項番 | 説明 |
---|---|
(1) |
アノテーションで実装する場合は、処理が必要なタイミングのアノテーションのみを付与すればよい。 |
(2) |
ItemProcessの処理後に行う処理を実装する。 |
(3) |
ItemProcessでエラーが発生したときの処理を実装する。 |
(4) |
アノテーションでリスナー実装がされているItemProcessを |
アノテーションを付与するメソッドの制約
アノテーションを付与するメソッドはどのようなメソッドでもよいわけではない。 対応するリスナーインターフェースのメソッドと、シグネチャを一致させる必要がある。 この点は、各アノテーションのjavadocに明記されている。 |
JobExecutionListenerをアノテーションで実装したときの注意
JobExecutionListenerは、他のリスナーとスコープが異なるため、上記の設定では自動的にリスナー登録がされない。
そのため、 |
Tasklet実装へのアノテーションによるリスナー実装
Tasklet実装へのアノテーションによるリスナー実装した場合、以下の設定では一切リスナーが起動しないため注意する。 Taskletの場合
タスクレットモデルの場合は、インターフェースとアノテーションの使い分けに従ってリスナーインターフェースを利用するのがよい。 |
リスナーの設定
リスナーは、Bean定義の<listeners>
.<listener>
タグによって設定する。
XMLスキーマ定義では様々な箇所に記述できるが、インターフェースの種類によっては意図とおり動作しないものが存在するため、
以下の位置に設定すること。
<!-- for chunk mode -->
<batch:job id="chunkJob" job-repository="jobRepository">
<batch:step id="chunkJob.step01">
<batch:tasklet transaction-manager="jobTransactionManager">
<batch:chunk reader="(1)"
processor="(1)"
writer="(1)" commit-interval="10"/>
<batch:listeners>
<batch:listener ref="(2)"/>
</batch:listeners>
</batch:tasklet>
</batch:step>
<batch:listeners>
<batch:listener ref="(3)"/>
</batch:listeners>
</batch:job>
<!-- for tasklet mode -->
<batch:job id="taskletJob" job-repository="jobRepository">
<batch:step id="taskletJob.step01">
<batch:tasklet transaction-manager="jobTransactionManager" ref="tasklet">
<batch:listeners>
<batch:listener ref="(2)"/>
</batch:listeners>
</batch:tasklet>
</batch:step>
<batch:listeners>
<batch:listener ref="(3)"/>
</batch:listeners>
</batch:job>
項番 | 説明 |
---|---|
(1) |
StepListenerに属するアノテーションによる実装を含んだコンポーネントを設定する。 |
(2) |
StepListenerに属するリスナーインターフェース実装を設定する。 |
(3) |
JobListenerに属するリスナーを設定する。 |
複数リスナーの設定
<batch:listeners>
タグには複数のリスナーを設定することができる。
複数のリスナーを登録したときに、リスナーがどのような順番で起動されるかを以下に示す。
-
ItemProcessListener実装
-
listenerA, listenerB
-
-
JobExecutionListener実装
-
listenerC, listenerD
-
<batch:job id="chunkJob" job-repository="jobRepository">
<batch:step id="chunkJob.step01">
<batch:tasklet transaction-manager="jobTransactionManager">
<batch:chunk reader="reader"
processor="pocessor"
writer="writer" commit-interval="10"/>
<batch:listeners>
<batch:listener ref="listenerA"/>
<batch:listener ref="listenerB"/>
</batch:listeners>
</batch:tasklet>
</batch:step>
<batch:listeners>
<batch:listener ref="listenerC"/>
<batch:listener ref="listenerD"/>
</batch:listeners>
</batch:job>
-
前処理に該当する処理は、リスナーの登録順に起動される。
-
後処理またはエラー処理に該当する処理は、リスナー登録の逆順に起動される。