如何使用注释向 Spring 批处理作业添加另一个步骤?

How to add another step to a Spring Batch job using Annotations?

我正在关注 Spring doc for creating batch service. I've tweaked it 。现在我想添加另一个步骤,该步骤从 MongoDb 集合中读取,处理并写入另一个集合。我找到了一些使用 MongoDb 的 spring 批处理教程,但它们使用 XML 文件来定义 bean 并且看起来已经过时了。

@Configuration
@EnableBatchProcessing
@Import(SpringMongoConfig.class)
public class BatchConfiguration {

  @Autowired
  private SpringMongoConfig springMongoConfig;

  private static final Logger log = LoggerFactory.getLogger(BatchConfiguration.class);
  private static final String csvFileName = "sample-data.csv",
      recordCollectionName = "records",
      processedRecordCollectionName = "processedRecords";

  @Bean
  public Job importUserJob(JobBuilderFactory jobs, Step s1, Step s2, JobExecutionListener listener) {
    return jobs.get("importUserJob")
        .incrementer(new RunIdIncrementer())
        .listener(listener)
        .flow(s1)
        .next(s2)
        .end()
        .build();
  }

  @Bean
  public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Record> csvRecordReader,
            ItemWriter<Record> mongoRecordWriter) {
    return stepBuilderFactory.get("step1").
        <Record, Record> chunk(10).
        reader(csvRecordReader())
        .writer(mongoRecordWriter())
        .build();
  }

  @Bean
  public Step step2(StepBuilderFactory stepBuilderFactory, ItemReader<Record> mongoRecordReader,
            ItemWriter<processedRecord> mongoprocessedRecordWriter, ItemProcessor<Record, processedRecord> processor) {
    return stepBuilderFactory.get("step2")
        .<Record, processedRecord> chunk(10)
        .reader(mongoRecordReader)
        .processor(processor)
        .writer(mongoprocessedRecordWriter)
        .build();
  }

  // tag::readerwriter[1]
  @Bean
  public ItemReader<Record> csvRecordReader() {
    FlatFileItemReader<Record> reader = new FlatFileItemReader<Record>();
    reader.setResource(new ClassPathResource(csvFileName));
    reader.setLineMapper(new DefaultLineMapper<Record>() {
      {
        setLineTokenizer(new DelimitedLineTokenizer() {
          {
            setNames(new String[] { "id", "firstName", "lastName" });
          }
        });
        setFieldSetMapper(new BeanWrapperFieldSetMapper<Record>() {
          {
            setTargetType(Record.class);
          }
        });
      }
    });
    return reader;
  }

  @Bean
  public ItemWriter<Record> mongoRecordWriter() {
    MongoItemWriter<Record> writer = new MongoItemWriter<Record>();
    try {
      writer.setTemplate(springMongoConfig.mongoTemplate());
    } catch (Exception e) {
      log.error(e.toString());
    }
    writer.setCollection(recordCollectionName);
    return writer;
  }
  // end::readerwriter[1]

  // tag::readerprocessorwriter[2]
  @Bean
  public ItemReader<Record> mongoRecordReader() {
    MongoItemReader<Record> reader = new MongoItemReader<Record>();
    try {
      reader.setTemplate(springMongoConfig.mongoTemplate());
    } catch (Exception e) {
      log.error(e.toString());
    }
    reader.setCollection(recordCollectionName);
    return reader;
  }

  @Bean
  public ItemProcessor<Record, processedRecord> processor() {
    return new RecordItemProcessor();
  }

  @Bean
  public ItemWriter<processedRecord> mongoprocessedRecordWriter() {
    MongoItemWriter<processedRecord> writer = new MongoItemWriter<processedRecord>();
    try {
      writer.setTemplate(springMongoConfig.mongoTemplate());
    } catch (Exception e) {
      log.error(e.toString());
    }
    writer.setCollection(processedRecordCollectionName);
    return writer;
  }
  // end::readerprocessorwriter[2]
}

我收到以下错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'importUserJob' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.core.Step]: : Error creating bean with name 'step1' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.item.ItemReader]: : Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'step1' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.item.ItemReader]: : Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:464)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:687)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:967)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:956)
  at com.xyz.abc.batch.App.main(App.java:18)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'step1' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.item.ItemReader]: : Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:464)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
  ... 17 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
  ... 31 common frames omitted
Caused by: java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.util.Assert.state(Assert.java:385)
  at org.springframework.batch.item.data.MongoItemReader.afterPropertiesSet(MongoItemReader.java:200)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
  ... 42 common frames omitted

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'importUserJob' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.core.Step]: : Error creating bean with name 'step1' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.item.ItemReader]: : Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'step1' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.item.ItemReader]: : Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:464)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:687)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:967)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:956)
  at com.xyz.abc.batch.App.main(App.java:18)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'step1' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [org.springframework.batch.item.ItemReader]: : Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:464)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
  ... 17 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoRecordReader' defined in class path resource [com/xyz/abc/batch/BatchConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
  ... 31 more
Caused by: java.lang.IllegalStateException: A type to convert the input into is required.
  at org.springframework.util.Assert.state(Assert.java:385)
  at org.springframework.batch.item.data.MongoItemReader.afterPropertiesSet(MongoItemReader.java:200)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
  ... 42 more

我认为您只是在 mongoRecordReader() 中错过了对 setTargetType 的调用。详见MongoItemReader的docs

另外,看看 AfterPropertiesSet:

@Override
public void afterPropertiesSet() throws Exception {
    Assert.state(template != null, "An implementation of MongoOperations is required.");
    Assert.state(type != null, "A type to convert the input into is required.");
    Assert.state(query != null, "A query is required.");
    Assert.state(sort != null, "A sort is required.");
}

还有一些其他必填字段。