Spring 集成调度器

概述

如果想在Spring中使用任务调度功能,除了集成调度框架Quartz这种方式,也可以使用Spring自己的调度任务框架。使用Spring的调度框架,优点是:支持注解@Scheduler,可以省去大量的配置。

实时触发调度任务

TaskScheduler接口

Spring3引入了TaskScheduler接口,这个接口定义了调度任务的抽象方法。TaskScheduler接口的声明:

  1. public interface TaskScheduler {
  2. ScheduledFuture schedule(Runnable task, Trigger trigger);
  3. ScheduledFuture schedule(Runnable task, Date startTime);
  4. ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
  5. ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
  6. ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
  7. ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
  8. }

从以上方法可以看出TaskScheduler有两类重要参数:

  • 一个是要调度的方法,即一个实现了Runnable接口的线程类的run()方法;
  • 另一个就是触发条件。

TaskScheduler接口的实现类它有三个实现类:DefaultManagedTaskSchedulerThreadPoolTaskSchedulerTimerManagerTaskSchedulerDefaultManagedTaskScheduler:基于JNDI的调度器。TimerManagerTaskScheduler:托管commonj.timers.TimerManager实例的调度器。ThreadPoolTaskScheduler:提供线程池管理的调度器,它也实现了TaskExecutor接口,从而使的单一的实例可以尽可能快地异步执行。

Trigger接口

Trigger接口抽象了触发条件的方法。Trigger接口的声明:

  1. public interface Trigger {
  2. Date nextExecutionTime(TriggerContext triggerContext);
  3. }

Trigger接口的实现类CronTrigger:实现了cron规则的触发器类(和Quartz的cron规则相同)。PeriodicTrigger:实现了一个周期性规则的触发器类(例如:定义触发起始时间、间隔时间等)。

完整范例

实现一个调度任务的功能有以下几个关键点:(1) 定义调度器在spring-bean.xml中进行配置使用task:scheduler标签定义一个大小为10的线程池调度器,spring会实例化一个ThreadPoolTaskScheduler

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc"
  5. xmlns:task="http://www.springframework.org/schema/task"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  8. http://www.springframework.org/schema/mvc
  9. http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  10. http://www.springframework.org/schema/task
  11. http://www.springframework.org/schema/task/spring-task-3.1.xsd">
  12. <mvc:annotation-driven/>
  13. <task:scheduler id="myScheduler" pool-size="10"/>
  14. </beans>

注:不要忘记引入xsd:

  1. http://www.springframework.org/schema/task
  2. http://www.springframework.org/schema/task/spring-task-3.1.xsd

(2) 定义调度任务定义实现Runnable接口的线程类。

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. public class DemoTask implements Runnable {
  4. final Logger logger = LoggerFactory.getLogger(this.getClass());
  5. @Override
  6. public void run() {
  7. logger.info("call DemoTask.run");
  8. }
  9. }

(3) 装配调度器,并执行调度任务在一个Controller类中用@Autowired注解装配TaskScheduler。然后调动TaskScheduler对象的schedule方法启动调度器,就可以执行调度任务了。

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.scheduling.TaskScheduler;
  3. import org.springframework.scheduling.support.CronTrigger;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RequestMethod;
  7. @Controller
  8. @RequestMapping("/scheduler")
  9. public class SchedulerController {
  10. @Autowired
  11. TaskScheduler scheduler;
  12. @RequestMapping(value = "/start", method = RequestMethod.POST)
  13. public void start() {
  14. scheduler.schedule(new DemoTask(), new CronTrigger("0/5 * * * * *"));
  15. }
  16. }

访问/scheduler/start接口,启动调度器,可以看到如下日志内容:

  1. 13:53:15.010 myScheduler-1 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
  2. 13:53:20.003 myScheduler-1 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
  3. 13:53:25.004 myScheduler-2 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
  4. 13:53:30.005 myScheduler-1 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run

@Scheduler的使用方法

Spring的调度器一个很大的亮点在于@Scheduler注解,这可以省去很多繁琐的配置。

启动注解

使用@Scheduler注解先要使用<task:annotation-driven>启动注解开关。例:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc"
  5. xmlns:task="http://www.springframework.org/schema/task"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  8. http://www.springframework.org/schema/mvc
  9. http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  10. http://www.springframework.org/schema/task
  11. http://www.springframework.org/schema/task/spring-task-3.1.xsd">
  12. <mvc:annotation-driven/>
  13. <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
  14. <task:executor id="myExecutor" pool-size="5"/>
  15. <task:scheduler id="myScheduler" pool-size="10"/>
  16. </beans>

@Scheduler定义触发条件

例:使用fixedDelay指定触发条件为每5000毫秒执行一次。注意:必须在上一次调度成功后的5000秒才能执行。

  1. @Scheduled(fixedDelay=5000)
  2. public void doSomething() {
  3. // something that should execute periodically
  4. }

例:使用fixedRate指定触发条件为每5000毫秒执行一次。注意:无论上一次调度是否成功,5000秒后必然执行。

  1. @Scheduled(fixedRate=5000)
  2. public void doSomething() {
  3. // something that should execute periodically
  4. }

例:使用initialDelay指定方法在初始化1000毫秒后才开始调度。

  1. @Scheduled(initialDelay=1000, fixedRate=5000)
  2. public void doSomething() {
  3. // something that should execute periodically
  4. }

例:使用cron表达式指定触发条件为每5000毫秒执行一次。cron规则和Quartz中的cron规则一致。

  1. @Scheduled(cron="*/5 * * * * MON-FRI")
  2. public void doSomething() {
  3. // something that should execute on weekdays only
  4. }

完整范例

(1) 启动注解开关,并定义调度器和执行器

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc"
  5. xmlns:task="http://www.springframework.org/schema/task"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  8. http://www.springframework.org/schema/mvc
  9. http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  10. http://www.springframework.org/schema/task
  11. http://www.springframework.org/schema/task/spring-task-3.1.xsd">
  12. <mvc:annotation-driven/>
  13. <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
  14. <task:executor id="myExecutor" pool-size="5"/>
  15. <task:scheduler id="myScheduler" pool-size="10"/>
  16. </beans>

(2) 使用@Scheduler注解来修饰一个要调度的方法下面的例子展示了@Scheduler注解定义触发条件的不同方式。

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.scheduling.annotation.Scheduled;
  4. import org.springframework.stereotype.Component;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Date;
  7. /**
  8. * @description 使用@Scheduler注解调度任务范例
  9. * @author Vicotr Zhang
  10. * @date 2016年8月31日
  11. */
  12. @Component
  13. public class ScheduledMgr {
  14. private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  15. final Logger logger = LoggerFactory.getLogger(this.getClass());
  16. /**
  17. * 构造函数中打印初始化时间
  18. */
  19. public ScheduledMgr() {
  20. logger.info("Current time: {}", dateFormat.format(new Date()));
  21. }
  22. /**
  23. * fixedDelay属性定义调度间隔时间。调度需要等待上一次调度执行完成。
  24. */
  25. @Scheduled(fixedDelay = 5000)
  26. public void testFixedDelay() throws Exception {
  27. Thread.sleep(6000);
  28. logger.info("Current time: {}", dateFormat.format(new Date()));
  29. }
  30. /**
  31. * fixedRate属性定义调度间隔时间。调度不等待上一次调度执行完成。
  32. */
  33. @Scheduled(fixedRate = 5000)
  34. public void testFixedRate() throws Exception {
  35. Thread.sleep(6000);
  36. logger.info("Current time: {}", dateFormat.format(new Date()));
  37. }
  38. /**
  39. * initialDelay属性定义初始化后的启动延迟时间
  40. */
  41. @Scheduled(initialDelay = 1000, fixedRate = 5000)
  42. public void testInitialDelay() throws Exception {
  43. Thread.sleep(6000);
  44. logger.info("Current time: {}", dateFormat.format(new Date()));
  45. }
  46. /**
  47. * cron属性支持使用cron表达式定义触发条件
  48. */
  49. @Scheduled(cron = "0/5 * * * * ?")
  50. public void testCron() throws Exception {
  51. Thread.sleep(6000);
  52. logger.info("Current time: {}", dateFormat.format(new Date()));
  53. }
  54. }

我刻意设置触发方式的间隔都是5s,且方法中均有Thread.sleep(6000);语句。从而确保方法在下一次调度触发时间点前无法完成执行,来看一看各种方式的表现吧。启动spring项目后,spring会扫描@Component注解,然后初始化ScheduledMgr。接着,spring会扫描@Scheduler注解,初始化调度器。调度器在触发条件匹配的情况下开始工作,输出日志。截取部分打印日志来进行分析。

  1. 10:58:46.479 localhost-startStop-1 o.z.n.s.scheduler.ScheduledTasks.<init> - Current time: 2016-08-31 10:58:46
  2. 10:58:52.523 myScheduler-1 o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:58:52
  3. 10:58:52.523 myScheduler-3 o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:58:52
  4. 10:58:53.524 myScheduler-2 o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:58:53
  5. 10:58:55.993 myScheduler-4 o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:58:55
  6. 10:58:58.507 myScheduler-1 o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:58:58
  7. 10:58:59.525 myScheduler-5 o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:58:59
  8. 10:59:03.536 myScheduler-3 o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:03
  9. 10:59:04.527 myScheduler-1 o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:59:04
  10. 10:59:05.527 myScheduler-4 o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:59:05
  11. 10:59:06.032 myScheduler-2 o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:59:06
  12. 10:59:10.534 myScheduler-9 o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:59:10
  13. 10:59:11.527 myScheduler-10 o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:59:11
  14. 10:59:14.524 myScheduler-4 o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:14
  15. 10:59:15.987 myScheduler-6 o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:59:15

构造方法打印一次,时间点在10:58:46。testFixedRate打印四次,每次间隔6秒。说明,fixedRate不等待上一次调度执行完成,在间隔时间达到时立即执行。testFixedDelay打印三次,每次间隔大于6秒,且时间不固定。说明,fixedDelay等待上一次调度执行成功后,开始计算间隔时间,再执行。testInitialDelay第一次调度时间和构造方法调度时间相隔7秒。说明,initialDelay在初始化后等待指定的延迟时间才开始调度。testCron打印三次,时间间隔并非5秒或6秒,显然,cron等待上一次调度执行成功后,开始计算间隔时间,再执行。此外,可以从日志中看出,打印日志的线程最多只有10个,说明2.1中的调度器线程池配置生效。

参考

Spring Framework官方文档