定時任務 Scheduled quartz

在項目應用中每每會用到任務定時器的功能,好比某某時間,或者多少多少秒而後執行某個騷操做等。
spring 支持多種定時任務的實現,其中不乏自身提供的定時器。
接下來介紹一下使用 spring 的定時器和使用 quartz 定時器。 css

前言

spring 自身提供了定時任務,爲何還要使用 quartz 呢?java

使用 spring 自帶的定時任務能夠很簡單很方便的完成一些簡單的定時任務,沒錯,這裏提到的是簡單,所以咱們想動態的執行咱們的定時任務是很是困難的。然而使用 quartz 卻能夠很容易的管理咱們的定時任務,很容易動態的操做定時任務。spring

一、 使用spring的定時器  

spring 自帶支持定時器的任務實現,其可經過簡單配置來使用到簡單的定時任務。數據庫

@Component
@Configurable
@EnableScheduling
public class ScheduledTasks{

    /**
     * 方式一
     * 每6秒執行一次
     **/

    @Scheduled(fixedRate = 6000)
    public void reportCurrentByCron(){
        System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (new Date ()));
    }

    /**
     * 方式二
     * 每6秒執行一次
     **/

    @Scheduled(cron = "*/6 * *  * * * ")
    public void reportCurrentByCron(){
        System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + dateFormat ().format (new Date ()));
    }

}
參數說明

@Scheduled 參數能夠接受兩種定時的設置,一種是咱們經常使用的 cron="/6 * * * ?",一種是 fixedRate = 6000,兩種均可表示固定週期執行定時任務。微信

fixedRate說明app

  • @Scheduled(fixedRate = 6000):上一次開始執行時間點以後 6 秒再執行。
  • @Scheduled(fixedDelay = 6000):上一次執行完畢時間點以後 6 秒再執行。
  • @Scheduled(initialDelay=1000, fixedRate=6000):第一次延遲 1 秒後執行,以後按 fixedRate 的規則每 6 秒執行一次。

cron說明框架

cron 必定有七位數,最後一位是年,SpringBoot 定時方案只須要設置六位便可:ide

  • 第一位, 表示秒, 取值是0 ~ 59
  • 第二位, 表示分. 取值是0 ~ 59
  • 第三位, 表示小時, 取值是0 ~ 23
  • 第四位, 表示天/日, 取值是0 ~ 31
  • 第五位, 表示月份, 取值是1 ~ 12
  • 第六位, 表示星期, 取值是1 ~ 7, 星期一,星期二…, 還有 1 表示星期日
  • 第七位, 年份, 能夠留空, 取值是1970 ~ 2099

cron中,還有一些特殊的符號,含義以下:spring-boot

  • (*) 星號,能夠理解爲每的意思,每秒、每分、天天、每個月、每一年…。
  • (?) 問號,問號只能出如今日期和星期這兩個位置,表示這個位置的值不肯定,天天 3 點執行,所以第六位星期的位置,是不須要關注的,就是不肯定的值;同時,日期和星期是兩個相互排斥的元素,經過問號來代表不指定值,好比 1 月 10 日是星期一,若是在星期的位置另指定星期二,就先後衝突矛盾了。
  • (-) 減號,表達一個範圍,如在小時字段中使用「10 - 12」,則表示從 10 到 12 點,即 十、十一、12。
  • (,) 逗號,表達一個列表值,如在星期字段中使用「1,2,4」,則表示星期1、星期2、星期四。
  • (/) 斜槓,如 x/y,x 是開始值,y 是步長,好比在第一位(秒),0/15 就是從 0 秒開始,每隔 15 秒執行一次,最後就是 0、1五、30、4五、60,另 */y,等同於 0/y。

舉幾個例子熟悉一下:工具

  • 0 0 3 * * ? :天天 3 點執行;
  • 0 5 3 * * ? :天天 3 點 5 分執行;
  • 0 5 3 ? * * :天天 3 點 5 分執行,與上面做用相同;
  • 0 5/10 3 * * ?:天天 3 點的 5 分、15 分、25 分、35 分、45 分、55分這幾個時間點執行;
  • 0 10 3 ? * 1:每週星期天,3 點 10 分執行,注,1 表示星期天;
  • 0 10 3 ? * 1#3:每月的第三個星期,星期天執行,# 號只能出如今星期的位置。

ok,spring的定時器就像如上這麼簡單,涉及到的幾個註解:

@EnableScheduling:標註啓動定時任務。
@Scheduled: 定義某個定時任務。

二、使用quartz實現定時任務

quartz 的設計者作了一個設計選擇來從調度分離開做業。
quartz 中的觸發器用來告訴調度程序做業何時觸發,框架提供了一把觸發器類型,但兩個最經常使用的是 SimpleTrigger 和 CronTrigger。

SimpleTrigger 爲須要簡單打火調度而設計。典型地,若是你須要在給定的時間和重複次數或者兩次打火之間等待的秒數打火一個做業,那麼SimpleTrigger適合你。

另外一方面,若是你有許多複雜的做業調度,那麼或許須要CronTrigger。

什麼是複雜調度?

當你須要在除星期六和星期天外的天天上午10點半執行做業時,那麼應該使用CronTrigger。正如它的名字所暗示的那樣,CronTrigger是基於Unix克隆表達式的。

開始以前須要瞭解的幾個概念:

  • Job:是一個接口,只定義一個方法 execute(JobExecutionContext context),在實現接口的 execute 方法中編寫所須要定時執行的 Job(任務),JobExecutionContext 類提供了調度應用的一些信息;Job 運行時的信息保存在 JobDataMap 實例中。
  • JobDetail:Quartz 每次調度 Job 時,都從新建立一個 Job 實例,所以它不接受一個 Job 的實例,相反它接收一個 Job 實現類(JobDetail,描述 Job 的實現類及其餘相關的靜態信息,如 Job 名字、描述、關聯監聽器等信息),以便運行時經過 newInstance() 的反射機制實例化 Job。
  • Trigger:是一個類,描述觸發 Job 執行的時間觸發規則,主要有 SimpleTrigger 和 CronTrigger 這兩個子類,上邊剛剛有提到。當且僅當需調度一次或者以固定時間間隔週期執行調度,SimpleTrigger 是最適合的選擇;而 CronTrigger 則能夠經過 Cron 表達式定義出各類複雜時間規則的調度方案:如工做日週一到週五的 15:00 ~ 16:00 執行調度等。
  • Scheduler:調度器就至關於一個容器,裝載着任務和觸發器,該類是一個接口,表明一個 Quartz 的獨立運行容器,Trigger 和 JobDetail 能夠註冊到 Scheduler 中,二者在 Scheduler 中擁有各自的組及名稱,組及名稱是 Scheduler 查找定位容器中某一對象的依據,Trigger 的組及名稱必須惟一,JobDetail 的組和名稱也必須惟一(但能夠和 Trigger 的組和名稱相同,由於它們是不一樣類型的)。Scheduler 定義了多個接口方法,容許外部經過組及名稱訪問和控制容器中 Trigger 和 JobDetail。

上邊的四個概念,建議通讀一遍,結合下方代碼,思路更清晰。

SpringBoot2.x 以後,完成了對 Quartz 自動化配置集成,省去了不少繁瑣的配置,下面進入正題吧。

2.一、引入依賴
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2.二、yml配置

spring:
    quartz:
        # 任務信息存儲至?MEMORY(內存方式:默認)、JDBC(數據庫方式)
        job-store-type: jdbc
        properties:
            org:
                quartz:
                    jobStore:
                        misfireThreshold: 100

2.三、用於增刪改查定時任務的Controller

/**
 * @author niceyoo
 */

@Slf4j
@RestController
@Api(description = "定時任務管理接口")
@RequestMapping("/tmax/quartzJob")
public class TmaxQuartzJobController {

    /**
     * 定時任務service
     */

    @Autowired
    private QuartzJobService quartzJobService;

    /**
     * 調度器
     */

    @Autowired
    private Scheduler scheduler;

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @ApiOperation(value = "添加定時任務")
    public Result<Object> addJob(@ModelAttribute QuartzJob job){

        add(job.getJobClassName(),job.getCronExpression(),job.getParameter());
        quartzJobService.save(job);
        return new ResultUtil<Object>().setSuccessMsg("建立定時任務成功");
    }

    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    @ApiOperation(value = "更新定時任務")
    public Result<Object> editJob(@ModelAttribute QuartzJob job){

        delete(job.getJobClassName());
        add(job.getJobClassName(),job.getCronExpression(),job.getParameter());
        job.setStatus(CommonConstant.STATUS_NORMAL);
        quartzJobService.update(job);
        return new ResultUtil<Object>().setSuccessMsg("更新定時任務成功");
    }

    @RequestMapping(value = "/pause", method = RequestMethod.POST)
    @ApiOperation(value = "暫停定時任務")
    public Result<Object> pauseJob(@ModelAttribute QuartzJob job){

        try {
            scheduler.pauseJob(JobKey.jobKey(job.getJobClassName()));
        } catch (SchedulerException e) {
            throw new TmaxException("暫停定時任務失敗");
        }
        job.setStatus(CommonConstant.STATUS_DISABLE);
        quartzJobService.update(job);
        return new ResultUtil<Object>().setSuccessMsg("暫停定時任務成功");
    }

    @RequestMapping(value = "/resume", method = RequestMethod.POST)
    @ApiOperation(value = "恢復定時任務")
    public Result<Object> resumeJob(@ModelAttribute QuartzJob job){

        try {
            scheduler.resumeJob(JobKey.jobKey(job.getJobClassName()));
        } catch (SchedulerException e) {
            throw new TmaxException("恢復定時任務失敗");
        }
        job.setStatus(CommonConstant.STATUS_NORMAL);
        quartzJobService.update(job);
        return new ResultUtil<Object>().setSuccessMsg("恢復定時任務成功");
    }

    @RequestMapping(value = "/delByIds/{ids}", method = RequestMethod.DELETE)
    @ApiOperation(value = "刪除定時任務")
    public Result<Object> deleteJob(@PathVariable String[] ids){

        for(String id:ids){
            QuartzJob job = quartzJobService.get(id);
            delete(job.getJobClassName());
            quartzJobService.delete(job);
        }
        return new ResultUtil<Object>().setSuccessMsg("刪除定時任務成功");
    }

    /**
     * 添加定時任務
     * @param jobClassName
     * @param cronExpression
     * @param parameter
     */

    public void add(String jobClassName, String cronExpression, String parameter){

        try {
            ##啓動調度器
            scheduler.start();

            ##構建job信息
            JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
                    .withIdentity(jobClassName)
                    .usingJobData("parameter", parameter)
                    .build();

            ##表達式調度構建器(即任務執行的時間) 使用withMisfireHandlingInstructionDoNothing() 忽略掉調度暫停過程當中沒有執行的調度
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();

            ##按新的cronExpression表達式構建一個新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName)
                    .withSchedule(scheduleBuilder).build();

            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            log.error(e.toString());
            throw new TmaxException("建立定時任務失敗");
        } catch (Exception e){
            throw new TmaxException("後臺找不到該類名任務");
        }
    }

    /**
     * 刪除定時任務
     * @param jobClassName
     */

    public void delete(String jobClassName){

        try {
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName));
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName));
            scheduler.deleteJob(JobKey.jobKey(jobClassName));
        } catch (Exception e) {
            throw new TmaxException("刪除定時任務失敗");
        }
    }

    public static Job getClass(String classname) throws Exception {
        Class<?> class1 = Class.forName(classname);
        return (Job)class1.newInstance();
    }

}
2.四、Job執行的任務類
@Slf4j
public class SampleJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        log.info(String.format("打印時間:"+ DateUtil.now()));
    }
}

4小步代碼看完後,咱們再來分析一波 quartz 的4個核心概念,先腦補一張圖。

主要看 TmaxQuartzJobController 的 add() 方法

  • Job 爲做業的接口,爲任務調度的對象,在代碼中體現爲 ,代碼中是由用戶傳遞過來的類絕對路徑;
  • JobDetail 用來描述 Job 的實現類及其餘相關的靜態信息,能夠看到代碼中經過 JobBuilder 的靜態方法 newJob(Class jobClass) 生成 JobBuilder 實例,其中 jobClass 的獲取採用反射機制;
  • Trigger 作爲做業的定時管理工具,一個 Trigger 只能對應一個做業實例,而一個做業實例可對應多個觸發器,代碼中採用的是 CronTrigger,經過表達式調度構建器構建任務的執行時間,注意,任務的執行時間是由前臺傳遞過來的 cron 表達式,而後按新的 cronExpression 表達式構建一個新的 trigger,TriggerBuilder.newTrigger().withIdentity(jobClassName) 表達了一個 Trigger 只能對應一個做業實例;
  • Scheduler 作爲定時任務容器,是 Quartz 最上層的東西,它提攜了全部觸發器和做業,使它們協調工做,每一個 Scheduler 都存有 JobDetail 和 Trigger 的註冊,一個 Scheduler 中能夠註冊多個 JobDetail 和多個 Trigger。

最後,調用 Controller 層的任務添加方法 /add 完成所有,效果以下:

2019-05-23 00:38:38.455  INFO 19856 --- [eduler_Worker-1] club.sscai.tmax.quartz.jobs.SampleJob   : 打印時間:2019-05-24 09:38:38
2019-05-23 00:38:39.035  INFO 19856 --- [eduler_Worker-2] club.sscai.tmax.quartz.jobs.SampleJob   : 打印時間:2019-05-24 09:38:39
2019-05-23 00:38:40.752  INFO 19856 --- [eduler_Worker-3] club.sscai.tmax.quartz.jobs.SampleJob   : 打印時間:2019-05-24 09:38:40
2019-05-23 00:38:41.033  INFO 19856 --- [eduler_Worker-4] club.sscai.tmax.quartz.jobs.SampleJob   : 打印時間:2019-05-24 09:38:41
2019-05-23 00:38:42.640  INFO 19856 --- [eduler_Worker-5] club.sscai.tmax.quartz.jobs.SampleJob   : 打印時間:2019-05-24 09:38:42
2019-05-23 00:38:43.023  INFO 19856 --- [eduler_Worker-6] club.sscai.tmax.quartz.jobs.SampleJob   : 打印時間:2019-05-24 09:38:43

習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:niceyoo

相關文章
相關標籤/搜索