能否为Spring定时任务传入自定义Clock实例以测试每日调度方法?
让Spring定时调度器使用自定义Clock实例的解决方案
这个需求完全可行!Spring的定时调度模块提供了足够的扩展性,我们可以通过自定义TaskScheduler和Trigger的实现,让调度器用上你共享的Clock实例,不用依赖系统时钟。下面是具体的实现步骤和代码示例:
1. 自定义支持Clock的CronTrigger
Spring默认的CronTrigger使用系统时钟计算下次执行时间,我们需要重写它的逻辑,替换成自定义的Clock:
public class ClockAwareCronTrigger extends CronTrigger { private final Clock clock; public ClockAwareCronTrigger(String cronExpression, Clock clock) { super(cronExpression); this.clock = clock; } @Override public Date nextExecutionTime(TriggerContext triggerContext) { // 用自定义Clock获取当前时间,覆盖TriggerContext的时钟 Instant currentInstant = clock.instant(); TriggerContext customContext = new TriggerContext() { @Override public Date lastScheduledExecutionTime() { return triggerContext.lastScheduledExecutionTime(); } @Override public Date lastActualExecutionTime() { return triggerContext.lastActualExecutionTime(); } @Override public Date lastCompletionTime() { return triggerContext.lastCompletionTime(); } @Override public Instant lastScheduledExecution() { return triggerContext.lastScheduledExecution(); } @Override public Instant lastActualExecution() { return triggerContext.lastActualExecution(); } @Override public Instant lastCompletion() { return triggerContext.lastCompletion(); } @Override public Instant getClock() { return currentInstant; } }; return super.nextExecutionTime(customContext); } }
2. 包装默认TaskScheduler,替换Trigger
我们需要实现TaskScheduler接口,包装Spring默认的调度器实例,在调度任务时把普通的CronTrigger替换成我们上面自定义的版本:
@Component("myScheduler") public class ClockAwareTaskScheduler implements TaskScheduler { private final TaskScheduler delegateScheduler; private final Clock sharedClock; // 注入默认的调度器实例和共享的Clock public ClockAwareTaskScheduler(@Qualifier("mySchedulerDelegate") TaskScheduler delegateScheduler, Clock sharedClock) { this.delegateScheduler = delegateScheduler; this.sharedClock = sharedClock; } @Override public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) { // 只处理CronTrigger,其他类型Trigger可按需扩展 if (trigger instanceof CronTrigger) { CronTrigger originalTrigger = (CronTrigger) trigger; ClockAwareCronTrigger customTrigger = new ClockAwareCronTrigger(originalTrigger.getExpression(), sharedClock); return delegateScheduler.schedule(task, customTrigger); } return delegateScheduler.schedule(task, trigger); } // 重写其他调度方法,直接委托给默认调度器即可 @Override public ScheduledFuture<?> schedule(Runnable task, Date startTime) { return delegateScheduler.schedule(task, startTime); } @Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) { return delegateScheduler.scheduleAtFixedRate(task, startTime, period); } @Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) { return delegateScheduler.scheduleAtFixedRate(task, period); } @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { return delegateScheduler.scheduleWithFixedDelay(task, startTime, delay); } @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) { return delegateScheduler.scheduleWithFixedDelay(task, delay); } }
3. 修改Spring XML配置
原来的调度器配置需要调整,把默认的调度器作为delegate,注册我们自定义的调度器为myScheduler:
<!-- 保留原来的调度器配置,修改id为delegate --> <task:scheduler id="mySchedulerDelegate" pool-size="10" /> <!-- 注册自定义的调度器,注入delegate和共享Clock --> <bean id="myScheduler" class="com.yourpackage.ClockAwareTaskScheduler"> <constructor-arg ref="mySchedulerDelegate" /> <constructor-arg ref="sharedClock" /> </bean> <!-- 把你的共享Clock注册为Spring Bean,方便注入 --> <bean id="sharedClock" class="java.time.Clock" factory-method="system"> <constructor-arg value="#{T(com.yourpackage.TimeUtil).getDefaultZoneId()}" /> </bean> <!-- 其他原有配置保持不变 --> <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> <task:scheduled-tasks scheduler="myScheduler"> <task:scheduled ref="refillingServiceImpl" method="refill" cron="0 0 21 * * *" /> </task:scheduled-tasks> <task:executor id="myExecutor" pool-size="5"/>
4. 测试时的使用
现在你测试时只需要通过TimeUtil.setClock()修改共享的Clock实例,调度器会自动使用这个自定义时钟计算执行时间:
// June 18, 2020 20:59:50 Local Time (Kiev, GMT +3), Thursday long offset = 1592503190 - Instant.now().getEpochSecond(); TimeUtil.setClock(Clock.offset(Clock.system(TimeUtil.getDefaultZoneId()), Duration.ofSeconds(offset)));
需要确保TimeUtil里的setClock方法更新的是Spring容器中的sharedClock实例,比如可以这样实现TimeUtil:
public class TimeUtil { private static Clock clock; // Spring初始化时注入 @Autowired public void setClock(Clock clock) { TimeUtil.clock = clock; } public static void setClock(Clock customClock) { TimeUtil.clock = customClock; } public static ZoneId getDefaultZoneId() { return ZoneId.of("Europe/Kiev"); } // 其他使用clock的方法... }
这样修改后,你的定时任务就会和其他非调度方法一样,使用同一个自定义Clock实例,测试时无需单独处理调度器的时钟问题。
内容的提问来源于stack exchange,提问作者Oleg Trofimenko
相关产品推荐
相关产品推荐

