概述
如果想在Spring中使用任務排程功能,除了內建排程架構Quartz這種方式,也可以使用Spring自己的排程任務架構。
使用Spring的排程架構,優點是:支援注解(@Scheduler),可以省去大量的配置。
實時觸發排程任務
TaskScheduler接口
Spring3引入了TaskScheduler接口,這個接口定義了排程任務的抽象方法。
TaskScheduler接口的聲明:
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有兩個重要次元:
一個是要排程的方法,即一個實作了Runnable接口的線程類的run()方法;另一個就是觸發條件。
TaskScheduler接口的實作類
它有三個實作類:DefaultManagedTaskScheduler、ThreadPoolTaskScheduler
、TimerManagerTaskScheduler。
DefaultManagedTaskScheduler:基于JNDI的排程器。
TimerManagerTaskScheduler:托管commonj.timers.TimerManager執行個體的排程器。
ThreadPoolTaskScheduler:提供線程池管理的排程器,它也實作了TaskExecutor接口,進而使的單一的執行個體可以盡可能快地異步執行。
Trigger接口
Trigger接口抽象了觸發條件的方法。
Trigger接口的聲明:
publicinterface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
Trigger接口的實作類
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
@Scheduler的使用方法
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>
@Scheduler定義觸發條件
例:使用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/