如何在运行时更改Spring的@Scheduled fixedDelay?

53

我有一个需求,需要定期运行批处理作业,并且能够在运行时更改此批处理作业的时间。为此,我了解到Spring框架提供了@Scheduled注释。但是我不确定如何在运行时更改fixedDelay的值。我进行了一些谷歌搜索,但没有找到任何有用的信息。


我看到你已经接受了最佳答案,但我仍然看到有一些未解决的问题。NPE问题解决了吗?你能否发布整个解决方案呢?干杯 - despot
你可以在这里看到我的答案:https://dev59.com/tmUp5IYBdhLWcg3wdnh6#51333059 - grep
6个回答

72
在 Spring Boot 中,您可以直接使用应用程序属性!例如:
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds}000")
private void process() {
    // your impl here
}
请注意,如果属性未定义,您也可以设置默认值,例如将默认值设置为“60”(秒):
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds:60}000")

我发现的其他事情:

  • 方法必须是void类型
  • 方法不能有参数
  • 方法可以是private私有访问修饰符

我发现使用private访问修饰符很方便,我是这样使用的:

@Service
public class MyService {
    public void process() {
        // do something
    }

    @Scheduled(fixedDelayString = "${my.poll.fixed.delay.seconds}000")
    private void autoProcess() {
        process();
    }
}

通过设置为私有(private),计划方法可以局限于您的服务,而不会成为您的服务API的一部分。

此外,这种方法允许process()方法返回值,而@Scheduled方法则不能。例如,您的process()方法可以如下所示:

public ProcessResult process() {
    // do something and collect information about what was done
    return processResult; 
}

提供有关处理期间发生的情况的一些信息。


谢谢,fixedDelayString 就是我在寻找的。 - prettyvoid
8
谢谢您的解决方案,但fixedDelay如何在运行时更新? - Clay Banks
57
这并不是一个有用的回答。原帖子要求在运行时进行操作,而你的解决方案需要重新启动? - dnang
@dnang 嗯,启动仍然是运行时。我相信 OP 使用了不正确的术语。他们应该使用的术语是“动态值”或类似的东西,意味着它应该能够在运行时更改。这个解决方案是在运行时但是静态的。 - Bakuriu
问题是关于动态改变固定延迟的。这不是解决方案。 - undefined
显示剩余7条评论

36

FYI - 我在代码中注意到 NullPointerException bug,并给你留了一条评论。 - jsf
有没有一种方法可以在当前触发器正在休眠时中断它并更改其值。 - jsf
你也可以看看我的答案:https://dev59.com/tmUp5IYBdhLWcg3wdnh6#51333059 - grep

15

创建接口,类似于这样:

    public abstract class DynamicSchedule{
        /**
         * Delays scheduler
         * @param milliseconds - the time to delay scheduler.
         */
        abstract void delay(Long milliseconds);

        /**
         * Decreases delay period
         * @param milliseconds - the time to decrease delay period.
         */
        abstract void decreaseDelayInterval(Long milliseconds);

        /**
         * Increases delay period
         * @param milliseconds - the time to increase dela period
        */
        abstract void increaseDelayInterval(Long milliseconds);
}

接下来,让我们实现Trigger接口,该接口位于spring-context项目中的org.springframework.scheduling位置。

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;

public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {

    private TaskScheduler taskScheduler;
    private ScheduledFuture<?> schedulerFuture;

    /**
     * milliseconds
     */
    private long delayInterval;

    public CustomDynamicSchedule(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
    }


    @Override
    public void increaseDelayInterval(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval += delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public void decreaseDelayInterval(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval -= delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public void delay(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval = delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public Date nextExecutionTime(TriggerContext triggerContext) {
        Date lastTime = triggerContext.lastActualExecutionTime();
        return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
    }
}

当前配置:

@Configuration
public class DynamicSchedulerConfig {
    @Bean
    public CustomDynamicSchedule getDynamicScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.initialize();
        return  new CustomDynamicSchedule(threadPoolTaskScheduler);
    }
}

和用法:

@EnableScheduling
@Component
public class TestSchedulerComponent {

    @Autowired
    private CustomDynamicSchedule dynamicSchedule;

    @Scheduled(fixedDelay = 5000)
    public void testMethod() {
        dynamicSchedule.delay(1000l);
        dynamicSchedule.increaseDelayInterval(9000l);
        dynamicSchedule.decreaseDelayInterval(5000l);
    }

}

这个语法是什么意思?() -> { } - Aliuk
我的评论的答案在这个问题中:https://dev59.com/U1QK5IYBdhLWcg3wL9Fq - Aliuk
你可以传递可运行的类。例如,你可以传递一个在时间改变时记录日志的类。 - grep
在2021年还要用@grep查找XML配置文件?! - parsecer
@parsecer 答案写于2018年7月13日 :) - grep
显示剩余6条评论

10
您还可以使用Spring表达式语言(SpEL)来实现此功能。
@Scheduled(fixedDelayString = "#{@applicationPropertyService.getApplicationProperty()}")
public void getSchedule(){
   System.out.println("in scheduled job");
}

@Service
public class ApplicationPropertyService {

    public String getApplicationProperty(){
        //get your value here
        return "5000";
    }
}

我的问题是,如果我们覆盖AppProp.Service类并给出两个不同的延迟时间,调度程序会运行两次吗? - mfaisalhyder
1
寻找类似于这样的东西 :) - ahrooran
无法在运行时更改。 - Maxrunner
是的,如果您需要在运行时进行更改,这将不起作用。根据您的使用情况,您可能希望使用触发器。 - Sagar Ahuja

2
据我所知,Spring API不会让您访问需要更改触发器的内部内容。但是您可以手动配置bean来实现此功能:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="startDelay" value="10000" />
    <property name="repeatInterval" value="50000" />
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

如在SchedulerFactoryBean中所述:

要在运行时动态注册作业,请使用对此SchedulerFactoryBean的bean引用以直接访问Quartz Scheduler (org.quartz.Scheduler)。这允许您创建新作业和触发器,并控制和监视整个调度程序。


1
我曾经遇到过同样的问题。我们需要在运行时更改cron表达式并重新安排服务。所以应该有以下要求:
  • 无需重新编译
  • 无需重新部署
  • 无需重新启动
我检查了所有流行的解决方案,但只有其中2个满足所有要求。 SchedulingConfigurer方法的缺点是它是基于拉取的,即每次执行服务的业务逻辑时都会拉取调度配置。总的来说这并不是件坏事情,但如果很少更改配置而执行间隔又很短,则会产生很多不必要的请求。 自定义解决方案 的劣势在于需要更多的编码工作,但它是基于推送的,并且会对配置更改做出反应,因此不会执行不必要的请求/调用。

如何只保存周期在数据库中实现? - Maxrunner

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