You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编程式添加调度任务时Actuator系统状态缺失调度器数据

问题描述

当前技术栈为Java 11、Spring Boot,此前通过@Scheduled注解实现的调度任务会自动展示在Actuator的#/administration/systemstatus页面。
因业务需要新增UI端强制触发调度任务的能力,需求逻辑如下:

  • 手动触发时调用TaskScheduler的schedule方法,将任务调度为当前时间立即执行
  • 任务执行结束后,按照application.properties中配置的cron表达式重新调度任务(默认任务始终按cron规则运行,仅少数场景支持用户通过UI额外触发)
    原生@Scheduled注解无法覆盖上述需求,因此编写了自定义调度组件,但出现副作用:Actuator系统状态页面不再展示任务及对应cron表达式的相关数据。
    自定义调度核心代码如下(重调度逻辑未展示):
@Component
@Slf4j
public class MyTaskService {

    private final TaskScheduler scheduler;
    private final List<CustomTask> taskList; //runnable tasks
    private final RunningTasksFutureHolder runningTasksFutureHolder;

    public MyTaskService(TaskScheduler scheduler,
                               List<CustomTask> taskList,
                               RunningTasksFutureHolder runningTasksFutureHolder) {
        this.scheduler = scheduler;
        this.taskList = taskList;
        this.runningTasksFutureHolder = runningTasksFutureHolder;
    }

    @PostConstruct
    public void afterLoad() {
        taskList.forEach(this::addTaskToScheduler);
    }

    public void addTaskToScheduler(CustomTask task) {
    // 此处添加到调度器的任务无法在Actuator中展示
        ScheduledFuture<?> scheduledTask = scheduler.schedule(task, new CronTrigger(task.getCronExpression()));
        runningTasksFutureHolder.add(task.getTaskType(), scheduledTask);
    }

}

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 空实现,未做任何任务注册
    }

    @Bean
    public TaskScheduler poolScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
        scheduler.setPoolSize(5);
        scheduler.initialize();
        return scheduler;
    }
}
问题根因

Spring Boot Actuator的调度任务端点(schedules端点)的数据源是容器中所有实现了ScheduledTaskHolder接口的Bean所记录的任务信息。
当前实现存在两个问题导致任务无法被采集:

  1. 自定义任务直接调用TaskScheduler.schedule()方法提交,没有被任何ScheduledTaskHolder实现类记录,Actuator无法感知到这些任务的存在
  2. SchedulingConfigurer的configureTasks方法为空,自定义的ThreadPoolTaskScheduler没有和Spring内置的ScheduledTaskRegistrar(本身就是ScheduledTaskHolder的实现类)绑定,绕开了Spring原生的任务注册流程
修复方案

所有任务的注册、取消、重调度操作都通过ScheduledTaskRegistrar完成,保证任务信息被Spring的调度注册表记录,Actuator即可正常采集展示。

第一步:修改调度配置类

在configureTasks方法中完成线程池初始化、与ScheduledTaskRegistrar的绑定,同时对外暴露注册器实例供后续业务操作使用:

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {

    private ScheduledTaskRegistrar taskRegistrar;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
        // 初始化调度线程池
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
        scheduler.setPoolSize(5);
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }

    public ScheduledTaskRegistrar getTaskRegistrar() {
        return taskRegistrar;
    }

    public TaskScheduler getTaskScheduler() {
        return taskRegistrar.getScheduler();
    }
}

第二步:修改自定义任务服务类

初始任务注册、手动触发后的重调度都通过ScheduledTaskRegistrar添加Cron任务,不要直接绕过注册器调用TaskScheduler的调度方法:

@Component
@Slf4j
public class MyTaskService implements ApplicationListener<ContextRefreshedEvent> {

    private final SchedulingConfig schedulingConfig;
    private final List<CustomTask> taskList;
    private final RunningTasksFutureHolder runningTasksFutureHolder;
    private ScheduledTaskRegistrar taskRegistrar;
    private TaskScheduler scheduler;

    public MyTaskService(SchedulingConfig schedulingConfig,
                         List<CustomTask> taskList,
                         RunningTasksFutureHolder runningTasksFutureHolder) {
        this.schedulingConfig = schedulingConfig;
        this.taskList = taskList;
        this.runningTasksFutureHolder = runningTasksFutureHolder;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 上下文刷新完成后获取注册器和调度器实例
        this.taskRegistrar = schedulingConfig.getTaskRegistrar();
        this.scheduler = schedulingConfig.getTaskScheduler();
        // 初始化注册所有默认cron任务
        taskList.forEach(this::addCronTaskToRegistrar);
        // 触发注册器提交所有任务到调度器
        taskRegistrar.afterPropertiesSet();
    }

    /**
     * 注册cron任务到注册器,会自动被Actuator采集
     */
    private void addCronTaskToRegistrar(CustomTask task) {
        taskRegistrar.addCronTask(task, task.getCronExpression());
    }

    /**
     * UI端手动触发任务的示例逻辑
     */
    public void triggerTaskImmediate(CustomTask task) {
        // 1. 取消该任务原有的cron调度
        taskRegistrar.getCronTaskList().stream()
                .filter(cronTask -> cronTask.getRunnable().equals(task))
                .findFirst()
                .ifPresent(cronTask -> {
                    ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
                    if (scheduledTask != null) {
                        scheduledTask.cancel();
                    }
                });
        // 2. 提交立即执行的任务
        ScheduledFuture<?> immediateTaskFuture = scheduler.schedule(task, Instant.now());
        runningTasksFutureHolder.add(task.getTaskType(), immediateTaskFuture);
        try {
            // 等待任务执行完成
            immediateTaskFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            log.error("手动触发任务执行异常", e);
            Thread.currentThread().interrupt();
        } finally {
            // 3. 重新注册cron任务,恢复默认调度规则
            addCronTaskToRegistrar(task);
            taskRegistrar.afterPropertiesSet();
        }
    }
}

配置校验

如果使用Spring Boot 2.7+版本,确认配置中没有禁用schedules端点:

management.endpoint.schedules.enabled=true

完成上述修改后,所有通过ScheduledTaskRegistrar注册的任务都会自动同步到Actuator的调度任务列表中,页面可正常展示任务名称、对应cron表达式等信息。


内容的提问来源于stack exchange,提问作者Frankio1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 02:48:26