若是想在Spring中使用任務調度功能,除了集成調度框架Quartz這種方式,也可使用Spring本身的調度任務框架。html
使用Spring的調度框架,優勢是:支持註解(@Scheduler),能夠省去大量的配置。java
Spring3引入了TaskScheduler接口,這個接口定義了調度任務的抽象方法。web
TaskScheduler接口的聲明:spring
public interface TaskScheduler { ScheduledFuture<?> schedule(Runnable task, Trigger trigger); ScheduledFuture<?> schedule(Runnable task, Date startTime); ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period); ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period); ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay); ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay); }
從以上方法能夠看出TaskScheduler有兩個重要維度:spring-mvc
一個是要調度的方法,即一個實現了Runnable接口的線程類的run()方法;另外一個就是觸發條件。mvc
它有三個實現類:DefaultManagedTaskScheduler、ThreadPoolTaskSchedulerapp
、TimerManagerTaskScheduler。框架
DefaultManagedTaskScheduler:基於JNDI的調度器。異步
TimerManagerTaskScheduler:託管commonj.timers.TimerManager實例的調度器。ide
ThreadPoolTaskScheduler:提供線程池管理的調度器,它也實現了TaskExecutor接口,從而使的單一的實例能夠儘量快地異步執行。
Trigger接口抽象了觸發條件的方法。
Trigger接口的聲明:
publicinterface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
CronTrigger:實現了cron規則的觸發器類(和Quartz的cron規則相同)。
PeriodicTrigger:實現了一個週期性規則的觸發器類(例如:定義觸發起始時間、間隔時間等)。
實現一個調度任務的功能有如下幾個關鍵點:
(1) 定義調度器
在spring-bean.xml中進行配置
使用task:scheduler標籤訂義一個大小爲10的線程池調度器,spring會實例化一個ThreadPoolTaskScheduler。
注:不要忘記引入xsd:
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd"> <mvc:annotation-driven/> <task:scheduler id="myScheduler" pool-size="10"/> </beans>
(2)定義調度任務
定義實現Runnable接口的線程類。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DemoTask implements Runnable { final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void run() { logger.info("call DemoTask.run"); } }
(3)裝配調度器,並執行調度任務
在一個Controller類中用@Autowired註解裝配TaskScheduler。
而後調動TaskScheduler對象的schedule方法啓動調度器,就能夠執行調度任務了。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/scheduler") public class SchedulerController { @Autowired TaskScheduler scheduler; @RequestMapping(value = "/start", method = RequestMethod.POST) public void start() { scheduler.schedule(new DemoTask(), new CronTrigger("0/5 * * * * *")); } }
訪問/scheduler/start接口,啓動調度器,能夠看到以下日誌內容:
13:53:15.010 [myScheduler-1] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
13:53:20.003 [myScheduler-1] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
13:53:25.004 [myScheduler-2] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
13:53:30.005 [myScheduler-1] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
Spring的調度器一個很大的亮點在於@Scheduler註解,這能夠省去不少繁瑣的配置。
使用@Scheduler註解先要使用<task:annotation-driven>
啓動註解開關。
例:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd"> <mvc:annotation-driven/> <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> <task:executor id="myExecutor" pool-size="5"/> <task:scheduler id="myScheduler" pool-size="10"/> </beans>
例:使用fixedDelay指定觸發條件爲每5000毫秒執行一次。注意:必須在上一次調度成功後的5000秒才能執行。
@Scheduled(fixedDelay=5000)
publicvoid doSomething() {
// something that should execute periodically
}
例:使用fixedRate指定觸發條件爲每5000毫秒執行一次。注意:不管上一次調度是否成功,5000秒後必然執行。
@Scheduled(fixedRate=5000)
publicvoid doSomething() {
// something that should execute periodically
}
例:使用initialDelay指定方法在初始化1000毫秒後纔開始調度。
@Scheduled(initialDelay=1000, fixedRate=5000)
publicvoid doSomething() {
// something that should execute periodically
}
例:使用cron表達式指定觸發條件爲每5000毫秒執行一次。cron規則和Quartz中的cron規則一致。
@Scheduled(cron="*/5 * * * * MON-FRI")
publicvoid doSomething() {
// something that should execute on weekdays only
}
(1)啓動註解開關,並定義調度器和執行器
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd"> <mvc:annotation-driven/> <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> <task:executor id="myExecutor" pool-size="5"/> <task:scheduler id="myScheduler" pool-size="10"/> </beans>
(2)使用@Scheduler註解來修飾一個要調度的方法
下面的例子展現了@Scheduler註解定義觸發條件的不一樣方式。
importorg.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; /** * @title ScheduledTasks * @description 使用@Scheduler註解調度任務範例 * @author Vicotr Zhang * @date 2016年8月31日 */ @Component public class ScheduledMgr { private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Loggerlogger = LoggerFactory.getLogger(this.getClass()); /** * 構造函數中打印初始化時間 */ public ScheduledMgr() { logger.info("Current time: {}", dateFormat.format(new Date())); } /** * fixedDelay屬性定義調度間隔時間。調度須要等待上一次調度執行完成。 */ @Scheduled(fixedDelay = 5000) public void testFixedDelay() throws Exception { Thread.sleep(6000); logger.info("Current time: {}", dateFormat.format(new Date())); } /** * fixedRate屬性定義調度間隔時間。調度不等待上一次調度執行完成。 */ @Scheduled(fixedRate = 5000) public void testFixedRate() throws Exception { Thread.sleep(6000); logger.info("Current time: {}", dateFormat.format(new Date())); } /** * initialDelay屬性定義初始化後的啓動延遲時間 */ @Scheduled(initialDelay = 1000, fixedRate = 5000) public void testInitialDelay() throws Exception { Thread.sleep(6000); logger.info("Current time: {}", dateFormat.format(new Date())); } /** * cron屬性支持使用cron表達式定義觸發條件 */ @Scheduled(cron = "0/5 * * * * ?") public void testCron() throws Exception { Thread.sleep(6000); logger.info("Current time: {}", dateFormat.format(new Date())); } }
我刻意設置觸發方式的間隔都是5s,且方法中均有Thread.sleep(6000);語句。從而確保方法在下一次調度觸發時間點前沒法完成執行,來看一看各類方式的表現吧。
啓動spring項目後,spring會掃描@Component註解,而後初始化ScheduledMgr。
接着,spring會掃描@Scheduler註解,初始化調度器。調度器在觸發條件匹配的狀況下開始工做,輸出日誌。
截取部分打印日誌來進行分析。
10:58:46.479 [localhost-startStop-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.<init> - Current time: 2016-08-31 10:58:46 10:58:52.523 [myScheduler-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:58:52 10:58:52.523 [myScheduler-3] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:58:52 10:58:53.524 [myScheduler-2] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:58:53 10:58:55.993 [myScheduler-4] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:58:55 10:58:58.507 [myScheduler-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:58:58 10:58:59.525 [myScheduler-5] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:58:59 10:59:03.536 [myScheduler-3] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:03 10:59:04.527 [myScheduler-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:59:04 10:59:05.527 [myScheduler-4] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:59:05 10:59:06.032 [myScheduler-2] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:59:06 10:59:10.534 [myScheduler-9] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:59:10 10:59:11.527 [myScheduler-10] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:59:11 10:59:14.524 [myScheduler-4] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:14 10:59:15.987 [myScheduler-6] [INFO ] 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官方文檔:
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/