抢占先机
VMware 提供培训和认证,助您加速进步。
了解更多继续 Keith 和 Chris 开始的 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 相关的内容。