定時任務——quartz
阿新 • • 發佈:2022-11-30
基本使用
官網:http://www.quartz-scheduler.org/
先理解quartz的模型關係
一個排程器可以排程多個觸發器
一個觸發器只可以觸發一個任務詳情
一個任務詳情可以被多個觸發器呼叫
一個Job可以被多個任務詳情包含(一般情況下不會用到這種場景)
一般會定義兩種觸發器,一種是自動觸發器,一種是手動觸發器
跟著官網文件走,匯入資料庫,然後就可以進行案例實踐了
這裡引入一個logback配置,方便檢視日誌資訊
<?xml version="1.0" encoding="UTF-8" ?> <configuration debug="false" scan="false"> <!-- Console log output --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <!-- WARN級別 --> <root level="WARN"> <appender-ref ref = "console"/> </root> </configuration>
官網上的Demo
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import java.util.Date; import java.util.StringJoiner; /** * Author: gzy * Date: 2022/11/22-10:52 * Description: * quartz中的job都要實現Job介面 * quartz所有的定時任務都要實現Job介面 */ public class HelloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { StringJoiner outStr = new StringJoiner(" ") .add("Hello.execute") .add(new Date().toString()) .add("======" + Thread.currentThread().getName()) .add(jobExecutionContext.getTrigger().getKey().getName()); // 觸發器的名稱 // System.out.println("HelloJobExecute: " + new Date() + "======" + Thread.currentThread().getName()); System.out.println(outStr); } }
/** * Author: gzy * Date: 2022/11/22-10:41 * Description: * 排程器 ->>> 觸發器 ->>> 任務詳情(任務) * 為什麼中間有一層觸發器 */ public class QuartzTest { public static void main(String[] args) { try { // Grab the Scheduler instance from the Factory // 排程器工廠獲取一個預設的排程器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // and start it off // 啟動 scheduler.start(); // define the job and tie it to our HelloJob class // JobDetail job = newJob(HelloJob.class) .withIdentity("job1", "group1") .build(); // Trigger the job to run now, and then repeat every 40 seconds // 觸發 jobDetail 觸發器 Trigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startNow() // 啟動 .withSchedule(simpleSchedule() // 排程策略 .withIntervalInSeconds(5) // 5s一次 .repeatForever()) // 永遠迴圈下去 .build(); // Tell quartz to schedule the job using our trigger // job 被 排程器排程 scheduler.scheduleJob(job, trigger); // 主執行緒先睡一下, 以看到程式的效果 TimeUnit.SECONDS.sleep(20); // 關閉 scheduler.shutdown(); } catch (SchedulerException | InterruptedException se) { se.printStackTrace(); } } }
接下來的案例驗證排程器、觸發器、作業詳情之間的關係
兩個觸發器呼叫一個JobDetail
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* Author: KongXiao
* Date: 2022/11/22-10:41
* Description:
* 排程器 ->>> 觸發器 ->>> 任務詳情(任務)
* 問什麼中間有一層觸發器
* 一對多的關係 一個排程器 多個觸發器; 多個觸發器 一個任務詳情
* 一個觸發器只能排程一個任務
( 一個 Job 可以被多個 JobDetail 引用
*/
public class QuartzTest2 {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 排程器工廠獲取一個預設的排程器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 啟動
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 觸發 jobDetail 觸發器
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
Trigger trigger2 = newTrigger()
.withIdentity("trigger2", "group1")
.startNow() // 啟動
.forJob("job1", "group1") // 指定 Job name 和 group,指定要觸發的 job
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
// job 被 排程器排程
scheduler.scheduleJob(job, trigger);
scheduler.scheduleJob(trigger2); // 如果是一個未被排程過的Job,使用 forJob的方式,然後排程器想要排程它,但是隻傳了一個觸發器,會出異常
// 主執行緒先睡一下
TimeUnit.SECONDS.sleep(3);
// 關閉
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
兩個排程器呼叫兩個作業詳情
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* Author: KongXiao
* Date: 2022/11/22-10:41
* Description:
* 排程器 ->>> 觸發器 ->>> 任務詳情(任務)
* 問什麼中間有一層觸發器
* 一對多的關係 一個排程器 多個觸發器; 多個觸發器 一個任務詳情
* 一個觸發器只能排程一個任務
* 一個 Job 可以被多個 JobDetail 引用
*
* 一個排程器對應倆觸發器,一個觸發器對應一個jobDetail,一個jobDetail可以被多個觸發器呼叫,一個JobDetail包含一個Job
*/
public class QuartzTest3 {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 排程器工廠獲取一個預設的排程器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 啟動
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
JobDetail job2 = newJob(HelloJob.class) // 兩個JobDetail包含了一個Job
.withIdentity("job2", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 觸發 jobDetail 觸發器
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
Trigger trigger2 = newTrigger()
.withIdentity("trigger2", "group1")
.startNow() // 啟動
.forJob("job2", "group1") // 指定 Job name 和 group,指定要觸發的 job
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
// job 被 排程器排程
scheduler.scheduleJob(job, trigger);
scheduler.scheduleJob(job2, trigger2); // 如果只傳入一個觸發器,會出異常
// scheduler.scheduleJob(trigger2); // org.quartz.JobPersistenceException: The job (group1.job2) referenced by the trigger does not exist.
// 主執行緒先睡一下
TimeUnit.SECONDS.sleep(3);
// 關閉
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
簡單理解一下JobDetail和Trigger的name屬性和group屬性
name屬性主要是用來方便標識的
group屬性主要用來方便管理和標識的
多個作業可以放在一個群組下面進行管理
可以不為group賦值,但是底層會為其賦一個 DEFAULT 值
只有一個JobDetail和一個Trigger時,可以不指定name和group,如下
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* Author: KongXiao
* Date: 2022/11/22-10:41
* Description:
* 排程器 ->>> 觸發器 ->>> 任務詳情(任務)
* 問什麼中間有一層觸發器
* 一對多的關係 一個排程器 多個觸發器; 多個觸發器 一個任務詳情 一個觸發器只能排程一個任務
* 一個 Job 可以被多個 JobDetail 引用
*
*
* JobDetail和Trigger的name和group的作用:
* name是用來標記的
* group是用來標記、方便管理的(比如,一個群組下也可以有多個job)
* 可以不為 group賦值,那麼 group底層會賦 DEFAULT 值
*
* 如果只有一個觸發器、一個JobDetail,那麼可以不指定name和group,可以試一試, 那麼此時執行緒名是MD5加密的串
*
*
* 那麼現在的模型大概是這樣的
* 一個排程器可以呼叫多個觸發器
* 一個觸發器只可以呼叫一個任務詳情
* 一個任務詳情可以被多個觸發器觸發
* 一個任務可以被多個任務詳情包含
*
* 一般情況下,不會使用多個JobDetail去包含一個Job的場景,會編寫兩種觸發器,一個自動觸發器,一個手動觸發器.
*/
public class QuartzTest4 {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 排程器工廠獲取一個預設的排程器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 啟動
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
// .withIdentity("job1", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 觸發 jobDetail 觸發器
Trigger trigger = newTrigger()
// .withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
// .repeatForever())
.withRepeatCount(1)) // 指定重複執行的次數
.build();
// Tell quartz to schedule the job using our trigger
// job 被 排程器排程
scheduler.scheduleJob(job, trigger);
// 主執行緒先睡一下
TimeUnit.SECONDS.sleep(3);
// 關閉
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
幾個觸發器
CalendarIntervalTriggerImpl (org.quartz.impl.triggers)
SimpleTriggerImpl (org.quartz.impl.triggers)
DailyTimeIntervalTriggerImpl (org.quartz.impl.triggers)
CronTriggerImpl (org.quartz.impl.triggers) 重點、常用!!!
CronExpression cron表示式
Cron表示式生成網站 https://cron.qqe2.com/
...
傳參以及依賴注入
Job的需要一個無參構造方法才可以被例項化,Job的例項化不是我們建立的,是quartz去幫我們實現的
傳入一個Job.class到newJob中可以創建出一個Job的例項
在Job例項化的時候傳參可以從JobDetail和Trigger下手,呼叫usingJobData()方法,底層是個Map。在Job中取值,可以從Job上下文中呼叫 getJobDetail()/getTrigger().getJobDataMap().get(key);
getMergedJobDataMap()可以獲取合併後的資料,如果JobDetail和Trigger的key相同,那麼會以Trigger的key優先,獲取到的就是Trigger相應的值
Job中定義一個變數並定義相應的set方法,也可以獲取值
如果在Job中注入Spring中的Bean該怎麼操作呢?
Spring的Bean是由Spring管理的,而quartz是不認這個的,所以直接在Job中注入依賴是獲取不到值的
@Component
public class SpringDIUtils implements ApplicationContextAware {
public static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Job中獲取值
xxx = (xxx) SpringDIUtils.applicationContext.getBean(StringUtils.uncapitalize(xxx.class.getSimpleName()));
quartz的配置檔案
quartz的配置檔案是quartz.properties,如果不自行配置的話會預設使用自帶的配置檔案, 在 quartz的jar包的 org.quartz包下
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore