Spring Batch Admin in Spring Boot 2
This article is about how to adapt Simple UI that is Spring Batch Admin for your Spring Boot 2 application.
Spring batch is a great framework for batch processing.
It provides a lot of control in terms of:
Chunk management, transactions, flow control, resilience, IO
Unfortunately, it does not provide an out-of-the-box interface for an easily accessible overview.
There the current solution is Spring Data Flow, which is designed to do a bit more than just that, providing easy to access UI for scheduled jobs.
— — —
Time required: ~15 minutes (optimistic). Most probably an hour due to errors. (please leave feedback in the comments)
Let's go!
Add config
@EnableWebMvc
Excluding config
SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
Some more config - Properties
# false # disable batch jobs auto-start
spring.batch.job.enabled=false
spring.jmx=true
spring.jmx.enabled=true
# Required for sprint batch admin
spring.main.allow-bean-definition-overriding=true
Additional dependencies
Add the following to your Maven pom file.
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-admin-manager</artifactId>
<version>1.3.1.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<artifactId>jackson-mapper-asl</artifactId>
<groupId>org.codehaus.jackson</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
New tables
<databaseChangeLog ...> <changeSet author="viktor.reinok" id="spring-batch-table"> <sqlFile path="liquibase/4.0.0/schema-postgresql.sql"
relativeToChangelogFile="false"
splitStatements="true"
dbms="postgresql"
stripComments="true"/> <rollback> <sqlFile path="liquibase/4.0.0/schema-drop-postgresql.sql"
relativeToChangelogFile="false"
splitStatements="true"
dbms="postgresql"
stripComments="true"/>
</rollback>
</changeSet>
</databaseChangeLog>
Here, in case you have your batch dependencies in the same maven module you could ref to the .sql in the build folder.
Actual config
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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">
<!-- README:
This file is a duplication of "classpath:/org/springframework/batch/admin/web/resources/webapp-config.xml" file
in which we do not import "classpath*:/META-INF/spring/batch/bootstrap/manager/data-source-context.xml" file
in order to create and manage datasource using Spring Boot default behavior (and not using Spring Batch Admin Manager)
-->
<!-- Workaround for INT-1831 (You might need it) -->
<!-- <bean id="dummy123" class="java.util.Date"/>-->
<import resource="classpath*:/META-INF/spring/batch/bootstrap/integration/*.xml"/>
<import resource="classpath*:/META-INF/spring/batch/bootstrap/resources/*.xml"/>
<import resource="classpath*:/META-INF/spring/batch/bootstrap/manager/execution-context.xml"/>
<!-- <import resource="classpath*:/META-INF/spring/batch/bootstrap/manager/env-context.xml" />-->
<!-- <import resource="classpath*:/META-INF/spring/batch/bootstrap/manager/jmx-context.xml" />-->
</beans>import javax.management.MBeanServer;
import javax.sql.DataSource;
import org.springframework.batch.admin.jmx.BatchMBeanExporter;
import org.springframework.batch.admin.service.JobService;
import org.springframework.batch.admin.service.SimpleJobServiceFactoryBean;
import org.springframework.batch.admin.web.filter.ParameterUnpackerFilter;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.support.MapJobRegistry;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
@Configuration
// Do not add @EnableBatchProcessing because there will be a conflict with
// the beans that are already declared in Spring Batch Admin XML webapp config
@ImportResource({"classpath:batch-admin-webapp-config-override.xml"})
@EnableMBeanExport
public class BatchAdminConfiguration {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private MBeanServer mbeanServer;
@Autowired
private JobService jobService;
@Autowired
private JobRepository jobRepository;
@Autowired
DataSource dataSource;
@Bean
public ServletRegistrationBean adminServletRegistrationBean() {
XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
applicationContext.setConfigLocation("classpath:/org/springframework/batch/admin/web/resources/servlet-config.xml");
DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
dispatcherServlet.setContextConfigLocation(null);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/*");
servletRegistrationBean.setName("batch-admin");
return servletRegistrationBean;
}
@Bean
public ParameterUnpackerFilter parameterUnpackerFilter() {
ParameterUnpackerFilter filter = new ParameterUnpackerFilter();
filter.setPrefix("unpack_");
filter.setPutEmptyParamsInPath(true);
return filter;
}
@Bean
@Qualifier("jobService")
public JobService jobService() throws Exception {
SimpleJobServiceFactoryBean bean = new SimpleJobServiceFactoryBean();
bean.setJobRepository(jobRepository);
bean.setJobLocator(new MapJobRegistry());
bean.setJobLauncher(new SimpleJobLauncher());
bean.setDataSource(dataSource);
bean.afterPropertiesSet();
return bean.getObject();
}
@Bean
@DependsOn("jobService")
@Scope( proxyMode = ScopedProxyMode.TARGET_CLASS )
public BatchMBeanExporter batchMBeanExporter() {
BatchMBeanExporter batchMBeanExporter = new BatchMBeanExporter();
batchMBeanExporter.setDefaultDomain("spring.application");
batchMBeanExporter.setServer(mbeanServer);
batchMBeanExporter.setJobService(jobService);
return batchMBeanExporter;
}
@Bean
public JobBuilderFactory jobBuilderFactory() {
return new JobBuilderFactory(jobRepository);
}
@Bean
public StepBuilderFactory stepBuilderFactory() {
return new StepBuilderFactory(jobRepository, transactionManager);
}
@Bean
public JobParametersIncrementer jobParametersIncrementer() {
return new RunIdIncrementer();
}
}
A hack (overwrite Spring class)
Add following class to org.springframework.web.util;
We are coping a static method from Spring Web 4 module and adding it to the latest Spring boot class.
https://gist.github.com/vikreinok/770dadd35e73df9bd0df6a7f254ff3a6
Final word
Let me know if you are facing some error on the way, lets's try to resolve them together. Leave a comment below or write me ;)