领先一步
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”元素指向包含我们“beans”的包。有两个: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 相关内容。