Spring 3.0 中的任务调度简化

工程 | Mark Fisher | 2010年1月5日 | ...

继续 KeithChris 开始的 Spring 3.0“简化系列”,我想快速概述一下 Spring 3.0 在调度和任务执行方面实现的简化。

我将逐步介绍一个基本的示例应用,您可以从 spring-samples Subversion 仓库中检出。它的设计力求简单,同时展示了 Spring 3.0 中基于注解和基于 XML 的任务调度方法。

让我们从基于注解的方法开始。您可以通过 AnnotationDemo 中的 main() 方法直接运行它。如果您查看一下,就会发现它只是一个 Spring ApplicationContext 的引导程序


public static void main(String[] args) {
    new ClassPathXmlApplicationContext("config.xml", AnnotationDemo.class);
}

之所以不需要其他任何东西,是因为 ApplicationContext 包含一个“活动”组件,我们稍后就会看到。由于这个组件的存在,main() 方法不会退出。config.xml 也非常精简,只包含两个元素


<context:component-scan base-package="org/springframework/samples/task/basic/annotation"/>

<task:annotation-driven/>

"component-scan" 元素指向包含我们“bean”的包。它们有两个:ScheduledProcessor 和 AsyncWorker。我们稍后会查看它们,但首先看看 "annotation-driven" 元素。这是 Spring 3.0 中新增的元素,它驱动两个注解:@Scheduled 和 @Async。您可以通过 "scheduler" 和 "executor" 属性分别提供对 Spring TaskScheduler 和 TaskExecutor 的引用,但对于本示例,我们将依赖默认设置。

ScheduledProcessor 的一个方法上包含 @Scheduled 注解,因此它是上面提到的“活动”组件。由于配置中存在 'annotation-driven' 元素,该方法将被注册到一个 Spring TaskScheduler 实例,该实例将以固定的 30 秒延迟周期性地执行该方法。


@Service
public class ScheduledProcessor implements Processor {

    private final AtomicInteger counter = new AtomicInteger();

    @Autowired
    private Worker worker;

    @Scheduled(fixedDelay = 30000)
    public void process() {
        System.out.println("processing next 10 at " + new Date());
        for (int i = 0; i < 10; i++) {
            worker.work(counter.incrementAndGet());
        }
    }
}

正如您在前面的代码片段中看到的,Worker 在 ScheduledProcessor 的一个循环中被调用。然而,AsyncWorker 实现的 work(..) 方法上包含 @Async 注解,由于配置中存在 'annotation-driven' 元素,该方法将被包装在一个代理中,从而实际由 TaskExecutor 实例调用。为了验证这一点,该方法内部显示了当前线程名称。同样,为了阐明工作正在并发执行,调用了 sleep(..) 方法来模拟耗时的工作。


@Component
public class AsyncWorker implements Worker {

    @Async
    public void work(int i) {
        String threadName = Thread.currentThread().getName(); 
        System.out.println("   " + threadName + " beginning work on " + i);
        try {
            Thread.sleep(5000); // simulates work
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("   " + threadName + " completed work on " + i);
    }
}

如果您运行 AnnotationDemo 的 main() 方法,输出应该类似于: processing next 10 at Mon Jan 04 18:20:52 EST 2010 SimpleAsyncTaskExecutor-1 beginning work on 1 SimpleAsyncTaskExecutor-2 beginning work on 2 SimpleAsyncTaskExecutor-3 beginning work on 3 SimpleAsyncTaskExecutor-5 beginning work on 5 SimpleAsyncTaskExecutor-4 beginning work on 4 SimpleAsyncTaskExecutor-6 beginning work on 6 SimpleAsyncTaskExecutor-7 beginning work on 7 SimpleAsyncTaskExecutor-8 beginning work on 8 SimpleAsyncTaskExecutor-9 beginning work on 9 SimpleAsyncTaskExecutor-10 beginning work on 10 SimpleAsyncTaskExecutor-1 completed work on 1 SimpleAsyncTaskExecutor-2 completed work on 2 SimpleAsyncTaskExecutor-3 completed work on 3 SimpleAsyncTaskExecutor-5 completed work on 5 SimpleAsyncTaskExecutor-6 completed work on 6 SimpleAsyncTaskExecutor-7 completed work on 7 SimpleAsyncTaskExecutor-8 completed work on 8 SimpleAsyncTaskExecutor-4 completed work on 4 SimpleAsyncTaskExecutor-10 completed work on 10 SimpleAsyncTaskExecutor-9 completed work on 9

关于该输出,有几点需要注意。首先,每次处理 10 行的操作将每隔 30 秒重复一次(由于 @Scheduled)。其次,工作项正在由不同的线程并发处理(由于 @Async)。最后一个“开始工作”消息和第一个“完成工作”消息之间应该有大约 5 秒的暂停。如果所有工作都在单个线程中运行,我们将看到顺序的开始/完成对,整个过程大约需要 50 秒。当然,时间方面无法在此博客文章中体现,所以您真的应该下载并亲自运行示例以获得完整效果(该项目可以直接导入到SpringSource Tool Suite或其他支持 Maven 的基于 Eclipse 的环境中)。

我想展示的最后一件事是 @Scheduled 注解的基于 XML 的替代方案。该示例包含另一个类 SimpleProcessor,其 process() 方法上不包含 @Scheduled 注解


@Service
public class SimpleProcessor implements Processor {

    private final AtomicInteger counter = new AtomicInteger();

    public void process() {
        System.out.println("processing next 10 at " + new Date());
        for (int i = 0; i < 10; i++) {
            System.out.println("   processing " + counter.incrementAndGet());
        }
    }
}

基于 XML 的版本只比基于注解的版本略微冗长,因为 Spring 3.0 现在提供了一个“task”命名空间来保持配置的简洁。


<context:component-scan base-package="org/springframework/samples/task/basic/xml"/>

<task:scheduled-tasks>
    <task:scheduled ref="simpleProcessor" method="process" cron="3/10 * * * * ?"/>
</task:scheduled-tasks>

如果您运行 XmlDemo 中的 main() 方法,您将看到该 process 每 10 秒执行一次。为了多样性,此示例使用 cron 表达式而不是简单的固定延迟。因此,您会注意到定时是基于 3 秒的偏移量(:13, :23 等),但当然 cron 表达式比这强大得多(我只是不想创建一个只在特定日期或时间运行的示例)。值得指出的是,Spring 3.0 本身就直接包含了对基于 cron 的调度的支持。

请务必查阅 Spring 3.0 参考手册任务执行和调度一章,了解更多关于新的 TaskScheduler 抽象和 Trigger 策略,它们为您在此处看到的内容奠定了基础。参考手册还讨论了“task”命名空间提供的其他元素,用于配置具有特定线程池设置的 TaskScheduler 和 TaskExecutor 实例。

我希望这篇文章对这些新特性提供了一个有用的概述。请继续关注 SpringSource 团队博客,获取更多与 Spring 3.0 相关的内容。

订阅 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

抢占先机

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举办的活动

查看 Spring 社区即将举办的所有活动。

查看全部