Spring 定時任務(wù)

本文參考自Spring官方文檔 34. Task Execution and Scheduling

在程序中常常有定時任務(wù)的需求钧椰,例如每隔一周生成一次報表、每個月月末清空用戶積分等等符欠。Spring也提供了相應(yīng)的支持嫡霞,我們可以非常方便的按時執(zhí)行任務(wù)。

項目準備

這里我使用Gradle來建立項目希柿,然后在build.gradle中添加下面一行诊沪。springVersion的值是目前最新的Spring版本'4.3.7.RELEASE'养筒。使用Maven的話也添加相應(yīng)的行。spring-context會自動引入spring-core等幾個最基本的依賴端姚。

compile group: 'org.springframework', name: 'spring-context', version: springVersion

定時任務(wù)屬于Spring的核心支持部分晕粪,所以我們不需要再添加其他的依賴了。所以定時任務(wù)功能既可以在命令行程序中使用渐裸,也可以在Java Web程序中使用巫湘。當然后者可能使用的更廣泛一些(畢竟Web程序需要一直運行的嘛)。

這里我們定義兩個任務(wù)昏鹃,后面會讓它們可以定時執(zhí)行尚氛。

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的任務(wù)抽象

TaskExecutor

TaskExecutor接口是任務(wù)執(zhí)行接口,類似于java.util.concurrent.Executor洞渤,該接口只有一個方法execute(Runnable task)阅嘶,用于執(zhí)行任務(wù)。

Spring提供了一組TaskExecutor的實現(xiàn)您宪,詳細列表可以看這里34.2.1. TaskExecutor types奈懒。要使用它們也很簡單,直接注冊為Spring Bean宪巨,然后注入到程序中即可使用磷杏。

TaskScheduler

TaskScheduler接口是定時器的抽象,它的源代碼如下捏卓〖觯可以看到,該接口包含了一組方法用于指定任務(wù)執(zhí)行的時間怠晴。

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提供了兩個實現(xiàn)遥金,一是TimerManagerTaskScheduler,會將任務(wù)代理到CommonJ TimerManager實例蒜田。第二個是ThreadPoolTaskScheduler稿械,當我們不需要管理線程的時候就可以使用該類。而且它還同時實現(xiàn)了TaskExecutor接口冲粤,所以一個ThreadPoolTaskScheduler實例即可同時用于執(zhí)行定時任務(wù)美莫。

Trigger

在定時器接口的方法中我們可以發(fā)現(xiàn)一個方法接受Trigger接口, 而Trigger也是一個接口梯捕,抽象了觸發(fā)任務(wù)執(zhí)行的觸發(fā)器厢呵。

Trigger接口有兩個實現(xiàn),先說說比較簡單的一個PeriodicTrigger傀顾。它直接按照給定的時間間隔觸發(fā)任務(wù)執(zhí)行襟铭。更常用的一個觸發(fā)器是CronTrigger,它使用Cron表達式指定何時執(zhí)行任務(wù)。下面是Spring官方的一個例子寒砖。

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

關(guān)于Cron表達式的信息可以參考這篇博客QuartZ Cron表達式赐劣。另外還有一個可以在線生成Cron表達式的網(wǎng)站:CroMaker,不過好像需要XX才能訪問入撒。而且好像Spring不支持第二個星期一這樣的定時器設(shè)置隆豹,所以如果有這樣的需求,需要使用Quartz茅逮。

配置任務(wù)

任務(wù)配置既可以使用Java配置璃赡,也可以使用XML配置。不管使用哪種方法献雅,首先需要將要執(zhí)行的方法所在的類配置為Spring Bean碉考。例如下面就用XML配置注冊了兩個要執(zhí)行的任務(wù)。

    <bean id="simpleService" class="yitian.study.service.SimpleService"/>
    <bean id="expensiveTaskService"
          class="yitian.study.service.ExpensiveTaskService"/>

Java配置

定時任務(wù)

首先看看Java配置挺身。我們需要在配置類上添加@EnableScheduling侯谁,如果需要異步的定時任務(wù),還需要添加@Async章钾。

@Configuration
@EnableAsync
@EnableScheduling
public class TaskConfiguration {
}

然后在要執(zhí)行的方法上添加@Scheduled注解墙贱。@Scheduled注解有幾個參數(shù),任務(wù)會在相應(yīng)參數(shù)的時間下執(zhí)行贱傀。cron參數(shù)指定Cron表達式惨撇;fixedDelay指定任務(wù)執(zhí)行的間隔,單位是毫秒府寒;initialDelay指定當程序啟動后多長時間開始執(zhí)行第一次任務(wù)魁衙,單位是毫秒;zone指定任務(wù)執(zhí)行時間所在的時區(qū)株搔。下面的例子簡單的指定了每隔一秒重復(fù)執(zhí)行一次任務(wù)剖淀。

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);
    }
}

異步任務(wù)

然后是異步任務(wù),如果任務(wù)執(zhí)行時間比較長的話纤房,我們可以考慮使用異步的任務(wù)纵隔。當調(diào)用異步任務(wù)的時候,異步方法直接返回炮姨,異步任務(wù)會交由相應(yīng)的任務(wù)執(zhí)行器來執(zhí)行捌刮。在Spring中標記異步方法很簡單,直接在方法上使用@Async注解剑令。如果需要指定異步方法使用的執(zhí)行器糊啡,可以向注解傳遞執(zhí)行器的名稱拄查。異步方法可以返回空值吁津。

@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
}

異步方法不僅可以用于定時任務(wù)中,在Spring的其他地方也可以使用典尾。例如Spring Data JPA可以使用@Async編寫異步的查詢方法役拴。

需要注意,異步方法沒有對應(yīng)的XML配置钾埂,如果我們想讓方法是異步的河闰,只能使用注解。當然也不是完全不行褥紫,不過就比較麻煩了姜性,你需要使用AsyncExecutionInterceptor和AOP配合才能達到類似的效果。

如果需要處理異步方法的異常髓考,我們需要實現(xiàn)一個AsyncUncaughtExceptionHandler部念。下面的異步異常處理器簡單的打印異常信息。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        ex.printStackTrace();
    }
}

然后通過實現(xiàn)AsyncConfigurer接口(Java配置方式)或者task:annotation-driven(XML配置方式)的exception-handler元素來配置氨菇。

XML配置

Spring提供了task命名空間儡炼,讓配置定時任務(wù)非常簡單。

定時器

task:scheduler會注冊一個ThreadPoolTaskScheduler定時器查蓉,它只有一個屬性線程池大小乌询。默認是1,我們需要根據(jù)任務(wù)的數(shù)量指定一個合適的大小奶是。

    <task:scheduler id="threadPoolTaskScheduler"
                    pool-size="10"/>

執(zhí)行器

task:executor會注冊一個ThreadPoolTaskExecutor執(zhí)行器楣责,我們可以使用它的相關(guān)屬性來配置該執(zhí)行器。默認情況下執(zhí)行隊列是無限的聂沙,可能會導(dǎo)致JVM使用完所有內(nèi)存秆麸。因此我們最好指定一個確定的數(shù)值。還有一個rejection-policy屬性及汉,指定執(zhí)行器隊列滿時的執(zhí)行策略:默認是AbortPolicy沮趣,直接拋出異常;如果當系統(tǒng)忙時丟棄某些任務(wù)是可接受的坷随,可以使用DiscardPolicyDiscardOldestPolicy策略房铭;當系統(tǒng)負載較重時還可以使用CallerRunsPolicy,它不會將任務(wù)交給執(zhí)行器線程温眉,而是讓調(diào)用者線程來執(zhí)行該任務(wù)缸匪。最后一個就是keep-alive屬性,也就是超出線程池數(shù)量 線程完成任務(wù)之后的存活時間类溢,單位是秒凌蔬。

    <task:executor id="threadPoolTaskExecutor"
                   pool-size="10"
                   queue-capacity="10"/>

執(zhí)行任務(wù)

執(zhí)行任務(wù)很簡單露懒,使用<task:scheduled-tasks>指定要執(zhí)行的Bean和方法即可。

    <task:scheduled-tasks>
        <task:scheduled ref="simpleService" method="doService"
                        cron="*/1 * * * * *"/>
        <task:scheduled ref="expensiveTaskService" method="doService"
                        cron="*/2 * * * * *"/>
    </task:scheduled-tasks>

要設(shè)置定時的話砂心,只需要指定相應(yīng)的屬性即可懈词。

<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是一個定時任務(wù)的庫。Spring也提供了它的支持辩诞。Quartz的使用方法請查閱相應(yīng)文檔坎弯。這里只簡單介紹一下。

Spring的Quartz集成在spring-context-support包中译暂,它還需要Spring事務(wù)的支持抠忘。因此我們需要下面這樣的依賴聲明。

    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'

定義任務(wù)

Quartz的任務(wù)需要繼承Quartz的Job接口外永。所以一個典型的任務(wù)可以寫成這樣褐桌。

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用來定義實現(xiàn)了Job接口的任務(wù)。如果需要添加更多信息象迎,可以使用jobDataAsMap屬性設(shè)置荧嵌。

<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

如果任務(wù)沒有實現(xiàn)Job接口,也可以執(zhí)行砾淌,這時候需要使用MethodInvokingJobDetailFactoryBean啦撮。如果存在任務(wù)對象,使用targetObject屬性汪厨,如果有任務(wù)類赃春,使用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>

觸發(fā)器

有了任務(wù)劫乱,就可以定義觸發(fā)器了织中。觸發(fā)器有兩個:SimpleTriggerFactoryBean,以指定的間隔重復(fù)執(zhí)行任務(wù)衷戈;CronTriggerFactoryBean狭吼,以給定的Cron表達式執(zhí)行任務(wù)。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>

執(zhí)行任務(wù)

有了觸發(fā)器,我們就可以執(zhí)行任務(wù)了谦趣。注冊一個SchedulerFactoryBean疲吸,然后將觸發(fā)器的Bean引用傳入即可。

<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger"/>
            <ref bean="simpleTrigger"/>
        </list>
    </property>
</bean>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末前鹅,一起剝皮案震驚了整個濱河市摘悴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舰绘,老刑警劉巖蹂喻,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件延赌,死亡現(xiàn)場離奇詭異,居然都是意外死亡叉橱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門者蠕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窃祝,“玉大人,你說我怎么就攤上這事踱侣》嘈。” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵抡句,是天一觀的道長探膊。 經(jīng)常有香客問我,道長待榔,這世上最難降的妖魔是什么逞壁? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任锐锣,我火速辦了婚禮,結(jié)果婚禮上雕憔,老公的妹妹穿的比我還像新娘。我一直安慰自己斤彼,他們只是感情好分瘦,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布嘲玫。 她就那樣靜靜地躺著,像睡著了一般并扇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拜马,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音俩莽,去河邊找鬼旺坠。 笑死,一個胖子當著我的面吹牛扮超,可吹牛的內(nèi)容都是我干的取刃。 我是一名探鬼主播蹋肮,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼璧疗!你這毒婦竟也來了坯辩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤崩侠,失蹤者是張志新(化名)和其女友劉穎漆魔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體却音,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡改抡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了系瓢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阿纤。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夷陋,靈堂內(nèi)的尸體忽然破棺而出欠拾,到底是詐尸還是另有隱情,我是刑警寧澤骗绕,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布清蚀,位于F島的核電站,受9級特大地震影響爹谭,放射性物質(zhì)發(fā)生泄漏枷邪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一诺凡、第九天 我趴在偏房一處隱蔽的房頂上張望东揣。 院中可真熱鬧,春花似錦腹泌、人聲如沸嘶卧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芥吟。三九已至,卻和暖如春专甩,著一層夾襖步出監(jiān)牢的瞬間钟鸵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工涤躲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棺耍,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓种樱,卻偏偏與公主長得像蒙袍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子害幅,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容