Skip to content

Revisit the configuration of infrastructure beans with @EnableBatchProcessing #3942

Closed
@fmbenhassine

Description

@fmbenhassine

Compared to the XML configuration style where infrastructure beans (JobRepository, JobLauncher, etc) should be defined manually, @EnableBatchProcessing does a good job in configuring those beans automatically and making them available for autowiring in users configuration classes. However, several issues have been reported regarding the default behaviour of this annotation as well as the customization of its behaviour. Here is a non exhaustive list:

1. Customization of infrastructure beans is not straightforward

For example, as reported in #3765, in order to create a custom serializer, one needs to provide a custom JobRepository. Now in order to provide a custom JobRepository, one needs to provide a custom BatchConfigurer (either by implementing the interface or by extending the default one and override a method), something like:

@Configuration
@EnableBatchProcessing
public class MyJobConfigWithCustomSerializer {

    @Bean
    public BatchConfigurer batchConfigurer() {
        return new DefaultBatchConfigurer() {
            @Override
            public JobRepository getJobRepository() {
                ExecutionContextSerializer serializer = new Jackson2ExecutionContextStringSerializer();
                // customize serializer
                JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
                factory.setSerializer(serializer);
                // set other properties on the factory bean
                try {
                    factory.afterPropertiesSet();
                    return factory.getObject();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

}

Moreover, in this case, the custom serializer should also be set on the JobExplorer in order to correctly deserialize the execution context while exploring meta-data that was persisted with the JobRepository. So one needs to do the following as well:

@Configuration
@EnableBatchProcessing
public class MyJobConfigWithCustomSerializer {

    @Bean
    public BatchConfigurer batchConfigurer() {
        return new DefaultBatchConfigurer() {
            @Override
            public JobRepository getJobRepository() {
                JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
                factory.setSerializer(createCustomSerializer());
                // set other properties on the factory bean
                try {
                    factory.afterPropertiesSet();
                    return factory.getObject();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public JobExplorer getJobExplorer() {
                JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
                factoryBean.setSerializer(createCustomSerializer());
                // set other properties on the factory bean
                try {
                    factoryBean.afterPropertiesSet();
                    return factoryBean.getObject();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            private ExecutionContextSerializer createCustomSerializer() {
                Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
                // customize serializer
                return serializer;
            }
        };
    }
    
}

The process is the same for other properties of the job repository/explorer like the tables prefix, LobHandler, etc.

2. Unconditional exposure of some beans

While batch specific beans like JobRepository, JobLauncher, etc could be exposed in the application context "safely", some beans like the transaction manager could be used in other parts of the application and exposing it unconditionally could be problematic (see #816). This is especially true when using Spring Boot, and this requires bean overriding which is not always wanted by users.

3. Extending infrastructure beans is not straightforward / possible

Since infrastructure beans are systematically defined and exposed by @EnableBatchProcessing and not looked up from the application context first, it is not easy/possible to extend those beans to add custom behaviour (like adding tracing to the JobRepository for instance, as reported in #3899) and use the extensions in place of default beans.

4. BatchConfigurer is eventually an unnecessary level of indirection

Most people tend to declare infrastructure beans in the application context and expect them to be picked up by Spring Batch (this is not a wrong expectation). Here are some examples:

If @EnableBatchProcessing is changed to look for beans in the application context first, requiring users to provide a custom BatchConfigurer would not become mandatory anymore. For example, the following way of configuring a custom JobRepository:

@Configuration
@EnableBatchProcessing
public class MyJobConfigWithCustomJobRepository {

    @Bean
    public BatchConfigurer batchConfigurer() {
        return new DefaultBatchConfigurer() {
            @Override
            public JobRepository getJobRepository() {
                JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
                // set properties on the factory bean
                try {
                    factory.afterPropertiesSet();
                    return factory.getObject();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };
    }

   // job bean definition
}

could become:

@Configuration
@EnableBatchProcessing
public class MyJobConfigWithCustomJobRepository {

    @Bean
    public JobRepository jobRepository() throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        // set properties on the factory bean
        factory.afterPropertiesSet();
        return factory.getObject();
    }

   // job bean definition
   
}

5. Confusing configuration when batch meta-data is not required

The configuration of the JobRepository/JobExplorer in @EnableBatchProcessing is based on the presence of a DataSource bean (if no data source is provided, a Map-based job repository/explorer is configured, which was deprecated anyway #3780). If the application context contains one or more datasource that should not be used by Spring Batch for its meta-data, things seem to become complicated and confusing to many people. Here are some examples:

It is concerning that people end up with an empty setter for the datasource:

The data source is actually an implementation detail of a particular JobRepository implementation, which is the JDBC based JobRepository. Other implementations of JobRepository might not need a data source at all (like a MongoDB based job repository for example). The point here is that the data source should not be a first order concern in terms of configuration, but rather a second order concern. In other words, @EnableBatchProcessing should first make sure the user wants to use the JDBC based job repository, and if so, only then check for a data source bean in the application context.


Possible solutions

I see a couple of options here, but I'm open for other suggestions as well.

1. Use annotation attributes to customize infrastructure beans

The idea here is to make @EnableBatchProcessing first look for infrastructure beans in the application context (this is similar and consistent with the way other projects from the portfolio configure apps, like Spring Security for instance). If those beans are not defined, then create them and register them in the application context. The same naming conventions used with XML configuration style should be used for consistency. For example:

@Configuration
@EnableBatchProcessing(dataSource = "myDataSource",  // could be omitted if named "dataSource"
                       transactionManager = "myTransactionManager", // could be omitted if named "transactionManager"
                       serializer = "mySerializer")
public class MyJobConfiguration {

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory) {
        return jobBuilderFactory.get("myJob")
                // define job flow
                .build();
    }

    @Bean // could be the one auto-configured by Spring Boot
    public ExecutionContextSerializer mySerializer() {
        ExecutionContextSerializer serializer = new Jackson2ExecutionContextStringSerializer();
        // customize serializer
        return serializer;
    }

}

In this example, @EnableBatchProcessing would first look for a JobRepository bean named jobRepository (same naming convention as XML) in the application context. If no such bean is defined, then it should create one by setting collaborators as defined in the annotation attributes.

2. Provide a base configuration class with infrastructure beans

Similar to the base XML application context that defines infrastructure beans with XML configuration, the idea is to provide a similar mechanism but for Java configuration. This means providing a base configuration class (which could be something like the current AbstractBatchConfiguration but with ready-to-use bean definitions in it) that users can extend to define their batch jobs:

@Configuration
public class MyBatchApplication extends BatchConfiguration {

    @Bean
    public Job job(JobBuilderFactory jobBuilderFactory) {
        return jobBuilderFactory.get("job")
                // define job
                .build();
    }

}

Any bean that needs customization can be declared in the user's class.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions