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的核心概念,方便我們後面學習
-
Job 表示一個工作,要執行的具體內容。此介面中只有一個方法,如下:
void execute(JobExecutionContext context) // context是重要的上下文,可以訪問到關聯的JobDetail物件和本次觸發的Trigger物件,以及在此之上設定的資料。
-
JobDetail 表示一個具體的可執行的排程程式,Job 是這個可執行程排程程式所要執行的內容,另外 JobDetail 還包含了這個任務排程的方案和策略。
-
Trigger 代表一個排程引數的配置,什麼時候去調。
-
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()));
}
}
啟動專案便可以看到效果。