在工做中有用到spring task做爲定時任務的處理,spring經過接口TaskExecutor
和TaskScheduler
這兩個接口的方式爲異步定時任務提供了一種抽象。這就意味着spring允許你使用其餘的定時任務框架,固然spring自身也提供了一種定時任務的實現:spring task。spring task支持線程池,能夠高效處理許多不一樣的定時任務。同時,spring還支持使用Java自帶的Timer
定時器和Quartz
定時框架。限於篇幅,這裏將只介紹spring task的使用。java
其實,官方文檔已經介紹地足夠詳細,只不過都是英文版,因此爲了更好地理解並使用spring task,首先會對spring task的實現原理作一個簡單的介紹,而後經過實際代碼演示spring task是如何使用的。這裏會涉及到一個很重要的知識點:cron表達式。spring
TaskExecutor和TaskSchedulerapache
TaskExecutor是spring task的第一個抽象,它很天然讓人聯想到jdk中concurrent包下的Executor
,實際上TaskExecutor就是爲區別於Executor
才引入的,而引入TaskExecutor的目的就是爲定時任務的執行提供線程池的支持,那麼,問題來了,爲何spring不直接使用jdk自帶的Executor呢?TaskExecutor源碼以下?app
public interface TaskExecutor extends Executor { void execute(Runnable var1); }
那麼,答案很顯然,TaskExecutor提供的線程池支持也是基於jdk自帶的Executor的。用法於Executor沒有什麼不一樣。框架
TaskScheduler是spring task的第二個抽象,那麼從字面的意義看,TaskScheduler就是爲了提供定時任務的支持咯。TaskScheduler須要傳入一個Runnable的任務作爲參數,並指定須要週期執行的時間或者觸發器,這樣Runnable任務就能夠週期性執行了。傳入時間很好理解,有意思的是傳入一個觸發器(Trigger
)的狀況,由於這裏須要使用cron表達式去觸發一個定時任務,因此有必要先了解下cron表達式的使用。異步
在spring 4.x中已經不支持7個參數的cronin表達式了,要求必須是6個參數(具體哪一個參數後面會說)。cron表達式的格式以下:maven
{秒} {分} {時} {日期(具體哪天)} {月} {星期}
,
-
*
/
,,
表示特定的某一秒纔會觸發任務,-
表示一段時間內會觸發任務,*
表示每一秒都會觸發,/
表示從哪個時刻開始,每隔多長時間觸發一次任務。?
,表示與{星期}互斥,即意味着若明確指定{星期}觸發,則表示{日期}無心義,以避免引發衝突和混亂。?
,表達的含義是與{日期}互斥,即意味着若明確指定{日期}觸發,則表示{星期}無心義。好比下面這個cron表達式:測試
// 表達的含義是:每半分鐘觸發一次任務 30 * * * * ?
spring提供了一個CronTrigger
,經過傳入一個Runnable任務和CronTrigger,就可使用cron表達式去指定定時任務了,是否是很是方面。實際上,在工程實踐上,cron表達式也是使用不少的。實際上,是執行了下面的代碼:ui
scheduler.schedule(task, new CronTrigger("30 * * * * ?"));
TaskScheduler抽象的好處是讓須要執行定時任務的代碼不須要指定特定的定時框架(好比Timer和Quartz)。TaskScheduler的更簡單的實現是ThreadPoolTaskScheduler
,它實際上代理一個jdk中的SchedulingTaskExecutor
,而且也實現了TaskExecutor接口,因此須要常常執行定時任務的場景可使用這個實現(Spring推薦)。咱們再來看一下TaskExecutor和TaskScheduler的類繼承關係:spa
一般而言,使用spring task實現定時任務有兩種方式:註解和xml配置文件。這裏使用xml配置文件的方式加以說明。
實戰
建立Maven工程,pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.rhwayfun</groupId> <artifactId>sring-task-demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.4.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
開發須要執行定時任務的方法:
package com.rhwayfun.task; import org.springframework.stereotype.Component; import java.time.LocalDateTime; /** * @author ZhongCB * @date 2016年09月10日 14:30 * @description */ @Component public class App { public void execute1(){ System.out.printf("Task: %s, Current time: %s\n", 1, LocalDateTime.now()); } public void execute2(){ System.out.printf("Task: %s, Current time: %s\n", 2, LocalDateTime.now()); } public void execute3(){ System.out.printf("Task: %s, Current time: %s\n", 3, LocalDateTime.now()); } public void execute4(){ System.out.printf("Task: %s, Current time: %s\n", 4, LocalDateTime.now()); } public void execute5(){ System.out.printf("Task: %s, Current time: %s\n", 5, LocalDateTime.now()); } public void execute6(){ System.out.printf("Task: %s, Current time: %s\n", 6, LocalDateTime.now()); } public void execute7(){ System.out.printf("Task: %s, Current time: %s\n", 7, LocalDateTime.now()); } public void execute8(){ System.out.printf("Task: %s, Current time: %s\n", 8, LocalDateTime.now()); } public void execute9(){ System.out.printf("Task: %s, Current time: %s\n", 9, LocalDateTime.now()); } public void execute10(){ System.out.printf("Task: %s, Current time: %s\n", 10, LocalDateTime.now()); } public void execute11(){ System.out.printf("Task: %s, Current time: %s\n", 11, LocalDateTime.now()); } }
spring配置文件以下:
<?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:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd"> <!-- 配置註解掃描 --> <context:component-scan base-package="com.rhwayfun.task"/> <task:scheduler id="taskScheduler" pool-size="100" /> <task:scheduled-tasks scheduler="taskScheduler"> <!-- 每半分鐘觸發任務 --> <task:scheduled ref="app" method="execute1" cron="30 * * * * ?"/> <!-- 每小時的10分30秒觸發任務 --> <task:scheduled ref="app" method="execute2" cron="30 10 * * * ?"/> <!-- 天天1點10分30秒觸發任務 --> <task:scheduled ref="app" method="execute3" cron="30 10 1 * * ?"/> <!-- 每個月20號的1點10分30秒觸發任務 --> <task:scheduled ref="app" method="execute4" cron="30 10 1 20 * ?"/> <!-- 每一年10月20號的1點10分30秒觸發任務 --> <task:scheduled ref="app" method="execute5" cron="30 10 1 20 10 ?"/> <!-- 每15秒、30秒、45秒時觸發任務 --> <task:scheduled ref="app" method="execute6" cron="15,30,45 * * * * ?"/> <!-- 15秒到45秒每隔1秒觸發任務 --> <task:scheduled ref="app" method="execute7" cron="15-45 * * * * ?"/> <!-- 每分鐘的每15秒時任務任務,每隔5秒觸發一次 --> <task:scheduled ref="app" method="execute8" cron="15/5 * * * * ?"/> <!-- 每分鐘的15到30秒之間開始觸發,每隔5秒觸發一次 --> <task:scheduled ref="app" method="execute9" cron="15-30/5 * * * * ?"/> <!-- 每小時的0分0秒開始觸發,每隔3分鐘觸發一次 --> <task:scheduled ref="app" method="execute10" cron="0 0/3 * * * ?"/> <!-- 星期一到星期五的10點15分0秒觸發任務 --> <task:scheduled ref="app" method="execute11" cron="0 15 10 ? * MON-FRI"/> </task:scheduled-tasks> </beans>
編寫測試代碼:
package com.rhwayfun.task; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author ZhongCB * @date 2016年09月10日 14:55 * @description */ public class AppTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/app-context-task.xml"); } }
運行測試代碼,控制檯會定時輸出每一個定時任務的日誌信息,說明測試經過。
1.cron表達式格式:
{秒} {分} {時} {日} {月} {周} {年(可選)}
2.cron各選項的取值範圍及解釋:
{秒}:取值範圍(0-59),不容許爲空值,若值不合法,調度器將拋出SchedulerException異常
{分}:取值範圍(0-59),不容許爲空值,若值不合法,調度器將拋出SchedulerException異常
{時}:取值範圍(0-23),不容許爲空值,若值不合法,調度器將拋出SchedulerException異常
"*" 表明每隔1分/秒/時觸發; "," 表明在指定的分/秒/時觸發,好比"10,20,40"表明10分/秒/時、20分/秒/時和40分/秒/時時觸發任務 "-" 表明在指定的範圍內觸發,好比"5-30"表明從5分/秒/時開始觸發到30分/秒/時結束觸 發,每隔1分/秒/時觸發 "/" 表明觸發步進(step),"/"前面的值表明初始值("*"等同"0"),後面的值表明偏移量,好比"0/25"或者"*/25"表明從0分/秒/時開始,每隔25分/秒/時觸發1次,即0分/秒/時觸發1次,第25分/秒/時觸發1次,第50分/秒/時觸發1次;"5/25"表明5分/秒/時觸發1次,30分/秒/時觸發1次,55分/秒/時觸發1次;"10-45/20"表明在[10,45]內步進20分/秒/時命中的時間點觸發,即10分/秒/時觸發1次,30分/秒/時觸發1次
{日}:取值範圍(1-31),不容許爲空值,若值不合法,調度器將拋出SchedulerException異常
"*" 表明天天觸發; "?" 與{周}互斥,即意味着若明確指定{周}觸發,則表示{日}無心義,以避免引發 衝突和混亂; "," 表明在指定的日期觸發,好比"1,10,20"表明1號、10號和20號這3天觸發; "-" 表明在指定的日期範圍內觸發,好比"10-15"表明從10號開始觸發到15號結束觸發,每隔1天觸發 "/" 表明觸發步進(step),"/"前面的值表明初始值("*"等同"1"),後面的值表明偏移量,好比"1/5"或者"*/5"表明從1號開始觸發,每隔5天觸發1次;"10/5"表明從10號開始觸發,之後每隔5天觸發一次;"1-10/2"表達式意味着在[1,10]範圍內,每隔2天觸發,即1號,3號,5號,7號,9號觸發 "L" 若是{日}佔位符若是是"L",即意味着當月的最後一天觸發 "W "意味着在本月內離當天最近的工做日觸發,所謂最近工做日,即當天到工做日的先後最短距離,若是當天即爲工做日,則距離爲0;所謂本月內的說法,就是不能跨月取到最近工做日,即便前/後月份的最後一天/第一天確實知足最近工做日;所以,"LW"則意味着本月的最後一個工做日觸發,"W"強烈依賴{月} "C" 根據日曆觸發,因爲使用較少,暫時不作解釋
{月}:取值範圍(1-12或JAN-DEC),不容許爲空值,若值不合法,調度器將拋出SchedulerException異常
{周}:取值範圍(1-7或SUN-SAT),1表示星期天,2表示星期一, 依次類推,不容許爲空值,若值不合法,調度器將拋出SchedulerException異常
"*" 表明每星期都觸發; "?" 與{日}互斥,即意味着若明確指定{日}觸發,則表示{周}無心義,以避免引發衝突和混亂 "," 表明在指定的星期約定觸發,好比"1,3,5"表明星期天、星期二和星期四觸發 "-" 表明在指定的星期範圍內觸發,好比"2-4"表明從星期一開始觸發到星期三結束觸發,每隔1天觸發 "/" 表明觸發步進(step),"/"前面的值表明初始值("*"等同"1"),後面的值表明偏移量,好比"1/3"或者"*/3"表明從星期天開始觸發,每隔3天觸發1次;"1-5/2"表達式意味着在[1,5]範圍內,每隔2天觸發,即星期天、星期2、星期四觸發 "L" 若是{周}佔位符若是是"L",即意味着星期的的最後一天觸發,即星期六觸發,L= 7或者 L = SAT,所以,"5L"意味着一個月的最後一個星期四觸發 "#" 用來指定具體的週數,"#"前面表明星期,"#"後面表明本月第幾周,好比"2#2"表示本月第二週的星期一,"5#3"表示本月第三週的星期四,所以,"5L"這種形式只不過是"#"的特殊形式而已 "C" 根據日曆觸發,因爲使用較少,暫時不作解釋
{年}:取值範圍(1970-2099),容許爲空值,(非空時)若值不合法,調度器將拋出SchedulerException異常
注:
①.秒、分、時、月、年這5項的佔位符以及佔位符的使用規則是一致的(只是單位上的區別),日、週中標紅的兩條須要重點比較!②.「?」和「L」字符僅被用於{日}和{周}兩個子表達式,表示不指定值 。對於「?」的應用,當2個子表達式其中之一被指定了值之後,爲了不衝突,須要將另外一個子表達式的值設爲「?」;在{日}表達式中,「L」表示一個月的最後一天,在{周}自表達式中,「L」表示一個星期的最後一天。若是在「L」前有具體的內容,它就具備其餘的含義了。例如:「6L」表示這個月的倒數第6天,「FRIL」表示這個月的最一個星期五 。
③.在使用「L」參數時,不要指定列表或範圍,由於這會致使問題
經常使用案例:
"0 0 10,14,16 * * ?" 天天上午10點,下午2點,4點 "0 0/30 9-17 * * ?" 朝九晚五工做時間內每半小時 "0 0 12 ? * WED" 表示每一個星期三中午12點 "0 0 12 * * ?" 天天中午12點觸發 "0 15 10 ? * *" 天天上午10:15觸發 "0 15 10 * * ?" 天天上午10:15觸發 "0 15 10 * * ? *" 天天上午10:15觸發 "0 15 10 * * ? 2005" 2005年的天天上午10:15觸發 "0 * 14 * * ?" 在天天下午2點到下午2:59期間的每1分鐘觸發 "0 0/5 14 * * ?" 在天天下午2點到下午2:55期間的每5分鐘觸發 "0 0/5 14,18 * * ?" 在天天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發 "0 0-5 14 * * ?" 在天天下午2點到下午2:05期間的每1分鐘觸發 "0 10,44 14 ? 3 WED" 每一年三月的星期三的下午2:10和2:44觸發 "0 15 10 ? * MON-FRI" 週一至週五的上午10:15觸發 "0 15 10 15 * ?" 每個月15日上午10:15觸發 "0 15 10 L * ?" 每個月最後一日的上午10:15觸發 "0 15 10 ? * 6L" 每個月的最後一個星期五上午10:15觸發 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每個月的最後一個星期五上午10:15觸發 "0 15 10 ? * 6#3" 每個月的第三個星期五上午10:15觸發 "30 * * * * ?" 每半分鐘觸發任務 "30 10 * * * ?" 每小時的10分30秒觸發任務 "30 10 1 * * ?" 天天1點10分30秒觸發任務 "30 10 1 20 * ?" 每個月20號1點10分30秒觸發任務 "30 10 1 20 10 ? *" 每一年10月20號1點10分30秒觸發任務 "30 10 1 20 10 ? 2011" 2011年10月20號1點10分30秒觸發任務 "30 10 1 ? 10 * 2011" 2011年10月天天1點10分30秒觸發任務 "30 10 1 ? 10 SUN 2011" 2011年10月每週日1點10分30秒觸發任務 "15,30,45 * * * * ?" 每15秒,30秒,45秒時觸發任務 "15-45 * * * * ?" 15到45秒內,每秒都觸發任務 "15/5 * * * * ?" 每分鐘的每15秒開始觸發,每隔5秒觸發一次 "15-30/5 * * * * ?" 每分鐘的15秒到30秒之間開始觸發,每隔5秒觸發一次 "0 0/3 * * * ?" 每小時的第0分0秒開始,每三分鐘觸發一次 "0 15 10 ? * MON-FRI" 星期一到星期五的10點15分0秒觸發任務 "0 15 10 L * ?" 每月最後一天的10點15分0秒觸發任務 "0 15 10 LW * ?" 每月最後一個工做日的10點15分0秒觸發任務 "0 15 10 ? * 5L" 每月最後一個星期四的10點15分0秒觸發任務 "0 15 10 ? * 5#3" 每月第三週的星期四的10點15分0秒觸發任務 "0 0 0 * * ?" 天天晚上12點觸發任務