11

根据文档, spring batch admin 很容易嵌入到现有应用程序中。只需复制 web.xml 和 index.jsp 然后添加所需的依赖项就足以让它工作。

但如果我想在现有的 Spring Boot 项目中使用它,情况会变得更糟。根据this example,配置有点hacky,但它可以工作。直到我尝试@EnableBatchProcessing在我的配置 bean 中使用注释。然后我得到以下异常。

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

我的配置很简单我有两个配置bean

@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 {
}

@Configuration
@EnableBatchProcessing
public class BatchImporterConfiguration { 
}

当我删除 @EnableBatchProcessing 并尝试使用 JobBuilderFactory 创建作业并使用 @StepScope 注释时,我得到了其他 ClassCastExceptions。

现在我正在使用基于 xml 的配置来创建作业、步骤和其他 bean。它工作得很好,但我实际上更喜欢 xml 自由配置。有没有办法轻松集成spring boot、spring batch和spring batch admin?

4

5 回答 5

5

Spring Batch Admin 2.0-BUILD-SNAPSHOT 引入了一个新的 Annoation @EnableBatchAdmin,便于与 Spring Boot 集成。还有一个示例项目https://github.com/spring-projects/spring-batch-admin-samples

于 2015-07-10T06:19:15.713 回答
3

要完成答案,这里是禁用 @EnableBatchProcessing 注释后创建两个 bean 的代码

@Autowired
JobRepository jobRepository;
@Autowired
PlatformTransactionManager transactionManager;

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

@Bean
public StepBuilderFactory stepBuilderFactory() {
    return new StepBuilderFactory(jobRepository, transactionManager);
}
于 2015-01-06T07:35:18.953 回答
2

简短的回答是您不想@EnableBatchProcessing与 Spring Batch Admin 一起使用。SBA 在全球范围内提供了许多 bean,它们@EnableBatchProcessing也提供。SBA 2.0(目前正在开发中)可能会填补当前存在的内容和@EnableBatchProcessing提供的内容(特别是提供JobBuilderFactoryand StepBuilderFactory)之间的空白。

为了让自己运行,您应该能够(我自己并没有厌倦这个)在META-INF/spring/batch/override/目录 aJobBuilderFactory和 a中配置以StepBuilderFactory供全局使用。从那里,您可以使用目录中的 XML 文件,这些文件仅对您的类META-INF/spring/batch/jobs进行组件扫描。@Configuration但是,@EnableBatchProcessing由于bean的重复,请不要使用。

作为记录,这不是 Spring Boot 问题,因为@EnableBatchProcessing它是 Spring Batch 注释,而不是 Boot 注释。

于 2014-11-26T15:58:25.817 回答
2

我在这里有一个基于相同示例的工作版本(我分叉了原始版本):https ://github.com/vesperaba/spring-batch-admin-spring-boot 。

我听从了 Michael Minella 的建议,并用自定义的属性覆盖了 SpringBatch 属性持有者。

我还添加了一个工作来检查它现在是否正常工作

于 2015-05-18T11:03:29.510 回答
1

这个 ClassCastException 是由

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

加载

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

其中包含

<mvc:annotation-driven />

这与 Spring Java 配置类中的 mvc 配置冲突。以下类可用于将 Spring Batch Admin 嵌入到使用 Java 配置的现有应用程序中。

@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;
    }


}

Spring Batch Admin 项目中其他地方引用的抽象基本菜单也需要单个 XML 文件。这是必需的,因为不能从 Spring Java 配置中提供抽象 bean。

<?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 依赖项。注意确保 Maven 只引入了基础 Spring 框架的一个版本。

<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 还期望以下文件的默认配置存在于类路径的根目录中。

批处理-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

批处理 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

业务时间表-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
) ;
于 2015-03-11T23:42:56.047 回答