定時任務應該這麼玩

說故事的五公子發表於2021-01-21

1.場景

在電商系統中會經常遇到這樣一種場景,就是商品的定時上下架功能,總不能每次都手動執行吧,這個時候我們首先想到的就是利用定時任務來實現這個功能。

目前實現定時任務主要有以下幾種方式:

  • JDK自帶 :JDK自帶的Timer以及JDK1.5+ 新增的ScheduledExecutorService;

  • 第三方框架 :使用 Quartz、elastic-job、xxl-job 等開源第三方定時任務框架,適合分散式專案應用。該方式的缺點是配置複雜。

  • Spring :使用 Spring 提供的一個註解 @Schedule,開發簡單,使用比較方便。

本文博主主要向大家介紹Quartz框架和Spring定時任務的使用。

2.什麼是Quartz

Quartz 是一個完全由 Java 編寫的開源作業排程框架,為在 Java 應用程式中進行作業排程提供了簡單卻強大的機制。

Quartz 可以與 J2EE 與 J2SE 應用程式相結合也可以單獨使用。

Quartz 允許程式開發人員根據時間的間隔來排程作業。

Quartz 實現了作業和觸發器的多對多的關係,還能把多個作業與不同的觸發器關聯。

3.Quartz幾個核心概念

在正式學習使用Quartz之前,我們需要了解幾個有關Quartz的核心概念,方便我們後面學習

  1. Job 表示一個工作,要執行的具體內容。此介面中只有一個方法,如下:

    void execute(JobExecutionContext context) 
    // context是重要的上下文,可以訪問到關聯的JobDetail物件和本次觸發的Trigger物件,以及在此之上設定的資料。
    
  2. JobDetail 表示一個具體的可執行的排程程式,Job 是這個可執行程排程程式所要執行的內容,另外 JobDetail 還包含了這個任務排程的方案和策略。

  3. Trigger 代表一個排程引數的配置,什麼時候去調。

  4. Scheduler 代表一個排程容器,一個排程容器中可以註冊多個 JobDetail 和 Trigger。當 Trigger 與 JobDetail 組合,就可以被 Scheduler 容器排程了。

4.Quartz初體驗

一、建立一個SpringBoot專案,pom.xml配置如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.songguoliang</groupId>
    <artifactId>spring-boot-quartz</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>spring-boot-quartz</name>
    <description>Spring Boot使用Quartz定時任務</description>

    <!-- Spring Boot啟動器父類 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- Spring Boot web啟動器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- quartz -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

二、建立一個Job(Job裡面是要執行的具體內容)

package com.example.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;

import java.time.LocalDateTime;

public class TestJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 通過context獲取trigger中的資料
        Object tv1 = context.getTrigger().getJobDataMap().get("t1");
        Object tv2 = context.getTrigger().getJobDataMap().get("t2");
        // 通過context獲取JobDetail中的資料
        Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
        Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
        Object sv = null;
        try {
            sv = context.getScheduler().getContext().get("skey");
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        System.out.println(tv1+":"+tv2);
        System.out.println(jv1+":"+jv2);
        System.out.println(sv);
        System.out.println("date:"+ LocalDateTime.now());
    }
}

三、執行Job

package com.example.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzTest {
    public static void main(String[] args)  {
        try {
            //建立一個scheduler
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            //向scheduler中put值
            scheduler.getContext().put("skey", "svalue");

            //建立一個Trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    //給該Trigger起一個id
                    .withIdentity("trigger1")
                    //以Key-Value形式關聯資料
                    .usingJobData("t1", "tv1")
                    //每3秒觸發一次,無限迴圈
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever()).build();
            trigger.getJobDataMap().put("t2","tv2");

            //建立一個JobDetail
            JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
                    //給該JobDetail起一個id
                    .withIdentity("myJob", "myGroup")
                    .usingJobData("j1", "jv1")
                    .build();
            jobDetail.getJobDataMap().put("j2", "jv2");

            //註冊trigger並啟動scheduler
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
            //如果想要停止這個Job,可以呼叫shutdown方法
            //scheduler.shutdown();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

控制檯輸出

10:46:54.075 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
10:46:54.079 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
10:46:54.089 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
10:46:54.089 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
10:46:54.090 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
10:46:54.091 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

10:46:54.091 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
10:46:54.091 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
10:46:54.104 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
10:46:54.104 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:46:54.106 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:46:54.110 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:46:54.110 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:46:54.144
10:46:57.092 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:46:57.092 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:46:57.092 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:46:57.092
10:47:00.101 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:47:00.101 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:47:00.101 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:47:00.101
10:47:03.096 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.example.quartz.TestJob
10:47:03.096 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
10:47:03.096 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob
tv1:tv2
jv1:jv2
svalue
date:2020-12-19T10:47:03.096

從輸出結果我們可以看到此Job每隔3秒執行一次

有關概念

1、Job

job的一個 trigger 被觸發後(稍後會講到),execute() 方法會被 scheduler 的一個工作執行緒呼叫;傳遞給 execute() 方法的 JobExecutionContext 物件中儲存著該 job 執行時的一些資訊 ,執行 job 的 scheduler 的引用,觸發 job 的 trigger 的引用,JobDetail 物件引用,以及一些其它資訊。

2、JobDetail

JobDetail 物件是在將 job 加入 scheduler 時,由客戶端程式(你的程式)建立的。它包含 job 的各種屬性設定,以及用於儲存 job 例項狀態資訊的 JobDataMap

3、Trigger

Trigger 用於觸發 Job 的執行。當你準備排程一個 job 時,你建立一個 Trigger 的例項,然後設定排程相關的屬性。Trigger 也有一個相關聯的 JobDataMap,用於給 Job 傳遞一些觸發相關的引數。Quartz 自帶了各種不同型別的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。SimpleTrigger 主要用於一次性執行的 Job(只在某個特定的時間點執行一次),或者 Job 在特定的時間點執行,重複執行 N 次,每次執行間隔T個時間單位。CronTrigger 在基於日曆的排程上非常有用,如“每個星期五的正午”,或者“每月的第十天的上午 10:15”等。

5.JobDetail詳解

在定義一個Job時,我們需要實現Job介面,該介面只有一個execute方法。

從上一節的案例中我們可以發現,我們通過Scheduler去執行Job,我們傳給scheduler一個JobDetail例項,因為我們在建立JobDetail時,將要執行的job的類名傳給了JobDetail,所以scheduler就知道了要執行何種型別的job。(這裡利用了Java中的反射建立例項物件)每次當scheduler執行job時,在呼叫其execute(…)方法之前會建立該類的一個新的例項;執行完畢,對該例項的引用就被丟棄了,例項會被垃圾回收;這種執行策略帶來的一個後果是,job必須有一個無參的建構函式(當使用預設的JobFactory時);另一個後果是,在job類中,不應該定義有狀態的資料屬性,因為在job的多次執行中,這些屬性的值不會保留。

那麼我們該如何給Job配置相關屬性呢?答案就是通過JobDetail

JobDataMap

JobDataMap實現了Map介面,可以存放鍵值對資料,在Job執行的時候,我們就可以通過JobExecutionContext獲取到JobDataMap中的資料,如下

JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
                    .withIdentity("myJob", "myGroup")
                    .usingJobData("j1", "jv1")
                    .usingJobData("j2","jv2")
                    .build();

在job的執行過程中,可以從JobDataMap中取出資料,如下示例:

Object jv1 = context.getJobDetail().getJobDataMap().get("j1");

當然,如果你希望實現屬性的自動注入,那麼你可以使用下面的方法

package com.example.quartz;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzTest2 {
    public static void main(String[] args)  {
        try {

            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1")
                    .usingJobData("t1", "tv1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever())
                    .build();

            JobDetail jobDetail = JobBuilder.newJob(TestJob2.class)
                    .withIdentity("jd")
                    .usingJobData("name", "張三")
                    .usingJobData("age", 12)
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
package com.example.quartz;

import org.quartz.*;

public class TestJob2 implements Job {

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobKey jobKey = context.getJobDetail().getKey();

        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();

        System.out.println("name:" + name + "age:" +age);
    }
}

給Job類加上get和set方法(屬性名稱要和JobDataMap中的key相同),那麼JobDataMap中的值就是自動注入到Job中,不需要手動獲取

6.Triggers詳解

Trigger 用於觸發 Job 的執行。當你準備排程一個 job 時,你建立一個 Trigger 的例項,然後設定排程相關的屬性。所有型別的trigger都有TriggerKey這個屬性,表示trigger的身份;除此之外,trigger還有很多其它的公共屬性。這些屬性,在構建trigger的時候可以通過TriggerBuilder設定。

triggers公共屬性

  • jobKey屬性:當trigger觸發時被執行的job的身份;
  • startTime屬性:設定trigger第一次觸發的時間;該屬性的值是java.util.Date型別,表示某個指定的時間點;有些型別的trigger,會在設定的startTime時立即觸發,有些型別的trigger,表示其觸發是在startTime之後開始生效。比如,現在是1月份,你設定了一個trigger–“在每個月的第5天執行”,然後你將startTime屬性設定為4月1號,則該trigger第一次觸發會是在幾個月以後了(即4月5號)。
  • endTime屬性:表示trigger失效的時間點。比如,”每月第5天執行”的trigger,如果其endTime是7月1號,則其最後一次執行時間是6月5號。

優先順序(priority)

如果你的trigger很多(或者Quartz執行緒池的工作執行緒太少),Quartz可能沒有足夠的資源同時觸發所有的trigger;這種情況下,你可能希望控制哪些trigger優先使用Quartz的工作執行緒,要達到該目的,可以在trigger上設定priority屬性。比如,你有N個trigger需要同時觸發,但只有Z個工作執行緒,優先順序最高的Z個trigger會被首先觸發。如果沒有為trigger設定優先順序,trigger使用預設優先順序,值為5;priority屬性的值可以是任意整數,正數、負數都可以。

注意:只有同時觸發的trigger之間才會比較優先順序。10:59觸發的trigger總是在11:00觸發的trigger之前執行。

注意:如果trigger是可恢復的,在恢復後再排程時,優先順序與原trigger是一樣的。

錯過觸發(misfire Instructions)

trigger還有一個重要的屬性misfire;如果scheduler關閉了,或者Quartz執行緒池中沒有可用的執行緒來執行job,此時永續性的trigger就會錯過(miss)其觸發時間,即錯過觸發(misfire)。不同型別的trigger,有不同的misfire機制。它們預設都使用“智慧機制(smart policy)”,即根據trigger的型別和配置動態調整行為

Simple Trigger

SimpleTrigger簡單點說,就是在具體的時間點執行一次,或者在具體的時間點執行,並且以指定的間隔重複執行若干次。類似於鬧鐘,你定了一個週末早晨7點的鬧鐘,這個鬧鐘會在週末早上7點準時響起。鬧鐘還有個功能就是過5分鐘之後再響一次,這對應著指定的間隔重複執行若干次。

1、指定時間開始觸發,不重複:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                    .withIdentity("st1", "group1")
                    .startAt(new Date()) // 從當前時間開始執行一次,不重複
                    .build();

2、指定時間觸發,每隔2秒執行一次,重複5次:

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st2", "group1")
        .startAt(new Date())
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(2) // 2秒
                .withRepeatCount(5)// 5次
        )
        .build();

3、1分鐘以後開始觸發,僅執行一次:

long time = 1 * 60 * 1000;
Date now = new Date();
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st3", "group1")
        .startAt(new Date(now.getTime() + time))
        .build();

4、立即觸發,每隔2秒鐘執行一次,直到2020-12-19 13:20:00

String dateStr="2020-12-19 13:20:00";
String pattern="yyyy-MM-dd HH:mm:ss";
SimpleDateFormat dateFormat=new SimpleDateFormat(pattern);
Date date = dateFormat.parse(dateStr);
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st4", "group1")
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInSeconds(2)
        .repeatForever())
        .endAt(date)
        .build();

5、在13:00觸發,然後每2小時重複一次:

String dateStr="2020-12-19 13:00:00";
String pattern="yyyy-MM-dd HH:mm:ss";
SimpleDateFormat dateFormat=new SimpleDateFormat(pattern);
Date date = dateFormat.parse(dateStr);
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st2", "group1")
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInHours(2)
        .repeatForever())
        .build();

SimpleTrigger Misfire

misfire:被錯過的執行任務策略

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withIdentity("st6")
        .withSchedule(
                SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMinutes(5)
                        .repeatForever()
                        .withMisfireHandlingInstructionNextWithExistingCount()
        )
        .build();

CronTrigger

CronTrigger通常比Simple Trigger更有用,如果你需要在指定日期執行某項任務,使用CronTrigger就非常方便,比如如果你想在每月的15號給會員發放優惠券,或者每週五中午12點統計使用者本週使用產品時長。

cron 表示式是一個字串,該字串由 6 個空格分為 7 個域,每一個域代表一個時間含義。 通常定義 “年” 的部分可以省略,實際常用的 Cron 表示式由前 6 部分組成。格式如下

 [秒] [分] [時] [日] [月] [周] [年]
 Seconds  Minutes  Hours   Day-of-Month  Month   Day-of-Week	Year (optional field)
是否必填 值以及範圍 萬用字元
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * ? / L W
1-12 或 JAN-DEC , - * /
1-7 或 SUN-SAT , - * ? / L #
1970-2099 , - * /

需要說明的是,Cron 表示式中,“周” 是從週日開始計算的。“周” 域上的 1 表示的是週日,7 表示週六。

每天晚上12點觸發任務:0 0 0 * * ?

每隔 1 分鐘執行一次:0 */1 * * * ?

每月 1 號凌晨 1 點執行一次:0 0 1 1 * ?

每月最後一天 23 點執行一次:0 0 23 L * ?

每週週六凌晨 3 點實行一次:0 0 3 ? * L

在24分,30分執行一次:0 24,30 * * * ?

是不是有點沒看懂,沒關係,我們可以使用Cron表示式生成器幫助我們生成Cron表示式

SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
        .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?"))
        .build();

7.@Schedule實現定時任務

很多時候我們都需要為系統建立一個定時任務來幫我們做一些事情,SpringBoot 已經幫我們實現好了一個,我們只需要直接使用即可

一、引入依賴

<dependencies>

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
 </dependency>

 <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
 </dependency>

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
 </dependency>

</dependencies>

二、開啟註解

在 SpringBoot 中我們只需要在啟動類上加上@EnableScheduling便可以啟動定時任務了。

@SpringBootApplication
@EnableScheduling
public class TaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

三、建立scheduled task

import org.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;
import java.util.concurrent.TimeUnit;

/**
 * @author wugongzi
 */
@Component
public class ScheduledTasks {
    private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    /**
     * fixedRate:固定速率執行。每5秒執行一次。
     */
    @Scheduled(fixedRate = 5000)
    public void reportCurrentTimeWithFixedRate() {
        log.info("Current Thread : {}", Thread.currentThread().getName());
        log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
    }

    /**
     * fixedDelay:固定延遲執行。距離上一次呼叫成功後2秒才執。
     */
    @Scheduled(fixedDelay = 2000)
    public void reportCurrentTimeWithFixedDelay() {
        try {
            TimeUnit.SECONDS.sleep(3);
            log.info("Fixed Delay Task : The time is now {}", dateFormat.format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * initialDelay:初始延遲。任務的第一次執行將延遲5秒,然後將以5秒的固定間隔執行。
     */
    @Scheduled(initialDelay = 5000, fixedRate = 5000)
    public void reportCurrentTimeWithInitialDelay() {
        log.info("Fixed Rate Task with Initial Delay : The time is now {}", dateFormat.format(new Date()));
    }

    /**
     * cron:使用Cron表示式。 每分鐘的1,2秒執行
     */
    @Scheduled(cron = "1-2 * * * * ? ")
    public void reportCurrentTimeWithCronExpression() {
        log.info("Cron Expression: The time is now {}", dateFormat.format(new Date()));
    }
}

啟動專案便可以看到效果。

相關文章