Is there a way to integrate spring-batch-admin and spring-boot properly?
Asked Answered
B

5

11

According to the documentation spring batch admin is very easy to embed into the existing application. Simply copying web.xml and index.jsp then adding needed dependencies is enough getting it to work.

But if I want to use it in an existing spring boot project it getting worse. According to this example the configuration is a bit hacky but it works. UNTIL I try to use @EnableBatchProcessing annotation in my configuriton bean. Then I get the following exception.

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobBuilders' defined in class path resource [org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.batch.core.configuration.annotation.JobBuilderFactory org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.jobBuilders() throws java.lang.Exception] threw exception; nested exception is java.lang.ClassCastException: org.springframework.batch.core.repository.support.JobRepositoryFactoryBean$$EnhancerBySpringCGLIB$$49fa0273 cannot be cast to org.springframework.batch.core.repository.JobRepository
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:597)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:952)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:941)
    at demo.Application.main(Application.java:35)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.batch.core.configuration.annotation.JobBuilderFactory org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.jobBuilders() throws java.lang.Exception] threw exception; nested exception is java.lang.ClassCastException: org.springframework.batch.core.repository.support.JobRepositoryFactoryBean$$EnhancerBySpringCGLIB$$49fa0273 cannot be cast to org.springframework.batch.core.repository.JobRepository
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:188)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:586)
    ... 17 more
Caused by: java.lang.ClassCastException: org.springframework.batch.core.repository.support.JobRepositoryFactoryBean$$EnhancerBySpringCGLIB$$49fa0273 cannot be cast to org.springframework.batch.core.repository.JobRepository
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04.jobRepository(<generated>)
    at org.springframework.batch.core.configuration.annotation.AbstractBatchConfiguration.jobBuilders(AbstractBatchConfiguration.java:58)
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04.CGLIB$jobBuilders$8(<generated>)
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04$$FastClassBySpringCGLIB$$d88bd05f.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$$EnhancerBySpringCGLIB$$b5c6eb04.jobBuilders(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
    ... 18 more

My configuration is quite simple I have two configuration beans

@Configuration
@ImportResource({"classpath:/org/springframework/batch/admin/web/resources/servlet-config.xml", 
        "classpath:/org/springframework/batch/admin/web/resources/webapp-config.xml"})
public class BatchAdminConfiguration {
}

and

@Configuration
@EnableBatchProcessing
public class BatchImporterConfiguration { 
}

When I remove @EnableBatchProcessing and try to create jobs with JobBuilderFactory and use @StepScope annotation I'm getting other ClassCastExceptions.

Now I'm using xml based configuration to create jobs, steps and other beans. It work well but I would actually preffer xml free configuration. Is there a way to easily integrate spring boot, spring batch and spring batch admin ?

Bomar answered 26/11, 2014 at 0:3 Comment(1)
Can you create a github project if you were able to resolve the issue finally, I have similar issue tooKomsomolsk
D
3

The short answer is that you won't want to use @EnableBatchProcessing with Spring Batch Admin. SBA provides a number of beans on a global scale that the @EnableBatchProcessing also provides. SBA 2.0 (currently in development) will probably fill the gaps between what is currently there and what @EnableBatchProcessing provides (specifically providing the JobBuilderFactory and StepBuilderFactory).

To get yourself running, you should be able to (I haven't tired this myself) configure in the META-INF/spring/batch/override/ directory a JobBuilderFactory and a StepBuilderFactory for global use. From there, you can use XML files in the META-INF/spring/batch/jobs directory that do nothing more than component scan for your @Configuration classes. However, leave off the @EnableBatchProcessing because of the duplication of beans.

For the record, this isn't an Spring Boot issue since @EnableBatchProcessing is a Spring Batch annotation, not a Boot one.

Darlinedarling answered 26/11, 2014 at 15:58 Comment(10)
Thanks for general explanation. Defining xml configuration in META-INF/spring/batch/jobs and adding <context:annotation-config/> and <context:component-scan /> tags helps to use annotation based configuration. But I cannot define Job or Step beans in Java config, I'm still getting weird errors, for examle Job scope is not defined. If I create my own JobScope bean then I get again class cast exception. Singleton scope beans can be used without any problem. But step or job scoped beans should be defined in xml. Weird :(Bomar
You're listing off a few issues separately. Feel free to reach out to me off line and I can walk you through each of those.Darlinedarling
Thank you for your kindness. When I find a free time slot, I will do it :)Bomar
when do you think will SBA 2.0 be announced?Stutter
I'm actively working on it right now. I'm hoping to get M1 out in a week or so with it going GA sometime first quarter of next year.Darlinedarling
looking forward to it... Please let us know when you have something to test.Stutter
If you're feeling adventurous, the current SNAPSHOTs should have the fixes in it to crudely support java config. I'll be polishing it up a bit before the M1 release.Darlinedarling
I used the same github example as a template and as soon I define a bean with a custom property like ${...} I got this error: Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'xxx' in string value "${xxx}" I debugged it and it's like Spring Batch has its own Placeholder and it's unable to the one is defined by Spring Boot. Any idea how to override this behaviour?Akanke
Spring Batch Admin does have it's own PropertyPlaceholder. You should (in theory) be able to override it and point it to boot's application.properties in an override context.Darlinedarling
Does spring batch still alive ?Render
E
5

Spring Batch Admin 2.0-BUILD-SNAPSHOT introduce a new Annoation @EnableBatchAdmin for easy to integrate with spring boot. There is also a samples project https://github.com/spring-projects/spring-batch-admin-samples.

Encrust answered 10/7, 2015 at 6:19 Comment(1)
I added jira.spring.io/browse/BATCHADM-235 Release new milestone (M2) with latest changes please. because of SNAPSHOT in version. We need to enable check for snapshots. Unfortunately Spring Batch development stalled. There are no new commits for 8 months...Cabriolet
D
3

The short answer is that you won't want to use @EnableBatchProcessing with Spring Batch Admin. SBA provides a number of beans on a global scale that the @EnableBatchProcessing also provides. SBA 2.0 (currently in development) will probably fill the gaps between what is currently there and what @EnableBatchProcessing provides (specifically providing the JobBuilderFactory and StepBuilderFactory).

To get yourself running, you should be able to (I haven't tired this myself) configure in the META-INF/spring/batch/override/ directory a JobBuilderFactory and a StepBuilderFactory for global use. From there, you can use XML files in the META-INF/spring/batch/jobs directory that do nothing more than component scan for your @Configuration classes. However, leave off the @EnableBatchProcessing because of the duplication of beans.

For the record, this isn't an Spring Boot issue since @EnableBatchProcessing is a Spring Batch annotation, not a Boot one.

Darlinedarling answered 26/11, 2014 at 15:58 Comment(10)
Thanks for general explanation. Defining xml configuration in META-INF/spring/batch/jobs and adding <context:annotation-config/> and <context:component-scan /> tags helps to use annotation based configuration. But I cannot define Job or Step beans in Java config, I'm still getting weird errors, for examle Job scope is not defined. If I create my own JobScope bean then I get again class cast exception. Singleton scope beans can be used without any problem. But step or job scoped beans should be defined in xml. Weird :(Bomar
You're listing off a few issues separately. Feel free to reach out to me off line and I can walk you through each of those.Darlinedarling
Thank you for your kindness. When I find a free time slot, I will do it :)Bomar
when do you think will SBA 2.0 be announced?Stutter
I'm actively working on it right now. I'm hoping to get M1 out in a week or so with it going GA sometime first quarter of next year.Darlinedarling
looking forward to it... Please let us know when you have something to test.Stutter
If you're feeling adventurous, the current SNAPSHOTs should have the fixes in it to crudely support java config. I'll be polishing it up a bit before the M1 release.Darlinedarling
I used the same github example as a template and as soon I define a bean with a custom property like ${...} I got this error: Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'xxx' in string value "${xxx}" I debugged it and it's like Spring Batch has its own Placeholder and it's unable to the one is defined by Spring Boot. Any idea how to override this behaviour?Akanke
Spring Batch Admin does have it's own PropertyPlaceholder. You should (in theory) be able to override it and point it to boot's application.properties in an override context.Darlinedarling
Does spring batch still alive ?Render
Q
3

to complete the answer, here is the code to create the two beans once you disable the @EnableBatchProcessing annotation

@Autowired
JobRepository jobRepository;
@Autowired
PlatformTransactionManager transactionManager;

@Bean
public JobBuilderFactory jobBuilderFactory() {
    return new JobBuilderFactory(jobRepository);
}

@Bean
public StepBuilderFactory stepBuilderFactory() {
    return new StepBuilderFactory(jobRepository, transactionManager);
}
Quesnay answered 6/1, 2015 at 7:35 Comment(0)
A
2

I've a working version here based on the same example (I forked the original one): https://github.com/vesperaba/spring-batch-admin-spring-boot.

I followed Michael Minella advice and I overwrote the SpringBatch property holder with a custom one.

I also added a job to check it's working now

Akanke answered 18/5, 2015 at 11:3 Comment(0)
C
1

This ClassCastException is caused by

classpath:/org/springframework/batch/admin/web/resources/servlet-config.xml

loading

META-INF/spring/batch/servlet/resources/resource-context.xml

which contains

<mvc:annotation-driven />

This conflicts with the mvc configuration in the Spring Java configuration class. The following class can be used to embed Spring Batch Admin within an existing application that uses Java configuration.

@Configuration
@EnableWebMvc
@ImportResource({"classpath*:/META-INF/spring/batch/bootstrap/**/*.xml"
    , "classpath*:/META-INF/spring/batch/override/**/*.xml"
    , "classpath*:/org/springframework/batch/admin/web/resources/webapp-config.xml" 
    , "classpath*:/META-INF/spring/batch/servlet/manager/**/*.xml"
    , "classpath:base-menu-config.xml"
    })
public class SpringBatchAdminConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/META-INF/");      
    }

    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
        return new SimpleControllerHandlerAdapter();
    }

    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
        return new BeanNameUrlHandlerMapping();
    }

    @Bean
    public BeanNameViewResolver beanNameViewResolver() {
        return new BeanNameViewResolver();
    }

    @Bean(name = "defaultResources")
    public PropertiesFactoryBean defaultResources() {
        return new PropertiesFactoryBean();
    }

    @Bean(name = "jsonResources")
    public PropertiesFactoryBean jsonResources() {
        return new PropertiesFactoryBean();
    }

    @Bean
    public HomeController homeController() throws IOException {
        HomeController homeController = new HomeController();
        homeController.setDefaultResources(defaultResources().getObject());
        homeController.setJsonResources(jsonResources().getObject());
        return homeController;
    }

    @Bean
    public MenuManager menuManager() {
        return new MenuManager();
    }

    @Bean(name = "freemarkerConfig")
    public HippyFreeMarkerConfigurer hippyFreeMarkerConfigurer() {
        HippyFreeMarkerConfigurer hippyFreeMarkerConfigurer = new HippyFreeMarkerConfigurer();
        hippyFreeMarkerConfigurer.setTemplateLoaderPaths("/WEB-INF/web", "classpath:/org/springframework/batch/admin/web");
        hippyFreeMarkerConfigurer.setPreferFileSystemAccess(false);
        hippyFreeMarkerConfigurer.setFreemarkerVariables(Collections.singletonMap("menuManager", (Object) menuManager()));
        Properties freemarkerSettings = new Properties();
        freemarkerSettings.put("default_encoding", "UTF-8");
        freemarkerSettings.put("output_encoding", "UTF-8");
        hippyFreeMarkerConfigurer.setFreemarkerSettings(freemarkerSettings);
        return hippyFreeMarkerConfigurer;
    }

    public AjaxFreeMarkerView parentLayout() {
        AjaxFreeMarkerView ajaxFreeMarkerView = new AjaxFreeMarkerView();
        FreeMarkerViewResolver freeMarkerViewResolver = new FreeMarkerViewResolver();
        freeMarkerViewResolver.setExposeSpringMacroHelpers(false);
        freeMarkerViewResolver.setAllowRequestOverride(true);
        ajaxFreeMarkerView.setViewResolver(freeMarkerViewResolver);
        Properties attributes = new Properties();
        attributes.put("titleCode", "home.title");
        attributes.put("titleText", "Spring Batch Admin");
        ajaxFreeMarkerView.setAttributes(attributes);
        return ajaxFreeMarkerView;
    }

    @Value("#{resourceService.servletPath}")
    private String servletPath;

    @Bean(name="standard")
    public AjaxFreeMarkerView standard() {
        AjaxFreeMarkerView standard = parentLayout();
        standard.setUrl("/layouts/html/standard.ftl");
        standard.setContentType("text/html;charset=UTF-8");
        standard.getAttributesMap().put("body", "/layouts/html/home.ftl");
        standard.getAttributesMap().put("servletPath", servletPath);
        return standard;        
    }

    @Bean(name="standard.rss")
    public AjaxFreeMarkerView standardRss() {
        AjaxFreeMarkerView standardRss = parentLayout();
        standardRss.setUrl("/layouts/html/standard.ftl");
        standardRss.setContentType("text/xml");
        standardRss.getAttributesMap().put("body", "/layouts/rss/home.ftl");
        standardRss.getAttributesMap().put("servletPath", servletPath);
        return standardRss;     
    }

    @Bean(name="standard.json")
    public AjaxFreeMarkerView standardJson() {
        AjaxFreeMarkerView standardJson = parentLayout();
        standardJson.setUrl("/layouts/json/standard.ftl");
        standardJson.setContentType("application/json");
        standardJson.getAttributesMap().put("body", "/layouts/json/home.ftl");
        standardJson.getAttributesMap().put("servletPath", servletPath);
        return standardJson;        
    }

    @Bean(name="home")
    public AjaxFreeMarkerView home() {
        return standard();
    }

    @Bean(name="home.json")
    public AjaxFreeMarkerView homeJson() {
        AjaxFreeMarkerView homeJson = standardJson();
        homeJson.getAttributesMap().put("body", "/layouts/json/home.ftl");
        return homeJson;
    }


}

A single XML file is also required for the abstract base menu which is referenced elsewhere in the Spring Batch Admin project. This is required as abstract beans can not be provided from a Spring Java configuration.

<?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-2.5.xsd">

    <bean id="baseMenu" abstract="true">
        <property name="prefix" value="#{resourceService.servletPath}" />
    </bean>

</beans>

Maven dependencies. Take care to ensure only a single version of the base Spring framework is pulled in by Maven.

<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-admin-manager</artifactId>
    <version>1.3.1.RELEASE</version>
</dependency>
<dependency>
        <groupId>hsqldb</groupId>
        <artifactId>hsqldb</artifactId>
        <scope>runtime</scope>
        <version>1.8.0.10</version>
</dependency>

Spring batch also expects in a default configuration for the following files to exist at the root of the classpath.

batch-default.properties

# Default placeholders for database platform independent features 
batch.remote.base.url=http://localhost:8080/spring-batch-admin-sample
# Non-platform dependent settings that you might like to change
batch.job.configuration.file.dir=/tmp/config

build.artifactId=1
build.version=1
build.buildNumber=1
build.timestamp=1
log.enableConsole=true

batch-hsql.properties

# Placeholders batch.*
#    for HSQLDB:
batch.jdbc.driver=org.hsqldb.jdbcDriver
batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true
# Override and use this one in for a separate server process so you can inspect 
# the results (or add it to system properties with -D to override at run time).
# batch.jdbc.url=jdbc:hsqldb:hsql://localhost:9005/samples
batch.jdbc.user=sa
batch.jdbc.password=
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer
batch.schema.script=classpath*:/org/springframework/batch/core/schema-hsqldb.sql
batch.drop.script=classpath*:/org/springframework/batch/core/schema-drop-hsqldb.sql
batch.business.schema.script=classpath:/business-schema-hsqldb.sql

# Non-platform dependent settings that you might like to change
# batch.data.source.init=true

business-schedule-hsqldb.sql

DROP TABLE  ERROR_LOG IF EXISTS;
CREATE TABLE ERROR_LOG  (
        JOB_NAME CHAR(20) ,
        STEP_NAME CHAR(20) ,
        MESSAGE VARCHAR(300) NOT NULL
) ;
Cabal answered 11/3, 2015 at 23:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.