Spring <task:scheduled>对象在运行时如何表示?

17

我有一个应用程序使用了 "task:scheduler" 和 "task:scheduled-tasks" 元素(后者包含 "task:scheduled" 元素)。这一切都工作得很好。

我正在尝试编写一些代码,以检查 "应用程序配置" 并获取一些重要信息的简短摘要,例如哪些任务已计划以及它们的计划表。

我已经有一个类,其中有一堆 "@Autowired" 实例变量,所以我可以遍历所有这些变量。添加一个 "List" 来获取所有的 TaskScheduler 对象很容易。我只有其中两个,每个对象中都有不同的已计划任务集合。

在这些 TaskScheduler 对象中,我看不到任何像是已计划任务列表的东西,因此我猜想已计划任务的列表记录在其他地方。

我可以使用哪些对象来检查已计划任务的集合以及它们所在的线程池?

4个回答

18

这个功能将在Spring 4.2中可用。

https://jira.spring.io/browse/SPR-12748(免责声明:我报告了此问题并为解决方案做出了贡献)。

// Warning there may be more than one ScheduledTaskRegistrar in your
// application context. If this is the case you can autowire a list of 
// ScheduledTaskRegistrar instead.
@Autowired   
private ScheduledTaskRegistrar scheduledTaskRegistrar; 

public List<Task> getScheduledTasks() {
    List<Task> result = new ArrayList<Task>();
    result.addAll(this.scheduledTaskRegistrar.getTriggerTaskList());
    result.addAll(this.scheduledTaskRegistrar.getCronTaskList());
    result.addAll(this.scheduledTaskRegistrar.getFixedRateTaskList());
    result.addAll(this.scheduledTaskRegistrar.getFixedDelayTaskList());
    return result;
}

// You can this inspect the tasks, 
// so for example a cron task can be inspected like this:

public List<CronTask> getScheduledCronTasks() {
    List<CronTask> cronTaskList = this.scheduledTaskRegistrar.getCronTaskList();
    for (CronTask cronTask : cronTaskList) {
        System.out.println(cronTask.getExpression);
    }
    return cronTaskList;
}

如果您正在使用在XML中定义的ScheduledMethodRunnable:

<task:scheduled method="run" cron="0 0 12 * * ?" ref="MyObject" />

你可以访问底层目标对象:

 ScheduledMethodRunnable scheduledMethodRunnable = (ScheduledMethodRunnable) task.getRunnable();
 TargetClass target = (TargetClass) scheduledMethodRunnable.getTarget();

很好,现在可以检查计划任务了!您贡献的代码是否也能使在运行时更改触发器成为可能,例如将固定速率任务的速率从1秒更改为2秒? - Markus Pscheidt
@Markus,你能详细说明一下你的使用场景吗?这样我就能更好地理解你想要解决的问题了。Spring调度任务架构被设计为不可变的。意图是,如果你需要更改已计划任务的属性,则会取消现有任务并安排一个新任务。 - Tobias M
我的应用程序中的间隔应该可以从用户界面进行配置。每x秒钟,执行一个任务进行一些计算。该任务可能会在一段时间内每10秒运行一次,然后用户决定每5秒运行它。任何类型的任务都可以(例如TriggerTask),只要间隔可以在运行时更改即可。 - Markus Pscheidt
1
当您使用@Scheduled注释时,似乎无法正常工作,因为ScheduledTaskRegistrar是私有的,并且只能在ScheduledAnnotationBeanPostProcessor处理注释时可用。 - ngeek
2
@ngeek 我已经提交了一个PR来修复这个问题,看看它是否被接受。https://github.com/spring-projects/spring-framework/pull/878 https://jira.spring.io/browse/SPR-13506 - Tobias M
@Markus 目前除非您想使用一些反射,否则这是不可能的。我不建议这样做。您需要从ScheduledTaskRegistrar获取Set<ScheduledFuture<?>>并取消任务。然后,您需要获取TaskScheduler并安排一个新任务。 - Tobias M

3

我有一段代码片段,适用于Spring 4.2之前的版本,因为它仍然停留在候选发布级别。

scheduledFuture接口由BlockingQueue中的每个可运行元素实现。

        Map<String, ThreadPoolTaskScheduler> schedulers = applicationContext
                .getBeansOfType(ThreadPoolTaskScheduler.class);
        for (ThreadPoolTaskScheduler scheduler : schedulers.values()) {
            ScheduledExecutorService exec = scheduler.getScheduledExecutor();
            ScheduledThreadPoolExecutor poolExec = scheduler
                    .getScheduledThreadPoolExecutor();
            BlockingQueue<Runnable> queue = poolExec.getQueue();
            Iterator<Runnable> iter = queue.iterator();
            while (iter.hasNext()) {
                ScheduledFuture<?> future = (ScheduledFuture<?>) iter.next();
                future.getDelay(TimeUnit.MINUTES);
            Runnable job = iter.next();
            logger.debug(MessageFormat.format(":: Task Class is {0}", JobDiscoverer.findRealTask(job)));
            }

以下是翻译的结果:

以下是一种反思方式,可获取关于哪个作业类在池中的信息,因为 threadPoolNamePrefix 对我来说没有返回不同的名称:

public class JobDiscoverer {
    private final static Field syncInFutureTask;
    private final static Field callableInFutureTask;
    private static final Class<? extends Callable> adapterClass;
    private static final Field runnableInAdapter;
    private static Field reschedulingRunnable;
    private static Field targetScheduledMethod;

    static {
        try {

            reschedulingRunnable = Class
                    .forName(
                            "org.springframework.scheduling.support.DelegatingErrorHandlingRunnable")
                    .getDeclaredField("delegate");
            reschedulingRunnable.setAccessible(true);

            targetScheduledMethod = Class
                    .forName(
                            "org.springframework.scheduling.support.ScheduledMethodRunnable")
                    .getDeclaredField("target");
            targetScheduledMethod.setAccessible(true);
            callableInFutureTask = Class.forName(
                    "java.util.concurrent.FutureTask$Sync").getDeclaredField(
                    "callable");
            callableInFutureTask.setAccessible(true);
            syncInFutureTask = FutureTask.class.getDeclaredField("sync");
            syncInFutureTask.setAccessible(true);

            adapterClass = Executors.callable(new Runnable() {
                public void run() {
                }
            }).getClass();
            runnableInAdapter = adapterClass.getDeclaredField("task");
            runnableInAdapter.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new ExceptionInInitializerError(e);
        } catch (SecurityException e) {
            throw new PiaRuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new PiaRuntimeException(e);
        }
    }

    public static Object findRealTask(Runnable task) {
        if (task instanceof FutureTask) {
            try {
                Object syncAble = syncInFutureTask.get(task);
                Object callable = callableInFutureTask.get(syncAble);
                if (adapterClass.isInstance(callable)) {
                    Object reschedulable = runnableInAdapter.get(callable);
                    Object targetable = reschedulingRunnable.get(reschedulable);
                    return targetScheduledMethod.get(targetable);
                } else {
                    return callable;
                }
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
        throw new ClassCastException("Not a FutureTask");
    }

3

使用基于@Scheduled的配置时,Tobias M的回答中的方法不能直接使用。

无法通过自动装配ScheduledTaskRegistrar实例(在注解式配置中不可用)来解决问题,而可以自动装配一个ScheduledTaskHolder,它只有一个getScheduledTasks()方法。

背景:

ScheduledAnnotationBeanPostProcessor用于管理@Scheduled任务,具有一个内部ScheduledTaskRegistrar,但它不是一个bean。然而,它确实实现了ScheduledTaskHolder


感谢您提供有关ScheduledAnnotationBeanPostProcessor的提示。 - SScholl

0

每个Spring XML元素都有一个对应的BeanDefinitionReader。对于<task:scheduled-tasks>,它是ScheduledTasksBeanDefinitionParser

这使用BeanDefinitionBuilder创建一个类型为ContextLifecycleScheduledTaskRegistrar的bean的BeanDefinition。您的定时任务将存储在该bean中。

任务将在默认的TaskScheduler或您提供的任务调度程序中执行。

我已经给出了类名,以便您自己查看源代码,如果需要更细粒度的细节。


我在ContextLifecycleScheduledTaskRegistrar类或相关的TaskScheduler类的公共接口中没有看到任何可以让我读取这些信息的内容。我想我可以使用一些严重的反射技巧,但这显然不是理想的选择。 - David M. Karr
@David 我手头没有,但是请检查注册机的父类。 - Sotirios Delimanolis
我试过了。那是ScheduledTaskRegistrar。我也找不到任何有用的东西。在它上面是Object。 - David M. Karr
@David,那些任务列表不是你要找的吗? - Sotirios Delimanolis
任务列表及其时间表正是我所需要的。这些类提供了一个接口来添加新任务,但没有公共接口来列出已安排的任务。 - David M. Karr
@DavidM.Karr 您说得对,确实没有。这些任务是您应该知道的,因为您是提交它们的人。如果您想检索它们,您将不得不使用反射。请注意,java.util.concurrent类也没有检索机制。 - Sotirios Delimanolis

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