每次Spring定时任务(@Scheduled)运行之前重置状态

3
我有一个Spring Boot批处理应用程序,需要每天运行。它会读取一个每日文件,在数据上进行一些处理,并将处理后的数据写入数据库。在此过程中,应用程序会保存一些状态,例如要读取的文件(存储在FlatFileItemReaderJobParameters中),运行时的当前日期和时间,一些用于比较读取项的文件数据等。
调度的一个选项是使用Spring的@Scheduled,如下所示:
@Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
    jobRunner.runJob(); //runs the batch job by calling jobLauncher.run(job, jobParameters);
}

这里的问题在于状态会在运行之间被保留。因此,我必须更新要读取的文件、当前运行的日期和时间、清除缓存的文件数据等等。
另一个选项是通过Unix Cron作业运行应用程序。这显然可以满足在运行之间清除状态的需求,但我更喜欢将作业调度与应用程序绑定,而不是与操作系统绑定(并且偏爱它对操作系统不可知)。@Scheduled 运行之间是否可以重置应用程序状态?

@Thomas Kåsene,@Hansjoerg Wingeier,感谢你们的帮助。这种方法确实有效,但是我有相当多的单例作用域的bean。我想我可以将它们全部更改为step或者prototype作用域。虽然可能可行,但与使用Unix cron相比似乎很繁琐。我还尝试了在主应用程序类中使用定时静态runJob来刷新上下文。但是这样做会导致GenericApplicationContext不支持多次刷新尝试的错误。 - undefined
我已经添加了一个示例,展示了如何处理单例bean。请看一下我编辑过的示例。 - undefined
2个回答

5
你可以将执行任务(更重要的是保持状态)的代码移动到原型范围的bean中。然后,每次运行计划方法时,您都可以从应用程序上下文中检索该bean的新实例。
示例
我创建了一个GitHub存储库,其中包含我所说内容的工作示例,但其要点在这两个类中:

ScheduledTask.java

注意@Scope注释。它指定此组件不应为单例。 randomNumber字段表示我们希望在每次调用时重置的状态。在这种情况下,“重置”意味着生成一个新的随机数,以表明它确实发生了变化。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class ScheduledTask {

    private double randomNumber = Math.random();

    void execute() {
        System.out.printf(
            "Executing task from %s. Random number is %f%n",
            this,
            randomNumber
        );
    }
}

TaskScheduler.java

通过在ApplicationContext中进行自动装配,您可以在scheduledTask方法内使用它来检索ScheduledTask的新实例。
@Component
public class TaskScheduler {

    @Autowired
    private ApplicationContext applicationContext;

    @Scheduled(cron = "0/5 * * * * *")
    public void scheduleTask() {
        ScheduledTask task = applicationContext.getBean(ScheduledTask.class);
        task.execute();
    }
}

输出

运行代码时,以下是一个示例:

Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask@329c8d3d. Random number is 0.007027
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask@3c5b751e. Random number is 0.145520
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask@3864e64d. Random number is 0.268644

谢谢你的回答。我本来会标记为已接受的,但是Hansjoerg Wingeier在处理Spring Batch应用程序中的多个单例bean时提供了更多信息。我已经给你的回答点赞,因为它非常有帮助。 - undefined

3

Thomas的方法似乎是一个合理的解决方案,这就是为什么我投了赞成票。缺少的是如何在Spring Batch作业的情况下应用它。因此,我稍微改进了他的例子:

@Component
public class JobCreatorComponent {


    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Job createJob() {
       // use the jobBuilderFactory to create your job as usual
       return jobBuilderFactory.get() ...
    }
}

您的组件带有启动方法:

@Component
public class ScheduledLauncher {

   @Autowired
   private ... jobRunner;

   @Autwired
   private JobCreatorComponent creator;

@Scheduled(cron = "${schedule}")
public void runJob() throws Exception {

    // it would probably make sense to check the applicationContext and
    // remove any existing job

    creator.createJob(); // this should create a complete new instance of 
                         //  the Job
    jobRunner.runJob(); //runs the batch job by calling jobLauncher.run(job, jobParameters);
}

我还没有尝试过这段代码,但这是我想尝试的方法。

在构建作业时,重要的是要确保所有读取器、处理器和写入器都是完全新的实例。这意味着,如果它们不是作为纯Java对象(而不是Spring Bean)或作为具有“步骤”范围的Spring Bean 实例化的,您必须确保始终使用新实例。

编辑: 如何处理Singleton Beans 有时无法避免单例Bean,在这些情况下必须有一种“重置”它们的方法。

一个简单的方法是定义一个“ResetableBean”接口,其中包含由这些bean实现的重置方法。然后可以使用Autowired收集所有这些bean的列表。

@Component
public class ScheduledLauncher {

    @Autowired
    private List<ResetableBean> resetables;

    ...

    @Scheduled(cron = "${schedule}")
    public void runJob() throws Exception {
       // reset all the singletons
       resetables.forEach(bean -> bean.reset());
       ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接