本文參考自Spring官方文檔 34. Task Execution and Scheduling。html
在程序中經常有定時任務的需求,例如每隔一週生成一次報表、每月月末清空用戶積分等等。Spring也提供了相應的支持,咱們能夠很是方便的按時執行任務。java
這裏我使用Gradle來創建項目,而後在build.gradle
中添加下面一行。springVersion的值是目前最新的Spring版本'4.3.7.RELEASE'
。使用Maven的話也添加相應的行。spring-context會自動引入spring-core等幾個最基本的依賴。spring
compile group: 'org.springframework', name: 'spring-context', version: springVersion
定時任務屬於Spring的核心支持部分,因此咱們不須要再添加其餘的依賴了。因此定時任務功能既能夠在命令行程序中使用,也能夠在Java Web程序中使用。固然後者可能使用的更普遍一些(畢竟Web程序須要一直運行的嘛)。異步
這裏咱們定義兩個任務,後面會讓它們能夠定時執行。async
public interface IService { void doService(); } public class SimpleService implements IService { @Override public void doService() { LocalTime time = LocalTime.now(); System.out.println("This is a simple service:" + time); } } public class ExpensiveTaskService implements IService { @Override public void doService() { try { Thread.sleep(TimeUnit.SECONDS.toMillis(1)); LocalTime time = LocalTime.now(); System.out.println("This is an expensive task:" + time); } catch (InterruptedException e) { e.printStackTrace(); } } }
TaskExecutor
接口是任務執行接口,相似於java.util.concurrent.Executor
,該接口只有一個方法execute(Runnable task)
,用於執行任務。ide
Spring提供了一組TaskExecutor的實現,詳細列表能夠看這裏34.2.1. TaskExecutor types。要使用它們也很簡單,直接註冊爲Spring Bean,而後注入到程序中便可使用。gradle
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); }
Spring提供了兩個實現,一是TimerManagerTaskScheduler
,會將任務代理到CommonJ TimerManager實例。第二個是ThreadPoolTaskScheduler
,當咱們不須要管理線程的時候就可使用該類。並且它還同時實現了TaskExecutor
接口,因此一個ThreadPoolTaskScheduler
實例便可同時用於執行定時任務。ui
在定時器接口的方法中咱們能夠發現一個方法接受Trigger接口, 而Trigger也是一個接口,抽象了觸發任務執行的觸發器。this
Trigger接口有兩個實現,先說說比較簡單的一個PeriodicTrigger
。它直接按照給定的時間間隔觸發任務執行。更經常使用的一個觸發器是CronTrigger
,它使用Cron表達式指定什麼時候執行任務。下面是Spring官方的一個例子。
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
關於Cron表達式的信息能夠參考這篇博客QuartZ Cron表達式。另外還有一個能夠在線生成Cron表達式的網站:CroMaker,不過好像須要XX才能訪問。並且好像Spring不支持第二個星期一這樣的定時器設置,因此若是有這樣的需求,須要使用Quartz。
任務配置既可使用Java配置,也可使用XML配置。無論使用哪一種方法,首先須要將要執行的方法所在的類配置爲Spring Bean。例以下面就用XML配置註冊了兩個要執行的任務。
<bean id="simpleService" class="yitian.study.service.SimpleService"/> <bean id="expensiveTaskService" class="yitian.study.service.ExpensiveTaskService"/>
首先看看Java配置。咱們須要在配置類上添加@EnableScheduling,若是須要異步的定時任務,還須要添加@Async。
@Configuration @EnableAsync @EnableScheduling public class TaskConfiguration { }
而後在要執行的方法上添加@Scheduled註解。@Scheduled註解有幾個參數,任務會在相應參數的時間下執行。cron參數指定Cron表達式;fixedDelay指定任務執行的間隔,單位是毫秒;initialDelay指定當程序啓動後多長時間開始執行第一次任務,單位是毫秒;zone指定任務執行時間所在的時區。下面的例子簡單的指定了每隔一秒重複執行一次任務。
public class SimpleService implements IService { @Scheduled(fixedDelay = 1000) @Override public void doService() { LocalTime time = LocalTime.now(); System.out.println("This is a simple service:" + time); } }
而後是異步任務,若是任務執行時間比較長的話,咱們能夠考慮使用異步的任務。當調用異步任務的時候,異步方法直接返回,異步任務會交由相應的任務執行器來執行。在Spring中標記異步方法很簡單,直接在方法上使用@Async註解。若是須要指定異步方法使用的執行器,能夠向註解傳遞執行器的名稱。異步方法能夠返回空值。
@Async("otherExecutor") void doSomething(String s) { // this will be executed asynchronously by "otherExecutor" }
可是若是異步方法想返回其餘值的話,就必須使用Future。不過不只是java.util.concurrent.Future
,異步方法還能夠返回Spring的org.springframework.util.concurrent.ListenableFuture
和JDK8的java.util.concurrent.CompletableFuture
類型。
@Async Future<String> returnSomething(int i) { // this will be executed asynchronously }
異步方法不只能夠用於定時任務中,在Spring的其餘地方也可使用。例如Spring Data JPA可使用@Async編寫異步的查詢方法。
須要注意,異步方法沒有對應的XML配置,若是咱們想讓方法是異步的,只能使用註解。固然也不是徹底不行,不過就比較麻煩了,你須要使用AsyncExecutionInterceptor
和AOP配合才能達到相似的效果。
若是須要處理異步方法的異常,咱們須要實現一個AsyncUncaughtExceptionHandler
。下面的異步異常處理器簡單的打印異常信息。
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { ex.printStackTrace(); } }
而後經過實現AsyncConfigurer
接口(Java配置方式)或者task:annotation-driven
(XML配置方式)的exception-handler
元素來配置。
Spring提供了task命名空間,讓配置定時任務很是簡單。
task:scheduler
會註冊一個ThreadPoolTaskScheduler
定時器,它只有一個屬性線程池大小。默認是1,咱們須要根據任務的數量指定一個合適的大小。
<task:scheduler id="threadPoolTaskScheduler" pool-size="10"/>
task:executor
會註冊一個ThreadPoolTaskExecutor
執行器,咱們可使用它的相關屬性來配置該執行器。默認狀況下執行隊列是無限的,可能會致使JVM使用完全部內存。所以咱們最好指定一個肯定的數值。還有一個rejection-policy
屬性,指定執行器隊列滿時的執行策略:默認是AbortPolicy
,直接拋出異常;若是當系統忙時丟棄某些任務是可接受的,可使用DiscardPolicy
或DiscardOldestPolicy
策略;當系統負載較重時還可使用CallerRunsPolicy
,它不會將任務交給執行器線程,而是讓調用者線程來執行該任務。最後一個就是keep-alive
屬性,也就是超出線程池數量 線程完成任務以後的存活時間,單位是秒。
<task:executor id="threadPoolTaskExecutor" pool-size="10" queue-capacity="10"/>
執行任務很簡單,使用<task:scheduled-tasks>
指定要執行的Bean和方法便可。
<task:scheduled-tasks> <task:scheduled ref="simpleService" method="doService" cron="*/1 * * * * *"/> <task:scheduled ref="expensiveTaskService" method="doService" cron="*/2 * * * * *"/> </task:scheduled-tasks>
要設置定時的話,只須要指定相應的屬性便可。
<task:scheduled-tasks scheduler="myScheduler"> <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/> <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/> <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/> </task:scheduled-tasks> <task:scheduler id="myScheduler" pool-size="10"/>
Quartz是一個定時任務的庫。Spring也提供了它的支持。Quartz的使用方法請查閱相應文檔。這裏只簡單介紹一下。
Spring的Quartz集成在spring-context-support
包中,它還須要Spring事務的支持。所以咱們須要下面這樣的依賴聲明。
compile group: 'org.springframework', name: 'spring-tx', version: springVersion compile group: 'org.springframework', name: 'spring-context-support', version: springVersion compile group: 'org.quartz-scheduler', name: 'quartz', version: '2.2.3'
Quartz的任務須要繼承Quartz的Job接口。因此一個典型的任務能夠寫成這樣。
public class QuartzService implements IService, Job { @Override public void doService() { System.out.println("This is a quartz service"); } @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Do something in execute method of quartz"); } }
JobDetailFactoryBean用來定義實現了Job接口的任務。若是須要添加更多信息,可使用jobDataAsMap
屬性設置。
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="yitian.study.service.QuartzService"/> <property name="jobDataAsMap"> <map> <entry key="timeout" value="10"/> </map> </property> </bean>
若是任務沒有實現Job接口,也能夠執行,這時候須要使用MethodInvokingJobDetailFactoryBean。若是存在任務對象,使用targetObject
屬性,若是有任務類,使用targetClass
屬性。
<bean id="methodJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="quartzService"/> <property name="targetMethod" value="doService"/> <property name="concurrent" value="true"/> </bean>
有了任務,就能夠定義觸發器了。觸發器有兩個:SimpleTriggerFactoryBean
,以指定的間隔重複執行任務;CronTriggerFactoryBean
,以給定的Cron表達式執行任務。Quartz的Cron表達式比Spring 的強大,它支持第幾個星期幾這樣的Cron表達式。
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <property name="jobDetail" ref="jobDetail"/> <property name="startDelay" value="0"/> <property name="repeatInterval" value="1000"/> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="methodJobDetail"/> <property name="cronExpression" value="*/2 * * * * ?"/> </bean>
有了觸發器,咱們就能夠執行任務了。註冊一個SchedulerFactoryBean
,而後將觸發器的Bean引用傳入便可。
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger"/> <ref bean="simpleTrigger"/> </list> </property> </bean>