Spring Batch 2.2 – JavaConfig Part 4: Job inheritance
22.6.2013| 7 minutes reading time
One important feature in XML is the possibility to write abstract job definitions like these:
1<jobid="abstractJob"abstract="true">2<listeners>3<listenerref="commonJobExecutionListener"/>4</listeners>5</job>
Concrete job definitions may inherit parts of their definition from it:
1<jobid="myJob"parent="abstractJob">2 ...3</job>
In enterprise environments it’s often necessary to definecommon functionality, for example a common job protocol, common logging or common return code mapping, but of course there are many more use cases. You achieve this by registering certain listeners, and with the parent functionality above, it’s easy to register listeners in an abstract job. And often you have very similar jobs in a certain line of business that share more than just listeners, maybe they have the same reader and writer, or the same skip policy etc. In XML you extract this common stuff toabstract job definitions.How can you achieve this with Java based configuration? What are the advantages / disadvantages?
This is the fourth post about the new Java based configuration features in Spring Batch 2.2. Previous posts are about acomparison between the new Java DSL and XML ,JobParameters, ExecutionContexts and StepScope andprofiles and environments . Future posts will be aboutmodular configurations andpartitioning and multi-threaded step , everything regarding Java based configuration, of course. You can find the JavaConfig code examples onGithub .
Builders and builder factories
There’s no direct equivalent to abstract job definitions in Java based configuration. But we have builders for jobs and steps, and we can prepare them with default functionality. If you look at theJobBuilderFactory
in Spring Batch 2.2, you see that it creates aJobBuilder
and calls the methodrepository
on it:
1publicclassJobBuilderFactory{23private JobRepository jobRepository;45publicJobBuilderFactory(JobRepository jobRepository){6this.jobRepository = jobRepository;7 }89public JobBuilderget(String name){10 JobBuilder builder =new JobBuilder(name).repository(jobRepository);11return builder;12 }1314}
That’s exactly how you implement job inheritance in Java based configuration: create a custom builder factory for your job or step and add the default functionality by calling the appropriate methods on the builder. The followingCustomJobBuilderFactory
allows for addingJobExecutionListener
s to aJobBuilder
.
1publicclassCustomJobBuilderFactoryextendsJobBuilderFactory{23private JobExecutionListener[] listeners;45publicCustomJobBuilderFactory(JobRepository jobRepository, JobExecutionListener... listeners){6super(jobRepository);7this.listeners = listeners;8 }910@Override11public JobBuilderget(String name){12 JobBuilder jobBuilder =super.get(name);13for (JobExecutionListener jobExecutionListener: listeners){14 jobBuilder = jobBuilder.listener(jobExecutionListener);15 }16return jobBuilder;17 }1819}
Configuration with delegation
Now that we have our custom job builder factory, how do we use it? We create a common configuration class that contains the listener we want to add to every job, and the factory, of course:
1@Configuration2publicclassCommonJobConfigurationForDelegation{34@Autowired5private JobRepository jobRepository;67@Bean8public CustomJobBuilderFactorycustomJobBuilders(){9returnnew CustomJobBuilderFactory(jobRepository, protocolListener());10 }1112@Bean13public ProtocolListenerprotocolListener(){14returnnew ProtocolListener();15 }1617}
You can tell by its name that it should be included by delegation in concrete job configurations like this:
1@Configuration2@Import(CommonJobConfigurationForDelegation.class)3publicclassDelegatingConfigurationJobConfiguration{45@Autowired6private CommonJobConfigurationForDelegation commonJobConfiguration;78@Bean9public JobdelegatingConfigurationJob(){10return commonJobConfiguration.customJobBuilders()11 .get("delegatingConfigurationJob")12 .start(step())13 .build();14 }1516 ...17}
Whydelegation? As you might know, there are two ways in Java to call common functionality: either you do it by delegating to an object that performs the logic, or you inherit the functionality from a super class. In the case above we use delegation, because we don’t inherit fromCommonJobConfigurationForDelegation
, we just import it and delegate the creation of theJobBuilder
to its methodcustomJobBuilders
. In generalI prefer delegation over inheritance because it isn’t as strict as inheritance, and classes aren’t so tightly coupled then. We may just extend one class, but we may delegate to as many objects as we want.
But let’scompare the Java configuration to the XML configuration now. The approaches are technically very different, though they achieve the same. In XML we define abstract Spring bean definitions that are completed with the information in the concrete job definition. In Java we prepare a builder with some default calls, and the concrete job is created with the prepared builder. First thing you notice: theJava approach is much more natural with less Spring magic in it. Now let’s assume that the parent functionality resides in some common library, in our case either the XML file with the abstract job definition or the classCommonJobConfigurationForDelegation
. The common library is added as a Maven dependency. Let’s see how everyday’s handling differ:
XML: In Eclipse you cannot open the parent XML with the ‘Open resource’ shortcut, you have to search it by hand in the dependencies. And even if you find it, there’s no direct connection between the concrete and parent job definition, you have to do a full text search in the parent XML to find it.
Java: You just take the class with the concrete job definition and do ‘Open implementation’ on the methodcustomJobBuilders
, and you jump directly to the place where the common stuff is defined.
The advantages are obvious, aren’t they?
Configuration with inheritance
I said I prefer delegation over inheritance, but that doesn’t mean that there aren’t valid use cases for inheritance. Let’s take a look at a configuration class designed for inheritance:
1publicabstractclassCommonJobConfigurationForInheritance{23@Autowired4private JobRepository jobRepository;56@Autowired7private PlatformTransactionManager transactionManager;89@Autowired10private InfrastructureConfiguration infrastructureConfiguration;1112protected CustomJobBuilderFactorycustomJobBuilders(){13returnnew CustomJobBuilderFactory(jobRepository, protocolListener());14 }1516protected CustomStepBuilderFactory<Partner,Partner>customStepBuilders(){17returnnew CustomStepBuilderFactory<Partner,Partner>(18 jobRepository,19 transactionManager,20 completionPolicy(),21 reader(),22 processor(),23 writer(),24 logProcessListener());25 }2627@Bean28public CompletionPolicycompletionPolicy(){29returnnew SimpleCompletionPolicy(1);30 }3132publicabstract ItemProcessor<Partner,Partner>processor();3334@Bean35public FlatFileItemReader<Partner>reader(){36 FlatFileItemReader<Partner> itemReader =new FlatFileItemReader<Partner>();37 itemReader.setLineMapper(lineMapper());38 itemReader.setResource(new ClassPathResource("partner-import.csv"));39return itemReader;40 }4142@Bean43public LineMapper<Partner>lineMapper(){44 DefaultLineMapper<Partner> lineMapper =new DefaultLineMapper<Partner>();45 DelimitedLineTokenizer lineTokenizer =new DelimitedLineTokenizer();46 lineTokenizer.setNames(new String[]{"name","email","gender"});47 lineTokenizer.setIncludedFields(newint[]{0,2,3});48 BeanWrapperFieldSetMapper<Partner> fieldSetMapper =new BeanWrapperFieldSetMapper<Partner>();49 fieldSetMapper.setTargetType(Partner.class);50 lineMapper.setLineTokenizer(lineTokenizer);51 lineMapper.setFieldSetMapper(fieldSetMapper);52return lineMapper;53 }5455@Bean56public ItemWriter<Partner>writer(){57 JdbcBatchItemWriter<Partner> itemWriter =new JdbcBatchItemWriter<Partner>();58 itemWriter.setSql("INSERT INTO PARTNER (NAME, EMAIL) VALUES (:name,:email)");59 itemWriter.setDataSource(infrastructureConfiguration.dataSource());60 itemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<Partner>());61return itemWriter;62 }6364@Bean65public ProtocolListenerprotocolListener(){66returnnew ProtocolListener();67 }6869@Bean70public LogProcessListenerlogProcessListener(){71returnnew LogProcessListener();72 }7374}
We have two builder factories, one for the job and one for the step. They areprotected
and may be used by a subclass. If you’re interested in the implementation of theCustomStepBuilderFactory
, take a look atGithub . The builder factories use a lot of the components defined in this configuration class. The processor has an abstract definition, so a subclass has to add a processor. All the other components may be overridden by a subclass if needed. Let’s take a look at such a subclass.
1@Configuration2publicclassInheritedConfigurationJobConfigurationextendsCommonJobConfigurationForInheritance{34@Bean5public JobinheritedConfigurationJob(){6return customJobBuilders().get("inheritedConfigurationJob")7 .start(step())8 .build();9 }1011@Bean12public Stepstep(){13return customStepBuilders().get("step")14 .faultTolerant()15 .skipLimit(10)16 .skip(UnknownGenderException.class)17 .listener(logSkipListener())18 .build();19 }2021@Override22@Bean23public ItemProcessor<Partner, Partner>processor(){24returnnew ValidationProcessor();25 }2627@Override28@Bean29public CompletionPolicycompletionPolicy(){30returnnew SimpleCompletionPolicy(3);31 }3233@Bean34public LogSkipListenerlogSkipListener(){35returnnew LogSkipListener();36 }3738}
So, what do we have here? This concrete configuration class implements theprocessor
method, of course. Furthermore it overrides the definition of theCompletionPolicy
. And then it uses the builder factories to create the job and the step, and it adds fault tolerance to the step.
Let’s take a look at the advantages / disadvantages. The coupling between parent and concrete definition is very tight, but in this case it’s fine. We want the parent to defineneeded components (the abstract method) andoverridable default components (the other methods), and you cannot do this with delegation. Of course you may just inherit from one parent class. You use this pattern if you clearly want such a tight coupling, for example if you have a lot ofvery similar jobs that share the same type of components. In general you should only haveone level of inheritance, take it as a bad smell and warning sign if there are more! Of course it’s always possible to combine delegation and inheritance.
Conclusion
Inheritance between jobs is important in enterprise environments. It’s achievable in XML and in Java based configuration in very different technical ways. The Java way may be a little bit more verbose, but has a lot of advantages that I pointed out in the paragraphs above.
Blog author
Tobias Flohre
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.
More articles
fromTobias Flohre
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte