Spring 调度任务 - 运行 仅一次

Spring scheduling task - run only once

是否可以在指定的时间仅安排一次 Spring 服务方法?例如,当前时间是下午 2 点,但当我按下操作按钮时,我希望我的服务方法在晚上 8 点开始。我熟悉 @Scheduled 注释,但我不确定如何定期将 cron 表达式写成 运行。这个 @Scheduled(cron = "0 0 20 * * ?") 每天晚上 8 点触发。
有什么建议吗?

您可以使用 Spring 的 TaskScheduler 实现之一。我在下面提供了一个不需要太多配置的示例(包装单线程计划执行程序的 ConcurrentTaskScheduler)。

The simplest method is the one named schedule that takes a Runnable and Date only. That will cause the task to run once after the specified time. All of the other methods are capable of scheduling tasks to run repeatedly.

阅读更多关于task execution & scheduling

简单的工作示例:

private TaskScheduler scheduler;

Runnable exampleRunnable = new Runnable(){
    @Override
    public void run() {
        System.out.println("Works");
    }
};

@Async
public void executeTaskT() {
    ScheduledExecutorService localExecutor = Executors.newSingleThreadScheduledExecutor();
    scheduler = new ConcurrentTaskScheduler(localExecutor);

    scheduler.schedule(exampleRunnable,
            new Date(1432152000000L));//today at 8 pm UTC - replace it with any timestamp in miliseconds to text
}

...

executeTaskT() //call it somewhere after the spring application has been configured

注意

To enable support for @Scheduled and @Async annotations add @EnableScheduling and @EnableAsync to one of your @Configuration classes


更新 - 取消计划任务

TaskScheduler 的调度方法 return 是一个 ScheduledFuture,它是一个 可以取消的延迟结果承载操作

所以为了取消它,您需要保留计划任务的句柄(即保留 ScheduledFuture return 对象)。

更改上述代码以取消任务:

  1. 在 executeTaskT 方法之外声明 ScheduledFuture。
    private ScheduledFuture scheduledFuture;
  1. 修改您对 schedule 的调用以保持 return 对象原样:
    scheduledFuture = scheduler.schedule(exampleRunnable,
                    new Date(1432152000000L));
  1. 在您的代码中的某处对 scheduledFuture 对象调用取消
    boolean mayInterruptIfRunning = true;
    scheduledFuture.cancel(mayInterruptIfRunning);

为了不在每次方法调用时创建 ScheduledExecutorServiceConcurrentTaskScheduler,在服务创建时初始化 TaskScheduler 很方便,例如

private final TaskScheduler taskScheduler = 
              new ConcurrentTaskScheduler(Executors.newScheduledThreadPool(10));

@Async 没有任何意义,因为我们只是安排任务并退出方法。

您可以按如下方式扩展 PeriodicTrigger - 它会检查 lastCompletionTime:如果任务以前从未 运行 过,它将为 null。如果你想 运行 在某个给定时间只执行一次任务,你可以尝试这个的变体。

class RunOnceTrigger extends PeriodicTrigger {
    public RunOnceTrigger(long period) {
        super(period);
        setInitialDelay(period);
    }

    @Override
    public Date nextExecutionTime(TriggerContext triggerContext) {
        if(triggerContext.lastCompletionTime() == null) {   // hasn't executed yet
            return super.nextExecutionTime(triggerContext);
        }
        return null;
    }
}

死人的解决方法:

@Scheduled(initialDelay = 1000 * 30, fixedDelay=Long.MAX_VALUE)

你会在它再次开火前死去。

问题的标题暗示需要 one-shot 任务 运行ner。这是一个 class 的任务,它将在延迟后 运行 一次(或根据 OP 请求替换为纪元)。此外,您可以在 应用程序中 enable/disable 它。properties/yaml 或配置文件。

@Slf4j
@Component
@ConditionalOnProperty({ "app.scheduler.onceRunner" })
public class RunOnceTask
{
    public static final long INITIAL_DELAY_SECS = 5L;  // As you wish

    @Autowired
    @Qualifier("mainTaskScheduler") // You could set a custom TPTS to set threads, name, etc.
    private ThreadPoolTaskScheduler poolTaskScheduler;

    @PostConstruct
    public void run()
    {
        poolTaskScheduler.scheduleWithFixedDelay( () -> {

            log.info( "Once-runner ran once" );

        }, new Date( System.currentTimeMillis() + (INITIAL_DELAY_SECS * 1000) ), Long.MAX_VALUE );    // Run once
    }
}