[Spring]支持注解的Spring調度器


概述

如果想在Spring中使用任務調度功能,除了集成調度框架Quartz這種方式,也可以使用Spring自己的調度任務框架。

使用Spring的調度框架,優點是:支持注解(@Scheduler),可以省去大量的配置。

 

實時觸發調度任務

TaskScheduler接口

Spring3引入了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);
    
}

從以上方法可以看出TaskScheduler有兩個重要維度:

一個是要調度的方法,即一個實現了Runnable接口的線程類的run()方法;另一個就是觸發條件。

 

TaskScheduler接口的實現類

它有三個實現類:DefaultManagedTaskSchedulerThreadPoolTaskScheduler

TimerManagerTaskScheduler

DefaultManagedTaskScheduler:基於JNDI的調度器。

TimerManagerTaskScheduler:托管commonj.timers.TimerManager實例的調度器。

ThreadPoolTaskScheduler:提供線程池管理的調度器,它也實現了TaskExecutor接口,從而使的單一的實例可以盡可能快地異步執行。

 

Trigger接口

Trigger接口抽象了觸發條件的方法。

Trigger接口的聲明:

publicinterface Trigger {
    Date nextExecutionTime(TriggerContext triggerContext);
}

 

Trigger接口的實現類

CronTrigger:實現了cron規則的觸發器類(和Quartzcron規則相同)。

PeriodicTrigger:實現了一個周期性規則的觸發器類(例如:定義觸發起始時間、間隔時間等)。

 

完整范例

實現一個調度任務的功能有以下幾個關鍵點:

(1)     定義調度器

spring-bean.xml中進行配置

使用task:scheduler標簽定義一個大小為10的線程池調度器,spring會實例化一個ThreadPoolTaskScheduler

注:不要忘記引入xsd

http://www.springframework.org/schema/task

http://www.springframework.org/schema/task/spring-task-3.1.xsd

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task-3.1.xsd">

  <mvc:annotation-driven/>
  <task:scheduler id="myScheduler" pool-size="10"/>
</beans>

 

(2)定義調度任務

定義實現Runnable接口的線程類。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DemoTask implements Runnable {
    final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void run() {
        logger.info("call DemoTask.run");
    }
}

 

(3)裝配調度器,並執行調度任務

在一個Controller類中用@Autowired注解裝配TaskScheduler

然后調動TaskScheduler對象的schedule方法啟動調度器,就可以執行調度任務了。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller

@RequestMapping("/scheduler")
public class SchedulerController {
    
    @Autowired
    TaskScheduler scheduler;
    
    @RequestMapping(value = "/start", method = RequestMethod.POST)
    public void start() {
        scheduler.schedule(new DemoTask(), new CronTrigger("0/5 * * * * *"));
    }

}

訪問/scheduler/start接口,啟動調度器,可以看到如下日志內容:

13:53:15.010 [myScheduler-1] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run

13:53:20.003 [myScheduler-1] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run

13:53:25.004 [myScheduler-2] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run

13:53:30.005 [myScheduler-1] [INFO ] o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run

 

@Scheduler的使用方法

Spring的調度器一個很大的亮點在於@Scheduler注解,這可以省去很多繁瑣的配置。

啟動注解

使用@Scheduler注解先要使用<task:annotation-driven>

啟動注解開關。

例:

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd">

  <mvc:annotation-driven/>

  <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
  <task:executor id="myExecutor" pool-size="5"/>
  <task:scheduler id="myScheduler" pool-size="10"/>
</beans>

 

@Scheduler定義觸發條件

例:使用fixedDelay指定觸發條件為每5000毫秒執行一次。注意:必須在上一次調度成功后的5000秒才能執行。

@Scheduled(fixedDelay=5000)

publicvoid doSomething() {

    // something that should execute periodically

}

 

例:使用fixedRate指定觸發條件為每5000毫秒執行一次。注意:無論上一次調度是否成功,5000秒后必然執行。

@Scheduled(fixedRate=5000)
publicvoid doSomething() {
    // something that should execute periodically
}

 

例:使用initialDelay指定方法在初始化1000毫秒后才開始調度。

@Scheduled(initialDelay=1000, fixedRate=5000)
publicvoid doSomething() {
    // something that should execute periodically
}

 

例:使用cron表達式指定觸發條件為每5000毫秒執行一次。cron規則和Quartz中的cron規則一致。

@Scheduled(cron="*/5 * * * * MON-FRI")
publicvoid doSomething() {
    // something that should execute on weekdays only
}

 

完整范例

(1)啟動注解開關,並定義調度器和執行器

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd">

  <mvc:annotation-driven/>

  <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
  <task:executor id="myExecutor" pool-size="5"/>
  <task:scheduler id="myScheduler" pool-size="10"/>
</beans>

 

(2)使用@Scheduler注解來修飾一個要調度的方法

下面的例子展示了@Scheduler注解定義觸發條件的不同方式。

 

importorg.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @title ScheduledTasks
 * @description 使用@Scheduler注解調度任務范例
 * @author Vicotr Zhang
 * @date 2016年8月31日
 */
@Component
public class ScheduledMgr {
    private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    final Loggerlogger = LoggerFactory.getLogger(this.getClass());

    /**
     * 構造函數中打印初始化時間
     */
    public ScheduledMgr() {
        logger.info("Current time: {}", dateFormat.format(new Date()));
    }

    /**
     * fixedDelay屬性定義調度間隔時間。調度需要等待上一次調度執行完成。
     */
    @Scheduled(fixedDelay = 5000)
    public void testFixedDelay() throws Exception {
        Thread.sleep(6000);
        logger.info("Current time: {}", dateFormat.format(new Date()));
    }

    /**
     * fixedRate屬性定義調度間隔時間。調度不等待上一次調度執行完成。
     */
    @Scheduled(fixedRate = 5000)
    public void testFixedRate() throws Exception {
        Thread.sleep(6000);
        logger.info("Current time: {}", dateFormat.format(new Date()));
    }

    /**
     * initialDelay屬性定義初始化后的啟動延遲時間
     */
    @Scheduled(initialDelay = 1000, fixedRate = 5000)
    public void testInitialDelay() throws Exception {
        Thread.sleep(6000);
        logger.info("Current time: {}", dateFormat.format(new Date()));
    }

    /**
     * cron屬性支持使用cron表達式定義觸發條件
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void testCron() throws Exception {
        Thread.sleep(6000);
        logger.info("Current time: {}", dateFormat.format(new Date()));
    }
}

我刻意設置觸發方式的間隔都是5s,且方法中均有Thread.sleep(6000);語句。從而確保方法在下一次調度觸發時間點前無法完成執行,來看一看各種方式的表現吧。

啟動spring項目后,spring會掃描@Component注解,然后初始化ScheduledMgr

接着,spring會掃描@Scheduler注解,初始化調度器。調度器在觸發條件匹配的情況下開始工作,輸出日志。

截取部分打印日志來進行分析。

10:58:46.479 [localhost-startStop-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.<init> - Current time: 2016-08-31 10:58:46

10:58:52.523 [myScheduler-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:58:52

10:58:52.523 [myScheduler-3] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:58:52

10:58:53.524 [myScheduler-2] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:58:53

10:58:55.993 [myScheduler-4] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:58:55

10:58:58.507 [myScheduler-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:58:58

10:58:59.525 [myScheduler-5] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:58:59

10:59:03.536 [myScheduler-3] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:03

10:59:04.527 [myScheduler-1] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:59:04

10:59:05.527 [myScheduler-4] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:59:05

10:59:06.032 [myScheduler-2] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:59:06

10:59:10.534 [myScheduler-9] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedRate - Current time: 2016-08-31 10:59:10

10:59:11.527 [myScheduler-10] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testInitialDelay - Current time: 2016-08-31 10:59:11

10:59:14.524 [myScheduler-4] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:14

10:59:15.987 [myScheduler-6] [INFO ] o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:59:15

構造方法打印一次,時間點在10:58:46

testFixedRate打印四次,每次間隔6秒。說明,fixedRate不等待上一次調度執行完成,在間隔時間達到時立即執行

testFixedDelay打印三次,每次間隔大於6秒,且時間不固定。說明,fixedDelay等待上一次調度執行成功后,開始計算間隔時間,再執行

testInitialDelay第一次調度時間和構造方法調度時間相隔7秒。說明,initialDelay在初始化后等待指定的延遲時間才開始調度

testCron打印三次,時間間隔並非5秒或6秒,顯然,cron等待上一次調度執行成功后,開始計算間隔時間,再執行

此外,可以從日志中看出,打印日志的線程最多只有10個,說明2.1中的調度器線程池配置生效。

 

參考

Spring Framework官方文檔:

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM