StepExecutionListener - Adding hooks or listeners - Spring Batch Part 8
You can add interceptors to you steps by using the Listeners around your steps. We will check the StepExecutionListener - no points for guessing what it does - in this blogpost.
We add add listeners both before and after Steps.
Lets consider that along with Corporate Gifts, you also want to supply beautifully arrange chocolates. Lets create a job for it.
full code available at: https://github.com/ricsr/spring-batch-demo/tree/exercise_08
@Bean
public Job orderChocolateBoxJob(){
return this.jobBuilderFactory.get("orderChocolateBoxJob")
.start(selectChocolatesStep())
.next(arrangeChocolatesStep())
.build();
}
@Bean
public Step selectChocolatesStep(){
return this.stepBuilderFactory.get("selectChocolatesStep").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Ordering Chocolate : "+ chunkContext.getStepContext().getJobParameters().get("chocolateType").toString());
return RepeatStatus.FINISHED;
}
}).build();
}
@Bean
public Step arrangeChocolatesStep(){
return this.stepBuilderFactory.get("arrangeChocolatesStep").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Ordering Chocolate : "+ chunkContext.getStepContext().getJobParameters().get("chocolateType").toString());
return RepeatStatus.FINISHED;
}
}).build();
}
Our orderChocolateBox consists of two steps:
- Selecting the Chocolates
- Arranging the Chocolates
The Client may order Almond Chocolates or Plain Chocolates. Whenever the Client orders Almond Chocolates, we want to add a not which says " Chocolate box contains nuts"
Lets see how we can do that using StepExecutionListener.
This listener should be added after we select the chocolates from the selectChocolatesStep. As you can see, we are passing chocolateType program parameter from our runConfiguration:
"chocolateType=Almonds"
Lets create the Listener:
package com.ricsr.springbootdemo;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
public class ChocolateTypeStepExecutionListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
System.out.println("Before arranging chocolates");
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
System.out.println("After arranging chocolates");
String chocolateType = stepExecution.getJobParameters().getString("chocolateType");
return chocolateType.equals("Almonds") ? new ExitStatus("NUTTY_CHOCOLATES") : new ExitStatus("NORMAL_CHOCOLATES");
}
}
As you can see, the StepExecutionListener has two methods:
- beforeStep: Executes before the step executes. Lets add a sysout statement here.
- afterStep: This will execute after the step executes. In our case, this is where we add the logic to determine if the chocolate contains NUTS or not, and which will drive whether we add a note to our chocolate box or not. We are making use of custom ExitStatus for the same. Obviously, we can have more categories of chocolate types, but lets leave it simple for the sake of our example.
Lets go back to our SpringbootdemoApplication class. And add a bean for the listener:
@Bean
public StepExecutionListener chocolateTypeStepExecutionListener(){
return new ChocolateTypeStepExecutionListener();
}
Lets add the listener to our selectChocolatesStep We use the listener method on the builder to add our listener:
@Bean
public Step selectChocolatesStep(){
return this.stepBuilderFactory.get("selectChocolatesStep").tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Ordering Chocolate : "+ chunkContext.getStepContext().getJobParameters().get("chocolateType").toString());
return RepeatStatus.FINISHED;
}
}).listener(chocolateTypeStepExecutionListener()).build();
}
Let us run with Program parameter: "chocolateType=NUTTY"
2020-07-21 23:12:36.801 INFO 17884 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=orderChocolateBoxJob]] launched with the following parameters: [{chocolateType=Almonds}]
2020-07-21 23:12:36.821 INFO 17884 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [selectChocolatesStep]
Before arranging chocolates
Ordering Chocolate : Almonds
After arranging chocolates
2020-07-21 23:12:36.837 INFO 17884 --- [ main] o.s.batch.core.step.AbstractStep : Step: [selectChocolatesStep] executed in 16ms
2020-07-21 23:12:36.860 INFO 17884 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [addNoteStep]
Chocolate box contains nuts
2020-07-21 23:12:36.879 INFO 17884 --- [ main] o.s.batch.core.step.AbstractStep : Step: [addNoteStep] executed in 19ms
2020-07-21 23:12:36.904 INFO 17884 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [arrangeChocolatesStep]
Ordering Chocolate : Almonds
2020-07-21 23:12:36.921 INFO 17884 --- [ main] o.s.batch.core.step.AbstractStep : Step: [arrangeChocolatesStep] executed in 16ms
2020-07-21 23:12:36.935 INFO 17884 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=orderChocolateBoxJob]] completed with the following parameters: [{chocolateType=Almonds}] and the following status: [COMPLETED] in 124ms
2020-07-21 23:12:36.939 INFO 17884 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2020-07-21 23:12:36.957 INFO 17884 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 5s
Now, let us run with Program parameter: "chocolateType=DARK"
2020-07-21 23:13:40.962 INFO 2284 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=orderChocolateBoxJob]] launched with the following parameters: [{chocolateType=DARK}]
2020-07-21 23:13:40.986 INFO 2284 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [selectChocolatesStep]
Before arranging chocolates
Ordering Chocolate : DARK
After arranging chocolates
2020-07-21 23:13:40.998 INFO 2284 --- [ main] o.s.batch.core.step.AbstractStep : Step: [selectChocolatesStep] executed in 12ms
2020-07-21 23:13:41.019 INFO 2284 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [arrangeChocolatesStep]
Ordering Chocolate : DARK
2020-07-21 23:13:41.029 INFO 2284 --- [ main] o.s.batch.core.step.AbstractStep : Step: [arrangeChocolatesStep] executed in 10ms
2020-07-21 23:13:41.036 INFO 2284 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=orderChocolateBoxJob]] completed with the following parameters: [{chocolateType=DARK}] and the following status: [COMPLETED] in 72ms
2020-07-21 23:13:41.040 INFO 2284 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2020-07-21 23:13:41.052 INFO 2284 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 3s
full code available at: https://github.com/ricsr/spring-batch-demo/tree/exercise_08
Note: I have dropped the Spring Job Repository tables just to able to rerun the application just to make it easy to rerun the Job
DROP TABLE BATCH_STEP_EXECUTION_CONTEXT ;
DROP TABLE BATCH_STEP_EXECUTION ;
DROP TABLE BATCH_JOB_INSTANCE ;
DROP TABLE BATCH_JOB_EXECUTION_PARAMS ;
DROP TABLE BATCH_JOB_EXECUTION_CONTEXT ;
DROP TABLE BATCH_JOB_EXECUTION ;
Comments