How to use Spring Batch Late Binding – Step Scope & Job Scope

Spring Batch Late Binding provides a solution for late binding Job’s Parameters with Beans’ properties. In the tutorial, JavaSampleApproach will introduce Spring Batch Late Binding cleary by a sample project.

Related Articles:
1. How to use Spring Batch Inheritance function
2. How to use Intercepting Job Execution in Spring Batch
3. How to start with Spring Batch using Spring Boot – Java Config
4. Spring Batch Partition for Scaling & Parallel Processing

I. Spring Batch Late Binding

See below configuration:

<!-- Abstract Parent Bean -->
<beans:bean id="parentBean" abstract="true">
	<beans:property name="folder" value="${folder}"/>
	<beans:property name="fileName" value="${inputFileName}"/>
</beans:bean> 

The configuration uses a traditional approach:${folder} & ${inputFileName}
folder & inputFileName must define in a configuration file, like application.properties:


folder=C:\\readfile
inputFileName=1.txt

Question: How to bind data between Job Parameters & Beans’ properties?
Answer: Spring Batch Late Binding with Job & Step attributes.

1. Step Scope

Spring Batch provides various form for late binding with Step attributes:

<bean id=".." scope="step"
      class="...">
    <property name="..." value="#{jobParameters['input.name']}" />
</bean>
or
<bean id=".." scope="step"
      class="...">
    <property name="..." value="#{jobExecutionContext['input.name']}" />
</bean>
or
<bean id=".." scope="step"
      class="...">
    <property name="..." value="#{stepExecutionContext['input.name']}" />
</bean>

Step Scope is used to late binding because a bean can NOT be instantiated until the step starts.
Sample:

<job id="job">
	<step id="step1" next="step2">
		<tasklet ref="tasklet1" />
	</step>
   ...
</job>

...

<!-- Tasklet for Step -->
<beans:bean id="tasklet1" scope="step"
	class="com.javasampleapproach.joblatebinding.tasklet.Tasklet1" parent="parentBean" />
<beans:bean class="org.springframework.batch.core.scope.StepScope" />

...

Launching the Job with .addString("input.file.name", fileName) on JobParameters, when step 1 starts then the bean tasklet1 will be instantiated with logs:


o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=job]] launched with the following parameters: [{input.file.name=1.txt, time=1488351126484}]
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
c.j.joblatebinding.tasklet.Tasklet1      : Contruction - # Tasklet 1

Because by default, Step Scope is not part of the Spring container, so we need declare the scope explicitly by a bean:

2. Job Scope

Job Scope provides some ways to pull value of beans’s properties from Job Execution Context & the Job Parameters.

<bean id="..." class="..." scope="job">
    <property name="name" value="#{jobParameters[input_key_name]}" />
</bean>

or

<bean id="..." class="..." scope="job">
    <property name="name" value="#{jobExecutionContext['input_key_name']}.txt" />
</bean>

Job scope is similar to Step scope But is a Scope for the Job context so there is only one instance per executing job.
Sample:

<job id="job">
	..
	<step id="step2" next="step3">
		<tasklet ref="tasklet2" />
	</step>
	...
	<step id="step4">
		<tasklet ref="tasklet2" />
	</step>
</job>
...
<beans:bean id="tasklet2" scope="job"
	class="com.javasampleapproach.joblatebinding.tasklet.Tasklet2" parent="parentBean" />

<beans:bean class="org.springframework.batch.core.scope.JobScope" />
...

Launching the Job with .addString("input.file.name", fileName) on JobParameters, when step 2 starts then the bean tasklet2 will be instantiated. step 4 will reuse the instance tasklet2 from step 2
Logs:


o.s.batch.core.job.SimpleStepHandler     : Executing step: [step2]
c.j.joblatebinding.tasklet.Tasklet2      : Contruction - # Tasklet 2
### Processing! Read content of File: 1.txt

...

o.s.batch.core.job.SimpleStepHandler     : Executing step: [step4]
### Processing! Read content of File: 1.txt

II. Practice

Create a Spring Boot project has a Batch Job with 4 steps and:
tasklet1 bean has Step scope: write content to a File
tasklet2 bean has Job scope: read content form a File then displays on console.
– Launch the Job from a JobLauncherController that has a file name request paramenter for a JobParameter.

Spring Batch Late Binding - Overview job

Technology

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– Spring Boot: 1.5.1.RELEASE
– MySQL Database 1.4

Step to do

– Create a Spring Boot project
– Create StepTasklets
– Config Batch Job
– Create JobLauncherController
– Run & Check results

1. Create a Spring Boot project

Create a Spring Boot project with needed dependencies:
spring-boot-starter-batch
spring-boot-starter-web
mysql-connector-java

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

2. Create StepTasklets

– Create an AbstracTasklet.java:


package com.javasampleapproach.joblatebinding.tasklet;

public abstract class AbstracTasklet {
	protected String folder;
	protected String fileName;

	// GET & SET for folder member
	public String getFolder() {
		return folder;
	}

	public void setFolder(String folder) {
		this.folder = folder;
	}

	// GET & SET for fileName
	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
}

– Create Tasklet1.java that extends AbstracTasklet:


package com.javasampleapproach.joblatebinding.tasklet;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

public class Tasklet1 extends AbstracTasklet implements Tasklet {
	
	Logger log = LoggerFactory.getLogger(this.getClass().getName());
	
	public Tasklet1(){
		log.info("Contruction - # Tasklet 1");
	}
	
	@Override
	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

		System.out.println("### Processing! Write to File: " + fileName);
		
		String pathFile = folder + "\\" + fileName;
		
		if (!new File(pathFile).exists()) {
			try (BufferedWriter bw = new BufferedWriter(new FileWriter(pathFile))) {
				bw.write("Line 1");
				bw.newLine();
				bw.write("Line 2");
				bw.newLine();
				bw.write("Line 3");
				bw.newLine();
				bw.write("Line 4");
				bw.newLine();
				bw.write("Line 5");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return RepeatStatus.FINISHED;
	}
}

– Create a Tasklet2.java that extends AbstracTasklet:


package com.javasampleapproach.joblatebinding.tasklet;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

public class Tasklet2 extends AbstracTasklet implements Tasklet {

	Logger log = LoggerFactory.getLogger(this.getClass().getName());
	
	public Tasklet2(){
		log.info("Contruction - # Tasklet 2");
	}

	@Override
	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
		
		System.out.println("### Processing! Read content of File: " + fileName);
		
		// Build pathFile
		String pathFile = folder + "\\" + fileName;
		try (Stream stream = Files.lines(Paths.get(pathFile))) {
			stream.forEach(System.out::println);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return RepeatStatus.FINISHED;
	}
}

3. Config Batch Job

– Configure a Batch Job:

<beans:beans xmlns="http://www.springframework.org/schema/batch"
	xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/batch
           http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">

	<job id="job">
		<step id="step1" next="step2">
			<tasklet ref="tasklet1" />
		</step>
		<step id="step2" next="step3">
			<tasklet ref="tasklet2" />
		</step>

		<step id="step3" next="step4">
			<tasklet ref="tasklet1" />
		</step>

		<step id="step4">
			<tasklet ref="tasklet2" />
		</step>
	</job>

	<!-- Abstract Parent Bean -->
	<beans:bean id="parentBean" abstract="true">
		<beans:property name="folder" value="${folder}" />
		<beans:property name="fileName"
			value="#{jobParameters['input.file.name']}" />
	</beans:bean>

	<!-- Tasklet for Ste -->
	<beans:bean id="tasklet1" scope="step"
		class="com.javasampleapproach.joblatebinding.tasklet.Tasklet1" parent="parentBean" />

	<beans:bean id="tasklet2" scope="job"
		class="com.javasampleapproach.joblatebinding.tasklet.Tasklet2" parent="parentBean" />

	<beans:bean class="org.springframework.batch.core.scope.StepScope" />
	<beans:bean class="org.springframework.batch.core.scope.JobScope" />

</beans:beans>

– Open application.properties file, configure Job’s Database & folder property:


spring.datasource.url=jdbc:mysql://localhost:3306/testdb
spring.datasource.username=root
spring.datasource.password=12345
spring.batch.job.enabled=false
folder=C:\\readfile

– Enable Batch Proccessing in main class:
@EnableBatchProcessing
@ImportResource("classpath:batchjob.xml")

4. Create JobLauncherController

Create a JobLauncherController having @RequestParam("fileName") String fileName for late binding on beans:tasklet1 & tasklet2.


package com.javasampleapproach.joblatebinding.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
  
@RestController
public class JobLauncherController {
  
    @Autowired
    JobLauncher jobLauncher;
  
    @Autowired
    Job job;
     
    @RequestMapping("/launchjob")
    public String handle(@RequestParam("fileName") String fileName) throws Exception {
  
        Logger logger = LoggerFactory.getLogger(this.getClass());
        try {
            JobParameters jobParameters = new JobParametersBuilder()
            											.addString("input.file.name", fileName)
            											.addLong("time", System.currentTimeMillis())
            											.toJobParameters();
            jobLauncher.run(job, jobParameters);
        } catch (Exception e) {
            logger.info(e.getMessage());
        }
  
        return "Done";
    }
}

5. Run & Check result

– Run the project with Spring Boot App mode, then make a launch request:
http://localhost:8080/launchjob?fileName=1.txt
Logs:


o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=job]] launched with the following parameters: [{input.file.name=1.txt, time=1488356996916}]
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
c.j.joblatebinding.tasklet.Tasklet1      : Contruction - # Tasklet 1
### Processing! Write to File: 1.txt
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step2]
c.j.joblatebinding.tasklet.Tasklet2      : Contruction - # Tasklet 2
### Processing! Read content of File: 1.txt
Line 1
Line 2
Line 3
Line 4
Line 5
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step3]
c.j.joblatebinding.tasklet.Tasklet1      : Contruction - # Tasklet 1
### Processing! Write to File: 1.txt
o.s.batch.core.job.SimpleStepHandler     : Executing step: [step4]
### Processing! Read content of File: 1.txt
Line 1
Line 2
Line 3
Line 4
Line 5
o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=job]] completed with the following parameters: [{input.file.name=1.txt, time=1488356996916}] and the following status: [COMPLETED]

III. Source Code

SpringBatchLateBinding



By grokonez | March 1, 2017.

Last updated on April 26, 2021.



Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*