Accessing scheduled tasks in Spring
Asked Answered
C

4

19

I have a couple of tasks scheduled within Spring's task scheduler:

<task:scheduled-tasks>
    <task:scheduled ref="task1" method="run"
        cron="0 0 */0 * * *" />
    <task:scheduled ref="task2" method="run"
        cron="0 0 */30 * * *" />
</task:scheduled-tasks>

<task:scheduler id="scheduler" pool-size="10" />

How can I access a list of scheduled tasks and retrieve meta-information (e.g the next execution time) from within the application context?

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
ThreadPoolTaskScheduler scheduler = (ThreadPoolTaskScheduler)context.getBean("scheduler");
//... how to continue from here?
Cattier answered 29/7, 2014 at 14:20 Comment(2)
It's now not context.getBean("scheduler") anymore, but context.getBean("taskScheduler"), at least in Spring Boot 2.6.3.Caterpillar
@Caterpillar That depends: If you are using spring-boot you already might have an autoconfigured bean that of course comes with its preconfigured bean name (id). The example above explicitly declares a scheduler bean with the "scheduler" id...Cattier
E
5

There is no public API in Spring to do this.

Related:

Eyebolt answered 29/7, 2014 at 14:42 Comment(0)
D
5

I just figured this out

start with this to get whats scheduled.

    ThreadPoolTaskScheduler xScheduler = (ThreadPoolTaskScheduler)this.taskScheduler;

    ScheduledThreadPoolExecutor xService = (ScheduledThreadPoolExecutor)xScheduler.getScheduledExecutor();

    BlockingQueue<Runnable> queue = xService.getQueue();
    Object[] scheduledJobs = queue.toArray();

If this array look at instance in debugger to find what you need out.

Then write reflection code like this to get at the hidden API's in Spring and Java. See the set Accessible this is only way to get at these private items. You might need to use different public classes to get at certain private fields, look at api docs and view source on these classes in eclipse.

            Method delayM = obj.getClass().getDeclaredMethod("getDelay", TimeUnit.class);
            delayM.setAccessible(true);
            // delayM = obj.getClass().getDeclaredMethod("getDelay", TimeUnit.class);
            Long delay = (Long)delayM.invoke(obj, new Object[] { tu } );

The trigger and root runnable is in the callable field of this object , instance of ReschedulingRunnable which is not a public class, ask Spring why they did this. You can get the delegate out of DelegatingErrorHandlingRunnable with reflection.

Dulcinea answered 20/11, 2015 at 15:18 Comment(0)
S
2

If you are using Spring Boot actuator in your project, there will be an endpoint (/actuator/scheduledtasks) doing this.

You can also use the code responsible for that endpoint in order to access it. If you take a look at the source code and see that the classes are public so you can use them if you have a dependency on spring-boot-actuator (with the compile-scope):

private final ScheduledTasksEndpoint endpoint;//TODO autowire
ScheduledTasksDescriptor scheduledTasks = endpoint.scheduledTasks();
for(TaskDescriptor taskDescriptor : scheduledTasks.getCron()){//iterate through all cron tasks here
    RunnableDescriptor rDescriptor = taskDescriptor.getRunnable();
    String target = rDescriptor.getTarget();
    if(taskDescriptor instanceof CronTaskDescriptor desc) {
        String expression = desc.getExpression();
    }
}

However, that won't allow you to actually run the tasks, just to get information about them. But since you are only interested in meta-information, this should be fine.

Swagman answered 19/9, 2023 at 9:11 Comment(0)
C
0

@Randy Poznan's answer is already pretty good, but it uses unecessary reflections and the delayM.setAccessible(true) may cause problems with Java 17 and it's stricter access rules (runtime exceptions!).

A solution without reflections is this:

// Bean is named "taskScheduler" now
ThreadPoolTaskScheduler taskScheduler = (ThreadPoolTaskScheduler) context.getBean("taskScheduler");
ScheduledThreadPoolExecutor scheduledExecutor =
    (ScheduledThreadPoolExecutor) taskScheduler.getScheduledExecutor();

BlockingQueue<Runnable> queue = scheduledExecutor.getQueue();
if (queue.isEmpty()) {
    return null;
} else {
    for (Runnable runnable : queue) {
        // This is it: Casting to `ScheduledFuture`.
        var scheduledFuture = (ScheduledFuture) runnable;
        final long delay = scheduledFuture.getDelay(TimeUnit.MINUTES);

        System.out.println("Delay: " + delay + " on runnable: " + runnable);
    }
}
Caterpillar answered 9/3, 2022 at 10:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.