深入淺出Spring task定時任務

在工作中有用到spring task作為定時任務的處理,spring通過介面TaskExecutorTaskScheduler這兩個介面的方式為非同步定時任務提供了一種抽象。這就意味著spring容許你使用其他的定時任務框架,當然spring自身也提供了一種定時任務的實現:spring task。spring task支持線程池,可以高效處理許多不同的定時任務。同時,spring還支持使用Java自帶的Timer定時器和Quartz定時框架。限於篇幅,這裡將只介紹spring task的使用。

其實,官方文檔已經介紹地足夠詳細,只不過都是英文版,所以為了更好地理解並使用spring task,首先會對spring task的實現原理做一個簡單的介紹,然後通過實際代碼演示spring task是如何使用的。這裡會涉及到一個很重要的知識點:cron表達式。

TaskExecutor和TaskScheduler

TaskExecutor是spring task的第一個抽象,它很自然讓人聯想到jdk中concurrent包下的Executor,實際上TaskExecutor就是為區別於Executor才引入的,而引入TaskExecutor的目的就是為定時任務的執行提供線程池的支持,那麼,問題來了,為什麼spring不直接使用jdk自帶的Executor呢?TaskExecutor源碼如下?

public interface TaskExecutor extends Executor { void execute(Runnable var1);}

  • 1
  • 2
  • 3
  • 那麼,答案很顯然,TaskExecutor提供的線程池支持也是基於jdk自帶的Executor的。用法於Executor沒有什麼不同。

    TaskScheduler是spring task的第二個抽象,那麼從字面的意義看,TaskScheduler就是為了提供定時任務的支持咯。TaskScheduler需要傳入一個Runnable的任務做為參數,並指定需要周期執行的時間或者觸發器,這樣Runnable任務就可以周期性執行了。傳入時間很好理解,有意思的是傳入一個觸發器(Trigger)的情況,因為這裡需要使用cron表達式去觸發一個定時任務,所以有必要先了解下cron表達式的使用。

    在spring 4.x中已經不支持7個參數的cronin表達式了,要求必須是6個參數(具體哪個參數後面會說)。cron表達式的格式如下:

    {秒} {分} {時} {日期(具體哪天)} {月} {星期}

  • 1
  • 秒:必填項,允許的值範圍是0-59,支持的特殊符號包括 , - * /,表示特定的某一秒才會觸發任務,-表示一段時間內會觸發任務,*表示每一秒都會觸發,/表示從哪一個時刻開始,每隔多長時間觸發一次任務。
  • 分:必填項,允許的值範圍是0-59,支持的特殊符號和秒一樣,含義類推
  • 時:必填項,允許的值範圍是0-23,支持的特殊符號和秒一樣,含義類推
  • 日期:必填項,允許的值範圍是1-31,支持的特殊符號相比秒多了?,表示與{星期}互斥,即意味著若明確指定{星期}觸發,則表示{日期}無意義,以免引起衝突和混亂。
  • 月:必填項,允許的值範圍是1-12(JAN-DEC),支持的特殊符號與秒一樣,含義類推
  • 星期:必填項,允許值範圍是1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此類推,7代表星期六,支持的符號相比秒多了?,表達的含義是與{日期}互斥,即意味著若明確指定{日期}觸發,則表示{星期}無意義。
  • 比如下面這個cron表達式:

    // 表達的含義是:每半分鐘觸發一次任務30 * * * * ?

  • 1
  • 2
  • spring提供了一個CronTrigger,通過傳入一個Runnable任務和CronTrigger,就可以使用cron表達式去指定定時任務了,是不是非常方面。實際上,在工程實踐上,cron表達式也是使用很多的。實際上,是執行了下面的代碼:

    scheduler.schedule(task, new CronTrigger("30 * * * * ?"));

  • 1
  • TaskScheduler抽象的好處是讓需要執行定時任務的代碼不需要指定特定的定時框架(比如Timer和Quartz)。TaskScheduler的更簡單的實現是ThreadPoolTaskScheduler,它實際上代理一個jdk中的SchedulingTaskExecutor,並且也實現了TaskExecutor介面,所以需要經常執行定時任務的場景可以使用這個實現(Spring推薦)。我們再來看一下TaskExecutor和TaskScheduler的類繼承關係:

    通常而言,使用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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 開發需要執行定時任務的方法:

    package com.rhwayfun.task;import org.springframework.stereotype.Component;import java.time.LocalDateTime;/** * @author ZhongCB * @date 2016年09月10日 14:30 * @description */@Componentpublic class App { public void execute1(){ System.out.printf("Task: %s, Current time: %s
    ", 1, LocalDateTime.now()); } public void execute2(){ System.out.printf("Task: %s, Current time: %s
    ", 2, LocalDateTime.now()); } public void execute3(){ System.out.printf("Task: %s, Current time: %s
    ", 3, LocalDateTime.now()); } public void execute4(){ System.out.printf("Task: %s, Current time: %s
    ", 4, LocalDateTime.now()); } public void execute5(){ System.out.printf("Task: %s, Current time: %s
    ", 5, LocalDateTime.now()); } public void execute6(){ System.out.printf("Task: %s, Current time: %s
    ", 6, LocalDateTime.now()); } public void execute7(){ System.out.printf("Task: %s, Current time: %s
    ", 7, LocalDateTime.now()); } public void execute8(){ System.out.printf("Task: %s, Current time: %s
    ", 8, LocalDateTime.now()); } public void execute9(){ System.out.printf("Task: %s, Current time: %s
    ", 9, LocalDateTime.now()); } public void execute10(){ System.out.printf("Task: %s, Current time: %s
    ", 10, LocalDateTime.now()); } public void execute11(){ System.out.printf("Task: %s, Current time: %s
    ", 11, LocalDateTime.now()); }}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 編寫測試代碼:

    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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 運行測試代碼,控制台會定時輸出每個定時任務的日誌信息,說明測試通過。

    小插曲

    由於項目使用jdk 1.8進行開發,所以初始的時候每次pom文件發生修改,編譯器的版本又變成了jdk 1.5,後面發現需要在pom文件中添加build便簽那部分才能將默認的編譯器進行修改。也算一個小收穫了。


    推薦閱讀:

    南海仲裁案發布當日 台艦將停太平島執行任務
    《空降任務》
    #攝論#【你不可不挑戰的13個攝影任務】
    一線經理在員工培訓中的三大任務
    亨利·基辛格:武器裝備一直在變,但政治家的任務不會變

    TAG:Spring | 任務 | 定時 |