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)。在最后一个“beginning work”消息和第一个“completed work”消息之间应该有大约 5 秒的暂停。如果所有工作程序都在单个线程中运行,我们将会看到连续的 beginning/completed 对,并且整个过程将花费大约 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 表达式可以比这强大得多(我只是不想创建一个仅在某些日期或时间运行的示例)。值得指出的是,对基于 cron 的调度的支持直接包含在 Spring 3.0 本身中。

请务必查看 Spring 3.0参考手册任务执行和调度章节,以了解有关新的 TaskScheduler 抽象和 Trigger 策略的更多信息,这些策略为这里您所看到的内容提供了基础。参考手册还讨论了“task”命名空间提供的其他元素,用于使用特定的线程池设置配置 TaskScheduler 和 TaskExecutor 实例。

希望这篇文章对这些新功能提供了一个有用的概述。敬请关注 SpringSource 团队博客,获取更多与 Spring 3.0 相关的內容。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,帮助您快速提升进度。

了解更多

获取支持

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

了解更多

即将举行的活动

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

查看全部