SpringBoot实现运行时可动态修改的定时任务

本文适用需求场景

原本使用cron表达式或fixedDelay/fixedRate的@Scheduled简单定时任务,但是间隔有时会改变,希望能运行时修改而无需每次更改代码和重启服务器

分析

scheduling-enable-annotation-support
根据文档,想实现更细粒度的控制,需要实现SchedulingConfigurer

@FunctionalInterface
public interface SchedulingConfigurer {
	void configureTasks(ScheduledTaskRegistrar taskRegistrar);
}

scheduling-trigger-implementations
想配置可动态修改的定时任务,需要实现自己的Trigger
原生的Trigger实现类只有CronTriggerPeriodicTrigger

CronTrigger部分源码

	private final CronExpression expression;

	private final ZoneId zoneId;
	@Override
	public Date nextExecutionTime(TriggerContext triggerContext) {
		Date date = triggerContext.lastCompletionTime();
		if (date != null) {
			Date scheduled = triggerContext.lastScheduledExecutionTime();
			if (scheduled != null && date.before(scheduled)) {
				// Previous task apparently executed too early...
				// Let's simply use the last calculated execution time then,
				// in order to prevent accidental re-fires in the same second.
				date = scheduled;
			}
		}
		else {
			date = new Date(triggerContext.getClock().millis());
		}
		ZonedDateTime dateTime = ZonedDateTime.ofInstant(date.toInstant(), this.zoneId);
		ZonedDateTime next = this.expression.next(dateTime);
		return (next != null ? Date.from(next.toInstant()) : null);
	}

计算下次执行时间的方法依赖于这两个private final参数,无法被修改

PeriodicTrigger部分源码

	private final long period;

	private final TimeUnit timeUnit;

	private volatile long initialDelay;

	private volatile boolean fixedRate;
	@Override
	public Date nextExecutionTime(TriggerContext triggerContext) {
		Date lastExecution = triggerContext.lastScheduledExecutionTime();
		Date lastCompletion = triggerContext.lastCompletionTime();
		if (lastExecution == null || lastCompletion == null) {
			return new Date(triggerContext.getClock().millis() + this.initialDelay);
		}
		if (this.fixedRate) {
			return new Date(lastExecution.getTime() + this.period);
		}
		return new Date(lastCompletion.getTime() + this.period);
	}

同样无法修改

为了最大程度兼容已有定时任务,减少修改量,直接基于原有两种Trigger,创建新的实现类

public class MutableCronTrigger implements Trigger {
    private CronExpression expression;

    private ZoneId zoneId;

    public void reconstruct(String expression) {
        reconstruct(expression, ZoneId.systemDefault());
    }

    public void reconstruct(String expression, TimeZone timeZone) {
        reconstruct(expression, timeZone.toZoneId());
    }

    public void reconstruct(String expression, ZoneId zoneId) {
        Assert.hasLength(expression, "Expression must not be empty");
        Assert.notNull(zoneId, "ZoneId must not be null");

        this.expression = CronExpression.parse(expression);
        this.zoneId = zoneId;
    }
    ...

可通过reconstruct方法重新修改参数

public class MutablePeriodicTrigger implements Trigger {
    public void setPeriod(long period) {
        this.period = period;
    }

    private volatile long period;

    private final TimeUnit timeUnit;

    private volatile long initialDelay;

    private volatile boolean fixedRate;
    ...

可修改执行间隔即periodfixedRate(Rate/Delay切换)setter函数原就是public

自定义ISchedule接口

这里设计一个接口是为了

  1. 方便一次加载所有自定义的定时任务
  2. 便于已有@Scheduled任务升级(原定时任务方法放在execute中,注解的参数单独拿出作为Trigger
import org.springframework.scheduling.Trigger;

public interface ISchedule {
    void execute();

    Trigger trigger();
}

SchedulingConfigurer 实现类

@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
    @Autowired
    List<ISchedule> scheduleList;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        if (scheduleList != null && scheduleList.size() != 0)
            scheduleList.forEach(schedule -> taskRegistrar.addTriggerTask(schedule::execute, schedule.trigger()));
    }
}

测试类

@Configuration
public class TestSchedule implements ISchedule {

    @Override
    @Async
    public void execute() {
        System.out.println("------");
        System.out.println(new Date());
        System.out.println("------");
    }

    @Override
    public Trigger trigger() {
        return trigger;
    }

    Trigger trigger = new MutableCronTrigger("*/5 * * * * *");
}

启动项目,成功打印日志

@RestController
public class TestController {

    @Autowired
    BlockSchedule blockSchedule;

    @RequestMapping("test")
    public void test() {
        ((MutableCronTrigger) blockSchedule.trigger()).reconstruct("*/10 * * * * *");
    }
}

测试修改,每整10秒一次,成功(最后一次会按老的cron执行,然后nextExecutionTime方法获取下一次执行时间才会改变)

后记

@Scheduled只支持单机,轻量便于快速开发,如果有集群需求,更换quartz

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值