我可以将单个静态项目注入 Spring 批处理项目 reader 吗?

Can I inject a single, static item into a Spring Batch item reader?

我们有一个 Spring 批处理作业,可以从文件中提取收件人的动态列表。我们想添加一个额外的收件人作为质量控制。我考虑添加一个新的 tasklet,它只是吐出这条记录并将其传递给真正的 reader。我在这里阅读了一些问题、其他地方的文章以及有关在 Spring 批处理步骤之间传输数据的文档,但我不确定这是完成此操作的最简单或最佳方法。

喜欢official documentation using listeners, this article using autowired components and different listeners, and this question and answers.

如果我确实设置了生成器 tasklet 并将其数据传递到 reader,我将如何将其插入到 reader 的实际记录中?

我们正在使用的一些代码片段 – 它纯粹是注释驱动的,任何地方都没有 XML 配置设置。

步骤生成器

public Step loadRecipients() {
    return stepBuilderFactory.get("loadRecipients").<Recipient, Recipient>chunk(chunkSize)
            .reader(recipientsItemReader)
            .processor(recipientsItemProcessor)
            .writer(recipientsWriter)
            .taskExecutor(taskExecutor)
            .throttleLimit(1)
            .build();
}

Reader 配置

@StepScope
public FlatFileItemReader<Recipient> recipientItemReader() {

    FlatFileItemReader<Recipient> itemReader = new FilePrefixItemReader<>(
            "theFilePath",
            staticResourceLoader(),
            FunctionUtils.propagateExceptions((org.springframework.core.io.Resource resource) -> new GZIPInputStream(resource.getInputStream()))
    );

    userCategoryItemReader.setLineMapper(userCategoriesDefaultLineMapper);

    return userCategoryItemReader;

}

我是否应该用一些时髦的包装器将我的额外记录骗到资源输入流中?我可以使用其他一些 Spring 魔法来添加我的静态记录吗?

wrap/extend Writer 并在那里添加静态项,粗略的源代码:

public class AddStaticItemWriter implements ItemWriter<String> {

    @Override
    public void write(final List<? extends String> items) throws Exception {
        // check some funky condition
        if (addStaticItem) {
            items.add(STATIC_ITEM);
        }
        // business code
        // or delegate to underlying writer
    }
}

一些提示(优点,缺点):

  • 添加的项目 spring 批处理未知,可能会导致一些奇怪的回滚场景(跳过,重试)
  • 像上面一样,您可以包装 reader 并在那里添加项目

我最终没有去歪曲项目作者,而是为此制作了一个特定的 tasklet。项目编写器方法的主要缺点是当前的实现非常精简并且有很多重用代码。扩展项目编写器添加了一些不属于那里的代码。

tasklet 的主要优点是坚持单一职责原则。让 tasklet 写入数据库资源非常容易。如果编写器正在写入更复杂的资源(例如 REST 模板或文件目标),则混合编写器会更干净。 (请注意,需要更多代码才能按顺序获取所有接收者参数,这只是一个基本的 tasklet 示例。

/**
 * Inject the internal email recipient, for monitoring and informational purposes.
 */
public class InjectInternalEmailRecipientTasklet implements Tasklet{

    public static final Float DEFAULT_MAX_AFFINITY_SCORE = 1.0f;

    private UserCategoryRepository userCategoryRepository;

    public InjectInternalEmailRecipientTasklet(RecipientRepository recipientRepository) {
        this.recipientRepository = recipientRepository;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

        // We can safely inject this record even on non-prod environments because the email processor obfuscates all emails on
        // non-prod environments. N.B. we do not want the internal user to receive TEST emails/placements.

        recipeintRepository.bulkInsert(new Recipient("testemail@example.com");
        return RepeatStatus.FINISHED;
    }
}

将 tasklet 步骤添加到作业配置中也很简单。

public Job loadRecipients() {
    return jobs.get("loadRecipients")
            .start(truncateRecipientsStep())
            .next(injectStaticAnalyticsUserCategoryStep())
            .next(loadRecipients())
            .preventRestart()
            .build();
}

public Step injectInternalEmailRecipientStep() {
    return stepBuilderFactory.get("injectAnalyticsEmailUserCategoryStep")
            .tasklet(injectInternalEmailRecipientTasklet())
            .build();
}

public Tasklet injectInternalEmailRecipientTasklet() {
    return new InjectInternalEmailRecipientTasklet(recipientRepository);
}

作业配置如此冗长是为了遵循能够很好地服务于更复杂作业的模式。