问题 从tasklet步骤向作业上下文添加参数,并在Spring Batch的后续步骤中使用


现在,我正在使用jobParameters来获取我的FlatFileItemReader和FlatFileItemWriter的文件名。可以测试我的批处理,但我的目标是读取某个目录中的文件(此目录中只有此文件),文件名可能会更改。输出文件名应取决于输入文件名。

因此,我考虑在我的工作中添加一个新步骤,此步骤将通过搜索好目录并在其中查找文件来设置输出和输入文件名。我读 将数据传递给未来的步骤 来自Spring Doc,和 这个帖子 从SO,但我不能使它工作,文件总是“空”。

首先,我定义了以下Tasklet

public class SettingFilenamesTasklet implements Tasklet {

    private StepExecution stepExecution;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        // TODO Search folder and set real filenames
        String inputFilename = "D:/TestInputFolder/dataFile.csv";
        String outputFilename = "D:/TestOutputFolder/dataFile-processed.csv";
        ExecutionContext stepContext = stepExecution.getExecutionContext();
        stepContext.put("inputFile", inputFilename);
        stepContext.put("outputFile", outputFilename);
        return RepeatStatus.FINISHED;
    }

    @BeforeStep
    public void saveStepExecution(StepExecution stepExec) {
        stepExecution = stepExec;
    }
}

然后,我添加了promotionListener bean

@Bean
public ExecutionContextPromotionListener promotionListener() {
    ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
    listener.setKeys(new String[]{
            "inputFile", "outputFile"
    });
    return listener;
}

我在我的FlatFileItemWriter定义中通过jobExecutionContext更改了jobParameters(我没有将一行更改为代码本身)

@Bean
@StepScope
public FlatFileItemWriter<RedevableCRE> flatFileWriter(@Value("#{jobExecutionContext[outputFile]}") String outputFile) {
    FlatFileItemWriter<Employee> flatWriter = new FlatFileItemWriter<Employee>();
    FileSystemResource isr;
    isr = new FileSystemResource(new File(outputFile));
    flatWriter.setResource(isr);
    DelimitedLineAggregator<RedevableCRE> aggregator = new DelimitedLineAggregator<RedevableCRE>();
    aggregator.setDelimiter(";");
    BeanWrapperFieldExtractor<RedevableCRE> beanWrapper = new BeanWrapperFieldExtractor<RedevableCRE>();
    beanWrapper.setNames(new String[]{
        "id", "firstName", "lastName", "phone", "address"
    });
    aggregator.setFieldExtractor(beanWrapper);
    flatWriter.setLineAggregator(aggregator);
    flatWriter.setEncoding("ISO-8859-1");
    return flatWriter;
}

我添加了我的Tasklet bean

@Bean
public SettingFilenamesTasklet settingFilenames() {
    return new SettingFilenamesTasklet();
}

我创建了一个新的步骤来添加我的工作声明

@Bean
public Step stepSettings(StepBuilderFactory stepBuilderFactory, SettingFilenamesTasklet tasklet, ExecutionContextPromotionListener listener) {
    return stepBuilderFactory.get("stepSettings").tasklet(tasklet).listener(listener).build();
}

目前,FlatFileItemReader仍然使用jobParameters值,我想让我的FlatFileItemWriter先工作。我收到以下错误:

[...]    
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.file.FlatFileItemWriter]: Factory method 'flatFileWriter' threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:591)
    ... 87 common frames omitted
Caused by: java.lang.NullPointerException: null
    at java.io.File.<init>(Unknown Source)
    at batchTest.BatchConfiguration.flatFileWriter(BatchConfiguration.java:165)
    at batchTest.BatchConfiguration$$EnhancerBySpringCGLIB$$5d415889.CGLIB$flatFileWriter$1(<generated>)
    at batchTest.BatchConfiguration$$EnhancerBySpringCGLIB$$5d415889$$FastClassBySpringCGLIB$$969a8527.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
    at batchTest.BatchConfiguration$$EnhancerBySpringCGLIB$$5d415889.flatFileWriter(<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:162)
    ... 88 common frames omitted

我试图用@JobScope替换@StepScope注释;将我的参数直接放入jobExecutionContext(+ JobExecutionListener)而不是使用StepContext + promotionListener ......没有任何作用。当我尝试创建FlatFileItemWriter时,资源文件始终为null。

我错过了什么?

谢谢你的帮助。


6325
2018-03-18 09:23


起源

你能尝试添加吗? @BeforeStep 给你的作家并设置一个断点来检查其中的内容 stepExecution.getExecutionContext() 和 stepExecution.getJobExecution().getExecutionContext()?去掉 @Value 所以你现在可以开始工作了。 - Nenad Bozic
看起来我的代码甚至没有执行 @BeforeStep 我写的方法...我创建了一个扩展FlatFileItemWriter的类来测试你所说的内容(我没看到如何添加一个 @BeforeStep 在批处理配置中,否则我在我的代码中实例化而不是通用的FlatFileItemWriter。我的调试器没有停在我设置的断点上...... - Carrm


答案:


在tasklet中你有 ChunkContext 在你的处置,所以你不需要 @BeforeStep,你可以删除它(在我的配置中它根本不被调用,当你把它想象为一个动作步骤没有多大意义但我没有看到NPE所以猜测那部分工作)。我们用两种方法之一解决了这个问题:

  1. 您可以将任何参数从tasklet放到作业中 ExecutionContext 直接使用 chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("inputFile", inputFilename);

  2. 你可以加 ExecutionContextPromotionListener 到你的tasklet步骤,然后做 chunkContext.getStepContext().getStepExecution().getExecutionContext().put("inputFile", inputFilename);


12
2018-03-18 10:44



我尝试了两种解决方案,我仍然有相同的错误消息。我应该做更多的事情,还是应该使用我的实际配置? - Carrm
我的错误:我没有使用好听众来解决第二个问题。它运作良好,谢谢! - Carrm