Spring 定時任務

weixin_33751566發表於2017-03-16

本文參考自 Spring官方文件 34. Task Execution and Scheduling
在程式中常常有定時任務的需求,例如每隔一週生成一次報表、每個月月末清空使用者積分等等。Spring也提供了相應的支援,我們可以非常方便的按時執行任務。
專案準備
這裡我使用Gradle來建立專案,然後在 build.gradle
中新增下面一行。springVersion的值是目前最新的Spring版本 '4.3.7.RELEASE'
。使用Maven的話也新增相應的行。spring-context會自動引入spring-core等幾個最基本的依賴。
compile group: 'org.springframework', name: 'spring-context', version: springVersion
定時任務屬於Spring的核心支援部分,所以我們不需要再新增其他的依賴了。所以定時任務功能既可以在命令列程式中使用,也可以在Java Web程式中使用。當然後者可能使用的更廣泛一些(畢竟Web程式需要一直執行的嘛)。
這裡我們定義兩個任務,後面會讓它們可以定時執行。
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(); } }}
Spring的任務抽象
TaskExecutor
TaskExecutor
介面是任務執行介面,類似於 java.util.concurrent.Executor
,該介面只有一個方法 execute(Runnable task)
,用於執行任務。
Spring提供了一組TaskExecutor的實現,詳細列表可以看這裡 34.2.1. TaskExecutor types。要使用它們也很簡單,直接註冊為Spring Bean,然後注入到程式中即可使用。
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);}
Spring提供了兩個實現,一是 TimerManagerTaskScheduler
,會將任務代理到CommonJ TimerManager例項。第二個是 ThreadPoolTaskScheduler
,當我們不需要管理執行緒的時候就可以使用該類。而且它還同時實現了 TaskExecutor
介面,所以一個 ThreadPoolTaskScheduler
例項即可同時用於執行定時任務。
Trigger
在定時器介面的方法中我們可以發現一個方法接受Trigger介面, 而Trigger也是一個介面,抽象了觸發任務執行的觸發器。
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配置
定時任務
首先看看Java配置。我們需要在配置類上新增@EnableScheduling,如果需要非同步的定時任務,還需要新增@Async。
@Configuration@EnableAsync@EnableSchedulingpublic 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
型別。
@AsyncFuture<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
元素來配置。
XML配置
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整合
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
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>
MethodInvokingJobDetailFactoryBean
如果任務沒有實現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>

相關文章