Spring Batch 2.2 - JavaConfig Part 2: JobParameters, ExecutionContext and StepScope - ...

:

This is the second post about the new Java based configuration features in Spring Batch 2.2. In the first post I compared the two configuration styles on a non-trivial batch job reading from a file and writing to a database.
In the first version of the job, I hard-coded the filename into the configuration of the FlatfileItemReader.

	@Bean
	public FlatFileItemReader<Partner> reader(){
		FlatFileItemReader<Partner> itemReader = new FlatFileItemReader<Partner>();
		itemReader.setLineMapper(lineMapper());
		itemReader.setResource(new ClassPathResource("partner-import.csv"));
		return itemReader;
	}

@Bean public FlatFileItemReader<Partner> reader(){ FlatFileItemReader<Partner> itemReader = new FlatFileItemReader<Partner>(); itemReader.setLineMapper(lineMapper()); itemReader.setResource(new ClassPathResource("partner-import.csv")); return itemReader; }

This sounds like a bad idea, because maybe I want to execute the job on different files. It would be good if it was a job parameter. So, this post is about accessing job parameters and execution contexts in Java based configuration.
In future posts I will talk about profiles and environments, job inheritance, modular configurations and partitioning and multi-threaded step, everything regarding Java based configuration, of course. You can find the JavaConfig code examples on Github.

What is a job parameter? What’s an execution context?

Job parameters are key-value-pairs of data you provide when you start a job. Before Spring Batch 2.2, every job parameter accounted for the JobInstance‘s identity, that has been weakened lately.
An execution context is a map of data that batch components may interact with during a job run, add data to the map or read data from the map. There is one ExecutionContext for each JobExecution, and one ExecutionContext for each StepExecution. Simplified: one map for the job and one map for each step.
Spring Batch creates those map-like components for us and adds them to the ApplicationContext, but how do we access them? They have reserved names, so the easiest way is to use the Spring Expression Language:

  • Access property ‘xy’ on job parameters: #{jobParameters[xy]}
  • Access property ‘xy’ on the job’s execution context: #{jobExecutionContext[xy]}
  • Access property ‘xy’ on the step’s execution context: #{stepExecutionContext[xy]}

Of course they have a different scope than the rest of our components, because they only exist when a job / step is running, but the default scope of Spring components is the scope Singleton. That’s why we have the StepScope in Spring Batch. Components configured with this scope are created when the step in which they are needed is started, and we may inject data from job parameters and execution contexts into these components.

Configuring access in Java

Let’s take a look at the configuration of the FlatfileItemReader with access to the job parameters.

	@Bean
	@StepScope
	public FlatFileItemReader<Partner> reader(
			@Value("#{jobParameters[pathToFile]}") String pathToFile){
		FlatFileItemReader<Partner> itemReader = new FlatFileItemReader<Partner>();
		itemReader.setLineMapper(lineMapper());
		itemReader.setResource(new ClassPathResource(pathToFile));
		return itemReader;
	}

@Bean @StepScope public FlatFileItemReader<Partner> reader( @Value("#{jobParameters[pathToFile]}") String pathToFile){ FlatFileItemReader<Partner> itemReader = new FlatFileItemReader<Partner>(); itemReader.setLineMapper(lineMapper()); itemReader.setResource(new ClassPathResource(pathToFile)); return itemReader; }

The annotation @StepScope indicates that the reader will be created for every StepExecution. The method creating the reader now takes the path to the file as argument, and we inject the job parameter with the name pathToFile into the method execution, using the annotation @Value. Injecting values from the jobExecutionContext or the stepExecutionContext can be done in the same way.

Using the reader in a step configuration

If you look at the configuration example in my previous blog post, you see that I call the @Bean method of the reader directly in the Step‘s builder chain. Now the method reader takes an argument, what do we do about that? We have two options, and choosing one is a matter of taste (though my favorite is quite clear).

Direct call with placeholder

	private static final String OVERRIDDEN_BY_EXPRESSION = null;
 
	@Bean
	public Step step(){
		return stepBuilders.get("step")
				.<Partner,Partner>chunk(1)
				.reader(reader(OVERRIDDEN_BY_EXPRESSION))
				.processor(processor())
				.writer(writer())
				.listener(logProcessListener())
				.build();
	}

private static final String OVERRIDDEN_BY_EXPRESSION = null; @Bean public Step step(){ return stepBuilders.get("step") .<Partner,Partner>chunk(1) .reader(reader(OVERRIDDEN_BY_EXPRESSION)) .processor(processor()) .writer(writer()) .listener(logProcessListener()) .build(); }

Here I call the method reader with the value null. That’s okay, because here only a proxy is created, the real reader object is created at a later time, and at that time the expression is used for injecting the pathToFile value. Even though it looks a little bit strange, it’s still my favorite way of configuring, because if I want to know how the reader is configured, I just jump into the method.

Autowire magic

	@Bean
	public Job flatfileToDbWithParametersAutowiringJob(Step step){
		return jobBuilders.get("flatfileToDbWithParametersAutowiringJob")
				.listener(protocolListener())
				.start(step)
				.build();
	}
 
	@Bean
	public Step step(ItemReader<Partner> reader){
		return stepBuilders.get("step")
				.<Partner,Partner>chunk(1)
				.reader(reader)
				.processor(processor())
				.writer(writer())
				.listener(logProcessListener())
				.build();
	}

@Bean public Job flatfileToDbWithParametersAutowiringJob(Step step){ return jobBuilders.get("flatfileToDbWithParametersAutowiringJob") .listener(protocolListener()) .start(step) .build(); } @Bean public Step step(ItemReader<Partner> reader){ return stepBuilders.get("step") .<Partner,Partner>chunk(1) .reader(reader) .processor(processor()) .writer(writer()) .listener(logProcessListener()) .build(); }

Here the reader is autowired into the method step as an argument. Somebody looking into the code cannot see at first glance where the reader comes from, and you have to autowire the step into the job as well. And there may be only one such reader and step (if you don’t wanna use qualifiers). So all in all I prefer the first configuration, but okay, with this one you don’t have that rather strange construct of calling the reader method with the value null.

Conclusion

Accessing JobParameters and ExecutionContexts in Java based configuration is as easy as in XML, and it’s possible to configure it without losing navigability in your configuration classes.
Next post will be about profiles and environments.