Overview
A listener is an interface for inserting processing before and after executing a job or a step.
Since this function works differently for chunk model and tasket model, respective explanations are given.
A listener consists of multiple interfaces, respective roles are explained here. Subsequently, how to set and implement a listener is explained.
Types of listener
A lot of listener interfaces are defined in Spring Batch. All will not be explained here, however we will focus on the interface with highest usage frequency.
A listener is roughly divided into 2 types.
- JobListener
-
An interface to insert the processing for execution of the job
- StepListener
-
An interface to insert the processing for execution of the step
About JobListener
An interface called |
JobListener
JobListener
interface consists of only one JobExecutionListener
.
- JobExecutionListener
-
Process is inserted prior to starting a job and after terminating a job.
public interface JobExecutionListener {
void beforeJob(JobExecution jobExecution);
void afterJob(JobExecution jobExecution);
}
StepListener
StepListener
interface is of multiple types as below.
- StepListener
-
Marker interfaces of various listeners will be introduced later.
- StepExecutionListener
-
Process is inserted prior to starting a step and after terminating a job.
public interface StepExecutionListener extends StepListener {
void beforeStep(StepExecution stepExecution);
ExitStatus afterStep(StepExecution stepExecution);
}
- ChunkListener
-
A process is inserted between before and after processing of one chunk and when an error occurs.
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);
}
Uses of ROLLBACK_EXCEPTION_KEY
It is used when the exception occurred is to be fetched by Usage example
|
- ItemReadListener
-
Insert a process before and after fetching 1 data record by ItemReader and when an error occurs.
public interface ItemReadListener<T> extends StepListener {
void beforeRead();
void afterRead(T item);
void onReadError(Exception ex);
}
- ItemProcessListener
-
Insert a process before and after processing 1 data record by ItemProcessor and when an error occurs.
public interface ItemProcessListener<T, S> extends StepListener {
void beforeProcess(T item);
void afterProcess(T item, S result);
void onProcessError(T item, Exception e);
}
- ItemWriteListener
-
Insert a process before and after output of 1 chunk by ItemWriter and when an error occurs.
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);
}
This guideline does not explain following listeners.
These listeners are intended to be used for exception handling, however, the policy of these guidelines is not to perform exception handling using these listeners. For details, refer Exception handling. |
How to use
Explanation is given about how to implement and set a listener.
Implementation of a listener
Explanation is given about how to implement and set a listener.
-
Implement the listener interface with
implements
. -
Implement components with method-based annotation.
The type of implementation to use will be choosed on the role of the listener. Criteria will be described later.
When an interface is to be implemented
Various listener interfaces are implemented by using implements
. Multiple interfaces can be implemented at the same time based on requirement.
Implementation example is shown below.
@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>
Sr. No. | Description |
---|---|
(1) |
Implement |
(2) |
Implement |
(3) |
Implement |
(4) |
Output job name and exit code in INFO log. Fetch necessary information from |
(5) |
Output exception occurred for each step in a log. Fetch and implement |
(6) |
Set the listener implemented in (1), in |
Listener support class
When multiple listener interfaces are set to Support class
|
When annotations are assigned
Annotations corresponding to various listener interfaces are assigned. Multiple annotations can also be implemented as required.
Listener interface | Annotation |
---|---|
|
|
|
|
|
|
|
|
|
|
|
These annotations work for the target scope by assigning them to the implementation method which is divided into components. Implementation example is given below.
@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>
Sr. No. | Description |
---|---|
(1) |
When the annotation is to be used for implementation, only the annotations of the timing required for the processing should be assigned. |
(2) |
Implement the process to be performed after the processing of ItemProcess. |
(3) |
Implement processing when an error occurs in ItemProcess. |
(4) |
Set ItemProcess wherein the listener is implemented by using annotation in |
Constraints for the method which assigns the annotations
Any method cannot be used as a method to assign the annotation. The signature must match with the method of corresponding listener interface. This point is clearly mentioned in javadoc of respective annotations. |
Precautions while implementing JobExecutionListener by an annotation
Since JobExecutionListener has a different scope than the other listeners, listener is not automatically registered in the configuration above.
Hence, it is necessary to explicitly set in the |
Implementation of a listener to Tasklet implementation by using annotation
When a listener is implemented in Tasklet implementation by using an annotation, Note that listener does not start with the following settings. In case of Tasklet
In case of Tasket model, the listener interface should be used in accordance with How to choose an interface or an annotation. |
Listener settings
Listeners are set by <listeners>
.<listener>
tag of Bean definition.
Although it can be described at various locations by XML schema definition, some operations do not work as intended based on the type of interface.
Set it to the following position.
<!-- 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>
Sr. No. | Description |
---|---|
(1) |
Set the component which includes the implementation attributing to StepListener, performed by using an annotation. |
(2) |
Set listener interface implementation attributing to StepListener. |
(3) |
Set listener attributing to JobListener. |
Setting multiple listeners
Multiple listeners can be set in <batch:listeners>
tag.
The sequence in which the listeners are started while registering multiple listeners is shown below.
-
ItemProcessListener implementation
-
listenerA, listenerB
-
-
JobExecutionListener implementation
-
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>
-
Processing corresponding to pre-processing is started in the sequence of listener registration.
-
Processing corresponding to post-processing or error processing is started in the reverse sequence of listener registration.
How to choose an interface or an annotation
How to choose listener used a interface or listener used an annotation is explained.
- Listener interface
-
It is used in case of cross-sectional processes which are shared across job, step and chunk.
- Annotation
-
It is used when business logic specific process is to be performed.
As a rule, it is implemented only for ItemProcessor.